Python词嵌入从零实现:底层代码解析不依赖第三方库的完整教程

2026-05-29阅读 0热度 0
Python

不依赖第三方库实现Word Embedding,本质上是手动搭建完整的词向量训练流程:从文本清洗与词典构建,到生成上下文样本,再到定义前向传播与反向更新。核心在于透彻理解Skip‑gram或CBOW的前向计算与梯度反向传播机制。以下用纯Python(仅依赖mathrandomcollections)构建一个可运行的轻量词嵌入模型,以Skip‑gram搭配负采样(Negative Sampling)为例。尽管无法处理大规模语料,但每一步都对应着NLP表征学习的底层逻辑,值得亲手实践。

1. 文本预处理与词汇表构建

首先将原始句子转换为整数索引序列,同时可剔除低频词与停用词(非必需,但能提升嵌入质量)。具体拆解如下:

  • 分词:按空格或简单标点切分,统一小写,去除空字符串;
  • 词频统计:使用Counter扫描所有词的出现次数;
  • 构建词表:仅保留出现次数≥min_count的词,按频次降序排列;为UNK(未登录词)预留索引0;
  • 映射:建立两个字典——word2idx(词→索引)和idx2word(索引→词),后续均通过它们查表。

2. 生成Skip‑gram训练样本

对于每个中心词,提取窗口范围内(如左右各2个)的词作为正样本;负样本则根据词频分布按概率采样——一个实用技巧:使用一元分布的3/4次方进行平滑,使低频词有更大概率被抽中。

  • 遍历每个句子中的每个词(跳过窗口边界处的词),以其为中心,收集左右window_size内的词作为正样本;
  • 针对每个正样本,随机采样n_neg个负样本:预先计算每个词的采样概率(freq^(3/4)后归一化),可采用轮盘赌或别名采样,简易版直接使用random.choices并设置权重即可;
  • 每条样本存储为三元组(center_idx, context_idx, label),正样本label=1,负样本label=0

3. 初始化与前向传播(Sigmoid + 点积)

需要维护两组向量:输入矩阵W(尺寸vocabulary_size × embedding_dim)和输出矩阵W'(相同大小)。Skip‑gram的做法是:用中心词查询W得到向量v_c,用上下文词查询W'得到v_w,计算两者点积v_c ⋅ v_w,再经过Sigmoid得到概率。

  • 初始化WW'均赋以小随机数,如uniform(-0.5/dim, 0.5/dim),避免对称性导致所有向量趋同;
  • Sigmoid手写实现def sigmoid(x): return 1 / (1 + math.exp(-max(-50, min(50, x))))——截断以防溢出;
  • 前向输出score = sum(v_c[i] * v_w[i] for i in range(dim)),然后prob = sigmoid(score)

4. 手动反向传播与参数更新

损失函数采用二元交叉熵:L = −[y·log(p) + (1−y)·log(1−p)]。正样本时y=1,梯度为(p−1);负样本时y=0,梯度为p。根据该误差信号更新两组向量:

  • g = prob - label(即误差信号);
  • 更新中心词向量:W[center_idx][i] -= lr * g * W_prime[context_idx][i]
  • 更新上下文词向量:W_prime[context_idx][i] -= lr * g * W[center_idx][i]
  • 学习率lr建议从0.025起步,随训练步数线性衰减。

全程不使用NumPy或PyTorch,全部基于原生Python列表和循环手写。效率确实低——仅适合小语料或教学演示——但优势在于清晰展示了词向量如何借助局部共现被“拉近”,以及负采样如何绕开softmax的大分母。归根结底,核心不在代码长短,而在于每一步都对应着“分布假设”的数学实现。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策