今天给大家介绍一下word2vec的Skip-Gram模型及其应用,并且简要介绍一下word2vec背后的一些数学原理,在这里我们主要通过python的Gensim库来实现word2vec的实战应用。
word2vec是一种词向量的方法, 它可以让含义相近的词或者紧密关联的一些词具有较高的相似度,举例来说:“法国”与“巴黎” 的相似度要比“法国”与“北京”的相似度要高,这是因为如果一篇文章中出现“巴黎”这个词,那么在这篇文章中出现“法国”这个词的概率要比出现“中国”的概率要高,类似的词语还有“国王”与“王后”、“男性”与“女性”等等。这些词在词义上有可能并不相近,但是却有非常强的关联性,同时出现在同一篇文章中的概率较高。word2vec可以捕捉到这种词与词之间的关联性并对其进行建模。
word2vec有2种算法:连续词袋模型(CBOW)和Skip-Gram模型。这里我们主要介绍Skip-Gram模型。
利用word2vec算法,不需要告知算法腾讯的创始人是小马哥,也不需要知道阿里巴巴的创始人是Jack Ma,word2vec通过类比推断会自动去判别,如下面图所示,这是因为word2vec能从相似性的角度去做推断(这里的相似性不是说句型一样,而是语境语意类似,比如说北京天安门,众所周知这是一个景点,然后说广州小蛮腰,知道这也是一个景点,重点是语意理解上两个单词表达都是这么情况,跟上述腾讯和阿里巴巴创始人的例子类似)。
以下介绍来自于这篇博客
Skip-Gram主要是通过输入句子中特定的单词来预测该单词周边的其他单词。如下面图中的句子所示,假设分词得到如下的结果,句子前面是一共五个单词,Skip-gram的意思就是输入“喜欢”这个词,然后通过训练模型推断出“小马哥”、“非常”和“学习”、“自然语言处理”四个单词,当然这里是假设窗口大小为5。之所以叫Skip-gram是因为这是一个n-grams模型,但是不是固定长度的n-gram而是略过了中间的单词来进行建模的一个语言模型。回到上面的例子,更准确的讲法是通过“喜欢”来预测“小马哥”这个单词的时候,中间是略过了非常,以此类推下去。
word2vec的原作者是用soft-max来做为最后的分类器的,模型都是跟数值直接打交道,所以我们需要对句子中的分词结果进行编码才能放入模型,而skip-gram的方式是直接用的独热编码的方式来对每个单词进行编码。具体的一个流程是对文本进行 分词 --> 统计词汇量 --> 基于词汇量对句子或者文本进行独热编码(向量wt表示的是该标识符在位置t的独热编码) --> 训练 --> 模型 --> 预测。网络结构可表述为下图所示,如果窗口大小为5,那么需要循环训练四次来得到目标单词周边的四个单词的预测结果:
当skip-gram的模型训练结束之后,模型训练得到的参数矩阵(weight matrix)就是被训练用来表征语意的。这里得益于独热编码的好处,训练得到的参数矩阵的每一行就是表征了文本中的每个单词。这里可以参看上图,输入层是一个1 x 5的向量,假设隐藏层有3个神经元,那么从输入层映射到隐藏层需要一个5 x 3的矩阵,在经过训练迭代之后得到这个5 x 3矩阵中的每一行就是表征了相对应的单词(具体的计算方式可以参看下图)。所以这里的参数矩阵就是最终需要的词嵌入,而参数矩阵跟独热编码得到输入向量的内积得到的就是词向量。不仅如此,在原作者的论文,他们还证明了语意上相近的单词会有类似的向量表征,这是因为这些单词最终是都有类似的周边单词。
通过上述向量和矩阵的相乘,并引入softmax作为激活函数得到最终的结果,而通过结果就可以理解原本的向量是5维的,现在得到的词向量的维度变小了
我们的原始数据来自于搜狗2006年新闻数据集,我对它进行了整理和分类,根据文章的内容我把所有的文章分词了9个大类包括:军事、科技、体育、健康、旅游、财经、教育、历史,每个分类中的文章数为1990篇。你可以在这里下载数据:
(链接:https://pan.baidu.com/s/1XVvWph0kA-TpiM2CcxvQyQ 提取码:n8uh)
df = pd.read_csv( './data/sogou2006_clean.csv')
len(df)
df.sample(10)
由于我们的文章中包含了大量的特殊字符和标点符号(如回车符,换行符,全角和半角的标点符号),这些特殊符号和标点符号会影响我们词向量的准确性,因此在生成词向量之前我们要对这些特殊字符和标点符号进行清洗,这里数据清洗要做的事情是:1.删除文章中的所有标点符号和特殊字符,只保留中文,字母和数字。2.删除文章中的一些无意义的中文停用词。
#定义删除除字母,数字,汉字以外的所有符号的函数
def remove_punctuation(line):
line = str(line)
if line.strip()=='':
return ''
rule = re.compile(u"[^a-zA-Z0-9\u4E00-\u9FA5]")
line = rule.sub('',line)
return line
def stopwordslist(filepath):
stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
return stopwords
#加载停用词
stopwords = stopwordslist("./data/chineseStopWords.txt")
df['text_clean'] = df['text'].apply(remove_punctuation)
df.sample(10)
经过数据清洗以后,我们在原来的数据集中增加了一列:text_clean,该列用来存放进过数据清洗后的文章内容,数据清洗完成以后我们要对文章内容进行中文分词操作,在这里我们使用的中文分词工具是:jieba
df['text_cut'] = df['text_clean'].apply(lambda x: " ".join([w for w in list(jb.cut(x)) if w not in stopwords]))
df.sample(10)
经过中文分词以后,我们在原来的数据集中又新增了一列:text_cut,该列用来存放进过分词后的文章内容。接下来我们要将分词后文章内容生成一个语料库。
sentences = [row.split() for row in df['text_cut']]
word_freq = defaultdict(int)
for sent in sentences:
for i in sent:
word_freq[i] += 1
len(word_freq)
word_freq
在我们的语料库中总共有297176个唯一词语,并且我们还统计了每个词语出现的次数。
这里我们将使用 Gensim来训练我们文章的word2vec 词嵌入。我们将使用下列参数和方法:
sentences = [row.split() for row in df['text_cut']]
model = Word2Vec(min_count=200,
window=5,
size=100,
workers=4)
model.build_vocab(sentences)
model.train(sentences, total_examples=model.corpus_count, epochs=model.iter)
model.init_sims(replace=True)
经过上述的建模和训练后,我们测试一下word2vec的效果,看看它能否匹配到含义相近或者含义紧密关联的词语:
我们看到“基金”这个金融词汇与“债券”、“股票”、“投资”等金融词汇有较高的相似度。
“失业”与“劳动力”、“就业”、“贫困”等词汇有较高的相似度。
下面我们将“曹操”与两种交通工具“马”和“汽车”进行比较,“马”作为古代的一种交通工具,曹操肯定会骑,但汽车应该不会开。
上述结果很明显“曹操”与“马”有较高的关联性,而“曹操”与“汽车”几乎没有关联关系。
这里需要注意一点的是,我在模型中使用了min_count这个参数,该参数将过滤掉词频小于min_count的词语,如果你修改min_count这个参数,那么你看到的结果会和现在有所差异。
下面我们使用 t-SNE算法将word2vec的词向量进行降维处理,由于我们之前训练的word2vec词向量是100维的(我们设置了size=100),所以无法可视化,因此我们在这里用t-SNE算法将词向量维度从100维降到2维,这样就可以可视化了同时仍然保留原来词向量的信息。另外由于语料库中的单词数量太多(有29万),为了可视化的效果更加美观,所以我们从中随机抽取少量的词汇进行可视化。
def tsne_plot(model):
labels = []
tokens = []
for word in model.wv.vocab:
tokens.append(model[word])
labels.append(word)
tsne_model = TSNE(perplexity=40, n_components=2, init='pca', n_iter=2500, random_state=23)
new_values = tsne_model.fit_transform(tokens)
x = []
y = []
for value in new_values:
x.append(value[0])
y.append(value[1])
plt.figure(figsize=(18, 18))
for i in range(len(x)):
plt.scatter(x[i],y[i])
plt.annotate(labels[i],
xy=(x[i], y[i]),
xytext=(5, 2),
textcoords='offset points',
ha='right',
va='bottom')
plt.show()
tsne_plot(model)
朋友们有没有发现,那些含义相近或者含义紧密关联的词汇他们之间的距离是不是更小?
下面我们挑选若干词汇:"行情", "旅游", "手机", "股权","基金", "招聘", "高考", "分析","录取", "学生", "员工", '军事', '老板', '战争', '国际', '服务',"健康","地区","俄罗斯","台湾","中国","董事会","风险","部队" 我们将这些词汇进行单独的可视化。
model_wv_df = pd.DataFrame(model[model.wv.vocab], list(model.wv.vocab))
keywords = ["行情", "旅游", "手机", "股权","基金", "招聘", "高考", "分析","录取", \
"学生", "员工", '军事', '老板', '战争','国际', '服务',"健康","地区",\
"俄罗斯","台湾","中国","董事会","风险","部队"]
words = [word for word in keywords if word in list(model.wv.vocab)]
X = model_wv_df.T[words].T
pca = PCA(n_components=2)
result = pca.fit_transform(X)
df = pd.DataFrame(result, columns=["Component 1", "Component 2"])
df["Word"] = keywords
df["Distance"] = np.sqrt(df["Component 1"]**2 + df["Component 2"]**2)
fig = px.scatter(df, x="Component 1", y="Component 2", text="Word", color="Distance", color_continuous_scale="agsunset",size="Distance")
fig.update_traces(textposition='top center')
fig.layout.xaxis.autorange = True
fig.data[0].marker.line.width = 1
fig.data[0].marker.line.color = 'rgb(0, 0, 0)'
fig.update_layout(height=800, template="plotly_white", paper_bgcolor="#f0f0f0")
fig.show()
从上面的图中我们可以看到“学生”、“录取”、“高考”这些词之间的距离较近,意味着这些词语之间可能有紧密关联的关系,这基本符合实际情况,类似的情况还有“老板”、“董事会”、“基金”、“风险”、“行情”等。当然这也和语料库中的文章数量有关,如果文章数量越多,涉及面越广,那么词汇之间的关联关系会被模型更加准确的捕捉到。
word2vec是如何工作的
Word2vec embeddings
Word2Vec Tutorial - The Skip-Gram Model
https://github.com/tongzm/ml-python/blob/master/work2vec%E7%9A%84%E5%AE%9E%E6%88%98%E5%BA%94%E7%94%A8.ipynb