news 2026/4/18 5:33:14

词向量深度笔记:从 OneHot 到 Word2Vec(逻辑链 + 代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
词向量深度笔记:从 OneHot 到 Word2Vec(逻辑链 + 代码)

词向量深度笔记:从 OneHot 到 Word2Vec(逻辑链 + 代码)

前言

这是一篇关于 NLP 基石——词向量(Word Embeddings)的系统笔记,内容来源于课程讲义、教材阅读和个人实践整理。

本文的核心目标是讲清楚逻辑链条:为什么需要词向量?OneHot 哪里不够?Word2Vec 如何改进?FastText 又解决了什么新问题?每个技术点都会按照"问题 → 朴素方案 → 局限 → 改进思路 → 公式/定义 → 直觉解释 → 工程实践"的路径展开。

掌握这些,你就理解了现代 NLP 模型是如何"读懂"文字的第一步。


1. 词向量基础:为什么需要向量化?

逻辑链条

  • 问题:计算机只认识数字,无法直接处理"苹果""香蕉"这种符号。
  • 需求:必须把文本转化为计算机可处理的向量形式
  • 目标:让模型理解文字的含义,而不仅仅是把它们当成编号。

词向量的作用

将离散的单词映射为低维、稠密、可学习的实数向量,用于刻画词的语义句法特征。

为什么重要

  • 让自然语言变成机器可处理的向量形式,是几乎所有 NLP 神经模型的输入基础。
  • 通过向量距离与方向表达词间相似度与类比关系(如king−man+woman≈queen \text{king} - \text{man} + \text{woman} \approx \text{queen}kingman+womanqueen)。
  • 显著优于 OneHot 等手工编码,是现代 NLP 性能提升的关键一步。

2. OneHot 编码:最简单的词表示

2.1 什么是 OneHot?

逻辑链条
  • 朴素方案:用数字编号(1=红色,2=蓝色,3=绿色)。
  • 局限:这会让模型误以为"3 > 2 > 1",即"绿色比红色大",这在分类问题中是错误的。
  • 改进方案OneHot(独热编码)——每个类别占据独立的一维,彼此正交。
定义

将每个词转换为一个只包含0 和 1的二进制向量。向量中只有一个位置为 1(对应该词在词表中的索引),其余位置均为0

直觉理解

想象一个巨大的开关面板,词表里有多少个词,就有多少个开关。对于"红色"这个词,只有第 1 个开关是开的;对于"蓝色",只有第 2 个开关是开的。它们在空间里是互相垂直的,完全独立。

示例代码
importnumpyasnpfromsklearn.preprocessingimportOneHotEncoder# 示例数据:包含三个类别的列表categories=['红色','蓝色','绿色','蓝色','红色']categories=np.array(categories).reshape(-1,1)# 创建 OneHotEncoder 对象encoder=OneHotEncoder(sparse_output=False)one_hot_encoded=encoder.fit_transform(categories)print("One-Hot 编码结果:")print(one_hot_encoded)# 输出:# [[0. 0. 1.] <-- 红色# [0. 1. 0.] <-- 蓝色# [1. 0. 0.] <-- 绿色# [0. 1. 0.] <-- 蓝色# [0. 0. 1.]] <-- 红色print("类别顺序:",encoder.categories_)### 2.2 为什么需要 OneHot?(三大理由)|理由|解释||:---|:---||不排队|不用123这种数字,避免让本来没大小的类别"假装有顺序"||好算账|变成0/1向量,就能在"坐标系"里算距离、点积,让模型听得懂这些特征||能一起算|和别的数值特征放在一起做归一化、训练模型都很自然、很方便|### 2.3 OneHot 的优点与缺点#### 优点-解决了分类器不好处理**属性数据**的问题。-在一定程度上起到了**扩充特征**的作用。-值只有01,不同类型存储在**垂直的空间**(几何上等距)。#### 缺点-**维度灾难**:词表有10万个词,每个词就是长度10万的向量(99,9990),极度稀疏。-**无法表达语义关系**:任意两个不同词的余弦相似度都是0。 $$ \text{similarity}(\text{麦当劳},\text{肯德基})=0$$ 模型无法知道"麦当劳""肯德基"是相似的。---## 3. Word2Vec:从符号到语义的飞跃### 逻辑链条-**问题**:OneHot 太稀疏,且完全不懂语义。-**核心假设****分布假设**——一个词的含义由它周围的词(上下文)决定。-**方案**:通过预测上下文或中心词,训练出稠密的词向量。### Word2Vec 定义一种用于生成**词向量(Embedding)**的模型,通过训练**浅层神经网络**来学习词向量表示。### 两种架构1.**CBOW**(Continuous Bag of Words):用上下文预测中心词。2.**Skip-Gram**:用中心词预测上下文。---### 3.1 CBOW 模型:用周围词猜中间词#### 逻辑链条-**任务**:做"完形填空"——给你前后文,猜中间的空是什么词。-**输入**:上下文词(左右窗口内的词)。-**处理**:把上下文词的向量**平均化**-**输出**:预测中心词。#### CBOW 的网络结构1.**输入层**:上下文词的 OneHot 向量(或直接用索引查表)。2.**隐藏层**:查询嵌入矩阵 $W_1$,得到每个词的向量,然后**求平均**$h=\frac{1}{k}\sum_{i}W_1[c_i]$。3.**输出层**:用矩阵 $W_2$ 计算分数 $u=W_2^T h$,经过 Softmax 得到概率分布。#### 代码实现(带详细注释)```pythonimportnumpyasnp# ==== 第一步:准备数据 ====corpus=[["I","love","NLP"],["NLP","is","fun"]]# 语料库vocab=list(set(wforsincorpusforwins))# 去重得到词表word_to_idx={w:ifori,winenumerate(vocab)}# 词→索引V=len(vocab)# 词汇表大小(比如 6 个词)D=3# 词向量维度(3 维空间)# ==== 第二步:随机初始化两个矩阵 ====W1=np.random.randn(V,D)*0.1# 输入矩阵(最终的词向量)W2=np.random.randn(D,V)*0.1# 输出矩阵(工具人矩阵)lr=0.01# 学习率window=1# 窗口大小:左右各看 1 个词# ==== 第三步:Softmax 函数 ====defsoftmax(x):e=np.exp(x-np.max(x))# 防溢出:先减最大值returne/e.sum()# 归一化成概率# ==== 第四步:训练循环 ====forepochinrange(50):# 训练 50 轮forsentincorpus:# 遍历每个句子foriinrange(len(sent)):# 遍历句子里每个词target=sent[i]# 中心词(要预测的)t_idx=word_to_idx[target]# 收集上下文词(左右窗口内的词)ctx=[]forjinrange(max(0,i-window),min(len(sent),i+window+1)):ifj!=i:# 排除中心词自己ctx.append(word_to_idx[sent[j]])ifnotctx:continue# 没上下文就跳过# 【核心】前向传播:上下文词向量的平均h=np.mean(W1[ctx],axis=0)# shape: (D,)# 预测中心词:h 乘以 W2 得到每个词的分数u=W2.T @ h# shape: (V,)y_pred=softmax(u)# 变成概率分布# 计算误差:真实标签 - 预测概率y_true=np.zeros(V)y_true[t_idx]=1# one-hot 向量e=y_pred-y_true# 误差向量# 【核心】反向传播:更新权重dW2=np.outer(h,e)# W2 的梯度dh=W2 @ e# 传回 h 的梯度# 更新两个矩阵W2-=lr*dW2forcinctx:W1[c]-=lr*dh/len(ctx)# 平均分配给每个上下文词# ==== 第五步:使用词向量 ====word_vec=W1[word_to_idx["NLP"]]print("NLP 的词向量:",word_vec)
口诀式总结

中心词做主角,逐个预测邻居,每个邻居单独更新。


3.3 CBOW vs Skip-Gram:如何选择?

维度CBOWSkip-Gram
输入 → 输出上下文 → 中心词中心词 → 上下文
隐藏层处理上下文词向量平均化直接取中心词向量
训练样本数每个窗口 1 个样本每个窗口2k2k2k个样本(kkk为窗口大小)
训练速度(一次预测一个词)慢(一次预测kkk个词)
对高频词效果好,语义平滑也好
对低频词较弱(被平均稀释)(单独建模,更细粒度)
适用场景大规模语料快速训练关注语义精度,尤其是罕见词
选择建议
  • 数据量巨大、追求训练速度 →CBOW
  • 关注低频实体名词质量(如专业术语、人名、地名)→Skip-Gram
  • 实践中可两者都训练,比较下游任务性能后再做选择

4. 训练优化:解决 Softmax 的瓶颈

逻辑链条

  • 问题:Word2Vec 的输出层是 Softmax,分母要对全词表(比如 100 万个词)求和:escore∑w=1Vescore\frac{e^{score}}{\sum_{w=1}^{V} e^{score}}w=1Vescoreescore。每算一个样本都要算 100 万次指数,速度太慢!
  • 朴素方案:能不能不算所有词?
  • 改进方案 1负采样(Negative Sampling)——把多分类变成二分类。
  • 改进方案 2层次化 Softmax(Hierarchical Softmax)——用二叉树组织词表。

4.1 负采样(Negative Sampling)

核心思想

我们不再做"从 100 万个词里找正确的那 1 个"这种多分类任务,而是改成做"是非题":

  • (中心词,正确上下文) → 标签 1(正样本)
  • (中心词,随机抽的噪声词) → 标签 0(负样本)

这样,我们只需要算1 个正样本kkk个负样本(通常k=5k=5k=5),复杂度从O(V)O(V)O(V)降到了O(k)O(k)O(k)

目标函数

对于中心词wcw_cwc与真实上下文词wow_owo,最大化:
log⁡σ(vwo⊤vwc)+∑i=1kEwi∼Pn(w)[log⁡σ(−vwi⊤vwc)] \log \sigma(v_{w_o}^\top v_{w_c}) + \sum_{i=1}^{k} \mathbb{E}_{w_i \sim P_n(w)}\left[\log \sigma(-v_{w_i}^\top v_{w_c})\right]logσ(vwovwc)+i=1kEwiPn(w)[logσ(vwivwc)]

  • 第一项:让正样本的 sigmoid 概率接近 1。
  • 第二项:让负样本的 sigmoid 概率接近 0(即−v-vv的 sigmoid 接近 1)。
代码实现
代码实现(核心区别)
importnumpyasnp# ==== 准备数据(和 CBOW 一样)====corpus=[["I","love","NLP"]]vocab=list(set(wforsincorpusforwins))word_to_idx={w:ifori,winenumerate(vocab)}V,D=len(vocab),3# ==== 初始化两个矩阵 ====W1=np.random.randn(V,D)*0.1W2=np.random.randn(D,V)*0.1lr=0.01window=1defsoftmax(x):e=np.exp(x-np.max(x))returne/e.sum()# ==== 训练循环(核心区别在这里!)====forepochinrange(50):forsentincorpus:foriinrange(len(sent)):center_word=sent[i]# 中心词是"主角"center_idx=word_to_idx[center_word]h=W1[center_idx]# 【关键】直接拿出主角的向量,不用平均!# 收集上下文词(邻居们)context_words_indices=[]forjinrange(max(0,i-window),min(len(sent),i+window+1)):ifj!=i:context_words_indices.append(word_to_idx[sent[j]])ifnotcontext_words_indices:continue# 【关键】对每一个邻居进行一次预测和更新fortarget_ctx_idxincontext_words_indices:# 前向传播:用主角 h 去预测邻居u=W2.T @ h y_pred=softmax(u)# 真实答案是邻居的 one-hoty_true=np.zeros(V)y_true[target_ctx_idx]=1# 计算误差e=y_pred-y_true# 反向传播 & 更新权重dW2=np.outer(h,e)dh=W2 @ e W2-=lr*dW2 W1[center_idx]-=lr*dh# 只更新主角的向量!# 最终词向量就是 W1word_vec=W1[word_to_idx["love"]]print("love 的词向量:",word_vec)
实战要点
  • 负样本数 (k):常用 2–10,语料越大可以越小。
  • 噪声分布 (P_n(w)):实践常用词频的 (3/4) 次幂归一化,兼顾高频与长尾。
  • 优点:每次更新只动很少一部分向量,非常适合向量化和并行。

4.2 层次化 Softmax(Hierarchical Softmax)

核心结构
  • 用一棵二叉树表示整个词表,每个叶子是一个词。
  • 预测一个词的概率 = 从根走到该叶子路径上,所有二分类概率的乘积。
  • 计算复杂度与树高成正比,一般是 (O(\log V))。
霍夫曼树(Huffman Tree)
  • 建树思路:按词频从小到大构建二叉树:
    • 高频词路径短(靠近根)。
    • 低频词路径长(离根较远)。
  • 预测思路
    • 从根到叶子,每一次用 sigmoid 做“向左 / 向右”的二分类决策。
    • 走完整条路径,大约需要 (\log V) 次二分类。
  • 和普通 Softmax 对比
    • 普通 Softmax:每次要算 (V) 个得分。
    • 层次化 Softmax:只算 (\log V) 个节点,速度可快几百倍。
霍夫曼树代码实现(建树)
importheapq# ==== 节点类定义 ====classNode:def__init__(self,word_id=None,freq=0):self.word_id=word_id# 词的编号(叶子节点才有)self.freq=freq# 频率(用来排序)self.left=Noneself.right=Nonedef__lt__(self,other):returnself.freq<other.freq# ==== 构建霍夫曼树 ====defbuild_huffman_tree(word_freq_dict):""" word_freq_dict: {词id: 出现次数} 返回: 霍夫曼树的根节点 """# 1. 为每个词创建叶子节点nodes=[Node(word_id,freq)forword_id,freqinword_freq_dict.items()]# 2. 扔进最小堆(按频率排序)heapq.heapify(nodes)# 3. 循环合并:每次取最小的两个,造一个新 parentwhilelen(nodes)>1:child1=heapq.heappop(nodes)child2=heapq.heappop(nodes)parent=Node(freq=child1.freq+child2.freq)parent.left=child1 parent.right=child2 heapq.heappush(nodes,parent)# 4. 最后剩一个节点,就是树根returnnodes
口诀式总结

建树:按频率从小到大建二叉树,高频词路径短(近根),低频词路径长。
预测:从根到叶子,每层用 sigmoid 二分类(去左 or 去右),总共 (\log(V)) 次计算。
对比:Softmax 算 (V) 次,Huffman 只算 (\log(V)) 次,速度快几百倍。


4.3 负采样 vs 层次化 Softmax

维度负采样层次化 Softmax
复杂度(O(k)),(k) 通常为 5(O(\log V))
概率分布近似,不是真正 Softmax精确,可恢复真正概率
实现难度简单易实现,易并行稍复杂,需要建树与路径编码
工程特点非常适合大规模词向量训练适合类别极多的分类任务
常见用途Word2Vec、推荐、图嵌入等文本分类、大标签空间任务

5. FastText:子词信息的引入

逻辑链条

  • 问题:Word2Vec 把每个词当作原子单位,不知道 “apple” 和 “apples” 有关系;对未登录词(OOV)也无能为力。
  • 思路:词是由字符或子词组成的,子词蕴含形态与语义(词根、前后缀等)。
  • 方案FastText引入 n-gram 子词特征,让词向量 = 词本身向量 + 子词向量之和。

5.1 FastText 的定义与特点

定义

FastText 是 Facebook 提出的高效文本分类与词向量学习工具,核心是利用子词(subword)和 n-gram 特征来表示词。

与 CBOW 的相同点
  • 都是三层结构:输入层、隐藏层、输出层。
  • 输入都是一堆向量(词或 n-gram 的 embedding)。
  • 隐藏层通过对这些向量做求和 / 平均得到句子或文档表示。
与 CBOW 的不同点
维度CBOWFastText
输出目标中心词(词预测)文档类别标签(文本分类)
输入单位词 + n-gram 子词
编码方式OneHot 或索引查表直接用 embedding 查表
OOV 能力弱(只能用 UNK)强(子词组合出未见词的向量)

5.2 FastText 的技术细节

损失函数
  • 文本分类部分使用交叉熵损失
    L=−∑cyclog⁡pc \mathcal{L} = - \sum_{c} y_c \log p_cL=cyclogpc
  • 等价于最大化正确类别的对数概率。
输出层的层次化 Softmax
  • 按类别频率构建霍夫曼树。
  • 复杂度从 (N) 降到 (\log N)。
  • 每个非叶节点对应一个带参数的 sigmoid 单元。
  • 负类沿左子树编码为 0,正类沿右子树编码为 1。
N-gram 特征
  • 将文本按字符或词为单位,用长度为 (N) 的窗口滑动,生成一系列 n-gram 片段。
  • 例如对单词 “apple”,字符 3-gram 可能包括:<apapppplplele>等。
  • FastText 为每个 n-gram 分配一个向量,最终词向量 = 词本身向量 + 所有 n-gram 向量的和或平均。
工程优化
  • 过滤出现次数过少的词和 n-gram,减少噪声与内存消耗。
  • 用哈希技巧存储大量 n-gram 特征,控制参数量。
  • 在保持精度的前提下,保证训练和推理速度都非常快。

6. 语言模型基础:从 N-gram 到自回归

6.1 自回归语言模型

定义

自回归语言模型假设:当前 token 的分布只依赖于之前已经生成的历史序列

整个句子的概率分解为:
P(x1,…,xT)=∏t=1TP(xt∣x1,…,xt−1) P(x_1, \dots, x_T) = \prod_{t=1}^{T} P(x_t \mid x_1, \dots, x_{t-1})P(x1,,xT)=t=1TP(xtx1,,xt1)

生成时:

  • 先根据 (P(x_1)) 采样第一个 token。
  • 再根据 (P(x_2 \mid x_1)) 采样第二个 token。
  • 如此循环,直到生成终止符。
训练目标

最小化负对数似然:
L=−∑t=1Tlog⁡P(xt∣x1,…,xt−1) \mathcal{L} = -\sum_{t=1}^{T} \log P(x_t \mid x_1, \dots, x_{t-1})L=t=1TlogP(xtx1,,xt1)
等价于最大化训练语料的似然,让真实句子更“有可能”。

采样温度的作用

对 logits (z_i) 进行缩放:
zi′=ziT z_i' = \frac{z_i}{T}zi=Tzi
再喂入 softmax:

  • 当 (T \to 0) 时,分布极尖锐,接近贪心选最大概率词。
  • 当 (T) 较大时,分布变平,采样更随机,多样性提高。

6.2 N-gram 语言模型

定义

N-gram 模型是自回归模型的一个有限记忆特例,只看最近的 (n-1) 个词:

P(xt∣x1,…,xt−1)≈P(xt∣xt−n+1,…,xt−1) P(x_t \mid x_1, \dots, x_{t-1}) \approx P(x_t \mid x_{t-n+1}, \dots, x_{t-1})P(xtx1,,xt1)P(xtxtn+1,,xt1)

概率估计示例
  • 二元模型(Bigram):
    P(xt∣xt−1)≈count(xt−1,xt)count(xt−1) P(x_t \mid x_{t-1}) \approx \frac{\text{count}(x_{t-1}, x_t)}{\text{count}(x_{t-1})}P(xtxt1)count(xt1)count(xt1,xt)
  • 三元模型(Trigram):
    P(xt∣xt−2,xt−1)≈count(xt−2,xt−1,xt)count(xt−2,xt−1) P(x_t \mid x_{t-2}, x_{t-1}) \approx \frac{\text{count}(x_{t-2}, x_{t-1}, x_t)}{\text{count}(x_{t-2}, x_{t-1})}P(xtxt2,xt1)count(xt2,xt1)count(xt2,xt1,xt)

实际中必须结合平滑(如加一、Kneser-Ney)避免零概率。

工程特点
  • 训练阶段主要是大规模 n-gram 计数和归一化。
  • 存储代价高,需要裁剪和压缩。
  • 推理阶段依赖查表和缓存优化。

6.3 自回归神经 LM vs N-gram

维度N-gram神经 LM(RNN / Transformer)
条件依赖只看最近 (n-1) 个词理论上可看全局历史
表示方式离散计数连续向量表示
数据稀疏严重,未见组合概率为 0可泛化到未见组合
长距离依赖几乎无能为力可以建模长依赖甚至整篇文档

可以把 N-gram 看作自回归模型的一种有限阶马尔可夫近似


7. 词向量知识架构总览

基础:词向量(NLP 根基)
├── OneHot 编码
├── Word2Vec(CBOW / Skip-Gram)
└── FastText(子词信息)

演进:RNN(早期序列模型)

核心:Attention 机制和 Transformer

深入:KV Cache、位置编码、归一化

应用:BERT、GPT、LLaMA、DeepSeek

优化:训练、微调、RLHF

实战:推理部署、RAG 技术


总结:逻辑链与记忆口诀

技术问题方案口诀
OneHot计算机不认识文字每个词一个独立维度不排队、好算账、能一起算
CBOWOneHot 无语义用上下文预测中心词上文求平均,预测中间,误差回传
Skip-GramCBOW 对低频词弱用中心词预测上下文中心做主角,逐个预测邻居
负采样Softmax 太慢多分类变二分类1 个正样本 + (k) 个负样本
层次化 SoftmaxSoftmax 太慢用二叉树组织词表建霍夫曼树,(\log(V)) 次计算
FastTextWord2Vec 不懂子词引入 n-gram 特征词向量 + n-gram 向量平均

核心脉络回顾:

  1. OneHot把离散符号嵌入欧式空间,但维度高且没有语义。
  2. Word2Vec利用“上下文预测”任务学习稠密词向量,CBOW 偏快,Skip-Gram 偏精细。
  3. 负采样 / 层次化 Softmax解决大词表下的训练效率问题。
  4. FastText把子词信息也嵌入进来,显著增强对未登录词和形态变化的处理能力。
  5. 在更高一层,语言模型(LM)用这些向量建模整句概率,从 N-gram 发展到自回归神经网络,再到 Transformer 大模型。

把这条线记住,再看任何 NLP 模型的 embedding 层,就都会更顺畅。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 22:07:43

字节跳动、蔚来、哔哩哔哩、小红书面试复盘!

沉默是金&#xff0c;总会发光大家好&#xff0c;我是沉默作为程序员&#xff0c;面试不仅是展示技术能力的舞台&#xff0c;也是检验自己知识体系的机会。最近在几场面试中&#xff0c;朋友遇到了一些技术性问题&#xff0c;尽管挑战较大&#xff0c;但它们促使他对基础知识和…

作者头像 李华
网站建设 2026/4/16 20:49:28

半条鱼设计公司如何为乌鲁木齐展厅打造独特空间体验?

半条鱼设计公司&#xff1a;为乌鲁木齐展厅打造独特空间体验的专业实践专业设计团队的深度洞察半条鱼设计公司自2013年创立以来&#xff0c;始终专注于室内空间设计领域&#xff0c;拥有200余位设计精英组成的专业团队。在乌鲁木齐展厅项目实践中&#xff0c;公司展现出对商业展…

作者头像 李华
网站建设 2026/4/14 16:19:32

C# NPOI入门指南:轻松操作Excel

目录 一、NPOI 核心原理&#xff08;通俗版&#xff09; 1. 什么是 NPOI&#xff1f; 2. NPOI 核心对象模型&#xff08;类比理解&#xff09; 3. 核心逻辑&#xff1a;流操作 二、环境准备&#xff08;初学者第一步&#xff09; 三、高频用法&#xff08;带完整示例&…

作者头像 李华
网站建设 2026/4/16 18:08:34

云渠道商:wordpress怎么搭建博客网站?

一、准备一台云服务器搭建WordPress博客&#xff0c;首先需要一台云服务器。云服务器为我们提供稳定、安全的运行环境&#xff0c;并且可以随时扩展资源。目前市面上主流的云服务提供商有阿里云、腾讯云、华为云、AWS和谷歌云等。 购买建议&#xff1a;对于个人博客&#xff0c…

作者头像 李华
网站建设 2026/4/15 22:57:49

__contain__和contain之间的区别

def contains(self, circle_2D):x1 self.__xy1 self.__yx2 circle_2D.get_x()y2 circle_2D.get_y()r1 self.__radiusr2 circle_2D.get_radius()countDis (pow((x2 - x1), 2) pow((y2 - y1), 2)) ** 0.5if countDis r2 < r1: # 修改后可以完全包含print(f"坐标…

作者头像 李华