跳转至

12 深入理解Word2Vec:解开词向量生成的奥秘

你好,我是独行。

前面几节课我们学习了机器学习和NLP的基本理论,相信你对人工智能已经有了初步的认识。这节课我们学习Word2Vec,顾名思义就是词语 to 向量。我们上一节课学习的NLP过程,在文本预处理之后,有一个特征提取,其中就涉及到将词语转化成数值形式,以便计算机能够理解,指的就是Word2Vec的过程。

为什么要学习Word2Vec?还是为了理解大模型的原理打基础,我们整个课程的目的之一就是弄懂大语言模型的原理,所以在正式学习Transformer之前,我会为你介绍一些前置知识,除了之前讲解的ML和NLP的基本概念外,还包括Word2Vec以及后面的Seq2Seq等。下面我们开始由浅入深学习下Word2Vec。

Word2Vec

Word2Vec是一种广泛使用的NLP技术,目的是将词语转换成向量形式,使计算机能够理解。它通过学习大量文本数据,捕捉到词语间的上下文关系,进而生成词的高维表示,即词向量。

Word2Vec有两种主要模型:Skip-Gram和CBOW,Skip-Gram的目标是根据目标词预测其周围的上下文词汇,与之相反,CBOW模型的目标是根据周围的上下文词汇来预测目标词。Word2Vec的优点是能够揭示词与词之间的相似性,比如通过计算向量之间的距离来找到语义上相近的词。Word2Vec的应用非常广泛,包括但不限于情感分析、机器翻译和推荐系统等。尽管非常有用,但是它也有局限性,比如无法处理多义词,因为每个词仅被赋予一个向量,不考虑上下文中的多种含义。

模型架构

Word2Vec模型基于两种主要架构:连续词袋(Continuous Bag of Words, CBOW)和跳字模型(Skip-Gram)。你可以看一下这两种架构的示意图。

连续词袋(CBOW)

示意图左半部分是CBOW模型,它是一种通过上下文预测目标词的神经网络架构。在Word2Vec中,CBOW尝试从一个词的“上下文”来预测这个词本身。上下文由目标词周围的一个或多个词组成,这个数目由窗口大小决定。窗口是指上下文词语的范围,如果窗口为10,那么模型将使用目标词前后各10个词。

跳字模型(Skip-Gram)

示意图的右半部分是Skip-Gram模型,它是一种通过一个给定的目标词来预测其上下文词的神经网络架构。与CBOW模型相反,Skip-Gram每次接收一个词作为输入,并预测它周围的词,这使其在处理较大数据集和捕获罕见词或短语时表现更出色。

构建自己的Word2Vec模型

基于上述的架构,我们自己从0~1构建一个Word2Vec模型。

数据收集和预处理

我从网上下载了一个公开的微博内容数据集。我们先进行文本预处理。回想一下我们前几节课提到的方法,步骤就是数据加载->去除停用词->分词等等。这里我们就进行这些简单的预处理操作。

import jieba
import xml.etree.ElementTree as ET

# 读取XML文件并解析
file_path = 'data.xml'
tree = ET.parse(file_path)
root = tree.getroot()

# 获取所有<article>标签的内容
texts = [record.find('article').text for record in root.findall('RECORD')]
print(len(texts))

# 停用词列表,实际应用中需要根据实际情况扩展
stop_words = set(["的", "了", "在", "是", "我", "有", "和", "就"])

# 分词和去除停用词
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)

预处理完成后,产生了40831条数据用来训练。

训练模型

我们直接使用gensim库,这个库提供了一个简洁的API来训练Word2Vec模型。我们选择CBOW模型。vector_size参数设置了词向量的维度,window参数设置了上下文窗口的大小,min_count参数设置了词频的最小阈值,workers参数设置了训练的线程数,sg=1表示使用Skip-Gram架构,sg=0表示使用CBOW架构。你可以看一下具体的代码。

# 训练Word2Vec模型
model = Word2Vec(sentences=processed_texts, vector_size=100, window=5, min_count=1, workers=4, sg=1)
# 保存模型
model.save("word2vec.model")

