Catalog
  1. 1. 一.NPLM:神经语言概率模型
    1. 1.1. 1.1 NPLM的基本思想
    2. 1.2. 1.2 NPLM实战
  2. 2. 1. 文本预处理
  3. 3. 二 .Word2Vec
    1. 3.1. 2.1 Word2Vec 介绍
    2. 3.2. 2.2 层级软最大
    3. 3.3. 2.3 负采样
    4. 3.4. 2.4 Word2vec实战
    5. 3.5. 2.5 类比关系实验
NPLM与word2vec实战

一.NPLM:神经语言概率模型

​       我们需要一种能避免几种传统编码方式缺陷的模型。我们希望,经过这个模型编码后的词向量长度固定,但不需要太长。另外,向量最好是密集的,也就是0元素要尽可能少,不要浪费存储空间。实际上,NPLM模型刚好可以满足我们的要求。

​      首先来看几个可能的例子。

  • “太阳”经NPLM模型编码后的词向量可能是【0.40,0.34,-0.17,0.88】;

  • “月亮”经NPLM模型编码后的词向量可能是【0.41,0.30,0.55,0.90】;

  • “猫’’ 经NPLM模型编码后的词向量可能是【0.01,-0.50,-0.95,0.20】;

            每个词向量的维度都是一样的。从语义上来说,”太阳“和”月亮“在向量空间中的距离更近。”猫“与”太阳“和”月亮“的距离很远。这是我们想要的结果。

1.1 NPLM的基本思想

       NPLM是一个基于神经网络的语言模型,有趣的是,这个模型的本意并不是获取词向量的而是用读到的前几个词去预测下一个词的。词向量只不过是NPLM模型的副产品。
      在学习NPLM模型之前,我们需要先简单了解一个自然语言处理中非常重要的概念N-gram模型,也叫N元语言模型。Ngam模型假设一个词语的出现只和它前面的N个词语相关按照这个假设,我们可以根据前N个词语去预测当前的词语。比如在“天空的颜色大是”这句话中,我们根据“天空”“的”“颜色”“是”这4个词语,很容易预测下一个词语是“蓝色果将这个过程抽象为N-gram模型运算过程,我们可以知道此处的N被设置为4也就是说,一个4-gram模型就可以解决当前问题。
        N-gram模型在自然语言处理领域中的应用非常广泛。理论上,我们希望能够以非常大的N 来建立N-gram模型,因为有的词语和前面的联系非常远,例如,“最近小镇旁边建立的工厂违规排放废气,造成了严重的大气污染,这里的天空的颜色是”。此处,如果用4-gram模型,我们依然会在空白处填为“蓝色”,但是如果N被设置得非常大,模型就可能会考虑“污染”“废气等词语的影响,而将此处填为“灰色”。事实上,在实际应用的过程中,将N设置得越大,模型的运算就越慢,所以在一般的项目中,我们通常将N设置为2或3。将N设置为3即可在大部分自然语言处理任务中取得较好的效果。
在下面的例子中,我们就选用3-gram模型,将其应用到NPLM网络中,即我们假设当前词的出现与它前面的3个词有关由此可见,N-gram模型的本质就是一个映射函数,它把前N个单词映射到下一个单词f( w,w2,…,WN)= W N+1,而NPLM想要做的,就是用一个神经网络通过机器学习的方式来学到这个映射函数f。
          非常有意思的是,NPLM在完成这个学习任务之后,我们就可以在这个神经网络中“读出”每个单词的向量编码了。换句话说,词向量不过是NPLM的一个副产品。

1.2 NPLM实战

首先,我们构建了一个简单的NGram语言模型,根据N个历史词汇预测下一个单词,从而得到每一个单词的向量表示。我们用小说《三体》为例,展示了我们的词向量嵌入效果。

其次,我们学习了如何使用成熟的Google开发的Word2Vec包来进行大规模语料的词向量训练,以及如何加载已经训练好的词向量,从而利用这些词向量来做一些简单的运算和测试。

# 加载必要的程序包
# PyTorch的程序包
import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

# 数值运算和绘图的程序包
import numpy as np
import matplotlib.pyplot as plt
import matplotlib


# 加载机器学习的软件包
from sklearn.decomposition import PCA

