Word2vec用CBOW模型的keras代码详解

2023-10-23 08:59

本文主要是介绍Word2vec用CBOW模型的keras代码详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Word2vec的原理和code在这篇文本处理算法汇总文章里有总结
代码来源: Word2vec用CBOW模型的keras代码

分析目的:
用于理解word2vec模型的输入、输出,及具体用法。

总结:这个模型的原理在于训练2个矩阵,及一个偏置,将一个word输入第一个矩阵emedding_1,这个矩阵将经过one-hot编码的一个及多个中心word,从训练集中出现的总词数(nb_word)维度转换为预先设好的word_size维度的词向量。将第一层的输出与第二个矩阵emedding_2相乘,由于第二个矩阵的第一列为当前word对应的word2id, 其余列为当前word的负采样wordid。 因此,两个矩阵点乘后,矩阵1与矩阵2的第一列相关性最大。

1.首先创建一个简单的文本。

save following words in a './text8_small.txt' file
This is the first document.
This is the second second document.
And the third one.
Is this the first document?

2.导入需要的库文件

import os
import jieba
import random
import numpy as np
from keras.layers import Input, Embedding, Lambda
from keras.models import Model
import keras.backend as K
from collections import Counter

3.定义各种参数 ,word_size = 12是预先设置要转换的词向量维度。window是COBW要取的上下文的词。

word_size = 12 #词向量维度
window = 3 #窗口大小
nb_negative = 2 #随机负采样的样本数
min_count = 1#频数少于min_count的词将会被抛弃,低频词类似于噪声,可以抛弃掉
nb_epoch = 2 #迭代次数

4.用jieba拆分文本,形成句子和word。

def get_corpus(file):words = []     #词库,不去重corpus = []try:with open(file, encoding='gbk') as fr:for line in fr:words+=jieba.lcut(line)     #jieba分词,将句子切分为一个个词,并添加到词库中corpus.append(jieba.lcut(line))except:passreturn words, corpus

我们能够看到拆分后打印出来的语料库,和word字典

words, corpus = get_corpus('./text8_small.txt')
words = dict(Counter(words))
print('corpus',corpus)
print('words',words)
corpus [['This', ' ', 'is', ' ', 'the', ' ', 'first', ' ', 'document', '.', '\n'], ['This', ' ', 'is', ' ', 'the', ' ', 'second', ' ', 'second', ' ', 'document', '.', '\n'], ['And', ' ', 'the', ' ', 'third', ' ', 'one', '.', '\n'], ['Is', ' ', 'this', ' ', 'the', ' ', 'first', ' ', 'document', '?']]
words {'This': 2, ' ': 16, 'is': 2, 'the': 4, 'first': 2, 'document': 3, '.': 3, '\n': 3, 'second': 2, 'And': 1, 'third': 1, 'one': 1, 'Is': 1, 'this': 1, '?': 1}
  1. 形成word2id ,计算词袋中总词数赋值nb_word。
total = sum(words.values()) #总词频
words = {i:j for i,j in words.items() if j >= min_count} #去掉低频词
id2word = {i+2:j for i,j in enumerate(words)} #id到词语的映射,习惯将0设置为PAD,1设置为UNK
id2word[0] = 'PAD'
id2word[1] = 'UNK'
word2id = {j:i for i,j in id2word.items()} #词语到id的映射
nb_word = len(id2word) #总词数
print('id2word',id2word,'word2id',word2id,'nb_word',nb_word)

可以看出sentence通过jieba分词后将每个单词,包括空格PAD, 标点,无法识别UNK。

id2word {2: 'This', 3: ' ', 4: 'is', 5: 'the', 6: 'first', 7: 'document', 8: '.', 9: '\n', 10: 'second', 11: 'And', 12: 'third', 13: 'one', 14: 'Is', 15: 'this', 16: '?', 0: 'PAD', 1: 'UNK'} 
word2id {'This': 2, ' ': 3, 'is': 4, 'the': 5, 'first': 6, 'document': 7, '.': 8, '\n': 9, 'second': 10, 'And': 11, 'third': 12, 'one': 13, 'Is': 14, 'this': 15, '?': 16, 'PAD': 0, 'UNK': 1} 
nb_word 17
  1. 生成数据,保存每个word作为中心词时,COBW在窗口采样的上下文word, 赋值x为,长度为2*window。保存中心词、负采样词,赋值y, 长度为nb_negative+1。 可以想到上下文的词向量求和平均后与中心词形成的词向量相关性最大。
def data_generator(): #训练数据生成器x,y = [],[]for sentence in corpus:sentence = [0]*window + [word2id[w] for w in sentence if w in word2id] + [0]*window#上面这句代码的意思是,因为我们是通过滑窗的方式来获取训练数据的,那么每一句语料的第一个词和最后一个词#如何出现在中心位置呢?答案就是给它padding一下,例如“我/喜欢/足球”,两边分别补窗口大小个pad,得到“pad pad 我 喜欢 足球 pad pad”#那么第一条训练数据的背景词就是['pad', 'pad','喜欢', '足球'],中心词就是'我'for i in range(window, len(sentence)-window):x.append(sentence[i-window: i]+sentence[i+1: window+i+1])y.append([sentence[i]]+get_negtive_sample(sentence[i], nb_word, nb_negative))x,y = np.array(x),np.array(y)z = np.zeros((len(x), nb_negative+1))z[:,0]=1return x,y,z

