词嵌入矩阵

词嵌入矩阵是自然语言处理中的核心组件,其核心作用是将离散的符号(如单词、字符)转换为连续的、稠密的向量表示,从而捕捉语义和语法信息。

核心作用

1. 语义表示

  • 将单词映射为向量:每个单词对应矩阵中的一行(一个向量)。
  • 相似词向量接近:语义相近的词(如“king”和“queen”)在向量空间中距离较近。
  • 解决“词汇鸿沟”:传统NLP中,单词被视作独立符号(如one-hot编码),无法表示词间关系。

2. 降维与稠密表示

  • one-hot编码缺点:维度高(词汇表大小)、稀疏、无语义信息。
  • 词嵌入:将高维稀疏向量压缩为低维稠密向量(如50-300维),携带丰富信息。

3. 关系捕捉

  • 向量运算可揭示关系。
  • 学习类比、类别等语言学模式。

4. 作为模型输入

  • 为下游任务(如文本分类、机器翻译)提供特征输入。
  • 通常是神经网络的第一层(嵌入层),可随机初始化并随任务训练,也可用预训练嵌入初始化。

技术实现方式

1. 静态预训练嵌入

  • Word2Vec:通过上下文预测词(Skip-Gram)或词预测上下文(CBOW)。
  • GloVe:基于全局词共现矩阵的统计信息。
  • FastText考虑子词(n-gram)信息,能处理未登录词。

2. 动态上下文嵌入

  • ELMo、BERT等:根据上下文生成不同向量(如“苹果”在“吃苹果”和“苹果手机”中向量不同)。

3. 可训练的嵌入层

  • 在任务中随机初始化,随模型一起训练。

Word2Vec

Word2Vec是Google在2013年推出的词向量学习工具,核心思想:具有相似上下文的单词具有相似语义。

核心创新

  • 从大规模无标注文本中自动学习词向量
  • 得到的向量具有线性语义关系(类比关系)
  • 计算高效,适合大规模语料

两种主要模型架构

1. Skip-gram(跳字模型):中心思想:通过中心词预测上下文词

输入:中心词 w(t)
输出:周围窗口内的上下文词 {w(t-2), w(t-1), w(t+1), w(t+2)}

例如:句子"The quick brown fox jumps"
      中心词="brown" → 预测{"The", "quick", "fox", "jumps"}                
              

特点:

  • 更适合小型数据集
  • 对罕见词效果更好
  • 训练时间相对较长

窗口大小设置

(1)常用经验范围

  • 典型值:3、5、7、10
  • 最小:2(仅相邻词)
  • 最大:20+(段落级上下文)

(2) 不同窗口大小的效果

窗口大小 捕获信息 适合场景
小窗口 (2-5) 语法模式句法关系、词性搭配 语法敏感任务句法分析、词性标注
中等窗口 (5-10) 混合信息平衡语法与局部语义 通用任务文本分类、情感分析
大窗口 (10-20) 主题/语义关联文档主题、远程关联 主题建模文档相似度

Skip-gram:窗口=10效果较好。CBOW:窗口=5左右

2. CBOW(连续词袋模型):中心思想:通过上下文词预测中心词

输入:上下文词 {w(t-2), w(t-1), w(t+1), w(t+2)}
输出:中心词 w(t)

例如:上下文{"The", "quick", "fox", "jumps"} → 预测"brown"
              

特点:

  • 更适合大型数据集
  • 训练速度更快
  • 对高频词效果更好

3.Skip-Gram与CBOW的差异

模型 输入 (Input) 输出 (Output) 训练目标
CBOW 窗口内的所有上下文词 一个中心词 最大化给定上下文时中心词的概率
Skip-Gram 一个中心词 窗口内的所有上下文词 最大化给定中心词时每个上下文词的概率

关键技术优化

1. 负采样(Negative Sampling)

问题:原始softmax计算开销大(需计算词汇表中所有词的概率),避免计算整个词汇表的softmax

解决方案:

  • 对每个正样本(中心词-上下文词对),采样K个负样本
  • 仅更新正样本和负样本对应的权重
  • 显著提升训练速度