当执行完上面的代码后,本地生成了3个文件。

  1. word2vec.model:主模型文件,包含了模型的参数、词汇表等信息。不仅存储了模型的架构信息,还包括了词汇频率、模型训练状态等。这个文件是加载完整模型所必需的。
  2. word2vec.model.wv.vectors.npy:这个文件存储了模型中所有词汇的词向量。Word2Vec模型通过学习这些词向量来捕捉词语之间的关系。.npy是NumPy数组的文件格式,这意味着这些向量是以NumPy数组的形式存储的,可以高效地加载和处理。
  3. word2vec.model.syn1neg.npy:这个文件存储的是训练过程中使用的负采样权重。当设置Word2Vec模型的negative参数大于0时,启用负采样来优化模型的训练过程。这个文件中的权重是模型训练中用于负采样的部分,对于模型的学习和生成词向量至关重要。

评估与应用

模型训练完成,我们可以简单看一下效果。

# 加载模型
model = Word2Vec.load("word2vec.model")
print("模型加载完成")

# 使用模型
# 获取一个词的向量
print(model.wv['科技'])

# 找到最相似的词
similar_words = model.wv.most_similar('科技', topn=5)
print(similar_words)

程序输出如下:

[ 0.08377746  1.4331518   0.5016794  -0.09138884 -0.1221515  -0.08544948
  0.20863684  0.9883061  -0.26002824 -0.02130504  0.43953782 -0.11446979
  0.4636304  -0.67642045  0.47473285 -0.4832982   0.35540286 -0.5201831
  0.0174433  -0.40980706 -0.14922573  0.5372444   0.53256696 -0.4517828
 -1.1696991   0.32669672 -0.34389514  0.5889707  -0.20616521 -0.20512877
  0.6516593  -1.3999687  -0.00352089  0.422699   -0.32610646  1.5900621
  0.8748267  -0.00933662 -0.77871656 -0.2894545   0.7261106  -0.05075585
 -0.5845707   0.7334658  -0.22150414  0.3801838  -0.50801146 -0.8370443
 -0.03138219  0.08028921  0.2562184  -0.49664307 -0.8038274   0.0211964
 -0.6316118   0.12551498  0.58615136  0.467213   -0.15562508 -0.58768135
  0.07793431  0.19536994 -0.1413024  -0.3790597   0.19154921  0.4437868
  0.08398101  0.10911901 -0.6428759  -0.07833739 -0.8982224   0.8185256
  0.4029754   0.05831718 -0.23952699  0.06487722 -0.6090112  -0.03935737
 -0.1745928   0.2225394  -0.7901157  -0.08253222 -0.3205032   0.16001679
 -0.06188322 -0.4120766  -0.55351204  1.1411817  -0.24971966  0.01067457
  0.205598    0.4778782  -0.2214068  -0.5329161   0.9778511   0.5545867
  0.50671256  0.6427801  -0.45557818 -0.29751778]
[('产业', 0.9526691436767578), ('创新', 0.9492118954658508), ('生态', 0.9462338089942932), ('应用', 0.9439343810081482), ('战略', 0.9437689185142517)]

这是一个100维的向量空间,和科技比较相似的词有产业、创新、生态、应用、战略。看着还挺像这么回事,基本准确的。我们怎么科学地进行评估呢?有好几种方式。

  1. 词相似度计算:通过比较模型生成的词与人工标注的词的相似度,来评估模型,一致性越高,说明效果越好。常用的数据集包括WordSim-353、SimLex-999等。
  2. 词类比计算:评估模型在解决“词A之于词B如同词C之于什么”这类问题的能力,比如,北京之于中国如同巴黎之于什么?
  3. OOV词比率:评估数据集中有多少词对因为包含未知词(模型词汇表外的词)而被排除在评估之外的比率。理想情况下,OOV率应该尽可能低,以确保评估结果能更全面地代表模型的性能。
  4. 定性分析:对于给定的词汇,查看模型认为与其最相似的其他词汇,判断这些相似词是否符合预期。
  5. 实际应用:将Word2Vec模型应用到具体的下游任务,如文本分类、情感分析、实体识别等,观察模型表现的提升。通过比较使用Word2Vec词向量前后的任务表现,可以间接评估Word2Vec模型的有效性。

我们采用第一种方式,来详细看一下评估过程。

首先,手动创建评估数据集 valid.tsv,内容如下:

图片

总共3列,第1列和第2列是相似词,第3列是我人工打的分,实际评估过程中,评估数据集可以自己基于偏好进行人工打分,同时,准备的词汇数量可以再多一些,为了节省时间,我只准备了二十来个词。数据集准备好就可以评估了。

# 加载模型
model = Word2Vec.load("word2vec.model")
print("模型加载完成")
# 类比
result = model.wv.evaluate_word_pairs("valid.tsv")
print(result)

运行程序输入如下结果:

(PearsonRResult(statistic=0.5510228130115481, pvalue=0.021875787422131296), SignificanceResult(statistic=0.5076852492192858, pvalue=0.03748715633121946), 10.526315789473683)

整体分为Pearson和Spearman相关性系数,以及OOV率,先来看Pearson系数。

  • statistic=0.5510228130115481:范围从-1到1,其中1表示完全正相关,-1表示完全负相关,0表示无相关。这个值0.55意味着我们的模型词向量与人工评分之间存在中等程度的正相关。
  • pvalue=0.021875787422131296:这是p值,用于检验相关性的统计显著性。一个常用的显著性水平阈值是0.05。这个p值约0.022,小于0.05,意味着这个相关性是统计显著的,我们有足够的证据认为模型的词向量与人工评分之间的相关性不是偶然出现的。简单来讲就像摇奖现场的公证员一样,证明评估结果不是偶然的,是有说服力的。
    再来看Spearman系数。
  • statistic=0.5076852492192858:同样范围从-1到1。Spearman相关性考虑的是变量之间的等级相关,而不是直接的数值大小。这个值0.51也表示了中等程度的正相关。
  • pvalue=0.03748715633121946:同样是p值,小于0.05的常用显著性水平,表示这种相关性是统计显著的。
    最后看下OOV率,就是评估数据集中出现的词汇不在模型词汇表里的概率。
  • 10.526315789473683:表示在我们的评估数据集中,有大约10.53%的词对包含至少一个不在模型词汇表中的词(OOV词)。这部分词对在评估过程中被忽略,不参与相关性计算。

总体看,一个好的Word2Vec模型应该在相关性测试中展现出与人类判断一致的趋势,比如有较高的Pearson和Spearman相关性系数、具有统计显著性,即较低的p值,同时具有可接受的OOV率。

我们本次训练的模型,相关性系数呈中等,统计显著的p值较低,表明模型有一定效果,但是否“足够好”还需要根据模型的实际应用场景和要求来决定。比如,对于一些应用来说,可能需要更高的相似度准确性;而对于其他应用来说,当前模型的表现可能已经足够。另外,我们还需要探索降低OOV率的方法,比如扩大训练数据集或采用预训练的词向量,可能会进一步提升模型的表现。

其他评估方法我们就不一一尝试了,大同小异,感兴趣的话,你可以挨个试一下。

模型的优缺点

为什么Word2Vec使用得比较广泛呢?从上面的学习过程中,我们就能看出来,Word2Vec 总体上还是比较容易理解的,上手也比较简单。这里,我们来总结下Word2Vec的优缺点。

优点

  1. 词嵌入质量高:Word2Vec能够学习到富含语义信息的高质量词向量,使语义上相近的词在向量空间中也相近。
  2. 捕捉多种语言规律:Word2Vec能够捕捉到一定的语法和语义规律,比如词类比:男人之于女人如同国王之于王后。
  3. 效率高:相比于早期的基于矩阵分解的词嵌入方法,Word2Vec的训练效率更高,尤其是在处理大规模语料库时。
  4. 可解释性:Word2Vec模型学习到的词向量具有一定的可解释性,可以通过向量运算进行词之间的关系探索。

缺点

  1. OOV问题:Word2Vec模型只能对其训练期间见过的词汇生成向量。对于新出现的或者罕见的词汇,模型无法直接提供词向量(尽管可以通过一些技巧进行处理)。
  2. 词义多样性:Word2Vec为每个词汇生成一个唯一的向量,因此无法直接处理一个词多种含义的情况,也就是多义词问题。
  3. 依赖于大量文本数据:为了训练出高质量的词向量,Word2Vec需要大量的文本数据。在数据量较小的情况下,模型的效果可能会受限。
  4. 上下文独立:Word2Vec生成的词向量是静态的,不考虑词在特定句子中的上下文。这与后来的上下文相关的词嵌入模型,如ELMo、BERT等形成对比。
  5. 缺乏层次化表示:Word2Vec提供的是词汇级别的向量表示,缺乏更细致的语法和语义结构信息,这些在一些复杂的NLP任务中可能是必需的。
    下面我们看下Word2Vec有哪些实际的应用场景。

应用场景及案例

Word2Vec模型因为能够捕捉到词语和词语之间复杂的语义语法关系,所以在NLP任务中被广泛使用。

计算文本相似度:比较文本中词向量的平均值或加权平均值,可以用于文档分类、推荐系统中相似项目的检索,或者在法律文档、学术论文等领域内查找相关内容。