通过打印可以看出sentence=[0]*window + [word2id[w] for w in sentence if w in word2id] + [0]*window 是将 sentence中每个word进行了word2id的映射,并在句子前后加入window个padding。

sentence ['This', ' ', 'is', ' ', 'the', ' ', 'first', ' ', 'document', '.', '\n']
sentence pad [0, 0, 0, 2, 3, 4, 3, 5, 3, 6, 3, 7, 8, 9, 0, 0, 0]

7.形成训练数据

x,y,z = data_generator() #获取训练数据
print('x,y,z',x,y,z)

现在研究下输入的x,y,z是什么样子的。
当data_generator()循环
i=3的时候,
x = [[0, 0, 0, 3, 4, 3]],
i=4的时候x.append(sentence[i-window: i]+sentence[i+1: window+i+1]),
x =[[0, 0, 0, 3, 4, 3], [0, 0, 2, 4, 3, 5]],
可见当x的长度是corpus中所有的字符43. 转换成array(x).shape=(43, 6),array(y).shape=(43,3)

  1. 创建模型
#苏神对多维向量或者叫张量的操作简直信手拈来,苏神经常使用这个K(keras.backend)对张量进行维度变换、维度提取和张量加减乘除。
#我这个小白看的是晕头转向,得琢磨半天。但是后来我也没有找到合适的方式来替换这个K,只能跟着用。
#第一个输入是周围词
input_words = Input(shape=(window*2,), dtype='int32')
#建立周围词的Embedding层
input_vecs = Embedding(nb_word, word_size, name='word2vec')(input_words)
#CBOW模型,直接将上下文词向量求和
input_vecs_sum = Lambda(lambda x: K.sum(x, axis=1))(input_vecs)
#第二个输入,中心词以及负样本词
samples = Input(shape=(nb_negative+1,), dtype='int32')
#同样的,中心词和负样本词也有一个Emebdding层,其shape为 (?, nb_word, word_size)
softmax_weights = Embedding(nb_word, word_size, name='W')(samples)
softmax_biases = Embedding(nb_word, 1, name='b')(samples)
#将加和得到的词向量与中心词和负样本的词向量分别进行点乘
#注意到使用了K.expand_dims,这是为了将input_vecs_sum的向量推展一维,才能和softmax_weights进行dot
input_vecs_sum_dot_ = Lambda(lambda x: K.batch_dot(x[0], K.expand_dims(x[1],2)))([softmax_weights,input_vecs_sum])
#然后再将input_vecs_sum_dot_与softmax_biases进行相加,相当于 y = wx+b中的b项
#这里又用到了K.reshape,在将向量加和之后,得到的shape是(?, nb_negative+1, 1),需要将其转换为(?, nb_negative+1),才能进行softmax计算nb_negative+1个概率值
add_biases = Lambda(lambda x: K.reshape(x[0]+x[1], shape=(-1, nb_negative+1)))([input_vecs_sum_dot_,softmax_biases])
#这里苏神用了K.softmax,而不是dense(nb_negative+1, activate='softmax')
#这是为什么呢?因为dense是先将上一层的张量先进行全联接,再使用softmax,而向下面这样使用K.softmax,就没有了全联接的过程。
#实验下来,小编尝试使用dense(activate='softmax')训练出来的效果很差。
softmax = Lambda(lambda x: K.softmax(x))(add_biases)
#编译模型
model = Model(inputs=[input_words,samples], outputs=softmax)
#使用categorical_crossentropy多分类交叉熵作损失函数
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()
model.fit([x,y],z, epochs=nb_epoch, batch_size=512)

a. Input
详解:Keras.Layer. Input https://keras.io/zh/layers/core/#input
Input() 用于实例化 Keras 张量。
shape: 一个尺寸元组(整数),不包含批量大小。
例如,shape=(32,) 表明期望的输入是按批次的 32 维向量,这个案例中Input(shape=(window2,), dtype=‘int32’)中的输入array(x)是43 * 6的数组,即2 * windows=6.*
如果 a, b 和 c 都是 Keras 张量,那么以下操作是可行的:
model = Model(input=[a, b], output=c)

b. Embeding https://keras.io/zh/layers/embeddings/

input_vecs = Embedding(nb_word, word_size, name='word2vec')(input_words)

nb_word: input_dim: int > 0。词汇表大小,即,最大整数 index + 1。
word_size: output_dim: int >= 0。词向量的维度。
输入尺寸为 (batch_size, sequence_length) 的 2D 张量。
输出尺寸为 (batch_size, sequence_length, output_dim) 的 3D 张量。

