- Word Embedding 基础
相信熟悉机器学习或 NLP 的同学都清楚, 特征提取 是模型训练过程中的一个重要步骤,它涉及将原始文本转换成可以被机器学习模型理解和处理的数值形式。之所以要这样做,是因为计算机无法理解原始的自然语言文本,必须要先进行格式转换,而 Word Embedding(词嵌入)就是一种非常主流的特征提取方式 。我们可以从数学计算和语义特征两个角度,来理解 Word Embedding。
数学计算
向量空间是有维度的,以我们熟悉的二维、三维空间为例:二维向量平面坐标系下的一条直线(x,y),而三维向量就是立体坐标系下的一个平面(x,y,z),以此类推,可以扩展到多维向量。
语义特征
向量的每一个维度,可以对应实体的一个特征值。 仍然以二维向量空间为例:假设我们要对“城市”这个实体进行特征提取,那么在二维向量空间内,可以设定人口和 GDP 总量这两个维度,进行归一化处理后,这两个维度的值都在 [0,1] 范围内,值越大,则代表人口越多或 GDP 总量越高。如下图所示:
经过 Word Embedding 处理后,自然语言的文本就被映射成了多维空间中的向量,便于机器学习模型去处理和训练。
而且,更加重要的是,这些向量在一定程度上捕获到了语义信息,即向量间的距离可以表示语义上的相似性。在向量空间中,沈阳(0.5,0.4)与哈尔滨(0.4,0.5)的距离更近,而与北京(0.8,0.9)的距离更远,反映了在人口规模、经济发展等方面,沈阳与哈尔滨更相近,而与北京的差距较大。
另外,需要补充说明的是,理论上向量的维度越高,所能提取的特征就越多,对语义的捕获也就越精准,但是相应地存储与计算的成本也就越高。
- Word2Vec 模型介绍
Word2Vec 是一个被广泛使用的 Word Embedding 模型,它的核心目标是将单词转换成向量形式,以便于计算机理解与计算。此外,它通过大量文本语料的训练,能够捕捉到词语间的上下文关系,进而生成词的高维表示,即词向量。
Word2Vec 有两种主要模型架构:Skip-Gram 跳字模型和 Continuous Bag of Words (CBOW) 连续词袋模型。 Skip-Gram 的目标是根据目标词预测其上下文单词;与之相反,CBOW 模型的目标是根据上下文前后的单词来预测目标词 。
连续词袋(CBOW)
CBOW 是一种通过上下文预测目标词的神经网络架构。在 Word2Vec 中,CBOW 尝试从一个词的“上下文”来预测这个词本身。上下文由目标词周围的一个或多个词组成,这个数目由窗口大小决定。窗口是指上下文词语的范围。例如,将窗口设置为5,那么模型将使用目标词前后各5个词。
跳字模型(Skip-Gram)
Skip-Gram 是一种通过一个给定的目标词来预测其上下文词的神经网络架构。与 CBOW 模型相反,Skip-Gram 每次接收一个词作为输入,并预测它周围的词,这使其在处理较大数据集和捕获罕见词或短语时表现更出色。
- 构建自己的 Word2Vec 模型
理解了前面的基础概念和模型架构,下面我们从零开始, 一步一步构建一个自己的 Word2Vec 模型 。
构建一个模型大致需要经过如下的步骤:
- 数据收集与预处理
- 模型训练
- 使用模型进行推理
我们从收集数据与预处理开始。
数据收集与预处理
网上有很多开源的文本语料,可以用作我们学习的数据。这里我选择了新浪微博的公开语料库,下载链接:http://www.nlpir.org/wordpress/2017/12/03/nlpir%E5%BE%AE%E5%8D%9A%E5%86%85%E5%AE%B9%E8%AF%AD%E6%96%99%E5%BA%93-23%E4%B8%87%E6%9D%A1/。
需要注意的是,这个原始语料库中包含一些特殊字符,在进行 XML 解析过程中可能会报错。我这里提供了一份处理后的语料库,可以直接下载,地址 :https://gitee.com/zhangshenao/happy-llm/blob/master/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%AE%9E%E6%88%98%E9%AB%98%E6%89%8B%E8%AF%BE/4-Word2Vec/weibo\_content\_corpus.xml
接下来就可以进行数据预处理了,大致过程如下:
- 数据加载
- 数据清洗
- 去除停用词
- 分词
具体代码:
import xml.etree.ElementTree as Et
from typing import List
import jieba
# 停用词列表,可以根据实际业务情况进行修改
stop_words = ("的", "了", "在", "是", "我", "有", "和", "就")
def pre_process(xml_path: str) -> List[List[str]]:
"""
数据收集与预处理
:param xml_path: xml格式的语料数据路径
:return: 预处理后的文本列表
"""
# 将文本内容解析为XML
tree = Et.parse(xml_path)
root = tree.getroot()
# 获取所有<article>标签的内容
texts = [record.find('article').text for record in root.findall('RECORD')]
print(f"原始语料数量: {len(texts)}")
# 分词和去除停用词
processed_texts = []
for text in texts:
if text is not None:
words = jieba.cut(text)
processed_text = [word for word in words if word not in stop_words]
processed_texts.append(processed_text)
# 打印预处理后的文本
# for text in processed_texts:
# print(text)
print(f"数据预处理完成\n预处理后的语料数量: {len(processed_texts)}")
return processed_texts
预处理完成后,我们获得了227367条原始语料,接下来就可以进行模型训练了。
模型训练
这里我们选择 gensim 库,它提供了简单易用的 API,可以直接帮助我们训练一个 Word2Vec 模型。
from typing import List
from gensim.models import Word2Vec
def train_word2vec(sentences: List[List[str]]) -> None:
"""
训练Word2Vec模型
:param sentences: 与处理好的语料
"""
# 使用gensim库,训练一个Word2Vec模型
model = Word2Vec(sentences=sentences, # 指定训练语料
vector_size=100, # 设置词向量维度
window=5, # 设置上下文窗口大小
min_count=1, # 设置词频的最小阈值
workers=4, # 设置进行训练的线程数
sg=1 # 模型架构 1:使用Skip-Gram架构 0:使用CBOW架构 这里因为是根据目标词预测上下文,因此采用Skip-Gram架构
)
# 将模型保存到本地
# word2vec.model: 主模型文件,包含了模型的参数、词汇表等信息。这个文件是加载完整模型所必需的
# word2vec.model.wv.vectors.npy: 这个文件存储了模型中所有词汇的词向量
# word2vec.model.syn1neg.npy: 这个文件存储的是训练过程中使用的负采样权重
model.save("word2vec.model")
print("Word2Vec 模型训练完成")
在训练模型时,有几个参数需要特殊说明:
-
vector_size:设置词向量的维度,这里选择了100。
-
window:设置上下文窗口大小,即上下文中单词的数量。
-
min_count:设置词频的最小阈值。
-
workers:设置进行训练的线程数。
-
sg: 模型架构,1代表使用 Skip-Gram 架构;0代表使用 CBOW 架构。这里因为是根据目标词预测上下文,因此采用Skip-Gram架构。
训练完成后,会将模型文件保存到本地,主要包含以下几个文件:
-
word2vec.model: 主模型文件,包含了模型的参数、词汇表等信息。这个文件是加载完整模型所必需的。
-
word2vec.model.wv.vectors.npy: 这个文件存储了模型中所有词汇的词向量。
-
word2vec.model.syn1neg.npy: 这个文件存储的是训练过程中使用的负采样权重。
模型推理
模型训练好之后,我们加载模型文件,就可以使用 Word2Vec 模型进行语义相似度检索了。
from typing import List
from gensim.models import Word2Vec
def similarity_search(query: str, top_n: int = 5) -> List[str]:
"""
基于语义相似度搜索
:param query: 搜索的文本
:param top_n: 返回的相似度最高的前n个结果
:return: 返回的相似度最高的前top_n个结果
"""
# 加载模型
model = Word2Vec.load("./word2vec.model")
print("模型加载完成")
# 使用模型
# 生成query对应的词向量
print(f"query: {query}")
print(f"词向量: \n{model.wv[query]}")
# 找到最相似的词
similar_words = model.wv.most_similar(query, topn=top_n)
return similar_words
我们设置了关键词是明星,相似度检索出的结果为 {孙红雷,芭莎,时尚界},看起来还是挺靠谱的!
- 总结
本篇文章我们首先讲解了 Word Embedding 的基础概念,之后介绍了 Word2Vec 模型的作用及模型架构,最后的重头戏是从零到一训练一个 Word2Vec 模型,并利用它进行简单的相似性搜索,可以看到效果还是不错的。
Word2Vec 是一个应用非常广泛的词嵌入模型,它的主要优势是词嵌入质量高、可以捕获多种语言规律,且具备一定的可解释性,可以通过向量计算表达词语之间的关联关系。而且更重要的是,Embedding 是大语言模型内部的核心机制,理解 Word2Vec 对于后面我们剖析大模型的内部原理具有非常重要的帮助!