202506

wget 使用代理

wget -qO- -e use_proxy=yes -e http_proxy=http://127.0.0.1:9080 ifconfig.me/all.json | jq
wget --debug -e use_proxy=yes -e http_proxy=http://127.0.0.1:9080

新建 .net 项目

dotnet new winforms -o WawaEditor
cd .\WawaEditor\
dotnet run

如何设计一个文本编辑器可以打开和编辑超大文本文件

好的,这是一个非常经典且有深度的问题。设计一个能够用较小内存打开、编辑超大文本文件(例如几个 GB 到几十 GB)的文本编辑器,其核心思想是**“分而治之”“虚拟化”**。绝对不能将整个文件一次性读入内存。

下面我将从基本思路到高级数据结构,为你梳理几个关键的设计思路。

核心挑战与错误思路

  • 核心挑战:一个 10GB 的文件远远超出了典型应用程序的内存分配能力,也超出了单个字符串或对象所能容纳的极限。
  • 错误思路:调用 File.ReadAllText()File.ReadAllLines()。这会尝试将整个文件加载到内存中,导致内存溢出(OutOfMemoryException)和程序崩溃。

思路一:文件索引 + 虚拟化视图 (最核心、最实用的方法)

这个思路将“数据模型”与“UI展现”完全分离,是所有高性能大文件编辑器的基础。

1. 建立行索引(Pre-indexing)

在打开文件时,并不读取文件内容,而是快速扫描一遍文件,建立一个行位置索引

  • 做法
    1. 打开一个文件的只读流 (FileStream)。
    2. 创建一个列表,例如 List<long>,用于存储每一行在文件中的起始位置(字节偏移量)。
    3. 从头到尾读取文件流(可以按一个较大的缓冲区,如 64KB,来提高效率),但不保存内容。
    4. 每当检测到一个换行符 (\n),就将紧随其后的字节的偏移量(stream.Position)记录到索引列表中。
  • 结果:你会得到一个相对很小的索引。例如,一个 10GB 的文件有 1 亿行,每行起始位置用一个 long (8字节) 存储,索引本身大约只需要 100,000,000 * 8 bytes ≈ 800 MB 的内存。这虽然也不小,但相比 10GB 已经是可以管理的了。

2. 实现虚拟化 UI(UI Virtualization)

UI 控件(无论是 WinForms 的 TextBox 还是自定义绘制的面板)永远只加载和渲染当前屏幕上可见的几十行文本。

  • 做法
    1. UI 控件维护一个“总行数”的属性,这个值就等于前面建立的行索引的长度。这让滚动条可以正确地表现出文件的总大小。
    2. 当用户滚动滚动条时,根据滚动条的位置计算出当前应该显示的起始行号(例如 line 1,000,000)。
    3. 程序使用行索引,找到第 1,000,000 行在文件中的字节偏移量。
    4. 使用 FileStream.Seek() 方法将文件指针直接移动到该位置。
    5. 从该位置开始,只读取屏幕上需要显示的几十行数据到内存缓冲区中,然后将其渲染到屏幕上。

3. 实现数据缓存(Data Caching)

为了让滚动体验更流畅,不应该只加载屏幕可见的行。

  • 做法:可以维护一个内存中的“窗口”或“缓冲区”,比如总是加载当前可视区域前后各 1000 行的数据。当用户滚动时,只要目标行在缓存中,就不需要进行磁盘 I/O,只有当滚动到缓存区域之外时,才去磁盘读取并更新缓存。

思路二:处理编辑操作 (最复杂的部分)

查看是只读操作,而编辑会改变文件内容和结构,这要复杂得多。

1. 保持源文件只读,使用变更日志(Change Log)

直接修改磁盘上的大文件是非常低效的,因为插入或删除一个字符都可能导致后续所有字节的移动。

  • 做法
    1. 在整个编辑会话中,始终保持原始的超大文件为只读状态
    2. 在内存中创建一个“变更日志”或“事务日志”。这个日志可以是一个字典,例如 Dictionary<int, string>,其中 Key 是行号,Value 是被修改后的新行内容。
    3. 对于修改操作:当用户修改第 N 行时,将新的行内容存入变更日志。
    4. 对于删除操作:在变更日志中标记第 N 行已被删除。
    5. 对于插入操作:在变更日志中记录在第 N 行之后插入了哪些新行。
  • 渲染逻辑:当需要显示第 N 行时,程序首先检查变更日志。
    • 如果日志中有第 N 行的修改/插入记录,则显示日志中的内容。
    • 如果日志中没有,则通过行索引从原始文件中读取并显示。

2. 文件保存策略

  • 另存为 (Save As):这是最简单的保存方式。程序创建一个新文件,然后从头到尾遍历所有行(根据原始索引和变更日志),将最终的内容逐行写入新文件。
  • 保存 (Save):安全的做法是先执行“另存为”到一个临时文件。如果临时文件写入成功,则删除原始文件,并将临时文件重命名为原始文件名。这能保证即使在保存过程中断电或程序崩溃,原始文件也不会损坏。

思路三:使用高级数据结构(优化编辑性能)

当插入和删除操作非常频繁时,简单地维护一个变更日志会变得复杂且低效。这时可以引入专门为文本编辑设计的数据结构。这些结构将文件内容切分成小块,使得中间的修改不会影响到整个数据。

1. Rope (绳索)

  • 概念:Rope 是一种二叉树结构,它的叶子节点是小的文本块(字符串),而中间节点则表示将它的子节点连接起来。
  • 优点
    • 快速编辑:在文本中间插入或删除内容,通常只需要修改树的几个节点,而不需要像数组一样移动大量内存。
    • 内存高效:多个 Rope 可以共享未被修改的子树节点,非常适合实现“撤销/重做”(Undo/Redo)功能。
  • 实现:将大文件切分成许多小的 Rope 叶子节点(例如每 64KB 一个),然后构建一棵 Rope 树来表示整个文件。所有编辑操作都在这棵树上进行。

2. Gap Buffer (间隙缓冲区)

  • 概念:这是一个在内存中维护的、包含一个“间隙”(Gap)的字符数组。光标所在的位置就是间隙所在的位置。
  • 优点
    • 打字速度极快:用户在光标处输入字符,只是简单地在间隙中填入字符并移动间隙的边界,非常高效。
    • 非常适合用户通常在同一位置进行连续编辑的场景。
  • 缺点:当光标大幅度跳跃时,需要移动整个间隙,这会涉及大量的内存复制,性能会下降。

总结与架构设计

一个强大的大文件文本编辑器通常是上述思路的结合体。

简化架构图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+--------------------------------+
| UI Layer | <-- 用户交互 (滚动, 输入)
| (Virtualized Text Control) |
+--------------------------------+
^
| (请求数据 / 发送编辑命令)
v
+--------------------------------+
| Editor Logic / Buffer | <-- 核心逻辑层
| Manager |
+--------------------------------+
| |
(读取/修改) | | (查找/修改)
v v
+---------------+ +-----------------+
| Data Model | | Change Log | <-- 内存中的数据结构
| (Rope/Gap/etc)| | (记录修改/删/增)|
+---------------+ +-----------------+
|
(按需读取) |
v
+--------------------------------+
| Original Large File on Disk | <-- 磁盘上的原始文件 (只读)
| (Accessed via FileStream) |
+--------------------------------+

总结一下关键点:

  1. 绝不一次性加载:这是第一原则。
  2. 建立索引:为快速定位行提供基础。
  3. UI虚拟化:只渲染屏幕上可见的内容。
  4. 编辑与数据分离:使用变更日志或高级数据结构来处理修改,不直接碰触原始数据。
  5. 安全保存:通过“另存为”+“重命名”的策略保证文件安全。

AI

  1. 初学者的生成式人工智能:
  • 生成式人工智能和语言模型的介绍
  • 探索和比较不同的语言模型
  • 负责任地使用生成式人工智能
  • 理解提示工程的基础知识

https://microsoft.github.io/generative-ai-for-beginners/

  1. 微软 - 初学者的人工智能:

探索这24个有趣课程:

  • 符号式人工智能
  • 神经网络
  • 计算机视觉
  • 自然语言处理
  • 和更多

通过动手实战项目、互动测验和实际实验,加强你的学习。

