您现在的位置是:首页 > 正文

gensim中word2vec python源码理解(一)初始化构建单词表

2024-02-29 11:07:14阅读 0

gensim中word2vec python源码理解(一)初始化构建单词表
gensim中word2vec python源码理解(二)Skip-gram模型训练

本文主要谈一谈对gensim包中封装的word2vec python源码中,使用Hierarchical Softmax构建单词表部分代码的理解。
由于之前阅读的论文是对使用Hierarchical Softmax的Skip-gram模型进行拓展,因此在阅读代码的时候重点阅读了Hierarchical Softmax构建单词表的方法,以及Skip-gram模型的训练方法。对于negative sampling方法和CBOW模型的实现方法,则会继续对代码进行研究。

init

初始化一个model(实际上是Word2Vec类的实例化对象):

model = Word2Vec(sentences, size=100, window=5, min_count=5, workers=4)

进入类的初始化方法__init__,对里面的属性值进行初始化。
在传入的训练句子不为空的情况下,主要调用两个方法:

self.build_vocab(sentences, trim_rule=trim_rule)
self.train(
                sentences, total_examples=self.corpus_count, epochs=self.iter,
                start_alpha=self.alpha, end_alpha=self.min_alpha
            )

build_vocab

该方法是从句子序列中构建单词表,其中每个句子都是字符串组成的列表。依次调用了三个方法:scan_vocabscale_vocabfinalize_vocab
下面依次介绍三个方法的功能:

在这里插入图片描述

scan_vocab :对句子中的单词进行初始化

代码内容阅读(有省略):

sentence_no = -1 #保存扫描完成的句子数量
total_words = 0 #保存出现的单词总数(不去重)
min_reduce = 1
vocab = defaultdict(int) #将单词表初始化为一个字典
checked_string_types = 0
#扫描每个句子
for sentence_no, sentence in enumerate(sentences): #取出语料中每个句子和其在语料库中的编号no
	for word in sentence:
		vocab[word] += 1 #记录每个词出现的次数
	total_words += len(sentence) #记录扫描过的句子里的单词总数
	if self.max_vocab_size and len(vocab) > self.max_vocab_size: #如果对于最大单词数有限制且当前超出限制
		#将语料库中小于min_reduce(初始值为1)的单词都删除
		utils.prune_vocab(vocab, min_reduce, trim_rule=trim_rule) 
		min_reduce += 1 #不断增大min_reduce,直到单词表长度不大于max_vocab_size

self.corpus_count = sentence_no + 1 #保存语料数(句子数)
self.raw_vocab = vocab #保存单词表
return total_words #返回单词总数
scale_vocab :应用min_count的词汇表设置(丢弃不太频繁的单词)和sample(控制更频繁单词的采样)。

代码内容阅读(有省略):
加载新的词汇表:

if not update: #加载一个新的词汇表
	retain_total, retain_words = 0, [] #保留总数,保留的单词
	#获得单词及其出现的数量,raw_vocab是scan_vocab中保存的单词表dict
	for word, v in iteritems(self.raw_vocab): 
		#判断当前单词是否被丢弃,trim_rule为修剪规则,默认为none
		if keep_vocab_item(word, v, min_count, trim_rule=trim_rule): 
			retain_words.append(word) #添加单词
			retain_total += v #添加词数
            if not dry_run:
	            #为每个单词构建一个Vocab类,传入词频、下标
	            self.wv.vocab[word] = Vocab(count=v, index=len(self.wv.index2word)) 
                self.wv.index2word.append(word)
		else: #不符合条件则丢弃
			drop_unique += 1
            drop_total += v

添加新的单词更新模型:

