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

前面介紹了深度神經(jīng)網(wǎng)絡(luò)和卷積神經(jīng)網(wǎng)絡(luò),這些神經(jīng)網(wǎng)絡(luò)有個特點:輸入的向量越大慕购,訓(xùn)練得到的模型越大。但是播急,擁有大量參數(shù)模型的代價是昂貴的脓钾,它需要大量的數(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ù)來解決這個問題,具體思路是:從未標(biāo)簽數(shù)據(jù)上學(xué)習(xí)數(shù)據(jù)的表征飞崖,用這些表征來解決監(jiān)督學(xué)習(xí)問題烂叔。

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

image.png

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

1.PCA算法

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

1.PCA原理

PCA的基本原理是從大量數(shù)據(jù)中找到少量的主成分變量枚赡,在數(shù)據(jù)維度降低的情況下,盡可能地保留原始數(shù)據(jù)的信息谓谦。比如贫橙,假設(shè)一個d維的數(shù)據(jù),找到一個新的m維數(shù)據(jù)反粥,其中m<d卢肃,這個新的數(shù)據(jù)盡量保留原始數(shù)據(jù)有用的信息疲迂。簡化起見,設(shè)d=2莫湘,m=1尤蒿,PCA原理如下圖所示。對于xy坐標(biāo)系中的二維數(shù)據(jù)(左圖)幅垮,u坐標(biāo)方向就是就是第一軸方向腰池,即主成分方向,z坐標(biāo)方向為第二軸方向忙芒,u和z相互垂直示弓。數(shù)據(jù)在第一軸方向上的離散程度最大,即方差最大呵萨,意味著數(shù)據(jù)點在第一軸的投影代表了原始數(shù)據(jù)的絕大部分信息奏属。首先把第一坐標(biāo)軸移向橫坐標(biāo)軸(中圖),接著把所有數(shù)據(jù)點沿著第二軸向第一軸投影(右圖)潮峦。這樣PCA就實現(xiàn)了數(shù)據(jù)降維囱皿,由二維降為一維。如果是多維的數(shù)據(jù)忱嘹,可以通過迭代過程來執(zhí)行這個PCA轉(zhuǎn)換嘱腥。首先沿著這個數(shù)據(jù)集有最大方差的方向計算一個單位矢量。由于這個方向包含了大部分的信息德谅,選擇這個方向作為第一個軸爹橱。然后從與這個第一軸正交的矢量集合中,選擇一個新的單位矢量窄做,使數(shù)據(jù)具有最大的方差愧驱。這是第二軸。繼續(xù)這個過程椭盏,直到找到代表新軸的d個新向量组砚,將數(shù)據(jù)投影到這組新的坐標(biāo)軸上。確定好m個向量掏颊,拋棄m個向量外的其他向量糟红,這樣主成分向量就保留最多的重要信息。

image.png

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

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

image.png

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

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

前面介紹的PCA算法使用了協(xié)方差矩陣孩等,下面代碼中的PCA計算過程使用SVD艾君。基于PyTorch的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)值等于輸入值。需要指出步清,這時的自編碼器模型網(wǎng)絡(luò)的層比較淺要门,只有一個輸入層、一個隱含層廓啊、一個輸出層欢搜。Hinton和Salakhutdinov于2006年在《Reducing the dimensionality of data with neural networks》一文中提出了深度自編碼器。其顯著特點是谴轮,模型網(wǎng)絡(luò)的層較深炒瘟,提高了學(xué)習(xí)能力。一般的书聚,沒有特殊說明唧领,常見的自編碼器都是深度自編碼器藻雌。

1.自編碼器原理

在前向神經(jīng)網(wǎng)絡(luò)中,每一個神經(jīng)網(wǎng)絡(luò)層都能夠?qū)W習(xí)更深刻的表征輸入斩个。在卷積神經(jīng)網(wǎng)絡(luò)中胯杭,最后一個卷積層能用作輸入圖片的低維度表征。但在非監(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)致一個結(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)過編碼壓縮得到低維度向量最住,這個部分稱為編碼器钞澳,因為它產(chǎn)生了低維度嵌入或者編碼。網(wǎng)絡(luò)的第二部分不同于在前向神經(jīng)網(wǎng)絡(luò)中把嵌入映射為輸出標(biāo)簽涨缚,而是把編碼器逆化轧粟,重建原始輸入,這個部分稱為解碼脓魏。

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實現(xiàn)