在函数中Embeding 包含了将一维的word进行one-hot转换,转换为nb-word=17维,输出为word_size=12维.
比如输入Input1 (None, 6) 输出word2vec (Embedding)形状为(None, 6, 12), 经过Lambda_1对6个surrounding的词求和后平均,输出形状为(None, 12),

同理Embeding W 输入Input_2 (InputLayer) 形状为 (None, 3) ,输出形状(None, 3, 12).

Lambda_2 是将W与Lambd1相乘,
比如Lambda(lambda x: K.batch_dot(x[0], K.expand_dims(x[1],2)))([softmax_weights,input_vecs_sum])
其中x[0]代表softmax_weights, shape为(None,3,12) , 其中K.expand_dims(x[1],2)代表将input_vecs_sum,从维度(None,12)拓展维度为(None,12,1),然后进行dot相乘, 成为(None,3,1)

K.expand_dims https://keras.io/zh/backend/#expand_dims
keras.backend.expand_dims(x, axis=-1)
x: 张量或变量。
axis: 需要添加新的轴的位置。

Lambda3是加上偏置,Lambda4是softmax, 输入(None,3) 输出(None,3) 。
在这里插入图片描述
9.验证模型

model.save_weights('word2vec.model')
#embedding层的权重永远在模型的第一层
embeddings = model.get_weights()[0]
def most_similar(w):v = embeddings[word2id[w]]sims = np.dot(embeddings, v)sort = sims.argsort()[::-1]sort = sort[sort > 0]return [(id2word[i],sims[i]) for i in sort[:10]]

可以看出用这个训练好的模型,求得输入得相近词,只用到了embeding Weight的第一层get_weights()[0]。

print(model.get_weights()[0].shape)
print(model.get_weights()[1].shape)
print(model.get_weights()[2].shape)
(17, 12)
(17, 12)
(17, 1)

word2id[w]]中的this 的id为15,找到embeddings的第15个元素,经过第一层矩阵,得到对应的长度为12的向量。

print(model.get_weights()[0][15])
[ 0.0499722   0.00947531 -0.04782331 -0.03567474 -0.00890872 -0.020598750.04561229 -0.02593856 -0.01512181  0.01678449 -0.03727285  0.00724424]
word2id {'This': 2, ' ': 3, 'is': 4, 'the': 5, 'first': 6, 'document': 7, '.': 8, '\n': 9, 'second': 10, 'And': 11, 'third': 12, 'one': 13, 'Is': 14, 'this': 15, '?': 16, 'PAD': 0, 'UNK': 1} 

通过让 embeding 矩阵与目标词向量进行点乘得到相似值,sims = np.dot(embeddings, v),然后使用 numpy 的内置函数 argsort 进行排序,从而得到索引,这样就可以得到与词相似度有高到低的一个排序。

import pandas as pd
pd.Series(most_similar(u'this'))
0      (this, 0.011237454)
1         (., 0.005504311)
2      (This, 0.004702386)
3      (UNK, 0.0033156318)
4      (And, 0.0031191725)
5       (Is, 0.0026839643)
6    (first, 0.0013143882)
7       (\n, 0.0011139032)
8       (?, 0.00050036504)
9     (one, -0.0008003423)
dtype: object

对比gensim用法
有相同的功能most_similar 方法

Gensim 官方手册
https://radimrehurek.com/gensim/models/word2vec.html

from gensim.models import word2vec
import logging
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s',level=logging.INFO)
sentences=word2vec.Text8Corpus("./text8")#加载分词语料
model=word2vec.Word2Vec(sentences,size=200)#训练skip-gram模型,默认window=5
print("输出模型",model)
y1=model.similarity("china","japan")
y2=model.most_similar("market",topn=20)#20个最相关的
for word in y2:print(word[0],word[1])
print("*********\n")
y1 := 0.73442537
y2 :
markets 0.7433197498321533
commodity 0.6431580781936646
economy 0.6383275985717773
price 0.6141645908355713
...

这篇关于Word2vec用CBOW模型的keras代码详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/266849

相关文章

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

mac中资源库在哪? macOS资源库文件夹详解

《mac中资源库在哪?macOS资源库文件夹详解》经常使用Mac电脑的用户会发现,找不到Mac电脑的资源库,我们怎么打开资源库并使用呢?下面我们就来看看macOS资源库文件夹详解... 在 MACOS 系统中,「资源库」文件夹是用来存放操作系统和 App 设置的核心位置。虽然平时我们很少直接跟它打交道,但了

关于Maven中pom.xml文件配置详解

《关于Maven中pom.xml文件配置详解》pom.xml是Maven项目的核心配置文件,它描述了项目的结构、依赖关系、构建配置等信息,通过合理配置pom.xml,可以提高项目的可维护性和构建效率... 目录1. POM文件的基本结构1.1 项目基本信息2. 项目属性2.1 引用属性3. 项目依赖4. 构

Rust 数据类型详解

《Rust数据类型详解》本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非... 目录一、标量类型(Scalar Types)1. 整数类型(Integer Types)1.1 整数字

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1