当前位置:   article > 正文

【Pytorch神经网络实战案例】30 jieba库分词+训练中文词向量_jieba如何训练语言词义

jieba如何训练语言词义

1 安装jieba

1.1 安装

pip install jieba

1.2 测试

  1. import jieba
  2. seg_list = jieba.cut("谭家和谭家和")
  3. for i in seg_list:
  4. printf(i);

1.3 词向量

NLP中,一般都会将该任务中涉及的词训练成词向量,然后让每个词以词向量的形式型的输入,进行一些指定任务的训练。对于一个完整的训练任务,词向量的练大多发生在预训练环节。

除了从头训练词向量,还可以使用已经训练好的词向量。尤其在样本不充足的情况下,可以增加模型的泛化性。

通用的词嵌入模型常以key-vaue的格式保存,即把词对应的向量一一列出来。这种方式具有更好的通用性,它可以不依赖任何框架。

2 代码实现:训练中文词向量

2.1 样本预处理并生成字典---skip_gram.py(第一部分)

  1. # 1.1 样本预处理并生成字典
  2. # 使用get_ch lable函数将所有文字读入raining_data,然后在fenci函数里使用jeba库对ci放入buld_dataset里并生成指定长度(350)的字典。
  3. # 指定设备
  4. device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  5. print(device)
  6. training_file = './data/一大段文本.txt'
  7. # 中文字
  8. def get_ch_lable(txt_file): # 获取文件内的文本信息
  9. labels= ""
  10. with open(txt_file, 'rb') as f:
  11. for label in f:
  12. labels =labels+label.decode('gb2312')
  13. return labels
  14. # 分词
  15. def fenci(training_data):
  16. seg_list = jieba.cut(training_data) # 默认是精确模式
  17. training_ci = " ".join(seg_list)
  18. training_ci = training_ci.split()
  19. # 以空格将字符串分开
  20. training_ci = np.array(training_ci)
  21. training_ci = np.reshape(training_ci, [-1, ])
  22. return training_ci
  23. # build dataset()函数实现了对样本文字的处理。
  24. # 在该函数中,对样本文字的词频进行统计,将照频次由高到低排序。同时,将排序后的列表中第0个索引设置成unknown(用“UNK”表示),
  25. # 这个unknown字符可用于对词频低的词语进行填充。如果设置字典为350,则频次排序在350的词都会被当作unknown字符进行处理。
  26. def build_dataset(words,n_words): #
  27. count = [['UNK', -1]]
  28. count.extend(collections.Counter(words).most_common(n_words - 1))
  29. dictionary = dict()
  30. for word, _ in count:
  31. dictionary[word] = len(dictionary)
  32. data = list()
  33. unk_count = 0
  34. for word in words:
  35. if word in dictionary:
  36. index = dictionary[word]
  37. else:
  38. index = 0 # dictionary['UNK']
  39. unk_count += 1
  40. data.append(index)
  41. count[0][1] = unk_count
  42. reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
  43. return data, count, dictionary, reversed_dictionary
  44. training_data = get_ch_lable(training_file)
  45. print("总字数",len(training_data)) # 1567
  46. training_ci =fenci(training_data)
  47. print("总词数",len(training_ci)) #
  48. training_label, count, dictionary, words = build_dataset(training_ci, 350)
  49. #计算词频
  50. word_count = np.array([freq for _,freq in count], dtype=np.float32)
  51. word_freq = word_count / np.sum(word_count)#计算每个词的词频
  52. word_freq = word_freq ** (3. / 4.)#词频变换
  53. words_size = len(dictionary)
  54. print("字典词数",words_size) # 350
  55. print('Sample data', training_label[:10], [words[i] for i in training_label[:10]]) # # 显示的是样本文字里前10个词的词频。