為了展示自編碼器的性能卓箫,本節(jié)用PyTorch實現(xiàn)一個自編碼器载矿,并用其進(jìn)行MNIST圖片分類。相對于PCA而言,自編碼器性能更加優(yōu)越闷盔。為了對比分析弯洗,分別用PyTorch實現(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ù)點的強(qiáng)表征來總結(jié)數(shù)據(jù)集的內(nèi)容沪摄。這種降維機(jī)制在數(shù)據(jù)點比較豐富且包含相關(guān)信息時運作良好。下面將討論使用自編碼器進(jìn)行圖片去噪的實例辖众。

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

本節(jié)將介紹一種圖像去噪的算法——去噪自編碼器卓起。人的視覺機(jī)制能夠自動的忍受圖像的噪聲來識別圖片。自編碼器的目標(biāo)是要學(xué)習(xí)一個近似的恒等函數(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

左圖是原圖变骡,中圖是原圖加上噪聲,右圖是將中圖去噪芭逝∷担可見,自編碼器去噪的效果還是非常不錯的旬盯。

3.詞嵌入

1.詞嵌入原理

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

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

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

我們可以先舉三個例子:

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

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

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

image.png

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

使用one-hot詞向量并不是一個好選擇猖吴。一個主要的原因是摔刁,one-hot詞向量無法表達(dá)不同詞之間的相似度。例如海蔽,任何一對詞的one-hot向量的余弦相似度都為0共屈。之前做分類問題的時候我們使用one-hot編碼,比如一共有5個類党窜,那么屬于第2類的話拗引,它的編碼就是(0,1,0,0,0)。對于分類問題幌衣,這樣當(dāng)然特別簡明矾削,但是對于單詞,這樣做就不行了豁护。比如有1000個不同的詞哼凯,那么使用one-hot這樣的方法效率就很低了,所以必須要使用另外一種方式去定義每一個單詞楚里。這就引出了word embedding断部。2013年,谷歌團(tuán)隊發(fā)布了Word2vec工具班缎。Word2vec工具主要包含兩個模型:即跳字模型(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é)將重點介紹Word2vec的模型和訓(xùn)練方法右冻。

(1)跳字模型

在跳字模型中,我們用一個詞來預(yù)測它在文本序列周圍的詞著拭。例如給定文本序列“the”纱扭、“man”、“hit”儡遮、“his”和“son”乳蛾。跳字模型所關(guān)心的是,給定“hit”,生成它的臨近詞“the”肃叶、“man”蹂随、“his”和“son”的概率。在這個例子中因惭,“hit”叫中心詞岳锁,“the”、“man”蹦魔、“his”和“son”叫背景詞激率。由于“hit”只生成與它距離不超過2的背景詞,因此該時間窗口大小為2勿决。

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

連續(xù)詞袋模型與跳字模型類似乒躺。與跳字模型最大的不同是,連續(xù)詞袋模型中用一個中心詞在文本序列周圍的詞來預(yù)測該中心詞剥险。例如給定文本序列“the”聪蘸、“man”、“hit”表制、“his”和“son”健爬。連續(xù)詞袋模型所關(guān)心的是,“the”么介、“man”娜遵、“his”和“son”一起生成中心詞“hit”的概率。我們可以看到壤短,無論是跳字模型還是連續(xù)詞袋模型设拟,每一步計算的開銷和詞典大小相關(guān)。當(dāng)詞典較大時久脯,例如幾十萬到上百萬纳胧,這種訓(xùn)練方法的計算開銷會很大。因此使用上述訓(xùn)練方法在實踐中是有難度的帘撰。

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

image.png

我們將使用近似的方法來計算這些梯度,從而減少計算開銷摧找。常用的近似訓(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:可選項,如果選擇综苔,對該index上的結(jié)果填充0惩系。
  • max_norm:可選項位岔,如果選擇,對詞嵌入歸一化時蛆挫,設(shè)置歸一化的最大值赃承。
  • norm_type:可選項,如果選擇悴侵,對詞嵌入歸一化時瞧剖,設(shè)置p-norm的p值。
  • scale_grad_by_freq:可選項可免,如果選擇抓于,在Mini-Batch時,根據(jù)詞頻對梯度進(jìn)行規(guī)整浇借。
  • sparse:可選項捉撮,如果選擇,梯度W妇垢、R巾遭、T權(quán)值矩陣將是一個稀疏張量。

常用的只有兩個參數(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))

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

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

print(hello_embed)

輸出如下:

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

這就是輸出的“hello”這個詞的word embedding。

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

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

(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表示想由前面的幾個單詞來預(yù)測這個單詞,這里設(shè)置為2圃酵,就是說希望通過這個單詞的前兩個單詞來預(yù)測這個單詞柳畔。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)}