#加载Word2Vec的软件包
import gensim as gensim
from gensim.models import Word2Vec
from gensim.models.keyedvectors import KeyedVectors
from gensim.models.word2vec import LineSentence

#加载‘结巴’中文分词软件包

import jieba

#加载正则表达式处理的包
import re

%matplotlib inline

1. 文本预处理

我们以刘慈欣著名的科幻小说《三体》为例,来展示利用NGram模型训练词向量的方法

预处理分为两个步骤:

1、读取文件

2、分词

3、将语料划分为N+1元组,准备好训练用数据

在这里,我们并没有去除标点符号,一是为了编程简洁,而是考虑到分词会自动将标点符号当作一个单词处理,因此不需要额外考虑。

#读入原始文件

f = open("三体.txt", 'r')
# 若想加快运行速度,使用下面的语句(选用了三体的其中一章):
#f = open("3body.txt", 'r')
text = str(f.read())
f.close()

text

# 分词
temp = jieba.lcut(text)
words = []
for i in temp:
#过滤掉所有的标点符号
i = re.sub("[\s+\.\!\/_,$%^*(+\"\'“”《》?“]+|[+——!,。?、~@#¥%……&*():]+", "", i)
if len(i) > 0:
words.append(i)
print(len(words))
words

构建三元组列表. 每一个元素为: ([ i-2位置的词, i-1位置的词 ], 下一个词)
# 我们选择的Ngram中的N,即窗口大小为2
trigrams = [([words[i], words[i + 1]], words[i + 2]) for i in range(len(words) - 2)]
# 打印出前三个元素看看
print(trigrams[:3])


# 得到词汇表
vocab = set(words)
print(len(vocab))
# 两个字典,一个根据单词索引其编号,一个根据编号索引单词
#word_to_idx中的值包含两部分,一部分为id,另一部分为单词出现的次数
#word_to_idx中的每一个元素形如:{w:[id, count]},其中w为一个词,id为该词的编号,count为该单词在words全文中出现的次数
word_to_idx = {}
idx_to_word = {}
ids = 0

#对全文循环,构建这两个字典
for w in words:
cnt = word_to_idx.get(w, [ids, 0])
if cnt[1] == 0:
ids += 1
cnt[1] += 1
word_to_idx[w] = cnt
idx_to_word[ids] = w

我们构造了一个三层的网络:

1、输入层:embedding层,这一层的作用是:先将输入单词的编号映射为一个one hot编码的向量,形如:001000,维度为单词表大小。 然后,embedding会通过一个线性的神经网络层映射到这个词的向量表示,输出为embedding_dim

2、线性层,从embedding_dim维度到128维度,然后经过非线性ReLU函数

3、线性层:从128维度到单词表大小维度,然后log softmax函数,给出预测每个单词的概率

class NGram(nn.Module):

def __init__(self, vocab_size, embedding_dim, context_size):
super(NGram, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim) #嵌入层
self.linear1 = nn.Linear(context_size * embedding_dim, 128) #线性层
self.linear2 = nn.Linear(128, vocab_size) #线性层

def forward(self, inputs):
#嵌入运算,嵌入运算在内部分为两步:将输入的单词编码映射为one hot向量表示,然后经过一个线性层得到单词的词向量
#inputs的尺寸为:1*context_size
embeds = self.embeddings(inputs)
#embeds的尺寸为: context_size*embedding_dim
embeds = embeds.view(1, -1)
#此时embeds的尺寸为:1*embedding_dim
# 线性层加ReLU
out = self.linear1(embeds)
out = F.relu(out)
#此时out的尺寸为1*128

# 线性层加Softmax
out = self.linear2(out)
#此时out的尺寸为:1*vocab_size
log_probs = F.log_softmax(out, dim = 1)
return log_probs
def extract(self, inputs):
embeds = self.embeddings(inputs)
return embeds

losses = [] #纪录每一步的损失函数
criterion = nn.NLLLoss() #运用负对数似然函数作为目标函数(常用于多分类问题的目标函数)
model = NGram(len(vocab), 10, 2) #定义NGram模型,向量嵌入维数为10维,N(窗口大小)为2
optimizer = optim.SGD(model.parameters(), lr=0.001) #使用随机梯度下降算法作为优化器

#循环100个周期
for epoch in range(20):
total_loss = torch.Tensor([0])
for context, target in trigrams:

