Word2vec纯python代码实现

1. 什么是 Word2vec?

在聊 Word2vec 之前,先聊聊 NLP (自然语言处理)。NLP 里面,最细粒度的是 词语,词语组成句子,句子再组成段落、篇章、文档。所以处理 NLP 的问题,首先就要拿词语开刀。

举个简单例子,判断一个词的词性,是动词还是名词。用机器学习的思路,我们有一系列样本(x,y),这里 x 是词语,y 是它们的词性,我们要构建 f(x)->y 的映射,但这里的数学模型 f(比如神经网络、SVM)只接受数值型输入,而 NLP 里的词语,是人类的抽象总结,是符号形式的(比如中文、英文、拉丁文等等),所以需要把他们转换成数值形式,或者说——嵌入到一个数学空间里,这种嵌入方式,就叫词嵌入(word embedding),而 Word2vec,就是词嵌入( word embedding) 的一种

我在前作『都是套路: 从上帝视角看透时间序列和数据挖掘』提到,大部分的有监督机器学习模型,都可以归结为:

f(x)->y

在 NLP 中,把 x 看做一个句子里的一个词语,y 是这个词语的上下文词语,那么这里的 f,便是 NLP 中经常出现的『语言模型』(language model),这个模型的目的,就是判断 (x,y) 这个样本,是否符合自然语言的法则,更通俗点说就是:词语x和词语y放在一起,是不是人话。

Word2vec 正是来源于这个思想,但它的最终目的,不是要把 f 训练得多么完美,而是只关心模型训练完后的副产物——模型参数(这里特指神经网络的权重),并将这些参数,作为输入 x 的某种向量化的表示,这个向量便叫做——词向量(这里看不懂没关系,下一节我们详细剖析)。

我们来看个例子,如何用 Word2vec 寻找相似词:

  • 对于一句话:『她们 夸 吴彦祖 帅 到 没朋友』,如果输入 x 是『吴彦祖』,那么 y 可以是『她们』、『夸』、『帅』、『没朋友』这些词
  • 现有另一句话:『她们 夸 我 帅 到 没朋友』,如果输入 x 是『我』,那么不难发现,这里的上下文 y 跟上面一句话一样
  • 从而 f(吴彦祖) = f(我) = y,所以大数据告诉我们:我 = 吴彦祖(完美的结论)

2. Skip-gram 和 CBOW 模型

上面我们提到了语言模型

  • 如果是用一个词语作为输入,来预测它周围的上下文,那这个模型叫做『Skip-gram 模型』
  • 而如果是拿一个词语的上下文作为输入,来预测这个词语本身,则是 『CBOW 模型』

Skip-gram体系结构实现(CBOW同理)

内容分为以下几个部分:

1.数据准备——定义语料库、整理、规范化和分词

2.超参数——学习率、训练次数、窗口尺寸、嵌入(embedding)尺寸

3.生成训练数据——建立词汇表,对单词进行one-hot编码,建立将id映射到单词的字典,以及单词映射到id的字典

4.模型训练——通过正向传递编码过的单词,计算错误率,使用反向传播调整权重和计算loss值

5.结论——获取词向量,并找到相似的词

6.进一步的改进 —— 利用Skip-gram负采样(Negative Sampling)和Hierarchical Softmax提高训练速度

详解

1.数据准备

首先,我们从以下语料库开始:

natural language processing and machine learning is fun and exciting

简单起见,我们选择了一个没有标点和大写的橘子。而且,我们没有删除停用词“and”和“is”。

实际上,文本数据是非结构化的,甚至可能很“很不干净”清理它们涉及一些步骤,例如删除停用词、标点符号、将文本转换为小写(实际上取决于你的实际例子)和替换数字等。KDnuggets 上有一篇关于这个步骤很棒的文章。另外,Gensim也提供了执行简单文本预处理的函数——gensim.utils.simple_preprocess,它将文档转换为由小写的词语(Tokens )组成的列表,并忽略太短或过长的词语。

在预处理之后,我们开始对语料库进行分词。我们按照单词间的空格对我们的语料库进行分词,结果得到一个单词列表:

[“natural”, “language”, “processing”, “ and”, “ machine”, “ learning”, “ is”, “ fun”, “and”, “ exciting”]

2.超参数

在进入word2vec的实现之前,让我们先定义一些稍后需要用到的超参数。

