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

相关文章

jupyter代码块没有运行图标的解决方案

《jupyter代码块没有运行图标的解决方案》:本文主要介绍jupyter代码块没有运行图标的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录jupyter代码块没有运行图标的解决1.找到Jupyter notebook的系统配置文件2.这时候一般会搜索到

Redis实现延迟任务的三种方法详解

《Redis实现延迟任务的三种方法详解》延迟任务(DelayedTask)是指在未来的某个时间点,执行相应的任务,本文为大家整理了三种常见的实现方法,感兴趣的小伙伴可以参考一下... 目录1.前言2.Redis如何实现延迟任务3.代码实现3.1. 过期键通知事件实现3.2. 使用ZSet实现延迟任务3.3

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Python Faker库基本用法详解

《PythonFaker库基本用法详解》Faker是一个非常强大的库,适用于生成各种类型的伪随机数据,可以帮助开发者在测试、数据生成、或其他需要随机数据的场景中提高效率,本文给大家介绍PythonF... 目录安装基本用法主要功能示例代码语言和地区生成多条假数据自定义字段小结Faker 是一个 python

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Java Predicate接口定义详解

《JavaPredicate接口定义详解》Predicate是Java中的一个函数式接口,它代表一个判断逻辑,接收一个输入参数,返回一个布尔值,:本文主要介绍JavaPredicate接口的定义... 目录Java Predicate接口Java lamda表达式 Predicate<T>、BiFuncti

详解如何通过Python批量转换图片为PDF

《详解如何通过Python批量转换图片为PDF》:本文主要介绍如何基于Python+Tkinter开发的图片批量转PDF工具,可以支持批量添加图片,拖拽等操作,感兴趣的小伙伴可以参考一下... 目录1. 概述2. 功能亮点2.1 主要功能2.2 界面设计3. 使用指南3.1 运行环境3.2 使用步骤4. 核

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

详解nginx 中location和 proxy_pass的匹配规则

《详解nginx中location和proxy_pass的匹配规则》location是Nginx中用来匹配客户端请求URI的指令,决定如何处理特定路径的请求,它定义了请求的路由规则,后续的配置(如... 目录location 的作用语法示例:location /www.chinasem.cntestproxy