查看全课大纲(第 16 / 69 节)
- 1.NLP 的研究领域、应用与学习路径
- 2.自然语言的基本术语
- 3.信息论基础:信息熵与交叉熵
- 4.语言的进化与语义变迁
- 5.主观概率、客观概率与条件概率
- 6.贝叶斯原理与推理
- 7.随机变量:二项式、期望方差与联合概率
- 8.常见概率分布
- 9.辛普森悖论与贝叶斯解题实例
- 10.语言模型:语言概率与词袋模型
- 11.n-gram 语言模型(二元 / 三元 / N-Gram)
- 12.语言模型评价:交叉熵、困惑度与插值
- 13.朴素贝叶斯文本分类:垃圾邮件
- 14.拼写检查
- 15.中文分词:构词法、最大匹配、最优路径与 Jieba
- 16.词表示问题与语义相似度
- 17.TF-IDF 权重处理
- 18.One-Hot 表达与词表示的局限
- 19.Word2Vec 核心思想
- 20.Word2Vec 训练过程
- 21.Skip-gram 模型详解
- 22.词性标注任务与标注方法(规则 + N-Gram)
- 23.EM 算法与混合模型
- 24.高斯混合模型 GMM
- 25.从混合模型到隐马尔可夫模型
- 26.隐马尔可夫模型(HMM)推导与维特比解码
- 27.线性链条件随机场 CRF
- 28.贝叶斯网络与图模型
- 29.隐马尔可夫链与 HMM 推导
- 30.PLSA 主题模型
- 31.LSI 与 SVD 向量表达
- 32.LDA 与狄利克雷分布
- 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
- 34.上下文无关文法(CFG)与句法分析
- 35.概率上下文无关文法 PCFG
- 36.统计机器翻译:词对齐与翻译模型
- 37.统计机器翻译中的对齐模型与参数估计
- 38.从统计机器翻译到神经机器翻译
- 39.神经元与反向传播速览(指向深度学习课)
- 40.CNN 原理及其在 NLP 中的应用
- 41.循环神经网络的基本原理与 Elman / Jordan 网络
- 42.LSTM:长短期记忆网络的核心思想与分步实现
- 43.Encoder-Decoder 框架
- 44.Seq2Seq 与早期注意力机制
- 45.注意力机制的背景与动机
- 46.注意力模型实现原理与匹配函数
- 47.记忆网络:组成与计算
- 48.注意力机制的应用与历史延展
- 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
- 50.Multi-Head Attention、FFNN 与位置编码
- 51.层归一化、损失函数与 Universal Transformer 延展
- 52.预训练范式:从 ELMo 到 BERT
- 53.BERT 与掩码语言模型
- 54.BERT 下游任务微调与 GLUE 口径
- 55.GPT 与自回归语言建模
- 56.GPT-1 到 GPT-4 的演进
- 57.指令微调与 RLHF
- 58.分词器:BPE、WordPiece 与中文词表
- 59.上下文窗口与长文本处理
- 60.能力边界、成本与适用场景
- 61.文本分类实战:从朴素贝叶斯到逻辑回归
- 62.命名实体识别实战
- 63.文本生成与摘要实战
- 64.语义检索与向量库实战
- 65.RAG 知识库问答
- 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
- 67.幻觉与事实性问题
- 68.文本与知识结合(知识图谱压缩)
- 69.跨媒体信息检索与未来展望
词表示问题与语义相似度
小象实战讲义 · 自然语言处理
在自然语言处理中,如何让计算机“理解”词语的含义是核心挑战。本节我们将探讨词表示(Word Representation)的基本问题,即如何将离散的、符号化的词语转换为计算机可以处理的数学对象。我们将从最直观但存在局限的独热表示(One-Hot)入手,引出基于分布式假设的语义相似度概念,并学习一种基于计数的经典方法,为后续学习更强大的词向量模型(如Word2Vec)奠定基础。本节内容衔接了传统语言模型与神经网络模型,是理解现代NLP模型如何“表示”语言的关键。
💡 核心导读
- 词表示的动机:将自然语言(离散符号序列)转化为数学向量,以便进行相似度计算、分类等下游任务。
- 独热表示的局限:向量稀疏、维度高、无法表达词语间的语义关系(正交性)。
- 分布式语义假设:“一个词的含义由其上下文决定”,这是构建稠密、语义化词向量的理论基础。
- 基于计数的词表示方法:通过统计一个词在语料库中特定窗口内与其他词共现的频率,构建其向量表示。
- 相似度度量:使用余弦相似度(Cosine Similarity)来衡量两个词向量在语义空间中的接近程度。
从符号到向量:词表示的动机
自然语言文本本质上是一个离散符号(Discrete Symbols)的序列,例如单词序列。对于计算机而言,像“kitten”(小猫)和“cat”(猫)这样的词,如果不经过特殊处理,它们就是两个完全独立、毫无关系的符号,就像“钱包”和“书包”一样。
为了让机器学习模型能够处理语言,我们必须将这些符号数学化。无论是机器翻译、语音识别还是拼写纠正,都需要将词或语言本身视为数据,并通过数学方式进行计算和推理。这就是词表示的核心目标:为词汇表中的每一个词,找到一个合适的数学表示(通常是向量),从而将语言问题转化为数学问题。
独热表示及其局限
最直观的词表示方法是独热表示(One-Hot Representation)。
定义:假设我们的词汇表(Vocabulary)大小为 。对于词汇表中的第 个词,我们用一个长度为 的向量来表示它,这个向量在第 维上是1,其余所有维都是0。
例如,假设词汇表 ,那么:
a的独热向量为hotel的独热向量为zebra的独热向量为
# 示例:独热向量表示
vocabulary = ['a', 'hotel', 'motel', 'dog', 'zebra']
vocab_size = len(vocabulary)
# 创建词到索引的映射
word_to_idx = {word: i for i, word in enumerate(vocabulary)}
def one_hot_encode(word):
vec = [0] * vocab_size
vec[word_to_idx[word]] = 1
return vec
hotel_vec = one_hot_encode('hotel')
motel_vec = one_hot_encode('motel')
dog_vec = one_hot_encode('dog')
print(f"词汇表: {vocabulary}")
print(f"hotel 的独热向量: {hotel_vec}")
print(f"motel 的独热向量: {motel_vec}")
print(f"dog 的独热向量: {dog_vec}")词汇表: ['a', 'hotel', 'motel', 'dog', 'zebra']
hotel 的独热向量: [0, 1, 0, 0, 0]
motel 的独热向量: [0, 0, 1, 0, 0]
dog 的独热向量: [0, 0, 0, 1, 0]有了向量表示,我们就可以进行数学计算,比如计算两个词向量的相似度(Similarity)。一个简单的相似度度量是向量的点积(内积)。
# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
vocabulary = ['a', 'hotel', 'motel', 'dog', 'zebra']
vocab_size = len(vocabulary)
word_to_idx = {word: i for i, word in enumerate(vocabulary)}
def one_hot_encode(word):
vec = [0] * vocab_size
vec[word_to_idx[word]] = 1
return vec
hotel_vec = one_hot_encode('hotel')
motel_vec = one_hot_encode('motel')
dog_vec = one_hot_encode('dog')
# 计算独热向量之间的点积(内积)
def dot_product(vec_a, vec_b):
return sum(a * b for a, b in zip(vec_a, vec_b))
print(f"hotel · motel = {dot_product(hotel_vec, motel_vec)}")
print(f"hotel · dog = {dot_product(hotel_vec, dog_vec)}")
print(f"hotel · hotel = {dot_product(hotel_vec, hotel_vec)}")hotel · motel = 0
hotel · dog = 0
hotel · hotel = 1独热表示的核心问题:
- 稀疏性(Sparse):向量维度极高(等于词汇表大小),且绝大多数元素为0。
- 正交性(Orthogonal):任意两个不同词的独热向量点积为0,即它们是正交的。这意味着模型无法从表示中得知
hotel和motel的语义相关性远高于它们与dog的相关性。 - 语义信息缺失:无法捕获词与词之间的任何语义关系(如同义、近义、上下位关系)。
我们希望得到的词表示,应该能让语义相近的词(如 hotel 和 motel)在向量空间中距离较近,而与语义较远的词(如 dog)距离较远。独热表示无法做到这一点。
分布式语义与基于计数的方法
如何获得能表达语义相似度的词表示呢?语言学家 J.R. Firth 在1957年提出了一个深刻见解:“You shall know a word by the company it keeps.”(观其伴,知其义。)这被称为分布式语义假设(Distributional Hypothesis):一个词的含义可以由其上下文(Context)中的词来定义。
基于这个假设,我们可以通过统计一个词在大量文本中与哪些词共同出现(共现)来构建它的向量表示。这就是基于计数的方法(Count-Based Methods)。
基本步骤:
- 定义一个上下文词基础词汇表(Basis Vocabulary) ,通常由语料库中除停用词(Stop Words)外的重要词构成。
- 定义一个窗口大小(Window Size) (例如, 表示考虑中心词左右各3个词)。
- 在语料库中扫描每个目标词(Target Word) 的出现位置。
- 对于每个目标词实例,统计其窗口内出现的、属于基础词汇表 的上下文词。
- 汇总所有实例的统计结果,形成一个基于上下文词计数的向量,作为目标词的表示。
举例说明: 假设我们有如下小型语料库(已去除 the, and, on, that, she 等停用词):
cute kitten purredcute furry cat purred miaowedsmall kitten miaowedloud furry dog ran bit
我们定义基础词汇表 = {bit, cute, furry, loud, miaowed, purred, ran, small},窗口大小 (即中心词左右各取两个词;四句话都很短,句内不足两个词的一侧就只取到句界为止)。
- 对于
kitten:- 在句子1中,上下文是 {
cute,purred} - 在句子3中,上下文是 {
small,miaowed} - 合并统计:{
cute,purred,small,miaowed} - 向量表示为(按C顺序):
[0, 1, 0, 0, 1, 1, 0, 1]
- 在句子1中,上下文是 {
- 对于
cat:- 在句子2中,上下文是 {
cute,furry,purred,miaowed} - 向量表示为:
[0, 1, 1, 0, 1, 1, 0, 0]
- 在句子2中,上下文是 {
- 对于
dog:- 在句子4中,上下文是 {
loud,furry,ran,bit} - 向量表示为:
[1, 0, 1, 1, 0, 0, 1, 0]
- 在句子4中,上下文是 {
# 基于计数的词表示示例
import math
# 基础上下文词汇表
context_vocab = ['bit', 'cute', 'furry', 'loud', 'miaowed', 'purred', 'ran', 'small']
context_to_idx = {word: i for i, word in enumerate(context_vocab)}
# 定义目标词的上下文计数向量(基于上述手工分析)
kitten_vec = [0, 1, 0, 0, 1, 1, 0, 1] # cute, miaowed, purred, small
cat_vec = [0, 1, 1, 0, 1, 1, 0, 0] # cute, furry, miaowed, purred
dog_vec = [1, 0, 1, 1, 0, 0, 1, 0] # bit, furry, loud, ran
def cosine_similarity(vec_a, vec_b):
"""计算两个向量的余弦相似度"""
dot = sum(a * b for a, b in zip(vec_a, vec_b))
norm_a = math.sqrt(sum(a * a for a in vec_a))
norm_b = math.sqrt(sum(b * b for b in vec_b))
if norm_a == 0 or norm_b == 0:
return 0
return dot / (norm_a * norm_b)
print("基于上下文的词向量(基础词汇表顺序):")
print(f"kitten: {kitten_vec}")
print(f"cat: {cat_vec}")
print(f"dog: {dog_vec}")
print("\n余弦相似度计算:")
print(f"kitten 与 cat 的余弦相似度: {cosine_similarity(kitten_vec, cat_vec):.4f}")
print(f"cat 与 dog 的余弦相似度: {cosine_similarity(cat_vec, dog_vec):.4f}")
print(f"kitten 与 dog 的余弦相似度: {cosine_similarity(kitten_vec, dog_vec):.4f}")基于上下文的词向量(基础词汇表顺序):
kitten: [0, 1, 0, 0, 1, 1, 0, 1]
cat: [0, 1, 1, 0, 1, 1, 0, 0]
dog: [1, 0, 1, 1, 0, 0, 1, 0]
余弦相似度计算:
kitten 与 cat 的余弦相似度: 0.7500
cat 与 dog 的余弦相似度: 0.2500
kitten 与 dog 的余弦相似度: 0.0000结果分析:
kitten与cat的余弦相似度为 0.7500:两者各有 4 个非零上下文维度,其中cute、purred、miaowed三项共享,kitten独有small、cat独有furry。共享比例高,所以相似度高,符合它们语义相近的直觉。cat与dog的余弦相似度为 0.2500:8 个维度里只共享furry一项(cat另有cute、purred、miaowed,dog另有loud、ran、bit)。共享少,相似度自然低。kitten与dog的相似度为 0.0000:两者的上下文集合在这个小语料里完全不相交,向量正交。
对比一下独热表示:三个词的独热向量两两点积都是 0,相似度全都一样是 0,无法把“kitten 更像 cat”这件事表达出来。基于计数的表示做到了 0.75 / 0.25 / 0 的区分,这才是它能表达语义相似度的原因。
余弦相似度(Cosine Similarity) 是衡量向量相似度的常用指标,它计算两个向量夹角的余弦值,取值范围为 ,值越大表示越相似。其定义为: 它具有归一化的优点,对向量的绝对长度不敏感,更适合比较不同频率词的表示。
方法的改进与局限
上述基于计数的方法虽然简单有效,但仍有改进空间:
- 高频词影响:像
the,a这样的高频词,作为上下文时可能提供很少的语义信息。我们需要区分哪些共现是信息丰富的,哪些只是偶然或普遍的。这引出了像 TF-IDF、点互信息(PMI) 等加权或归一化方法。 - 向量维度:向量维度等于基础词汇表大小,仍然可能很高。我们能否学习到一个更低维、更稠密的表示?这直接引向了基于神经网络的词嵌入模型,如 Word2Vec。
从表示学习的角度看,基于计数的方法可以看作是在学习一个嵌入矩阵(Embedding Matrix) ,其中每一行对应一个目标词的向量。而独热向量 可以用来检索这个矩阵中的某一行:。
📝 动手练一练
- 概念辨析:独热表示和基于上下文的计数表示,在表达词语“语义相似度”的能力上有何本质区别?请结合
hotel,motel,dog的例子说明。
👉 点击查看参考答案
本质区别在于是否利用了词的分布信息(上下文)。
- 独热表示:每个词被映射到一个唯一且正交的维度。
hotel和motel的向量点积为0,hotel和dog的点积也为0。这种表示法完全无法区分语义相近词对 (hotel,motel) 与语义无关词对 (hotel,dog) 在相似度上的差异,因为它没有编码任何语义关联。 - 基于上下文的计数表示:一个词的向量由其周围出现的词(上下文)的统计信息构成。
hotel和motel很可能拥有相似的上下文(如book,stay,room),因此它们的向量在非零维度上有大量重叠,余弦相似度高。而dog的上下文(如walk,bark,pet)与hotel差异很大,导致向量重叠少,相似度低。这种表示法能够通过共享的上下文来反映语义的相似性。
- 手动计算:假设有一个极小的语料库,包含三个句子(已分词):
['fast', 'car', 'race'],['new', 'car', 'buy'],['fast', 'dog', 'run']。设定上下文基础词汇表为['fast', 'new', 'race', 'buy', 'run'],窗口大小 。请手动计算或编写代码验证car和dog的基于计数的向量表示,并计算它们的余弦相似度。
👉 点击查看参考答案
# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
vocabulary = ['a', 'hotel', 'motel', 'dog', 'zebra']
vocab_size = len(vocabulary)
word_to_idx = {word: i for i, word in enumerate(vocabulary)}
def one_hot_encode(word):
vec = [0] * vocab_size
vec[word_to_idx[word]] = 1
return vec
hotel_vec = one_hot_encode('hotel')
motel_vec = one_hot_encode('motel')
dog_vec = one_hot_encode('dog')
def dot_product(vec_a, vec_b):
return sum(a * b for a, b in zip(vec_a, vec_b))
import math
context_vocab = ['bit', 'cute', 'furry', 'loud', 'miaowed', 'purred', 'ran', 'small']
context_to_idx = {word: i for i, word in enumerate(context_vocab)}
kitten_vec = [0, 1, 0, 0, 1, 1, 0, 1] # cute, miaowed, purred, small
cat_vec = [0, 1, 1, 0, 1, 1, 0, 0] # cute, furry, miaowed, purred
dog_vec = [1, 0, 1, 1, 0, 0, 1, 0] # bit, furry, loud, ran
def cosine_similarity(vec_a, vec_b):
"""计算两个向量的余弦相似度"""
dot = sum(a * b for a, b in zip(vec_a, vec_b))
norm_a = math.sqrt(sum(a * a for a in vec_a))
norm_b = math.sqrt(sum(b * b for b in vec_b))
if norm_a == 0 or norm_b == 0:
return 0
return dot / (norm_a * norm_b)
# 手动推导/代码验证
corpus_small = [['fast', 'car', 'race'], ['new', 'car', 'buy'], ['fast', 'dog', 'run']]
context_vocab_small = ['fast', 'new', 'race', 'buy', 'run']
c2i = {w:i for i,w in enumerate(context_vocab_small)}
def manual_count(word, corpus, window=1):
vec = [0]*len(context_vocab_small)
for sent in corpus:
for i, w in enumerate(sent):
if w == word:
start = max(0, i-window)
end = min(len(sent), i+window+1)
# 收集窗口内的上下文词
context_words = sent[start:end]
# 移除中心词本身
context_words.remove(word)
# 统计
for cw in context_words:
if cw in c2i:
vec[c2i[cw]] += 1
return vec
car_vec_small = manual_count('car', corpus_small)
dog_vec_small = manual_count('dog', corpus_small)
print(f"基础词汇表: {context_vocab_small}")
print(f"car 的计数向量: {car_vec_small}")
print(f"dog 的计数向量: {dog_vec_small}")
# 使用前面定义的 cosine_similarity 函数
sim = cosine_similarity(car_vec_small, dog_vec_small)
print(f"car 与 dog 的余弦相似度: {sim:.4f}")基础词汇表: ['fast', 'new', 'race', 'buy', 'run']
car 的计数向量: [1, 1, 1, 1, 0]
dog 的计数向量: [1, 0, 0, 0, 1]
car 与 dog 的余弦相似度: 0.3536计算过程:
car的上下文:- 在
['fast', 'car', 'race']中,窗口内词为fast,race。 - 在
['new', 'car', 'buy']中,窗口内词为new,buy。 - 合并统计:
fast(1),new(1),race(1),buy(1),run(0)。向量为[1, 1, 1, 1, 0]。
- 在
dog的上下文:- 在
['fast', 'dog', 'run']中,窗口内词为fast,run。 - 合并统计:
fast(1),new(0),race(0),buy(0),run(1)。向量为[1, 0, 0, 0, 1]。
- 在
- 余弦相似度计算:
- 点积 = 11 + 10 + 10 + 10 + 0*1 = 1
car的模 =dog的模 =- 相似度 = ,与代码输出一致。
- 分析:
car和dog因为共享了上下文词fast,所以相似度不为0,但整体相似度不高,符合直觉。
本章小结
本节我们深入探讨了自然语言处理中词表示的根本问题。我们认识到,将词语从离散符号转化为有意义的数学向量是NLP模型工作的基础。独热表示虽然简单,但其稀疏性和正交性使其无法捕获语义信息。Firth的分布式假设为我们指明了方向:通过词的上下文来定义其含义。基于计数的方法实践了这一思想,通过统计共现信息构建词向量,并利用余弦相似度来衡量语义关联,取得了比独热表示合理得多的结果。
然而,基于计数的方法通常会产生高维向量,且对高频常见词的处理不够精细。这为下一阶段基于神经网络的词嵌入模型(如Word2Vec)的兴起埋下了伏笔,后者能够学习到更低维、更稠密、且语义关系更丰富的词向量。
📋 行动清单
- 理解独热表示的构造方式及其在语义相似度计算上的根本缺陷。
- 掌握分布式语义假设的核心思想:“观其伴,知其义”。
- 能够描述基于计数的词表示方法的基本步骤,并理解其如何体现语义相似度。
- 会计算两个向量的余弦相似度,并能解释其结果在语义上的含义。
—— 小象教研组