一句话总结
分词解决了"怎么切",文本表示解决"怎么算"--把词语变成带语义信息的数字向量,让模型能做加减乘除.
从词袋到词向量的演进,本质是从"有没有这个词"到"这个词是什么意思"的跃迁.
为什么需要文本表示
上一篇我们知道了分词和编码会把文本变成 ID 序列:
|
但这些 ID 只是查找键--数字 872 比 5554 小,不代表"你"比"脑"更消极. ID 之间没有数学关系,不能直接做加减比较.
我们需要一种表示方式,让:
- "骂"和"骂人"在数学上靠近(语义相似)
- "骂"和"夸"在数学上远离(语义相反)
- 模型能通过向量运算推导出语义关系
ID = 数据库主键,只用于定位行,不能比较大小关系. 但如果给每行加一个"语义向量"字段,就能做相似度搜索了--文本表示做的就是给每个词/句子加上这样一个"语义坐标".
文本表示的输入是什么
上一篇讲了"分词 → 编码为 ID",那文本表示的输入是 ID 序列还是原文字符串? 答案是:取决于用哪种方法,二者是并行的两条路径,不是先后两步.
|
| 方法 | 输入 | 为什么 |
|---|---|---|
| TF-IDF | 分词后的字符串列表 | 内部自己统计词频,计算权重 |
| FastText | 分词后的字符串文本 | 内部自己做 word → vector 映射 |
| Word2Vec | 分词后的字符串列表 | 用字符串查预训练向量表 |
| BERT | Tokenizer 输出的 ID 序列 | 用 ID 查 Embedding 矩阵 |
上一篇讲的"编码为 ID"专指 BERT 路径. 传统方法(TF-IDF/FastText)不需要手动编码--把分词后的字符串喂进去,它们内部处理剩下的事.
词袋模型:最朴素的起点
词袋模型(Bag of Words,BoW)是最简单的文本表示方式--只看文本中出现了哪些词,以及出现了几次,完全忽略词序.
假设词表只有 6 个词:
|
每个文本被表示为一个长度等于词表大小的向量,每个位置记录对应词的出现次数.
词袋的致命缺陷
"你有问题"和"问题有你"的词袋向量完全相同--因为词序被丢弃了. "这个东西不好"和"这个东西好"只差一个"不"字,但语义完全相反.
对于对骂检测,"我服了你"和"你服了我"意思截然不同,词袋分不出来.
尽管如此,词袋 + 简单分类器在很多场景下依然有效. 如果对骂文本有明显的标志词("滚""傻""废物"),词袋模型看到这些词出现就能做出正确判断. 它是 baseline 中的 baseline.
TF-IDF:给词袋加权重
词袋模型的另一个问题:所有词同等重要. 但显然"的""了""是"这种高频词不如"废物""垃圾"这种低频词有区分度.
TF-IDF(Term Frequency - Inverse Document Frequency)通过两个因子给每个词打分:
| 因子 | 含义 | 直觉 |
|---|---|---|
| TF(词频) | 这个词在当前文本中出现的频率 | 出现越多,对这篇文本越重要 |
| IDF(逆文档频率) | log(总文档数 / 包含该词的文档数) | 越少文档包含这个词,这个词区分度越高 |
TF-IDF = TF × IDF. 效果是:
- "废物":TF 高(在对骂文本中频繁出现) × IDF 高(正常文本很少用) → 高权重
- "的":TF 高 × IDF 极低(几乎所有文本都有) → 低权重
TF-IDF = Go 服务中的限流权重. 不是所有请求同等重要,来自核心接口的请求(低频但重要)权重比心跳探测(高频但无意义)高得多. TF-IDF 自动把"信息量大的词"权重调高.
|
TF-IDF 在项目中的位置
阶段二会用 TF-IDF + LogisticRegression 做第一个 baseline 分类器. 它训练只需几秒钟,效果往往就能达到 80%+,用来快速验证"数据标注质量是否够用"非常合适.
词袋系方法的共同局限
不管是 BoW 还是 TF-IDF,它们有三个本质限制:
- 高维稀疏:向量维度 = 词表大小(几万到几十万),但每个文本只命中极少数词,绝大部分位置是 0
- 无语义关系:"开心"和"高兴"在向量空间中没有任何关系--它们只是词表中不同位置的 1
- 忽略词序:"我打你"和"你打我"表示完全相同
这就引出了下一代方法--词向量:用一个固定长度的稠密向量来表示每个词,让语义相近的词在向量空间中距离更近.
Word2Vec:让词有了"坐标"
Word2Vec(2013,Google)是词向量的开山之作. 核心思想用一句话概括:
一个词的含义,由它周围的词决定.(You shall know a word by the company it keeps.)
直觉解释:如果"废物"和"垃圾"总是出现在相似的上下文中(比如"你这个___"),那么它们的含义应该相近--它们的向量也应该相近.
训练过程(直觉版)
Word2Vec 有两种训练方式,这里讲最好理解的 CBOW(Continuous Bag of Words):
|
训练完成后,每个词得到一个固定维度的向量(通常 100-300 维):
|
词向量的神奇性质
训练出的词向量能做语义算术:
|
这意味着向量空间中编码了语义关系. 对对骂检测来说,重要的不是这些花哨的例子,而是一个实用推论:
对骂检测的意义
即使训练数据中只标注了"废物"是骂人的词,模型也能通过词向量的相似性推断出"垃圾""蠢货""白痴"也是类似含义--因为它们的向量坐标靠近. 这就是词向量带来的泛化能力.
FastText:Word2Vec 的实用升级
FastText(2016,Facebook)是 Word2Vec 的改进版,对中文和网络用语场景特别有价值. 核心改进:
基于子词(subword)构建词向量--把每个词拆成字符 n-gram,用子片段的向量相加得到词向量.
|
FastText 的优势:
| 特性 | Word2Vec | FastText |
|---|---|---|
| 未登录词(OOV) | 无法处理 | 用子词拼合,总能得到向量 |
| 训练速度 | 快 | 同样快(几分钟训完) |
| 拼写错误容忍 | 无 | 有("废物"和"废务"共享子词) |
| 内置分类器 | 无 | 有(直接做文本分类) |
FastText 在项目中的位置
阶段二会用 FastText 的文本分类功能(不是词向量功能)做对骂检测的第一个 baseline. 它训练极快,部署极简(一个二进制文件),是验证数据质量的最佳工具.
静态词向量 vs 上下文词向量
Word2Vec 和 FastText 有一个根本限制:每个词只有一个固定向量,不管它出现在什么上下文中.
|
这就是静态词向量(Static Embedding)的局限--一词一义.
BERT 解决了这个问题:它为每个词生成的向量取决于上下文--同一个"有意思",在不同句子中会得到不同的向量表示. 这叫上下文词向量(Contextual Embedding).
|
静态词向量 = Go 的常量(const),编译时确定,运行时不变.
上下文词向量 = 接口值(interface{}),同一个变量名,绑定的具体实现取决于运行时的上下文.
Embedding:把 ID 变成向量的那一步
前面讲了 TF-IDF, Word2Vec, FastText, BERT 四种文本表示方法,它们看起来很不同,但其实都要解决同一个问题:怎么把一个"词"变成一个"向量".
这个"词 → 向量"的转换动作,在神经网络中有个统一的名字叫 Embedding(嵌入). 理解了它,前面所有方法就能串起来.
Embedding 到底是什么
Embedding 的本质极其简单--一张二维表格,行数 = 词表大小,列数 = 向量维度. 查表就是 Embedding 的全部操作.
|
|
Embedding 表 = Redis 的 HGET 操作. 给一个 key(token ID),返回一个 value(向量). 区别在于这张表的 value 不是人写的,而是通过训练"学"出来的--让语义相近的词对应的向量在数学上靠近.
输入方式:整个 ID 序列一起进去
这里的 token ID 就是上一篇 Tokenizer 输出的那个 ID 序列. 输入方式是整个句子的 ID 序列一起输入,同时查表得到一批向量:
|
|
为什么必须一起输入而不是一个个来? 因为 BERT 的下一步(Transformer 层)需要让每个位置的向量"看到"序列中所有其他位置. 如果一个个输入,就没有"其他位置"可以参照了.
这也是 Word2Vec 和 BERT 的本质区别:
- Word2Vec:一个个查或一起查都无所谓,因为查完就是最终结果,各词向量互不影响
- BERT:必须整个序列一起输入,因为查表只是第一步,后面 Transformer 要让所有向量互相交互--"病"的最终向量会受到"你""脑子"等其他位置的影响
|
为什么要引入 Embedding 这个概念
因为前面讲的所有方法,本质上都是在回答**"这张表怎么来"**这个问题:
| 方法 | 表怎么来 | 表的特点 |
|---|---|---|
| 词袋 / TF-IDF | 统计计算(不是真正的 Embedding) | 稀疏,高维,无语义 |
| Word2Vec | 用大量文本无监督训练出来的 | 稠密,低维,有语义,但固定不变 |
| FastText | 同上 + 子词增强 | 同上 + 能处理新词 |
| BERT | 大规模预训练出来的 | 稠密,低维,有语义,且会被后续 Transformer 层动态修改 |
所以 Embedding 不是一个"新方法",而是对前面所有方法的统一抽象--它们都在做"ID → 向量"这件事,只是表的生成方式和使用方式不同.
静态查表 vs 动态计算
这是最关键的区分:
|
用 Go 类比:
|
这就是为什么 BERT 能区分"你真有意思"(夸)和"你少来这套有意思的"(讽)--同一个"有意思"的初始 Embedding 相同,但经过 Transformer 层后,受到不同上下文的影响,最终向量截然不同.
对任务怎么选
|
实际项目建议两者都做:FastText 作为快速 baseline 和兜底方案(极低延迟),BERT 作为精度方案(处理复杂语义). 线上可以用 FastText 先过滤掉明显的对骂(置信度 > 0.95),剩下不确定的再走 BERT.
快速回顾
- 词袋/TF-IDF:统计"有没有/多不多",高维稀疏,无语义关系,但够快够简单做 baseline
- Word2Vec:通过上下文共现学习词向量,语义相近的词向量靠近,但一词一义
- FastText:Word2Vec 加子词增强,能处理 OOV 和拼写变体,自带文本分类器
- BERT Embedding:上下文感知的动态词向量,同一个词在不同语境中表示不同--解决反讽/阴阳怪气问题的关键
- 演进主线:稀疏统计 → 稠密静态 → 稠密动态(上下文感知)
动手练习
- 生成 TF-IDF 向量:用 sklearn 的
TfidfVectorizer对种子数据集的 40 条文本生成 TF-IDF 向量,观察维度和稀疏度 - 验证语义空间:用
gensim加载预训练中文 Word2Vec,查询"废物""垃圾""天气"的相似词,验证语义空间直觉 - 计算类间距离:计算种子数据集中对骂样本和正常样本的 TF-IDF 向量的平均余弦相似度--看两类文本在向量空间中是否天然分开