2.2 代码实现:按照skip-Gram模型的规则制作数据集---skip_gram.py(第二部分)

  1. # 1.2 按照skip-Gram模型的规则制作数据集
  2. # 使用Dataset与Dataloader接口制作数据集。
  3. # 在自定义Dataset类中,按照Skip-Gram模型的规则对样本及其对应的标签进行组合。
  4. # 每批次取12个,每个词向量的维度为128,中心词前后的取词个数为3,负采样的个数为64。具体代码如下。
  5. C = 3 # 定义中心词前后的取词个数
  6. num_sampled = 64 # 负采样个数
  7. BATCH_SIZE = 12
  8. EMBEDDING_SIZE = 128
  9. class SkipGramDataset(Dataset): # 自定义数据集
  10. # 样本中的每个词都被当作一个中心词,对于任意一个样本中心词,都会生成两组标签:正向标签与负向标签。
  11. # 正向标签来自中心词的前后位置,负向标签主要来自词频的多项式来样。
  12. def __init__(self,training_label,word_to_idx,idx_to_word,word_freqs):
  13. super(SkipGramDataset, self).__init__()
  14. self.text_encode = torch.Tensor(training_label).long()
  15. self.word_to_idx = word_to_idx
  16. self.idx_to_word = idx_to_word
  17. self.word_freqs = torch.Tensor(word_freqs)
  18. def __len__(self):
  19. return len(self.text_encode)
  20. def __getitem__(self, idx):
  21. idx = min(max(idx,C),len(self.text_encode)-2-C) # 防止越界:对组合标签过程中的越界问题做了处理,使提取样本的索引为3~(总长度-5)。
  22. center_word = self.text_encode[idx]
  23. pos_indices = list(range(idx-C,idx)) + list(range(idx+1,idx+1+C))
  24. pos_words = self.text_encode[pos_indices]
  25. # ---start---对负向标签进行采样。在使用多项式分布采样之后,还要从中去掉与正向标签相同的索引。其中np.setdiff1d()函数用于对两个数组做差集。
  26. # 多项式分布采样,取出指定个数的高频词
  27. neg_words = torch.multinomial(self.word_freqs, num_sampled + 2 * C, False) # True)
  28. # 去掉正向标签
  29. neg_words = torch.Tensor(np.setdiff1d(neg_words.numpy(), pos_words.numpy())[:num_sampled]).long()
  30. # ---end---对负向标签进行采样。在使用多项式分布采样之后,还要从中去掉与正向标签相同的索引。其中np.setdiff1d()函数用于对两个数组做差集。
  31. return center_word, pos_words,neg_words
  32. print("制作数据集...")
  33. train_dataset = SkipGramDataset(training_label,dictionary,words,word_freq)
  34. dataloader = torch.utils.data.DataLoader(train_dataset,batch_size=BATCH_SIZE,drop_last=True,shuffle=True)
  35. sample = iter(dataloader) #将数据集转化成迭代器
  36. center_word, pos_words, neg_words = sample.next() #从迭代器中取出一批次样本
  37. print(center_word[0],words[np.long(center_word[0])],[words[i] for i in pos_words[0].numpy()])

2.3 搭建模型并进行训练

2.3.1 余弦定理