# 准备好输入模型的数据,将词汇映射为编码
context_idxs = [word_to_idx[w][0] for w in context]

# 包装成PyTorch的Variable
context_var = torch.tensor(context_idxs, dtype = torch.long)

# 清空梯度:注意PyTorch会在调用backward的时候自动积累梯度信息,故而每隔周期要清空梯度信息一次。
optimizer.zero_grad()

# 用神经网络做计算,计算得到输出的每个单词的可能概率对数值
log_probs = model(context_var)

# 计算损失函数,同样需要把目标数据转化为编码,并包装为Variable
loss = criterion(log_probs, torch.tensor([word_to_idx[target][0]], dtype = torch.long))

# 梯度反传
loss.backward()

# 对网络进行优化
optimizer.step()

# 累加损失函数值
total_loss += loss.data
losses.append(total_loss)
print('第{}轮,损失函数为:{:.2f}'.format(epoch, total_loss.numpy()[0]))

结果展示

# 从训练好的模型中提取每个单词的向量
vec = model.extract(torch.tensor([v[0] for v in word_to_idx.values()], dtype = torch.long))
vec = vec.data.numpy()

# 利用PCA算法进行降维
X_reduced = PCA(n_components=2).fit_transform(vec)


# 绘制所有单词向量的二维空间投影
fig = plt.figure(figsize = (30, 20))
ax = fig.gca()
ax.set_facecolor('white')
ax.plot(X_reduced[:, 0], X_reduced[:, 1], '.', markersize = 1, alpha = 0.4, color = 'black')


# 绘制几个特殊单词的向量
words = ['智子', '地球', '三体', '质子', '科学', '世界', '文明', '太空', '加速器', '平面', '宇宙', '信息']

# 设置中文字体,否则无法在图形上显示中文
zhfont1 = matplotlib.font_manager.FontProperties(fname='./华文仿宋.ttf', size=16)
for w in words:
if w in word_to_idx:
ind = word_to_idx[w][0]
xy = X_reduced[ind]
plt.plot(xy[0], xy[1], '.', alpha =1, color = 'red')
plt.text(xy[0], xy[1], w, fontproperties = zhfont1, alpha = 1, color = 'black')

可以看到,这些词语在语义上并没有什么关联,这说明我们的NPLM模型学出来的词向量并不好

1.3 NPLM的总结与局限

我们可以这样理解NPLM的网络原理。实际上,它接受的输入是独热编码,在运作过程中,会先尝试将这个独热编码映射为一个特定维数的词向量,然后再用词向量取预测可能出现在这个词后面的词。随着训练次数的增加和反向传播的调整,网络渐渐获取了将意义相近的词映射为相似的词向量的能力。

缺点:速度太慢。NPLM于2003年诞生,但正是由于这个缺陷,没有得到广泛使用。

二 .Word2Vec

2.1 Word2Vec 介绍

​ word2vec是NPLM的升级版,它在多方面进行了改进,大幅提升了NPLM模型的运算速度和精度。

​ Word2Vec是一组(2个)模型,分别叫做CBOW(continuous bag of words)模型和Skip-gram模型。

  • CBOW:用当前词的前n个词和后n个词来预测当前的词。
  • Skip-gram:和CBOW相反,用当前词预测上下文。

2.2 层级软最大

在NPLM模型中,输出层单元用一层神经元来对应当前词,这种结构对于当前词的查询和反馈都是比较耗时的。层级软最大(hierarchical softmax)的核心思路是对输出层单元的结构进行更改,将其由原来的“扁平结构”编码为一个哈夫曼树(Huffman tree),树中的每一个叶节点对应一个词语。

如果词典中有n个词,输出层经过哈夫曼编码后,只需要经过log(n)步即可查询到该词。而且,在每次训练的反馈过程中,都会更新从根节点到当前词对应的叶节点的所有连边的权值,这也加速了网络的training.

2.3 负采样

层级软最大改进了输出层结构,而负采样则改进了目标函数。

核心思路:在训练过程汇总,我们不知道哪个词是正确的,而且知道随机选取的词应该是错误的。在每次训练的过程中,我们可以随机选取多个负样本一同参与损失函数的计算,模型就会同时考虑正负样本的影响。

