自然语言处理之Word Embedding

Word Embedding

在自然语言处理中词向量是很重要的,那什么是词向量呢?
通常我们在做分类问题的时候大家应该都还记得我们会使用one-hot编码,比如一共有5类,那么属于第二类的话,它的编码就是(0, 1, 0, 0, 0),对于分类问题,这样当然特别简明,但是对于单词,这样做就不行了,比如有1000个不同的词,那么使用one-hot这样的方法效率就很低了,所以我们必须要使用另外一种方式去定义每一个单词,这就引出了word embedding。
我们可以先举三个例子,比如

The cat likes playing ball.
The kitty likes playing wool.
The dog likes playing ball.
The boy likes playing ball.
假如我们使用一个二维向量(a, b)来定义一个词,其中a,b分别代表这个词的一种属性,比如a代表是否喜欢玩飞盘,b代表是否喜欢玩毛线,并且这个数值越大表示越喜欢,这样我们就可以区分这三个词了,为什么呢?

假设对于cat,它的词向量就是(-1, 4),对于kitty,它的词向量就是(-2, 5),对于dog,它的词向量就是(3, -2),对于boy,它的词向量就是(-2, -3),我们怎么去定义他们之间的相似度呢,我们可以通过他们之间的夹角来定义他们的相似度。
在这里插入图片描述
上面这张图就显示出了不同的词之间的夹角,我们可以发现kitty和cat是非常相似的,而dog和boy是不相似的。

而对于一个词,我们自己去想它的属性不是很困难吗,所以这个时候就可以交给神经网络了,我们只需要定义我们想要的维度,比如100,然后通过神经网络去学习它的每一个属性的大小,而我们并不用关心到底这个属性代表着什么,我们只需要知道词向量的夹角越小,表示他们之间的语义更加接近。

code

在pytorch里面word embedding实现是通过一个函数来实现的nn.Embedding.

import torch.nn as nn
import torch
word_to_ix ={'hello': 0, 'world': 1}
embeds = nn.Embedding(2, 5)

首先我们需要word_to_ix = {\’hello\’: 0, \’world\’: 1},每个单词我们需要用一个数字去表示他,这样我们需要hello的时候,就用0来表示它。
接着就是word embedding的定义nn.Embedding(2, 5),这里的2表示有2个词,5表示5维,其实也就是一个2×5的矩阵,所以如果你有1000个词,每个词希望是100维,你就可以这样建立一个word embedding,nn.Embedding(1000, 100)。
如何访问每一个词的词向量是下面两行的代码,注意这里的词向量的建立只是初始的词向量,并没有经过任何修改优化,我们需要建立神经网络通过learning的办法修改word embedding里面的参数使得word embedding每一个词向量能够表示每一个不同的词。

hello_idx = torch.LongTensor([word_to_ix['hello']])
hello_embed = embeds(hello_idx)
print(hello_embed)

tensor([[-0.7694,  1.6624, -0.1027, -0.0286,  0.6692]],
       grad_fn=<EmbeddingBackward>)

其实我们可以定义需要定义任意多个词和相应的维度。如:我们有10个单词,每个单词假定有3个属性,即每个单词的维度为3.

 embedding = nn.Embedding(10, 3) 

nn.Embedding(10, 3) 起到初始化一个10X3的矩阵的效果。我们可以获取被初始化的每个单词的初始结果。

input = torch.LongTensor([[1,2,4,5,0],[4,3,2,9,0]])
embedding(input)

tensor([[[ 1.6370, -1.9253, -0.2562],
         [-1.7304, -1.3495,  0.1432],
         [ 0.6550, -0.2040,  0.5745],
         [-2.1956, -0.4447, -0.5321],
         [ 0.7693, -0.5994,  0.3616]],

        [[ 0.6550, -0.2040,  0.5745],
         [ 0.9506, -0.3834,  0.5379],
         [-1.7304, -1.3495,  0.1432],
         [-1.8107, -0.4929,  1.0156],
         [ 0.7693, -0.5994,  0.3616]]], grad_fn=<EmbeddingBackward>)

N-Gram language Modeling

