|
**自然语言处理(NLP)** 研究计算机如何"理解、解释和生成"人类语言。本质是一句话:
> 把一段**自然语言文本**作为输入,输出一种**机器可计算的结构化结果**。
NLP 任务可以形式化为 7 大类别(输入 → 输出):
| 任务类别 | 输入 → 输出 | 通俗例子 |
| --- | --- | --- |
| 文本分类 | 文本 → 类别 | "这家店服务差" → 差评(情感分析) |
| 序列标注 | 文本 → 每词一个标签 | "马云创办阿里" → 马云/人 阿里/组织(NER) |
| 文本生成 NLG | 文本 → 另一段文本 | 机器翻译 / 摘要 / ChatGPT 续写 |
| 文本理解 NLU | 文本+问题 → 答案 | 阅读理解、自然语言推理(蕴含/矛盾/中立) |
| 信息抽取 IE | 文本 → 三元组/事件 | "马云创立阿里" → (马云,创立,阿里) |
| 文本匹配 | 文本A+文本B → 相似度 | FAQ 问答检索 |
| 多模态 | 文本 ↔ 图像/语音 | 看图说话 |
> **难度提示**:NLI(蕴含/矛盾/中立)会卡壳——记住一句话:**NLI 判断"句子 A 能否推出 B"**。
|
|
无论是弹幕情感分类还是训练 ChatGPT,工程上都逃不开这条**九步流水线**(可类比**做一道菜**):
```
问题定义 → 数据获取 → 数据探索(EDA) → 清洗与预处理
→ 数据分割 → 特征工程 → 建模 → 评估 → 推理部署 → 线上跟踪优化
```
| 步骤 | 做菜类比 | 目标 |
| --- | --- | --- |
| 1 问题定义 | 决定做什么菜 | 业务需求→明确的输入/输出/指标 |
| 2 数据获取 | 买菜 | 内部库/公开数据集/爬虫/标注/增强 |
| 3 数据探索 EDA | 检查食材 | 统计+可视化"认识"数据 |
| 4 清洗预处理 | 洗菜切菜 | 去噪/分词/去停用词/标准化 |
| 5 数据分割 | 留一份试吃 | 训练/验证/测试集 |
| 6 特征工程 | 调成半成品 | 文本→数值向量(BoW/TF-IDF) |
| 7 建模 | 下锅炒 | 选择并训练模型 |
| 8 评估 | 尝咸淡 | Accuracy/F1 打分 |
| 9 推理部署 | 装盘上桌 | 封装 API + 监控 + A/B 测试 |
> 九步**不是直线,而是螺旋迭代**——建模后看 Bad Case,再回到 EDA。**高阶算法工程师都是业务专家。**
|
|
> 人工智能是**数据驱动**的科学:**Garbage in, garbage out.**
**数据获取 5 途径**:已有数据集(Kaggle / Papers with Code / Google Dataset Search)/ 网络爬虫(须合规)/ 数据标注(Label Studio)/ 数据增强(同义词替换、**回译**)/ 数据集成(Join 多表)。
**数据探索 EDA** 的核心是**学会对数据提问**:数据量?字段类型?分布?缺失/重复/异常?类别是否平衡?金句——**"模型是人脑对数据理解的映射"**。
EDA 常用维度:文本长度分布、类别分布、停用词分析、词频 & N-gram 分析、主题分析、情感/NER/词性探索、文本复杂度。
> **难度提示**:N-gram、主题模型、NER 在这里只是**"展示给你看数据"**,初学者知道"有这些工具能观察数据"即可,原理在第 2、3 课深入。
|
|
文本预处理(**不是顺序的、不是都必须的、要按任务定**):
```python
import re, pandas as pd
def clean_text(text):
# 清洗:去网址/邮箱/手机号脱敏/合并重复标点/压缩空格
if pd.isna(text): return ""
text = str(text)
text = re.sub(r'https?://[^\s]+', '', text) # 去网址
text = re.sub(r'1[3-9]\d{9}', '[手机号]', text) # 手机号脱敏
text = re.sub(r'([!?。,])\1+', r'\1', text) # 重复标点合并
return text
df = df.drop_duplicates(subset=['text'])
df['text'] = df['text'].apply(clean_text)
```
```python
import jieba
stop_words = {'的','了','是','在','我','有','和','就','不','都'}
def tokenize(text):
# 中文分词 + 去停用词 + 去单字/纯标点
return [w for w in jieba.lcut(text)
if w not in stop_words and len(w.strip()) > 1]
# 特征工程:TF-IDF(把文字变成数字)
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(max_features=2000, ngram_range=(1, 2), sublinear_tf=True)
X = tfidf.fit_transform([' '.join(tokenize(t)) for t in df['text']])
```
> **关键抽象跳跃**:计算机不认字只认数字,必须把文本变成**数值向量**。传统 ML 这一步至关重要;深度学习部分被 embedding 替代。
|
|
**建模**:先基线(朴素贝叶斯/逻辑回归)→ 集成(随机森林)→ 深度学习。用交叉验证 + GridSearch 选参:
```python
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.metrics import classification_report
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y)
model = MultinomialNB()
scores = cross_val_score(model, X_tr, y_tr, cv=5, scoring='accuracy') # 5 折交叉验证
model.fit(X_tr, y_tr)
print(classification_report(y_te, model.predict(X_te))) # 每类 P/R/F1
```
**关键公式**:
朴素贝叶斯(带拉普拉斯平滑,$|V|$ 为词表大小,$\alpha$ 常取 1):
$$\hat{y}=\arg\max_c P(c)\prod_{i=1}^{n}P(x_i\mid c), \quad P(x_i\mid c)=\frac{\text{count}(x_i,c)+\alpha}{\text{count}(c)+\alpha|V|}$$
TF-IDF(词在文档频次高 + 全语料稀有 ⇒ 最具代表性):
$$\mathrm{TF}(t,d)=\frac{\text{count}(t,d)}{|d|}, \quad \mathrm{IDF}(t)=\log\frac{N+1}{\mathrm{df}(t)+1}+1$$
分类评估(基于混淆矩阵 TP/FP/TN/FN):
$$F1=\frac{2\cdot P\cdot R}{P+R}, \quad P=\frac{TP}{TP+FP}, \quad R=\frac{TP}{TP+FN}$$
|
|
**EDA(数据探索)** 核心是**学会对数据提问**:数据量?字段类型?分布?缺失/重复/异常?
> 第一个坑:真实中文 CSV 几乎都有编码问题(UTF-8 / GBK / GB2312),对策是**逐一尝试**:
```python
import pandas as pd
def smart_read_csv(file_path, sample_size=1000):
# 逐一尝试常见中文编码,先用小样本试读再读全量
for enc in ['utf-8', 'gbk', 'gb2312', 'utf-8-sig', 'latin1']:
try:
pd.read_csv(file_path, encoding=enc, nrows=sample_size)
return pd.read_csv(file_path, encoding=enc), enc
except Exception:
continue
return None, None
```
建立第一印象:
```python
df.head() # 前 5 行
df.info() # 类型 + 非空数
df.isnull().sum() # 每列缺失数
df['CONTENT'].astype(str).str.len().describe() # 文本长度分布
```
|
|
> **难度跳跃**:从"看一眼"到"诊断处理脏数据",原则是**先诊断再处理**(像医生体检)。
缺失值按风险分级:**核心字段缺失直接删,辅助字段填默认,数值字段用中位数**(比均值抗异常值):
```python
df = df.dropna(subset=['CONTENT']) # 评论缺失 → 删
df['CREATOR'] = df['CREATOR'].fillna('未知用户') # 用户名 → 填默认
df['RATING'] = pd.to_numeric(df['RATING'], errors='coerce')
df['RATING'] = df['RATING'].fillna(df['RATING'].median()) # 评分 → 中位数
```
**重复值三层**:完全重复(`drop_duplicates`)/ 内容重复(同用户同电影)/ 近似重复(**SimHash + 汉明距离**:分词哈希加权得 64 位指纹,距离 ≤ 阈值即判相似)。
**异常值**:文本长度异常、重复字符堆叠、特殊符号占比、评分越界。
|
|
> **第二个跳跃点**:英文天然空格分词;中文必须用工具。`jieba` 三种模式:
```python
import jieba
jieba.add_word('复仇者联盟') # 加载领域词典,提升专有名词召回
text = "我爱自然语言处理"
print(jieba.lcut(text)) # 精确模式: ['我','爱','自然语言','处理']
print(jieba.lcut(text, cut_all=True)) # 全模式: ['我','爱','自然','自然语言','语言','处理']
print(jieba.lcut_for_search(text)) # 搜索引擎模式
```
经典歧义例:"自然语言处理" → `自然/语言/处理` 或 `自然语言/处理`。
**停用词**("的、了、是、在…"高频虚词)要**按场景定制**:
```python
stopwords = {'的','了','是','在','我','电影','影片','觉得'}
# 情感分析要【保留】程度词(很/非常/超级)——它们改变情感强度!
filtered = [w for w in words if w not in stopwords and len(w) >= 2]
```
|
|
> **第三个跳跃点**:API 庞大,重点先**分清两套接口**。
**Matplotlib** 三层(Figure 画布 / Axes 绘图区 / Artist);推荐**面向对象接口** `fig, ax = plt.subplots()`。**Seaborn** 数据驱动,一行画统计图,支持 `hue=` 分类。
选图口诀:两数比关系→散点;分组比分布→箱线/小提琴;数类别→计数图;多变量→热力图;单变量→直方图。
```python
import matplotlib.pyplot as plt, seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei'] # ← 中文显示
plt.rcParams['axes.unicode_minus'] = False # ← 负号
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sns.histplot(data=df, x='text_length', bins=30, ax=axes[0])
sns.boxplot(data=df, x='genre', y='rating', ax=axes[1])
```
词云(**中文必须指定字体**否则乱码):
```python
from wordcloud import WordCloud
wc = WordCloud(font_path='C:/Windows/Fonts/SimHei.ttf',
background_color='white').generate(' '.join(words))
```
|
|
> **第四个跳跃点(数学密集)**:沿"**离散稀疏 → 连续稠密**"演进。
| 方法 | 解决了什么 | 仍有的痛点 |
| --- | --- | --- |
| One-Hot | 把词变成向量 | 维度爆炸、无法表达相似度 |
| BOW 词袋 | 文档级频数 | 丢顺序、丢语义 |
| N-Gram | 捕获局部词序 | 维度随 N 指数增长、无法解决 OOV |
| TF-IDF | 压低高频词、突出区分词 | 仍稀疏离散、无语义 |
| Embedding | 低维稠密、可算相似度 | 需训练、OOV 需 FastText |
**One-Hot** 维度 = 词典 $|V|$,每词仅一位置 1,**任意两词内积为 0 ⇒ 无法表达相似度**:
$$\mathrm{onehot}(w_i)=(0,\dots,0,\underbrace{1}_{\text{第 }i\text{位}},0,\dots,0)\in\mathbb{R}^{|V|}$$
**TF-IDF**("本地常客 + 全球稀客"的词得分高):
$$\mathrm{tfidf}(t,d)=\frac{\text{count}(t,d)}{|d|}\cdot\log\frac{N}{\mathrm{df}(t)+1}$$
```python
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
cv = CountVectorizer(ngram_range=(1, 2)) # unigram + bigram
X_bow = cv.fit_transform(corpus)
X_tfidf = TfidfVectorizer(ngram_range=(1, 2)).fit_transform(corpus)
```
> 经典直觉:$\text{King}-\text{Man}+\text{Woman}\approx\text{Queen}$(Embedding 才能做到)。
|
|
语言模型本质是给句子算概率,所以先打概率论地基。
**条件概率**——B 已发生时 A 也发生的概率:
$$P(A\mid B)=\frac{P(A\cap B)}{P(B)}$$
**独立性**——B 发不发生都不影响 A:
$$A,B\text{ 独立}\iff P(A\cap B)=P(A)\,P(B)$$
**贝叶斯定理**——看到证据后更新认知(先验→后验):
$$P(A\mid B)=\frac{P(B\mid A)\,P(A)}{P(B)}$$
> 白话:医生看到"发烧"(证据 B),把"感冒"的先验更新成后验。贝叶斯是**朴素贝叶斯分类器**和**语言模型平滑**的思想源头。
|
|
> **全章最关键跳跃**:从概率论符号跳到"给句子打分"。
**链式法则**(句子概率的理论基础,但历史太长算不动):
$$P(w_1,\dots,w_n)=\prod_{i=1}^{n}P(w_i\mid w_1,\dots,w_{i-1})$$
**一阶马尔科夫假设**(只看前一个词 ⇒ bigram):
$$P(w_i\mid w_1,\dots,w_{i-1})\approx P(w_i\mid w_{i-1})$$
**bigram 句子概率**(约定 $w_0=\langle s\rangle$,$w_{n+1}=\langle /s\rangle$ 首尾标记):
$$P(w_1,\dots,w_n)\approx\prod_{i=1}^{n+1}P(w_i\mid w_{i-1})$$
**最大似然估计 MLE**——数数做除法:
$$P_{\text{MLE}}(w_i\mid w_{i-1})=\frac{C(w_{i-1},w_i)}{C(w_{i-1})}$$
课件算例:$P(s_1)=P(i|\langle s\rangle)P(\text{want}|i)\cdots\approx 3.1\times10^{-5}$;$P(\text{"eat"}|\text{"i"})=\frac{9}{2533}\approx0.0036$。
```python
from collections import defaultdict
uni, bi = defaultdict(int), defaultdict(int)
for s in ["我爱中国", "我爱学习", "我喜欢中国"]:
w = list(s)
for c in w: uni[c] += 1
for i in range(len(w)-1): bi[(w[i], w[i+1])] += 1
p = bi[("我","爱")] / uni["我"] # P(爱|我) = count(我,爱)/count(我)
```
|
|
只要句子有一个 bigram 没见过,整句概率就被"一票否决"成 0:
$$\exists\,(w_{i-1},w_i)\notin\text{语料}\;\Rightarrow\;P_{\text{MLE}}=0\;\Rightarrow\;P(\text{句子})=0$$
**拉普拉斯(加一)平滑**——分子 +1,分母 +$V$(词表大小):
$$P_{\text{Laplace}}(w_i\mid w_{i-1})=\frac{C(w_{i-1},w_i)+1}{C(w_{i-1})+V}$$
**Add-k 平滑**(推广,$0<k\le 1$):
$$P_{\text{Add-}k}(w_i\mid w_{i-1})=\frac{C(w_{i-1},w_i)+k}{C(w_{i-1})+kV}$$
```python
V = 8
test = ('我', '讨厌') # 训练里没见过的组合
mle = bi.get(test, 0) / uni['我'] # = 0(灾难)
laplace = (bi.get(test, 0) + 1) / (uni['我'] + V) # 加一平滑
add_k = (bi.get(test, 0) + 0.5) / (uni['我'] + 0.5*V) # Add-k
```
> 进阶方法(了解):**Good-Turing**(用 c+1 次的词数估 c 次的真实概率)/ **Interpolation**(tri/bi/uni 加权混合)/ **Kneser-Ney**(工业标配:折扣 + continuation 概率)。
|
|
**对数似然**(连乘下溢 ⇒ 取 log 变加法,数值稳定):
$$\log P(w_1,\dots,w_n)=\sum_{i=1}^{n}\log P(w_i\mid w_{i-1})$$
**交叉熵**(每个词的平均负对数似然):
$$H=-\frac{1}{N}\sum_{i=1}^{N}\log_2 P(w_i\mid w_{i-1})$$
**困惑度 Perplexity**——"模型平均在多少个候选词里犹豫",**越低越好**:
$$\mathrm{PPL}=2^{H}=\exp\!\left(-\frac{1}{N}\sum_{i=1}^{N}\ln P(w_i\mid w_{i-1})\right)$$
> 四位一体:**似然↑ ↔ 熵↓ ↔ PPL↓ ↔ 模型越好**。PPL=100 ≈ 平均每个词要在 100 个选项里瞎猜。
```python
import math
def perplexity(test_sents, bi, uni, V):
logp = n = 0
for s in test_sents:
w = ['<s>'] + s.split() + ['</s>']
for i in range(1, len(w)):
p = (bi.get((w[i-1], w[i]), 0) + 1) / (uni[w[i-1]] + V) # 平滑
logp += math.log2(p); n += 1
return 2 ** (-logp / n) # PPL = 2^H
```
- 通常NLP模型PPL在10-20之间
- 太高(>100)说明模型不收敛,或者数据集太小
- 太低模型太固化,可能过拟合,或者数据集太小,在固定的几模式之间,死板
- 现实中Loss不可能会接近0,如果接近0可能是数据集太小,或者模型过拟合
- 大概率是出问题了
- 正常的情况是train的loss会比valid loss低一些,valid loss会比test loss低一些,
- 不要差太多,也不要完全一致,如果完全一致,有可能valid是从train中抽出来的,或者valid和train是同一个数据集
- 现实中不可能出现loss接近0的情况,除非是数据集太小,或者模型过拟合
## loss过小
这是一个非常经典且容易让人困惑的问题。直接回答你:**这两段话并不矛盾,而且“损失越小越好”和“PPL有个最佳区间”这两者都是对的。**
之所以让你觉得矛盾,是因为你把**“训练模型的优化目标”**和**“评判模型泛化能力的指标”**混为一谈了。
为了帮你理清,我们从三个层次来深度分析:
### 1. 先厘清概念:Loss(损失)与 PPL(困惑度)
- **Loss(交叉熵损失)**:是模型在训练时**真正在优化的数学目标**。它的绝对值没有物理意义,只代表“预测概率分布的负对数似然”。
- **PPL(困惑度)**:是**基于 Loss 计算出的工程指标**,公式为 \( PPL = e^{Loss} \)。它代表了“模型预测下一个词时,有多少个等概率的候选词”。
**关系**:**Loss 越小,PPL 一定越小**(因为是指数函数单调递增)。所以“期望损失越小”在数学上永远成立。
---
### 2. 为什么原文说“PPL太低(<10)反而不好”?
原文想表达的不是“Loss太小不好”,而是**“在验证集/测试集上,PPL极端低,预示着模型出了问题”**。
- **情况A(正常)**:训练集 Loss=0.1(PPL≈1.1),验证集 Loss=2.3(PPL≈10)。这是**好模型**,泛化能力强。
- **情况B(过拟合)**:训练集 Loss=0.01(PPL≈1.0),验证集 Loss=4.6(PPL≈100)。这是**差模型**。
- **情况C(数据集太小/死板)**:验证集本身只有 10 句固定模板的话(比如“今天天气真好”反复出现)。模型很容易把验证集背下来,验证集 Loss 也能降到 0.01(PPL≈1.0)。
**原文说的“太低太死板”指的是情况C**。这时候模型不是“聪明”,而是“死记硬背”。它输出的概率分布极其尖锐(比如预测“天气”后的词,模型给“真好”打了 99.99% 的概率),这导致 Loss 极低。
**但注意**:这种极低的 Loss 只对**这一个特定的、很小的测试集**有效。一旦换到真实的、多样化的新文本上,Loss 会立刻暴涨。所以,**“PPL太低不好”是指“泛化能力不好”,并不是指“Loss这个数值本身不好”。**
---
### 3. 为什么原文说“PPL太高(>100)说明不收敛”?
- 如果是随机猜测,PPL 等于词表大小(比如 50000),即 Loss ≈ 10.8。
- 如果 PPL > 100(Loss > 4.6),说明模型连最基础的词序、语法都没学好,预测下一个词时还有上百个候选词在“打架”。
- 这通常是因为**模型没训练够(不收敛)**,或者**训练数据太少,模型没见到足够的模式**。
---
### 4. 终极解答:为什么“损失越小越好”和“PPL有个最佳区间”不矛盾?
**因为“损失越小越好”是有严格前提的:必须在“同一个测试集”上比较。**
- **在同一测试集上**:模型A的PPL=10,模型B的PPL=8,**B绝对优于A**。此时,损失越小越好,毫无争议。
- **在不同场景下**:一个在“10万条新闻”上训出的模型,PPL=20;另一个在“100条天气预报”上训出的模型,PPL=5。**你能说后者比前者好吗?** 不能。因为后者的低 PPL 是建立在“任务极其简单、搜索空间极其狭窄”的基础上的。
---
### 总结给你的一句大白话:
> **“PPL太低不好”不是说“损失小错了”,而是说“因为任务太简单/数据太单一,导致这个损失小得没有含金量”。**
**正确的评判标准是:**
在**足够大、足够多样化**的测试集上,**PPL 越低,模型越好**。如果测试集够大够难,你能把 PPL 从 20 降到 10,那说明模型真的变强了,绝对不存在“太低不好”的说法。
所以,原文为了通俗科普,把“数据集太小导致的虚假低PPL”和“过拟合”混在一起说,造成了你的误解。你在实际调参时,**只需要盯着验证集 Loss,只要它还在下降,就放心大胆地继续训练**。只有当验证集 Loss 开始上升(过拟合)时,才需要停止。**损失越小,永远是正确的优化方向。**
|
|
> **跳跃点**:从"统计计数"到"神经网络学习的稠密向量"。经典直觉 $\text{King}-\text{Man}+\text{Woman}\approx\text{Queen}$。
**TF-IDF**(文档表示基线,"本地常客 + 全球稀客"得分高):
$$\text{TF-IDF}(t,d)=\frac{C(t,d)}{|d|}\cdot\log\frac{N}{|\{d:t\in d\}|}$$
**Word2Vec 两架构**(都是单隐藏层神经网络,隐藏层权重 $E_{|V|\times d}$ 即词向量):
| 架构 | 方向 | 特点 | gensim 参数 |
| --- | --- | --- | --- |
| CBOW | 上下文 → 预测中心词 | 训练快、高频词好 | `sg=0` |
| Skip-gram | 中心词 → 预测上下文 | 慢、低频词/小数据好 | `sg=1` |
```python
from gensim.models import Word2Vec
model = Word2Vec(sentences, vector_size=100, window=5,
min_count=1, workers=8, sg=0) # sg=0 CBOW, sg=1 Skip-gram
print(model.wv.most_similar("human", topn=5)) # 语义相近的词
print(model.wv["human"].shape) # (100,) 稠密向量,取代 One-Hot
```
> **OOV(词表外词)**:One-Hot/BOW/TF-IDF/Word2Vec 都无法天然处理;**FastText** 把词拆成字符 n-gram 为 OOV 构造向量,是主流解。
> **演进埋伏笔**:链式法则 → 马尔科夫 → NNLM → RNN → Transformer → GPT,沿"如何更好建模 history"一路演化。
|
|
- 统计学的语言模型,就是根据统计学概率的方式,由1-n序列推测第n+1个单词的最大概率的过程,对吧?
完全正确!你的理解非常精准,一针见血。
为了让你对这个认知更加坚实,我帮你做两个层面的深化:
### 1. 数学上的精确表达
你说的“由1-n序列推测第n+1个单词”,在统计学上被称为**最大似然估计**,公式如下:
\[
P(w_{n+1} | w_1, w_2, ..., w_n)
\]
这个公式读作:“在给定前面 n 个词(历史)的条件下,下一个词是 \( w_{n+1} \) 的概率”。
统计语言模型的核心任务,就是**穷尽词表里所有的词,分别计算这个条件概率,然后挑选出概率最大的那一个词作为输出**。
---
### 2. 一个形象的例子(帮你具象化)
假设模型看到的前缀是:“我想喝一杯......”
- 统计学模型会在海量语料中数数:
- “我想喝一杯**水**”出现了 1000 次 → 概率 50%
- “我想喝一杯**咖啡**”出现了 800 次 → 概率 40%
- “我想喝一杯**石头**”出现了 0 次 → 概率 0%
- 模型最终会选择 **“水”**,因为它的统计概率最大。
---
### 3. 但这里有一个“极简统计学”的致命缺陷(顺便帮你补全认知)
你刚才的描述,严格来说是**传统的 N-gram 统计语言模型**(靠计数和除法)。
但现在的**大语言模型(如 ChatGPT)**虽然本质上也遵循“预测下一个词”的逻辑,却**不再是单纯的统计学数数**了。区别在于:
- **传统统计模型**:靠频次计数。如果语料里从没出现过“我想喝一杯奶茶”,概率就是 0,模型就傻了(这就是著名的**数据稀疏问题**)。
- **现代神经网络模型(如 Transformer)**:不数数,而是把词语映射成高维空间的**向量**。即使没在语料里见过“奶茶”和“水”出现在“喝一杯”后面,但模型发现“奶茶”和“水”在向量空间里距离很近(都是液体),于是会把概率平滑地分配给“奶茶”。
---
### 总结你的理解:
你的说法 **“根据统计学概率,由1-n推测第n+1个最大概率”**,**完全正确**,这既是统计语言模型的**定义**,也是所有现代大模型(包括 GPT)**预训练阶段的核心任务(即“自回归生成”)**。
唯一需要微调的是:现代模型不再直接使用“频次/总数”这种原始统计学,而是用**神经网络**来“模拟”并“泛化”这个概率分布。但底层的“求最大概率”这个目标,**自始至终没变过**。
如果你还想了解“为什么现在的模型不直接数数,而要费力去训练神经网络”,我可以接着给你讲“数据稀疏”和“维度灾难”这两个概念,它们是开启现代 NLP 大门的钥匙。有兴趣吗?
|
|
原理理解力,将问题拆解成一系列的细节步骤,
- 形成一个完整的知识体系,形成一个完整的知识图谱
- 然后由AI生成完成细节的现实
简单来说,一定要先理解原理,然后写伪代码,最后再让AI生成真实代码。
|
|
**问题**:标准交叉熵里有两个概率——$P_A(x_i)$(真分布,当**权重**)乘以 $-\log P_B(x_i)$(模型分布,当**信息量**)。为什么语言模型的交叉熵公式里只剩一个 $P(w_i\mid w_{i-1})$,"少"了一个概率?
$$H(A,B)=-\sum_i P_A(x_i)\log P_B(x_i)\quad\Longleftrightarrow\quad H=-\frac{1}{N}\sum_{i=1}^{N}\log_2 P(w_i\mid w_{i-1})$$
**一句话答案**:$P_A$ 没有丢,它被**测试语料里每个词的出现次数**替代了——右边是对 N 个真实出现的词求平均,哪个词出现几次,它的 $-\log P_B$ 就被累加几次,**权重藏在求和次数里**。这叫用**经验分布**(数数)近似真分布。
### 1. 两个概率各自的角色
| 符号 | 角色 | 直白含义 |
| --- | --- | --- |
| $P_A(x_i)$ | 权重 | 事件 $x_i$ **真的会发生**的频率,决定它的信息量"计几份" |
| $-\log P_B(x_i)$ | 信息量 | 模型 B 给 $x_i$ 分的概率取负对数,即编码它要几个比特 |
$H(A,B)$ 的直觉:**拿 B 的编码本去编码从 A 抽出来的数据,平均每条要多少比特**。A 决定"数据从哪来",B 决定"用谁的码本"。
### 2. 少掉的概率去哪了:被经验分布顶替
语言的真分布 $P_A$(全人类真实说出句子的分布)**无人知晓**,只能拿测试语料当抽样样本:词 $x$ 出现 $c(x)$ 次,经验分布为
$$\hat{P}_A(x)=\frac{c(x)}{N}\approx P_A(x)$$
代回标准式,三步变形($x$ 即"上文+下一词"对):
- 条件概率 单词m出现的前提下单词n出现的概率,
- 在遍历 1-N全量单词时,大多数概率为0,即没出现过
- 出现过的,恰好就是Count的次数,因此二者等价,只是后者计算了大量的0
$$H(A,B)=-\sum_x\hat{P}_A(x)\log P_B(x)=-\frac{1}{N}\sum_x c(x)\log P_B(x)=-\frac{1}{N}\sum_{i=1}^{N}\log P_B(w_i\mid w_{i-1})$$
1. 真分布 $P_A$ 未知 ⇒ 用经验分布 $\hat{P}_A(x)=c(x)/N$ 顶上
2. 权重 $\frac{c(x)}{N}$ 拆成"次数 $c(x)$ × 平均因子 $\frac{1}{N}$"
3. 出现 $c(x)$ 次的词就把 $\log P_B$ 重复加 $c(x)$ 次——**"乘权重"展开成"重复累加"**,$P_A$ 就从公式里隐身了
### 3. 数值小例子
测试集 100 个词,"的"出现 5 次($\hat{P}_A(\text{的})=5/100$),模型给 $P_B(\text{的}\mid\text{上文})=0.1$:
- 标准式:$0.05\times(-\log_2 0.1)$ —— 权重 × 信息量
- 语言模型式:$-\log_2 0.1$ **被加 5 次再除以 100**,即 $5\times(-\log_2 0.1)/100$ —— 完全等价
常见词自动被多加几次(权重大),罕见词自动少加几次(权重小),权重不必显式写出。大数定律保证:N 越大,这个"数数平均"越逼近真期望 $E_{x\sim A}[-\log P_B(x)]$。
### 4. 不是语言模型特有的写法
机器学习所有"交叉熵损失"都是同一套路——**真分布一律用数据集的经验分布代替**:
$$\text{Loss}=-\frac{1}{N}\sum_{i=1}^{N}\log P_{\theta}(y_i\mid x_i)$$
- 分类:标签 one-hot,经验分布就是 0/1 计数
- 语言模型:测试集里每个词投 1 票
- 评估指标同源:$\mathrm{PPL}=2^{H}$,$H$ 正是这个经验交叉熵
> 延伸:$H(A,B)=H(A)+D_{KL}(A\parallel B)$。测试集给定后 $H(A)$ 是常数,所以**最小化交叉熵 ≡ 最小化 KL 散度**——训练的损失和评估的 PPL 用的是同一把尺子。
|
|
**nlp skill** 是把 NLP 黄金九步法固化下来的多智能体协作体系:**1 个总控 skill + 9 个环节智能体 + 2 个横向专项智能体(nlp_chart 图表 / nlp_nbgen 流程 Notebook 装配)**,格式规范沿用 aidev / llmdev 体系。
```
.config/skills/nlp/ ← 总控 skill(意图路由)
├── SKILL.md ← 主入口:触发词 + 智能体注册表
├── README.md ← 体系结构 + 新增智能体指南
├── methodology/ ← 跨智能体复用方法论
│ ├── nlp-golden-pipeline.md ← 黄金九步法详解
│ └── pipeline-self-check.md ← 验证驱动自检闭环
└── refs/
├── nlp-course-datasets.md ← 三份示例数据集台账
└── chart-gallery.md ← 17 种图表速查
.config/agent/ ← 智能体实体(与 skills 平级)
├── nlp_define/ ① 问题定义
├── nlp_acquire/ ② 数据获取(内置数据集索引)
├── nlp_eda/ ③ 数据探索 EDA
├── nlp_chart/ 横向专项:图表生成(scripts/nlp_eda_charts.py)
├── nlp_preprocess/ ④ 清洗预处理(scripts/nlp_clean.py)
├── nlp_split/ ⑤ 数据集分割(scripts/nlp_split.py)
├── nlp_feature/ ⑥ 特征工程(scripts/nlp_vectorize.py)
├── nlp_model/ ⑦ 建模(scripts/nlp_train.py)
├── nlp_eval/ ⑧ 评估(scripts/nlp_evaluate.py)
└── nlp_deploy/ ⑨ 推理部署(scripts/nlp_classifier.py)
```
> **关联方式**:纯 Markdown 约定——SKILL.md 的"智能体注册表" + 每个 agent 目录下的 `AIDEV.md`(frontmatter 的 name/description 触发词)+ 双向路径回指,无任何机械注册文件。
|
|
每个智能体**专职九步法中的一环**,产物是下一环的输入(依赖链铁律):
| 智能体 | 环节 | 核心职责 | 主资产 |
| --- | --- | --- | --- |
| nlp_define | ①问题定义 | 业务需求→技术问题;7 大任务类型判定;成功标准与约束 | task_definition.md 模板 |
| nlp_acquire | ②数据获取 | 5 种途径(业务/公开集/爬虫/标注/增强);内置豆瓣40万评论、Tweets、搜狗新闻三份示例数据 | 数据集台账 |
| nlp_eda | ③数据探索 | 描述统计+图表解读+清洗建议清单 | 分析框架(5 维度) |
| nlp_chart | 横向专项 | 17 种分布图一站式生成(见下一 Tab) | nlp_eda_charts.py |
| nlp_preprocess | ④清洗预处理 | 缺失/去重/SimHash/正则去噪/jieba分词/停用词;产出 text/label/tokens 契约列 | nlp_clean.py |
| nlp_split | ⑤数据集分割 | 分层抽样(StratifiedShuffleSplit);自动验证三子集分布偏差 <0.02 | nlp_split.py |
| nlp_feature | ⑥特征工程 | 7 种文本表示法(One-Hot/词袋/N-Grams/TF-IDF/Word2Vec/GloVe/FastText);只用 train 拟合防泄露 | nlp_vectorize.py |
| nlp_model | ⑦建模 | 5 模型交叉验证对比(NB/LR/RF/SVM/KNN)自动选优;N-gram 语言模型(平滑/困惑度/生成) | nlp_train.py |
| nlp_eval | ⑧评估 | 技术/性能/业务/鲁棒性 4 层次;混淆矩阵图;**只在 test 集终评** | nlp_evaluate.py |
| nlp_deploy | ⑨推理部署 | vectorizer+model 打包单文件;缓存+规则兜底+性能统计 | nlp_classifier.py |
| nlp_nbgen | 横向专项 | 把九步装配为**可执行 .ipynb**:每步 = 步骤说明 + 代码单元,变量链前后串联(df_raw→df→df_clean→splits→features→model→eval→deploy),`--execute` 执行并把图表/指标内嵌 | nlp_pipeline_nb.py |
> **设计要点**:数据相关工作(获取+清洗+EDA)约占 70% 精力——体系里 ①-⑤ 五个智能体都在跟数据打交道,与行业调研结论一致。
|
|
**nlp_chart** 是横向服务智能体(不在依赖链上),源于课程"16 种必画图形"要求,实际交付 **17 种**,任何环节需要"用图表描述数据分布"都可调用:
| 分组 | 图表 | 函数 |
| --- | --- | --- |
| 单变量分布(5) | 直方图 / 箱线图 / 小提琴图 / 累积分布图 / QQ图 | sns.histplot · boxplot · violinplot · cumsum · scipy.probplot |
| 类别变量(4) | 柱状图 / 水平柱状图 / 饼图 / 计数图 | plt.bar · barh · pie · sns.countplot |
| 关系(5) | 散点图 / 回归线图 / 相关性热力图 / 配对图 / 分面图 | sns.scatterplot · lmplot · heatmap · pairplot · relplot |
| 时间(2) | 折线图 / 面积图 | plt.plot · fill_between |
| 文本专项(2) | 词云图 / 高频词共现网络图 | wordcloud + jieba · networkx |
```bash
# 一条命令出全套图(豆瓣 2 万条实测 18 张全部成功)
python .config/agent/nlp_chart/scripts/nlp_eda_charts.py \
--csv comments.csv --text-col CONTENT --label-col RATING --time-col TIME \
--out figures/ --chart all --sample 20000
```
**内置健壮性**:中文字体自动查找(WSL 直接复用 Windows 字体);CSV 多编码兜底(utf-8/gbk/gb2312);大文件采样;无头渲染(Agg 后端)。
|
|
agent 体系建成后,立即按 NLP 流程在 `ai58/0815-nlp/test/` 目录**实测跑通全流程**(豆瓣评论情感分类,2 万条采样):
| 步骤 | 智能体 | 产物 | 关键结果 |
| --- | --- | --- | --- |
| ① | nlp_define | 01-define/task_definition.md | 二分类情感分析,F1≥0.80 目标 |
| ② | nlp_acquire | 02-acquire/raw/ + 台账 | 采样 20000 条,neg:pos≈45:55 |
| ③ | nlp_eda+nlp_chart | 03-eda/report.md + 18 张图 | 长度右偏(偏度1.29),P95=138字→截断建议140 |
| ④ | nlp_preprocess | 04-preprocess/clean.csv | 20000→18589 行(损耗7%),契约列 text/label/tokens |
| ⑤ | nlp_split | 05-split/ 三子集 | 分层分割,最大分布偏差 0.0002 ✅ |
| ⑥ | nlp_feature | 06-feature/ | TF-IDF 20000维 (1,2)gram,词表IDF最高词=领域词 |
| ⑦ | nlp_model | 07-model/best_model.pkl | NB 0.770 / **LR 0.779** / RF 0.754 / SVM 0.775 / KNN 0.459 |
| ⑧ | nlp_eval | 08-eval/ | test F1=0.782 / Acc=0.788 + 混淆矩阵图 |
| ⑨ | nlp_deploy | 09-deploy/nlp_classifier.pkl | 打包单文件,3 条冒烟预测全对 |
> **指标复盘(诚实结论)**:test F1=0.782 略低于 0.80 目标——2 星评论含大量反讽("两个小时超长预告片"),TF-IDF+线性模型对反讽无能为力;改进路径:全量 40 万训练 / 字级 n-gram / 预训练词向量。作为流程演示,九步全链路贯通、产物契约全部匹配。
|
|
**方法论沉淀**(methodology/)与本次实测踩坑实录:
**1. 验证驱动自检闭环(五问)**:产物存在吗 → 能跑吗 → 对吗 → 接口匹配吗 → 教训沉淀了吗。本体系每个智能体执行完都必须走一遍。
**2. 本次实战发现并修复的真实 bug**:
```
症状:冒烟预测 "这部电影太好看了" → 误判 neg
定位:查模型权重发现「好看」系数为负
→ 统计训练集:含「好看」样本 pos/neg = 325:321 几乎对半
→ 根因:单字过滤误删否定词「不」("不好看"被切成"好看")
修复:分词器增加单字白名单(不/没/无/很/太/最…)
验证:重跑 ④-⑨,冒烟 3 条全部正确
沉淀:修复写入 nlp_clean.py 与 nlp_classifier.py(SINGLE_CHAR_KEEP),成为可复用资产
```
**3. 其他踩坑清单**:
| 坑 | 解法 |
| --- | --- |
| matplotlib 注册 .ttc 字体成功但渲染方块 | 纯 .ttf(simhei)优先于 .ttc |
| sns.set_style 覆盖中文字体设置 | 必须先 set_style 再 setup_chinese_font |
| 新版 pandas ArrowDtype 列 astype(str) 对 NA 失效 | 先转 object 再 np.where 兜底 |
| pairplot 的 hue 列同时出现在 vars 报错 | hue 列从 vars 剔除 |
| 向量化器在全量数据 fit | 数据泄露大忌:只在 train fit,val/test 仅 transform |
| 类别不平衡直接随机切 | StratifiedShuffleSplit 分层,切完验证分布偏差 |
**4. 复用方式**:对总控说一句"按 NLP 流程跑一遍 xx 任务",即从 nlp_define 开始沿依赖链依次调度;单环节需求(如"只画分布图")则直接命中对应智能体;**要 Notebook 交付时走 nlp_nbgen**——`/ai/wks/jupyter/nlp_douban/` 即其产物:单文件 ipynb 内含九步说明 + 前后串联可执行代码 + 内嵌图表与指标(test F1=0.781,冒烟 3/3),`outputs/` 落盘与 CLI 流程同契约的中间产物。
|
|
### 从"数词"到"懂词":为什么需要词向量
One-hot 编码维度=词表大小且任意两词正交("苹果"与"香蕉"距离=与"电脑"距离),无法表达语义。**词向量**把每个词映射为低维稠密向量(如 50 维),让"语义相近的词在空间中彼此靠近"。
**演化主线**(理解 Word2Vec 的历史位置):
| 阶段 | 代表 | 核心思想 | 问题 |
|---|---|---|---|
| 统计语言模型 | N-gram | 计数共现 + 马尔可夫假设 | 稀疏、未见组合概率为 0 |
| 神经网络语言模型 | NNLM (Bengio 2003) | Embedding 查表 + 神经网络,训练"一举两得"(既得语言模型又得词向量) | 输出层 O(H×V) 是瓶颈 |
| **Word2Vec** (Mikolov 2013) | CBOW / Skip-gram | 去掉隐藏层,只学词向量 + 负采样 | 静态向量:多义词/未登录词无解 |
### 两种任务结构
- **CBOW(连续词袋)**:用上下文预测中心词——"周围这些词,中间是什么?"
- **Skip-gram(跳字)**:用中心词预测上下文——"这个词周围会出现什么?"
| 特性 | CBOW | Skip-gram |
|---|---|---|
| 方向 | 上下文 → 中心词 | 中心词 → 上下文 |
| 速度/数据 | 快、小数据、高频词好 | 慢、大数据、低频词好、精度高 |
### 数学原理(三步看懂)
**第 1 步 · Skip-gram 目标**:给定中心词 $w_c$,窗口内上下文词 $w_o$ 的概率(全 softmax):
```text
P(w_o | w_c) = exp(v_wo · v_wc) / Σ(w∈V) exp(v_w · v_wc)
```
其中 v_w 是词向量,"·"是向量点积。**痛点**:分母要遍历整个词表(10 万词 × 每步),训练极慢。
**第 2 步 · 负采样**(把多分类变二分类):随机采 k 个"负样本",只需判断"是/不是真实邻居":
```text
L = -log σ(v_wo · v_wc) - Σ(i=1..k) log σ(-v_wi · v_wc)
σ(x) = 1 / (1 + e^(-x)) # sigmoid
```
直觉:拉近正样本向量、推远负样本向量。负样本按词频的 3/4 次方采样(给低频词更多机会)。复杂度从 O(V·d) 降到 O(k·d),训练快 1000 倍以上。
**第 3 步 · CBOW**:上下文向量取平均后 softmax 预测中心词:
```text
v_ctx = (1/2m) Σ(j≠0) v_w(t+j)
P(w_c | ctx) = softmax(v_wc · v_ctx)
```
### 经典验证:词类比
训练好后应有:**国王 − 男人 + 女人 ≈ 女王**——说明向量空间捕获了语义关系(性别方向、类别方向)。
|
|
### Word2Vec 八步处理流程
```text
① 语料获取(如搜狗新闻 CN_Corpus,注意中文编码陷阱)
② 编码检测读取(chardet 检测 + gbk/gb2312/utf-8 回退链)
③ 文本清洗(正则去 URL/数字/多余空白,只留中文范围)
④ 分词(jieba,过滤单字与标点 —— 中文必需步骤)
⑤ 滑窗构造样本(中心词/上下文对 + 负采样)
⑥ 训练(手写 NumPy SGD 或 gensim)
⑦ 评估可视化(相似词/词类比/PCA 散点/Embedding Projector)
⑧ 持久化与应用(三种保存方式 + 文档相似度等)
```
### gensim 关键 API 与参数
```python
from gensim.models import Word2Vec
model = Word2Vec(sentences=分词后句子列表, vector_size=50, window=2,
min_count=1, sg=1, hs=0, negative=5, epochs=20,
alpha=0.025, min_alpha=0.0001, seed=42, compute_loss=True)
model.wv.most_similar('苹果', topn=5) # 相似词
model.wv.most_similar(positive=['黄色','苹果'], negative=['红色'], topn=3) # 类比
```
**参数选型口诀**:
| 参数 | 小数据 | 大数据/学术 | 生产 |
|---|---|---|---|
| vector_size | 50-100 | 200-300 | 200 |
| window | 2-3(语法) | 5-10(语义) | 5 |
| min_count | 1-2 | 5-10 | 5-10 |
| negative | 5-20 | 2-5 | 5 |
| epochs | 10-20 | 3-5 | 5 |
### 三种保存方式
| 保存 | 加载 | 用途 |
|---|---|---|
| `model.save('m.model')` | `Word2Vec.load` | 可继续训练 |
| `model.wv.save('wv.kv')` | `KeyedVectors.load` | 生产推理(更小更快) |
| `wv.save_word2vec_format('vectors.txt')` | `load_word2vec_format` | 跨平台 / 注入 Keras Embedding |
### 常见坑
- 中文语料混编码 → chardet + 回退链;**必须先分词**,gensim 输入是 list[list[str]]
- 未登录词直接查报错 → 先 `if w in model.wv`;根因解法:降 min_count 或换 FastText(子词)
- 数值稳定:sigmoid 输入 clip ±500、log 加 1e-10
- `get_latest_training_loss()` 返回累积值,监控要按 epoch 差分
- hs(层次softmax)与 negative 互斥,默认负采样
|
|
### 与其他两类主题的区别(初学者选型)
| 维度 | Word2Vec | 深度学习语言模型 | Seq2Seq |
|---|---|---|---|
| 学什么 | 静态词向量(词级) | 序列到下一词(句子级概率) | 变长序列到变长序列 |
| 输入→输出 | 词 ↔ 词 | 前缀 → 下一词 | 完整句 → 完整句 |
| 典型任务 | 相似词/语义搜索/推荐 | 输入法联想/续写 | 翻译/摘要/问答对 |
| 训练开销 | 小(CPU 可训练) | 中 | 中大 |
### 行业应用场景
1. **语义搜索 / 文档相似度**:文档向量 = 词向量平均 + 余弦相似度(RAG 检索的雏形)
2. **推荐系统**:内容向量、用户兴趣向量做相似召回
3. **文本分类 / 情感分析**:词向量作为 LSTM/分类器输入特征
4. **关键词提取 / NER**:利用向量相似度扩展词表
5. **输入法 / 搜索联想**:与语言模型配合预测下一词
### 预训练 vs 自训练(企业选型)
| 方案 | 适用 | 要点 |
|---|---|---|
| 预训练(腾讯 AI Lab 800万中文词向量,200维,Directional Skip-Gram) | 通用任务、快速开发 | word2vec 文本格式:首行"词数 维度";可用 limit 只读部分提速 |
| 自训练 | 专业领域(医学/法律术语) | min_count=2, alpha=0.05, epochs=15 保留术语 |
| 混合 | 效果最优 | 预训练 + 微调;Keras `Constant(matrix)` / PyTorch `from_pretrained` 注入 |
### 固有局限与演进
静态向量:一词多义无解("苹果"水果/公司同向量)、未登录词无解、无句子级语义 → **FastText**(子词)、**GloVe**(全局共现)、**ELMo/BERT**(上下文相关向量)。
|
|
### 可执行 Notebook(code/test/word2vec.ipynb)
由 `nlp_w2v` 智能体一键生成,**每节 = 步骤说明 + 可执行代码,代码单元变量前后串联**:
```text
corpus(分词语料) → samples(滑窗样本) → model_scratch(NumPy从零实现+损失曲线)
→ model_gensim(工程对照) → 相似词/类比评估 → vecs_2d(PCA可视化)
→ doc_sim(文档向量+余弦) → 三种保存演示
```
**核心代码一:NumPy 手写 Skip-gram(理解算法本质)**
```python
class Word2VecSkipGram:
def __init__(self, vocab_size, embedding_dim=50, lr=0.01, neg_k=5):
rng = 0.5 / embedding_dim # 均匀初始化 [-0.5/d, 0.5/d]
self.W_in = np.random.uniform(-rng, rng, (vocab_size, embedding_dim))
self.W_out = np.random.uniform(-rng, rng, (embedding_dim, vocab_size))
self.lr, self.neg_k = lr, neg_k
def train_step(self, center, context):
v = self.W_in[center]
pos = 1/(1+np.exp(-np.clip(v @ self.W_out[:, context], -500, 500)))
# 正样本拉近(梯度上升似然),负样本推远 ……(完整版见 notebook)
```
**核心代码二:PCA 可视化(直观看语义聚类)**
```python
X = wv.vectors - wv.vectors.mean(axis=0)
U, S, Vt = np.linalg.svd(X, full_matrices=False)
vecs_2d = X @ Vt[:2].T # 投影到前两个主成分平面
```
**核心代码三:文档相似度(语义搜索最小实现)**
```python
def doc_vector(tokens):
return np.mean([wv.get_vector(w) for w in tokens if w in wv], axis=0)
def cosine(a, b):
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
```
在 Jupyter 中打开 notebook 顺序执行即可复现全流程;也可由智能体重新生成:`python .config/agent/nlp_w2v/scripts/nlp_w2v_nb.py --out word2vec.ipynb --execute`
|
|
### 语言模型是什么
**语言模型(LM)**:给前文,预测下一个词的概率。
```text
P(w1, w2, ..., wT) = Π(t=1..T) P(wt | w1..w(t-1)) # 链式法则
```
- **统计方法(N-gram)**:马尔可夫假设只看前 n-1 个词;数共现频率。缺陷:稀疏(未见组合=0)、参数随 n 指数增长 → 需要平滑。
- **神经网络方法(本主题)**:用 Embedding + 神经网络学概率,泛化到未见过但语义相近的组合。
**统一评估指标 —— 困惑度(Perplexity)**:
```text
PP = 2^(-(1/T) Σ(t) log2 P(wt | ctx))
```
直觉:"模型平均在多少个候选词中纠结"。**越低越好**:小于 10 优秀,大于 100 较差。它是本类所有模型的通用尺子。
### 三代结构演进
**第 1 代 · NNLM(Bengio 2003)**——第一个用神经网络学词向量的模型:
```text
y = softmax( U · tanh( H·C(x) + d ) + b )
输入前 n-1 词 → C: Embedding 查表(参数共享) → 拼接 → 隐藏层 → 全词表 softmax
```
三大创新:分布式词表示 / 参数共享 / 自动学得语义相似(训练"一举两得":语言模型+词向量)。
局限:复杂度 O(ND + NDH + HV),**输出层 H×V 是瓶颈**(这正是 Word2Vec 要解决的痛点)。
**第 2 代 · RNN**——引入隐状态记忆,处理任意长度:
```text
h(t) = tanh( W_xh·x(t) + W_hh·h(t-1) + b_h )
y(t) = softmax( W_hy·h(t) )
```
缺陷:BPTT 反传时梯度连乘,**长程梯度消失/爆炸**。
**第 3 代 · LSTM(长短期记忆)**——三门一记忆,加法通道保梯度:
```text
f(t) = σ(W_f·[h(t-1), x(t)] + b_f) # 遗忘门:忘掉多少旧记忆
i(t) = σ(W_i·[h(t-1), x(t)] + b_i) # 输入门:写入多少新信息
c̃(t) = tanh(W_c·[h(t-1), x(t)] + b_c) # 候选记忆
c(t) = f(t)⊙c(t-1) + i(t)⊙c̃(t) # 细胞状态:加法更新 → 梯度不消失
o(t) = σ(W_o·[h(t-1), x(t)] + b_o) # 输出门
h(t) = o(t)⊙tanh(c(t)) # 隐状态
```
(⊙ 为逐元素乘)。**GRU** 是 LSTM 简化版:只留更新门+重置门,参数更少、效果相近。
|
|
### 处理流程(八步)
```text
① 语料获取 → ② 编码检测读取 → ③ 清洗 → ④ 分词
⑤ 词汇表(
|
|
### 与其他两类主题的区别(初学者选型)
| 维度 | 深度学习语言模型 | Word2Vec | Seq2Seq |
|---|---|---|---|
| 核心问题 | 下一个词是什么 | 词的语义怎么表示 | 整句怎么翻译成另一句 |
| 输出 | 一个词(全词表分布) | 词向量 | 变长词序列 |
| 评估 | 困惑度 | 相似词/类比 | BLEU/人工评估 |
| 与后两者关系 | 承上启下:NNLM 诞生词向量(→Word2Vec);RNN/LSTM 是编码器积木(→Seq2Seq) | LM 的副产品发扬光大 | 把 LM 的"下一词生成"扩展为两阶段框架 |
### 行业应用场景
1. **输入法 / 搜索联想**:下一词预测直接商用(搜狗/百度输入法)
2. **智能写作续写**:给定开头自动补全(办公套件、营销文案)
3. **语音识别打分**:ASR 候选句用 LM 重排序纠错
4. **文本分类 / 情感分析**:LSTM 编码句向量(电商评论、舆情监控)
5. **NER 序列标注**:BiLSTM+CRF 抽取实体(医疗病历、金融公告)
6. **推荐系统**:用户行为序列建模(点击流预测)
### 选型经验
- 短句分类:先跑 TF-IDF + 线性模型基线,需要词序/长依赖再上 LSTM
- 需要"生成"而不是"分类" → Seq2Seq(见第 7 节)
- 困惑度对比是硬道理:神经模型若不优于 N-gram 基线就没有存在意义(**实验定论**铁律)
- 演进视角:LSTM 串行计算慢 + 固定窗口 → 注意力机制与 Transformer 取代之
|
|
### 可执行 Notebook(code/test/dlm.ipynb)
由 `nlp_dlm` 智能体一键生成,变量链:
```text
tokens(语料) → bigram_pp(统计基线困惑度) → nnlm(PyTorch NNLM+损失曲线)
→ generated_text(贪心滑窗续写) → pp_cmp(RNN/LSTM/GRU 困惑度对比柱状图)
→ clf_acc(LSTM 分类模板)
```
**看点一:Bigram + 拉普拉斯平滑(对照组)**
```python
def bigram_perplexity(tokens, k=1.0):
# P(wt|w(t-1)) = (c(对) + k) / (c(前词) + k·V),未见组合不再为 0
```
**看点二:贪心文本生成(滑窗解码)**
```python
def generate(model, seed_words, n_words=8):
seq = [word2idx.get(w, 1) for w in seed_words]
for _ in range(n_words):
ctx = torch.tensor([seq[-CTX:]])
nxt = torch.exp(model(ctx)).argmax(dim=1).item()
if nxt in (0, 1): continue # 跳过 PAD/UNK
seq.append(nxt)
```
**看点三:三网络同条件对比实验**
RNN/LSTM/GRU 语言模型同一语料训练,`torch.exp(cross_entropy)` 即困惑度,柱状图横向对比——亲手验证"LSTM 解决长程依赖"是否在你的数据上成立。
**看点四:LSTM 分类器**
`Embedding(padding_idx=0) → LSTM → Linear`,用 `pad_sequence` 对齐变长句——企业情感分析模板的最小可运行版本。
重新生成:`python .config/agent/nlp_dlm/scripts/nlp_dlm_nb.py --out dlm.ipynb --execute`
|
|
### 编码器-解码器(Encoder-Decoder)框架
思想 = **"整体理解 vs 逐词翻译"**:像人类译员先听完整句、理解含义、再组织目标语言。
```text
输入 "what is your name"
│ Encoder(LSTM) 读完整句 → 压缩为固定长度上下文向量 (hidden, cell) ← 基础版
▼ │ 或保留全部时间步输出 [B,L,H] ← 注意力版
Decoder(LSTM) 以 <SOS> 起步逐词生成 → 你叫什么名字 → <EOS> 停止
```
**四大特殊符号**(必须先记住):
| 符号 | 索引 | 用途 |
|---|---|---|
| < PAD > | 0 | batch 内不等长序列填充 |
| < SOS > | 1 | 解码器起始输入 |
| < EOS > | 2 | 源句结尾 / 生成终止条件 |
| < UNK > | 3 | 词表外词兜底 |
### 训练 vs 推理(错位一格,最易错)
| 阶段 | decoder 输入 | 标签/行为 |
|---|---|---|
| 训练 | [<SOS>, 你, 叫, 什么](目标左移一位) | [你, 叫, 什么, 名字, <EOS>] 逐位交叉熵 |
| 推理 | 只有 [<SOS>],每步喂上一步自己的 argmax | 贪心解码,遇 <EOS> 停 |
**Teacher Forcing**:训练时下一步输入按概率取真实标签(引导收敛)或模型预测(缩小曝光偏差)。调度:初期 0.8 → 后期 0.3-0.5;**评估/推理必须 = 0**。
### 信息瓶颈:注意力的动机
基础版把任意长句压进一个固定维向量(如 128 维)——装不下:
1. **长句信息丢失**:有限数字编码不了无限长度
2. **无法动态关注**:生成"什么"时应看 "What",生成"名字"时应看 "name",固定向量做不到
3. **早期信息稀释**:句首信息被逐步冲淡
### 注意力三步(数学核心)
记编码器各时间步输出 h_j,解码器当前状态 s_i:
```text
① 打分: Bahdanau(加性): e_ij = vᵀ·tanh(W_h·h_j + W_s·s_i)
Luong(点积): e_ij = h_jᵀ · s_i
② 归一: α_ij = exp(e_ij) / Σ(j') exp(e_ij') # softmax,构成概率分布
③ 加权: c_i = Σ(j) α_ij · h_j # 上下文向量 = 智能加权平均
```
解码器把 c_i 与当前词嵌入**拼接**后送 LSTM(故输入维 = emb_dim + hidden_dim),每生成一个词都重新"回头看"全部源词。**副产品红利**:注意力权重矩阵可画成热力图 = 软对齐,可解释性强。
|
|
### 处理流程(中英翻译 cmn.txt 七步)
```text
① 数据:cmn.txt 每行"英文\t中文\t版权";英文小写空格分词、中文按字符切;
过滤英文>10词或中文>20字的句对
② 词汇表:4 特殊符号起步;encode(未登录→UNK) / decode(遇EOS停、跳PAD)
③ Dataset+collate_fn:tgt_input=[SOS]+目标、tgt_output=目标+[EOS],三路 PAD 填充
④ 模型:Encoder / Decoder(LSTM) / Seq2Seq 组装(forward 带 teacher_forcing_ratio)
⑤ 训练:CrossEntropyLoss(ignore_index=PAD) + clip_grad_norm_(1.0) + Adam(1e-3)
⑥ 推理:translate() 贪心解码,EOS + max_length 双保险
⑦ 注意力可视化:热力图(x=源词 y=译词,对角线高亮=对齐证据)
```
### 关键代码:Seq2Seq 前向(teacher forcing 分叉)
```python
def forward(self, src, tgt_in, tf_ratio=0.5):
_, hidden = self.encoder(src)
dec_in = tgt_in[:, :1] # SOS
outputs = torch.zeros(B, T, vocab_size)
for t in range(T):
out, hidden = self.decoder(dec_in, hidden)
outputs[:, t] = out
if random.random() < tf_ratio:
dec_in = tgt_in[:, t+1:t+2] # 真实标签(教师强制)
else:
dec_in = out.argmax(-1) # 模型自己的预测
return outputs
```
### 关键代码:BahdanauAttention
```python
def forward(self, dec_h, enc_out): # dec_h:[B,H] enc_out:[B,L,H]
q = dec_h.unsqueeze(1).expand(-1, L, -1)
scores = self.v(torch.tanh(self.W_h(enc_out) + self.W_s(q))).squeeze(2) # e
attn = F.softmax(scores, dim=1) # α = softmax(e)
ctx = torch.bmm(attn.unsqueeze(1), enc_out).squeeze(1) # c = Σ α·h
return ctx, attn
```
### 常见坑(课程实录)
1. **时间步错位**:从 t=1 训练会丢第一个字符——从 0 开始 / 标签去掉首位 START
2. PAD 参与损失:必须 `ignore_index=PAD`;Embedding 加 `padding_idx=0`
3. Teacher forcing 调度:初期高后期低;评估必须 0
4. 梯度爆炸:`clip_grad_norm_(model.parameters(), 1.0)`
5. 解码必须有 max_length + EOS **双保险**,防无限生成
6. 分词方式训练/推理必须一致(字符级与词级不可混用)
7. UNK 双向兜底:编码 `.get(t, UNK)`、解码 `idx2word.get(i, '<UNK>')`
8. `model.train()/eval()` + `no_grad()` 不可省;固定随机种子保复现
|
|
### 基础版 vs 注意力版(结构差异速查)
| 维度 | 基础 Seq2Seq | 注意力 Seq2Seq |
|---|---|---|
| 编码器传递 | 仅最终 (h,c) 固定向量 | **全部时间步输出** [B,L,H] |
| 上下文向量 | 一次计算、固定 | 每步重算 c_i = Σ α_ij·h_j |
| LSTM 输入维 | emb_dim | emb_dim + hid_dim(拼接上下文) |
| 长句能力 | 信息瓶颈、早期衰减 | 任意位置直接访问 |
| 可解释性 | 无 | 注意力热力图(软对齐) |
### 与其他两类主题的区别(初学者选型)
| 维度 | Seq2Seq | Word2Vec | 深度学习语言模型 |
|---|---|---|---|
| 任务形态 | 变长序列 → 变长序列 | 词 → 向量 | 前缀 → 下一词 |
| 输入输出长度 | 双侧均可变 | 定长 | 输入变、输出一词 |
| 典型数据 | 平行语料(cmn.txt) | 单语语料 | 单语语料 |
| 核心机制 | 编码-解码 + 注意力 | 负采样 | 循环网络记忆 |
一句话区分:**Word2Vec 学"词的意思",语言模型学"句子怎么说",Seq2Seq 学"两种说法怎么转换"**。
### 行业应用场景
1. **机器翻译**:本主题原型任务(跨语言客服、文档本地化)
2. **文本摘要**:长文 → 短摘要(新闻、会议纪要)
3. **问答对生成 / 对话系统**:客服话术、FAQ 生成(任务型机器人前身)
4. **语音识别**:声学序列 → 文本(编码器换声学模型)
5. **代码 / SQL 生成**:自然语言 → 代码(chat2sql 类产品的经典起点)
6. **拼写纠错 / 文本规范化**:错误序列 → 正确序列
### 演进路线(通向大模型)
```text
Word2Vec(静态向量) → RNN/LM(序列) → Seq2Seq(编解码框架)
→ 注意力(动态上下文) → Transformer(自注意力全面取代循环) → BERT/GPT(预训练大模型)
```
**注意力机制是通向现代大模型的关键桥梁**——Transformer = "只留注意力、扔掉循环"。
|
|
### 可执行 Notebook(code/test/seq2seq.ipynb)
由 `nlp_seq2seq` 智能体一键生成,变量链:
```text
pairs(平行句对) → src/tgt_vocab → loader(collate_fn 三路PAD)
→ model_s2s(基础版+teacher forcing+损失曲线) → 翻译推理
→ model_attn(注意力版) → attn_map(注意力热力图)
```
**看点一:错位一格样本构造(三件套)**
```python
self.items.append((sv.encode(en), # src + EOS
[1] + tv.encode(zh, False), # tgt_input = [SOS]+目标
tv.encode(zh))) # tgt_output = 目标+[EOS]
```
**看点二:基础版与注意力版同场训练对比**
同一份 loader、同一套损失/裁剪,双损失曲线画在一张图——直观看注意力带来的收敛差异。
**看点三:注意力热力图(软对齐)**
```python
plt.imshow(attn_map, cmap="Blues", aspect="auto")
plt.xticks(range(len(words)), words) # x = 英文源词
plt.yticks(range(len(zh)), list(zh)) # y = 中文译词
```
对角线颜色越深说明"翻译哪个词时就看哪个源词"——这就是可解释性。
**看点四:真实数据支持**
`--data D:\wks\ai58\0822-word2vec\code\seq2seq\cmn.txt` 可切换到真实中英平行语料(自动过滤超长句、限量 2000 对)。
重新生成:`python .config/agent/nlp_seq2seq/scripts/nlp_seq2seq_nb.py --out seq2seq.ipynb --execute`
|
|
### nlp skill 新增三个主题智能体
在原有"黄金九步法 + 横向专项(nlp_chart / nlp_nbgen)"基础上,新增三个**模型主题**智能体,把 Day16 课程的知识、方法、流程全部沉淀为可复用资产——后续使用 skill 时直接调用智能体,无需重复实现:
| 智能体 | 主题 | 触发词示例 | 沉淀的课程来源 |
|---|---|---|---|
| `nlp_w2v` | Word2Vec 词向量 | 词向量/CBOW/Skip-gram/负采样/gensim/词类比/腾讯词向量 | Word2Vec 及企业应用实战(一)(二) |
| `nlp_dlm` | 深度学习语言模型 | NNLM/RNN/LSTM/GRU/困惑度/文本生成/LSTM分类 | 神经网络语言模型 + 深度学习自然语言模型 |
| `nlp_seq2seq` | Seq2Seq+注意力 | Seq2Seq/编码器解码器/注意力/机器翻译/teacher forcing | Seq2Seq 模型与机器翻译 |
### 每个智能体的双重作用
1. **知识资产库**:`refs/knowledge.md` 沉淀该主题的完整知识——演化主线、数学公式、处理流水线、关键代码模式、常见坑、企业应用场景、数据文件索引。任何后续任务需要这些知识时**直接读取,不重复整理**。
2. **Notebook 工厂**:`scripts/nlp_*_nb.py` 一键生成"步骤说明 + 前后串联可执行代码"的教学 ipynb——既有理论讲解又有可运行验证,`--execute` 连执行结果一并内嵌。
### 与原有体系的关系
```text
nlp skill(总控路由)
├── 九步链:nlp_define → acquire → eda → preprocess → split → feature → model → eval → deploy
├── 横向专项:nlp_chart(图表)、nlp_nbgen(九步流程 Notebook)
└── 主题专项(新增):nlp_w2v / nlp_dlm / nlp_seq2seq ← 横向服务第⑥⑦环,也可独立响应
```
主题专项与 nlp_nbgen 的区别:nlp_nbgen 装配**分类任务九步流程**;主题专项各自装配**单一模型主题的深度教程**(含数学推导与从零实现)。
|
|
### 智能体内部结构(四件套)
每个主题智能体目录 `.config/agent/nlp_<主题>/` 结构统一:
```text
nlp_w2v/ nlp_dlm/ nlp_seq2seq/
├── AIDEV.md # 主入口:身份与能力边界(能做/不做)+ 使用方法 + Notebook 段落契约
├── refs/knowledge.md # 知识库:公式/流水线/代码模式/坑/应用场景/数据索引(知识沉淀核心)
├── workflows/phase-*.md # 工作流(六段式:目标/前置/动作/产物/自检/交接)
└── scripts/nlp_*_nb.py # Notebook 生成器(主资产,可直接运行)
```
### 各智能体的 Notebook 段落契约(装配结构)
**nlp_w2v**(8 节):演化背景 → 滑窗样本 → NumPy 从零实现负采样 → gensim 对照 → 相似词/类比评估 → PCA 可视化 → 文档相似度 → 三种保存
**nlp_dlm**(7 节):语料与词汇表 → Bigram 基线困惑度 → NNLM 训练 → 贪心文本生成 → RNN/LSTM/GRU 三网络对比 → LSTM 分类模板 → 总结选型
**nlp_seq2seq**(7 节):平行语料 → 错位一格样本 → 基础 Seq2Seq+teacher forcing → 注意力原理 → 注意力版训练 → 翻译推理+热力图 → 对比与演进
### 自检闭环(质量保证)
每个 workflow 的第⑤段是自检项,例如 nlp_seq2seq:输入/标签严格错开一位?损失 ignore_index=PAD?teacher forcing 推理=0?解码 EOS+max_length 双保险?——生成 Notebook 后逐项核对,通过才算交付。
|
|
### Notebook 生成步骤(以三个主题为例)
```text
① 读取对应智能体的 refs/knowledge.md + workflows/phase-*.md(知识与方法来源)
② 调用智能体的 scripts/nlp_*_nb.py:
python .config/agent/nlp_w2v/scripts/nlp_w2v_nb.py --out word2vec.ipynb --execute
python .config/agent/nlp_dlm/scripts/nlp_dlm_nb.py --out dlm.ipynb --execute
python .config/agent/nlp_seq2seq/scripts/nlp_seq2seq_nb.py --out seq2seq.ipynb --execute
③ 脚本把"说明单元 + 代码单元"组装为 .ipynb(变量链前后依赖)
④ --execute 时用 nbclient 在内核中顺序执行,输出/图表/指标内嵌回 notebook
⑤ 按 workflow 自检项核对结果
```
### 变量链设计(核心特性)
三份 Notebook 的代码单元都不是孤立的——后一步直接使用前一步的产物变量:
| Notebook | 变量链 |
|---|---|
| word2vec.ipynb | corpus → samples → model_scratch → model_gensim → wv → vecs_2d → doc_sim |
| dlm.ipynb | tokens → bigram_pp → nnlm → generated_text → pp_cmp → clf_acc |
| seq2seq.ipynb | pairs → vocabs → loader → model_s2s → model_attn → attn_map |
改上游单元后从该单元起顺序重跑即可;既有步骤说明,又有可执行代码,理论+实操一体。
### 健壮性设计
- gensim/jieba 缺失时自动降级(纯 NumPy 实现 / 预分词语料),保证零依赖可跑
- seq2seq 支持 `--data cmn.txt` 切换真实平行语料
- 中文字体自适应(微软雅黑/黑体/Noto),图表不乱码
- 固定随机种子,结果可复现
|
|
### 使用方法
**方式一 · 对话路由(推荐)**:直接向 nlp skill 提需求,总控按注册表路由:
- "帮我训练词向量并可视化" → `nlp_w2v`
- "训练一个 LSTM 语言模型并算困惑度" → `nlp_dlm`
- "用注意力机制做中英翻译 demo" → `nlp_seq2seq`
- "生成 XX 主题的教学 Notebook" → 对应主题智能体的 nb 脚本
**方式二 · 命令行直接调用**:
```bash
PY=python # 或 nlp11 环境
$PY .config/agent/nlp_w2v/scripts/nlp_w2v_nb.py --out <目录>/word2vec.ipynb --execute
```
| 参数 | 说明 |
|---|---|
| --out | 输出 .ipynb 路径 |
| --execute | 生成后执行并内嵌结果 |
| --timeout | 执行超时(默认 1200-3600s) |
| --data(仅 seq2seq) | 平行语料路径(cmn.txt);缺省内置玩具语料 |
**方式三 · 作为第⑥⑦环的深化**:九步流程走到特征工程/建模时,需要词向量或深度模型即可横向调用主题智能体。
### 产物位置
本次生成的三份可执行 Notebook 位于 `D:\wks\ai58\0822-word2vec\code\test\`:
`word2vec.ipynb` / `dlm.ipynb` / `seq2seq.ipynb`——Jupyter 打开即含步骤说明、可执行代码与已内嵌的运行结果(损失曲线、困惑度对比、注意力热力图等)。
|
|
### 课程代码目录与理论对照
课程代码集中在 `D:\wks\ai58\0822-word2vec\code\`,各目录对应的理论主题:
| 代码目录 | 内容 | 对应理论主题 |
|---|---|---|
| `language-model/语言模型教程.ipynb` | N-gram/平滑/困惑度 | 主题6 · 统计语言模型基础 |
| `NNLM/神经网络语言模型教程.ipynb` | PyTorch NNLM + CN_Corpus | 主题6 · NNLM(词向量诞生地) |
| `Word2vec/word2vec教程.ipynb` | 从零实现 + gensim + 负采样 | 主题5 · Word2Vec |
| `language-model-deepnet/sequence-components.ipynb` | RNN/LSTM/GRU/Seq2Seq/Attention 组件 | 主题6/7 · 循环网络与注意力 |
| `language-model-deepnet/text_classification_rnn.ipynb` | TF 双向 LSTM 文本分类 | 主题6 · LSTM 分类应用 |
| `seq2seq/Seq2Seq教程.ipynb` | 基础版中英翻译全流程 | 主题7 · Encoder-Decoder |
| `seq2seq/带有注意力机制的Seq2Seq.ipynb` | Bahdanau/Dot + 可视化 | 主题7 · 注意力机制 |
| `test/*.ipynb`(本次生成) | 三主题教学 Notebook | 三主题综合复现 |
### 主题与代码的多对多关系
- 一份 `sequence-components.ipynb` 同时服务主题 6(RNN/LSTM 组件)与主题 7(Seq2Seq/Attention 组件)
- NNLM 是主题 5 与主题 6 的交汇点:它属于语言模型,却"顺手"发明了词向量
- 注意力代码出现在两处:组件级(sequence-components)与系统级(带注意力的Seq2Seq)——先懂组件再懂组装
### 建议学习路径
```text
主题5 word2vec.ipynb(词是怎么变成向量的)
→ 主题6 dlm.ipynb(向量怎么组成句子概率 + 循环网络)
→ 主题7 seq2seq.ipynb(句子怎么翻译成句子 + 注意力)
→ 回看原课程 notebook 对照工程细节(cn语料、真实数据训练)
```
每一步都可在 Jupyter 中动手执行验证——**理解理论 + 实际操作 + 验证效果**三位一体。
|
|
### 从生活直觉到 Q/K/V
**火车站类比**:嘈杂车站里,大脑自动聚焦朋友喊你名字的声音,忽略广播与鸣笛——注意力的本质就是**智能聚焦**。
**图书馆类比**(理解 Q/K/V 的最佳抓手):
| 角色 | 图书馆里 | 注意力机制中 |
|---|---|---|
| **Query(查询)** | "我要找机器学习的书" | 当前词的查询向量 |
| **Key(索引)** | 书架上所有书的标题 | 每个词的"可被匹配"标识 |
| **Value(内容)** | 每本书的具体内容 | 每个词真正贡献的信息 |
| **注意力权重** | 书名与意图的相关度 | softmax 后的百分比 |
**注意力四步工作流程**:
```text
① 准备:输入 [我, 爱, 学习] → 生成 Q、K、V 向量
② 匹配:Query 与每个 Key 算相似度(点积)
③ 归一:相似度 → softmax → 权重(所有权重和=1)
④ 融合:权重 × Value → 加权求和得输出
```
> **关键洞察**:注意力不是简单的信息过滤,而是**智能的信息整合**——动态聚焦、智能选择、建立联系、高效处理。
> 技术演进:RNN/LSTM(逐词处理,2010-2015)→ 传统注意力(2015-2017,翻译对齐)→ 自注意力(2017-,Transformer/BERT/GPT)。
|
|
### 两者的本质区别(初学者必分清)
| 比较项 | 传统注意力 | 自注意力(Self-Attention) |
|---|---|---|
| 关注范围 | **两个不同序列**之间 | **同一个序列内部** |
| 典型应用 | 中→英翻译的词对齐 | 理解句子内部关系 |
| Q 来源 | 解码器隐状态 | 序列自身 |
| K/V 来源 | 编码器输出 | 序列自身(Q=K=V 同源于 X) |
| 核心能力 | "我"→"I" 软对齐 | "他"→"小明" 指代消解 |
| 处理方式 | 依赖 RNN 顺序隐状态 | 全并行矩阵运算 |
### 动机案例:代词指代
句子"**小明买了苹果,他很开心**"——"他"指谁?
- 传统注意力答不了:它只处理两个序列之间的关系
- 自注意力让句中每个词"环顾四周":计算后"他"的注意力权重集中到"小明"——**指代消解**完成
### 行业场景中的选择
| 场景 | 用哪种 | 原因 |
|---|---|---|
| 机器翻译的词对齐 | 传统(跨序列)注意力 | 两个序列间建立桥梁 |
| 阅读理解/文本分类 | 自注意力 | 理解文档内部关系 |
| 搜索/推荐匹配 | 两者皆可 | query-doc 用跨序列,文档理解用自注意力 |
| 大语言模型底座 | 自注意力 | 并行 + 长距离依赖,可规模堆叠 |
**自注意力四大优势**:①建立词语关系(语法/语义/指代)②并行处理 ③任意两位置路径长度=1(长距离依赖)④上下文动态词义。
|
|
### 数学五步(含维度表)
核心公式:
$$\text{Attention}(Q,K,V)=\text{softmax}(QK^\top/\sqrt{d_k})\,V$$
| 步骤 | 公式 | 维度 | 含义 |
|---|---|---|---|
| 1 输入 | $X$ | $n\times d$ | n 个词,每词 d 维 |
| 2 投影 | $Q=XW^Q,\ K=XW^K,\ V=XW^V$ | $n\times d_k$ | 三个可学习矩阵($d\times d_k$) |
| 3 分数 | $S=QK^\top/\sqrt{d_k}$ | $n\times n$ | 每对词的匹配度 |
| 4 归一 | $\alpha_{ij}=\exp(S_{ij})/\sum_k\exp(S_{ik})$ | $n\times n$ | **每行和为 1** |
| 5 输出 | $O=\alpha V$ | $n\times d_v$ | 按权重融合 value |
> **为什么要三个投影矩阵而非 X 直接点积**:让模型从不同子空间、以不同投影分别理解"查询/匹配/内容"三种角色,表达力更强。
### 七步手算(课程标准样例,面试常考)
3 个 4 维输入,W 为 4×3,全程矩阵乘法:
```text
① x=[[1,0,1,0],[0,2,0,2],[1,1,1,1]] 准备输入
② w_key/w_query/w_value (4×3) 初始化参数
③ keys=x@w_key, querys=x@w_query, values=x@w_value
④ attn_scores = querys @ keys.T 第1行 [2,4,4]
⑤ softmax([2,4,4]) ≈ [0.063, 0.468, 0.468] 归一化
⑥ weighted = 每个 value × 对应权重 加权
⑦ output = 按行求和 → 3×3 输出 得新表示
```
要点:value 的维度=输出维度;真实网络中 W 用 Xavier/Kaiming 随机初始化,课程固定整数仅为便于手算核对。
|
|
### 为什么除以 √d_k(缩放因子)
$d_k$ 维向量点积的方差随维度线性增长(各分量独立时方差=$d_k$)。分数过大 → softmax 进入**饱和区**(输出趋近 one-hot)→ 梯度≈0 → 模型学不动。除以 $\sqrt{d_k}$ 把方差压回 1:
| 维度 d_k | 不缩放最大权重 | 缩放后最大权重 |
|---|---|---|
| 4 | ~0.4 | ~0.4 |
| 64 | ~0.7 | ~0.3 |
| 1024 | ≈1.0(one-hot 饱和) | ~0.2(平滑) |
> 维度越大,不缩放越饱和;Transformer 默认 $d_k=64$ 正是稳定区间。
### 注意力可视化 = 可解释性红利
注意力权重矩阵 `attn[i,j]` = 第 i 个词对第 j 个词的关注度,画成**热力图**就能直接"看到模型在看什么"——这是注意力相对黑盒 RNN 的核心红利。
```python
weights = torch.softmax(Q @ K.T / math.sqrt(d_k), dim=-1) # 权重矩阵
plt.imshow(weights.numpy(), cmap="Blues") # 热力图
```
对角线高亮 = 对齐证据;"他"行聚焦"小明"列 = 指代证据。
|
|
### 行业应用场景速览
| 场景 | 注意力作用 | 落地形态 |
|---|---|---|
| 机器翻译 | 目标词对齐源词 | 在线翻译 API |
| 阅读理解/QA | 问题词对齐文章段落 | 智能问答 |
| 搜索引擎 | 查询意图匹配文档 | 语义搜索/query 改写 |
| 文本摘要 | 聚焦关键句 | 新闻/会议纪要摘要 |
| 情感分析 | 聚焦情感词("服务差"权重大) | 评论审核/舆情 |
| 智能客服 | 聚焦意图关键词 | 工单自动分类 |
| 图像描述(多模态) | 文本词对齐图像区域 | 看图说话 |
### 常见坑(教学与面试高频)
| 坑 | 现象 | 对策 |
|---|---|---|
| softmax 维度错 | 权重含义颠倒 | 恒用 `dim=-1`(对 key 维归一化) |
| 忘记缩放 | 维度大时训练不动 | 除以 $\sqrt{d_k}$ |
| 矩阵转置错 | 维度不匹配报错 | `q @ k.transpose(-2,-1)` |
| mask 时机错 | softmax 后再 mask 无效 | **softmax 前** `masked_fill(-1e9)` |
| 权重解读过度 | 把热力图当因果 | 权重=相关性,非因果,仅作参考 |
| 手算向量当语义 | 以为演示向量有真实语义 | 真实向量由 Embedding 学习得到 |
|
|
### 为什么需要位置编码
注意力机制是**"位置无关"**的——"我爱你"与"你爱我"算出的注意力完全相同。
类比:收到一堆打乱顺序的拼图碎片,每片只有图案没有位置标注,拼不出完整图片。Transformer 面临同样问题,必须把位置信息**注入**词向量。
### sin/cos 位置编码公式
$$PE_{pos,2i}=\sin(pos/10000^{2i/d}),\qquad PE_{pos,2i+1}=\cos(pos/10000^{2i/d})$$
**为什么这样设计(四个性质)**:
1. **周期性**:不同维度不同频率(低维高频看局部、高维低频看全局),可外推到更长序列
2. **唯一性**:每个位置编码独一无二,像指纹
3. **有界性**:值域 [-1,1],不随位置爆炸
4. **相对位置线性**:$PE_{pos+k}$ 可表示为 $PE_{pos}$ 的线性函数($\sin(\alpha+\beta)$ 展开),模型容易学"相对距离"
### 两个工程细节(课程实现)
```python
x = x * math.sqrt(self.d_model) # 细节1:词嵌入先放大,防止被 PE 淹没
x = x + self.pe[:, :x.size(1)] # 细节2:PE 直接"加"到 embedding(register_buffer 不训练)
```
> 替代方案:可学习位置嵌入(BERT 用)。sin/cos 版优点=零参数+可外推;学习版优点=更灵活。
|
|
### 类比:单头=一个人读文章,多头=多人多角度共读
课程实例"小明昨天买了苹果":**头1**关注主语关系("小明"是主语)、**头2**关注时间信息("昨天"表过去时)、**头3**关注动作-宾语("买-苹果")。
### 公式与拆头流程
$$\text{head}_i=\text{Attention}(QW_i^Q,\ KW_i^K,\ VW_i^V),\qquad \text{MultiHead}=\text{Concat}(\text{head}_1,\dots,\text{head}_h)\,W^O$$
```text
Q,K,V 线性变换 → 拆成 h 份 [B,L,d_model]→[B,h,L,d_k] → 各头并行算注意力
→ concat 拼回 [B,L,d_model] → 过输出矩阵 W^O
```
**拆头代码链(最易错)**:`view(B,L,h,d_k)` → `transpose(1,2)` → 注意力 → `transpose(1,2).contiguous().view(B,L,d_model)`
(transpose 后内存不连续,必须先 `contiguous()` 再 view)
### 堆叠与注意事项
- **堆叠理解**:单层=初级理解(第一次读抓表面);多层=逐层深入(句子关系→逻辑篇章)
- **三个优势**:信息丰富性(不同头捕获不同关系)/ 鲁棒性(单头出错有冗余)/ 子空间并行
- **不是越多越好**:层数太多→过拟合+梯度问题+成本指数增长;参考 BERT-base=12层12头
- **参数量**:$4d^2$/层($W^Q,W^K,W^V,W^O$ 各 $d^2$)
|
|
### 两种掩码必须分清(课程重点难点)
| 掩码 | 用在哪 | 解决什么 | 构造 |
|---|---|---|---|
| **Padding Mask** | Encoder + Decoder | 补齐的 PAD 位不应参与注意力 | 序列 [<PAD>,<PAD>] → mask [1,1,1,0,0] |
| **Look-ahead Mask** | 仅 Decoder 自注意力 | 预测第 i 词不能"偷看"后面内容 | 上三角矩阵(对角线以上=0) |
### 实现关键
掩码必须在 **softmax 之前**生效:
```python
scores = q @ k.transpose(-2, -1) / math.sqrt(d_k)
scores = scores.masked_fill(mask == 0, -1e9) # 掩盖位 → 负无穷
weights = F.softmax(scores, dim=-1) # softmax 后权重≈0
```
Decoder 实际使用的掩码 = padding **&** nopeak(按位与):
```python
trg_mask = (trg != 0).unsqueeze(1).unsqueeze(2) & torch.tril(torch.ones(L, L)).bool()
```
> 类比:考试做题(Decoder 生成)不能偷看后面的答案;batch 对齐的补齐位(PAD)不算有效信息。
|
|
### 残差连接:梯度高速公路
**动机(传话游戏类比)**:信息沿弯弯绕绕的山路从山脚传到山顶,越传越失真——深层网络的**梯度消失**。
$$y = x + F(x)$$
反向传播时梯度可经 $+x$ **直达浅层**,12~24 层堆叠成为可能;网络只需学"增量"而非从头学映射。Transformer 每个子层后都是 Add & Norm。
### LayerNorm:为什么 Transformer 选 LN 而非 BN
**ICS(Internal Covariate Shift)**:每层输入分布随上层参数更新不断变化,上层要反复适应新分布,学习变慢。
| | BatchNorm | LayerNorm |
|---|---|---|
| 归一化轴 | 跨样本、同特征(按列) | 单样本内、跨特征(按行) |
| 依赖 batch | 是(推理用全局统计) | 否 |
| 变长序列 | "我爱你"3词 vs 长句15词,统计量不稳 | 天然适配 |
$$\text{LN}(x)=\gamma\,(x-\mu)/\sqrt{\sigma^2+\epsilon}+\beta\quad(\mu,\sigma \text{ 在最后一维计算})$$
**课程实现为 Pre-LN 变体**:`x = x + dropout(sublayer(norm(x)))`(先归一化再进子层,深层训练更稳)。
|
|
### FFN:信息加工的大脑皮层
类比:多头注意力=**信息收集员**(在图书馆查找资料);FFN=**大脑皮层**(复杂加工分析)+ **知识库**(存储调用)。
注意力输出只是"信息重新加权",真正的"加工理解"发生在 FFN。
$$\text{FFN}(x)=\max(0,\ xW_1+b_1)W_2+b_2,\qquad d_{model}\to d_{ff}\to d_{model}\ (d_{ff}\text{ 常为 }4d)$$
对每个**位置独立**做同一非线性变换(position-wise),逐位置增强表达能力。
### 参数量与计算复杂度(面试/工程必会)
| 模块 | 参数量 | 备注 |
|---|---|---|
| 注意力(每层) | $4d^2$ | Q/K/V/O 四个方阵 |
| FFN(每层) | $2d\cdot d_{ff}=8d^2$ | **占单层 2/3,模型压缩首选** |
| Embedding | $V\times d$ | 词表大时可观(BERT 30k×768≈23M) |
| 单层合计 | $\approx 12d^2$ | BERT-base 总约 110M |
**计算复杂度**:自注意力 $O(n^2\cdot d)$(n=序列长度,n 增 8 倍→计算量增 64 倍,长文本昂贵);FFN $O(n\cdot d^2)$。
> **记忆**:参数量决定模型大小(显存);复杂度决定训练/推理速度——长文本优化(稀疏注意力/滑窗)都围绕 $n^2$ 做文章。
### 六组件设计动机总表
| 组件 | 解决的问题 | 一句话类比 |
|---|---|---|
| 位置编码 | 注意力没有顺序感 | 拼图碎片标注位置 |
| 多头注意力 | 单一视角看句子 | 多人多角度共读 |
| 掩码 | PAD 干扰 / 偷看未来 | 考试不能看后面的答案 |
| 残差 | 深层梯度消失 | 梯度高速公路 |
| LayerNorm | 内部协变量偏移 | 每层楼地面调平 |
| FFN | 加权≠理解,缺非线性加工 | 大脑皮层/知识库 |
|
|
### Transformer 全景数据流
```text
源句 src ──► Embedder ──► PositionalEncoder ──► Encoder×N ──► enc_outputs ──┐
│ 交叉注意力
目标句 trg ─► Embedder ──► PositionalEncoder ──► Decoder×N ◄────────────────┘
│
Linear(trg_vocab) + Softmax ──► 逐词概率
```
- **Encoder 职责=理解**:单层 = 多头自注意力 → Add&Norm → FFN → Add&Norm
- **Decoder 职责=生成**:单层 = **Masked** 自注意力 → Add&Norm → **交叉注意力** → Add&Norm → FFN → Add&Norm
- **交叉注意力(最易错点)**:中间那个注意力层,**K、V 来自 Encoder 输出,Q 来自 Decoder 自身**——口诀"**Q 问自家,KV 查编码器**"
- **堆叠**:`get_clones(module, N)` 用 `copy.deepcopy` 造 N 份**独立参数**的层(共享同一对象会导致梯度串扰)
### 与其他主题的边界(初学者区分指南)
| 主题 | 关注点 | 一句话 |
|---|---|---|
| 主题9 注意力 | 单个注意力怎么算 | 相似度+归一化+加权 |
| 主题10 组件 | 每块积木为什么存在 | 动机+公式+拆解 |
| 主题11 系统(本主题) | 积木怎么装成能训练的模型 | 数据流+训练+推理 |
|
|
### 数据接入:分词器双轨
课程原版用 spacy(`zh_core_web_sm` 中文 / `en_core_web_sm` 英文),但语言模型需联网下载、离线常失败——工程上必须**可降级**:
```python
def tokenize_en(s): return s.lower().split() # 英文空格分词
def tokenize_zh(s): return list(s) # 中文字符级(训练/推理必须一致)
```
词表四特殊符号沿用 Seq2Seq 约定:`<PAD>=0 <SOS>=1 <EOS>=2 <UNK>=3`(PAD=0 与损失 ignore_index 对齐)。
### 训练三大铁律(与 Seq2Seq 一致)
| 铁律 | 内容 | 违反后果 |
|---|---|---|
| ① 错位一格 | 输入 `trg[:, :-1]`、标签 `trg[:, 1:]` | 模型"偷看答案",loss 虚低但不会翻译 |
| ② PAD 不算损失 | `CrossEntropyLoss(ignore_index=0)` | 模型浪费容量学补位 |
| ③ 梯度裁剪 | `clip_grad_norm_(1.0)` | 深层训练梯度爆炸 |
### create_masks 与训练循环骨架
```python
src_mask = (src != 0).unsqueeze(1).unsqueeze(2) # [B,1,1,Ls]
trg_mask = (trg != 0).unsqueeze(1).unsqueeze(2) & nopeak # [B,1,Lt,Lt]
preds = model(src, trg_in, src_mask, trg_mask)
loss = criterion(preds.reshape(-1, V), trg_out.reshape(-1))
```
工业实践再加:**label smoothing**(防过度自信)与 **Adam warmup**(原论文 Noam 调度);玩具语料上 loss 稳定下降即管线正确(本次实战 3.53→0.002)。
|
|
### 贪心解码推理
```text
SOS 起步 → 每步 argmax 取最大概率词 → 追加到输入 → 遇 EOS 或达 max_length 停止(双保险)
```
```python
out = model.out(model.decoder(trg, e_outputs, src_mask, trg_mask))
nxt = out[:, -1].argmax(-1).item() # 只取最后一个位置的预测
if nxt == EOS: break
```
> 课程基线为贪心解码;工业界常用 **beam search**(保留 top-k 候选)提升译文质量,本主题不展开。
### 交叉注意力热力图 = 软对齐证据
取最后一层 Decoder 交叉注意力的权重(多头平均),行=译词、列=源词:对角线趋势说明模型翻译时确实"对得上号"——这是 Transformer 可解释性的直接体现。
本次实战(玩具中英语料,46,342 参数,CPU 60 epoch)四句验证全部正确:
```text
'what is your name' → '你叫什么名字'
'i love you' → '我爱你'
'good morning teacher' → '老师早上好'
'how are you' → '你好吗'
```
|
|
### 行业应用场景
| 场景 | Transformer 角色 | 落地形态 |
|---|---|---|
| 机器翻译 | 原始战场(2017 论文任务) | 在线翻译 API |
| 文本摘要 | Encoder-Decoder 架构 | 新闻/会议纪要摘要 |
| 智能客服多轮对话 | 上下文长距离建模 | 对话系统底座 |
| 代码生成 | 序列到序列转换 | Copilot 类工具 |
| 语义搜索 | 双塔/交互式编码 | 搜索相关性排序 |
| 预训练底座 | BERT=Encoder / GPT=Decoder | 一切下游任务的基座 |
### 通向预训练时代(演进地图)
```text
完整 Transformer(本主题)
├─ 只取 Encoder → BERT(理解类任务:分类/NER/QA)
├─ 只取 Decoder → GPT(生成类任务:续写/对话)
└─ 保留双塔 → T5/BART(转换类任务:翻译/摘要)
```
> **理解本主题 = 拿到预训练时代的总钥匙**:BERT/GPT 不是新发明,而是 Transformer 的"半边"按需取用 + 大规模预训练。
|
|
### 课程代码目录与理论对照(D:\wks\ai58\0826-att\)
| 代码文件 | 内容 | 对应理论 |
|---|---|---|
| `课上代码/从注意力到自注意力的教程.ipynb` | 生活类比 + NumPy 演示 + 可视化 | 主题9 · 概念与动机 |
| `.../transformer-all-in-one/self-attention-implementation.ipynb` | 七步手撕(3×4 矩阵全程打印) | 主题9 · 数学五步/手算 |
| `.../transformer-all-in-one/transformer-implementation.ipynb` | spacy 分词→Embedding→PE→MHA→Norm→FFN→Enc/Dec→训练演示 | 主题10+11 · 组件与系统 |
| `课后作业/课后作业/Transformer.ipynb`(+Transformer.png/Block.jpg) | 结构报告 + 同款实现 | 主题11 · 结构对照复习 |
| `8.注意力到Transformer.pptx`(60页,dkmkv2 提取至 extracted/) | 概念/类比/参数量/作业 | 主题9/10 全部 |
| `预习/PREVIEW.pdf`、`思维脑图/*.xmind`(4份) | 预习公式、知识框架 | 全部主题 |
### 本次生成的实战 Notebook(agent 产出,已执行内嵌结果)
| Notebook(`0826-att\code\`) | 生成智能体 | 内容 |
|---|---|---|
| `attention.ipynb` | nlp_attn | 类比→五步手算→七步手撕→缩放实验→指代热力图 |
| `transformer_block.ipynb` | nlp_tblock | 六组件逐一验证 + 参数量计算器 + 单层总装 |
| `transformer.ipynb` | nlp_transformer | 语料→词表→堆叠→训练→翻译+交叉注意力图 |
### 建议学习路径
```text
主题9 attention.ipynb(注意力怎么算)
→ 主题10 transformer_block.ipynb(积木为什么这样设计)
→ 主题11 transformer.ipynb(积木怎么装成系统并训练)
→ 回看课程原版 notebook 对照工程细节(spacy 分词、真实数据)
```
|
|
### nlp skill 新增"注意力三部曲"主题智能体
承接主题8的三个模型智能体(nlp_w2v / nlp_dlm / nlp_seq2seq),本次把 Day17《注意力机制与Transformer应用》课程沉淀为三个**递进依赖**的主题智能体:
| 智能体 | 类别 | 触发词示例 | 沉淀来源 |
|---|---|---|---|
| `nlp_attn` | 概念类 | 注意力/自注意力/QKV/手撕注意力/热力图/代词指代 | 从注意力到自注意力教程 + self-attention-implementation |
| `nlp_tblock` | 组件类 | 位置编码/多头/掩码/残差/LayerNorm/FFN/参数量 | PPT 组件章 + transformer-implementation 组件段 |
| `nlp_transformer` | 系统类 | 完整Transformer/Encoder/Decoder/create_masks/翻译训练 | transformer-implementation 全文 + 课后作业报告 |
### 依赖链(学习顺序即调用顺序)
```text
nlp_attn(概念:注意力是什么、怎么算)
→ nlp_tblock(组件:Transformer 由哪些积木构成、为什么)
→ nlp_transformer(系统:积木怎么装、怎么训练、怎么翻译)
→【桥梁】Day18 预训练主题(BERT/GPT)
```
### 双重作用(与主题8智能体一致)
1. **知识资产库**:`refs/knowledge.md` 沉淀公式/流水线/代码模式/坑/行业场景/**课程数据索引**(PPT 提取稿、预习指南、课上代码、课后作业、思维脑图的路径与用途)
2. **Notebook 工厂**:`scripts/nlp_*_nb.py` 一键生成"步骤说明 + 变量链可执行代码"的教学 ipynb,`--execute` 连执行结果一并内嵌
|
|
### 智能体内部结构(统一四件套)
```text
nlp_attn/ nlp_tblock/ nlp_transformer/
├── AIDEV.md # 主入口:能力边界(能做/❌不做)+ 使用方法 + Notebook 段落契约
├── refs/knowledge.md # 知识库:公式/流水线/代码模式/坑/应用场景/课程数据索引
├── workflows/phase-*.md # 工作流(六段式:目标/前置/动作/产物/自检/交接)
└── scripts/nlp_*_nb.py # Notebook 生成器(可直接运行,输出目录自动创建)
```
### 各智能体的 Notebook 段落契约
| 智能体 | 段落(说明单元 → 代码单元) |
|---|---|
| nlp_attn(7节) | QKV 类比 → 传统vs自注意力 → 五步手算 → 七步手撕 → 缩放实验 → 指代热力图 → 演进 |
| nlp_tblock(7节) | 位置编码 → 多头注意力 → 双掩码 → 残差+LN → FFN → 参数量计算器 → 单层总装 |
| nlp_transformer(7节) | 语料分词 → Embed+PE → 四组件 → Encoder → Decoder+masks → 训练 → 翻译可视化 |
### 能力边界设计(防越权)
每个智能体 AIDEV.md 明确 ❌ 不做清单:nlp_attn 不做多头/掩码(归 nlp_tblock);nlp_tblock 不做训练(归 nlp_transformer);nlp_transformer 不做 beam search/真实大数据(教学基线),spacy 不可用时**必须降级**简易分词器。
|
|
### Notebook 生成步骤
```text
① 读取智能体的 refs/knowledge.md + workflows/phase-*.md(知识与方法来源)
② 调用 scripts/nlp_*_nb.py:
python .config/agent/nlp_attn/scripts/nlp_attn_nb.py --out attention.ipynb --execute
python .config/agent/nlp_tblock/scripts/nlp_tblock_nb.py --out transformer_block.ipynb --execute
python .config/agent/nlp_transformer/scripts/nlp_transformer_nb.py --out transformer.ipynb --execute
③ 脚本把"说明单元 + 代码单元"组装为 .ipynb(变量链前后依赖)
④ --execute 用 nbclient 在内核顺序执行,输出/图表/指标内嵌回 notebook
⑤ 按 workflow 第⑤段自检项逐项核对(数值与课程一致/loss 下降/掩码维度…)
```
### 变量链设计(代码有前后关系的核心)
| Notebook | 变量链 |
|---|---|
| attention.ipynb | word_vecs → Q,K,V → scores → weights → output → attn_matrix |
| transformer_block.ipynb | pe → mha → src_mask/trg_mask → norm_layer → ffn → param_table → block_out |
| transformer.ipynb | pairs → vocabs → loader → embed/pe → encoder/decoder → masks → model → losses → translations |
改上游单元后从该单元起顺序重跑即可——**步骤说明 + 可执行代码 + 内嵌执行结果**三位一体。
### 健壮性设计
- 零外部数据依赖(内置玩具语料/手工向量),`--data` 可接真实 cmn.txt
- 不依赖 spacy(双轨分词器降级);无 `.cuda()` 硬编码,全程 CPU
- 中文字体自适应;固定随机种子;输出目录自动创建
|
|
### 使用方法
**方式一 · 对话路由(推荐)**:直接向 nlp skill 提需求,总控按注册表路由:
- "注意力机制是什么/帮我手撕 self-attention" → `nlp_attn`
- "讲一下位置编码/多头/掩码/LayerNorm" → `nlp_tblock`
- "从零实现一个能翻译的 Transformer" → `nlp_transformer`
- "生成注意力/Transformer 主题教学 Notebook" → 对应智能体的 nb 脚本
**方式二 · 命令行直接调用**:
```bash
python .config/agent/nlp_transformer/scripts/nlp_transformer_nb.py --out <目录>/transformer.ipynb --execute
```
| 参数 | 说明 |
|---|---|
| --out | 输出 .ipynb 路径(目录不存在自动创建) |
| --execute | 生成后执行并内嵌结果 |
| --timeout | 执行超时(1200-1800s) |
| --data(transformer) | 平行语料路径(cmn.txt);缺省内置玩具语料 |
### 产物位置
- 流程版:`D:\wks\ai58\0826-att\test\`(attention / transformer_block / transformer 三份 + 处理过程 README)
- 正式版:`D:\wks\ai58\0826-att\code\`(agent 健壮性增强后重新生成,结构一致)
三份 notebook 均已执行验证:手算数值与课程一致([2,4,4]→softmax≈[0.063,0.468,0.468])、Transformer 训练 loss 3.53→0.002、四句玩具翻译全部正确。
|
|
### dkmkv2:课件文档 → Markdown 的提取搭档
课件的 PPT/PDF 知识提取由 dkmkv2 skill 完成(本次已同步增强健壮性):
```bash
python .config/skills/dkmkv2/scripts/any2md.py -r \
-i "ai58/0826-att/8月26日-Day17-注意力机制及应用" -o ".../extracted"
```
**三类文件分流(本次增强)**:
| 文件类型 | 处理 | 本次课件实例 |
|---|---|---|
| 可转换文档(pptx/pdf/docx) | 提取为 Markdown | 60页PPT + PREVIEW.pdf → 2 份 md(成功 3) |
| 直通文本(.md/.txt) | 跳过(可 --copy-passthrough 复制) | PREVIEW.md、README.md |
| 资产文件(图片/ipynb/zip/xmind) | 跳过**不计失败** | ipynb×5、图片、xmind×4(共跳过 19) |
**其他增强**:`-r` 递归子目录;自动过滤 `__MACOSX`、`.ipynb_checkpoints`、`._*`、`.DS_Store` 等噪音;报告按成功/跳过/失败三类统计,跳过不影响退出码。
### 协作模式(nlp 与 dkmkv2 的分工)
```text
dkmkv2:文档 → Markdown(格式转换层,"把课件读出来")
↓ extracted/*.md 作为课程数据资产
nlp 主题智能体:Markdown + ipynb → 知识沉淀(refs/knowledge.md)→ Notebook 生成(scripts/*_nb.py)
(知识加工层,"把知识用起来")
```
|
|
### 为什么需要预训练——一词多义的死结
**静态词向量(Word2Vec/GloVe)的致命短板**:每个词只有一个固定向量——
```text
"I went to the bank to deposit money" → bank = 银行
"We sat by the river bank" → bank = 河岸
Word2Vec 给 "bank" 的却是同一个向量(歧义无解)
```
**解法 = 上下文动态表示**:同一个词在不同句子,由周围词经注意力加权实时算出不同表示
($\text{output}_i=\sum_j \alpha_{ij} v_j$,权重随上下文变化)——这正是主题9 注意力机制的直接延伸。
### 预训练+微调两阶段范式(GPT 确立,BERT 发扬光大)
```text
阶段1 预训练:大规模无标签文本 → 自监督学习语言通用表示(语法/语义/知识)
阶段2 微调: 保留预训练参数初始化 + 替换任务输出层 → 少量标注数据训练
```
| 优势 | 说明 |
|---|---|
| 数据 | 充分利用海量无标签文本,减少对标注数据依赖 |
| 效率 | 一个底座适配 N 种下游任务 |
| 效果 | 发布即刷新多项 NLP 任务最佳成绩 |
**微调三原则**:小学习率(2e-5 级)、少轮次(2-4 epoch)、只换任务头(防灾难性遗忘)。
### 与前面主题的关系(初学者定位)
主题5 词向量(静态表示)→ 主题6-7 序列建模(RNN/Seq2Seq)→ 主题9-11 注意力与 Transformer(架构)
→ **本主题:把 Transformer 的一半拿出来大规模预训练**(BERT 取 Encoder,GPT 取 Decoder)。
|
|
### BERT = 只取 Transformer 的 Encoder(双向理解)
信息流向对比:
```text
GPT: [→→→→→] 单向,只能看左边的词
BERT: [↕↕↕↕↕] 深层双向,所有层同时看到左右全部上下文
```
### 输入表示:三嵌入相加 + 特殊令牌
```text
输入: [CLS] 我 喜欢 吃 苹果 [SEP] 你 呢 [SEP]
Segment: A A A A A A B B B ← 区分句A/句B
Position:0 1 2 3 4 5 6 7 8 ← 可学习位置嵌入
最终输入 = Token Emb + Segment Emb + Position Emb(逐元素相加)
```
| 特殊令牌 | 作用 |
|---|---|
| [CLS] | 句首;其位置向量接分类头 |
| [SEP] | 分隔两个句子 |
| [MASK] | MLM 预训练的掩码 |
| [PAD] | batch 补齐 |
**WordPiece 子词分词**:词与字之间的折中——常见词整体保留,罕见词拆子词("playing"→"play"+"##ing"),缓解 OOV。
### 预训练双任务(核心创新)
**任务1 MLM 掩码语言模型**:随机掩盖 15% 的词让模型预测 → 迫使同时利用左右上下文。
**80/10/10 策略**(防预训练与微调分布不匹配):
```text
选中词 "开心":80% → 我今天很[MASK] | 10% → 随机词"悲伤" | 10% → 保持"开心"
```
$$L_{MLM}=-\sum_{i \in M}\log P(x_i \mid x_{\backslash M})$$(只计算被掩码位置的交叉熵)
**任务2 NSP 下一句预测**:50% IsNext(原文连续两句)/ 50% NotNext(随机拼凑),学句间关系(服务 QA/NLI):
$$L_{NSP}=-[y\log p+(1-y)\log(1-p)],\qquad L = L_{MLM} + L_{NSP}$$
### 架构类族(课程 PyTorch 实现)
`BERTEmbeddings`(三嵌入)→ `BERTEncoder`(N×BERTLayer:MHA→Add&LN→FFN→Add&LN)→ `BERTPooler`(取 [CLS] 过 tanh)→ 任务头 `BERTForPreTraining` / `...SequenceClassification` / `...TokenClassification`。
|
|
### GPT = 只取 Transformer 的 Decoder(单向生成)
GPT(Generative Pre-trained Transformer,OpenAI):GPT-1(2018, 12层) → GPT-2(2019, 15亿参数) → GPT-3(2020, 1750亿, 96层) → GPT-4(2023, 多模态)。
### 核心机制:掩码自注意力 + 自回归
- **掩码自注意力**:上三角掩码保证位置 i 只能看到 ≤i 的词(主题10 的 Look-ahead Mask 在这里是唯一主角)
- **自回归目标**:预测下一个词
$$P(x)=\prod_{i=1}^{n}P(x_i \mid x_1,\dots,x_{i-1})$$
- **损失**:逐位交叉熵(每个位置都产生监督信号)
### 单层结构(Decoder 层去掉交叉注意力)
```python
def transformer_decoder_layer(x, mask):
attn = multi_head_attention(x, x, x, mask) # 掩码自注意力(信息单向流动)
x = layer_norm(x + attn) # 残差 + LayerNorm
ffn = feed_forward_network(x) # FFN
return layer_norm(x + ffn)
```
### 微调与生成
- `GPTForClassification`:GPT 主干 + 取**最后一个 token** 的表示接分类头
- 生成推理:逐词 argmax/采样追加 → 停止符或长度上限(与主题7/11 的贪心解码同源)
- 常见 HF 模型:`gpt2`(英文)/ `uer/gpt2-chinese-cluecorpussmall`(中文)
> 理解 GPT 的意义:今天的 ChatGPT 就是在本主题架构上 + 指令微调 + RLHF 规模化而来。
|
|
### BERT vs GPT 七维对比
| 特性 | GPT | BERT |
|---|---|---|
| 架构 | 仅 Decoder | 仅 Encoder |
| 注意力 | 单向(掩码自注意力) | 双向(完全自注意力) |
| 预训练目标 | 自回归(预测下一个词) | MLM + NSP |
| 信息流向 | 只能看左侧 | 左右全看 |
| 适合任务 | 生成式:文本生成/对话/续写 | 理解式:分类/问答/NER/匹配 |
| 上下文感知 | 有限(仅前文) | 完整(全句) |
| 代表模型 | GPT-2/3/4、ChatGPT | BERT、RoBERTa、ERNIE |
> **一句话记忆**:BERT 做完形填空(双向看全句),GPT 做接龙写作(只看左边)。
### 行业场景选型
| 场景 | 选型 | 原因 |
|---|---|---|
| 情感分析/工单分类 | BERT 系 | [CLS]+分类头,少量标注即可微调 |
| NER/序列标注 | BERT 系 | TokenClassification 头逐词打标 |
| 问答/阅读理解 | BERT 系 | 起止位置预测头 |
| 语义搜索/相似度 | BERT 系(SBERT) | 双塔向量召回 |
| 对话/文案/代码生成 | GPT 系 | 自回归生成是本职 |
| 翻译/摘要 | T5/BART | 保留 Encoder+Decoder 全家桶 |
| RAG 检索增强 | 两者结合 | BERT 类做向量检索,GPT 类做答案生成 |
### 常见坑
| 坑 | 对策 |
|---|---|
| 用 BERT 做长文本生成 | BERT 是理解模型,生成选 GPT 系 |
| MLM 全用 [MASK] 替换 | 必须 80/10/10,否则预训练-微调分布不匹配 |
| 微调学习率过大 | 2e-5 级起步,防灾难性遗忘 |
| [CLS] 直接当句向量上生产 | 句向量用 mean-pooling 或 SBERT |
| BERT 超长文本硬截断 | 512 上限;长文用滑窗/层级池化 |
|
|
### HuggingFace 工具链六件套(工程落地)
| 组件 | 作用 | 课程 notebook(huggingface-tutorials/) |
|---|---|---|
| tokenizer | 分词/padding/truncation/特殊令牌 | 2.tokenizer |
| datasets | 数据集加载(ChnSentiCorp 中文情感) | 3.datasets |
| metrics | 评估指标 | 4.metrics |
| pipeline | 一行代码推理 | 5.pipeline |
| model | 模型加载与结构 | 6.model |
| Trainer | 训练封装(训练/评估/保存) | 10.trainer / 11.中文分类_CUDA |
```python
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tok = AutoTokenizer.from_pretrained("bert-base-chinese")
inputs = tok(texts, padding=True, truncation=True, return_tensors="pt")
# pipeline 一行填空:pipeline("fill-mask", model="bert-base-chinese")("今天天气很[MASK]")
```
### 课程代码目录与理论对照(D:\wks\ai58\0829-预训练\)
| 代码文件 | 内容 | 对应理论 |
|---|---|---|
| `课上代码/bert教程.ipynb` | 背景/WordPiece/MLM+NSP/PyTorch 实现/情感分析实战 | 本主题 BERT 全部 |
| `课上代码/GPT教程.ipynb` | 范式/架构/掩码自注意力/训练/对比 BERT | 本主题 GPT 全部 |
| `huggingface-tutorials/1-15` | 安装→tokenizer→datasets→pipeline→中文分类/填空/句关系→trainer→大模型使用与微调 | 工具链落地 |
| `huggingface-tutorials/data/ChnSentiCorp/`(arrow) | 中文情感数据集 | 微调实战数据 |
| `预习/从Transformer到BERT预习资料.md`(+pdf) | 演进预习 | 范式动机 |
### 本次生成产物(nlp_pretrained 智能体)
`D:\wks\ai58\0829-预训练\code\pretrained.ipynb`(已执行内嵌结果):一词多义演示 → 玩具 BERTEmbeddings →
玩具 MLM 训练(loss 4.00→0.02,"今天天气很[MASK]"→预测"好")→ 玩具 GPT 自回归训练与续写 →
七维对比 → HF 工具链(离线自动降级)。解析中间产物:`0829-预训练\parsed\`(资产清单)、`extracted\`(PDF 提取稿)、`outlines\`(notebook 大纲)。
|
|
### xy_doc_jiexi —— 通用课件解析元智能体
**定位**:把一个课程目录(PPT/PDF/预习文档/课上 ipynb/课后作业/脑图/数据)转化为三类产物——
| 产物 | 形态 | 落点 |
|---|---|---|
| ① 分类主题智能体 | 四件套(AIDEV/refs/workflows/scripts) | `.config/agent/nlp_<主题>/` |
| ② 可执行教学 Notebook | 步骤说明 + 变量链代码 + 内嵌执行结果 | `<课程>/code/*.ipynb` |
| ③ web73 文档主题 | 五段式章节(本页主题9-13 即其产物) | `web73/views/xuetu/*.html` |
**跨 skill 注册**:同时注册在 **xyvn** skill(课件解析入口)与 **nlp** skill(处理 NLP 课程资料、优化其下 agent)——
任何 skill 的课程资料解析需求都可路由到它。
### 为什么需要它(方法论复用)
主题8(Day16)与主题9-12(Day17)的智能体生成过程本质上是**同一套流程的两次手工执行**。
xy_doc_jiexi 把这套流程本身沉淀为智能体:**知识、方法、流程都进 agent,后面直接用,不重复实现**。
本主题(主题13 Day18)即是它上岗后的第一个实战产出。
|
|
### 七步法(标准解析流程)
```text
① 扫描分类 scan_course.py -i <课程目录> → parsed/course_inventory.md(六分类资产清单)
② 文档提取 dkmkv2 any2md.py -r → extracted/*.md(PPT/PDF→Markdown)
③ 代码大纲 nb_outline.py -i <目录> -r → outlines/*.md(标题层级+代码摘要)
④ 整体理解 读提取稿+大纲,按知识内聚度分类(每类 = 一个未来智能体)
⑤ 沉淀智能体 每类建四件套;课程文件路径写入 refs/knowledge.md「数据索引」
⑥ 生成验证 各智能体 *_nb.py --out <课程>/code/ --execute;按自检项核对
⑦ 文档输出 按 web73 五段式追加主题(本页主题9-13 的生成方式),过避坑清单
```
### 两个主资产脚本
| 脚本 | 作用 |
|---|---|
| `scripts/scan_course.py` | 递归盘点课程目录:文档/代码/数据/脑图/图片/直通文本六分类,自动滤噪(`__MACOSX`/`.ipynb_checkpoints`/`._*`),输出 md+json |
| `scripts/nb_outline.py` | 提取 ipynb 大纲(markdown 标题 + 代码首行 + 执行状态)——大 notebook 先大纲后精读,不爆上下文 |
**分类经验轴**:概念/组件/系统(递进,Day17 实践);按模型主题(并列,Day16 实践);按任务阶段(黄金九步法)。
|
|
### 智能体生成方法(七条铁律)
1. **先分类再动手**:整体理解后按知识内聚度分类,每类一个 agent,命名 `<前缀>_<主题>`
2. **能力边界先行**:AIDEV.md 写清"能做 / ❌不做",防越权、明交接(如 nlp_attn 不做多头、nlp_tblock 不做训练)
3. **知识资产化**:`refs/knowledge.md` = 唯一权威知识源(类比/公式/代码模式/坑/场景/**课程数据索引**),后续直接读不重复整理
4. **代码生成器化**:不手抄课程代码,提炼为"变量链 + 步骤说明"的 `*_nb.py` 生成器
5. **零依赖可跑**:内置玩具数据保证任何环境可执行;真实数据走 `--data`;可选依赖自动降级
6. **注册闭环**:skill 注册表 + README 体系图同步更新
7. **验证闭环**:`--execute` 零 error + workflow 自检项 + 关键数值复现(如手算 [2,4,4]→softmax≈[0.063,0.468,0.468])
### Notebook 设计契约
- 说明单元与代码单元**交替**;代码单元变量链前后依赖(`pairs→vocabs→loader→model→losses`)
- 中文字体自适应 + 固定随机种子 + 无 `.cuda()` 硬编码 + 输出目录自动创建
- 训练类必须打印 loss 首尾对比 + 曲线;验证类必须 assert 维度/数值
|
|
### 使用方法
**方式一 · 对话路由(推荐)**:
- "解析 D:\wks\ai58\XXXX 这个课程目录,提取知识点" → xyvn/nlp skill 路由到 `xy_doc_jiexi`
- "把这个课程的知识沉淀为 nlp 智能体" / "生成这门课的教学 Notebook" → 同上
**方式二 · 命令行直接调用(七步法逐步执行)**:
```bash
# ① 扫描分类
python .config/agent/xy_doc_jiexi/scripts/scan_course.py -i "ai58/0829-预训练"
# ② 文档提取(dkmkv2)
python .config/skills/dkmkv2/scripts/any2md.py -r -i "ai58/0829-预训练" -o "ai58/0829-预训练/extracted"
# ③ notebook 大纲
python .config/agent/xy_doc_jiexi/scripts/nb_outline.py -i "<课程>/课上代码" -r
# ④-⑦ 由智能体按 workflows/phase-doc-jiexi.md 执行
```
### 资源索引
| 资源 | 路径 |
|---|---|
| 主入口 | `.config/agent/xy_doc_jiexi/AIDEV.md` |
| 方法论详述 | `.config/agent/xy_doc_jiexi/refs/methodology.md` |
| 模板与避坑清单 | `.config/agent/xy_doc_jiexi/refs/templates.md` |
| 工作流(六段式) | `.config/agent/xy_doc_jiexi/workflows/phase-doc-jiexi.md` |
|
|
### 实战案例(本方法论的出处与验证)
| 课程 | 分类 | 产出智能体 | Notebook 产物 |
|---|---|---|---|
| Day16 词向量/语言模型/Seq2Seq | 按模型主题(并列) | nlp_w2v / nlp_dlm / nlp_seq2seq | `0822-word2vec/code/test/*.ipynb` |
| Day17 注意力机制与Transformer | 概念/组件/系统(递进) | nlp_attn / nlp_tblock / nlp_transformer | `0826-att/{test,code}/*.ipynb`(已执行) |
| Day18 大规模预训练模型BERT | 预训练类(BERT/GPT/HF 三模块) | nlp_pretrained | `0829-预训练/code/pretrained.ipynb`(已执行) |
### 对 dkmkv2 的健壮性反哺(解析实战中踩坑修复)
| 问题 | 修复 |
|---|---|
| 批量不递归子目录 | 新增 `-r` 递归(自动跳过噪音目录) |
| `.md` 直通文件计为失败 | 三类分流:转换/跳过/失败;跳过不影响退出码 |
| 图片/ipynb/xmind 等资产计为失败 | 资产扩展名集合自动跳过(含 `.arrow` HF 缓存) |
| `__MACOSX`/`._*` 噪音混入 | 噪音文件/目录过滤表 |
### 这套方法论的通用价值
任何"一堆课件 → 结构化知识 + 可执行代码 + 文档页面"的需求都可复用:企业内训资料沉淀、论文配套代码解读、开源项目教程生成——**先扫描分类,再逐类沉淀智能体,最后以 Notebook 与文档页面双形态交付**。
|