跟我一起學(xué)PyTorch-07:嵌入與表征學(xué)習(xí)

前面介紹了深度神經(jīng)網(wǎng)絡(luò)和卷積神經(jīng)網(wǎng)絡(luò),這些神經(jīng)網(wǎng)絡(luò)有個(gè)特點(diǎn):輸入的向量越大,訓(xùn)練得到的模型越大。但是,擁有大量參數(shù)模型的代價(jià)是昂貴的,它需要大量的數(shù)據(jù)進(jìn)行訓(xùn)練,否則由于缺少足夠的訓(xùn)練數(shù)據(jù),就可能出現(xiàn)過擬合的問題。盡管卷積神經(jīng)網(wǎng)絡(luò)能夠在不損失模型性能的情況下減少模型參數(shù),但是仍然需要大量帶有標(biāo)簽的數(shù)據(jù)進(jìn)行訓(xùn)練。半監(jiān)督學(xué)習(xí)通過進(jìn)一步學(xué)習(xí)未標(biāo)簽數(shù)據(jù)來解決這個(gè)問題,具體思路是:從未標(biāo)簽數(shù)據(jù)上學(xué)習(xí)數(shù)據(jù)的表征,用這些表征來解決監(jiān)督學(xué)習(xí)問題。

本章介紹的非監(jiān)督學(xué)習(xí)中的嵌入方法,又稱為低維度表征。非監(jiān)督學(xué)習(xí)不用自動特征選取,只用少量數(shù)據(jù)學(xué)習(xí)一個(gè)相對較小的嵌入模型來解決學(xué)習(xí)問題,如下圖所示。

image.png

為了更好地理解嵌入學(xué)習(xí),需要探索其他的低維度表征算法,比如可視化和PCA。如果考慮到所有的重要信息都包含在原始的輸入時(shí),嵌入學(xué)習(xí)就等同于一個(gè)有效的壓縮算法。本章首先介紹經(jīng)典的降維算法PCA,然后介紹基于強(qiáng)大神經(jīng)網(wǎng)絡(luò)的嵌入學(xué)習(xí)算法。

1.PCA算法

主成分分析(Principal Component Analysis,PCA),是一種分析、簡化數(shù)據(jù)的常用技術(shù)。PCA能夠減少數(shù)據(jù)的維度,同時(shí)保持?jǐn)?shù)據(jù)集中的對方差貢獻(xiàn)較大的特征。這種方法是通過保留低階主成分、忽略高階主成分做到的。

1.PCA原理

PCA的基本原理是從大量數(shù)據(jù)中找到少量的主成分變量,在數(shù)據(jù)維度降低的情況下,盡可能地保留原始數(shù)據(jù)的信息。比如,假設(shè)一個(gè)d維的數(shù)據(jù),找到一個(gè)新的m維數(shù)據(jù),其中m<d,這個(gè)新的數(shù)據(jù)盡量保留原始數(shù)據(jù)有用的信息。簡化起見,設(shè)d=2,m=1,PCA原理如下圖所示。對于xy坐標(biāo)系中的二維數(shù)據(jù)(左圖),u坐標(biāo)方向就是就是第一軸方向,即主成分方向,z坐標(biāo)方向?yàn)榈诙S方向,u和z相互垂直。數(shù)據(jù)在第一軸方向上的離散程度最大,即方差最大,意味著數(shù)據(jù)點(diǎn)在第一軸的投影代表了原始數(shù)據(jù)的絕大部分信息。首先把第一坐標(biāo)軸移向橫坐標(biāo)軸(中圖),接著把所有數(shù)據(jù)點(diǎn)沿著第二軸向第一軸投影(右圖)。這樣PCA就實(shí)現(xiàn)了數(shù)據(jù)降維,由二維降為一維。如果是多維的數(shù)據(jù),可以通過迭代過程來執(zhí)行這個(gè)PCA轉(zhuǎn)換。首先沿著這個(gè)數(shù)據(jù)集有最大方差的方向計(jì)算一個(gè)單位矢量。由于這個(gè)方向包含了大部分的信息,選擇這個(gè)方向作為第一個(gè)軸。然后從與這個(gè)第一軸正交的矢量集合中,選擇一個(gè)新的單位矢量,使數(shù)據(jù)具有最大的方差。這是第二軸。繼續(xù)這個(gè)過程,直到找到代表新軸的d個(gè)新向量,將數(shù)據(jù)投影到這組新的坐標(biāo)軸上。確定好m個(gè)向量,拋棄m個(gè)向量外的其他向量,這樣主成分向量就保留最多的重要信息。

image.png

從數(shù)學(xué)上看,PCA可以看做是輸入數(shù)據(jù)X在向量空間W上的投影,向量空間W由輸入數(shù)據(jù)的協(xié)方差矩陣的前m個(gè)特征向量擴(kuò)展得到。假設(shè)輸入數(shù)據(jù)是一個(gè)維度為n * d的矩陣X,需要創(chuàng)建一個(gè)尺寸為n * m的矩陣T(PCA變換),可以使用公式T=WX得到。其中,W的每列對于矩陣XX^T的特征向量。

