从零玩转 NLP:分词、文本表示与 Word2Vec 实战
一、开场:让计算机"看见"文字
先做一个思想实验。
你把一句中文"我爱自然语言处理"丢给计算机,它会"看到"什么?
答案是一串毫无意义的字符——我 爱 我 自 然 语 言 处 理 这种 Unicode 编码的二进制组合。计算机本身既不"懂"汉字的语义,也"不懂"词的边界。
要让计算机真正"读懂"一句话,我们必须走完一段旅程:
这就是本文要讲的两件最基础也最关键的事:分词 与 文本张量表示。搞懂这两步,你就能读懂后续 80% 的 NLP 代码(Word2Vec、Embedding、RNN、Transformer 全部建立在这两步之上)。
📌 本文脉络: 我们会先聊 NLP 是什么、它能干什么、它怎么从"规则系统"一路进化到"深度学习";然后聚焦"文本处理的完整 pipeline",重点实战 jieba 分词 和 One-Hot / Word2Vec / nn.Embedding 三种文本表示方法。代码全部可运行,注释拉满,建议边读边敲。
二、NLP 是什么?能干什么?
2.1 定义
自然语言处理(Natural Language Processing, NLP)是人工智能领域的一个重要分支。所谓"自然语言",指人类日常使用的语言(中文、英文、法文……)。NLP 的目标,是让计算机"理解"或"使用"这些语言——说人话就是:让机器听得懂人话、说得出人话。
2.2 五大常见任务
NLP 的应用虽然千变万化,但拆开来看都离不开这五类核心任务:
| 文本分类 | 对整段文本打标签 / 归类 | 情感分析(正面 / 负面)、垃圾邮件识别、新闻主题分类 |
| 序列标注 | 对句子中的每个词打标签 | 命名实体识别(人名 / 地名 / 手机号)、词性标注 |
| 文本生成 | 根据已有内容生成新文本 | 自动写作、摘要生成、智能客服、对话系统 |
| 信息抽取 | 从文本中抽出结构化信息 | 关系抽取、阅读理解(给定文本 + 问题 → 抽答案) |
| 文本转换 | 把一种文本变成另一种 | 机器翻译、文本改写、文本纠错 |
💡 快速判定任务类型: 看输出。如果输出是"一个标签"(好 / 坏 / 体育 / 娱乐)→ 分类;如果输出是"一串等长的标签"(每个词一个标签)→ 序列标注;如果输出是"一段新的文字"→ 生成或转换。
2.3 技术演进史:四个阶段
理解 NLP 的历史,能帮你一眼看穿今天大火的 ChatGPT 为啥能这么"猛"——它是几十年的技术堆叠的产物。
| 规则系统 | 1950s ~ 1980s | 语言学家手写语法规则 | 1954 年 Georgetown-IBM 机器翻译实验;1966 年 ELIZA 聊天机器人 | 通用性差,扩展性差 |
| 统计方法 | 1990s | 用概率建模代替规则 | N-gram 语言模型、隐马尔可夫模型(HMM) | 依赖手工设计特征,泛化弱 |
| 机器学习 | 2000s | SVM / LR / CRF + 特征工程 | 词袋模型 + LR 做文本分类 | 特征工程费时费力 |
| 深度学习 | 2010s 至今 | 神经网络自动学表示 | RNN / LSTM / GRU → Transformer → BERT / GPT | 数据 / 算力需求大 |
举个具体的例子感受下"统计方法的局限"。下面两条完全相反的评论:
- 评论 A:“服务很好但味道差劲”
- 评论 B:“味道很好但服务差劲”
早期"词袋模型"(Bag-of-Words)只会统计词频,完全忽略词序。这两句话分词后:
- A:[服务, 很, 好, 但, 味道, 差劲]
- B:[味道, 很, 好, 但, 服务, 差劲]
在词袋模型眼里,两条评论的向量完全一样。这显然不对,但当时只能靠"古人"手动加 n-gram(把相邻 N 个词作为整体)来救场——这正是后来深度学习要彻底解决的问题:让模型自己学出包含语序和语义的表示。
三、文本处理的完整 Pipeline
在正式讲分词和文本表示之前,先上一张全景图。所有 NLP 任务,从经典的情感分类到最火的 ChatGPT,文本处理都遵循下面这个流程:
整个流程可以拆成 5 步:
下面重点讲 分词 和 文本张量表示 这两步。
四、分词:把句子"切碎"
分词(Tokenization)是把原始文本切分为若干具有独立语义的最小单元(token)的过程,是所有 NLP 任务的起点。没有这一步,后面所有的"向量化"都是空中楼阁。
4.1 英文分词的三种粒度
英文的分词有词级(Word-Level)、字符级(Character-Level)、子词级(Subword-Level)三种,特点如下:
| 词级分词 | 很大(10w+) | 严重 | 强 | 短 | 传统 NLP 工具(NLTK、spaCy) |
| 字符级分词 | 极小(~256) | 几乎无 | 弱 | 长 | 字符级 CNN / RNN |
| 子词级分词 | 适中(3w~5w) | 极少 | 中等 | 中 | BERT、GPT 等现代大模型 |
📌 什么是 OOV? OOV(Out-Of-Vocabulary)指"词表里没有的词"。举个例子:词表里没有"栓 Q",那"栓 Q"就是 OOV。词级分词最容易遇到 OOV;子词级(BPE)会把"栓 Q"拆成"栓"和"Q"两个子词,从而几乎不会 OOV——这也是现代大模型几乎都用子词分词的核心理由。
💡 想深入了解 BPE? 推荐 HuggingFace 官方课程:Byte-Pair Encoding tokenization。它一步步演示了 BPE 是怎么从字符开始、一步步合并出子词词表的。
4.2 中文分词与 jieba
中文没有天然的"空格"作为词边界,所以"分词"这个动作在中文里比英文更关键。中文分词的代表工具:
- 词级分词:jieba、HanLP —— 传统 NLP 任务标配;
- 子词级分词:HuggingFace Tokenizers、SentencePiece、OpenAI tiktoken —— 大模型时代主流。
下面重点演示 jieba。它提供三种分词模式,适用场景各不相同:
| 精确模式 | 试图将句子最精确地切开,适合文本分析 | jieba.cut(text, cut_all=False) |
| 全模式 | 把句子中所有可能成词的词都扫出来,速度快但有歧义 | jieba.cut(text, cut_all=True) |
| 搜索引擎模式 | 在精确模式基础上对长词再次切分,提高召回率 | jieba.cut_for_search(text) |
📌 精确模式(默认,最常用)
import jieba
text = '我爱学习,我爱敲代码'
# 精确模式:cut_all=False(默认)
words = jieba.cut(text, cut_all=False)
print('/ '.join(words))
输出:
我/ 爱/ 学习/ ,/ 我/ 爱/ 敲/ 代码
📌 全模式
import jieba
text = '我爱学习,我爱敲代码'
# 全模式:cut_all=True
words = jieba.cut(text, cut_all=True)
print('/ '.join(words))
输出(注意"我爱学习"这种较长词组也会被切出来):
我/ 爱/ 学习/ 我爱/ 学习/ ,/ 我/ 爱/ 敲/ 代码
📌 搜索引擎模式
import jieba
text = '我爱学习自然语言处理'
# 搜索引擎模式:基于精确模式,对长词再次切分
words = jieba.cut_for_search(text)
print('/ '.join(words))
输出(既保留"自然语言处理"这种长词,又拆出"自然""语言"这种短词):
我/ 爱/ 学习/ 自然/ 语言/ 处理/ 自然语言/ 自然语言处理
💡 选哪个?
- 做情感分析、文本分类等"重语义"任务 → 精确模式(歧义少);
- 自己做搜索索引、要尽可能多召回 → 搜索引擎模式;
- 全模式在生产环境几乎不用(噪音太多)。
4.3 自定义词典:解决分词歧义
jieba 自带一个 30w+ 的通用词库,但对专业领域(医疗、法律、金融)和新兴网络用语(“栓 Q”“city 不 city”)是无能为力的。直接后果就是分词歧义。举经典例子:
“下雨天留客天留我不留” 不同断句可以解释出完全相反的意思。jieba 纯靠通用词库分不准。
解决办法:加载自定义词典。
import jieba
# 加载自定义词典(每行一个词:词语 词频 词性)
# user_dict.txt 内容示例:
# 栓 Q 5 n
# 云计算 3 n
jieba.load_userdict('user_dict.txt')
text = '学 Python 离不开云计算'
print('/ '.join(jieba.cut(text)))
# 输出:学/ Python/ 离不开/ 云计算 ← 云计算被正确识别为一个词
4.4 命名实体识别(NER)与词性标注(POS)
分完词之后,下一步常常是给每个 token 打标签——这是后续做关系抽取、问答系统的基石。jieba 自带词性标注功能:
from jieba import posseg
# res 是一个生成器,遍历得到 (word, flag) 对
res = posseg.cut("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")
for item in res:
print(item.word, item.flag, sep='\\t')
# 输出:
# 小明 nr # nr: 人名
# 硕士 n # n: 普通名词
# 毕业 n
# 于 p # p: 介词
# 中国科学院 nt # nt: 机构名
# 计算所 n
# , x # x: 标点
# 后在 t
# 日本京都大学 nt
# 深造 v # v: 动词
📷 jieba 词性对照表 完整标签集参见 jieba 官方词性对照表。生产环境做 NER 通常会用更专业的 HanLP 或 LAC。
五、文本张量表示:让文字变成"算得动"的数字
把一段文本用张量(Tensor)的形式表示,这个过程就叫文本张量表示。词被表示为词向量,一句话就成了一个词向量矩阵。
为什么必须做这一步?因为计算机不懂字、也不懂词,它只懂张量上的加减乘除。
常见的文本表示方法有三类,我们由浅入深一一展开。
5.1 One-Hot:最朴素的离散表示
One-Hot 编码是最基础的表示方法,思路简单到极致:
- 词表有多大,向量就有多长;
- 每个词对应一个向量,只有该词索引位置是 1,其余全是 0。
举例:词表为 [周杰伦, 陈奕迅, 王力宏, 吴亦凡, 刘德华],“周杰伦” 的 One-Hot 向量就是 [1, 0, 0, 0, 0]。
One-Hot 优点:
- 简单直观,实现容易;
- 配合 torch.nn.functional.one_hot 一行代码搞定。
One-Hot 致命缺点:
- 维度灾难:中文词表动辄几十万,向量长度也跟着几十万,模型根本跑不动;
- 完全丢弃语义:任意两个不同词的 One-Hot 向量都是正交的(点积 = 0),无法表达"猫"和"狗"都是动物、"好"和"棒"是近义词。
⚠️ 重要提示 One-Hot 在生产中几乎不用,但它是所有 Embedding 方法的"地基"——理解 One-Hot 的"维度灾难"和"语义缺失"问题,才能体会 Word2Vec 为啥是 NLP 史上里程碑式的工作。
手写 One-Hot
"""
演示 One-Hot 编码的手写实现
理解原理即可:先把词映射成 id,再把 id 映射成"独热向量"
"""
# 1. 构建词表
words = ['周杰伦', '陈奕迅', '王力宏', '吴亦凡', '刘德华']
token_id = {'周杰伦': 0, '陈奕迅': 1, '王力宏': 2, '吴亦凡': 3, '刘德华': 4}
def one_hot(word: str) –> list:
"""根据词表生成该词的 One-Hot 向量"""
if word not in token_id:
raise ValueError(f'词表里没有 "{word}"')
vector = [0] * len(token_id) # 初始化全 0 向量
vector[token_id[word]] = 1 # 把自己对应的位置置 1
return vector
# 测试
print(one_hot('周杰伦')) # [1, 0, 0, 0, 0]
print(one_hot('陈奕迅')) # [0, 1, 0, 0, 0]
PyTorch 官方 One-Hot
import torch
import torch.nn.functional as F
# 假设有 3 个 token,词表大小 5
token_ids = torch.tensor([0, 3, 2]) # shape: (3,)
vocab_size = 5
# F.one_hot 会把 id 转成 0/1 向量
one_hot_vectors = F.one_hot(token_ids, num_classes=vocab_size)
# shape: (3, 5)
# 结果:
# [[1, 0, 0, 0, 0],
# [0, 0, 0, 1, 0],
# [0, 0, 1, 0, 0]]
print(one_hot_vectors)
print(one_hot_vectors.shape) # torch.Size([3, 5])
# 注意:F.one_hot 返回的是 int64 类型
# 如果要喂给深度学习模型(float 输入),记得 .float()
one_hot_vectors = one_hot_vectors.float()
5.2 Word2Vec:让向量"有语义"
Word2Vec 是 Google 在 2013 年提出的词嵌入模型,核心思想只有一句话:
通过一个浅层神经网络在大规模语料上训练,把每个词映射成低维稠密向量,使得语义相近的词在向量空间里距离更近。
它直接解决了 One-Hot 的两个核心痛点:维度从"几十万"压到"几百",并且"语义"被编码进了向量的方向里。
Word2Vec 包含两种训练模式:
- CBOW(Continuous Bag-of-Words):用上下文预测中心词。训练快,适合小语料;
- Skip-gram:用中心词预测上下文。对低频词更友好,适合大语料。
Word2Vec 最神奇的地方是支持语义代数:
- vec(国王) – vec(男性) + vec(女性) ≈ vec(女王)
- vec(巴黎) – vec(法国) + vec(日本) ≈ vec(东京)
这就是"词的语义被编码到向量空间里"的实锤。
方式一:用 FastText 训练
FastText 是 Facebook 出的库,自带 Word2Vec 的 CBOW 和 Skip-gram 实现,安装简单,开箱即用。
📌 安装
# 推荐 uv 安装预编译 wheel
uv add fasttext-wheel
# 或 pip
pip install fasttext-wheel
⚠️ 版本坑 FastText 在 Python 3.14 上会有兼容性问题,Python 3.8 ~ 3.12 是最稳的。如果用 3.14 报 distutils 相关错误,建议降到 3.12 或换用 Gensim。
📌 准备语料
# corpus.txt:每行一句,词语已经用空格分开
我 爱 学习 自然 语言 处理
今天 天气 真 好
机器 学习 是 人工智能 的 重要 分支
深度 学习 推动 了 自然语言处理 的 进步
📌 训练 Skip-gram 模型
import fasttext
# 训练无监督 Word2Vec
model = fasttext.train_unsupervised(
input='corpus.txt',
model='skipgram', # 也可填 'cbow'
dim=100, # 词向量维度,越大表示能力越强但越慢
ws=5, # 上下文窗口大小
minCount=2, # 词频低于此值的词会被过滤
epoch=5 # 训练轮数
)
# 保存模型
model.save_model("word2vec.bin")
📌 加载 & 最近邻查询
import fasttext
# 加载训练好的模型
model = fasttext.load_model("word2vec.bin")
# 1. 取单个词的词向量
vec = model.get_word_vector("自然语言")
print(vec.shape) # (100,)
# 2. 找最相近的 5 个词
neighbors = model.get_nearest_neighbors("人工智能", k=5)
for similarity, word in neighbors:
print(f"{word}: {similarity:.4f}")
# 输出示例:
# 机器学习: 0.8765
# 深度学习: 0.8234
# 大数据: 0.7891
💡 FastText 关键参数速查
| model | 训练模式 cbow 或 skipgram | skipgram |
| dim | 词向量维度 | 100 |
| ws | 上下文窗口 | 5 |
| minCount | 最低词频 | 5 |
| epoch | 训练轮数 | 5 |
| lr | 学习率 | 0.05 |
| thread | 训练线程数 | CPU 核心数 |
方式二:用 Gensim 加载 / 训练
Gensim 是 NLP 圈最常用的主题模型 / 词向量库,提供了加载预训练词向量和自行训练两种姿势。
📌 加载第三方预训练词向量
from gensim.models import KeyedVectors
# 加载腾讯 / 微博 / Google 公开词向量(这里以微博 300 维为例)
# 文件很大(几 GB),首次下载需耐心
model = KeyedVectors.load_word2vec_format('sgns.weibo.word.bz2')
print(model.vector_size) # 300
print(model['地铁'][:10]) # 取"地铁"词向量的前 10 维
📷 推荐词向量资源
- Tencent AI Lab Embedding Corpus:覆盖中英 800w+ 词;
- Chinese-Word-Vectors:北大开源,多种训练语料;
- Google News Word2Vec:英文 300 维,300w 词。
📌 余弦相似度与语义代数
# 1. 计算两个词的余弦相似度
sim = model.similarity('地铁', '公交')
print(f"地铁 vs 公交 相似度: {sim:.4f}")
# 2. 找最相似的词
print(model.similar_by_word('男孩', topn=3))
# 3. 经典语义代数:爸爸 – 男性 + 女性 ≈ 妈妈
result = model.most_similar(
positive=['爸爸', '女性'],
negative=['男性'],
topn=3
)
print(result)
📌 余弦相似度公式
similarity
(
w
1
,
w
2
)
=
cos
(
θ
)
=
w
1
⋅
w
2
∣
w
1
∣
⋅
∣
w
2
∣
\\text{similarity}(w_1, w_2) = \\cos(\\theta) = \\frac{w_1 \\cdot w_2}{|w_1| \\cdot |w_2|}
similarity(w1,w2)=cos(θ)=∣w1∣⋅∣w2∣w1⋅w2 取值范围 [-1, 1]:接近 1 → 语义相近,接近 0 → 不相关,接近 -1 → 方向相反。
📌 用 Gensim 自行训练
from gensim.models import Word2Vec
# 语料:每个元素是一个已分词的句子(list of tokens)
sentences = [
['我', '每天', '乘坐', '地铁', '上班'],
['我', '每天', '乘坐', '公交', '上班'],
['地铁', '比', '公交', '快', '很多'],
['北京', '的', '地铁', '非常', '拥挤'],
]
# 训练 Skip-gram(sg=1);sg=0 表示 CBOW
model = Word2Vec(
sentences,
vector_size=100, # 词向量维度
window=5, # 上下文窗口
min_count=1, # 最低词频(语料少时设小一点)
sg=1, # 1: Skip-Gram, 0: CBOW
workers=4 # 并行线程数
)
# 找最相似的词
print(model.wv.most_similar('地铁', topn=3))
# 保存词向量
model.wv.save_word2vec_format('my_vectors.vec')
5.3 Word Embedding:深度学习里的"可训练词向量"
Word Embedding 是个比 Word2Vec 更宽泛的概念:把离散的 token 映射为低维稠密向量的过程都叫词嵌入。
在现代深度学习中,它通常特指神经网络里的 Embedding 层——一个可训练的参数矩阵。这是和 Word2Vec 最大的区别:
| 训练方式 | 先在语料上预训练,词向量冻住用 | 跟着下游任务端到端训练 |
| 适用场景 | 词向量当特征 / 检索 | 配合 LSTM / Transformer 微调 |
| 典型 API | gensim.Word2Vec | torch.nn.Embedding |
PyTorch nn.Embedding 实战
import torch
import torch.nn as nn
# 1. 创建 Embedding 层
# 词表 1000 词,每个词映射成 128 维向量
embedding = nn.Embedding(num_embeddings=1000, embedding_dim=128)
# 2. 喂入 token id
# shape: (batch=2, seq_len=4)
token_ids = torch.tensor([
[10, 200, 5, 333],
[88, 2, 17, 999]
])
vectors = embedding(token_ids)
# 输出 shape: (2, 4, 128)
# 对应 batch=2 条句子,每条 4 个 token,每个 token 128 维向量
print(vectors.shape) # torch.Size([2, 4, 128])
# 3. 查看可训练参数
print(embedding.weight.shape) # torch.Size([1000, 128])
⚠️ 踩坑提示
- 输入必须是 LongTensor(int64),不是 FloatTensor,否则会报 RuntimeError: expected scalar type Long;
- nn.Embedding 内部就是一个 (num_embeddings, embedding_dim) 的可学习矩阵,初始值随机;
- 如果要加载预训练词向量(Word2Vec / GloVe),用 nn.Embedding.from_pretrained(…),并指定 freeze=False 决定是否微调。
import torch
import torch.nn as nn
import numpy as np
# 假设已经从 gensim 拿到词向量矩阵 (vocab_size, dim)
pretrained_weights = np.random.randn(1000, 128).astype(np.float32) # 替换成真实词向量
embedding_from_pt = nn.Embedding.from_pretrained(
torch.from_numpy(pretrained_weights),
freeze=False # True: 不微调;False: 跟着任务继续训练
)
六、避坑指南 & 最佳实践
⚠️ 5 个最常见的"翻车"现场
💡 工业级最佳实践
- 小数据 + 简单任务 → jieba + 预训练 Word2Vec + LR / SVM,传统但稳定;
- 中等数据 + 文本分类 → jieba + 自训 Embedding + LSTM / BiLSTM;
- 大数据 + 通用任务 → HuggingFace Tokenizers + BERT / RoBERTa 微调,永远不会错;
- 超大规模 + 生成任务 → 直接调用 ChatGPT / Qwen API,自己训 LLM 性价比极低。
七、总结:一张表打通"选型"
| 出现时间 | 1980s ~ | 2013 (Google) | 深度学习时代 |
| 向量类型 | 高维稀疏(0/1) | 低维稠密(float) | 低维稠密(可训练) |
| 词表规模 | 极小(<1w) | 适中(10w 级) | 任意 |
| 语义信息 | ❌ 无 | ✅ 强 | ✅ 强(依赖数据量) |
| OOV 处理 | ❌ 不支持 | ⚠️ FastText 子词兜底 | ⚠️ 需 <UNK> 兜底 |
| 训练成本 | 0 | 中(CPU 几十分钟) | 跟着下游任务 |
| 适用场景 | 教学 / 入门 demo | 传统 NLP 任务 / 文本检索 | 配合 LSTM / Transformer |
📌 下一步学什么? 有了"分词 + 文本表示"这块地基,就可以继续往 NLP 深处走了:
笔者后续会按顺序更新这些笔记,欢迎关注~
参考资料
- jieba 中文分词
- FastText 官方文档
- Gensim Word2Vec 官方文档
- HuggingFace NLP Course – BPE 分词
- PyTorch nn.Embedding 官方文档
- Tencent AI Lab Embedding Corpus
- Chinese-Word-Vectors(北大开源词向量)
- HuggingFace Tokenizers
- SentencePiece
- OpenAI tiktoken

![[特殊字符]DeepSeek‑Harness(DSH)小白保姆教程-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260816085112-6a817a009aabf-220x150.png)