將句子中切好的單詞郭赐,每三個分為一組薪韩,前兩個作為傳入的數(shù)據(jù)确沸,第三個作為預(yù)測的結(jié)果。并給每個單詞編碼俘陷,以便下面得到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ù)測需要前面幾個單詞context_size拉盾。然后在向前傳播中桨菜,首先傳入單詞得到的詞向量,比如在該模型中傳入兩個詞捉偏,得到的詞向量是(2,100)倒得,然后將詞向量展開成(1,200),接著傳入一個線性模型夭禽,經(jīng)過ReLU激活函數(shù)再傳入一個線性模型霞掺,輸出的維數(shù)是詞典大小,可以看成是一個分類問題讹躯,要最大化預(yù)測單詞的概率菩彬,最后經(jīng)過一個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)
        # 計算損失
        loss = loss_function(log_probs,autograd.Variable(torch.LongTensor([word_to_idx[target]])))
        # 后向傳播
        loss.backward()
        # 更新梯度
        optimizer.step()
        # 累計損失
        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個批次骗灶。在每個epoch中,context1,context2代表預(yù)測單詞的前面兩個單詞酷麦,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ù)這個例子打印的結(jié)果可知:該三元語言模型的錯誤率近似為0.022琴昆,即正確率達(dá)到了97.8%,效果還是很不錯的馆揉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末业舍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子升酣,更是在濱河造成了極大的恐慌舷暮,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件噩茄,死亡現(xiàn)場離奇詭異下面,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绩聘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門沥割,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耗啦,“玉大人,你說我怎么就攤上這事机杜≈慕玻” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵椒拗,是天一觀的道長似将。 經(jīng)常有香客問我,道長陡叠,這世上最難降的妖魔是什么玩郊? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮枉阵,結(jié)果婚禮上译红,老公的妹妹穿的比我還像新娘。我一直安慰自己兴溜,他們只是感情好侦厚,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拙徽,像睡著了一般刨沦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膘怕,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天想诅,我揣著相機(jī)與錄音,去河邊找鬼岛心。 笑死来破,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忘古。 我是一名探鬼主播徘禁,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼髓堪!你這毒婦竟也來了送朱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤干旁,失蹤者是張志新(化名)和其女友劉穎驶沼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體争群,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡商乎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祭阀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹉戚。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖专控,靈堂內(nèi)的尸體忽然破棺而出抹凳,到底是詐尸還是另有隱情,我是刑警寧澤伦腐,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布赢底,位于F島的核電站,受9級特大地震影響柏蘑,放射性物質(zhì)發(fā)生泄漏幸冻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一咳焚、第九天 我趴在偏房一處隱蔽的房頂上張望洽损。 院中可真熱鬧,春花似錦革半、人聲如沸碑定。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽更鲁。三九已至魂那,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碘赖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工外构, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留普泡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓典勇,卻偏偏與公主長得像劫哼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子割笙,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容