2.4 Word2vec实战

# 读入文件、分词,形成一句一句的语料
# 注意跟前面处理不一样的地方在于,我们一行一行地读入文件,从而自然利用行将文章分开成“句子”
f = open("三体.txt", 'r',encoding = 'utf-8')
lines = []
for line in f:
temp = jieba.lcut(line)
words = []
for i in temp:
#过滤掉所有的标点符号
i = re.sub("[\s+\.\!\/_,$%^*(+\"\'””《》]+|[+——!,。?、~@#¥%……&*():;‘]+", "", i)
if len(i) > 0:
words.append(i)
if len(words) > 0:
lines.append(words)
# 调用Word2Vec的算法进行训练。
# 参数分别为:size: 嵌入后的词向量维度;window: 上下文的宽度,min_count为考虑计算的单词的最低词频阈值
model = Word2Vec(lines, size = 20, window = 2 , min_count = 0)

model.wv.most_similar('智子', topn = 20)
# 将词向量投影到二维空间
rawWordVec = []
word2ind = {}
for i, w in enumerate(model.wv.vocab):
rawWordVec.append(model[w])
word2ind[w] = i
rawWordVec = np.array(rawWordVec)
X_reduced = PCA(n_components=2).fit_transform(rawWordVec)
# 绘制星空图
# 绘制所有单词向量的二维空间投影
fig = plt.figure(figsize = (15, 10))
ax = fig.gca()
ax.set_facecolor('white')
ax.plot(X_reduced[:, 0], X_reduced[:, 1], '.', markersize = 1, alpha = 0.3, color = 'black')


# 绘制几个特殊单词的向量
words = ['智子', '地球', '三体', '质子', '科学', '世界', '文明', '太空', '加速器', '平面', '宇宙', '进展','的']

# 设置中文字体,否则无法在图形上显示中文
zhfont1 = matplotlib.font_manager.FontProperties(fname='./华文仿宋.ttf', size=16)
for w in words:
if w in word2ind:
ind = word2ind[w]
xy = X_reduced[ind]
plt.plot(xy[0], xy[1], '.', alpha =1, color = 'green')
plt.text(xy[0], xy[1], w, fontproperties = zhfont1, alpha = 1, color = 'blue')
# 加载词向量
word_vectors = KeyedVectors.load_word2vec_format('./vectors.bin', binary=True, unicode_errors='ignore')
len(word_vectors.vocab)
# PCA降维
rawWordVec = []
word2ind = {}
for i, w in enumerate(word_vectors.vocab):
rawWordVec.append(word_vectors[w])
word2ind[w] = i
rawWordVec = np.array(rawWordVec)
X_reduced = PCA(n_components=2).fit_transform(rawWordVec)
# 查看相似词
word_vectors.most_similar('python', topn = 20)
# 绘制星空图
# 绘制所有的词汇
fig = plt.figure(figsize = (30, 15))
ax = fig.gca()
ax.set_facecolor('black')
ax.plot(X_reduced[:, 0], X_reduced[:, 1], '.', markersize = 1, alpha = 0.1, color = 'white')

ax.set_xlim([-12,12])
ax.set_ylim([-10,20])


# 选择几个特殊词汇,不仅画它们的位置,而且把它们的临近词也画出来
words = {'徐静蕾','吴亦凡','物理','红楼梦','量子'}
all_words = []
for w in words:
lst = word_vectors.most_similar(w)
wds = [i[0] for i in lst]
metrics = [i[1] for i in lst]
wds = np.append(wds, w)
all_words.append(wds)


zhfont1 = matplotlib.font_manager.FontProperties(fname='./华文仿宋.ttf', size=16)
colors = ['red', 'yellow', 'orange', 'green', 'cyan', 'cyan']
for num, wds in enumerate(all_words):
for w in wds:
if w in word2ind:
ind = word2ind[w]
xy = X_reduced[ind]
plt.plot(xy[0], xy[1], '.', alpha =1, color = colors[num])
plt.text(xy[0], xy[1], w, fontproperties = zhfont1, alpha = 1, color = colors[num])
plt.savefig('88.png',dpi =600)

星空图如下:

2.5 类比关系实验

Author: superzhaoyang
Link: http://yoursite.com/2020/05/11/NPLM&&word2vec/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付宝

Comment