PCA用于數(shù)據(jù)降維已經(jīng)很多年了,但是對于分段線性和非線性問題是不起作用的。如下圖所示。原始數(shù)據(jù)是兩個(gè)同心圓,如果做PCA變換操作,變換后結(jié)果還是兩個(gè)同心圓。作為人來說,可以直觀地將兩個(gè)同心圓進(jìn)行區(qū)分,只要做極坐標(biāo)變換,兩個(gè)同心圓就會變成兩個(gè)豎向量,這樣數(shù)據(jù)就變成線性可分的了。

image.png

上圖表示了PCA算法在處理復(fù)雜數(shù)據(jù)時(shí)的局限。通常的數(shù)據(jù)集合(比如圖片、文本)都是非線性的,因此有必要找到新的方法來處理非線性的數(shù)據(jù)降維。采用深度神經(jīng)網(wǎng)絡(luò)模型就是一個(gè)不錯(cuò)的思路。

2.PCA的PyTorch實(shí)現(xiàn)

前面介紹的PCA算法使用了協(xié)方差矩陣,下面代碼中的PCA計(jì)算過程使用SVD?;赑yTorch的PCA方法如下:

from sklearn import datasets
import torch
import numpy as np
import matplotlib.pyplot as plt

def PCA(data, k=2):
    X = torch.from_numpy(data)
    X_mean = torch.mean(X, 0)
    X = X - X_mean.expand_as(X)
    # SVD
    U,S,V = torch.svd(torch.t(X))
    return torch.mm(X,U[:,:k])

iris = datasets.load_iris()
X = iris.data
y = iris.target
X_pca = PCA(X)
pca = X_pca.numpy()

plt.figure()
color = ['red','green','blue']
for i,target_name in enumerate(iris.target_names):
    plt.scatter(pca[y == i, 0], pca[y == i, 1], label=target_name, color=color[i])

plt.legend()
plt.title('PCA of IRIS dataset')
plt.show()
image.png

從圖中可以看出,對比原始數(shù)據(jù)和進(jìn)行PCA的數(shù)據(jù),PCA對數(shù)據(jù)有聚類的作用,有利于數(shù)據(jù)分類。

2.自編碼器

1986年Rumelhart提出了自編碼器的概念,并將其用于高維復(fù)雜數(shù)據(jù)的降維。自編碼器是一種無監(jiān)督學(xué)習(xí)算法,使用反向傳播,訓(xùn)練目標(biāo)是讓目標(biāo)值等于輸入值。需要指出,這時(shí)的自編碼器模型網(wǎng)絡(luò)的層比較淺,只有一個(gè)輸入層、一個(gè)隱含層、一個(gè)輸出層。Hinton和Salakhutdinov于2006年在《Reducing the dimensionality of data with neural networks》一文中提出了深度自編碼器。其顯著特點(diǎn)是,模型網(wǎng)絡(luò)的層較深,提高了學(xué)習(xí)能力。一般的,沒有特殊說明,常見的自編碼器都是深度自編碼器。

1.自編碼器原理

在前向神經(jīng)網(wǎng)絡(luò)中,每一個(gè)神經(jīng)網(wǎng)絡(luò)層都能夠?qū)W習(xí)更深刻的表征輸入。在卷積神經(jīng)網(wǎng)絡(luò)中,最后一個(gè)卷積層能用作輸入圖片的低維度表征。但在非監(jiān)督學(xué)習(xí)中,就不能用這種前向神經(jīng)網(wǎng)絡(luò)來做低維度表征。這些神經(jīng)網(wǎng)絡(luò)層的確包含輸入數(shù)據(jù)的信息,但是這些信息是當(dāng)前神經(jīng)網(wǎng)絡(luò)訓(xùn)練得到的,也只對當(dāng)前的任務(wù)或目標(biāo)函數(shù)有效。這樣可能會導(dǎo)致一個(gè)結(jié)果,即一些對當(dāng)前任務(wù)不那么重要的信息丟掉了,但是這些信息對其他分類任務(wù)至關(guān)重要。

在非監(jiān)督學(xué)習(xí)中,提出了新的神經(jīng)網(wǎng)絡(luò)。這種新的神經(jīng)網(wǎng)絡(luò)叫做自編碼器。自編碼器的結(jié)構(gòu)如下圖所示。輸入數(shù)據(jù)經(jīng)過編碼壓縮得到低維度向量,這個(gè)部分稱為編碼器,因?yàn)樗a(chǎn)生了低維度嵌入或者編碼。網(wǎng)絡(luò)的第二部分不同于在前向神經(jīng)網(wǎng)絡(luò)中把嵌入映射為輸出標(biāo)簽,而是把編碼器逆化,重建原始輸入,這個(gè)部分稱為解碼。

