大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
CBOW一个用于快速训练得到词向量的神经网络模型,它的核心原理是中心词的前R个词和后R个词来预测中心词。
它的网络模型相比NNLM模型来说,最大的变化是直接去除隐层的非线性激活过程,以此来加速网络的训练速度。
CBOW的输入:
假设中心词 w i w_{i} wi的上下文 C ( w i ) = { w j ∣ j ∈ [ i − R , i ) ∩ [ i + 1 , i + R ) } C(w_{i})=\{w_{j}|j \in [i-R,i) \cap [i+1,i+R)\} C(wi)={
wj∣j∈[i−R,i)∩[i+1,i+R)},也就上文为中心词的前R个词,下文为中心词的后R个词。每次词的词向量为 e ( w i ) e(w_{i}) e(wi).那么输入到网络中的向量是这 2 R − 1 2R-1 2R−1个上下文词向量的平均值。即:
X = 1 2 R − 1 ∑ w ′ ∈ C ( w i ) e ( w ′ ) X=\frac{1}{2R-1}\sum_{w'\in C(w_{i})}e(w') X=2R−11w′∈C(wi)∑e(w′)
而其中的 e ( w i ) e(w_{i}) e(wi)则定义为从词向量矩阵 W ∣ V ∣ × ∣ D ∣ W_{|V|\times|D|} W∣V∣×∣D∣中取出词 w i w_{i} wi对应的那一行,或者那一列。 ∣ V ∣ |V| ∣V∣是这个待研究的预料库的词典的大小,一般为4000~7000。 ∣ D ∣ |D| ∣D∣是我们选择的词向量的长度,一般为50~500即可。
虽然我们知道 e ( w i ) e(w_{i}) e(wi)是将词 w i w_{i} wi对应的那一行词向量取出来,直观上也觉得很简单,然而这个“取”的过程并不好实现,尤其是GPU不好实现,GPU最愿意看到的是矩阵乘法那样子的东西。那么,能不能将这个”取”的过程,写成一个矩阵乘法的形式呢?
还真可以!
我们知道,某个单位正交基 e i ⃗ = ( 0 , 0 , 0 , … , 1 , … , 0 ) \vec{e_{i}}=(0,0,0,\dots,1,\dots,0) ei=(0,0,0,…,1,…,0),就是第i列的位置为1,其它位置为0的特殊向量,这个形式刚好又与我们常见到的one-hot向量一样。而 e i ⃗ \vec{e_{i}} ei左乘一个矩阵 W W W,恰好就是将 W W W的第i行单独取出来。于是这个将 w i w_{i} wi的词向量从 W ∣ V ∣ × ∣ D ∣ W_{|V|\times|D|} W∣V∣×∣D∣“取”出的过程恰好就可以表示为 e i ⃗ × W ∣ V ∣ × ∣ D ∣ \vec{e_{i}}\times W_{|V|\times|D|} ei×W∣V∣×∣D∣, e i ⃗ \vec{e_{i}} ei恰好也就是 w i w_{i} wi的one-hot向量。
我们再看刚刚的输入公式:
X = 1 2 R − 1 ∑ w ′ ∈ C ( w i ) e ( w ′ ) X=\frac{1}{2R-1}\sum_{w'\in C(w_{i})}e(w') X=2R−11w′∈C(wi)∑e(w′)
把它具体化就是:
X = 1 2 R − 1 ∑ w ′ ∈ C ( w i ) ( w ′ 的 o n e − h o t 向 量 ) × W ∣ V ∣ × ∥ D ∣ X=\frac{1}{2R-1}\sum_{w'\in C(w_{i})}(w'的one-hot向量)\times W_{|V|\times\|D|} X=2R−11w′∈C(wi)∑(w′的one−hot向量)×W∣V∣×∥D∣
而对于这个求和来说,里面有一个公共的因子 W ∣ V ∣ × ∥ D ∣ W_{|V|\times\|D|} W∣V∣×∥D∣。将它提取出来,原式变成:
X = 1 2 R − 1 ( ∑ w ′ ∈ C ( w i ) ( w ′ 的 o n e − h o t 向 量 ) ) × W ∣ V ∣ × ∥ D ∣ X=\frac{1}{2R-1}(\sum_{w'\in C(w_{i})}(w'的one-hot向量))\times W_{|V|\times\|D|} X=2R−11(w′∈C(wi)∑(w′的one−hot向量))×W∣V∣×∥D∣
再做一点点变形:
X = ( 1 2 R − 1 ∑ w ′ ∈ C ( w i ) ( w ′ 的 o n e − h o t 向 量 ) ) × W ∣ V ∣ × ∥ D ∣ X=(\frac{1}{2R-1}\sum_{w'\in C(w_{i})}(w'的one-hot向量) ) \times W_{|V|\times\|D|} X=(2R−11w′∈C(wi)∑(w′的one−hot向量))×W∣V∣×∥D∣
我们发现,我们可以先计算上下文词的One-hot的平均向量,再用这个向量是左乘矩阵。这么做的好处是可以大大的减少计算量,变形前需要计算2R-1次矩阵乘法,变形后只需一次矩阵乘法!
另外,值得一提的是,为什么这个模型叫做词袋模型呢?我们看到,上面这个输入,其实会将 w i w_{i} wi上下文对应的词向量都加起来求平均向量。显然因为向量加法的交换性,导致这个计算过程中上下文词是可以打断顺序的!而这与我们对语言的理解也是吻合的,举个例子:
这一是个很好的明说词袋模型的例子。你会发现前一句话中有些词序是混乱的,但是你依然知道这段文字在表达什么意思。
CBOW的输出: p ( w ′ ∣ C ( w i ) ) p(w'|C(w_{i}) ) p(w′∣C(wi)),
其中 p ( w ′ ∣ C ( w i ) ) p(w'|C(w_{i})) p(w′∣C(wi))= s o f t m a x ( X 1 × ∣ D ∣ × W ∣ D ∣ × ∣ V ∣ ′ ) softmax(X_{1\times|D|} \times W'_{|D|\times|V|}) softmax(X1×∣D∣×W∣D∣×∣V∣′)
这个输出 p ( w ′ ∣ C ( w i ) ) p(w'|C(w_{i})) p(w′∣C(wi))是一个形状为 1 × ∣ V ∣ 1\times |V| 1×∣V∣的行向量,其实是给定一个上下文 C ( w i ) C(w_{i}) C(wi)后模型认为其中心为词典里面各个词 w ′ w' w′的概率。然而,我们的目标是使得这个分布里面 w i w_{i} wi对应的概率最大。因些,我们将将 p ( w ′ ∣ C ( w i ) ) p(w'|C(w_{i})) p(w′∣C(wi))的第i个数提取出来,同样的为了完成这个“提取”任务,我们只需要将 p ( w ′ ∣ C ( w i ) ) p(w'|C(w_{i})) p(w′∣C(wi))左乘一个单位正交基 e ⃗ i \vec e_{i} ei,即可,这个正交基恰好又是 w i w_{i} wi的one-hot向量。
于是我们的目标函数就是:
L ( θ ) = ( w i 的 o n e − h o t 向 量 ) × s o f t m a x ( X 1 × ∣ D ∣ × W ∣ D ∣ × ∣ V ∣ ′ ) L(\theta)=(w_{i}的one-hot向量)\times softmax(X_{1\times|D|} \times W'_{|D|\times|V|}) L(θ)=(wi的one−hot向量)×softmax(X1×∣D∣×W∣D∣×∣V∣′)
最大化这个目标函数即可。一般 L ( θ ) L(\theta) L(θ)会比较小,于是我们会转而最大化 L ′ ( θ ) = l o g ( L ( θ ) ) L'(\theta)=log(L(\theta)) L′(θ)=log(L(θ))。一般又是最小化某个函数,于是我们会转而最小化 L ′ ′ ( θ ) = − l o g ( L ( θ ) ) L''(\theta)=-log(L(\theta)) L′′(θ)=−log(L(θ))。
以上就是CBOW的核心原理。
接下来,我们就来实现之。
TextLoader类的实现:
TextLoader类主要实现对原始预料文本文件进行词典提取、生成批训练数据等功能。
class TextLoader(object):
def __init__(self,input_data_path,Context_length=10,batch_size=10,min_frq = 3):
self.Context_length = Context_length #定义上下文的长度
self.V = {} # 将词映射到在词典中的下标
self.inverseV ={} #将下标映射到词典中的某个词
self.raw_text =list()
self.x_data =list()
self.y_data =list()
self.number_batch = 0
self.batch_size = batch_size
raw_text = []
#输入原始数据并统计词频
V = dict() #{'word':frq}
with open(input_data_path,"r",encoding="utf8") as fp:
lines = fp.readlines()
for line in lines:
line =line.split(" ")
line = ['<START>']+line+['<END>'] #为每句话加上<START>,<END>
raw_text += line
for word in line:
if word in V:
V[word] +=1
else:
V.setdefault(word,1)
#清除词频太小的词,同时为各个词建立下标到索引之间的映射
self.V.setdefault('<UNK>',0)
self.inverseV.setdefault(0,'<UNK>')
cnt = 1
for word in V:
if V[word] <= min_frq:
continue
else:
self.V.setdefault(word,cnt)
self.inverseV.setdefault(cnt,word)
cnt +=1
self.vacb_size = len(self.V)
#将文本由字符串序列转换为词的下标的序列
for word in raw_text:
self.raw_text +=[self.V[word] if word in self.V else self.V['<UNK>']]
#生成batches
self.gen_batch()
def gen_batch(self):
self.x_data =[]
self.y_data =[]
for index in range(self.Context_length,len(self.raw_text)-self.Context_length):
#index的前Context,加上index的后Context个词,一起构成了index词的上下文
x = self.raw_text[(index-self.Context_length):index]+ self.raw_text[(index+1):(self.Context_length+index)]
y = [ self.raw_text[index] ]
self.x_data.append(x)
self.y_data.append(y)
self.number_batch =int( len(self.x_data) / self.batch_size)
def next_batch(self):
batch_pointer = random.randint(0,self.number_batch-1)
x = self.x_data[batch_pointer:(batch_pointer+self.batch_size)]
y = self.y_data[batch_pointer:(batch_pointer+self.batch_size)]
return x ,y
TextLoader每次返回的训练batch里面的词是各个词在词典中的下标,在训练阶段,我们需要将其转换为向量。
训练逻辑:
#coding:utf-8
__author__ = 'jmh081701'
import tensorflow as tf
import numpy as np
import json
corpus_path = "data\\input.en.txt"
embedding_word_path = corpus_path+".ebd"
vacb_path = corpus_path+".vab"
data = TextLoader(corpus_path,batch_size=300)
embedding_word_length = 50
learning_rate =0.0001
#输入
input_x = tf.placeholder(dtype=tf.float32,shape=[None,data.vacb_size],name="inputx")
input_y = tf.placeholder(dtype=tf.float32,shape=[None,data.vacb_size],name='inputy')
W1 = tf.Variable(name="embedding_word",initial_value=tf.truncated_normal(shape=[data.vacb_size,embedding_word_length],stddev=1.0/(data.vacb_size)))
#W1其实就是 词向量矩阵
W2 = tf.Variable(tf.truncated_normal(shape=[embedding_word_length,data.vacb_size],stddev=1.0/data.vacb_size))
#计算过程
#累加所有上下文的one-hot向量,然后取平均值,再去乘以词向量矩阵;其效果就是相当于,选择出所有的上下文的词向量,然后取平均
hidden = tf.matmul(input_x,W1)
output = tf.matmul(hidden,W2) #batch_size * vacb_size的大小
output_softmax = tf.nn.softmax(output)
#取出中心词的那个概率值,因为iinput_y是一个one-hot向量,output_softmax左乘一个列向量,就是将这个列的第i行取出
output_y = tf.matmul(input_y,output_softmax,transpose_b=True)
loss = tf.reduce_sum(- tf.log(output_y)) #将batch里面的output_y累加起来
train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
max_epoch =10000
for epoch in range(1,max_epoch):
_x,_y = data.next_batch()
#生成本次的输入
x =[]
y =[]
for i in range(0,len(_x)):
#将Context*2 -1 个向量,求和取平均为一个向量,用于将来和词向量矩阵相乘
vec = np.zeros(shape=[data.vacb_size])
for j in range(0,len(_x[i])):
vec[ _x[i][j] ] += 1
vec /= len(_x[i])
x.append(vec)
y_vec = np.zeros(shape=[data.vacb_size])
y_vec[_y[i]] = 1
y.append(y_vec)
_loss,_ = sess.run([loss,train_op],feed_dict={input_x:x,input_y:y})
if (epoch % 100 )==0 :
print({'loss':_loss,'epoch':epoch})
#保存词向量
_W1 = sess.run(W1,feed_dict={input_x:[np.zeros([data.vacb_size])],input_y:[np.zeros([data.vacb_size])]})
#每一行就是对应词的词向量
np.save(embedding_word_path,_W1)
with open(vacb_path,"w",encoding='utf8') as fp:
json.dump(data.inverseV,fp)
print("Some TEST:")
print("<START>:",_W1[data.V['<START>']])
print("<END>:",_W1[data.V['<END>']])
print("<UNK>:",_W1[data.V['<UNK>']])
print("you:",_W1[data.V['you']])
print("are:",_W1[data.V['are']])
print("is:",_W1[data.V['is']])
print("Find Some Word pairs With high similarity")
一些输出:
Some TEST:
<START>: [-0.32865554 -0.32892272 -0.32865554 -0.32903448 -0.32862166 -0.3286371
-0.32855278 -0.32865041 -0.3287456 0.32913685 0.3284275 0.3293942
-0.32878074 0.32895386 -0.3293958 0.32897744 -0.3285544 0.32878345
-0.32894143 -0.32877466 0.32910895 -0.32860583 -0.32861212 0.32891735
0.3287292 0.32835644 -0.32884252 -0.32896605 -0.32906595 0.32867116
0.3286172 0.3290027 -0.32881436 -0.32877848 0.328693 -0.32874435
0.3287963 -0.3290643 -0.3288444 -0.32919246 -0.32911804 0.3285764
-0.3288233 0.32885128 -0.32878864 -0.32906076 -0.32880557 0.32889417
0.32867458 0.3288717 ]
<END>: [-0.32887468 -0.32819295 -0.32811585 -0.32842168 -0.32838833 -0.32807946
-0.328573 -0.32846293 -0.32863003 0.32853135 0.3285702 0.3289579
-0.32843226 0.32874456 -0.32906574 0.32823783 -0.328379 0.32890162
-0.32847401 -0.32865068 0.32868966 -0.32853377 -0.32895073 0.32866135
0.32829925 0.3286695 -0.3279959 -0.32863376 -0.32822555 0.32869092
0.3287056 0.32847905 -0.328476 -0.3285315 0.3284099 -0.32846484
0.32861754 -0.32874164 -0.32856745 -0.3281496 -0.32804772 0.32813182
-0.32847726 0.32866627 -0.32860965 -0.32843712 -0.32851657 0.32848006
0.3284792 0.32827806]
<UNK>: [-0.33541423 -0.33470714 -0.3370783 -0.33635023 -0.33554107 -0.33692467
-0.3357885 -0.3362881 -0.33724156 0.3363039 0.3367789 0.33465096
-0.33774552 0.334528 -0.33478507 0.3361384 -0.33670798 0.3359792
-0.33699095 -0.3372743 0.33699647 -0.3349662 -0.3352877 0.33607063
0.3360799 0.3374416 -0.33622378 -0.3356342 -0.33574662 0.33515957
0.33587998 0.33657932 -0.3346068 -0.33668125 0.3373789 -0.33578864
0.33600768 -0.3351044 -0.33618957 -0.33537337 -0.33528054 0.33497527
-0.33659405 0.33648187 -0.33672735 -0.3356181 -0.33677348 0.3358079
0.33754358 0.33513647]
you: [ 7.5410731e-05 -2.2068098e-05 1.5926472e-04 -3.7533080e-04
1.5225924e-04 5.7152174e-05 1.1864441e-04 1.8325352e-04
-3.3901614e-04 1.2916526e-04 1.3833319e-05 2.6736190e-04
-4.7140598e-05 2.6220654e-04 3.6094731e-04 -3.7336904e-05
1.6925091e-04 3.0941452e-04 6.1381006e-06 8.4891668e-05
3.2646640e-04 2.9357689e-04 1.1827118e-05 -3.3071122e-04
-6.8303793e-06 1.6745779e-04 -3.6067510e-04 5.1347135e-05
1.4563915e-04 1.7205811e-04 4.1834958e-04 2.2660226e-04
5.4340864e-05 5.7893725e-05 4.7014220e-04 1.5608841e-05
2.5751774e-04 2.9816956e-04 -3.2765078e-04 7.9145197e-05
-1.4246249e-04 1.0391908e-04 -4.8424668e-06 1.1221454e-04
3.8156973e-04 -1.1834640e-04 -4.6865684e-05 2.7329332e-04
-3.1904834e-05 1.3008594e-04]
are: [-0.2812496 -0.2820716 -0.27766886 -0.2796223 -0.28174222 -0.2794273
-0.28108445 -0.28041014 -0.27624518 0.27735004 0.2795439 0.28412956
-0.27596313 0.28378895 -0.28383565 0.27873477 -0.27815878 0.27781972
-0.2785313 -0.27764395 0.27687937 -0.28168035 -0.28165868 0.27915266
0.27918822 0.27295998 -0.28038228 -0.280024 -0.2794214 0.2795479
0.2802325 0.2777838 -0.28295064 -0.27867603 0.27600077 -0.27959383
0.2796351 -0.2815734 -0.27917543 -0.28051388 -0.2809812 0.28227493
-0.2791555 0.28032318 -0.27791157 -0.28050876 -0.2775616 0.28203183
0.27543807 0.280927 ]
is: [-0.32220727 -0.32266894 -0.31880832 -0.32086748 -0.32186344 -0.3214381
-0.32217908 -0.3221054 -0.3179382 0.31926474 0.32116815 0.32492614
-0.31819957 0.32392636 -0.32451728 0.32015812 -0.3204411 0.3186195
-0.3208769 -0.31925762 0.31855124 -0.32200468 -0.32246563 0.3201145
0.31962588 0.31499264 -0.32167593 -0.32168335 -0.32059893 0.3198868
0.3216786 0.3199376 -0.32269898 -0.3208138 0.31806418 -0.32053798
0.32106066 -0.3218284 -0.31995344 -0.32122964 -0.3216572 0.3230505
-0.32118168 0.32176948 -0.31983265 -0.32104513 -0.31971234 0.32368666
0.31785336 0.32170975]
看起来跟我们经验还是吻合的,哈哈哈
本blog的项目:
https://github.com/jmhIcoding/CBOW.git
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/196450.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...