[window_size/窗口尺寸]:上下文单词是与目标单词相邻的单词。但是,这些词应该有多远或多近才能被认为是相邻的呢?这里我们将窗口尺寸定义为2,这意味着目标单词的左边和右边最近的2个单词被视为上下文单词。

[n]:这是单词嵌入(word embedding)的维度,通常其的大小通常从100到300不等,取决于词汇库的大小。超过300维度会导致效益递减(参见图2(a)的1538页)。请注意,维度也是隐藏层的大小。

[epochs] :表示遍历整个样本的次数。在每个epoch中,我们循环通过一遍训练集的样本。

[learning_rate/学习率]:学习率控制着损失梯度对权重进行调整的量。

3.生成训练数据

在本节中,我们的主要目标是将语料库转换one-hot编码表示,以方便Word2vec模型用来训练。

为了生成one-hot训练数据,我们首先初始化word2vec()对象,然后使用对象w2v通过settings 和corpus 参数来调用函数generate_training_data。

在函数generate_training_data内部,我们进行以下操作:

  1. self.v_count: 词汇表的长度(注意,词汇表指的就是语料库中不重复的单词的数量)
  2. self.words_list: 在词汇表中的单词组成的列表
  3. self.word_index: 以词汇表中单词为key,索引为value的字典数据
  4. self.index_word: 以索引为key,以词汇表中单词为value的字典数据
  5. for循环给用one-hot表示的每个目标词和其的上下文词添加到training_data中,one-hot编码用的是word2onehot函数。

4.模型训练

Word2Vec——skip-gram的网络结构

拥有了training_data,我们现在可以准备训练模型了。训练从w2v.train(training_data)开始,我们传入训练数据,并执行train函数。

Word2Vec2模型有两个权重矩阵(w1和w2),为了展示,我们把值初始化到形状分别为(9x10)和(10x9)的矩阵。这便于反向传播误差的计算,在实际的训练中,随机初始化这些权重(使用np.random.uniform())。

训练——向前传递

接下来,我们开始用第一组训练样本来训练第一个epoch,方法是把w_t 传入forward_pass 函数,w_t 是表示目标词的one-hot向量。在forward_pass 函数中,我们执行一个w1 和w_t 的点乘积,得到h (原文是24行,但图中实际是第22行)。然后我们执行w2和h 点乘积,得到输出层的u( 原文是26行,但图中实际是第24行 )。最后,在返回预测向量y_pred和隐藏层h 和输出层u 前,我们使用softmax把u 的每个元素的值映射到0和1之间来得到用来预测的概率(第28行)。

训练——误差,反向传播和损失(loss)

误差——对于y_pred、h 和u,我们继续计算这组特定的目标词和上下文词的误差。这是通过对y_pred 与在w_c 中的每个上下文词之间的差的加合来实现的。

反向传播——接下来,我们使用反向传播函数backprop ,通过传入误差EI 、隐藏层h 和目标字w_t 的向量,来计算我们所需的权重调整量。

为了更新权重,我们将权重的调整量(dl_dw1 和dl_dw2 )与学习率相乘,然后从当前权重(w1 和w2 )中减去它。

损失——最后,根据损失函数计算出每个训练样本完成后的总损失。注意,损失函数包括两个部分。第一部分是输出层(在softmax之前)中所有元素的和的负数。第二部分是上下文单词的数量乘以在输出层中所有元素(在 exp之后)之和的对数。

5. 推论和总结(Inferencing)

既然我们已经完成了50个epoch的训练,两个权重(w1和w2)现在都准备好执行推论了。

获取单词的向量

有了一组训练后的权重,我们可以做的第一件事是查看词汇表中单词的词向量。我们可以简单地通过查找单词的索引来对训练后的权重(w1)进行查找。在下面的示例中,我们查找单词“machine”的向量。

> print(w2v.word_vec("machine"))
[ 0.76702922 -0.95673743  0.49207258  0.16240808 -0.4538815
-0.74678226  0.42072706 -0.04147312  0.08947326 -0.24245257]

查询相似的单词

我们可以做的另一件事就是找到类似的单词。即使我们的词汇量很小,我们仍然可以通过计算单词之间的余弦相似度来实现函数vec_sim 。

全部代码:

import numpy as np
from collections import defaultdictclass word2vec():def __init__(self):self.n = settings['n']self.lr = settings['learning_rate']self.epochs = settings['epochs']self.window = settings['window_size']def generate_training_data(self, settings, corpus):"""得到训练数据"""#defaultdict(int)  一个字典,当所访问的键不存在时,用int类型实例化一个默认值word_counts = defaultdict(int)#遍历语料库corpusfor row in corpus:for word in row:#统计每个单词出现的次数word_counts[word] += 1# 词汇表的长度self.v_count = len(word_counts.keys())# 在词汇表中的单词组成的列表self.words_list = list(word_counts.keys())# 以词汇表中单词为key,索引为value的字典数据self.word_index = dict((word, i) for i, word in enumerate(self.words_list))#以索引为key,以词汇表中单词为value的字典数据self.index_word = dict((i, word) for i, word in enumerate(self.words_list))training_data = []for sentence in corpus:sent_len = len(sentence)for i, word in enumerate(sentence):w_target = self.word2onehot(sentence[i])w_context = []for j in range(i - self.window, i + self.window):if j != i and j <= sent_len - 1 and j >= 0:w_context.append(self.word2onehot(sentence[j]))training_data.append([w_target, w_context])return np.array(training_data)def word2onehot(self, word):#将词用onehot编码word_vec = [0 for i in range(0, self.v_count)]word_index = self.word_index[word]word_vec[word_index] = 1return word_vecdef train(self, training_data):#随机化参数w1,w2self.w1 = np.random.uniform(-1, 1, (self.v_count, self.n))self.w2 = np.random.uniform(-1, 1, (self.n, self.v_count))for i in range(self.epochs):self.loss = 0# w_t 是表示目标词的one-hot向量#w_t -> w_target,w_c ->w_contextfor w_t, w_c in training_data:#前向传播y_pred, h, u = self.forward(w_t)#计算误差EI = np.sum([np.subtract(y_pred, word) for word in w_c], axis=0)#反向传播,更新参数self.backprop(EI, h, w_t)#计算总损失self.loss += -np.sum([u[word.index(1)] for word in w_c]) + len(w_c) * np.log(np.sum(np.exp(u)))print('Epoch:', i, "Loss:", self.loss)def forward(self, x):"""前向传播"""h = np.dot(self.w1.T, x)u = np.dot(self.w2.T, h)y_c = self.softmax(u)return y_c, h, udef softmax(self, x):""""""e_x = np.exp(x - np.max(x))return e_x / np.sum(e_x)def backprop(self, e, h, x):d1_dw2 = np.outer(h, e)d1_dw1 = np.outer(x, np.dot(self.w2, e.T))self.w1 = self.w1 - (self.lr * d1_dw1)self.w2 = self.w2 - (self.lr * d1_dw2)def word_vec(self, word):"""获取词向量通过获取词的索引直接在权重向量中找"""w_index = self.word_index[word]v_w = self.w1[w_index]return v_wdef vec_sim(self, word, top_n):"""找相似的词"""v_w1 = self.word_vec(word)word_sim = {}for i in range(self.v_count):v_w2 = self.w1[i]theta_sum = np.dot(v_w1, v_w2)#np.linalg.norm(v_w1) 求范数 默认为2范数,即平方和的二次开方theta_den = np.linalg.norm(v_w1) * np.linalg.norm(v_w2)theta = theta_sum / theta_denword = self.index_word[i]word_sim[word] = thetawords_sorted = sorted(word_sim.items(), key=lambda kv: kv[1], reverse=True)for word, sim in words_sorted[:top_n]:print(word, sim)def get_w(self):w1 = self.w1return  w1
#超参数
settings = {'window_size': 2,   #窗口尺寸 m#单词嵌入(word embedding)的维度,维度也是隐藏层的大小。'n': 10,'epochs': 50,         #表示遍历整个样本的次数。在每个epoch中,我们循环通过一遍训练集的样本。'learning_rate':0.01 #学习率
}#数据准备
text = "natural language processing and machine learning is fun and exciting"
#按照单词间的空格对我们的语料库进行分词
corpus = [[word.lower() for word in text.split()]]
print(corpus)#初始化一个word2vec对象
w2v = word2vec()training_data = w2v.generate_training_data(settings,corpus)#训练
w2v.train(training_data)# 获取词的向量
word = "machine"
vec = w2v.word_vec(word)
print(word, vec)# 找相似的词
w2v.vec_sim("machine", 3)