image.png

自編碼器是一種類似PCA的神經(jīng)網(wǎng)絡(luò),它是無監(jiān)督學(xué)習(xí)方法,目標(biāo)輸出就是其輸出。盡管自編碼器和PCA都能對數(shù)據(jù)進(jìn)行壓縮,但是自編碼器比PCA靈活和強(qiáng)大得多。在編碼過程中,自編碼器能夠表征線性變換,也能夠表征非線性變換;而PCA只能表征線性變換。自編碼器能夠用于數(shù)據(jù)的壓縮和恢復(fù),還可以用于數(shù)據(jù)的去噪。

2.自編碼器的PyTorch實(shí)現(xiàn)

為了展示自編碼器的性能,本節(jié)用PyTorch實(shí)現(xiàn)一個(gè)自編碼器,并用其進(jìn)行MNIST圖片分類。相對于PCA而言,自編碼器性能更加優(yōu)越。為了對比分析,分別用PyTorch實(shí)現(xiàn)自編碼器來進(jìn)行MNIST圖片分類,自編碼器的嵌入維度是2維。自編碼器的結(jié)構(gòu)如下圖所示。

image.png

基于PyTorch的自編碼器如下。
(1)加載庫和配置參數(shù)

import os
import pdb
import torch
import torchvision
from torch import nn
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import MNIST
from torchvision.utils import save_image
from torchvision import datasets
import matplotlib.pyplot as plt

torch.manual_seed(1)
batch_size = 128
learning_rate = 1e-2
num_epochs = 10

(2)下載數(shù)據(jù)和預(yù)處理

train_dataset = datasets.MNIST(root='./data',train=True,transform=transforms.ToTensor(),download=True)
test_dataset = datasets.MNIST(root='./data',train=False,transform=transforms.ToTensor())

train_loader = DataLoader(train_dataset, batch_size=batch_size,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size=10000,shuffle=False)

(3)自編碼器模型

class autoencoder(nn.Module):
    def __init__(self):
        super(autoencoder,self).__init__()
        self.encoder = nn.Sequential(
            nn.Linear(28*28,1000),
            nn.ReLU(True),
            nn.Linear(1000,500),
            nn.ReLU(True),
            nn.Linear(500,250),
            nn.ReLU(True),
            nn.Linear(250,2)
        )
        self.decoder = nn.Sequential(
            nn.Linear(2,250),
            nn.ReLU(True),
            nn.Linear(250,500),
            nn.ReLU(True),
            nn.Linear(500,1000),
            nn.ReLU(True),
            nn.Linear(1000,28*28),
            nn.Tanh()
        )
    
    def forward(self,x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x


model = autoencoder()
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(),lr=learning_rate,weight_decay=1e-5)

(4)模型訓(xùn)練

for epoch in range(num_epochs):
    for data in train_loader:
        img,_ = data
        img = img.view(img.size(0),-1)
        img = Variable(img)
        output = model(img)
        loss = criterion(output,img)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    print('epoch [{}/{}], loss: {:.4f}'.format(epoch+1,num_epochs,loss))

輸出如下:

epoch [1/10], loss: 0.0461
epoch [2/10], loss: 0.0449
epoch [3/10], loss: 0.0432
epoch [4/10], loss: 0.0385
epoch [5/10], loss: 0.0428
epoch [6/10], loss: 0.0432
epoch [7/10], loss: 0.0401
epoch [8/10], loss: 0.0426
epoch [9/10], loss: 0.0418
epoch [10/10], loss: 0.0461

(5)模型測試

model.eval()
eval_loss = 0
with torch.no_grad():
    for data in test_loader:
        img, label = data
        img = img.view(img.size(0),-1)
        img = Variable(img)
        label = Variable(label)
        out = model(img)
        y = label.data.numpy()
        plt.scatter(out[:,0],out[:,1],c=y)
        plt.colorbar()
        plt.title('autocoder of MNIST test dataset')
        plt.show()

結(jié)果如下:

image.png

在代碼中,編碼和解碼模塊的系數(shù)參考本節(jié)前面的自編碼器結(jié)構(gòu)圖。在文件中,生成多次迭代后的圖片,解碼后的圖片像素精度越來越高,逐漸和原圖比較類似。

在本節(jié)討論了自編碼器進(jìn)行圖片的壓縮和恢復(fù)。已經(jīng)探索了如何使用自動解碼通過發(fā)現(xiàn)數(shù)據(jù)點(diǎn)的強(qiáng)表征來總結(jié)數(shù)據(jù)集的內(nèi)容。這種降維機(jī)制在數(shù)據(jù)點(diǎn)比較豐富且包含相關(guān)信息時(shí)運(yùn)作良好。下面將討論使用自編碼器進(jìn)行圖片去噪的實(shí)例。

3.基于自編碼器的圖像去噪