负采样的实现过程:

  • 统计词汇表中每个词的出现频率 $\text{freq}(w)$
  • 计算每个词的采样权重:$\text{weight}(w) = \text{freq}(w)^{0.75}$。
  • 将所有权重归一化为概率分布 $P(w) = \frac{\text{weight}(w)}{ \sum(\text{weight})}$
  • 构建一个巨大的数组(即采样表),其长度通常为1000万或1亿。每个词根据其概率 $P(w)$ 占据表中相应比例的位置。
  • 例如,如果词A的概率是0.01,而表长为1千万,那么词A的ID就会在这个表中出现大约 10^7 * 0.01 = 10万 次。

    这样,后续采样就简化为了 O(1) 操作:每次需要负样本时,只需在1到表长之间生成一个随机整数,然后查表得到对应的词ID即可。

word2vec在训练时,不是直接使用整个词汇表进行随机采样,而是通过预先构建一个巨大的、根据词频权重分布填充的查找表(负采样表),将复杂的、基于非均匀概率分布的采样问题,转化成了一个极其简单的、均匀的“查表”问题。这是一种经典的“以空间换时间” 的工程优化,对于需要执行数十亿次采样操作的大型模型训练至关重要。

2. 高频词下采样

  • 对"the", "a"等高频词以概率丢弃
  • 平衡罕见词与高频词的训练机会
  • 提升向量质量

3. 高频词下采样(Subsampling)方法

公式 1:保留概率 (keep probability)

$$ \text{keep prob} = \left( \sqrt{\frac{\text{threshold}}{\text{freq}}} + 1 \right) \times \frac{\text{freq}}{\text{threshold}} $$

其中 feq是词频,threshold控制着下采样的强度:

  • 较小的 threshold → 更激进的下采样(更多词被丢弃)
  • 较大的 threshold → 更保守的下采样(更少词被丢弃)

公式 2:丢弃概率 (discard probability)

$$ \text{discard prob} = 1 - \sqrt{\frac{\text{threshold }}{\text{freq}}}$$

原始公式(公式 1)更复杂,来自 word2vec 源码。变体公式(公式 2):更简单,数学上更优雅

判断是否丢弃

import random

# 判断是否丢弃当前词
if random.random() < discard_prob:
    discard_word()  # 丢弃
else:
    keep_word()     # 保留                
              

这里的关键是:random.random() 生成一个在 [0, 1) 范围内的均匀分布随机数。假设discard_prob = 0.3 表示丢弃概率是 30%,random.random() 均匀分布在 [0, 1),条件 random.random() < 0.3 成立的概率正好是 30%,以下是关于负采样和下采样的比较:

特性 负采样 (Negative Sampling) 下采样 (Subsampling)
作用阶段 训练时的损失函数计算阶段 训练前的数据预处理阶段
解决的问题 避免计算整个词汇表的softmax 减少高频词的训练次数
操作对象 神经网络输出的计算 输入文本数据本身
主要目的 降低计算复杂度 平衡词频,提升质量
影响 训练速度提升10-100倍 训练速度提升2-5倍,向量质量提升

如何衡量词嵌入矩阵好坏

1. 词相似度任务(Word Similarity)

余弦线相似度:余弦相似度是衡量两个向量方向相似程度的指标。余弦相似度的定义:

$$ \cos(\theta) = (A\cdot B) / (\begin{Vmatrix}A \end{Vmatrix} * \begin{Vmatrix}B \end{Vmatrix}) $$

取值范围: [-1, 1], “1”: 完全相同方向, “0”: 正交(无关), “-1”: 完全相反方向。优点: 只考虑方向,不考虑向量长度

计算步骤:

  • 计算点积(对应元素相乘后求和)
  • 计算每个向量的范数(平方和开根号)
  • 点积除以范数乘积

例如:

$$ \text{A} = [1,2,3],\text{B}=[4,5,6] $$

$$ A \cdot B = 1\times 4 + 2\times 5 + 3\times 6 = 32 $$

$$ \begin{Vmatrix}A \end{Vmatrix} = \sqrt{1^2+2^2+3^2} = \sqrt{14},\begin{Vmatrix}B \end{Vmatrix} = \sqrt{4^2+5^2+6^2} = \sqrt{77}$$