Word2vec代码实现相关推荐

  1. word2vec代码_Word2Vec-——gensim实战教程

    最近斯坦福的CS224N开课了,看了下课程介绍,去年google发表的Transformer以及最近特别火的Contextual Word Embeddings都会在今年的课程中进行介绍.NLP领域确 ...

  2. word2vec代码注释

    虽然word2vec火了很久了,但自己一直拖延了没去看,借实验室组会讨论的机会花一天时间阅读了和word2vec相关的一点东西,算是先知后觉. 邓澍军老师的中文教程很不错,顺着里面的文献一路看下去就能 ...

  3. word2vec代码_TensorFlow2.0 代码实战专栏(四):Word2Vec (Word Embedding)

    作者 |  Aymeric Damien编辑 | 奇予纪出品 | 磐创AI团队 Word2Vec (Word Embedding) 使用TensorFlow 2.0实现Word2Vec算法计算单词的向 ...

  4. word2vec代码实战

    1 代码及数据集下载 代码链接:https://github.com/ttb1534/word2vec-include-datapreprocess 数据集链接:https://pan.baidu.c ...

  5. word2vec的原理及实现(附github代码)

    目录 一.word2vec原理 二.word2vec代码实现 (1)获取文本语料 (2)载入数据,训练并保存模型 ①  # 输出日志信息 ②  # 将语料保存在sentence中 ③  # 生成词向量 ...

  6. Word2Vec详解-公式推导以及代码

    Word2Vec 1.前记 2.一些背景知识 2.1词向量简单介绍 2.2哈弗曼树简单介绍 3.基于层次softmax的模型 3.1COBW 层次softmax 3.1.1整体结构 3.1.2 前向传 ...

  7. 机器学习之自然语言处理——基于TfidfVectorizer和CountVectorizer及word2vec构建词向量矩阵(代码+原理)

    目录 理论知识准备 构造文本特征向量 TF-IDF 值 sklearn中TfidfVectorizer 代码实例 CountVectorizer() 代码实操 Word2Vec 代码案例 总结 每文一 ...

  8. word2vec自训练词向量(代码+注释+训练过程和结果)

    数据集MSRP.SICK.STS下载地址分享 百度云:https://pan.baidu.com/s/1sqlCc702owp_T6KjyNT6Yw 提取码: 66nb 运行:网盘中msr_train ...

  9. Word2vec 讨论

    我没有在自然语言处理完成.但基于Deep Learning 关注,自然知道一些Word2vec强大. Word2vec 是google 在2013年提供的一款将词表征为实数值向量的高效工具.而Word ...

最新文章

  1. 软件工程网络15结对编程作业
  2. b2evolution_0.9.2修改(1)
  3. 20165303实验一 Java开发环境的熟悉
  4. Linux/Centos7系统管理之深入理解Linux文件系统与日志分析
  5. Linux安装vsftpd组件
  6. ubuntu下eclipse新建项目没有java project的解决办法
  7. 如何知道osg模型每个节点的名称
  8. TCP多进程并发服务端 Linux socket编程入门(2)
  9. 工作274:ele-图标使用
  10. github ssh密钥_如何使用SSH密钥在一台机器上管理多个GitHub帐户
  11. 轻松生成ip地址的姿势,最快得到批量***目标
  12. mysql5.5.53安装教程_mysql5.5.28安装教程 超详细!
  13. MacOS下DockerCE的使用方式
  14. 973页kubernetes学习笔记,涵盖K8S所有核心知识点,仅分享3天
  15. Jupyter制作slides
  16. 环宇成功签约世界级海外文旅夜游项目,探索夜游新模式!
  17. IP地址冲突怎么办? 如何解决局域网IP地址冲突?
  18. 提笔,再回忆~落笔,成悔,一切皆已随风:伤感日志
  19. steam文件夹移动后游戏需要重新安装怎么办
  20. 提高PLC编程能力的6种电路,新手必学!

热门文章

  1. EPICS--areaDetector--ADEiger
  2. 无线路由器不能产生信号
  3. 如何解决服务器挖矿木马
  4. archivelog模式和flashback db以及guarantee restore point之间的相互制约关系!
  5. 22(线性方程组求解)高斯赛德尔迭代法
  6. Android 6.0 AppOps 简介
  7. python怎么横着输出_对python3中, print横向输出的方法详解
  8. iOS-【转载】架构模式 - 简述 MVC, MVP, MVVM 和 VIPER (译)
  9. Nginx证书配置:cer文件和jks文件转nginx证书.crt和key文件
  10. hdfs单点故障和内存受限问题