2.3.2 代码片段

 2.3.3 词嵌入夹角余弦结构

 2.3.4 代码实现:搭建模型并进行训练---skip_gram.py(第三部分)

  1. # 1.3 搭建模型并进行训练
  2. # 首先定义一个词嵌入层用手训练,将输入的样本和标签分别用词嵌入层进行转化。
  3. # 在训练过程中,将输入与标签的词嵌入当作两个向量,将二者的矩阵相乘当作两个向量间的夹角余弦值,并用该夹角余弦值作为被优化的损失函数。
  4. # 在训练模型时,定义了验证模型的相关参数,其中valid_size表示在0~words_size/2中随机取不能重复的16个字来验证模型。
  5. class Model(nn.Module):
  6. def __init__(self,vocab_size,embed_size):
  7. super(Model, self).__init__()
  8. self.vocab_size = vocab_size
  9. self.embed_size = embed_size
  10. initrange = 0.5 / self.embed_size
  11. self.in_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False)
  12. self.in_embed.weight.data.uniform_(-initrange, initrange)
  13. def forward(self,input_lables,pos_labels,neg_labels):
  14. # LogSigmoid激活函数,该激活函数的值域是(-inf,0](inf是无穷值的意思),即当输入值越大,输出值越接近于0。
  15. # 如果将输入样本的词嵌入和目标标签的词嵌入分别当作两个向量,则可以用这两个向量间的夹角余弦值来当作二者的相似度。
  16. # 为了规范计算,先通过LogSigmoid激活函数中的Sigmoid函数将参与运算的向量控制为0~1,再从正 / 负标签两个方向进行相似度计算:
  17. # ① 对于正向标签,可以直接进行计算:
  18. # ② 对于负向标签,可以先用1减去输入样本的词嵌入,得到输入样本对应的负向量,再将该结果与负向标签的词向量一起计算相似度。
  19. # 根据Sigmoid函数的对称特性1 - Sigmoid(x) = Sigmoid(-x),可以直接对输入样本词向量的符号取负来实现向量的转化。
  20. input_embedding = self.in_embed(input_lables)
  21. pos_embedding = self.in_embed(pos_labels)
  22. neg_embedding = self.in_embed(neg_labels)
  23. # 计算输入与正向标签间的夹角余弦值:用bmm函数完成两个带批次数据的矩阵相乘【bmm函数处理的必须是批次数据,即形状为{b,m,n]与[b,n,m]矩阵相乘;】
  24. log_pos = torch.bmm(pos_embedding, input_embedding.unsqueeze(2)).squeeze()
  25. # 计算输入与负向标签间的夹角余弦值:用bmm函数完成两个带批次数据的矩阵相乘【bmm函数处理的必须是批次数据,即形状为{b,m,n]与[b,n,m]矩阵相乘;】
  26. log_neg = torch.bmm(neg_embedding, -input_embedding.unsqueeze(2)).squeeze() # 在计算输入与负向标签间的夹角余弦值时,使用样本词嵌入的赋值。这样做与使用LogSigmoid激活函数有关。
  27. # 使用LogSigmoid激活函数
  28. log_pos = F.logsigmoid(log_pos).sum(1)
  29. log_neg = F.logsigmoid(log_neg).sum(1)
  30. loss = log_pos + log_neg
  31. return -loss # 对最终的损失值取负,将损失函数的值域由(-inf,0]变为(0,inf]。这种变换有利于使用优化器在迭代训练中进行优化(因为优化器只能使损失值沿着最小化的方向优化)。
  32. # 实例化模型
  33. model = Model(words_size,EMBEDDING_SIZE).to(device)
  34. model.eval()
  35. # 定义测试样本
  36. valid_size = 16
  37. valid_window = words_size/2 # 取样数据的分布范围
  38. valid_examples = np.random.choice(int(valid_window),valid_size,replace=True) #0- words_size/2,中的数取16个。不能重复。
  39. optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
  40. NUM_EPOCHS = 200
  41. for e in range(NUM_EPOCHS): # 训练模型
  42. for ei, (input_labels, pos_labels, neg_labels) in enumerate(dataloader):
  43. input_labels = input_labels.to(device)
  44. pos_labels = pos_labels.to(device)
  45. neg_labels = neg_labels.to(device)
  46. optimizer.zero_grad()
  47. loss = model(input_labels, pos_labels, neg_labels).mean()
  48. loss.backward()
  49. optimizer.step()
  50. if ei % 20 == 0: # 显示训练结果
  51. print("epoch: {}, iter: {}, loss: {}".format(e, ei, loss.item()))
  52. if e % 40 == 0: # 测试模型
  53. # ---start---实现对现有模型的能力测试。该代码会从验证样本中取出指定的个数的词,通过词嵌入的转化,在已有的训练样本中找到与其语义相近的词,并显示出来。
  54. # 计算测试样本词嵌入和所有样本词嵌入间的余弦相似度
  55. norm = torch.sum(model.in_embed.weight.data.pow(2), -1).sqrt().unsqueeze(1) # norm代表每一个词对应向量的长度矩阵,见式(3-5)。
  56. normalized_embeddings = model.in_embed.weight.data / norm # normalized_embeddings表示向量除以自己的模,即单位向量。它可以确定向量的方向。
  57. valid_embeddings = normalized_embeddings[valid_examples]
  58. # 计算余弦相似度:用mm函数完成矩阵相乘【mm函数处理的是普通矩阵数据,即形状为[m,n]与[n,m]矩阵相乘】
  59. similarity = torch.mm(valid_embeddings, normalized_embeddings.T) # similanity就是valid_dataset中对应的单位向量vald_embeddings与整个词嵌入字典中单位向量的夹角余弦。
  60. for i in range(valid_size):
  61. valid_word = words[valid_examples[i]]
  62. top_k = 8 # 取最近的排名前8的词
  63. # similarity就是当前词与整个词典中每个词的夹角余弦,夹角余弦值越大,就代表相似度越高。
  64. nearest = (-similarity[i, :]).argsort()[1:top_k + 1] # argsort()用于将数组中的值按从小到大的顺序排列后,返回每个值对应的索引。在使用argsort函数之前,将similarity取负,得到的就是从小到大的排列。
  65. log_str = 'Nearest to %s:' % valid_word # 格式化输出日志
  66. for k in range(top_k):
  67. close_word = words[nearest[k].cpu().item()]
  68. log_str = '%s,%s' % (log_str, close_word)
  69. print(log_str)
  70. # ---end---实现对现有模型的能力测试。该代码会从验证样本中取出指定的个数的词,通过词嵌入的转化,在已有的训练样本中找到与其语义相近的词,并显示出来。