$$ \text{cosine\_similarity} = \frac{32}{\sqrt{14\times 75}} \approx 0.9746 $$

相似度解释:

分数 相似度解释
cos_sim > 0.9 几乎相同方向(高度相似)
cos_sim > 0.7 方向很接近(强相似)
cos_sim > 0.5 方向比较接近(中等相似)
cos_sim > 0.3 有一定相似性
cos_sim > 0 轻微相似
cos_sim == 0 正交(无关)
cos_sim > -0.3 轻微相反
cos_sim > -0.7 比较相反
其它 几乎完全相反

上面计算的是两个词$A,B$的余弦相似度,如果需要计算两个文本(包含多个词)的余弦相似度呢?下面介绍文本向量化和余弦相似度计算方法

词嵌入平均法(Word Embedding Average)

                def text_to_vector_word_embedding(text, embeddings, pooling='mean'):
                """
                使用词嵌入将文本转换为向量
                
                Args:
                    text: 输入文本
                    embeddings: 词嵌入字典 {word: vector}
                    pooling: 池化方法 'mean', 'sum', 'max'
                """
                
                # 分词
                words = text.lower().split()
                
                # 获取每个词的向量
                word_vectors = []
                for word in words:
                    if word in embeddings:
                        word_vectors.append(embeddings[word])
                
                if not word_vectors:
                    # 如果没有词在嵌入中,返回零向量
                    return np.zeros_like(next(iter(embeddings.values())))
                
                word_vectors = np.array(word_vectors)  # [n_words, embed_dim]
                
                # 池化操作
                if pooling == 'mean':
                    text_vector = np.mean(word_vectors, axis=0)
                elif pooling == 'sum':
                    text_vector = np.sum(word_vectors, axis=0)
                elif pooling == 'max':
                    text_vector = np.max(word_vectors, axis=0)
                else:
                    raise ValueError(f"未知的池化方法: {pooling}")
                
                return text_vector
            
            def compare_pooling_methods(text1, text2, embeddings):
                """比较不同的池化方法"""
                
                methods = ['mean', 'sum', 'max']
                
                print(f"文本1: '{text1}'")
                print(f"文本2: '{text2}'")
                print(f"词嵌入维度: {len(next(iter(embeddings.values())))}")
                
                results = {}
                
                for method in methods:
                    vec1 = text_to_vector_word_embedding(text1, embeddings, method)
                    vec2 = text_to_vector_word_embedding(text2, embeddings, method)
                    
                    similarity = cosine_similarity_manual(vec1, vec2)
                    results[method] = similarity
                    
                    print(f"\n{method.upper()}池化:")
                    print(f"  文本1向量范数: {np.linalg.norm(vec1):.4f}")
                    print(f"  文本2向量范数: {np.linalg.norm(vec2):.4f}")
                    print(f"  余弦相似度: {similarity:.4f}")
                
                return results                
              

其过程是:

  • 将文本分词
  • 获取每个词的词向量
  • 对所有词向量取平均得到文本向量

词相似度任务的Spearman相关系数:是用来衡量两个变量单调关系强度的统计指标。记作$\rho$或$r_s$(> 0.6为良好):

  • 非参数的:不假设数据分布
  • 衡量单调关系:不一定是线性,只要是一个变量增加时另一个也增加(或减少)
  • 基于排序:使用数据的秩(排名)而非原始值
  • 基于排序:使用数据的秩(排名)而非原始值
  • 范围:-1 到 +1
    • +1:完全正相关(一个排名增加,另一个也增加)
    • -1:完全负相关(一个排名增加,另一个减少)
    • 0:无单调关系

解释Spearman相关系数$\rho$

$\rho$ 模型
$\rho \ge 0.9 $ 极强相关
$\rho \ge 0.7 $ 强相关
$\rho \ge 0.5 $ 中等相关
$\rho \ge 0.3 $ 弱相关
$\rho < 0.3 $ 极弱或无相关

2. 词类比任务(Word Analogy)

QkCalc中的Word2Vec

在QkCalc中可以使用“机器学习”中的“词嵌入向量”创建和训练Word2Vec模型,具体的训练是通过QkAIService服务来完成。通过QkCalc可对该服务进行管理并对训练的结果进行评估。