情感分析:尤其是社交媒体平台,用Word2Vec模型来识别用户评论、帖子或新闻报道中的情绪态度。

机器翻译:Word2Vec可以用来生成源语言和目标语言的词向量,通过这些向量,可以改进翻译模型的性能,尤其是在处理罕见词或短语时,Word2Vec能够提供更加丰富的语义信息。

搜索引擎优化:在搜索引擎中,Word2Vec可以用来理解用户查询的意图,并提高搜索结果的相关性。通过分析查询和文档内容的词向量相似度,搜索引擎能够提供更准确、更贴近用户意图的搜索结果。

内容推荐系统:在推荐系统中,Word2Vec可以用来分析用户的阅读或购买历史,并推荐语义上相近的产品或内容。这种基于内容的推荐方式能够提供更加个性化的推荐,提高用户满意度和参与度。

小结

这节课从原理到实操讲解了Word2Vec模型,我们通过脑图来简单总结下这节课内容的知识点。

图片

你可以按照学习的知识自己构建一个Word2Vec模型,并应用到实际产品中,当然也可以使用业界已经训练好的比较成熟的Word2Vec模型,比如Google的GoogleNews-vectors-negative300模型,这个模型在包含大约1000亿个单词的Google新闻数据集上预训练得到,模型中的每个单词都被表示为一个300维的向量,模型是比较强大的。你可以试一试。

思考题

目前不论是电商、新闻还是社交平台,个性化推荐都很常见,如果让你来设计一个新闻推荐系统,请思考一下,怎么实现?你可以把思路放在评论区,我们一起讨论,如果你觉得这节课的内容对你有帮助的话,欢迎你分享给其他朋友,我们下节课再见。

精选留言(5)
  • 张申傲 👍(6) 💬(1)

    第12讲打卡~ 思考题:实现新闻推荐系统,一个简单的思路是,将每个用户的浏览、搜索、评论等历史记录,通过Embedding向量化后,存储在向量数据中;同时将新闻的文本也向量化入库。在做相关推荐时,可以基于向量数据库的索引能力,利用余弦距离等,计算相关度最高的几条新闻,给用户推荐。

    2024-06-24

  • 张 ·万岁! 👍(0) 💬(1)

    关于这个xml文件无法正常读取的问题,主要是因为xml默认没有预定义HTML中的 &brvbar; 实体,还有存在某些不能编码的字符串。我的解决方案是直接简单暴力的字符串替换 import os path1 = 'data.xml' path2 = 'data1.xml' with open(path1, 'r', encoding='utf-8',) as f1: with open(path2, 'a+', encoding='utf-8') as f2: line = f1.readline() while line: # print(line.strip()) f2.write(line.replace('&brvbar;', '¦').replace('￿','')) line = f1.readline()

    2024-07-23

  • 黄蓉 Jessie 👍(0) 💬(2)

    对于&brvbar;无法解析的问题,可以用代码将其替换为|。然后还会遇到一个报错,xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 411575, column 19。这个是因为xml文件里有特殊字符,找到对应行411575,复制这个字符批量替换掉就可以运行成功。 实测能运行起来的预处理代码如下(替换特殊字符后) ```python import jieba import xml.etree.ElementTree as ET import re # 预处理步骤 # 读取XML文件并解析 file_path = 'data.xml' # 读取XML文件内容 with open(file_path, 'r', encoding='utf-8') as file: content = file.read() # 替换特殊实体,这里将 &brvbar; 替换为相应的符号(¦) content = content.replace('&brvbar;', '¦') # 使用ET.fromstring解析字符串,而不是直接解析文件 root = ET.fromstring(content) # 获取所有<article>标签的内容 texts = [record.find('article').text for record in root.findall('RECORD')] print(len(texts)) # 停用词列表,实际应用中需要根据实际情况扩展 stop_words = set(["的", "了", "在", "是", "我", "有", "和", "就"]) # 分词和去除停用词 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)

    2024-07-23

  • Juha 👍(0) 💬(4)

    这个xml文件,不知道有没有一样无法正常读取的,如果换了lxml的话,数据会变得很多,结果不准确

    2024-07-09

  • 浩仔是程序员 👍(0) 💬(0)

    老师,你好,基于Word2Vec是怎么实现情感分析的呢?是预先定义一些比如负面情绪的词语,计算拿到一组向量。然后输入一个词,计算出向量,跟预先定义好负面情绪的向量计算相似度,判断是否为负面情绪吗?

    2024-12-22