https://microsoft.github.io/AI-For-Beginners/

  1. 生成式人工智能:周到在线搜索的进化
  • 在线搜索信息的进化

  • 改变我们搜索和共享信息的方式

你将掌握的技能:

  • 生成式人工智能
  • 先进的搜索引擎技术

https://www.linkedin.com/learning/generative-ai-the-evolution-of-thoughtful-online-search/harness-the-power-of-prompt-engineering

Markdown PPT

为开发者打造的演示文稿工具
https://cn.sli.dev/

pyenv

curl https://pyenv.run | bash

pyenv install 3.7.10
pyenv virtualenv 3.7.10 gector 
pyenv activate gector 

pip install --upgrade pip setuptools wheel
pip install jsonnet==0.17.0
pip install -r requirements.txt --prefer-binary


pyenv versions
pyenv uninstall <version>
pyenv virtualenvs

setuptools wheel 这两个是干啥的,python没有自带吗?

工具 作用
setuptools 用于 打包和构建 Python 包(生成 .egg / .tar.gz
wheel 用于 构建和安装 .whl 文件(一种更快的包格式)
  • 几乎所有 pip 安装的包都依赖 setuptools 来“打包”它们自己
  • wheel 是 Python 官方推荐的现代包格式(.whl 文件)
    • 安装快(无需编译),跨平台可用(只要匹配 Python/OS 架构)
    • 使用 pip install --only-binary :all: some_package
  • Python 安装时自带了 pip,通常会预装一个老版本的 setuptools 和 wheel
    • 有些系统精简安装会没有 wheel
    • 很多编译失败问题都是因为这俩太旧或缺失
    • 建议每次建新环境后运行 pip install --upgrade pip setuptools wheel

unshre

unshare 是 Linux 下一个非常强大的命令行工具,用于 创建新的命名空间(namespace)并在其中运行命令。这使你可以在某些隔离的环境下执行程序,比如隔离网络、挂载点、PID 等,类似于容器的某些功能。

工具/系统调用 作用
unshare 创建新的命名空间(如 mount、PID、network、user 等),用于隔离进程资源视图。
chroot 更改当前进程的“根目录”,限制进程访问其他目录,是一种文件系统层面的隔离手段
pivot_root 把新的挂载点作为根文件系统,原根变成子目录,是更强的 rootfs 替换方式,常用于 init 或容器。

实现了一个隔离的运行环境

unshare --mount --uts --ipc --net --pid --fork bash
mount --bind /myroot /myroot
chroot /myroot /bin/bash

好的!下面我将为你提供一个完整的 Bash 脚本,它使用 unshare + chroot 构建一个类容器(轻量沙箱)环境,你可以在里面运行独立命令,和宿主系统隔离。


🧱 脚本目标

  • 创建新的命名空间(mount、PID、UTS、IPC、net)
  • 使用 unshare 隔离这些命名空间
  • 使用 chroot 切换到一个你准备好的根文件系统(比如一个最小的 BusyBox 或 Debian 系统目录)
  • 自动挂载 /proc 和其他必要目录
  • 模拟容器效果

🧾 脚本内容:sandbox.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

set -e

# 1. 指定你准备的 rootfs 路径(提前准备 busybox、debian 根目录等)
ROOTFS=/sandbox/rootfs

# 2. 检查 root 权限
if [ "$EUID" -ne 0 ]; then
echo "请用 root 运行这个脚本"
exit 1
fi

# 3. 必须的目录结构
mkdir -p $ROOTFS/{proc,sys,dev,tmp}

# 4. 创建沙箱:新命名空间 + chroot + /proc 挂载
unshare --mount --uts --ipc --net --pid --fork bash <<EOF
# 挂载 /proc
mount -t proc proc $ROOTFS/proc

# 可选:挂载 dev
mount --bind /dev $ROOTFS/dev
mount --bind /sys $ROOTFS/sys

# 设置主机名
hostname sandbox

# 切换 rootfs
cd $ROOTFS
chroot . /bin/bash

# 退出时卸载
umount $ROOTFS/proc
umount $ROOTFS/dev
umount $ROOTFS/sys
EOF

精简

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
set -e
ROOTFS=/sandbox/rootfs
mkdir -p $ROOTFS/{proc,sys,dev,tmp}
unshare --mount --uts --ipc --net --pid --fork bash -c "
mount -t proc proc $ROOTFS/proc
mount --bind /dev $ROOTFS/dev
mount --bind /sys $ROOTFS/sys
hostname sandbox
exec chroot $ROOTFS /bin/sh
"

📦 如何准备 $ROOTFS

方法一:用 busybox 最小系统

1
2
3
4
5
6
7
mkdir -p /sandbox/rootfs
cd /sandbox/rootfs
cp /bin/busybox ./bin/
# 创建符号链接
for cmd in sh ls mount umount echo cat; do
ln -s /bin/busybox ./bin/$cmd
done

方法二:用 Debian 制作 rootfs(推荐)

1
debootstrap --arch=amd64 stable /sandbox/rootfs http://deb.debian.org/debian/

🧪 使用方式

1
2
sudo chmod +x sandbox.sh
sudo ./sandbox.sh

你会进入一个完全隔离的 bash,你在里面看不到宿主的进程、网络、主机名,甚至可以模拟 root(如果使用 --user)。


🚀 进阶(添加用户命名空间)

想用非 root 用户运行(模拟 root 权限):

1
unshare --user --mount --uts --ipc --net --pid --fork --map-root-user bash

需要额外做 UID/GID 映射,这可以再扩展支持。


如果你告诉我你想基于什么系统(busybox、Debian、Ubuntu)或你的使用场景(如运行 Node、Python、curl、网络实验、进程沙箱),我可以为你生成更详细、更高级的容器脚本。是否继续?

太好了,恭喜你成功运行了沙箱环境!👏
现在你想把宿主机上的 tree 命令“提供”给沙箱使用 —— 也就是 把它连同依赖复制进沙箱 rootfs,这样在 chroot 后可以运行 tree


✅ 步骤概览

要让沙箱内能用 tree 命令,你需要:

  1. 找出 tree 可执行文件的位置;
  2. 找出它依赖的共享库(.so 文件);
  3. 把这些文件复制到沙箱的 rootfs 中对应的位置;
  4. 在沙箱内运行验证。

🛠️ 操作步骤(以 Ubuntu 为例)

🔍 1. 查找 tree 路径

1
which tree

输出示例:

1
/usr/bin/tree

🔍 2. 查看依赖库

1
ldd /usr/bin/tree

输出示例:

1
2
3
linux-vdso.so.1 =>  (0x00007ffce45f4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f158ae00000)
/lib64/ld-linux-x86-64.so.2 (0x00007f158b200000)

📦 3. 创建目标路径并复制文件

假设你的 $ROOTFS=/sandbox/rootfs

1
2
3
4
5
6
# 可执行文件
cp /usr/bin/tree $ROOTFS/usr/bin/

# 共享库
cp /lib/x86_64-linux-gnu/libc.so.6 $ROOTFS/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 $ROOTFS/lib64/

注意:libc.so.6ld-linux 的路径和平台有关,请以你的系统为准。


⛑️ 4. 如目录不存在,先创建它:

1
2
3
mkdir -p $ROOTFS/usr/bin
mkdir -p $ROOTFS/lib/x86_64-linux-gnu
mkdir -p $ROOTFS/lib64

✅ 5. 进入沙箱验证

进入你之前配置好的沙箱:

1
sudo ./sandbox.sh

然后在沙箱中运行:

1
tree

你应该能看到正常输出。


🧠 小提示:写个通用复制函数

你可以写个函数批量复制任何命令和其依赖,比如:

1
2
3
4
5
6
7
8
9
10
11
12
copy_command_to_rootfs() {
CMD=$1
DEST=$2
BIN=$(which $CMD)

echo "Copying $CMD from $BIN"
cp --parents "$BIN" "$DEST"

for LIB in $(ldd $BIN | awk '{print $3}' | grep -v '^$'); do
cp --parents "$LIB" "$DEST"
done
}

使用方式:

1
2
3
copy_command_to_rootfs tree /sandbox/rootfs
copy_command_to_rootfs ls /sandbox/rootfs
copy_command_to_rootfs ps /sandbox/rootfs

✅ 总结

步骤 命令
查找命令位置 which tree
查找依赖 ldd /usr/bin/tree
复制文件 cp 可执行文件和依赖库
进入沙箱 sudo ./sandbox.sh
运行验证 tree

这是一个非常好的问题,说明你在深入理解 Linux 沙箱或容器底层原理。
我们之所以在沙箱(chroot + unshare)环境中挂载 /proc/dev/sys,是为了让沙箱内的程序能够正常运行、获取系统信息,模拟“更真实的 Linux 环境”。

下面我来详细解释这三个挂载点的意义和用途:


✅ 1. /proc虚拟的进程信息文件系统

  • /proc 是一个由内核提供的虚拟文件系统,不是真正存在的文件,而是系统运行时动态生成的内容。
  • 它包含了进程信息(如 /proc/[pid]/status)、内核配置、CPU、内存等信息。

🚀 如果不挂载 /proc

  • ps, top, htop, free, uptime, cat /proc/cpuinfo 等命令将无法工作;
  • 会报错:procfs not mounted, No such file or directory, ps: command failed.

✅ 沙箱中挂载示例:

1
mount -t proc proc /sandbox/rootfs/proc

✅ 2. /dev设备节点目录

  • /dev 包含各种设备文件,例如:

    • /dev/null, /dev/zero, /dev/random
    • /dev/tty, /dev/pts/*(终端)
    • /dev/sda, /dev/loop0(磁盘)
  • 许多命令和 shell 交互都会依赖这些设备节点。

🚀 如果不挂载 /dev

  • 很多命令会报错,例如:

    • ls 输出时写不到 /dev/tty
    • 程序访问 /dev/null 失败
    • 无法输入密码、不能交互

✅ 挂载方式(复制宿主 /dev 进沙箱):

1
mount --bind /dev /sandbox/rootfs/dev

⚠️ 注意:这不是“完全隔离的设备”,只是复用了宿主 /dev,安全性适中。


✅ 3. /sys内核对象模型文件系统(sysfs)

  • /sys 是另一种虚拟文件系统,用于查看和配置内核设备与模块信息,如:

    • /sys/class/net(网卡信息)
    • /sys/block(磁盘信息)
    • /sys/kernel(内核状态)
  • 很多系统工具(如 udevadm, ip, lsblk, systemd)都依赖它。

🚀 如果不挂载 /sys

  • ip, ifconfig, udevadm, lsblk 等命令会失效;
  • 可能报错:sysfs not mounted, cannot read /sys/...

✅ 挂载方式:

1
mount --bind /sys /sandbox/rootfs/sys

🎯 总结:为什么挂这三大目录?

目录 类型 作用/必要性 如果不挂会怎样
/proc 虚拟 fs 提供进程/内核/内存等信息 ps, top, free 等都失效
/dev 设备节点 提供基本输入/输出、tty、null 等设备支持 无法交互、输出失败、命令报错
/sys 虚拟 fs 提供内核/硬件/网络等信息 ip, lsblk, udevadm 无法使用

🧠 想象一下如果你用 Docker

你运行一个 Docker 容器时,其底层也会挂载这些内容(可能是隔离版或 bind mount):

1
docker run -it ubuntu

Docker 就会自动挂载:

  • --mount type=proc
  • --mount type=sysfs
  • --mount /dev(部分绑定)

✅ 补充建议

如果你想提高安全性,也可以选择只挂一些最小需要的设备(比如 /dev/null, /dev/tty)而不是整挂 /dev,甚至配合 devtmpfsudevd 模拟完整环境。

links

ChatGPT技术原理解析
https://zhuanlan.zhihu.com/p/618320262

OpenAI ChatGPT(三):十分钟读懂 GPT-2
https://zhuanlan.zhihu.com/p/613895056

ck

ck distinct 爆内存,可以设置不同的 distinct 算法

SET count_distinct_implementation = 'uniq';

SELECT countDistinct(xxx)
FROM data 
WHERE xxx != '';
实现名 描述
uniq 使用 HyperLogLog++,近似但非常快
uniqCombined 平衡准确性和内存
uniqExact 完全精确,但慢
uniqHLL12 12-bit HyperLogLog(近似)

向量的 L1,L2,L∞ 范数的作用

向量的 L1 范数L2 范数L∞(无穷)范数 是衡量向量“大小”或“长度”的不同方式。它们在数学、机器学习、优化等领域中有广泛用途。下面我分别介绍它们的定义、直观理解和典型应用:


✅ 1. L1 范数(曼哈顿距离)

定义:

$$
|x|1 = \sum{i=1}^{n} |x_i|
$$

即所有分量的绝对值之和

几何理解:

  • 表示从原点到点 $x$ 的“曼哈顿距离”。
  • 在二维平面上,等 L1 范数的点集是一个菱形。

作用 / 应用:

  • 稀疏性:L1 范数常用于稀疏建模(如 Lasso 回归),能使一些系数变为0,达到特征选择的效果。
  • 在某些优化问题中,L1 范数约束比 L2 更容易生成稀疏解。

✅ 2. L2 范数(欧几里得范数)

定义:

$$
|x|2 = \sqrt{\sum{i=1}^{n} x_i^2}
$$

即向量的平方和的平方根

几何理解:

  • 这是我们最熟悉的“距离”概念。
  • 在二维平面上,等 L2 范数的点集是一个圆。

作用 / 应用:

  • 稳定性强:L2 范数会平滑参数,不会像 L1 那样强制为0,适合保留所有特征。
  • 用于岭回归(Ridge),可以缓解共线性问题。
  • 常用于损失函数,如 MSE(均方误差)中的平方项就是 L2 范数的平方。

✅ 3. L∞ 范数(无穷范数 / 最大范数)

定义:

$$
|x|\infty = \max{i} |x_i|
$$

即向量中绝对值最大的分量

几何理解:

  • 在二维平面上,等 L∞ 范数的点集是一个正方形(轴对齐)。

作用 / 应用:

  • 最大误差控制:适用于当你想限制某个变量偏差不能太大,比如鲁棒优化、控制最大误差。
  • 有时用于神经网络权重的约束,确保每个权重不超过某个值。

✅ 对比总结

范数 数学表达 特点和作用 常见应用
L1 ( \sum x_i ) 稀疏、易于特征选择 Lasso、稀疏编码
L2 $\sqrt{\sum x_i^2}$ 平滑、稳定,强调整体误差 Ridge 回归、MSE、神经网络
L∞ ( \max x_i ) 控制单个最大误差、注重极端值 鲁棒优化、最大误差约束

最大似然估计法的思想是使训练数据出现的概率最大化,依此确定概率分布中的未知参数,估计出的概率分布也就最符合训练数据的分布。最大后验概率法的思想则是根据训练数据和已知的其他条件,使未知参数出现的可能性最大化,并选取最可能的未知参数取值作为估计值。
解释这段话

这段话是在对比两种常见的统计推断方法
最大似然估计(Maximum Likelihood Estimation, MLE)
最大后验概率估计(Maximum A Posteriori Estimation, MAP)


我们来逐句拆解和解释这段话:


🔹 1. 「最大似然估计法的思想是使训练数据出现的概率最大化,依此确定概率分布中的未知参数」

解释:

  • 给定一组观测数据(训练数据),我们假设这些数据是从某种分布 $P(x \mid \theta)$ 中产生的,$\theta$ 是我们想估计的参数。
  • 最大似然的目标是:找一个参数 $\theta$,使得在这个参数下,整个训练数据出现的可能性最大

公式上写作:

$$
\hat{\theta}{\text{MLE}} = \arg\max{\theta} P(D \mid \theta)
$$

其中 $D$ 是观测数据,$P(D \mid \theta)$ 是数据在参数 $\theta$ 下出现的“似然函数”。

所以你可以理解为:

MLE 用模型解释已有数据,并找一个最可能“生成”这些数据的参数。


🔹 2. 「估计出的概率分布也就最符合训练数据的分布。」

解释:

MLE 得到的参数 $\hat{\theta}$,是最可能生成训练数据的那个参数,因此它构建出的分布 $P(x \mid \hat{\theta})$ 就最接近数据分布的形状(在模型允许的范围内)。


🔹 3. 「最大后验概率法的思想则是根据训练数据和已知的其他条件,使未知参数出现的可能性最大化」

解释:

这时你不再只根据数据来估计参数,还考虑参数本身可能的先验分布 $P(\theta)$。

也就是说,你相信:

参数 $\theta$ 并不是完全未知的,它服从某个先验概率分布。

MAP 的目标是:

$$
\hat{\theta}{\text{MAP}} = \arg\max{\theta} P(\theta \mid D)
$$

也就是:

找出在给定数据 $D$ 之后,参数 $\theta$ 出现概率(后验概率)最大的那个取值。

根据贝叶斯公式:

$$
P(\theta \mid D) \propto P(D \mid \theta) \cdot P(\theta)
$$

所以:

$$
\hat{\theta}{\text{MAP}} = \arg\max{\theta} P(D \mid \theta) \cdot P(\theta)
$$

✅ 可见:MAP = MLE × 先验
(如果先验是均匀分布,MAP就退化成MLE)


🔹 4. 「并选取最可能的未知参数取值作为估计值。」

这就是“最大后验”的意义:

在考虑了数据和先验之后,最有可能的参数取值是谁?我们就选它作为我们的参数估计。


✅ 总结对比

方法 核心思想 数学形式 是否使用先验 典型场景
MLE 找一个参数,使训练数据的出现概率最大 $\arg\max_\theta P(D \mid \theta)$ ❌ 不使用 模型拟合、最大化似然
MAP 找一个参数,使它在观测数据下最有可能 $\arg\max_\theta P(D \mid \theta) P(\theta)$ ✅ 使用 有先验信息,希望正则化参数

🎯 类比理解

  • MLE 就像一个只看考试成绩的老师,谁考得高就选谁
  • MAP 就像一个考虑了“平时表现”的老师,不仅看这次考试,还看学生的日常表现(先验)

为什么P(D∣θ) 是个函数而P(x∣θ)是一种分布呢,不太理解

你这个问题非常关键,是很多人在学习统计推断时的“认知断层”之一。我们来逐步、深入但通俗地解释:


🎯 首先区分两个视角:

符号 是谁在变? 是谁被视为“已知” 本质
$P(x \mid \theta)$ x 是变量 $\theta$ 已知 这是一个分布(给定 $\theta$,x 的分布)
$P(D \mid \theta)$ θ 是变量 D(数据)已知 这是一个函数(θ 的函数),叫“似然函数

🧠 更直白地讲:

  • $P(x \mid \theta)$ 是“生成数据的模型”:

    给你一套参数(比如正态分布的均值和方差),那么 x 是如何分布的?我们就可以画出这个分布图。

  • $P(D \mid \theta)$ 是“观察了数据后,θ 的可能性如何变化”:

    数据是观察到的事实,我用它来评估每个 $\theta$ 对这份数据的解释能力有多强。它是 $\theta$ 的函数。


🔍 举个直观例子:硬币抛掷

你要估计一个硬币正面概率 $\theta$

  • 你观察到:抛了 10 次,结果是 7 次正面、3 次反面(数据 $D$)

A. 用 $P(x \mid \theta)$:描述模型

这是“给定θ,某次抛正面的概率是多少”:

$$
P(x = H \mid \theta) = \theta \
P(x = T \mid \theta) = 1 - \theta
$$

这是一个分布
每一个θ值,都会生成一个特定的抛硬币模型。


B. 用 $P(D \mid \theta)$:评价 θ 的可能性

这是似然函数,告诉你:如果正面概率是θ,出现“7正3反”的这个结果有多大可能?

$$
P(D \mid \theta) = \theta^7 (1 - \theta)^3
$$

你可以把这个看成是 θ 的函数,在 θ∈[0,1] 上作图,它的最大值对应最可能的 θ(这就是 MLE)。


✅ 所以你的问题是:

为什么 $P(x \mid \theta)$ 是个分布,而 $P(D \mid \theta)$ 是个函数?

✅ 答案:

  • 因为两者的“自变量”不同

    • 在 $P(x \mid \theta)$ 里,x 是变量,θ是固定的 → 所以它是“x 的概率分布”
    • 在 $P(D \mid \theta)$ 里,θ 是变量,D是固定的 → 所以它是“θ 的函数”,即似然函数

✅ 它们本质上是同一个公式,只是关注点不同:

名称 形式 谁是变量 用来干什么
概率分布 $P(x \mid \theta)$ x 用已知参数描述数据如何分布
似然函数 $\mathcal{L}(\theta) = P(D \mid \theta)$ θ 用已知数据估计参数θ

📌 总结口诀:

同一个公式,换个变量看主角,
x是变量就是分布,θ是变量就是似然!


如果你愿意,我可以画一个图,展示这个视角的“切换”过程,更直观。是否需要图示说明?

ck

clickhouse中如何每隔1000条数据取一条数据

1
2
3
4
5
6
7
8
SELECT *
FROM (
SELECT
*,
row_number() OVER () AS rn
FROM your_table
) AS sub
WHERE rn % 1000 = 1

ck中如何查看一个字段的分散度或基数?

1
2
3
4
5
SELECT
uniq(your_column) AS uniq_count,
count() AS total_count,
uniq(your_column) / count() AS dispersion_ratio
FROM your_table;
  • uniq 是 ClickHouse 提供的近似去重计数函数(基于 HyperLogLog),比 count(DISTINCT …) 快得多。
  • 如果需要更高精度,也可以使用 uniqExact(your_column)。
  • dispersion_ratio 越接近 1,说明字段越分散(越接近每行一个不同值)
  • 越接近 0,说明字段值高度集中(低基数)

python

wav to mp3

from pydub import AudioSegment

# 读取 WAV 文件
audio = AudioSegment.from_wav("output.wav")

# 导出为 MP3
audio.export("output.mp3", format="mp3")

shell

sudo apt-get install sox libsox-dev
sox input.wav output.mp3

ck

cityHash64和crc32的区别

特性 cityHash64 crc32
目标用途 哈希分片、采样、去重 数据完整性校验
位数 64 位 32 位
速度 极快(专为性能优化) 非常快
碰撞率 极低 高(不适合作为哈希键)
安全性 非加密,不可逆 非加密,不可逆
用于采样/分片 ✅ 推荐使用 ❌ 不推荐使用

我有一个crc32的列,如何分成100个均等的段儿,查看每个段的数据量?

SELECT
    intDiv(crc32_value, pow(2, 32) / 100) AS bucket,
    count(*) AS count
FROM your_table
GROUP BY bucket
ORDER BY bucket;

如何求出每个桶的数量的均值和标准差

SELECT
    avg(cnt) AS mean,
    stddevPop(cnt) AS stddev
FROM (
    SELECT
        intDiv(crc32_value, pow(2, 32) / 100) AS bucket,
        count(*) AS cnt
    FROM your_table
    GROUP BY bucket
) AS bucket_counts;
  • stddevPop(cnt):总体标准差(不是样本标准差);
  • 如果你更倾向于样本标准差,可用 stddevSamp(cnt)。

如何均匀的遍历一个cityhash64范围,用python,遍历的过程中调用一个函数,如果函数报错,则缩小本次遍历的范围,如果缩小后函数仍然报错,继续缩小,如果无法再缩小,则跳出循环。要做到不重不漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
MAX_UINT64 = 2**64 - 1
MIN_BLOCK_SIZE = 1000 # 最小粒度,防止无限分裂

def do_task(start, end):
"""
你自定义的函数,比如处理某个 cityHash64 范围
报错可以是内存、网络、查询失败等
"""
if end - start > 10**9:
raise RuntimeError("模拟处理失败")
print(f"Processed range: {start} ~ {end}")

def process_range(start, end):
try:
do_task(start, end)
except Exception as e:
if end - start <= MIN_BLOCK_SIZE:
print(f"Too small to split further: {start} ~ {end}, skipping. Error: {e}")
return
mid = (start + end) // 2
process_range(start, mid)
process_range(mid + 1, end)

def main():
STEP = 2**32 # 初始大步长
for bucket_start in range(0, MAX_UINT64, STEP):
bucket_end = min(bucket_start + STEP - 1, MAX_UINT64)
process_range(bucket_start, bucket_end)

if __name__ == "__main__":
main()