本節(jié)將介紹一種圖像去噪的算法——去噪自編碼器。人的視覺機(jī)制能夠自動的忍受圖像的噪聲來識別圖片。自編碼器的目標(biāo)是要學(xué)習(xí)一個(gè)近似的恒等函數(shù),使得輸出近似等于輸入。去噪自編碼器采用隨機(jī)的部分帶噪輸入來解決恒等函數(shù)的問題,自編碼器能夠獲得輸入的良好表征,該表征使得自編碼器能夠進(jìn)行去噪或回復(fù)。

基于Autoendecoder的圖片去噪步驟如下。

(1)加載庫和配置參數(shù)

import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
from torch.utils.data import DataLoader
import torchvision
from torchvision import utils 
from torchvision import datasets 
from torchvision import transforms
import matplotlib.pyplot as plt

torch.manual_seed(1)
batch_size = 200
learning_rate = 1e-4
num_epochs = 20

(2)下載圖片庫訓(xùn)練集

train_dataset = datasets.MNIST(root='./data',train=True,transform=transforms.ToTensor(),target_transform=None,download=True)
test_dataset = datasets.MNIST(root='./data',train=False,transform=transforms.ToTensor())

train_loader = DataLoader(train_dataset, batch_size=batch_size,shuffle=True)
test_loader = DataLoader(test_dataset,batch_size=10000,shuffle=False)