2.4 代码实现:实现词向量可视化---skip_gram.py(第四部分)

  1. # 1.4 实现词向量可视化
  2. # 对与可视化相关的引入库做了初始化,具体说明如下:
  3. # ①通过设置mpl的值让p|ot能够显示中文信息。
  4. # ②Scikit-learn(也称为sklearn)库的t-SNE算法模块,作用是非对称降维。
  5. # t-SNE算法结合t分布,将高维空间的数据点映射到低维空间的距离,主要用于可视化和理解高维数据。
  6. #
  7. # 将词典中的词嵌入向量转成单位向量(只有方向),然后将它们通过t-SNE算法降维映射到二维平面中进行显示。
  8. def plot_with_labels(low_dim_embs, labels, filename='./data/tsne.png'):
  9. assert low_dim_embs.shape[0] >= len(labels), 'More labels than embeddings'
  10. plt.figure(figsize=(18, 18)) # in inches
  11. for i, label in enumerate(labels):
  12. x, y = low_dim_embs[i, :]
  13. plt.scatter(x, y)
  14. plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points',
  15. ha='right', va='bottom')
  16. plt.savefig(filename)
  17. final_embeddings = model.in_embed.weight.data.cpu().numpy()
  18. tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
  19. plot_only = 200 # 输出100个词
  20. low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only, :])
  21. labels = [words[i] for i in range(plot_only)]
  22. plot_with_labels(low_dim_embs, labels)