else:
	new_total = pre_exist_total = 0
	new_words = pre_exist_words = []
	for word, v in iteritems(self.raw_vocab):#遍历更新的单词表
		if keep_vocab_item(word, v, min_count, trim_rule=trim_rule): #判断当前单词是否被丢弃
			if word in self.wv.vocab: #如果单词存在在之前的单词表中
				pre_exist_words.append(word) #添加至先前存在的单词list
				pre_exist_total += v#添加词频
				if not dry_run:
					self.wv.vocab[word].count += v#更新原单词表的词频
			else: #如果单词不存在在之前的单词表中(新单词)
				new_words.append(word)
                new_total += v
                if not dry_run:
		            #为单词构建一个Vocab类
		            self.wv.vocab[word] = Vocab(count=v, index=len(self.wv.index2word))
	                self.wv.index2word.append(word)#给单词添加下标
		else:#不符合条件则丢弃
			drop_unique += 1
			drop_total += v

计算采样阈值

# 预先计算每个词汇项目的采样阈值
if not sample:
	# no words downsampled 没有单词被downsample,阈值等于单词总数
	threshold_count = retain_total
elif sample < 1.0:
	# traditional meaning: set parameter as proportion of total
	threshold_count = sample * retain_total
else:
	# new shorthand: sample >= 1 means downsample all words with higher count than sample
	threshold_count = int(sample * (3 + sqrt(5)) / 2)

downsample_total, downsample_unique = 0, 0
for w in retain_words:
	v = self.raw_vocab[w]#v是当前单词出现的次数
	word_probability = (sqrt(v / threshold_count) + 1) * (threshold_count / v)
	if word_probability < 1.0:
		downsample_unique += 1
		downsample_total += word_probability * v
	else: #如果没有设置sample值的话,word_probability一定>1
		word_probability = 1.0
		downsample_total += v
	if not dry_run:
		self.wv.vocab[w].sample_int = int(round(word_probability * 2**32)) #设置一个采样值,round返回浮点数x的四舍五入值。

finalize_vocab :根据最终词汇表设置建立表格和模型权重。

代码内容阅读(有省略):

if not self.wv.index2word:
	self.scale_vocab()
if self.sorted_vocab and not update:
	self.sort_vocab() #按照词频降序排列,使得词频大的词下标更小
if self.hs:
	# 添加每个单词的Huffman编码信息
	self.create_binary_tree()
if self.negative:
	# 负采样
	self.make_cum_table()
if self.null_word:
	# create null pseudo-word for padding when using concatenative L1 (run-of-words)
	# this word is only ever input – never predicted – so count, huffman-point, etc doesn't matter
	word, v = '\0', Vocab(count=1, sample_int=0)
	v.index = len(self.wv.vocab)
	self.wv.index2word.append(word)
	self.wv.vocab[word] = v
# set initial input/projection and hidden weights
if not update:#如果不是添加新词以更新,则重置权重矩阵
	self.reset_weights()
else:
	self.update_weights()

从代码中可以看出,Hierarchical Softmax方法和negative sampling方法对应两种构建词表的方法,分别是create_binary_treemake_cum_table

create_binary_tree 构建二叉树

Hierarchical Softmax方法,使用存储的词汇单词及其词频创建一个二进制哈夫曼树。频繁的词编码更短。

# build the huffman tree
heap = list(itervalues(self.wv.vocab)) #将字典中的value以列表形式返回,其value是Vocab类的实例
heapq.heapify(heap)
for i in xrange(len(self.wv.vocab) - 1): #保存内节点
	min1, min2 = heapq.heappop(heap), heapq.heappop(heap)#取出最小的两个
	#放入两个小值节点的父节点,下标从单词表长度向后取,count值取两个孩子节点的count之和,设置左右孩子
	heapq.heappush( 
		heap, Vocab(count=min1.count + min2.count, index=i + len(self.wv.vocab), left=min1, right=min2)
	)#最终只剩一个根节点在堆栈中

