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

相关文章

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注