首先我们介绍一下 N-Gram 模型。在一篇文章中,每一句话有很多单词组成,而对于一句话,这些单词的组成顺序也是很重要的,我们想要知道在一篇文章中我们是否可以给出几个词然后预测这些词后面的一个单词,比如’I lived in France for 10 years, I can speak _ .’那么我们想要做的就是预测最后这个词是French。
知道了我们想要做的事情之后,我们就可以引出 N-Gram 模型了。先给出其公式:
P ( w i ∣ w i − 1 , w i − 2 , . . . , w i − n + 1 ) P(w_{i}|w_{i-1},w_{i-2},...,w_{i-n+1}) P(wiwi1,wi2,...,win+1)
这就是一个条件概率,也就是我们给定想要预测的单词的前面几个单词,然后最大化我们想要预测的这个单词的概率。

Code

数据预处理
首先我们给出了一段文章作为我们的训练集.

CONTEXT_SIZE = 2
EMBEDDING_DIM = 100
# We will use Shakespeare Sonnet 2
test_sentence = """When forty winters shall besiege thy brow,
And dig deep trenches in thy beauty\\'s field,
Thy youth\\'s proud livery so gazed on now,
Will be a totter\\'d weed of small worth held:
Then being asked, where all thy beauty lies,
Where all the treasure of thy lusty days;
To say, within thine own deep sunken eyes,
Were an all-eating shame, and thriftless praise.
How much more praise deserv\\'d thy beauty\\'s use,
If thou couldst answer \\'This fair child of mine
Shall sum my count, and make my old excuse,\\'
Proving his beauty by succession thine!
This were to be new made when thou art old,
And see thy blood warm when thou feel\\'st it cold.""".split()

CONTEXT_SIZE表示我们想由前面的几个单词来预测这个单词,这里设置为2,就是说我们希望通过这个单词的前两个单词来预测这一个单词。 EMBEDDING_DIM表示word embedding的维数,

print(test_sentence)

['When', 'forty', 'winters', 'shall', 'besiege', ...,"feel\\'st", 'it', 'cold.']

接下来我们需要将数据整理好,也就是我们需要将单词三个分组,每个组前两个作为传入的数据,而最后一个作为预测的结果.

trigram = [((test_sentence[i], test_sentence[i 1]), test_sentence[i 2])
           for i in range(len(test_sentence)-2)]
print(trigram)
[(('When', 'forty'), 'winters'),
 (('forty', 'winters'), 'shall'),
 (('winters', 'shall'), 'besiege'),
 (('shall', 'besiege'), 'thy'),
 .....
 (('when', 'thou'), "feel\\'st"),
 (('thou', "feel\\'st"), 'it'),
 (("feel\\'st", 'it'), 'cold.')]

接下来需要给每个单词编码,也就是用数字来表示每个单词,这样才能够传入word embeding得到词向量。

vocb = set(test_sentence) # 通过set将重复的单词去掉
word_to_idx = {word: i for i, word in enumerate(vocb)}
idx_to_word = {word_to_idx[word]: word for word in word_to_idx}
定义模型
class NgramModel(nn.Module):
    def __init__(self, vocb_size, context_size, n_dim):
        super(NgramModel, self).__init__()
        self.n_word = vocb_size
        self.embedding = nn.Embedding(self.n_word, n_dim)
        self.linear1 = nn.Linear(context_size*n_dim, 128)
        self.linear2 = nn.Linear(128, self.n_word)
 
    def forward(self, x):
        emb = self.embedding(x)
        emb = emb.view(1, -1)
        out = self.linear1(emb)
        out = F.relu(out)
        out = self.linear2(out)
        log_prob = F.log_softmax(out)
        return log_prob

这个模型需要传入的参数是所有的单词数,预测单词需要的前面单词数,即CONTEXT_SIZE,词向量的维度。

然后在向前传播中,首先传入单词得到词向量,比如在该模型中传入两个词,得到的词向量是(2, 100),然后将词向量展开成(1, 200),然后传入一个线性模型,经过relu激活函数再传入一个线性模型,输出的维数是单词总数,可以看成一个分类问题,要最大化预测单词的概率,最后经过一个log softmax激活函数。

在这里插入图片描述