(3)Encoder和Decoder模型設(shè)置

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder,self).__init__()
        self.layer1 =  nn.Sequential(
            nn.Conv2d(1,32,3,padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(32),
            nn.Conv2d(32,32,3,padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(32),
            nn.Conv2d(32,64,3,padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.Conv2d(64,64,3,padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.MaxPool2d(2,2)
        )
        self.layer2 = nn.Sequential(
            nn.Conv2d(64,128,3,padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.Conv2d(128,128,3,padding=1),
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.MaxPool2d(2,2),
            nn.Conv2d(128,256,3,padding=1),
            nn.ReLU()
        )
        
    def forward(self,x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.view(batch_size,-1)
        return out
    

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.layer1 = nn.Sequential(
            nn.ConvTranspose2d(256,128,3,2,1,1),
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.ConvTranspose2d(128,128,3,1,1),
            nn.ReLU(),
            nn.BatchNorm2d(128),
            nn.ConvTranspose2d(128,64,3,1,1),
            nn.ReLU(),
            nn.BatchNorm2d(64),
            nn.ConvTranspose2d(64,64,3,1,1),
            nn.ReLU(),
            nn.BatchNorm2d(64)
        )
        self.layer2 = nn.Sequential(
            nn.ConvTranspose2d(64,32,3,1,1),
            nn.ReLU(),
            nn.BatchNorm2d(32),
            nn.ConvTranspose2d(32,32,3,1,1),
            nn.ReLU(),
            nn.BatchNorm2d(32),
            nn.ConvTranspose2d(32,1,3,2,1,1),
            nn.ReLU()
        )
        
    def forward(self,x):
        out = x.view(batch_size,256,7,7)
        out = self.layer1(out)
        out = self.layer2(out)
        return out
    

encoder = Encoder().cuda()
decoder = Decoder().cuda()    

(4)Loss函數(shù)和優(yōu)化器

parameters = list(encoder.parameters()) + list(decoder.parameters())
loss_func = nn.MSELoss()
optimizer = torch.optim.Adam(params=parameters,lr=learning_rate)

(5)自編碼器訓(xùn)練

noise = torch.rand(batch_size,1,28,28)
for epoch in range(num_epochs):
    for image,label in train_loader:
        image_n = torch.mul(image+0.25,0.1*noise)
        image = Variable(image).cuda()
        image_n = Variable(image_n).cuda()
        optimizer.zero_grad()
        output = encoder(image_n)
        output = decoder(output)
        loss = loss_func(output,image)
        loss.backward()
        optimizer.step()
    print('epoch [{}/{}], loss: {:.4f}'.format(epoch+1, num_epochs,loss.item()))

輸出如下:

epoch [1/20], loss: 0.0123
epoch [2/20], loss: 0.0098
epoch [3/20], loss: 0.0090
epoch [4/20], loss: 0.0082
epoch [5/20], loss: 0.0076
epoch [6/20], loss: 0.0071
epoch [7/20], loss: 0.0068
epoch [8/20], loss: 0.0066
epoch [9/20], loss: 0.0063
epoch [10/20], loss: 0.0065
epoch [11/20], loss: 0.0063
epoch [12/20], loss: 0.0058
epoch [13/20], loss: 0.0056
epoch [14/20], loss: 0.0053
epoch [15/20], loss: 0.0052
epoch [16/20], loss: 0.0050
epoch [17/20], loss: 0.0051
epoch [18/20], loss: 0.0047
epoch [19/20], loss: 0.0045
epoch [20/20], loss: 0.0048

(6)帶噪圖片和去噪圖片的對比

img = image[0].cpu()
input_img = image_n[0].cpu()
output_img = output[0].cpu()
origin = img.data.numpy()
inp = input_img.data.numpy()
out = output_img.data.numpy()
plt.figure('denoising autoencoder')
plt.subplot(1,3,1)
plt.imshow(origin[0],cmap='gray')
plt.subplot(1,3,2)
plt.imshow(inp[0],cmap='gray')
plt.subplot(1,3,3)
plt.imshow(out[0],cmap='gray')
plt.show()
print(label[0])

結(jié)果如下:

tensor(4)
image.png

左圖是原圖,中圖是原圖加上噪聲,右圖是將中圖去噪??梢?,自編碼器去噪的效果還是非常不錯(cuò)的。

3.詞嵌入

1.詞嵌入原理

人類語言的詞匯量很大,語言表示的方法有很多種,詞嵌入就是最近涌現(xiàn)出來的優(yōu)秀方法。詞嵌入(word enbedding)是自然語言處理中語言模型與表征學(xué)習(xí)技術(shù)的統(tǒng)稱。從概念上講,它是指把一個(gè)維數(shù)為所有詞數(shù)的高維空間嵌入到一個(gè)維度低得多的連續(xù)向量空間中,每個(gè)單詞或詞組被映射為實(shí)數(shù)域上的向量。詞嵌入技術(shù)可以追溯到2000年約書亞-本希奧在一系列論文中使用了神經(jīng)概率語言模型使機(jī)器習(xí)得詞語的分布式表征,從而達(dá)到將詞語空間降維的目的。2013年谷歌一個(gè)托馬斯-米科洛維領(lǐng)導(dǎo)的團(tuán)隊(duì)發(fā)明了一套工具Word2Vec來進(jìn)行詞嵌入,向量空間模型的訓(xùn)練速度比以往的方法都快。此后,詞嵌入技術(shù)在語言模型、文本分類等自然語言處理中流行起來。

目前使用詞嵌入技術(shù)的流行訓(xùn)練軟件有有谷歌的Word2vec、臉書的fasttext和斯坦福大學(xué)的GloVe。詞向量是目前詞嵌入中運(yùn)用最多的技術(shù)。詞向量的使用方法大致有兩種:一是直接用于神經(jīng)網(wǎng)絡(luò)模型的輸入層,這個(gè)思路在語言模型、機(jī)器翻譯、文本分類、文本情感分析等應(yīng)用上廣泛使用;二是作為輔助特征擴(kuò)充現(xiàn)有模型,這個(gè)思路在命名實(shí)體識別和短語識別上進(jìn)一步提高了效果。

要對語言進(jìn)行處理,必須找到方法把詞匯符號化。最簡單、最直觀的方法就是用one-hot向量來表示詞。假設(shè)詞典中不同詞的數(shù)量為N,每個(gè)詞可以和從0到N-1的連續(xù)整數(shù)一一對應(yīng)。假設(shè)一個(gè)詞的相應(yīng)整數(shù)表示為i,為了得到該詞的one-hot向量表示,我們創(chuàng)建一個(gè)全0的長度為N的向量,并將其第i位設(shè)成1。

我們可以先舉三個(gè)例子:

The cat likes playing ball.
The kitty likes playing wool.
The dog likes playing ball.
The boy likes playing ball.

假設(shè)使用一個(gè)二維向量(a,b)來定義一個(gè)詞,其中a,b分別代表這個(gè)詞的一種屬性,比如a代表是否喜歡玩飛盤,b代表是否喜歡玩毛線,并且這個(gè)數(shù)值越大表示越喜歡,這樣我們就可以區(qū)分這三個(gè)詞了,為什么呢?

假設(shè),cat的詞向量是(-1,4),kitty的詞向量是(-2,5),dog的詞向量是(3,-2),boy的詞向量是(-2,-3)。我們怎么去定義它們之間的相似度呢?我們可以通過它們之間的夾角定義它們之間的相似度(這就是余弦相似度)。

image.png

上圖顯示出了不同的詞之間的夾角,我們可以發(fā)現(xiàn)kitty和cat是非常相似的,而dog和boy是不相似的。

使用one-hot詞向量并不是一個(gè)好選擇。一個(gè)主要的原因是,one-hot詞向量無法表達(dá)不同詞之間的相似度。例如,任何一對詞的one-hot向量的余弦相似度都為0。之前做分類問題的時(shí)候我們使用one-hot編碼,比如一共有5個(gè)類,那么屬于第2類的話,它的編碼就是(0,1,0,0,0)。對于分類問題,這樣當(dāng)然特別簡明,但是對于單詞,這樣做就不行了。比如有1000個(gè)不同的詞,那么使用one-hot這樣的方法效率就很低了,所以必須要使用另外一種方式去定義每一個(gè)單詞。這就引出了word embedding。2013年,谷歌團(tuán)隊(duì)發(fā)布了Word2vec工具。Word2vec工具主要包含兩個(gè)模型:即跳字模型(skip-gram)和連續(xù)詞袋模型(Continuous Bags of Words,即CBOW),以及兩種高效訓(xùn)練的方法,即負(fù)采樣(negative sampling)和層序Softmax(hierarchical softmax)。值得一提的是,Word2vec詞向量可以較好地表達(dá)不同詞之間的相似和類比關(guān)系。Word2vec自提出后被廣泛應(yīng)用在自然語言處理任務(wù)中。它的模型和訓(xùn)練方法也啟發(fā)了很多后續(xù)的詞向量模型。本節(jié)將重點(diǎn)介紹Word2vec的模型和訓(xùn)練方法。

(1)跳字模型

在跳字模型中,我們用一個(gè)詞來預(yù)測它在文本序列周圍的詞。例如給定文本序列“the”、“man”、“hit”、“his”和“son”。跳字模型所關(guān)心的是,給定“hit”,生成它的臨近詞“the”、“man”、“his”和“son”的概率。在這個(gè)例子中,“hit”叫中心詞,“the”、“man”、“his”和“son”叫背景詞。由于“hit”只生成與它距離不超過2的背景詞,因此該時(shí)間窗口大小為2。

(2)連續(xù)詞袋模型

連續(xù)詞袋模型與跳字模型類似。與跳字模型最大的不同是,連續(xù)詞袋模型中用一個(gè)中心詞在文本序列周圍的詞來預(yù)測該中心詞。例如給定文本序列“the”、“man”、“hit”、“his”和“son”。連續(xù)詞袋模型所關(guān)心的是,“the”、“man”、“his”和“son”一起生成中心詞“hit”的概率。我們可以看到,無論是跳字模型還是連續(xù)詞袋模型,每一步計(jì)算的開銷和詞典大小相關(guān)。當(dāng)詞典較大時(shí),例如幾十萬到上百萬,這種訓(xùn)練方法的計(jì)算開銷會很大。因此使用上述訓(xùn)練方法在實(shí)踐中是有難度的。

跳字模型和連續(xù)詞袋模型如下圖所示。

image.png

我們將使用近似的方法來計(jì)算這些梯度,從而減少計(jì)算開銷。常用的近似訓(xùn)練方法包括負(fù)采樣和層序Softmax。

在PyTorch中,詞嵌入使用nn.embedding:

class torch.nn.Embedding(num_embeddings, embedding_dim, padding_idx=None, max_norm=None, norm_type=2, scale_grad_by_freq=False, sparse=False)

參數(shù)含義如下:

  • num_embeddings:用于詞嵌入的字典大小。
  • embedding_dim:詞嵌入的維度。
  • padding_idx:可選項(xiàng),如果選擇,對該index上的結(jié)果填充0。
  • max_norm:可選項(xiàng),如果選擇,對詞嵌入歸一化時(shí),設(shè)置歸一化的最大值。
  • norm_type:可選項(xiàng),如果選擇,對詞嵌入歸一化時(shí),設(shè)置p-norm的p值。
  • scale_grad_by_freq:可選項(xiàng),如果選擇,在Mini-Batch時(shí),根據(jù)詞頻對梯度進(jìn)行規(guī)整。
  • sparse:可選項(xiàng),如果選擇,梯度W、R、T權(quán)值矩陣將是一個(gè)稀疏張量。

常用的只有兩個(gè)參數(shù):num_embeddings和embedding_dim。

詞嵌入的簡單使用例子如下:

import torch
import torch.nn as nn
import torch.autograd as autograd
word_to_idx = {"hello":0, "PyTorch":1}
embeds = nn.Embedding(2,5)
lookup_tensor = torch.LongTensor([word_to_idx["PyTorch"]])
hello_embed = embeds(autograd.Variable(lookup_tensor))

首先把每個(gè)單詞用一個(gè)數(shù)字去表示,“hello”用0表示,“PyTorch”用1表示。然后定義Embedding,這里nn.Embedding(2,5)表示有2個(gè)單詞,每個(gè)單詞表示成5個(gè)維度,其實(shí)就是2 * 5的矩陣。注意,這里建立的詞向量只是初始的詞向量,并沒有經(jīng)過任何修改和優(yōu)化,我們需要建立神經(jīng)網(wǎng)絡(luò),通過訓(xùn)練修改word embedding中的參數(shù),使得每一個(gè)詞向量能夠表示每一個(gè)不同的詞。

我們可以查看一下embedding的內(nèi)容:

print(hello_embed)

輸出如下:

tensor([[-0.7846, -0.1158, -1.5107, -1.2108, -0.2174]],
       grad_fn=<EmbeddingBackward>)

這就是輸出的“hello”這個(gè)詞的word embedding。

2.基于詞向量的語言模型的PyTorch實(shí)現(xiàn)

下面使用PyTorch實(shí)現(xiàn)一個(gè)基于詞向量的語言模型:通過某個(gè)單詞的前兩個(gè)單詞來預(yù)測這個(gè)單詞。

(1)加載庫和設(shè)置參數(shù)

import torch
import torch.autograd as autograd
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
torch.manual_seed(1)
CONTEXT_SIZE = 2
EMBEDDING_DIM = 10
N_EPHCNS = 1000

CONTEXT_SIZE表示想由前面的幾個(gè)單詞來預(yù)測這個(gè)單詞,這里設(shè)置為2,就是說希望通過這個(gè)單詞的前兩個(gè)單詞來預(yù)測這個(gè)單詞。EMBEDDING_DIM表示word embedding的維數(shù)。
(2)數(shù)據(jù)準(zhǔn)備

# 語料
test_sentence = """Word embeddings are dense vectors of real numbers, one per word in your vocabulary. 
IN NLP, it is almost always the case that your features are words! But how should you represent a word 
in a computer? You could store its ascii character representation, but that only tells you what the word 
is, it doesn't say much about what it means (you might be able to derive its part of speech from its 
affiex, or propertites from its capitalization, but not much). Even more, in what sense could you combine 
these representations?""".split()
# 三元模型
trigrams = [([test_sentence[i],test_sentence[i+1],test_sentence[i+2]]) for i in range(len(test_sentence)-2)]
# 詞典
vocab = set(test_sentence)
word_to_idx = {word:i for i,word in enumerate(vocab)}
idx_to_word = {i:word for i,word in enumerate(vocab)}

將句子中切好的單詞,每三個(gè)分為一組,前兩個(gè)作為傳入的數(shù)據(jù),第三個(gè)作為預(yù)測的結(jié)果。并給每個(gè)單詞編碼,以便下面得到embedding的詞向量。
(3)語言模型

class NGramLanguageModeler(nn.Module):
    def __init__(self,vocab_size,embedding_dim,context_size):
        super(NGramLanguageModeler,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):
        embeds = self.embeddings(inputs).view((1,-1))
        out = F.relu(self.linear1(embeds))
        out = self.linear2(out)
        log_probs = F.log_softmax(out,dim=len(out))
        return log_probs

該模型需要傳入的參數(shù)是詞典大小vocab_size,詞向量維度embedding_dim和預(yù)測需要前面幾個(gè)單詞context_size。然后在向前傳播中,首先傳入單詞得到的詞向量,比如在該模型中傳入兩個(gè)詞,得到的詞向量是(2,100),然后將詞向量展開成(1,200),接著傳入一個(gè)線性模型,經(jīng)過ReLU激活函數(shù)再傳入一個(gè)線性模型,輸出的維數(shù)是詞典大小,可以看成是一個(gè)分類問題,要最大化預(yù)測單詞的概率,最后經(jīng)過一個(gè)log_softmax激活函數(shù)。
(4)損失函數(shù)和優(yōu)化器

losses = []
loss_function = nn.NLLLoss()
model = NGramLanguageModeler(len(vocab),EMBEDDING_DIM,CONTEXT_SIZE)
optimizer = optim.SGD(model.parameters(),lr=0.001)

(5)訓(xùn)練語言模型

for epoch in range(N_EPHCNS):
    total_loss = torch.Tensor([0])
    for context1,context2,target in trigrams:
        # 得到詞向量
        context_idxs = [word_to_idx[context1],word_to_idx[context2]]
        context_var = autograd.Variable(torch.LongTensor(context_idxs))
        # 梯度初始化
        model.zero_grad()
        # 前向傳播
        log_probs = model(context_var)
        # 計(jì)算損失
        loss = loss_function(log_probs,autograd.Variable(torch.LongTensor([word_to_idx[target]])))
        # 后向傳播
        loss.backward()
        # 更新梯度
        optimizer.step()
        # 累計(jì)損失
        total_loss += loss.data
    print('\r epoch[{}] - loss: {:.6f}'.format(epoch,total_loss[0]))

輸出如下:

epoch[0] - loss: 384.948334
 epoch[1] - loss: 383.013763
 epoch[2] - loss: 381.093689
 epoch[3] - loss: 379.185699
 epoch[4] - loss: 377.290375
 epoch[5] - loss: 375.404846
 epoch[6] - loss: 373.529083
 epoch[7] - loss: 371.662842
 epoch[8] - loss: 369.805328
 epoch[9] - loss: 367.956299
 epoch[10] - loss: 366.113464
……
 epoch[990] - loss: 5.137405
 epoch[991] - loss: 5.133943
 epoch[992] - loss: 5.130613
 epoch[993] - loss: 5.127238
 epoch[994] - loss: 5.123822
 epoch[995] - loss: 5.120545
 epoch[996] - loss: 5.117153
 epoch[997] - loss: 5.113895
 epoch[998] - loss: 5.110688
 epoch[999] - loss: 5.107241

進(jìn)行訓(xùn)練,這里一共跑了1000個(gè)批次。在每個(gè)epoch中,context1,context2代表預(yù)測單詞的前面兩個(gè)單詞,target代表要預(yù)測的詞。然后記住需要將它們轉(zhuǎn)換成Variable,接著進(jìn)入網(wǎng)絡(luò)得到結(jié)果,最后通過loss函數(shù)得到損失,進(jìn)行反向傳播,更新參數(shù)。
(6)預(yù)測結(jié)果

errors = 0
for i in range(len(trigrams)):
    word1,word2,label = trigrams[i]
    words = autograd.Variable(torch.LongTensor([word_to_idx[word1],word_to_idx[word2]]))
    out = model(words)
    _,predict_label = torch.max(out,1)
    predict_word = idx_to_word[predict_label.item()]
    if label != predict_word:
        errors += 1
    print("real word is '{}', predict word is '{}'".format(label,predict_word))
print("error rate is {}/{} = {:.6f}".format(errors, len(trigrams),errors/len(trigrams)))

輸出如下:

real word is 'are', predict word is 'are'
real word is 'dense', predict word is 'dense'
real word is 'vectors', predict word is 'vectors'
real word is 'of', predict word is 'of'
real word is 'real', predict word is 'real'
real word is 'numbers,', predict word is 'numbers,'
real word is 'one', predict word is 'one'
real word is 'per', predict word is 'per'
real word is 'word', predict word is 'word'
real word is 'in', predict word is 'in'
real word is 'your', predict word is 'a'
real word is 'vocabulary.', predict word is 'vocabulary.'
real word is 'IN', predict word is 'IN'
real word is 'NLP,', predict word is 'NLP,'
real word is 'it', predict word is 'it'
real word is 'is', predict word is 'is'
real word is 'almost', predict word is 'almost'
real word is 'always', predict word is 'always'
real word is 'the', predict word is 'the'
real word is 'case', predict word is 'case'
real word is 'that', predict word is 'that'
real word is 'your', predict word is 'your'
real word is 'features', predict word is 'features'
real word is 'are', predict word is 'are'
real word is 'words!', predict word is 'words!'
real word is 'But', predict word is 'But'
real word is 'how', predict word is 'how'
real word is 'should', predict word is 'should'
real word is 'you', predict word is 'you'
real word is 'represent', predict word is 'represent'
real word is 'a', predict word is 'a'
real word is 'word', predict word is 'word'
real word is 'in', predict word is 'in'
real word is 'a', predict word is 'a'
real word is 'computer?', predict word is 'computer?'
real word is 'You', predict word is 'You'
real word is 'could', predict word is 'could'
real word is 'store', predict word is 'store'
real word is 'its', predict word is 'its'
real word is 'ascii', predict word is 'ascii'
real word is 'character', predict word is 'character'
real word is 'representation,', predict word is 'representation,'
real word is 'but', predict word is 'but'
real word is 'that', predict word is 'that'
real word is 'only', predict word is 'only'
real word is 'tells', predict word is 'tells'
real word is 'you', predict word is 'you'
real word is 'what', predict word is 'what'
real word is 'the', predict word is 'the'
real word is 'word', predict word is 'word'
real word is 'is,', predict word is 'is,'
real word is 'it', predict word is 'it'
real word is 'doesn't', predict word is 'doesn't'
real word is 'say', predict word is 'say'
real word is 'much', predict word is 'much'
real word is 'about', predict word is 'about'
real word is 'what', predict word is 'what'
real word is 'it', predict word is 'it'
real word is 'means', predict word is 'means'
real word is '(you', predict word is '(you'
real word is 'might', predict word is 'might'
real word is 'be', predict word is 'be'
real word is 'able', predict word is 'able'
real word is 'to', predict word is 'to'
real word is 'derive', predict word is 'derive'
real word is 'its', predict word is 'its'
real word is 'part', predict word is 'part'
real word is 'of', predict word is 'of'
real word is 'speech', predict word is 'speech'
real word is 'from', predict word is 'from'
real word is 'its', predict word is 'its'
real word is 'affiex,', predict word is 'capitalization,'
real word is 'or', predict word is 'or'
real word is 'propertites', predict word is 'propertites'
real word is 'from', predict word is 'from'
real word is 'its', predict word is 'its'
real word is 'capitalization,', predict word is 'capitalization,'
real word is 'but', predict word is 'but'
real word is 'not', predict word is 'not'
real word is 'much).', predict word is 'much).'
real word is 'Even', predict word is 'Even'
real word is 'more,', predict word is 'more,'
real word is 'in', predict word is 'in'
real word is 'what', predict word is 'what'
real word is 'sense', predict word is 'sense'
real word is 'could', predict word is 'could'
real word is 'you', predict word is 'you'
real word is 'combine', predict word is 'combine'
real word is 'these', predict word is 'these'
real word is 'representations?', predict word is 'representations?'
error rate is 2/90 = 0.022222

根據(jù)這個(gè)例子打印的結(jié)果可知:該三元語言模型的錯(cuò)誤率近似為0.022,即正確率達(dá)到了97.8%,效果還是很不錯(cuò)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容