Word2Vec模型训练全流程解析:从数据预处理到实体识别应用
请添加图片描述
训练Word2Vec模型
概述
问题
- 我们如何训练Word2Vec模型?
- 在特定数据集上训练Word2Vec模型何时是有利的?
目标
- 理解在自有数据上训练Word2Vec模型而非使用预训练模型的优势
Colab环境配置
运行以下代码以启用辅助函数并重新读取数据。
PYTHON代码
# 运行此单元格以挂载你的Google Drive
from google.colab import drive
drive.mount('/content/drive')# 显示现有Colab笔记本和helpers.py文件
from os import listdir
wksp_dir = '/content/drive/My Drive/Colab Notebooks/text-analysis/code'
print(listdir(wksp_dir))# 将文件夹添加到Colab的路径中,以便导入辅助函数
import sys
sys.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_csv
data = 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 = 500
print(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 him
now. He was ever dusting his old lexicons and grammars, with a queer
handkerchief, mockingly embellished with all the gay flags of all the
known nations of the world. He loved to dust his old grammars; it
somehow 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 nltk
nltk.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 pd
sentences_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)**相关任务。