# recurse over the tree, assigning a binary code to each vocabulary word 
#在树上递归,为每个词汇词分配一个二进制代码,保存到达该节点的路径上经过的内节点
if heap:
	max_depth, stack = 0, [(heap[0], [], [])] #定义一个最大深度,一个堆栈,放入根节点
	while stack:
	    node, codes, points = stack.pop()
	    #node节点对应一个Vocab类的实例(也就是一个节点),code对应该节点的编码,points对应到达该节点经过的节点
		if node.index < len(self.wv.vocab):
		#如果取出的节点下标小于单词表的长度,即该词在单词表内,取出的是叶节点
			# 叶节点=>从根存储它的路径
			node.code, node.point = codes, points
            max_depth = max(len(codes), max_depth)
		else: #否则,取出的是内节点=>继续递归
            # inner node => continue recursion
            #保存路径经过的节点
            points = array(list(points) + [node.index - len(self.wv.vocab)], dtype=uint32)
            # 把左右孩子节点放入栈中
            stack.append((node.left, array(list(codes) + [0], dtype=uint8), points))
            stack.append((node.right, array(list(codes) + [1], dtype=uint8), points))

在构建单词表完成后,每个单词对应的都是类Vocab的一个实例,构建哈夫曼树完成之后,二叉树中每个内节点对应的也是一个Vocab类的实例,其left和right属性分别保存了其左右孩子,points保存根节点到达该节点的路径(由经过的内节点的序号构成),codes保存该节点的二进制编码。

make_cum_table 构建负采样时的每个单词占用长度

首先回顾下negative sampling原理:
如果词汇表的大小为 V V V,那么我们就将一段长度为1的线段分成 V V V份,每份对应词汇表中的一个词。当然每个词对应的线段长度是不一样的,高频词对应的线段长,低频词对应的线段短。每个词 w w w的线段长度由下式决定:

l e n ( w ) = c o u n t ( w ) 3 / 4 ∑ u ∈ v o c a b c o u n t ( u ) 3 / 4 len(w)=\frac{count(w)^{3/4}}{\sum_{u\in vocab}count(u)^{3/4}} len(w)=uvocabcount(u)3/4count(w)3/4

在采样前,我们将这段长度为1的线段划分成 M M M等份,这里 M > > V M>>V M>>V,这样可以保证每个词对应的线段都会划分成对应的小块。而 M M M份中的每一份都会落在某一个词对应的线段上。在采样的时候,我们只需要从 M M M个位置中采样出neg个位置就行,此时采样到的每一个位置对应到的线段所属的词就是我们的负例词。

原理分析参考博客:word2vec原理(三) 基于Negative Sampling的模型

def make_cum_table(self, power=0.75, domain=2**31 - 1):
	'''
	domain表示均分的份数,对应原理公式中的M(M>>V, V是词表长度,即vocab_size)
	'''
    vocab_size = len(self.wv.index2word)
    self.cum_table = zeros(vocab_size, dtype=uint32)  # 将所有的单词占比初始化为0
    # compute sum of all power (Z in paper)
    train_words_pow = 0.0
    # 先计算公式中的分母
    for word_index in xrange(vocab_size):
        train_words_pow += self.wv.vocab[self.wv.index2word[word_index]].count**power
    # 计算分子
    cumulative = 0.0
    for word_index in xrange(vocab_size):
        # 到第i个单词的时候,分子是前i个单词的词频(3/4次幂)之和
        cumulative += self.wv.vocab[self.wv.index2word[word_index]].count**power
        # 在代码中实际上计算的不是每个单词占据的len,而是在这条“线段”所占据的部分的右端index。
        self.cum_table[word_index] = round(cumulative / train_words_pow * domain)
    # 设置最右端长度为domain(即为公式中的M)
    if len(self.cum_table) > 0:
        assert self.cum_table[-1] == domain

至此,负采样所需的词对应占比表构建完成。

reset_weights

重置隐藏层的权重

