代码中使用的中文语料,停用词语料,之后的英文语料:
链接:https://pan.baidu.com/s/1XshI0_zRu9NzBSiGXi8U7A
提取码:csg6
在自然语言处理任务中,首先需要考虑词如何在计算机中表示。通常,有两种表示方式:one-hot representation和distribution representation。
1. 离散表示(one-hot representation)
2. 分布式表示(distribution representation)
两者区别和关于NLP的基础可以参考其他博客和论文
论文:
1.A neural probabilistic language model
2.Efficient Estimation of Word Representations in Vector Space
3.Recurrent neural network based language model
博客:
1.图解Word2vec,读这一篇就够了 重点推荐
2.什么是词向量?(NPL入门)_mawenqi0729的博客-CSDN博客_词向量
3.[NLP] 秒懂词向量Word2vec的本质 - 知乎
4.【word2vec】算法原理 公式推导_夏目的博客-CSDN博客_word2vec公式
使用word2vec模型学习的单词的向量表示已经被证明能够携带语义信息,且在各种NLP任务中都是有用的。
本文实现的是skip-gram模型
skip_gram_en是训练英文词向量的,skip_gram_zh是训练中文词向量的。
enDemoTest和zhDemoTest分别是英文语料和中文语料。由于我的模型太过基础且没有计算优化
所以导致需要非常大的计算量。出现内存爆炸现象。所以这两个语料是我从en.txt和zh.txt随便截取的。这两个很大,我机器算不动。
可以随便找语料,然后在代码里修改参数就行。
根据前面的输出结果,把红框里的数据改一下就行。
这样就不会报错了。最后转为词向量的结果保存在文件中。再用图片显示出来。
所需要的依赖要有numpy,结巴分词,sklearn,matplotlib
#!/usr/bin/env python
# -#-coding:utf-8 -*-
# author:by ucas iie 魏兴源
# datetime:2021/10/21 13:04:43
# software:PyCharm
"""
1.下载数据or找个实验数据,进行数据预处理,
①对于中文可能有乱码问题,都使用utf-8加载
②matplotlib中文变成方框问题,使用下面代码解决
# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']
# 用来正常显示负号,否则负号会显示成方块
plt.rcParams['axes.unicode_minus'] = False
2.将原词汇数据转换为字典映射,求三大参数,即word2index,index2word,word2one-hot
3.为 skip-gram模型 建立一个扫描器
4.建立并训练 skip-gram 模型
5.开始训练模型
6.结果可视化
"""
import numpy as np
import jieba
from matplotlib import pyplot as plt
from sklearn.decomposition import PCA
from tqdm import trange
from tqdm import tqdm
# 加载停用词词表
def load_stop_words():
"""
停用词是指在信息检索中,
为节省存储空间和提高搜索效率,
在处理自然语言数据(或文本)之前或之后
会自动过滤掉某些字或词
"""
with open('data/stopwords.txt', "r", encoding="utf-8") as f:
return f.read().split("\n")
# 加载文本,切词
def cut_words():
stop_words = load_stop_words()
with open('data/zhDemoTest.txt', encoding='utf8') as f:
allData = f.readlines()
result = []
for words in allData:
c_words = jieba.lcut(words)
result.append([word for word in c_words if word not in stop_words])
return result
# 用一个集合存储所有的词
wordList = []
# 获得word2index
word_2_index = {}
data = cut_words()
count = 0
for words in data:
for word in words:
if word not in word_2_index.keys():
word_2_index[word] = count
count += 1
wordList.append(word)
index_2_word = {}
# 获得 index2word
for word in word_2_index.keys():
index_2_word[word_2_index[word]] = word
print("word2index len= ", len(word_2_index))
print("word2index= ", word_2_index)
print("index2word len= ", len(index_2_word))
print("index2word=", index_2_word)
print("word len={},wordLsit={}".format(len(wordList), wordList))
# 传入一个词,获得其对应的one-hot向量
def getOneHotWordVec(input_word, word2Index):
"""
:param input_word: 输入的一个词,比如输入一个“政界”
:param word2Index: 字面意思
:return: 返回输入词所对应的ont-hot向量
"""
oneHot = np.zeros(shape=(len(word2Index), 1))
oneHot[word2Index[input_word]] = 1
return oneHot
# word = '目前'
# print(repr(word) + "的onehot=", getOneHotWordVec(word, word_2_index))
# 返回一个词表
def getWindows(input_word_list, window_size):
"""
:param input_word_list: 所有词的集合
:param window_size: 窗口大小表示,当前词与其他词距离为size的词
比如
words = [
'目前', '粮食', '出现', '阶段性',
'过剩', '恰好', '换', '森林', '草地',
'再造', '西部', '秀美', '山川', '\n'
]
令window_size = 3
则会返回Dataset= [('目前', '粮食'), ('目前', '出现'),
('目前', '阶段性'), ('粮食', '目前'), ('粮食', '出现'),
('粮食', '阶段性'), ('粮食', '过剩'), ('出现', '目前'),
('出现', '粮食'), ('出现', '阶段性'), ('出现', '过剩'),
('出现', '恰好'), ('阶段性', '目前'), ('阶段性', '粮食'),
('阶段性', '出现'), ('阶段性', '过剩'), ('阶段性', '恰好'),
('阶段性', '换'), ('过剩', '粮食'), ('过剩', '出现'),
('过剩', '阶段性'), ('过剩', '恰好'), ('过剩', '换'),
('过剩', '森林'), ('恰好', '出现'), ('恰好', '阶段性'),
('恰好', '过剩'), ('恰好', '换'), ('恰好', '森林'),
('恰好', '草地'), ('换', '阶段性'), ('换', '过剩'),
('换', '恰好'), ('换', '森林'), ('换', '草地'),
('换', '再造'), ('森林', '过剩'), ('森林', '恰好'),
('森林', '换'), ('森林', '草地'), ('森林', '再造'),
('森林', '西部'), ('草地', '恰好'), ('草地', '换'),
('草地', '森林'), ('草地', '再造'), ('草地', '西部'),
('草地', '秀美'), ('再造', '换'), ('再造', '森林'),
('再造', '草地'), ('再造', '西部'), ('再造', '秀美'),
('再造', '山川'), ('西部', '森林'), ('西部', '草地'),
('西部', '再造'), ('西部', '秀美'), ('西部', '山川'),
('西部', '\n'), ('秀美', '草地'), ('秀美', '再造'),
('秀美', '西部'), ('秀美', '山川'), ('秀美', '\n'),
('山川', '再造'), ('山川', '西部'), ('山川', '秀美'),
('山川', '\n'), ('\n', '西部'), ('\n', '秀美'),
('\n', '山川')
]
这个距离不是远大越好,也不是越小越好,越大的话包括的范围太小了,
可能没有什么关联的词也都一起训练。范围太小的话可能导致中心词与
其他词的联系不足
:return: 词表集合
"""
Dataset = []
# 获取
length = len(input_word_list)
for i in range(length):
# 取前后n个单词
for j in range(i - window_size, i + window_size + 1, 1):
# print("j=", j)
if j < 0 or j > (length - 1) or j == i:
continue
Dataset.append((input_word_list[i], input_word_list[j]))
return Dataset
# 生成训练集
def getTrain(input_word_list, window_size, word2index):
"""
:param input_word_list: 所有词的集合,一维数组,里面存着所有词,和上面的wordList一样
:param window_size: 与中心词相关联的几个词,比如"北京"这个词,他们的前window_size个词都是相关联的
:param word2index: 词与索引的字典
:return:返回训练集合x,y
"""
X_train, y_train = [], []
Dataset = getWindows(input_word_list, window_size)
# print("Dataset=", Dataset)
batch_size = 100
for i in trange(1, 100000, batch_size):
# 每个中心词和上下文词都在当前窗口内
for centre_word, context_word in Dataset[i: i + batch_size - 1]:
# 拿到x的one-hot向量
X_train.append(getOneHotWordVec(centre_word, word2index))
y_train.append(getOneHotWordVec(context_word, word2index))
# print("X_train", X_train)
# print("y_train", y_train)
return X_train, y_train
X_train, y_train = getTrain(wordList, 3, word_2_index)
print("X_train len=", len(X_train))
print("y_train len=", len(y_train))
X_train = np.array(X_train)
print("x转换成array完成")
y_train = np.array(y_train)
print("y转换成array完成")
print("X_train array shape=", X_train.shape)
print("y_train array shape=", y_train.shape)
X_train = X_train.reshape(6392, 37956)
print("reshape之后X_train.shape=", X_train.shape)
y_train = y_train.reshape(6392, 37956)
print("reshape之后y_train.shape=", y_train.shape)
print("X_train=", X_train)
print("y_train=", y_train)
# 初始化权重矩阵,有个两个矩阵,分别是VxD 和 DxV,为什么这么定义,参考论文的原理
def weightInit(dimension, pDimension):
# 第一层矩阵初始化
W1 = np.random.randn(pDimension, dimension)
# 第一层偏置初始化
b1 = np.random.randn(pDimension, 1)
# 第二层矩阵初始化,维度和是第一个矩阵的转置
W2 = np.random.randn(dimension, pDimension)
# 第二层偏置初始化
b2 = np.random.randn(dimension, 1)
return W1, b1, W2, b2
# relu激活函数
def relu(z):
return np.maximum(0, z)
# softmax函数
def softmax(z):
ex = np.exp(z)
return ex / np.sum(ex, axis=0)
# 前向传播
def forward(x, W1, b1, W2, b2):
# 第一层前向传播即 权重与输入的x的乘积加上偏置
Z1 = np.dot(W1, x) + b1
# 添加一个rule函数
Z1 = relu(Z1)
# 第二层前向传播即 上一层的输出作为当前层的输入与当前权重的乘积,再加上第二层的偏置值
Z2 = np.dot(W2, Z1) + b2
# 将第二层的输出值经过softmax函数,得到概率值
ypred = softmax(Z2)
return Z1, Z2, ypred
# 误差计算
def errorCalculation(y, ypred, m):
error = -(np.sum(np.multiply(y, np.log(ypred)))) / m
return error
# 反向传播
def backProp(W1, b1, W2, b2, Z1, Z2, y, ypred, x):
dW1 = np.dot(relu(np.dot(W2.T, ypred - y)), x.T)
db1 = relu(np.dot(W2.T, ypred - y))
dW2 = np.dot(ypred - y, Z1.T)
db2 = ypred - y
return dW1, db1, dW2, db2
# 模型训练
def model(x, y, epoches=10, learning_rate=0.00001):
# x的行列数
dimension = x.shape[0]
m = x.shape[1]
# 生成词向量维度,300表示生成的词向量用300行数字表示这个词
pDimension = 300
W1, b1, W2, b2 = weightInit(dimension, pDimension)
error = []
for i in tqdm(range(epoches)):
Z1, Z2, ypred = forward(x, W1, b1, W2, b2)
error.append(errorCalculation(y, ypred, m))
dW1, db1, dW2, db2 = backProp(W1, b1, W2, b2, Z1, Z2, y, ypred, x)
# 更新权重和偏置
W1 = W1 - learning_rate * dW1
b1 = b1 - learning_rate * db1
W2 = W2 - learning_rate * dW2
b2 = b2 - learning_rate * db2
print("error=", error)
return ypred, error, W1, W2
# 传入训练集和epoch次数,以及学习率
ypred, error, W1, W2 = model(X_train, y_train, 10, 0.00001)
W = np.add(W1, W2.T) / 2
print("W=", W)
# 生成词嵌入字典,即{单词1:词向量1,单词2:词向量2...}的格式
word_2_vec = {}
for word in word_2_index.keys():
# 词向量矩阵中某个词的索引所对应的那一列即为所该词的词向量
word_2_vec[word] = W[:, word_2_index[word]]
print("word2vec=", word_2_vec)
pca = PCA(n_components=2)
principalComponents = pca.fit_transform(W.T)
# 降维后在生成一个词嵌入字典,即即{单词1:(维度一,维度二),单词2:(维度一,维度二)...}的格式
word2ReduceDimensionVec = {}
for word in word_2_index.keys():
word2ReduceDimensionVec[word] = principalComponents[word_2_index[word], :]
# 将生成的字典写入到文件中,字符集要设定utf8,不然中文乱码
with open("zh_vector.txt", 'w', encoding='utf-8') as f:
for key in word_2_index.keys():
f.write('\n')
f.writelines('"' + str(key) + '":' + str(word_2_vec[key]))
f.write('\n')
# 将词向量可视化
plt.figure(figsize=(20, 20))
# 只画出1000个,太多显示效果很差
count = 0
for word, wordvec in word2ReduceDimensionVec.items():
if count < 1000:
plt.scatter(wordvec[0], wordvec[1])
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号,否则负号会显示成方块
plt.annotate(word, (wordvec[0], wordvec[1]))
count += 1
plt.show()
最后的画图结果:
输出的文件: