词向量:将token变成数值向量,从简单表示到语义理解

前言

在NLP中,如何将分词后得到的文本表示(token)成数值向量,是所有NLP任务(如文本分类、文本翻译等)的基础。NLP发展到今天,文本向量化的方法有很多,接下来我将详细地介绍一下词向量的本质、发展与主流模型,揭开语言如何变成“向量”的奥秘。


一、词向量是什么?

在NLP中,词向量(Word Embedding)是一种将离散单词映射侧灰姑娘稠密向量的方法。这种向量能够捕捉词语之间的语义关系和上下文相似性。

比如:国王和女王是具有相同语义的词,因此我们希望:

vector("king") - vector("man") + vector("woman") ≈ vector("queen)

再比如:apple应该与banana的向量相似,apple与cat的向量应该极度不相似。

这些例子说明,词向量不仅仅是一个表示,更是一种语言中的数学建模方式。

二、One-Hot Encoding :词向量的起点

1. 什么是ont-hot?

one-hot编码,通常称为独热编码,它是NLP中最基础的词表示方式,它的思想很简单:

用一个长度为词汇表大小的向量,来表示每一个词。在该向量中,只有一个位置为1,其他位置值均为0

 2. 原理

假设有一个词汇表,共包含五个词:

["我", "喜欢", "吃", "苹果", "你"]

接下来,我们将这五个词按照顺序编号为:

"我"   --> 0
"喜欢" --> 1 
"吃"   --> 2
"苹果" --> 3
"你"   --> 4

那么,它们的one-hot向量表示为:

"我"   --> 0   [1, 0, 0, 0, 0]
"喜欢" --> 1   [0, 1, 0, 0, 0]
"吃"   --> 2   [0, 0, 1, 0, 0] 
"苹果" --> 3   [0, 0, 0, 1, 0]
"你"   --> 4   [0, 0, 0, 0, 1]

使用pytorch简答表示一下:

import torch

words = ['我', '喜欢', '吃', '苹果', '你']

# 创建词汇表
vocab = {word: idx for idx, word in enumerate(sorted(set(words)))}
vocab_size = len(vocab)

# 将单词转换为索引
word_indices = [vocab[word] for word in words]

# 创建one-hot编码
one_hot = torch.zeros(len(words), vocab_size)
for i, idx in enumerate(word_indices):
    one_hot[i][idx] = 1

print("词汇表:", vocab)
print("单词索引:", word_indices)
print("One-Hot编码矩阵:")
print(one_hot)

3. 缺点

one-hot 的构建很简单,但是缺点很明显:

  • 离散稀缺性严重:若词汇表有10000万个词,那么每个词的向量就是一个包含9999个0的向量,内存和计算效率低。
  • 无法表达语义关系:例如 “我” 和 “你” 在语义上相似,但它们的 one-hot 向量完全正交,没有任何内在联系。

三、Bag of Words(词袋):从离散到稠密词表示的第一步

1. 什么是BOW?

词袋模型的思想是:

忽略词语出现的顺序和语法,只关注每个词在文中出现的频率。

将每个文本表示为一个固定长度的向量,该向量的每个维度表示一个词是否在文本中出现过(或出现过几次)

可以理解为:把每段文本看作一个词的“袋子”明知统计有哪些词,词出现了几次

2. 原理

首先构建词汇表:收集所有文本中出现过的不重复词语,按顺序编号,形成一个全局词汇表。假设语料如下:

文档 1:我 喜欢 吃 苹果
文档 2:你 喜欢 吃 香蕉
文档 3:我 喜欢 喜欢 吃 香蕉

那么词汇表就是:

["我", "喜欢", "吃", "苹果", "你", "香蕉"]

将每个文档转换为词频向量 :每个文档被转换为一个与词汇表等长的向量,每个维度表示该词在文档中出现过的次数(或是否出现):

文档                  向量表示
-----------------------------------------------
文档1                 [1, 1, 1, 1, 0, 0] (我 喜欢 吃 苹果)
文档2                 [0, 1, 1, 0, 1, 1] (你 喜欢 吃 香蕉)
文档3                 [1, 2, 1, 0, 1, 1] (我 喜欢 喜欢 吃 香蕉)

 我们使用sklearn来实现一下:

from sklearn.feature_extraction.text import CountVectorizer

corpus = ["我 喜欢 吃 苹果", "你 喜欢 吃 香蕉", "我 喜欢 喜欢 吃 香蕉"]
vectorizer = CountVectorizer()

X = vectorizer.fit_transform(corpus)

print("词汇表:", vectorizer.get_feature_names_out())
print("向量表示:\n", X.toarray())

 3. 缺点

BOW实现简单,且每个维度都清楚地表示某个词的频率,然而,缺陷也很明显:

  • 忽略语序,例如 “苹果吃我” 和 “我吃苹果” 表示一样
  • 随着词汇量增加,向量变得非常高维稀疏
  • 缺乏语义信息,“好” 与 “棒” 是近义词,但向量表示完全无关。
  • 对一些高频的无意义词敏感,比如 “的”,“是” 之类的高频无意义词会干扰模型

四、TF-IDF:词袋模型的加权升级版

1. 什么是TF-IDF?

BOW是根据词频来构建词袋的,TF-IDF 是在词袋(BOW)的基础上,为了解决“高频但无意义的词”(比如“的”“是”等)对模型影响过大的问题而提出的加权方法。它的思想也很简单:

  • 一个词在某篇文档中出现的越频繁,且在整个语料库中出现的越少,它对该文档的区分能力越强。

2. 原理

TF-IDF 是由两部分组成的乘积:

(1)TF:词频(Term Frequency)

表示某个词在当前文档中出现的频率:

TF(t,d)= 该词在文档中出现的次数 / 文档总次数

(2)IDF:逆文档频率:

衡量词在所有文档中“稀有度”:

  • :语料库中文档总数
  • :包含该词 t 的文档数量
  • 加 1 避免 除 0 错误 

(3)TF-IDF 综合公式:

例如:假设有三个文档:

文档 1:我 喜欢 吃 苹果
文档 2:你 喜欢 吃 香蕉
文档 3:他 喜欢 吃 苹果 和 香蕉

构建词汇表:

​["我", "喜欢", "吃", "苹果", "你", "香蕉", "他"]

 计算 “苹果” 在每个文档中的TF-IDF:文档总数 N=3,“苹果”出现在 文档1 和 文档3中,则df(“苹果”)=2, 则 IDF(“苹果”)=log(3/1+3)=0,说明“苹果”并不稀有。计算所有的文档的TF-IDF:

文档                                TF-IDF 向量表示
---------------------------------------------------------
文档1 (我 喜欢 吃 苹果)               [0, -0.03, -0.03, 0, 0, 0, 0] 
文档2 (你 喜欢 吃 香蕉)               [0, -0.03, -0.03, -0.030, 0.0425, 0.0425, 0] 
文档3 (我 喜欢 吃 苹果 香蕉)           [0, -0.02, -0.02, 0, 0, 0.028, 0] 

 使用sklearn实现一下:

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = ["我 喜欢 吃 苹果", "你 喜欢 吃 香蕉", "他 喜欢 苹果 和 香蕉"]
vectorizer = TfidfVectorizer()

X = vectorizer.fit_transform(corpus)

print("词汇表:", vectorizer.get_feature_names_out())
print("TF-IDF矩阵:\n", X.toarray())

3. 缺点

TF-IDF 能弱化无意义词(比如“的”“是”)的影响,突出文档关键词,但是缺点也很明显:

  • 忽略词序、上下文
  • 仍然是稀疏向量表示
  • 对同义词无识别能力

五、Word2Vec:词向量革命的起点

1. 什么是Word2Vec?

Word2Vec是一种将词语映射到稠密向量空间的算法,由Google团队于2013年提出。与BOW、TF-IDF不同,它可以捕捉到词语次之间的语义关系和上下文关联。它的思想是:

  • 上下文相似的词,应该拥有相似的向量表示。

比如:对于“king”和“queen”,词向量可以学出以下关系:

vector("king") - vector("man") + vector("woman") ≈ vector("queen")

 2. 原理

Word2Vec就是将词转换成向量,也就是词向量模型。但是如何训练它将词转换成向量呢?这就需要用到神经网络,学习怎么样将一个词转换成合适的向量。整体流程如下:

因此,我们最重要的任务就是构建输入数据。假如我们有一个数据样本:

"today is a good day"

那么模型需要做的就是预测下一个词是否是正确的,其实也就是分类任务:

如上图所示,Word2Vec 在训练时,在更新参数权重的同时,也更新词向量库,当经过数轮训练后,词向量库就能准确的表示对应的词了。 

通过对Word2Vec训练的时候,我们能发现,构造输入数据库是很重要的。我们通过滑动窗口来构造输入数据,例如对于一条数据:

"today is a good day"

我们可以根据滑动窗口来构建训练数据:

input 1input 2output
todayisa
isagood
agoodday

 有了训练数据后,我们就可以训练 Word2Vec 了,Word2Vec有两种不同的架构:

  • CBOW
  • Skip-gram

3. CBOW

CBOW(Continuous Bag of Words) 是Word2Vec的两种结构之一,它的核心思想是:

  • 根据上下文词,预测中间的目标词

其输入格式是这样的:

假设数据为:

"today is a good day"

那么根据滑动窗口(这里滑动窗口设置为1)来构造训练数据: 

input 1input 2output
todayais
isgooda
adaygood

 使用Gensim库来实现一下CBOW:

from gensim.models import Word2Vec

# 构造样例语料
sentences = [["I", "love", "deep", "learning"],
             ["I", "enjoy", "machine", "learning"],
             ["deep", "learning", "is", "powerful"]]

# 训练 CBOW 模型(sg=0 表示 CBOW)
model = Word2Vec(sentences, vector_size=50, window=2, sg=0, min_count=1)

# 查询词向量
print("Vector for 'deep':\n", model.wv['deep'])

# 查询相似词
print("Most similar to 'deep':", model.wv.most_similar('deep'))

4. Skip-gram

Skip-gram与CBOW相反,它的思想是:

  • 根据当前的中心词,去预测它的上下文词

目标是给定一个单词,预测它周围的上下文(上下窗口内的单词)

假设数据为:

"today is a good day"

 那么根据滑动窗口(这里滑动窗口设置为1)来构造训练数据:

input 1output
isthis
isa

我们以 Skip-gram 模型举例,来看一下训练的过程:

首先构造训练数据集:以下面样本举例:

"today is a good day"

设定上下滑动窗口为1,则训练集为: 

input 1target
isthis
isa
a

is

agood
gooda
goodday

则训练为:

从随机初始化词库向量中找到初始词向量,然后训练得到预测值,根据真实标签计算损失,反向传播,同时更新网络权重和词向量库,经过多轮训练后,得到最终的词向量库,此时的词向量库就可以被用于下游任务。

使用Gensim实现一下Skip-gram:

from gensim.models import Word2Vec

sentences = [["I", "love", "deep", "learning"],
             ["I", "enjoy", "machine", "learning"],
             ["deep", "learning", "is", "powerful"]]

# 训练 Skip-gram 模型(sg=1 表示 Skip-gram)
model = Word2Vec(sentences, vector_size=50, window=2, sg=1, min_count=1)

# 查询词向量
print("Vector for 'learning':", model.wv["learning"])

# 查看相似词
print("Most similar to 'deep':", model.wv.most_similar("deep"))

5. 负采样

如果一个语料库很大,假设语料库中有五万个词,那么在进行Softmax计算时,模型就相当于进行五万分类,计算十分耗时。因此,我们需要想办法来解决这个问题。

首先,我们可以这样想:输入前后对应的两个单词,看模型预测的是不是对应的输入与输出,根据概率判断,相当于二分类任务。此时构建的训练数据集为:

input outputtarget
istoday1
isa1
ais1
agood1
          _ _ _ _ _ _ _      
         |             |
is  ---> |   Model     | ---> a
         |_ _ _ _ _ _ _| 
               
          改为 ||
               
          _ _ _ _ _ _ _      
is       |             |
a   ---> |   Model     | ---> 0.8
         |_ _ _ _ _ _ _| 
 

但是这样设置有个问题,那就是此时构建出来的训练集的标签全为 1,导致完全不平衡,无法很好的训练模型。

解决办法就是负采样,核心思想就是:

  • 构建训练集时,人为加入一些负样本

比如:

input outputtarget
istoday1
isa1
isgood0
isthe0
......0
ais1
agood1

在实际添加负样本时,个数通常设置为5。(注意,此时的 input 与 output 都是模型的输入)

6. 缺陷

虽然 Word2Vec 构造的向量稠密,可捕捉语义信息,且能够表示词之间的类比关系(比如 king- man + woman ≈ queen),但是缺陷仍然存在:

  • 无法处理OOV问题
  • 不能捕捉多意词,比如 apple,word2piece无法区分它是吃的苹果,还是代表了手机。

总结

以上就是本文介绍的词向量的全部内容:从one-hot、BOW、TF-IDF这些无法描述语义信息的稀疏向量,发展到Word2Vec能够很好的表示语义信息的稠密向量。虽然Word2Vec大幅提升了词语表示能力,但它依然存在“每个词一个向量”的问题。为了解决多义词、上下文动态变化等问题,近期也出现了BGE(基于Transformer的注意力机制,根据语境动态计算词向量,即Embedding模型)方法。


如果小伙伴们觉得本文对各位有帮助,欢迎:👍点赞 | ⭐ 收藏 |  🔔 关注。我将持续在专栏《NLP核心技术专栏》中更新人工智能知识,帮助各位小伙伴们打好扎实的理论与操作基础,欢迎🔔订阅本专栏,向AI工程师进阶!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值