目录
三、词的分布式表示(Distributed representation)
一、什么是词向量
计算机倾向于和vector(向量)打交道,而不是一一个个单词,所以需要吧单词转化为词向量
二、词向量的离散表示
离散表示:
1、one-hot编码
词典中有10000个单词,则one-hot编码为10000维的向量,在正确单词表示处为1,其余为0
缺点:不能表示单词间语义相近性
如:
2、 Bag of Words表示
将每个单词在语料库中出现的次数加到one-hot编码中,即为单词的Bag of words表示。
缺点:单词的顺序没有被考虑,语义信息也没有被考虑
3、TF-IDF表示
为了表示单词的重要性,给词加上一个权重,罕见的单词给高一点的权重,常见的给低一点的权重。
缺点:还是没有考虑单词顺序和语义信息。
4、 Bi-gram和N-gram
本来是1个单词的单词表,又把两个单词拼在一起组成单词表。还是没有在根本上解决语义的相关性问题。
5、离散表示的问题:
如何保证词编码之间的相似性,比如一个许多不同品种的鸟,都属于鸟类,所以要保证它们之间的相关性。
三、词的分布式表示(Distributed representation)
想要知道一个单词什么意思,看它周围经常出现什么单词就可以了。
Word2Vec文章介绍了两个词向量的模型:本章主要学习单词怎样编码才能更好的表示其相似性(学习更好的词编码),初始编码是随机初始化的。
1、Skip-Gram模型
用一个词附近的词定义该单词。即这个单词如果能够预测处它附近的单词,那这个单词的特征也就知道了,意思也大约能猜出来了。
总的损失函数如下:
为了加快训练速度,采用负例采样(Negative Sampling):不要在所有吧的单词上预测概率,相当于做一个二分类任务,给定一个单词,预测这个单词是不是中心词周围的单词,是的话输出1,不是的话,输出0。即把50万分类问题变为一个二分类问题。不计算具体的概率,只关心是不是周围词,如果是,给一个高一点的概率就可以了,不是就给一个低一点的概率。
首先给一个中心词和一个正确 的周围单词,还有若干个错误的周围词(可以从单词表中随机采样),希望前面的部分越大越好(第二个公式加号前面的公式),后面的饿部分越小越好(第二个公式,但是加了一个负号,所以此处也是越大越好)。
下面为模型目标函数:是典型的sigmoid函数:,第二个公式即为先对输入进行sigmoid,然后做log操作,sigmoid是一个增函数,单词相近性越高(),则得到的数值越高。采样的单词和中心词相近性越低(),得到的结果越低,但是此处添加了一个负号,表示得到的结果越高越好。则整个第二个公式表示得到的结果越高越好。以此来训练,形成更好的词向量编码。需要训练的参数在词向量编码中。
2、代码
'''中心思想:
在一个text(文章)中:
输入:一个中心词w(t),t表示这个单词的位置
得到其附近C位置上的单词,以及负采样单词(负采样是把单词出现频率作为一个list,然后在其中采样k*2*c次,返回其下标),返回这三种单词的编码center_input,pos_input,neg_input(神经网络输入的是数据,把单词变为数据/list下标进行输入)
神经网络再对其进行编码,得到center_encode,pos_encode,neg_encode
损失函数:对于center_encode*pos_encode,其乘积越高表示对center_encode的编码效果越好,-(center_encode*neg_encode)越高表示编码效果越好,再对乘积做logsigmoid操作,取负值即为loss。注意将两个乘积的第1维进行sum()操作(第0维表示有多少个中心词,第1维表示每个中心词产生多少个周围/负采样词,第2维表示每个词的编码长度)。
权重取值:取出对center_encode的权重(__init__()中nn.Embedding方法训练的权重,而不是forward中单个单词产生的权重),变为numpy数据类型。eg:self.in_encode.weight.data.numpy()
'''
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data as tud
from collections import Counter
import numpy as np
import random
import math
import pandas as pd
import sklearn
from sklearn.metrics.pairwise import cosine_similarity
#USE_CUDA=torch.cuda.is_available()
#模型每次训练得到的值都不一样,这是因为有些数据是随机初始化的,现在让这些数据初始化都一样
random.seed(1)
np.random.seed(1)
torch.manual_seed(1)
#设定一些超参数
c=3#定义中心词周围有3个单词
k=100#随机采样100个负例,如果训练数据够大的话,可以把k调到20以下,即没出现一个正确的词要出现100个错误的词
num_epoch=2#训练2个epoch
max_vocab_size=30000#词汇表有多少个单词,此处训练30000个常见的单词
batch_size=128
learning_rate=0.2
embedding_size=100#编码长度为100
#读进来的是一篇文章,希望把它变成一个一个单词
def word_tokenize(text):
return text.split()
with open("text8.train.txt","r") as fin:
text=fin.read()
#text[:100].split()#split()字符串自动切割,默认以空格为切割点
#将单词变为一张词汇表
text=text.split()
print(type(text))
vocab=dict(Counter(text).most_common(max_vocab_size-1))#Counter(text)能够把每个单词都数一遍,并计算出不同的单词出现多少遍,,然后把最常见的3000个单词取出来并变为字典
vocab["<unk>"]=len(text)-np.sum(list(vocab.values()))#vocab.values()表示每个vocab单词出现的次数(取出字典中的值),变成list,然后再求和,就是所有常见单词的出现次数,用所有单词的长度减去它,即所有不常见单词的出现次数
构建两张关于词汇的mapping
index_to_word=[word for word in vocab.keys()]#取出所有单词表中的单词
word_to_index={word:i for i,word in enumerate(index_to_word)}#把index_to_word中的内容反过来,并标上序号,enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中
word_count=np.array([count for count in vocab.values()],dtype=np.float32)#为什么用numpy数据类型
word_freqs=word_count/np.sum(word_count)#得到每个单词出现的概率
#论文中提到要把概率都提到3/4次方
word_freqs=word_freqs**(3./4.)
word_freqs=word_freqs/np.sum(word_freqs)#再重新normalize一次(归一化)
vocab_size=len(index_to_word)#如果训练数据不够30000个,则更新单词数量
class WordEmbaddingDataset(tud.Dataset):
def __init__(self,text,word_to_index,index_to_word,word_count,word_freqs):
super(WordEmbaddingDataset,self).__init__()
self.text_encode=[word_to_index.get(word,max_vocab_size-1) for word in text]
self.text_encode=torch.tensor(self.text_encode).long()
self.word_to_index=word_to_index
self.index_to_word=index_to_word
self.word_count=word_count
self.word_freqs=word_freqs
def __len__(self):#数据集一共有多少个item()
return len(self.text_encode)
def __getitem__(self,idx):
#给定一个index,返回其代表的哪一个数据,一般返回torch.tensor,然后把这些tensor 拼在一起,拼成一个batch
center_word=self.text_encode[idx]
pos_word=list(range(idx-c,idx))+list(range(idx+1,idx+c+1))
pos_word=[i%len(self.text_encode) for i in pos_word]
pos_word=self.text_encode(pos_word)
neg_word=torch.multinorminal(self.word_freqs,K*pos_word.shape[0],True)
return center_word,pos_word,neg_word
dataset=WordEmbaddingDataset(text,word_to_index,index_to_word,word_count,word_freqs)
dataloader=tud.DataLoader(dataset,batch_size=batch_size,shuffle=True,num_workers=0)
class EmbaddingModel(nn.Module):
def __init__(self,vocab_size,embad_size):
super(EmbaddingModel,self).__init__()
self.vocab_size=vocab_size
self.embad_size=embad_size
self.in_embad=nn.Embedding(self.vocab_size,self.embad_size)
self.out_embad=nn.Embedding(self.vocab_size,self.embad_size)
def forward(self,input_lable,pos_lable,neg_lable):
input_embadding=self.in_embad(input_lable)
pos_embadding=self.out_embad(pos_lable)
neg_embadding=self.out_embad(neg_lable)
input_embadding=input_embadding.unsqueeze(2)
pos_dot=torch.bmm(pos_embadding,input_embadding).squeeze(2)
neg_dot=torch.bmm(neg_embadding,-input_embadding).squeeze(2)
log_pos=F.logsigmoid(pos_dot ).sum(1)
log_neg=F.logsigmoid(neg_dot).sum(1)
loss=-(log_pos+log_neg)
return loss
def input_embadding(self):
return self.in_embad.weight.data.numpy()
model=Embaddingmodel(vocab_size,embad_size)
optimizer=torch.optim.SGD(model.parameters(),lr=learning_rate)
for e in range(epoch):
for i,(input_lable,pos_lable,neg_lable) in enumerate(dataloader):
input_lable=input_lable.long()
pos_lable=pos_lable.long()
neg_lable=neg_lable.long()
optimizer.zero_grad()
loss=modle(input_lable,pos_lable,neg_lable)
loss.backward()
optimizer.step()
if i%100==0:
print("epoch",epoch,"iteration",i,loss.item())