Word2Vec模型训练全流程解析:从数据预处理到实体识别应用
请添加图片描述
训练Word2Vec模型
概述
问题
- 我们如何训练Word2Vec模型?
- 在特定数据集上训练Word2Vec模型何时是有利的?
目标
- 理解在自有数据上训练Word2Vec模型而非使用预训练模型的优势
Colab环境配置
运行以下代码以启用辅助函数并重新读取数据。
PYTHON代码
# 运行此单元格以挂载你的Google Drivefrom google.colab import drivedrive.mount(\'/content/drive\')# 显示现有Colab笔记本和helpers.py文件from os import listdirwksp_dir = \'/content/drive/My Drive/Colab Notebooks/text-analysis/code\'print(listdir(wksp_dir))# 将文件夹添加到Colab的路径中,以便导入辅助函数import syssys.path.insert(0, wksp_dir)
输出结果
Mounted at /content/drive[\'analysis.py\', \'pyldavis.py\', \'.gitkeep\', \'helpers.py\', \'preprocessing.py\', \'attentionviz.py\', \'mit_restaurants.py\', \'plotfrequency.py\', \'__pycache__\']
PYTHON代码
# 安装parse模块(helpers.py中调用)所需的依赖!pip install parse
加载数据
PYTHON代码
# 重新读取数据from pandas import read_csvdata = read_csv(\"/content/drive/My Drive/Colab Notebooks/text-analysis/data/data.csv\")
创建我们分析时要用到的文件列表。我们先将Word2Vec模型拟合到列表中的一本书——《白鲸记》(Moby Dick)。
PYTHON代码
single_file = data.loc[data[\'Title\'] == \'moby_dick\',\'File\'].item()single_file
输出结果
\'/content/drive/My Drive/Colab Notebooks/text-analysis/data/melville-moby_dick.txt\'
我们预览文件内容,确保代码和目录设置正常工作。
PYTHON代码
# 打开并读取文件f = open(single_file,\'r\')file_contents = f.read()f.close()# 预览文件内容preview_len = 500print(file_contents[0:preview_len])
输出结果
[Moby Dick by Herman Melville 1851]ETYMOLOGY.(Supplied by a Late Consumptive Usher to a Grammar School)The pale Usher--threadbare in coat, heart, body, and brain; I see himnow. He was ever dusting his old lexicons and grammars, with a queerhandkerchief, mockingly embellished with all the gay flags of all theknown nations of the world. He loved to dust his old grammars; itsomehow mildly reminded him of his mortality.\"While you take in hand to school others, and to teach them by wha
PYTHON代码
file_contents[0:preview_len] # 注意实际字符串中仍包含\\n(print()会将其处理为换行)
输出结果
\'[Moby Dick by Herman Melville 1851]\\n\\n\\nETYMOLOGY.\\n\\n(Supplied by a Late Consumptive Usher to a Grammar School)\\n\\nThe pale Usher--threadbare in coat, heart, body, and brain; I see him\\nnow. He was ever dusting his old lexicons and grammars, with a queer\\nhandkerchief, mockingly embellished with all the gay flags of all the\\nknown nations of the world. He loved to dust his old grammars; it\\nsomehow mildly reminded him of his mortality.\\n\\n\"While you take in hand to school others, and to teach them by wha\'
预处理步骤
- 将文本拆分为句子
- 对文本进行分词
- 对所有词元进行词形还原并转为小写
- 移除停用词
1. 将文本转换为句子列表
要记住,我们利用句子中单词的序列来学习有意义的词嵌入。一个句子的最后一个单词并不总是和下一个句子的第一个单词相关。因此,在进一步处理前,我们要将文本拆分为单个句子。
Punkt句子分词器
NLTK的句子分词器(“punkt”)在大多数情况下表现良好,但当遇到包含大量标点、感叹号、缩写或重复符号的复杂段落时,可能无法正确检测句子。要解决这些问题没有通用的标准方法。如果希望确保用于训练Word2Vec的每个“句子”都是真正的句子,需要编写一些额外的(且高度依赖数据的)代码,利用正则表达式和字符串操作来处理罕见错误。
就我们的目的而言,愿意容忍少量句子分词错误。如果这项工作要发表,仔细检查punkt的分词结果是值得的。
PYTHON代码
import nltknltk.download(\'punkt\') # sent_tokenize函数的依赖sentences = nltk.sent_tokenize(file_contents)
输出结果
[nltk_data] Downloading package punkt to /root/nltk_data...[nltk_data] Package punkt is already up-to-date!
PYTHON代码
sentences[300:305]
输出结果
[\'How then is this?\', \'Are the green fields gone?\', \'What do they\\nhere?\', \'But look!\', \'here come more crowds, pacing straight for the water, and\\nseemingly bound for a dive.\']
2-4:分词、词形还原与移除停用词
调用文本预处理辅助函数并拆解代码逻辑……
- 我们将在每个句子上运行此函数
- 词形还原、分词、小写转换和停用词处理都是之前学过的内容
- 在词形还原步骤,我们使用NLTK的词形还原器,它运行速度很快
- 我们还会使用NLTK的停用词列表和分词函数。回忆一下,停用词通常是语言中最常见的单词。移除它们后,Word2Vec模型可以只关注有意义单词的序列。
PYTHON代码
from helpers import preprocess_text
PYTHON代码
# 测试函数string = \'It is not down on any map; true places never are.\'tokens = preprocess_text(string, remove_stopwords=True, verbose=True)print(\'Result\', tokens)
输出结果
Tokens [\'It\', \'is\', \'not\', \'down\', \'on\', \'any\', \'map\', \'true\', \'places\', \'never\', \'are\']Lowercase [\'it\', \'is\', \'not\', \'down\', \'on\', \'any\', \'map\', \'true\', \'places\', \'never\', \'are\']Lemmas [\'it\', \'is\', \'not\', \'down\', \'on\', \'any\', \'map\', \'true\', \'place\', \'never\', \'are\']StopRemoved [\'map\', \'true\', \'place\', \'never\']Result [\'map\', \'true\', \'place\', \'never\']
PYTHON代码
# 将句子列表转换为pandas Series,以便使用apply功能import pandas as pdsentences_series = pd.Series(sentences)
PYTHON代码
tokens_cleaned = sentences_series.apply(preprocess_text, remove_stopwords=True, verbose=False)
PYTHON代码
# 查看清洗前的句子sentences[300:305]
输出结果
[\'How then is this?\', \'Are the green fields gone?\', \'What do they\\nhere?\', \'But look!\', \'here come more crowds, pacing straight for the water, and\\nseemingly bound for a dive.\']
PYTHON代码
# 查看清洗后的句子tokens_cleaned[300:305]
输出结果
300 [] 301 [green, field, gone] 302 [] 303 [look] 304 [come, crowd, pacing, straight, water, seeming... dtype: object
PYTHON代码
tokens_cleaned.shape # 共9852个句子
PYTHON代码
# 移除空句子和仅含1个单词的句子(全为停用词)tokens_cleaned = tokens_cleaned[tokens_cleaned.apply(len) > 1]tokens_cleaned.shape
使用分词后文本训练Word2Vec模型
现在我们可以用这些数据训练Word2Vec模型。首先从gensim
导入Word2Vec
模块。然后将分词后的句子列表传入Word2Vec
函数,并设置sg=0
(“skip-gram”)以使用**连续词袋(CBOW)**训练方法。
为完全确定性运行设置种子和工作线程:接下来我们设置一些可复现性参数。设置种子,确保每次运行代码时向量的随机初始化方式相同。为了实现完全确定性的复现,我们还将模型限制为单工作线程(workers=1
),以消除操作系统线程调度带来的顺序抖动
PYTHON代码
# 导入gensim的Word2Vec模块from gensim.models import Word2Vec# 使用清洗后的数据训练Word2Vec模型model = Word2Vec(sentences=tokens_cleaned, seed=0, workers=1, sg=0)
Gensim的实现基于Tomas Mikolov最初的word2vec模型,该模型会根据频率自动下采样所有高频词。下采样能节省模型训练时间。
后续步骤:词嵌入应用场景
现在我们已获得《白鲸记》中所有(经过词形还原和去停用词的)单词的向量表示。看看如何用这些向量从文本数据中获取洞见。
最相似单词
和预训练Word2Vec模型一样,我们可以用most_similar
函数找到与查询词有意义关联的单词。
PYTHON代码
# 默认设置model.wv.most_similar(positive=[\'whale\'], topn=10)
输出结果
[(\'great\', 0.9986481070518494), (\'white\', 0.9984517097473145), (\'fishery\', 0.9984385371208191), (\'sperm\', 0.9984176158905029), (\'among\', 0.9983417987823486), (\'right\', 0.9983320832252502), (\'three\', 0.9983301758766174), (\'day\', 0.9983181357383728), (\'length\', 0.9983041882514954), (\'seen\', 0.998255729675293)]
词汇表限制
注意,Word2Vec只能为训练数据中出现过的单词生成向量表示。
PYTHON代码
model.wv.most_similar(positive=[\'orca\'], topn=30)
KeyError: \"Key \'orca\' not present in vocabulary\"
fastText解决OOV问题
若需获取未登录词(OOV)的词向量,可改用fastText
词嵌入模型(Gensim
也提供该模型)。fastText
模型可通过对单词的字符n-gram分量向量求和,为未登录词生成向量(只要至少有一个字符n-gram在训练数据中出现过)。
Word2Vec用于命名实体识别
这个“最相似”功能能用来做什么?一种用法是构建相似单词列表来表示某类概念。例如,我们想知道《白鲸记》中还提到了哪些海洋生物。可以用gensim
的most_similar
函数来构建平均代表“海洋生物”类别的单词列表。
我们将使用以下步骤:
- 初始化一个表示“海洋生物”类别的小单词列表
- 计算该单词列表的平均向量表示
- 用这个平均向量找到最相似的前N个向量(单词)
- 检查相似单词并更新海洋生物列表
- 重复步骤1-4,直到找不到新的海洋生物
PYTHON代码
# 初始化表示海洋生物的小单词列表sea_creatures = [\'whale\', \'fish\', \'creature\', \'animal\']# 以下代码将计算列表中单词的平均向量,# 并找到与该平均向量最相似的向量/单词model.wv.most_similar(positive=sea_creatures, topn=30)
输出结果
[(\'great\', 0.9997826814651489), (\'part\', 0.9997532963752747), (\'though\', 0.9997507333755493), (\'full\', 0.999735951423645), (\'small\', 0.9997267127037048), (\'among\', 0.9997209906578064), (\'case\', 0.9997204542160034), (\'like\', 0.9997190833091736), (\'many\', 0.9997131824493408), (\'fishery\', 0.9997081756591797), (\'present\', 0.9997068643569946), (\'body\', 0.9997056722640991), (\'almost\', 0.9997050166130066), (\'found\', 0.9997038245201111), (\'whole\', 0.9997023940086365), (\'water\', 0.9996949434280396), (\'even\', 0.9996913075447083), (\'time\', 0.9996898174285889), (\'two\', 0.9996897578239441), (\'air\', 0.9996871948242188), (\'length\', 0.9996850490570068), (\'vast\', 0.9996834397315979), (\'line\', 0.9996828436851501), (\'made\', 0.9996813535690308), (\'upon\', 0.9996812343597412), (\'large\', 0.9996775984764099), (\'known\', 0.9996767640113831), (\'harpooneer\', 0.9996761679649353), (\'sea\', 0.9996750354766846), (\'shark\', 0.9996744990348816)]
PYTHON代码
# 将shark加入列表model.wv.most_similar(positive=[\'whale\', \'fish\', \'creature\', \'animal\', \'shark\'], topn=30)
输出结果
[(\'great\', 0.9997999668121338), (\'though\', 0.9997922778129578), (\'part\', 0.999788761138916), (\'full\', 0.999781608581543), (\'small\', 0.9997766017913818), (\'like\', 0.9997683763504028), (\'among\', 0.9997652769088745), (\'many\', 0.9997631311416626), (\'case\', 0.9997614622116089), (\'even\', 0.9997515678405762), (\'body\', 0.9997514486312866), (\'almost\', 0.9997509717941284), (\'present\', 0.9997479319572449), (\'found\', 0.999747633934021), (\'water\', 0.9997465014457703), (\'made\', 0.9997431635856628), (\'air\', 0.9997406601905823), (\'whole\', 0.9997400641441345), (\'fishery\', 0.9997299909591675), (\'harpooneer\', 0.9997295141220093), (\'time\', 0.9997290372848511), (\'two\', 0.9997289776802063), (\'sea\', 0.9997265934944153), (\'strange\', 0.9997244477272034), (\'large\', 0.999722421169281), (\'place\', 0.9997209906578064), (\'dead\', 0.9997198581695557), (\'leviathan\', 0.9997192025184631), (\'sometimes\', 0.9997178316116333), (\'high\', 0.9997177720069885)]
PYTHON代码
# 将leviathan(海 serpent)加入列表model.wv.most_similar(positive=[\'whale\', \'fish\', \'creature\', \'animal\', \'shark\', \'leviathan\'], topn=30)
输出结果
[(\'though\', 0.9998274445533752), (\'part\', 0.9998168349266052), (\'full\', 0.9998133182525635), (\'small\', 0.9998107552528381), (\'great\', 0.9998067021369934), (\'like\', 0.9998064041137695), (\'even\', 0.9997999668121338), (\'many\', 0.9997966885566711), (\'body\', 0.9997950196266174), (\'among\', 0.999794602394104), (\'found\', 0.9997929334640503), (\'case\', 0.9997885823249817), (\'almost\', 0.9997871518135071), (\'made\', 0.9997868537902832), (\'air\', 0.999786376953125), (\'water\', 0.9997802972793579), (\'whole\', 0.9997780919075012), (\'present\', 0.9997757077217102), (\'harpooneer\', 0.999768853187561), (\'place\', 0.9997684955596924), (\'much\', 0.9997658729553223), (\'time\', 0.999765157699585), (\'sea\', 0.999765157699585), (\'dead\', 0.999764621257782), (\'strange\', 0.9997624158859253), (\'high\', 0.9997615218162537), (\'two\', 0.999760091304779), (\'sometimes\', 0.9997592568397522), (\'half\', 0.9997562170028687), (\'vast\', 0.9997541904449463)]
没有找到新的海洋生物。看来我们已用Word2Vec找回了海洋生物列表。
局限性
我们的列表中至少遗漏了一种海洋生物——大王乌贼。大王乌贼在《白鲸记》中仅被提及几次,因此我们的Word2Vec模型可能无法为“squid”(乌贼)训练出良好的表示。神经网络只有在数据量充足时才能表现良好。
探索skip-gram算法
skip-gram
算法在捕捉训练数据中罕见单词的语义方面有时表现更优。用skip-gram
算法训练新的Word2Vec模型,看看能否重复上述类别搜索任务找到“squid”(乌贼)这个词。
PYTHON代码
# 导入gensim的Word2Vec模块from gensim.models import Word2Vec# 使用清洗后的数据训练Word2Vec模型model = Word2Vec(sentences=tokens_cleaned, seed=0, workers=1, sg=1)model.wv.most_similar(positive=[\'whale\', \'fish\', \'creature\', \'animal\', \'shark\', \'leviathan\'], topn=100) # 仍未找到squid
输出结果
[(\'whalemen\', 0.9931729435920715), (\'specie\', 0.9919217824935913), (\'bulk\', 0.9917919635772705), (\'ground\', 0.9913252592086792), (\'skeleton\', 0.9905602931976318), (\'among\', 0.9898401498794556), (\'small\', 0.9887762665748596), (\'full\', 0.9885162115097046), (\'captured\', 0.9883950352668762), (\'found\', 0.9883666634559631), (\'sometimes\', 0.9882548451423645), (\'snow\', 0.9880553483963013), (\'magnitude\', 0.9880378842353821), (\'various\', 0.9878063201904297), (\'hump\', 0.9876748919487), (\'cuvier\', 0.9875931739807129), (\'fisherman\', 0.9874721765518188), (\'general\', 0.9873012900352478), (\'living\', 0.9872495532035828), (\'wholly\', 0.9872384667396545), (\'bone\', 0.987160861492157), (\'mouth\', 0.9867696762084961), (\'natural\', 0.9867129921913147), (\'monster\', 0.9865870475769043), (\'blubber\', 0.9865683317184448), (\'indeed\', 0.9864518046379089), (\'teeth\', 0.9862186908721924), (\'entire\', 0.9861844182014465), (\'latter\', 0.9859246015548706), (\'book\', 0.9858523607254028)]
讨论练习结果:使用Word2Vec揭示某类别中的项目时,可能会遗漏很少被提及的项目。即使使用在罕见词上表现更优的Skip-gram
训练方法,也是如此。因此,有时将此类任务留待处理更大的文本语料库更合适。在后续课程中,我们将探索大语言模型(LLM)如何在命名实体识别相关任务中表现更优。
实体识别应用
在你的研究中,还可以如何利用这类分析?和小组分享你的想法。
示例:在19世纪的报纸文章上训练模型,收集语料库中提及的食物列表(所选主题)。对20世纪的报纸文章做同样处理,比较流行食物随时间的变化。
跨作者比较向量表示
回忆一下,Word2Vec模型基于单词最常见的上下文单词来学习编码单词的语义/表示。例如,在两位不同作者的书籍(每位作者对应一个模型)上训练两个独立的Word2Vec模型,我们可以比较不同作者用词的差异。这种方法可以用来研究哪些研究问题或单词?
一种可能的方法是比较作者如何表现不同性别。由于历史性别规范,早期(过时的!)书籍中“man”和“woman”的词向量可能比新书的词向量距离更远。
其他词嵌入模型
尽管Word2Vec是著名模型且至今仍在许多NLP应用中使用,但还有其他一些词嵌入模型值得探索。GloVe
和fastText
是目前最流行的两种选择。
PYTHON代码
# 预览可用的其他词嵌入模型print(list(api.info()[\'models\'].keys()))
输出结果
[\'fasttext-wiki-news-subwords-300\', \'conceptnet-numberbatch-17-06-300\', \'word2vec-ruscorpora-300\', \'word2vec-google-news-300\', \'glove-wiki-gigaword-50\', \'glove-wiki-gigaword-100\', \'glove-wiki-gigaword-200\', \'glove-wiki-gigaword-300\', \'glove-twitter-25\', \'glove-twitter-50\', \'glove-twitter-100\', \'glove-twitter-200\', \'__testing_word2vec-matrix-synopsis\']
相似性
- 三种算法都在高维空间中生成单词的向量表示
- 它们可用于解决多种自然语言处理任务
- 它们都是开源的,在研究社区中广泛使用
差异
- Word2Vec通过给定句子中单词的周围单词来预测其上下文以生成嵌入,而
GloVe
和fastText
通过预测语料库中单词与其他单词共现的概率来生成嵌入 fastText
还包含字符n-gram,使其能为训练中未见过的单词生成嵌入,在处理未登录词时特别有用- 总体而言,在三种嵌入技术(
GloVe
、fastText
、Word2Vec
)中,fastText
被认为是训练最快的。这是因为fastText
使用子词信息,减少了词汇量并允许模型处理未登录词。此外,fastText
在训练中使用分层softmax,比Word2Vec使用的传统softmax更快。最后,fastText
可在多线程上训练,进一步加快训练过程
关键要点
- 作为使用预训练模型的替代方案,在特定数据集上训练Word2Vec模型可让你将Word2Vec用于**命名实体识别(NER)**相关任务。