当前位置:   article > 正文

中文词向量:word2vec之skip-gram实现(不使用框架实现词向量模型)_中文词向量模型

中文词向量模型

代码中使用的中文语料,停用词语料,之后的英文语料:

       链接: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模型

      

代码实现

        1.项目结构

         

        2. 项目内容 

skip_gram_en是训练英文词向量的,skip_gram_zh是训练中文词向量的。

enDemoTest和zhDemoTest分别是英文语料和中文语料。由于我的模型太过基础且没有计算优化

所以导致需要非常大的计算量。出现内存爆炸现象。所以这两个语料是我从en.txt和zh.txt随便截取的。这两个很大,我机器算不动。

     

        可以随便找语料,然后在代码里修改参数就行。

 

 根据前面的输出结果,把红框里的数据改一下就行。

这样就不会报错了。最后转为词向量的结果保存在文件中。再用图片显示出来。

所需要的依赖要有numpy,结巴分词,sklearn,matplotlib

完整代码

          

  1. #!/usr/bin/env python
  2. # -#-coding:utf-8 -*-
  3. # author:by ucas vv
  4. # datetime:2021/10/21 13:04:43
  5. # software:PyCharm
  6. """
  7. 1.下载数据or找个实验数据,进行数据预处理,
  8. ①对于中文可能有乱码问题,都使用utf-8加载
  9. ②matplotlib中文变成方框问题,使用下面代码解决
  10. # 用来正常显示中文标签
  11. plt.rcParams['font.sans-serif'] = ['SimHei']
  12. # 用来正常显示负号,否则负号会显示成方块
  13. plt.rcParams['axes.unicode_minus'] = False
  14. 2.将原词汇数据转换为字典映射,求三大参数,即word2index,index2word,word2one-hot
  15. 3.为 skip-gram模型 建立一个扫描器
  16. 4.建立并训练 skip-gram 模型
  17. 5.开始训练模型
  18. 6.结果可视化
  19. """
  20. import numpy as np
  21. import jieba
  22. from matplotlib import pyplot as plt
  23. from sklearn.decomposition import PCA
  24. from tqdm import trange
  25. from tqdm import tqdm
  26. # 加载停用词词表
  27. def load_stop_words():
  28. """
  29. 停用词是指在信息检索中,
  30. 为节省存储空间和提高搜索效率,
  31. 在处理自然语言数据(或文本)之前或之后
  32. 会自动过滤掉某些字或词
  33. """
  34. with open('data/stopwords.txt', "r", encoding="utf-8") as f:
  35. return f.read().split("\n")
  36. # 加载文本,切词
  37. def cut_words():
  38. stop_words = load_stop_words()
  39. with open('data/zhDemoTest.txt', encoding='utf8') as f:
  40. allData = f.readlines()
  41. result = []
  42. for words in allData:
  43. c_words = jieba.lcut(words)
  44. result.append([word for word in c_words if word not in stop_words])
  45. return result
  46. # 用一个集合存储所有的词
  47. wordList = []
  48. # 获得word2index
  49. word_2_index = {}
  50. data = cut_words()
  51. count = 0
  52. for words in data:
  53. for word in words:
  54. if word not in word_2_index.keys():
  55. word_2_index[word] = count
  56. count += 1
  57. wordList.append(word)
  58. index_2_word = {}
  59. # 获得 index2word
  60. for word in word_2_index.keys():
  61. index_2_word[word_2_index[word]] = word
  62. print("word2index len= ", len(word_2_index))
  63. print("word2index= ", word_2_index)
  64. print("index2word len= ", len(index_2_word))
  65. print("index2word=", index_2_word)
  66. print("word len={},wordLsit={}".format(len(wordList), wordList))
  67. # 传入一个词,获得其对应的one-hot向量
  68. def getOneHotWordVec(input_word, word2Index):
  69. """
  70. :param input_word: 输入的一个词,比如输入一个“政界”
  71. :param word2Index: 字面意思
  72. :return: 返回输入词所对应的ont-hot向量
  73. """
  74. oneHot = np.zeros(shape=(len(word2Index), 1))
  75. oneHot[word2Index[input_word]] = 1
  76. return oneHot
  77. # word = '目前'
  78. # print(repr(word) + "的onehot=", getOneHotWordVec(word, word_2_index))
  79. # 返回一个词表
  80. def getWindows(input_word_list, window_size):
  81. """
  82. :param input_word_list: 所有词的集合
  83. :param window_size: 窗口大小表示,当前词与其他词距离为size的词
  84. 比如
  85. words = [
  86. '目前', '粮食', '出现', '阶段性',
  87. '过剩', '恰好', '换', '森林', '草地',
  88. '再造', '西部', '秀美', '山川', '\n'
  89. ]
  90. 令window_size = 3
  91. 则会返回Dataset= [('目前', '粮食'), ('目前', '出现'),
  92. ('目前', '阶段性'), ('粮食', '目前'), ('粮食', '出现'),
  93. ('粮食', '阶段性'), ('粮食', '过剩'), ('出现', '目前'),
  94. ('出现', '粮食'), ('出现', '阶段性'), ('出现', '过剩'),
  95. ('出现', '恰好'), ('阶段性', '目前'), ('阶段性', '粮食'),
  96. ('阶段性', '出现'), ('阶段性', '过剩'), ('阶段性', '恰好'),
  97. ('阶段性', '换'), ('过剩', '粮食'), ('过剩', '出现'),
  98. ('过剩', '阶段性'), ('过剩', '恰好'), ('过剩', '换'),
  99. ('过剩', '森林'), ('恰好', '出现'), ('恰好', '阶段性'),
  100. ('恰好', '过剩'), ('恰好', '换'), ('恰好', '森林'),
  101. ('恰好', '草地'), ('换', '阶段性'), ('换', '过剩'),
  102. ('换', '恰好'), ('换', '森林'), ('换', '草地'),
  103. ('换', '再造'), ('森林', '过剩'), ('森林', '恰好'),
  104. ('森林', '换'), ('森林', '草地'), ('森林', '再造'),
  105. ('森林', '西部'), ('草地', '恰好'), ('草地', '换'),
  106. ('草地', '森林'), ('草地', '再造'), ('草地', '西部'),
  107. ('草地', '秀美'), ('再造', '换'), ('再造', '森林'),
  108. ('再造', '草地'), ('再造', '西部'), ('再造', '秀美'),
  109. ('再造', '山川'), ('西部', '森林'), ('西部', '草地'),
  110. ('西部', '再造'), ('西部', '秀美'), ('西部', '山川'),
  111. ('西部', '\n'), ('秀美', '草地'), ('秀美', '再造'),
  112. ('秀美', '西部'), ('秀美', '山川'), ('秀美', '\n'),
  113. ('山川', '再造'), ('山川', '西部'), ('山川', '秀美'),
  114. ('山川', '\n'), ('\n', '西部'), ('\n', '秀美'),
  115. ('\n', '山川')
  116. ]
  117. 这个距离不是远大越好,也不是越小越好,越大的话包括的范围太小了,
  118. 可能没有什么关联的词也都一起训练。范围太小的话可能导致中心词与
  119. 其他词的联系不足
  120. :return: 词表集合
  121. """
  122. Dataset = []
  123. # 获取
  124. length = len(input_word_list)
  125. for i in range(length):
  126. # 取前后n个单词
  127. for j in range(i - window_size, i + window_size + 1, 1):
  128. # print("j=", j)
  129. if j < 0 or j > (length - 1) or j == i:
  130. continue
  131. Dataset.append((input_word_list[i], input_word_list[j]))
  132. return Dataset
  133. # 生成训练集
  134. def getTrain(input_word_list, window_size, word2index):
  135. """
  136. :param input_word_list: 所有词的集合,一维数组,里面存着所有词,和上面的wordList一样
  137. :param window_size: 与中心词相关联的几个词,比如"北京"这个词,他们的前window_size个词都是相关联的
  138. :param word2index: 词与索引的字典
  139. :return:返回训练集合x,y
  140. """
  141. X_train, y_train = [], []
  142. Dataset = getWindows(input_word_list, window_size)
  143. # print("Dataset=", Dataset)
  144. batch_size = 100
  145. for i in trange(1, 100000, batch_size):
  146. # 每个中心词和上下文词都在当前窗口内
  147. for centre_word, context_word in Dataset[i: i + batch_size - 1]:
  148. # 拿到x的one-hot向量
  149. X_train.append(getOneHotWordVec(centre_word, word2index))
  150. y_train.append(getOneHotWordVec(context_word, word2index))
  151. # print("X_train", X_train)
  152. # print("y_train", y_train)
  153. return X_train, y_train
  154. X_train, y_train = getTrain(wordList, 3, word_2_index)
  155. print("X_train len=", len(X_train))
  156. print("y_train len=", len(y_train))
  157. X_train = np.array(X_train)
  158. print("x转换成array完成")
  159. y_train = np.array(y_train)
  160. print("y转换成array完成")
  161. print("X_train array shape=", X_train.shape)
  162. print("y_train array shape=", y_train.shape)
  163. X_train = X_train.reshape(6392, 37956)
  164. print("reshape之后X_train.shape=", X_train.shape)
  165. y_train = y_train.reshape(6392, 37956)
  166. print("reshape之后y_train.shape=", y_train.shape)
  167. print("X_train=", X_train)
  168. print("y_train=", y_train)
  169. # 初始化权重矩阵,有个两个矩阵,分别是VxD 和 DxV,为什么这么定义,参考论文的原理
  170. def weightInit(dimension, pDimension):
  171. # 第一层矩阵初始化
  172. W1 = np.random.randn(pDimension, dimension)
  173. # 第一层偏置初始化
  174. b1 = np.random.randn(pDimension, 1)
  175. # 第二层矩阵初始化,维度和是第一个矩阵的转置
  176. W2 = np.random.randn(dimension, pDimension)
  177. # 第二层偏置初始化
  178. b2 = np.random.randn(dimension, 1)
  179. return W1, b1, W2, b2
  180. # relu激活函数
  181. def relu(z):
  182. return np.maximum(0, z)
  183. # softmax函数
  184. def softmax(z):
  185. ex = np.exp(z)
  186. return ex / np.sum(ex, axis=0)
  187. # 前向传播
  188. def forward(x, W1, b1, W2, b2):
  189. # 第一层前向传播即 权重与输入的x的乘积加上偏置
  190. Z1 = np.dot(W1, x) + b1
  191. # 添加一个rule函数
  192. Z1 = relu(Z1)
  193. # 第二层前向传播即 上一层的输出作为当前层的输入与当前权重的乘积,再加上第二层的偏置值
  194. Z2 = np.dot(W2, Z1) + b2
  195. # 将第二层的输出值经过softmax函数,得到概率值
  196. ypred = softmax(Z2)
  197. return Z1, Z2, ypred
  198. # 误差计算
  199. def errorCalculation(y, ypred, m):
  200. error = -(np.sum(np.multiply(y, np.log(ypred)))) / m
  201. return error
  202. # 反向传播
  203. def backProp(W1, b1, W2, b2, Z1, Z2, y, ypred, x):
  204. dW1 = np.dot(relu(np.dot(W2.T, ypred - y)), x.T)
  205. db1 = relu(np.dot(W2.T, ypred - y))
  206. dW2 = np.dot(ypred - y, Z1.T)
  207. db2 = ypred - y
  208. return dW1, db1, dW2, db2
  209. # 模型训练
  210. def model(x, y, epoches=10, learning_rate=0.00001):
  211. # x的行列数
  212. dimension = x.shape[0]
  213. m = x.shape[1]
  214. # 生成词向量维度,300表示生成的词向量用300行数字表示这个词
  215. pDimension = 300
  216. W1, b1, W2, b2 = weightInit(dimension, pDimension)
  217. error = []
  218. for i in tqdm(range(epoches)):
  219. Z1, Z2, ypred = forward(x, W1, b1, W2, b2)
  220. error.append(errorCalculation(y, ypred, m))
  221. dW1, db1, dW2, db2 = backProp(W1, b1, W2, b2, Z1, Z2, y, ypred, x)
  222. # 更新权重和偏置
  223. W1 = W1 - learning_rate * dW1
  224. b1 = b1 - learning_rate * db1
  225. W2 = W2 - learning_rate * dW2
  226. b2 = b2 - learning_rate * db2
  227. print("error=", error)
  228. return ypred, error, W1, W2
  229. # 传入训练集和epoch次数,以及学习率
  230. ypred, error, W1, W2 = model(X_train, y_train, 10, 0.00001)
  231. W = np.add(W1, W2.T) / 2
  232. print("W=", W)
  233. # 生成词嵌入字典,即{单词1:词向量1,单词2:词向量2...}的格式
  234. word_2_vec = {}
  235. for word in word_2_index.keys():
  236. # 词向量矩阵中某个词的索引所对应的那一列即为所该词的词向量
  237. word_2_vec[word] = W[:, word_2_index[word]]
  238. print("word2vec=", word_2_vec)
  239. pca = PCA(n_components=2)
  240. principalComponents = pca.fit_transform(W.T)
  241. # 降维后在生成一个词嵌入字典,即即{单词1:(维度一,维度二),单词2:(维度一,维度二)...}的格式
  242. word2ReduceDimensionVec = {}
  243. for word in word_2_index.keys():
  244. word2ReduceDimensionVec[word] = principalComponents[word_2_index[word], :]
  245. # 将生成的字典写入到文件中,字符集要设定utf8,不然中文乱码
  246. with open("zh_vector.txt", 'w', encoding='utf-8') as f:
  247. for key in word_2_index.keys():
  248. f.write('\n')
  249. f.writelines('"' + str(key) + '":' + str(word_2_vec[key]))
  250. f.write('\n')
  251. # 将词向量可视化
  252. plt.figure(figsize=(20, 20))
  253. # 只画出1000个,太多显示效果很差
  254. count = 0
  255. for word, wordvec in word2ReduceDimensionVec.items():
  256. if count < 1000:
  257. plt.scatter(wordvec[0], wordvec[1])
  258. plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
  259. plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号,否则负号会显示成方块
  260. plt.annotate(word, (wordvec[0], wordvec[1]))
  261. count += 1
  262. plt.show()

  最后的画图结果:

               

输出的文件:

         

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/607214
推荐阅读
相关标签
  

闽ICP备14008679号