ngrammodel = NgramModel(len(word_to_idx), CONTEXT_SIZE, EMBEDDING_DIM)
criterion = nn.NLLLoss()
optimizer = optim.SGD(ngrammodel.parameters(), lr=1e-3)
训练
for epoch in range(100):
    print(\'epoch: {}\'.format(epoch 1))
    print(\'*\'*10)
    running_loss = 0
    for data in trigram:
        word, label = data
        word = Variable(torch.LongTensor([word_to_idx[i] for i in word]))
        label = Variable(torch.LongTensor([word_to_idx[label]]))
        # forward
        out = ngrammodel(word)
        loss = criterion(out, label)
        running_loss  = loss.data[0]
        # backward
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print(\'Loss: {:.6f}\'.format(running_loss / len(word_to_idx)))

接着进行训练,一共跑100个epoch,在每个epoch中,word代表着预测单词的前面两个词,label表示要预测的词,然后记住需要将他们转换成Variable,接着进入网络得到结果,然后通过loss函数得到loss进行反向传播,更新参数。
训练完100个epoch后的结果如下:
在这里插入图片描述
我们发现loss已经降到了0.37,也可以通过预测来检测我们的模型是否有效.

word, label = trigram[3]
word = Variable(torch.LongTensor([word_to_idx[i] for i in word]))
out = ngrammodel(word)
_, predict_label = torch.max(out, 1)
predict_word = idx_to_word[predict_label.item()]
print('real word is {}, predict word is {}'.format(label, predict_word))

real word is thy, predict word is thy

Continuous Bag-of-Words model (CBOW)

可以发现我们能够准确地预测这个单词。N-Gram模型通过双边的单词来预测中间的单词,这种模型有个专门的名字,叫 Continuous Bag-of-Words model (CBOW).

导入包
import torch
from torch import nn, optim
from torch.autograd import Variable
import torch.nn.functional as F

连续袋词模型(CBOW)是NLP深度学习中常用的一种方法。这是一个模型,它试图预测单词在目标单词之前和之后的上下文。这与语言建模不同,因为CBOW不是连续的,也不必是概率性的。典型地,CBOW用于快速训练词嵌入,而这些嵌入用于初始化一些更复杂模型的嵌入。通常,这被称为预训练嵌入。它几乎总是能帮助性能提高几个百分点。

数据处理
CONTEXT_SIZE = 2  # 2 words to the left, 2 to the right
raw_text = """We are about to study the idea of a computational process.
Computational processes are abstract beings that inhabit computers.
As they evolve, processes manipulate other abstract things called data.
The evolution of a process is directed by a pattern of rules
called a program. People create programs to direct processes. In effect,
we conjure the spirits of the computer with our spells.""".split()

CONTEXT_SIZE表示,预测单词的左边有2个单词,右边有2个单词。只是预测中间的单词。

vocab = set(raw_text)
word_to_idx = {word: i for i, word in enumerate(vocab)}

data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
    context = [
        raw_text[i - 2], raw_text[i - 1], raw_text[i + 1], raw_text[i + 2]
    ]
    target = raw_text[i]
    data.append((context, target))
构建模型
class CBOW(nn.Module):
    def __init__(self, n_word, n_dim, context_size):
        super(CBOW, self).__init__()
        self.embedding = nn.Embedding(n_word, n_dim)
        self.project = nn.Linear(n_dim, n_dim, bias=False)
        self.linear1 = nn.Linear(n_dim, 128)
        self.linear2 = nn.Linear(128, n_word)

    def forward(self, x):
        x = self.embedding(x)
        x = self.project(x)
        x = torch.sum(x, 0, keepdim=True)#按列求和
        x = self.linear1(x)
        x = F.relu(x, inplace=True)
        x = self.linear2(x)
        x = F.log_softmax(x)
        return x
优化器和损失函数
model = CBOW(len(word_to_idx), 100, CONTEXT_SIZE)
if torch.cuda.is_available():
    model = model.cuda()

criterion = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=1e-3)
训练模型
for epoch in range(100):
    print('epoch {}'.format(epoch))
    print('*' * 10)
    running_loss = 0
    for word in data:
        context, target = word
        context = Variable(torch.LongTensor([word_to_idx[i] for i in context]))
        target = Variable(torch.LongTensor([word_to_idx[target]]))
        if torch.cuda.is_available():
            context = context.cuda()
            target = target.cuda()
        # forward
        out = model(context)
        loss = criterion(out, target)
        running_loss += loss.data[0]
        # backward
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print('loss: {:.6f}'.format(running_loss / len(data)))
测试模型
idx_to_word = {word_to_idx[word]: word for word in word_to_idx}
word, label = data[0]
word = Variable(torch.LongTensor([word_to_idx[i] for i in word]))
out = model(word)
_, predict_label = torch.max(out, 1)
print(idx_to_word[predict_label.item()])

'about'
predict_word = idx_to_word[predict_label.item()]
print('real word is {}, predict word is {}'.format(label, predict_word))

real word is about, predict word is about
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值