3 skip_gram.py(代码总览)

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. from torch.utils.data import DataLoader, Dataset
  5. import collections
  6. from collections import Counter
  7. import numpy as np
  8. import random
  9. import jieba
  10. from sklearn.manifold import TSNE
  11. import matplotlib as mpl
  12. import matplotlib.pyplot as plt
  13. plt.rcParams['font.sans-serif'] = [u'SimHei']
  14. plt.rcParams['axes.unicode_minus'] = False
  15. # 1.1 样本预处理并生成字典
  16. # 使用get_ch lable函数将所有文字读入raining_data,然后在fenci函数里使用jeba库对ci放入buld_dataset里并生成指定长度(350)的字典。
  17. # 指定设备
  18. device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  19. print(device)
  20. training_file = './data/一大段文本.txt'
  21. # 中文字
  22. def get_ch_lable(txt_file): # 获取文件内的文本信息
  23. labels= ""
  24. with open(txt_file, 'rb') as f:
  25. for label in f:
  26. labels =labels+label.decode('gb2312')
  27. return labels
  28. # 分词
  29. def fenci(training_data):
  30. seg_list = jieba.cut(training_data) # 默认是精确模式
  31. training_ci = " ".join(seg_list)
  32. training_ci = training_ci.split()
  33. # 以空格将字符串分开
  34. training_ci = np.array(training_ci)
  35. training_ci = np.reshape(training_ci, [-1, ])
  36. return training_ci
  37. # build dataset()函数实现了对样本文字的处理。
  38. # 在该函数中,对样本文字的词频进行统计,将照频次由高到低排序。同时,将排序后的列表中第0个索引设置成unknown(用“UNK”表示),
  39. # 这个unknown字符可用于对词频低的词语进行填充。如果设置字典为350,则频次排序在350的词都会被当作unknown字符进行处理。
  40. def build_dataset(words,n_words): #
  41. count = [['UNK', -1]]
  42. count.extend(collections.Counter(words).most_common(n_words - 1))
  43. dictionary = dict()
  44. for word, _ in count:
  45. dictionary[word] = len(dictionary)
  46. data = list()
  47. unk_count = 0
  48. for word in words:
  49. if word in dictionary:
  50. index = dictionary[word]
  51. else:
  52. index = 0 # dictionary['UNK']
  53. unk_count += 1
  54. data.append(index)
  55. count[0][1] = unk_count
  56. reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
  57. return data, count, dictionary, reversed_dictionary
  58. training_data = get_ch_lable(training_file)
  59. print("总字数",len(training_data)) # 1567
  60. training_ci =fenci(training_data)
  61. print("总词数",len(training_ci)) #
  62. training_label, count, dictionary, words = build_dataset(training_ci, 350)
  63. #计算词频
  64. word_count = np.array([freq for _,freq in count], dtype=np.float32)
  65. word_freq = word_count / np.sum(word_count)#计算每个词的词频
  66. word_freq = word_freq ** (3. / 4.)#词频变换
  67. words_size = len(dictionary)
  68. print("字典词数",words_size) # 350
  69. print('Sample data', training_label[:10], [words[i] for i in training_label[:10]]) # # 显示的是样本文字里前10个词的词频。
  70. # 1.2 按照skip-Gram模型的规则制作数据集
  71. # 使用Dataset与Dataloader接口制作数据集。
  72. # 在自定义Dataset类中,按照Skip-Gram模型的规则对样本及其对应的标签进行组合。
  73. # 每批次取12个,每个词向量的维度为128,中心词前后的取词个数为3,负采样的个数为64。具体代码如下。
  74. C = 3 # 定义中心词前后的取词个数
  75. num_sampled = 64 # 负采样个数
  76. BATCH_SIZE = 12
  77. EMBEDDING_SIZE = 128
  78. class SkipGramDataset(Dataset): # 自定义数据集
  79. # 样本中的每个词都被当作一个中心词,对于任意一个样本中心词,都会生成两组标签:正向标签与负向标签。
  80. # 正向标签来自中心词的前后位置,负向标签主要来自词频的多项式来样。
  81. def __init__(self,training_label,word_to_idx,idx_to_word,word_freqs):
  82. super(SkipGramDataset, self).__init__()
  83. self.text_encode = torch.Tensor(training_label).long()
  84. self.word_to_idx = word_to_idx
  85. self.idx_to_word = idx_to_word
  86. self.word_freqs = torch.Tensor(word_freqs)
  87. def __len__(self):
  88. return len(self.text_encode)
  89. def __getitem__(self, idx):
  90. idx = min(max(idx,C),len(self.text_encode)-2-C) # 防止越界:对组合标签过程中的越界问题做了处理,使提取样本的索引为3~(总长度-5)。
  91. center_word = self.text_encode[idx]
  92. pos_indices = list(range(idx-C,idx)) + list(range(idx+1,idx+1+C))
  93. pos_words = self.text_encode[pos_indices]
  94. # ---start---对负向标签进行采样。在使用多项式分布采样之后,还要从中去掉与正向标签相同的索引。其中np.setdiff1d()函数用于对两个数组做差集。
  95. # 多项式分布采样,取出指定个数的高频词
  96. neg_words = torch.multinomial(self.word_freqs, num_sampled + 2 * C, False) # True)
  97. # 去掉正向标签
  98. neg_words = torch.Tensor(np.setdiff1d(neg_words.numpy(), pos_words.numpy())[:num_sampled]).long()
  99. # ---end---对负向标签进行采样。在使用多项式分布采样之后,还要从中去掉与正向标签相同的索引。其中np.setdiff1d()函数用于对两个数组做差集。
  100. return center_word, pos_words,neg_words
  101. print("制作数据集...")
  102. train_dataset = SkipGramDataset(training_label,dictionary,words,word_freq)
  103. dataloader = torch.utils.data.DataLoader(train_dataset,batch_size=BATCH_SIZE,drop_last=True,shuffle=True)
  104. sample = iter(dataloader) #将数据集转化成迭代器
  105. center_word, pos_words, neg_words = sample.next() #从迭代器中取出一批次样本
  106. print(center_word[0],words[np.long(center_word[0])],[words[i] for i in pos_words[0].numpy()])
  107. # 1.3 搭建模型并进行训练
  108. # 首先定义一个词嵌入层用手训练,将输入的样本和标签分别用词嵌入层进行转化。
  109. # 在训练过程中,将输入与标签的词嵌入当作两个向量,将二者的矩阵相乘当作两个向量间的夹角余弦值,并用该夹角余弦值作为被优化的损失函数。
  110. # 在训练模型时,定义了验证模型的相关参数,其中valid_size表示在0~words_size/2中随机取不能重复的16个字来验证模型。
  111. class Model(nn.Module):
  112. def __init__(self,vocab_size,embed_size):
  113. super(Model, self).__init__()
  114. self.vocab_size = vocab_size
  115. self.embed_size = embed_size
  116. initrange = 0.5 / self.embed_size
  117. self.in_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False)
  118. self.in_embed.weight.data.uniform_(-initrange, initrange)
  119. def forward(self,input_lables,pos_labels,neg_labels):
  120. # LogSigmoid激活函数,该激活函数的值域是(-inf,0](inf是无穷值的意思),即当输入值越大,输出值越接近于0。
  121. # 如果将输入样本的词嵌入和目标标签的词嵌入分别当作两个向量,则可以用这两个向量间的夹角余弦值来当作二者的相似度。
  122. # 为了规范计算,先通过LogSigmoid激活函数中的Sigmoid函数将参与运算的向量控制为0~1,再从正 / 负标签两个方向进行相似度计算:
  123. # ① 对于正向标签,可以直接进行计算:
  124. # ② 对于负向标签,可以先用1减去输入样本的词嵌入,得到输入样本对应的负向量,再将该结果与负向标签的词向量一起计算相似度。
  125. # 根据Sigmoid函数的对称特性1 - Sigmoid(x) = Sigmoid(-x),可以直接对输入样本词向量的符号取负来实现向量的转化。
  126. input_embedding = self.in_embed(input_lables)
  127. pos_embedding = self.in_embed(pos_labels)
  128. neg_embedding = self.in_embed(neg_labels)
  129. # 计算输入与正向标签间的夹角余弦值:用bmm函数完成两个带批次数据的矩阵相乘【bmm函数处理的必须是批次数据,即形状为{b,m,n]与[b,n,m]矩阵相乘;】
  130. log_pos = torch.bmm(pos_embedding, input_embedding.unsqueeze(2)).squeeze()
  131. # 计算输入与负向标签间的夹角余弦值:用bmm函数完成两个带批次数据的矩阵相乘【bmm函数处理的必须是批次数据,即形状为{b,m,n]与[b,n,m]矩阵相乘;】
  132. log_neg = torch.bmm(neg_embedding, -input_embedding.unsqueeze(2)).squeeze() # 在计算输入与负向标签间的夹角余弦值时,使用样本词嵌入的赋值。这样做与使用LogSigmoid激活函数有关。
  133. # 使用LogSigmoid激活函数
  134. log_pos = F.logsigmoid(log_pos).sum(1)
  135. log_neg = F.logsigmoid(log_neg).sum(1)
  136. loss = log_pos + log_neg
  137. return -loss # 对最终的损失值取负,将损失函数的值域由(-inf,0]变为(0,inf]。这种变换有利于使用优化器在迭代训练中进行优化(因为优化器只能使损失值沿着最小化的方向优化)。
  138. # 实例化模型
  139. model = Model(words_size,EMBEDDING_SIZE).to(device)
  140. model.eval()
  141. # 定义测试样本
  142. valid_size = 16
  143. valid_window = words_size/2 # 取样数据的分布范围
  144. valid_examples = np.random.choice(int(valid_window),valid_size,replace=True) #0- words_size/2,中的数取16个。不能重复。
  145. optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
  146. NUM_EPOCHS = 200
  147. for e in range(NUM_EPOCHS): # 训练模型
  148. for ei, (input_labels, pos_labels, neg_labels) in enumerate(dataloader):
  149. input_labels = input_labels.to(device)
  150. pos_labels = pos_labels.to(device)
  151. neg_labels = neg_labels.to(device)
  152. optimizer.zero_grad()
  153. loss = model(input_labels, pos_labels, neg_labels).mean()
  154. loss.backward()
  155. optimizer.step()
  156. if ei % 20 == 0: # 显示训练结果
  157. print("epoch: {}, iter: {}, loss: {}".format(e, ei, loss.item()))
  158. if e % 40 == 0: # 测试模型
  159. # ---start---实现对现有模型的能力测试。该代码会从验证样本中取出指定的个数的词,通过词嵌入的转化,在已有的训练样本中找到与其语义相近的词,并显示出来。
  160. # 计算测试样本词嵌入和所有样本词嵌入间的余弦相似度
  161. norm = torch.sum(model.in_embed.weight.data.pow(2), -1).sqrt().unsqueeze(1) # norm代表每一个词对应向量的长度矩阵,见式(3-5)。
  162. normalized_embeddings = model.in_embed.weight.data / norm # normalized_embeddings表示向量除以自己的模,即单位向量。它可以确定向量的方向。
  163. valid_embeddings = normalized_embeddings[valid_examples]
  164. # 计算余弦相似度:用mm函数完成矩阵相乘【mm函数处理的是普通矩阵数据,即形状为[m,n]与[n,m]矩阵相乘】
  165. similarity = torch.mm(valid_embeddings, normalized_embeddings.T) # similanity就是valid_dataset中对应的单位向量vald_embeddings与整个词嵌入字典中单位向量的夹角余弦。
  166. for i in range(valid_size):
  167. valid_word = words[valid_examples[i]]
  168. top_k = 8 # 取最近的排名前8的词
  169. # similarity就是当前词与整个词典中每个词的夹角余弦,夹角余弦值越大,就代表相似度越高。
  170. nearest = (-similarity[i, :]).argsort()[1:top_k + 1] # argsort()用于将数组中的值按从小到大的顺序排列后,返回每个值对应的索引。在使用argsort函数之前,将similarity取负,得到的就是从小到大的排列。
  171. log_str = 'Nearest to %s:' % valid_word # 格式化输出日志
  172. for k in range(top_k):
  173. close_word = words[nearest[k].cpu().item()]
  174. log_str = '%s,%s' % (log_str, close_word)
  175. print(log_str)
  176. # ---end---实现对现有模型的能力测试。该代码会从验证样本中取出指定的个数的词,通过词嵌入的转化,在已有的训练样本中找到与其语义相近的词,并显示出来。
  177. # 1.4 实现词向量可视化
  178. # 对与可视化相关的引入库做了初始化,具体说明如下:
  179. # ①通过设置mpl的值让p|ot能够显示中文信息。
  180. # ②Scikit-learn(也称为sklearn)库的t-SNE算法模块,作用是非对称降维。
  181. # t-SNE算法结合t分布,将高维空间的数据点映射到低维空间的距离,主要用于可视化和理解高维数据。
  182. #
  183. # 将词典中的词嵌入向量转成单位向量(只有方向),然后将它们通过t-SNE算法降维映射到二维平面中进行显示。
  184. def plot_with_labels(low_dim_embs, labels, filename='./data/tsne.png'):
  185. assert low_dim_embs.shape[0] >= len(labels), 'More labels than embeddings'
  186. plt.figure(figsize=(18, 18)) # in inches
  187. for i, label in enumerate(labels):
  188. x, y = low_dim_embs[i, :]
  189. plt.scatter(x, y)
  190. plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points',
  191. ha='right', va='bottom')
  192. plt.savefig(filename)
  193. final_embeddings = model.in_embed.weight.data.cpu().numpy()
  194. tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)
  195. plot_only = 200 # 输出100个词
  196. low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only, :])
  197. labels = [words[i] for i in range(plot_only)]
  198. plot_with_labels(low_dim_embs, labels)

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/547318
推荐阅读
相关标签
  

闽ICP备14008679号