使用PyTorch進(jìn)行深度學(xué)習(xí)

譯者:bdqfork

作者: Robert Guthrie

深度學(xué)習(xí)構(gòu)建模塊:仿射映射, 非線性函數(shù)以及目標(biāo)函數(shù)

深度學(xué)習(xí)表現(xiàn)為使用更高級(jí)的方法將線性函數(shù)和非線性函數(shù)進(jìn)行組合。非線性函數(shù)的引入使得訓(xùn)練出來的模型更加強(qiáng)大。在本節(jié)中,我們將學(xué)習(xí)這些核心組件,建立目標(biāo)函數(shù),并理解模型是如何構(gòu)建的。

仿射映射

深度學(xué)習(xí)的核心組件之一是仿射映射,仿射映射是一個(gè)關(guān)于矩陣A和向量x,bf(x)函數(shù),如下所示:

image

需要訓(xùn)練的參數(shù)就是該公式中的Ab。

PyTorch以及大多數(shù)的深度學(xué)習(xí)框架所做的事情都與傳統(tǒng)的線性代數(shù)有些不同。它的映射輸入是行而不是列。也就是說,下面代碼輸出的第i行是輸入的第i行進(jìn)行A變換,并加上偏移項(xiàng)的結(jié)果。看下面的例子:

# Author: Robert Guthrie

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

lin = nn.Linear(5, 3)  # maps from R^5 to R^3, parameters A, b
# data is 2x5.  A maps from 5 to 3... can we map "data" under A?
data = torch.randn(2, 5)
print(lin(data))  # yes

輸出:

tensor([[ 0.1755, -0.3268, -0.5069],
        [-0.6602,  0.2260,  0.1089]], grad_fn=<AddmmBackward>)

非線性函數(shù)

首先,注意以下這個(gè)例子,它將解釋為什么我們需要非線性函數(shù)。假設(shè)我們有兩個(gè)仿射映射 f(x) = Ax + bg(x) = Cx + d 。那么 f(g(x)) 又是什么呢?

image

AC 是一個(gè)矩陣,Ad + b是一個(gè)向量,可以看出,兩個(gè)仿射映射的組合還是一個(gè)仿射映射。

由此可以看出,使用多個(gè)仿射映射的鏈?zhǔn)浇M合形成神經(jīng)網(wǎng)絡(luò),并不會(huì)對(duì)提升模型的性能,和一個(gè)仿射映射沒什么區(qū)別。

但是,如果我們?cè)趦蓚€(gè)仿射映射之間引入非線性,那么結(jié)果就大不一樣了,我們可以構(gòu)建出一個(gè)高性能的模型。

最常用的核心的非線性函數(shù)有:tanh(x),σ(x),ReLU(x)。你可能會(huì)想:“為什么是這些函數(shù)?明明有其他更多的非線性函數(shù)?!边@些函數(shù)常用的原因是它們擁有可以容易計(jì)算的梯度,而計(jì)算梯度是學(xué)習(xí)的本質(zhì)。例如

image

注意:盡管你可能在AI課程的介紹中學(xué)習(xí)了一些神經(jīng)網(wǎng)絡(luò),在這些神經(jīng)網(wǎng)絡(luò)中σ(x)是默認(rèn)非線性的,但是通常在實(shí)際使用的過程中都會(huì)避開它們。這是因?yàn)楫?dāng)參數(shù)的絕對(duì)值增長(zhǎng)時(shí),梯度會(huì)很快消失。小梯度意味著很難學(xué)習(xí)。大部分都會(huì)選擇tanh或者ReLU。

# In pytorch, most non-linearities are in torch.functional (we have it imported as F)
# Note that non-linearites typically don't have parameters like affine maps do.
# That is, they don't have weights that are updated during training.
data = torch.randn(2, 2)
print(data)
print(F.relu(data))

輸出:

tensor([[-0.5404, -2.2102],
        [ 2.1130, -0.0040]])
tensor([[0.0000, 0.0000],
        [2.1130, 0.0000]])

Softmax和概率

Softmax(x)也是一個(gè)非線性函數(shù),但它的特殊之處在于,它通常是神經(jīng)網(wǎng)絡(luò)的最后一個(gè)操作。這是因?yàn)樗邮軐?shí)數(shù)向量,并且返回一個(gè)概率分布。它的定義如下。設(shè)x為實(shí)數(shù)向量(正、負(fù),無論什么,沒有約束)。然后Softmax(x)的第i個(gè)分量是:

image

很明顯,輸出的是一個(gè)概率分布:每一個(gè)元素都非負(fù)且和為1。

你也可以認(rèn)為這只是一個(gè)對(duì)輸入的元素進(jìn)行的求冪運(yùn)算符,使所有的內(nèi)容都非負(fù),然后除以規(guī)范化常量。

# Softmax is also in torch.nn.functional
data = torch.randn(5)
print(data)
print(F.softmax(data, dim=0))
print(F.softmax(data, dim=0).sum())  # Sums to 1 because it is a distribution!
print(F.log_softmax(data, dim=0))  # theres also log_softmax

輸出:

tensor([ 1.3800, -1.3505,  0.3455,  0.5046,  1.8213])
tensor([0.2948, 0.0192, 0.1048, 0.1228, 0.4584])
tensor(1.)
tensor([-1.2214, -3.9519, -2.2560, -2.0969, -0.7801])

目標(biāo)函數(shù)

目標(biāo)函數(shù)是訓(xùn)練網(wǎng)絡(luò)使其最小化的函數(shù)(因此,它常常被稱作損失函數(shù)或者成本函數(shù))。這需要首先選擇一個(gè)訓(xùn)練實(shí)例,通過神經(jīng)網(wǎng)絡(luò)運(yùn)行它,計(jì)算輸出的損失。然后通過損失函數(shù)的導(dǎo)數(shù)來更新模型的參數(shù)。直觀來講,如果你的模型完全相信它的結(jié)果,而它的結(jié)果是錯(cuò)誤的,那么損失將會(huì)很高。

在你的訓(xùn)練實(shí)例中最小化損失函數(shù)的目的是使你的網(wǎng)絡(luò)擁有很好的泛化能力,可以在開發(fā)數(shù)據(jù)集,測(cè)試數(shù)據(jù)集或者生產(chǎn)中擁有很小的損失。損失函數(shù)的一個(gè)例子是負(fù)對(duì)數(shù)似然損失函數(shù),這個(gè)函數(shù)經(jīng)常在多級(jí)分類中出現(xiàn)。在監(jiān)督多級(jí)分類中,這意味著訓(xùn)練網(wǎng)絡(luò)最小化正確輸出的負(fù)對(duì)數(shù)概率(或等效的,最大化正確輸出的對(duì)數(shù)概率)。

優(yōu)化和訓(xùn)練

那么,我們?cè)撛趺从?jì)算函數(shù)實(shí)例的損失函數(shù)呢?我們應(yīng)該做什么呢?我們?cè)谥傲私獾?,Tensor知道如何計(jì)算梯度以及計(jì)算梯度相關(guān)的東西。由于我們的損失是一個(gè)Tensor,我們可以計(jì)算梯度以及所有用來計(jì)算梯度的參數(shù)。然后我們可以進(jìn)行標(biāo)準(zhǔn)梯度更新。設(shè) θ為我們的參數(shù),L(θ)為損失函數(shù),η一個(gè)正的學(xué)習(xí)率。然后:

image

目前,有大量的算法和積極的研究試圖做一些除了這種普通的梯度更新以外的事情。許多人嘗試去基于訓(xùn)練時(shí)發(fā)生的事情來改變學(xué)習(xí)率。但是,你不需要擔(dān)心這些特殊的算法到底在干什么,除非你真的很感興趣。Torch提供了大量的算法在torch.optim包中,且全部都是透明的。使用復(fù)雜的算法和使用最簡(jiǎn)單的梯度更新沒有什么區(qū)別。嘗試不同的更新算法和在更新算法中使用不同的參數(shù)(例如不同的初始學(xué)習(xí)率)對(duì)于優(yōu)化你的網(wǎng)絡(luò)的性能很重要。通常,僅僅將普通的SGD替換成一個(gè)例如Adam或者RMSProp優(yōu)化器都可以顯著的提升性能。

使用PyTorch創(chuàng)建網(wǎng)絡(luò)組件

在我們繼續(xù)關(guān)注NLP之前,讓我們先使用PyTorch構(gòu)建一個(gè)只用仿射映射和非線性函數(shù)組成的網(wǎng)絡(luò)示例。我們也將了解如何計(jì)算損失函數(shù),使用PyTorch內(nèi)置的負(fù)對(duì)數(shù)似然函數(shù),并通過反向傳播更新參數(shù)。

所有的網(wǎng)絡(luò)組件應(yīng)該繼承nn.Module并覆蓋forward()方法。繼承nn.Module提供給了一些方法給你的組件。例如,它可以跟蹤可訓(xùn)練的參數(shù),你可以通過.to(device)方法在CPU和GPU之間交換它們。.to(device)方法中的device可以是CPU設(shè)備torch.device("cpu")或者CUDA設(shè)備torch.device("cuda:0")。

讓我們寫一個(gè)神經(jīng)網(wǎng)絡(luò)的示例,它接受一些稀疏的BOW表示,然后輸出分布在兩個(gè)標(biāo)簽上的概率:“English”和“Spanish”。這個(gè)模型只是一個(gè)邏輯回歸。

示例: 邏輯回歸詞袋分類器

我們的模型將會(huì)把BOW表示映射成標(biāo)簽上的對(duì)數(shù)概率。我們?yōu)樵~匯中的每個(gè)詞指定一個(gè)索引。例如,我們所有的詞匯是兩個(gè)單詞“hello”和"world",用0和1表示。句子“hello hello hello hello”的表示是

[4,0]

對(duì)于“hello world world hello”, 則表示成

[2,2]

通常表示成

[Count(hello),Count(world)]

用x來表示這個(gè)BOW向量。網(wǎng)絡(luò)的輸出是:

image

也就是說,我們數(shù)據(jù)傳入一個(gè)仿射映射然后做softmax的對(duì)數(shù)。

data = [("me gusta comer en la cafeteria".split(), "SPANISH"),
        ("Give it to me".split(), "ENGLISH"),
        ("No creo que sea una buena idea".split(), "SPANISH"),
        ("No it is not a good idea to get lost at sea".split(), "ENGLISH")]

test_data = [("Yo creo que si".split(), "SPANISH"),
             ("it is lost on me".split(), "ENGLISH")]

# word_to_ix maps each word in the vocab to a unique integer, which will be its
# index into the Bag of words vector
word_to_ix = {}
for sent, _ in data + test_data:
    for word in sent:
        if word not in word_to_ix:
            word_to_ix[word] = len(word_to_ix)
print(word_to_ix)

VOCAB_SIZE = len(word_to_ix)
NUM_LABELS = 2

class BoWClassifier(nn.Module):  # inheriting from nn.Module!

    def __init__(self, num_labels, vocab_size):
        # calls the init function of nn.Module.  Dont get confused by syntax,
        # just always do it in an nn.Module
        super(BoWClassifier, self).__init__()

        # Define the parameters that you will need.  In this case, we need A and b,
        # the parameters of the affine mapping.
        # Torch defines nn.Linear(), which provides the affine map.
        # Make sure you understand why the input dimension is vocab_size
        # and the output is num_labels!
        self.linear = nn.Linear(vocab_size, num_labels)

        # NOTE! The non-linearity log softmax does not have parameters! So we don't need
        # to worry about that here

    def forward(self, bow_vec):
        # Pass the input through the linear layer,
        # then pass that through log_softmax.
        # Many non-linearities and other functions are in torch.nn.functional
        return F.log_softmax(self.linear(bow_vec), dim=1)

def make_bow_vector(sentence, word_to_ix):
    vec = torch.zeros(len(word_to_ix))
    for word in sentence:
        vec[word_to_ix[word]] += 1
    return vec.view(1, -1)

def make_target(label, label_to_ix):
    return torch.LongTensor([label_to_ix[label]])

model = BoWClassifier(NUM_LABELS, VOCAB_SIZE)

# the model knows its parameters.  The first output below is A, the second is b.
# Whenever you assign a component to a class variable in the __init__ function
# of a module, which was done with the line
# self.linear = nn.Linear(...)
# Then through some Python magic from the PyTorch devs, your module
# (in this case, BoWClassifier) will store knowledge of the nn.Linear's parameters
for param in model.parameters():
    print(param)

# To run the model, pass in a BoW vector
# Here we don't need to train, so the code is wrapped in torch.no_grad()
with torch.no_grad():
    sample = data[0]
    bow_vector = make_bow_vector(sample[0], word_to_ix)
    log_probs = model(bow_vector)
    print(log_probs)

輸出:

{'me': 0, 'gusta': 1, 'comer': 2, 'en': 3, 'la': 4, 'cafeteria': 5, 'Give': 6, 'it': 7, 'to': 8, 'No': 9, 'creo': 10, 'que': 11, 'sea': 12, 'una': 13, 'buena': 14, 'idea': 15, 'is': 16, 'not': 17, 'a': 18, 'good': 19, 'get': 20, 'lost': 21, 'at': 22, 'Yo': 23, 'si': 24, 'on': 25}
Parameter containing:
tensor([[ 0.1194,  0.0609, -0.1268,  0.1274,  0.1191,  0.1739, -0.1099, -0.0323,
         -0.0038,  0.0286, -0.1488, -0.1392,  0.1067, -0.0460,  0.0958,  0.0112,
          0.0644,  0.0431,  0.0713,  0.0972, -0.1816,  0.0987, -0.1379, -0.1480,
          0.0119, -0.0334],
        [ 0.1152, -0.1136, -0.1743,  0.1427, -0.0291,  0.1103,  0.0630, -0.1471,
          0.0394,  0.0471, -0.1313, -0.0931,  0.0669,  0.0351, -0.0834, -0.0594,
          0.1796, -0.0363,  0.1106,  0.0849, -0.1268, -0.1668,  0.1882,  0.0102,
          0.1344,  0.0406]], requires_grad=True)
Parameter containing:
tensor([0.0631, 0.1465], requires_grad=True)
tensor([[-0.5378, -0.8771]])

上面的哪一個(gè)值對(duì)應(yīng)的是ENGLISH的對(duì)數(shù)概率,哪一個(gè)是SPANISH的對(duì)數(shù)概率?我們還沒有定義,但是如果我們想要訓(xùn)練一些東西,我們必須進(jìn)行定義。

label_to_ix = {"SPANISH": 0, "ENGLISH": 1}


讓我們來訓(xùn)練吧! 我們將實(shí)例傳入來獲取對(duì)數(shù)概率,計(jì)算損失函數(shù),計(jì)算損失函數(shù)的梯度,然后使用一個(gè)梯度步長(zhǎng)來更新參數(shù)。在PyTorch的nn包里提供了損失函數(shù)。nn.NLLLoss()是我們想要的負(fù)對(duì)數(shù)似然損失函數(shù)。它也在torch.optim定義了優(yōu)化方法。這里,我們只使用SGD。

注意,NLLLoss的輸入是一個(gè)對(duì)數(shù)概率的向量以及目標(biāo)標(biāo)簽。它不會(huì)為我們計(jì)算對(duì)數(shù)概率。這也是為什么我們最后一層網(wǎng)絡(luò)是log_softmax的原因。損失函數(shù)nn.CrossEntropyLoss()除了給你做了一個(gè)sofmax的對(duì)數(shù)之外和NLLLoss()沒什么區(qū)別。

# Run on test data before we train, just to see a before-and-after
with torch.no_grad():
    for instance, label in test_data:
        bow_vec = make_bow_vector(instance, word_to_ix)
        log_probs = model(bow_vec)
        print(log_probs)

# Print the matrix column corresponding to "creo"
print(next(model.parameters())[:, word_to_ix["creo"]])

loss_function = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# Usually you want to pass over the training data several times.
# 100 is much bigger than on a real data set, but real datasets have more than
# two instances.  Usually, somewhere between 5 and 30 epochs is reasonable.
for epoch in range(100):
    for instance, label in data:
        # Step 1\. Remember that PyTorch accumulates gradients.
        # We need to clear them out before each instance
        model.zero_grad()

        # Step 2\. Make our BOW vector and also we must wrap the target in a
        # Tensor as an integer. For example, if the target is SPANISH, then
        # we wrap the integer 0\. The loss function then knows that the 0th
        # element of the log probabilities is the log probability
        # corresponding to SPANISH
        bow_vec = make_bow_vector(instance, word_to_ix)
        target = make_target(label, label_to_ix)

        # Step 3\. Run our forward pass.
        log_probs = model(bow_vec)

        # Step 4\. Compute the loss, gradients, and update the parameters by
        # calling optimizer.step()
        loss = loss_function(log_probs, target)
        loss.backward()
        optimizer.step()

with torch.no_grad():
    for instance, label in test_data:
        bow_vec = make_bow_vector(instance, word_to_ix)
        log_probs = model(bow_vec)
        print(log_probs)

# Index corresponding to Spanish goes up, English goes down!
print(next(model.parameters())[:, word_to_ix["creo"]])

輸出:

tensor([[-0.9297, -0.5020]])
tensor([[-0.6388, -0.7506]])
tensor([-0.1488, -0.1313], grad_fn=<SelectBackward>)
tensor([[-0.2093, -1.6669]])
tensor([[-2.5330, -0.0828]])
tensor([ 0.2803, -0.5605], grad_fn=<SelectBackward>)

我們得到了正確的結(jié)果!你可以看到Spanish的對(duì)數(shù)概率比第一個(gè)例子中的高的多,English的對(duì)數(shù)概率在第二個(gè)測(cè)試數(shù)據(jù)中更高,結(jié)果也應(yīng)該是這樣。

現(xiàn)在你了解了如何創(chuàng)建一個(gè)PyTorch組件,將數(shù)據(jù)傳入并進(jìn)行梯度更新。我們準(zhǔn)備深入挖掘NLP所能提供的東西了。

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

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

  • 激活函數(shù)(Activation Function) 為了讓神經(jīng)網(wǎng)絡(luò)能夠?qū)W習(xí)復(fù)雜的決策邊界(decision bou...
    御風(fēng)之星閱讀 5,491評(píng)論 0 8
  • 主要內(nèi)容 自然語言輸入編碼 前饋網(wǎng)絡(luò) 卷積網(wǎng)絡(luò) 循環(huán)網(wǎng)絡(luò)(recurrent networks ) 遞歸網(wǎng)絡(luò)(re...
    JackHorse閱讀 4,479評(píng)論 0 2
  • 第二個(gè)Topic講深度學(xué)習(xí),承接前面的《淺談機(jī)器學(xué)習(xí)基礎(chǔ)》。 深度學(xué)習(xí)簡(jiǎn)介 前面也提到過,機(jī)器學(xué)習(xí)的本質(zhì)就是尋找最...
    我偏笑_NSNirvana閱讀 16,193評(píng)論 7 49
  • 今天上午去辦了車位簽約手續(xù),整整花了一上午的時(shí)間。以后就不止有車貸房貸了,還多了一個(gè)車位貸每月一千多元,加起來將近...
    翻滾吧海闊天空閱讀 199評(píng)論 0 1
  • 出華嚴(yán)經(jīng)隨疏演義鈔,謂諸昏煩惱亂心神之法。隨逐眾生。造無量業(yè)。故名隨煩惱也。 一忿隨煩惱暴怒之心名忿。謂對(duì)現(xiàn)前一切...
    金石明鏡閱讀 1,477評(píng)論 0 2

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