#syn0表示词向量矩阵
#单词数为行,向量维数为列, empty 会创建一个没有使用特定值来初始化的数组
self.wv.syn0 = empty((len(self.wv.vocab), self.vector_size), dtype=REAL) 
# 对于每个单词分别为其初始化一个向量,而不是立即在RAM中实现巨大的随机矩阵
for i in xrange(len(self.wv.vocab)): #对于单词表中的每一个单词
    #初始化单词向量
    self.wv.syn0[i] = self.seeded_vector(self.wv.index2word[i] + str(self.seed)) 
    if self.hs:
	    #syn0表示二叉树的内节点向量矩阵,全部初始化为0向量
        self.syn1 = zeros((len(self.wv.vocab), self.layer1_size), dtype=REAL)
    if self.negative:
        self.syn1neg = zeros((len(self.wv.vocab), self.layer1_size), dtype=REAL)
    self.wv.syn0norm = None

    self.syn0_lockf = ones(len(self.wv.vocab), dtype=REAL)  # zeros suppress learning

至此,构建单词表完成。

网站文章

  • 边缘光 rim

    边缘光 rim

    边缘光在游戏中是很常用到的,比如角色受到攻击,模型边缘会有一圈红色的边框,这些都是边缘光得到的奇妙效果。下图展现了边缘光的效果边缘关的原理如下图所示代码如下图[csharp] view plain copyShader "Custom/Rim" {      Properties {  

    2024-02-29 11:07:08
  • matplotlib 画图笔记 柱状图、曲线图、box图

    matplotlib 画图笔记 柱状图、曲线图、box图

    import matplotlib.pyplot as plt import numpy as np from matplotlib.backends.backend_pdf import PdfPa...

    2024-02-29 11:07:01
  • 算法与数据结构

    算法与数据结构

    文章目录算法数据结构算法复杂度数据结构1、前言2、数组3、链表4、栈5、队列6、树7、图8、散列表9、堆动态规划搜索与回溯算法分治算法排序查找算法双指针位运算数学模拟算法数据结构算法: 动态规划、回溯...

    2024-02-29 11:06:54
  • cesium态势标绘示例目录

    cesium态势标绘欢迎您的订阅,订阅后您可以直接查看以下内容;此专栏为vue + cesium标绘与编辑,均采用es6模块化写法逻辑清晰,会有明显的标注说明,使代码容易读懂,理解和学习,相信读完此专...

    2024-02-29 11:06:24
  • .net core web api简单创建

    .net core web api简单创建

    第一步:创建一个asp .net core web应用项目第二步:编辑下controller效果第三步:为了方便管理api搭建Swagger安装修改配置文件:startup.cs第四步启动项目

    2024-02-29 11:06:18
  • 微信小程序(模板)

    微信小程序(模板)

    1.模板:WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。定义模板:使用name属性,作为模板的名字。然后在内定义代码片段,如:<template name="postItem"> <view class="post-container"> <view class="post-author-date"> <image clas

    2024-02-29 11:06:10
  • 【计算机算法设计与分析】6-5 最小重量机器设计问题(C++_回溯法/分支限界法)

    问题描述 设某一机器由 n 个部件组成,每一种部件都可以从 m 个不同的供应商处购得。设 wij 是从供应商 j 处购得的部件 i 的重量, cij 是相应的价格。 设计一个优先队列式分支限界法,给出...

    2024-02-29 11:05:39
  • oracle 反斜杠 /

    sqlplus / as sysdba是一种简化写法,其实是省略了用户名和密码,完整的写法是sqlplus sys/password as sysdba而sqlplus /nolog是另外的用法了,nolog表示不进行登录,仅仅是进入sqlplus软件而已

    2024-02-29 11:05:32
  • 深度学习简介和反向传播---李宏毅《机器学习》笔记04

    深度学习简介和反向传播---李宏毅《机器学习》笔记04

    文章目录一、深度学习的三个步骤Step1:神经网络(Neural network)Step2:模型评估(Goodness of function)Step3:选择最优函数(Pick best func...

    2024-02-29 11:05:26
  • 开源基于ASP.Net Core开发的一套通用后台框架

    开源基于ASP.Net Core开发的一套通用后台框架

    开源基于ASP.Net Core开发的一套通用后台框架【转】果冻栋吖程序员30 人赞同了该文章基于http://ASP.NetCore开发一套通用后台框架写在前面这是本人在学习的过程中搭建学习的框架,...

    2024-02-29 11:04:56