全國(guó)咨詢(xún)/投訴熱線(xiàn):400-618-4000

首頁(yè)人工智能技術(shù)資訊正文

使用Transformer構(gòu)建語(yǔ)言模型

更新時(shí)間:2022-04-26 來(lái)源:黑馬程序員 瀏覽量:

什么是語(yǔ)言模型:

以一個(gè)符合語(yǔ)言規(guī)律的序列為輸入,模型將利用序列間關(guān)系等特征,輸出一個(gè)在所有詞匯上的概率分布.這樣的模型稱(chēng)為語(yǔ)言模型。

# 語(yǔ)言模型的訓(xùn)練語(yǔ)料一般來(lái)自于文章,對(duì)應(yīng)的源文本和目標(biāo)文本形如:
src1 = "I can do" tgt1 = "can do it"
src2 = "can do it", tgt2 = "do it <eos>"

語(yǔ)言模型能解決哪些問(wèn)題:

1, 根據(jù)語(yǔ)言模型的定義,可以在它的基礎(chǔ)上完成機(jī)器翻譯,文本生成等任務(wù),因?yàn)槲覀兺ㄟ^(guò)最后輸出的概率分布來(lái)預(yù)測(cè)下一個(gè)詞匯是什么.

2, 語(yǔ)言模型可以判斷輸入的序列是否為一句完整的話(huà),因?yàn)槲覀兛梢愿鶕?jù)輸出的概率分布查看最大概率是否落在句子結(jié)束符上,來(lái)判斷完整性.

3, 語(yǔ)言模型本身的訓(xùn)練目標(biāo)是預(yù)測(cè)下一個(gè)詞,因?yàn)樗奶卣魈崛〔糠謺?huì)抽象很多語(yǔ)言序列之間的關(guān)系,這些關(guān)系可能同樣對(duì)其他語(yǔ)言類(lèi)任務(wù)有效果.因此可以作為預(yù)訓(xùn)練模型進(jìn)行遷移學(xué)習(xí).

整個(gè)案例的實(shí)現(xiàn)可分為以下五個(gè)步驟

第一步: 導(dǎo)入必備的工具包

第二步: 導(dǎo)入wikiText-2數(shù)據(jù)集并作基本處理

第三步: 構(gòu)建用于模型輸入的批次化數(shù)據(jù)

第四步: 構(gòu)建訓(xùn)練和評(píng)估函數(shù)

第五步: 進(jìn)行訓(xùn)練和評(píng)估(包括驗(yàn)證以及測(cè)試)

第一步: 導(dǎo)入必備的工具包

pytorch版本必須使用1.3.1, python版本使用3.6.x

pip install torch==1.3.1
# 數(shù)學(xué)計(jì)算工具包math
import math

# torch以及torch.nn, torch.nn.functional
import torch
import torch.nn as nn
import torch.nn.functional as F

# torch中經(jīng)典文本數(shù)據(jù)集有關(guān)的工具包
# 具體詳情參考下方torchtext介紹
import torchtext

# torchtext中的數(shù)據(jù)處理工具, get_tokenizer用于英文分詞
from torchtext.data.utils import get_tokenizer

# 已經(jīng)構(gòu)建完成的TransformerModel
from pyitcast.transformer import TransformerModel

torchtext:它是torch工具中處理NLP問(wèn)題的常用數(shù)據(jù)處理包.

torchtext的重要功能:對(duì)文本數(shù)據(jù)進(jìn)行處理, 比如文本語(yǔ)料加載, 文本迭代器構(gòu)建等.

包含很多經(jīng)典文本語(yǔ)料的預(yù)加載方法. 其中包括的語(yǔ)料有:用于情感分析的SST和IMDB, 用于問(wèn)題分類(lèi)的TREC, 用于及其翻譯的 WMT14, IWSLT,以及用于語(yǔ)言模型任務(wù)wikiText-2, WikiText103, PennTreebank.

我們這里使用wikiText-2來(lái)訓(xùn)練語(yǔ)言模型, 下面有關(guān)該數(shù)據(jù)集的相關(guān)詳情:

1650944025293_11.png

wikiText-2數(shù)據(jù)集的體量中等, 訓(xùn)練集共有600篇短文, 共208萬(wàn)左右的詞匯, 33278個(gè)不重復(fù)詞匯, OoV(有多少正常英文詞匯不在該數(shù)據(jù)集中的占比)為2.6%,數(shù)據(jù)集中的短文都是維基百科中對(duì)一些概念的介紹和描述.

第二步: 導(dǎo)入wikiText-2數(shù)據(jù)集并作基本處理

# 創(chuàng)建語(yǔ)料域, 語(yǔ)料域是存放語(yǔ)料的數(shù)據(jù)結(jié)構(gòu), 
# 它的四個(gè)參數(shù)代表給存放語(yǔ)料(或稱(chēng)作文本)施加的作用. 
# 分別為 tokenize,使用get_tokenizer("basic_english")獲得一個(gè)分割器對(duì)象,
# 分割方式按照文本為基礎(chǔ)英文進(jìn)行分割. 
# init_token為給文本施加的起始符 <sos>給文本施加的終止符<eos>, 
# 最后一個(gè)lower為T(mén)rue, 存放的文本字母全部小寫(xiě).
TEXT = torchtext.data.Field(tokenize=get_tokenizer("basic_english"),
                            init_token='<sos>',
                            eos_token='<eos>',
                            lower=True)

# 最終獲得一個(gè)Field對(duì)象.
# <torchtext.data.field.Field object at 0x7fc42a02e7f0>

# 然后使用torchtext的數(shù)據(jù)集方法導(dǎo)入WikiText2數(shù)據(jù), 
# 并切分為對(duì)應(yīng)訓(xùn)練文本, 驗(yàn)證文本,測(cè)試文本, 并對(duì)這些文本施加剛剛創(chuàng)建的語(yǔ)料域.
train_txt, val_txt, test_txt = torchtext.datasets.WikiText2.splits(TEXT)

# 我們可以通過(guò)examples[0].text取出文本對(duì)象進(jìn)行查看.
# >>> test_txt.examples[0].text[:10]
# ['<eos>', '=', 'robert', '<unk>', '=', '<eos>', '<eos>', 'robert', '<unk>', 'is']

# 將訓(xùn)練集文本數(shù)據(jù)構(gòu)建一個(gè)vocab對(duì)象, 
# 這樣可以使用vocab對(duì)象的stoi方法統(tǒng)計(jì)文本共包含的不重復(fù)詞匯總數(shù).
TEXT.build_vocab(train_txt)

# 然后選擇設(shè)備cuda或者cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

該案例的所有代碼都將實(shí)現(xiàn)在一個(gè)transformer_lm.py文件中.

第三步: 構(gòu)建用于模型輸入的批次化數(shù)據(jù)

批次化過(guò)程的第一個(gè)函數(shù)batchify代碼分析:

def batchify(data, bsz):
    """batchify函數(shù)用于將文本數(shù)據(jù)映射成連續(xù)數(shù)字, 并轉(zhuǎn)換成指定的樣式, 指定的樣式可參考下圖.
       它有兩個(gè)輸入?yún)?shù), data就是我們之前得到的文本數(shù)據(jù)(train_txt, val_txt, test_txt),
       bsz是就是batch_size, 每次模型更新參數(shù)的數(shù)據(jù)量"""
    # 使用TEXT的numericalize方法將單詞映射成對(duì)應(yīng)的連續(xù)數(shù)字.
    data = TEXT.numericalize([data.examples[0].text])
    # >>> data
    # tensor([[   3],
    #    [  12],
    #    [3852],
    #    ...,
    #    [   6],
    #    [   3],
    #    [   3]])

    # 接著用數(shù)據(jù)詞匯總數(shù)除以bsz,
    # 取整數(shù)得到一個(gè)nbatch代表需要多少次batch后能夠遍歷完所有數(shù)據(jù)
    nbatch = data.size(0) // bsz

    # 之后使用narrow方法對(duì)不規(guī)整的剩余數(shù)據(jù)進(jìn)行刪除,
    # 第一個(gè)參數(shù)是代表橫軸刪除還是縱軸刪除, 0為橫軸,1為縱軸
    # 第二個(gè)和第三個(gè)參數(shù)代表保留開(kāi)始軸到結(jié)束軸的數(shù)值.類(lèi)似于切片
    # 可參考下方演示示例進(jìn)行更深理解.
    data = data.narrow(0, 0, nbatch * bsz)
    # >>> data
    # tensor([[   3],
    #    [  12],
    #    [3852],
    #    ...,
    #    [  78],
    #    [ 299],
    #    [  36]])
    # 后面不能形成bsz個(gè)的一組數(shù)據(jù)被刪除

    # 接著我們使用view方法對(duì)data進(jìn)行矩陣變換, 使其成為如下樣式:
    # tensor([[    3,    25,  1849,  ...,     5,    65,    30],
    #    [   12,    66,    13,  ...,    35,  2438,  4064],
    #    [ 3852, 13667,  2962,  ...,   902,    33,    20],
    #    ...,
    #    [  154,     7,    10,  ...,     5,  1076,    78],
    #    [   25,     4,  4135,  ...,     4,    56,   299],
    #    [    6,    57,   385,  ...,  3168,   737,    36]])
    # 因?yàn)闀?huì)做轉(zhuǎn)置操作, 因此這個(gè)矩陣的形狀是[None, bsz],
    # 如果輸入是訓(xùn)練數(shù)據(jù)的話(huà),形狀為[104335, 20], 可以通過(guò)打印data.shape獲得.
    # 也就是data的列數(shù)是等于bsz的值的.
    data = data.view(bsz, -1).t().contiguous()
    # 最后將數(shù)據(jù)分配在指定的設(shè)備上.
    return data.to(device)

batchify的樣式轉(zhuǎn)化圖:

1650944305127_12.png

注:大寫(xiě)字母A,B,C ... 代表句子中的每個(gè)單詞.

torch.narrow演示:

>>> x = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> x.narrow(0, 0, 2)
tensor([[ 1,  2,  3],
        [ 4,  5,  6]])
>>> x.narrow(1, 1, 2)
tensor([[ 2,  3],
        [ 5,  6],
        [ 8,  9]])

接下來(lái)我們將使用batchify來(lái)處理訓(xùn)練數(shù)據(jù),驗(yàn)證數(shù)據(jù)以及測(cè)試數(shù)據(jù).

# 訓(xùn)練數(shù)據(jù)的batch size
batch_size = 20

# 驗(yàn)證和測(cè)試數(shù)據(jù)(統(tǒng)稱(chēng)為評(píng)估數(shù)據(jù))的batch size
eval_batch_size = 10

# 獲得train_data, val_data, test_data
train_data = batchify(train_txt, batch_size)
val_data = batchify(val_txt, eval_batch_size)
test_data = batchify(test_txt, eval_batch_size)

上面的分割批次并沒(méi)有進(jìn)行源數(shù)據(jù)與目標(biāo)數(shù)據(jù)的處理, 接下來(lái)我們將根據(jù)語(yǔ)言模型訓(xùn)練的語(yǔ)料規(guī)定來(lái)構(gòu)建源數(shù)據(jù)與目標(biāo)數(shù)據(jù).

語(yǔ)言模型訓(xùn)練的語(yǔ)料規(guī)定:

如果源數(shù)據(jù)為句子ABCD, ABCD代表句子中的詞匯或符號(hào), 則它的目標(biāo)數(shù)據(jù)為BCDE, BCDE分別代表ABCD的下一個(gè)詞匯.

語(yǔ)言模型的訓(xùn)練語(yǔ)料規(guī)定

如圖所示,我們這里的句子序列是豎著的, 而且我們發(fā)現(xiàn)如果用一個(gè)批次處理完所有數(shù)據(jù), 以訓(xùn)練數(shù)據(jù)為例, 每個(gè)句子長(zhǎng)度高達(dá)104335, 這明顯是不科學(xué)的, 因此我們?cè)谶@里要限定每個(gè)批次中的句子長(zhǎng)度允許的最大值bptt.

批次化過(guò)程的第二個(gè)函數(shù)get_batch代碼分析:

# 令子長(zhǎng)度允許的最大值bptt為35
bptt = 35

def get_batch(source, i):
    """用于獲得每個(gè)批次合理大小的源數(shù)據(jù)和目標(biāo)數(shù)據(jù).
       參數(shù)source是通過(guò)batchify得到的train_data/val_data/test_data.
       i是具體的批次次數(shù).
    """

    # 首先我們確定句子長(zhǎng)度, 它將是在bptt和len(source) - 1 - i中最小值
    # 實(shí)質(zhì)上, 前面的批次中都會(huì)是bptt的值, 只不過(guò)最后一個(gè)批次中, 句子長(zhǎng)度
    # 可能不夠bptt的35個(gè), 因此會(huì)變?yōu)閘en(source) - 1 - i的值.
    seq_len = min(bptt, len(source) - 1 - i)

    # 語(yǔ)言模型訓(xùn)練的源數(shù)據(jù)的第i批數(shù)據(jù)將是batchify的結(jié)果的切片[i:i+seq_len]
    data = source[i:i+seq_len]

    # 根據(jù)語(yǔ)言模型訓(xùn)練的語(yǔ)料規(guī)定, 它的目標(biāo)數(shù)據(jù)是源數(shù)據(jù)向后移動(dòng)一位
    # 因?yàn)樽詈竽繕?biāo)數(shù)據(jù)的切片會(huì)越界, 因此使用view(-1)來(lái)保證形狀正常.
    target = source[i+1:i+1+seq_len].view(-1)
    return data, target

輸入實(shí)例:

# 以測(cè)試集數(shù)據(jù)為例
source = test_data
i = 1

輸出效果:

data = tensor([[   12,  1053,   355,   134,    37,     7,     4,     0,   835,  9834],
        [  635,     8,     5,     5,   421,     4,    88,     8,   573,  2511],
        [    0,    58,     8,     8,     6,   692,   544,     0,   212,     5],
        [   12,     0,   105,    26,     3,     5,     6,     0,     4,    56],
        [    3, 16074, 21254,   320,     3,   262,    16,     6,  1087,    89],
        [    3,   751,  3866,    10,    12,    31,   246,   238,    79,    49],
        [  635,   943,    78,    36,    12,   475,    66,    10,     4,   924],
        [    0,  2358,    52,     4,    12,     4,     5,     0, 19831,    21],
        [   26,    38,    54,    40,  1589,  3729,  1014,     5,     8,     4],
        [   33, 17597,    33,  1661,    15,     7,     5,     0,     4,   170],
        [  335,   268,   117,     0,     0,     4,  3144,  1557,     0,   160],
        [  106,     4,  4706,  2245,    12,  1074,    13,  2105,     5,    29],
        [    5, 16074,    10,  1087,    12,   137,   251, 13238,     8,     4],
        [  394,   746,     4,     9,    12,  6032,     4,  2190,   303, 12651],
        [    8,   616,  2107,     4,     3,     4,   425,     0,    10,   510],
        [ 1339,   112,    23,   335,     3, 22251,  1162,     9,    11,     9],
        [ 1212,   468,     6,   820,     9,     7,  1231,  4202,  2866,   382],
        [    6,    24,   104,     6,     4,     4,     7,    10,     9,   588],
        [   31,   190,     0,     0,   230,   267,     4,   273,   278,     6],
        [   34,    25,    47,    26,  1864,     6,   694,     0,  2112,     3],
        [   11,     6,    52,   798,     8,    69,    20,    31,    63,     9],
        [ 1800,    25,  2141,  2442,   117,    31,   196,  7290,     4,   298],
        [   15,   171,    15,    17,  1712,    13,   217,    59,   736,     5],
        [ 4210,   191,   142,    14,  5251,   939,    59,    38, 10055, 25132],
        [  302,    23, 11718,    11,    11,   599,   382,   317,     8,    13],
        [   16,  1564,     9,  4808,     6,     0,     6,     6,     4,     4],
        [    4,     7,    39,     7,  3934,     5,     9,     3,  8047,   557],
        [  394,     0, 10715,  3580,  8682,    31,   242,     0, 10055,   170],
        [   96,     6,   144,  3403,     4,    13,  1014,    14,     6,  2395],
        [    4,     3, 13729,    14,    40,     0,     5,    18,   676,  3267],
        [ 1031,     3,     0,   628,  1589,    22, 10916, 10969,     5, 22548],
        [    9,    12,     6,    84,    15,    49,  3144,     7,   102,    15],
        [  916,    12,     4,   203,     0,   273,   303,   333,  4318,     0],
        [    6,    12,     0,  4842,     5,    17,     4,    47,  4138,  2072],
        [   38,   237,     5,    50,    35,    27, 18530,   244,    20,     6]])

target =  tensor([  635,     8,     5,     5,   421,     4,    88,     8,   573,  2511,
            0,    58,     8,     8,     6,   692,   544,     0,   212,     5,
           12,     0,   105,    26,     3,     5,     6,     0,     4,    56,
            3, 16074, 21254,   320,     3,   262,    16,     6,  1087,    89,
            3,   751,  3866,    10,    12,    31,   246,   238,    79,    49,
          635,   943,    78,    36,    12,   475,    66,    10,     4,   924,
            0,  2358,    52,     4,    12,     4,     5,     0, 19831,    21,
           26,    38,    54,    40,  1589,  3729,  1014,     5,     8,     4,
           33, 17597,    33,  1661,    15,     7,     5,     0,     4,   170,
          335,   268,   117,     0,     0,     4,  3144,  1557,     0,   160,
          106,     4,  4706,  2245,    12,  1074,    13,  2105,     5,    29,
            5, 16074,    10,  1087,    12,   137,   251, 13238,     8,     4,
          394,   746,     4,     9,    12,  6032,     4,  2190,   303, 12651,
            8,   616,  2107,     4,     3,     4,   425,     0,    10,   510,
         1339,   112,    23,   335,     3, 22251,  1162,     9,    11,     9,
         1212,   468,     6,   820,     9,     7,  1231,  4202,  2866,   382,
            6,    24,   104,     6,     4,     4,     7,    10,     9,   588,
           31,   190,     0,     0,   230,   267,     4,   273,   278,     6,
           34,    25,    47,    26,  1864,     6,   694,     0,  2112,     3,
           11,     6,    52,   798,     8,    69,    20,    31,    63,     9,
         1800,    25,  2141,  2442,   117,    31,   196,  7290,     4,   298,
           15,   171,    15,    17,  1712,    13,   217,    59,   736,     5,
         4210,   191,   142,    14,  5251,   939,    59,    38, 10055, 25132,
          302,    23, 11718,    11,    11,   599,   382,   317,     8,    13,
           16,  1564,     9,  4808,     6,     0,     6,     6,     4,     4,
            4,     7,    39,     7,  3934,     5,     9,     3,  8047,   557,
          394,     0, 10715,  3580,  8682,    31,   242,     0, 10055,   170,
           96,     6,   144,  3403,     4,    13,  1014,    14,     6,  2395,
            4,     3, 13729,    14,    40,     0,     5,    18,   676,  3267,
         1031,     3,     0,   628,  1589,    22, 10916, 10969,     5, 22548,
            9,    12,     6,    84,    15,    49,  3144,     7,   102,    15,
          916,    12,     4,   203,     0,   273,   303,   333,  4318,     0,
            6,    12,     0,  4842,     5,    17,     4,    47,  4138,  2072,
           38,   237,     5,    50,    35,    27, 18530,   244,    20,     6,
           13,  1083,    35,  1990,   653,    13,    10,    11,  1538,    56])

第四步: 構(gòu)建訓(xùn)練和評(píng)估函數(shù)

設(shè)置模型超參數(shù)和初始化模型

# 通過(guò)TEXT.vocab.stoi方法獲得不重復(fù)詞匯總數(shù)
ntokens = len(TEXT.vocab.stoi)

# 詞嵌入大小為200
emsize = 200

# 前饋全連接層的節(jié)點(diǎn)數(shù)
nhid = 200

# 編碼器層的數(shù)量
nlayers = 2

# 多頭注意力機(jī)制的頭數(shù)
nhead = 2

# 置0比率
dropout = 0.2

# 將參數(shù)輸入到TransformerModel中
model = TransformerModel(ntokens, emsize, nhead, nhid, nlayers, dropout).to(device)

# 模型初始化后, 接下來(lái)進(jìn)行損失函數(shù)和優(yōu)化方法的選擇.

# 關(guān)于損失函數(shù), 我們使用nn自帶的交叉熵?fù)p失
criterion = nn.CrossEntropyLoss()

# 學(xué)習(xí)率初始值定為5.0
lr = 5.0

# 優(yōu)化器選擇torch自帶的SGD隨機(jī)梯度下降方法, 并把lr傳入其中
optimizer = torch.optim.SGD(model.parameters(), lr=lr)

# 定義學(xué)習(xí)率調(diào)整方法, 使用torch自帶的lr_scheduler, 將優(yōu)化器傳入其中.
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1.0, gamma=0.95)

模型訓(xùn)練代碼分析:

# 導(dǎo)入時(shí)間工具包
import time

def train():
    """訓(xùn)練函數(shù)"""
    # 模型開(kāi)啟訓(xùn)練模式
    model.train()
    # 定義初始損失為0
    total_loss = 0.
    # 獲得當(dāng)前時(shí)間
    start_time = time.time()
    # 開(kāi)始遍歷批次數(shù)據(jù)
    for batch, i in enumerate(range(0, train_data.size(0) - 1, bptt)):
        # 通過(guò)get_batch獲得源數(shù)據(jù)和目標(biāo)數(shù)據(jù)
        data, targets = get_batch(train_data, i)
        # 設(shè)置優(yōu)化器初始梯度為0梯度
        optimizer.zero_grad()
        # 將數(shù)據(jù)裝入model得到輸出
        output = model(data)
        # 將輸出和目標(biāo)數(shù)據(jù)傳入損失函數(shù)對(duì)象
        loss = criterion(output.view(-1, ntokens), targets)
        # 損失進(jìn)行反向傳播以獲得總的損失
        loss.backward()
        # 使用nn自帶的clip_grad_norm_方法進(jìn)行梯度規(guī)范化, 防止出現(xiàn)梯度消失或爆炸
        torch.nn.utils.clip_grad_norm_(model.parameters(), 0.5)
        # 模型參數(shù)進(jìn)行更新
        optimizer.step()
        # 將每層的損失相加獲得總的損失
        total_loss += loss.item()
        # 日志打印間隔定為200
        log_interval = 200
        # 如果batch是200的倍數(shù)且大于0,則打印相關(guān)日志
        if batch % log_interval == 0 and batch > 0:
            # 平均損失為總損失除以log_interval
            cur_loss = total_loss / log_interval
            # 需要的時(shí)間為當(dāng)前時(shí)間減去開(kāi)始時(shí)間
            elapsed = time.time() - start_time
            # 打印輪數(shù), 當(dāng)前批次和總批次, 當(dāng)前學(xué)習(xí)率, 訓(xùn)練速度(每豪秒處理多少批次),
            # 平均損失, 以及困惑度, 困惑度是衡量語(yǔ)言模型的重要指標(biāo), 它的計(jì)算方法就是
            # 對(duì)交叉熵平均損失取自然對(duì)數(shù)的底數(shù).
            print('| epoch {:3d} | {:5d}/{:5d} batches | '
                  'lr {:02.2f} | ms/batch {:5.2f} | '
                  'loss {:5.2f} | ppl {:8.2f}'.format(
                    epoch, batch, len(train_data) // bptt, scheduler.get_lr()[0],
                    elapsed * 1000 / log_interval,
                    cur_loss, math.exp(cur_loss)))

            # 每個(gè)批次結(jié)束后, 總損失歸0
            total_loss = 0
            # 開(kāi)始時(shí)間取當(dāng)前時(shí)間
            start_time = time.time()

模型評(píng)估代碼分析:

def evaluate(eval_model, data_source):
    """評(píng)估函數(shù), 評(píng)估階段包括驗(yàn)證和測(cè)試,
       它的兩個(gè)參數(shù)eval_model為每輪訓(xùn)練產(chǎn)生的模型
       data_source代表驗(yàn)證或測(cè)試數(shù)據(jù)集"""
    # 模型開(kāi)啟評(píng)估模式
    eval_model.eval()
    # 總損失歸0
    total_loss = 0
    # 因?yàn)樵u(píng)估模式模型參數(shù)不變, 因此反向傳播不需要求導(dǎo), 以加快計(jì)算
    with torch.no_grad():
        # 與訓(xùn)練過(guò)程相同, 但是因?yàn)檫^(guò)程不需要打印信息, 因此不需要batch數(shù)
        for i in range(0, data_source.size(0) - 1, bptt):
            # 首先還是通過(guò)通過(guò)get_batch獲得驗(yàn)證數(shù)據(jù)集的源數(shù)據(jù)和目標(biāo)數(shù)據(jù)
            data, targets = get_batch(data_source, i)
            # 通過(guò)eval_model獲得輸出
            output = eval_model(data)
            # 對(duì)輸出形狀扁平化, 變?yōu)槿吭~匯的概率分布
            output_flat = output.view(-1, ntokens)
            # 獲得評(píng)估過(guò)程的總損失
            total_loss += criterion(output_flat, targets).item()
            # 計(jì)算平均損失
            cur_loss = total_loss / ((data_source.size(0) - 1) / bptt)            

    # 返回平均損失
    return cur_loss

第五步: 進(jìn)行訓(xùn)練和評(píng)估(包括驗(yàn)證以及測(cè)試)

模型的訓(xùn)練與驗(yàn)證代碼分析:

# 首先初始化最佳驗(yàn)證損失,初始值為無(wú)窮大
import copy
best_val_loss = float("inf")

# 定義訓(xùn)練輪數(shù)
epochs = 3

# 定義最佳模型變量, 初始值為None
best_model = None

# 使用for循環(huán)遍歷輪數(shù)
for epoch in range(1, epochs + 1):
    # 首先獲得輪數(shù)開(kāi)始時(shí)間
    epoch_start_time = time.time()
    # 調(diào)用訓(xùn)練函數(shù)
    train()
    # 該輪訓(xùn)練后我們的模型參數(shù)已經(jīng)發(fā)生了變化
    # 將模型和評(píng)估數(shù)據(jù)傳入到評(píng)估函數(shù)中
    val_loss = evaluate(model, val_data)
    # 之后打印每輪的評(píng)估日志,分別有輪數(shù),耗時(shí),驗(yàn)證損失以及驗(yàn)證困惑度
    print('-' * 89)
    print('| end of epoch {:3d} | time: {:5.2f}s | valid loss {:5.2f} | '
          'valid ppl {:8.2f}'.format(epoch, (time.time() - epoch_start_time),
                                     val_loss, math.exp(val_loss)))
    print('-' * 89)
    # 我們將比較哪一輪損失最小,賦值給best_val_loss,
    # 并取該損失下的模型為best_model
    if val_loss < best_val_loss:
        best_val_loss = val_loss
        # 使用深拷貝,拷貝最優(yōu)模型
        best_model = copy.deepcopy(model)
    # 每輪都會(huì)對(duì)優(yōu)化方法的學(xué)習(xí)率做調(diào)整
    scheduler.step()

輸出效果:

| epoch   1 |   200/ 2981 batches | lr 5.00 | ms/batch 30.03 | loss  7.68 | ppl  2158.52
| epoch   1 |   400/ 2981 batches | lr 5.00 | ms/batch 28.90 | loss  5.26 | ppl   193.39
| epoch   1 |   600/ 2981 batches | lr 5.00 | ms/batch 28.90 | loss  4.07 | ppl    58.44
| epoch   1 |   800/ 2981 batches | lr 5.00 | ms/batch 28.88 | loss  3.41 | ppl    30.26
| epoch   1 |  1000/ 2981 batches | lr 5.00 | ms/batch 28.89 | loss  2.98 | ppl    19.72
| epoch   1 |  1200/ 2981 batches | lr 5.00 | ms/batch 28.90 | loss  2.79 | ppl    16.30
| epoch   1 |  1400/ 2981 batches | lr 5.00 | ms/batch 28.91 | loss  2.67 | ppl    14.38
| epoch   1 |  1600/ 2981 batches | lr 5.00 | ms/batch 28.92 | loss  2.58 | ppl    13.19
| epoch   1 |  1800/ 2981 batches | lr 5.00 | ms/batch 28.91 | loss  2.43 | ppl    11.32
| epoch   1 |  2000/ 2981 batches | lr 5.00 | ms/batch 28.92 | loss  2.39 | ppl    10.93
| epoch   1 |  2200/ 2981 batches | lr 5.00 | ms/batch 28.91 | loss  2.33 | ppl    10.24
| epoch   1 |  2400/ 2981 batches | lr 5.00 | ms/batch 28.91 | loss  2.36 | ppl    10.59
| epoch   1 |  2600/ 2981 batches | lr 5.00 | ms/batch 28.90 | loss  2.33 | ppl    10.31
| epoch   1 |  2800/ 2981 batches | lr 5.00 | ms/batch 28.92 | loss  2.26 | ppl     9.54
-----------------------------------------------------------------------------------------
| end of epoch   1 | time: 90.01s | valid loss  1.32 | valid ppl     3.73
-----------------------------------------------------------------------------------------
| epoch   2 |   200/ 2981 batches | lr 4.75 | ms/batch 29.08 | loss  2.18 | ppl     8.83
| epoch   2 |   400/ 2981 batches | lr 4.75 | ms/batch 28.93 | loss  2.11 | ppl     8.24
| epoch   2 |   600/ 2981 batches | lr 4.75 | ms/batch 28.93 | loss  1.98 | ppl     7.23
| epoch   2 |   800/ 2981 batches | lr 4.75 | ms/batch 28.93 | loss  2.00 | ppl     7.39
| epoch   2 |  1000/ 2981 batches | lr 4.75 | ms/batch 28.94 | loss  1.94 | ppl     6.96
| epoch   2 |  1200/ 2981 batches | lr 4.75 | ms/batch 28.92 | loss  1.97 | ppl     7.15
| epoch   2 |  1400/ 2981 batches | lr 4.75 | ms/batch 28.94 | loss  1.98 | ppl     7.28
| epoch   2 |  1600/ 2981 batches | lr 4.75 | ms/batch 28.92 | loss  1.97 | ppl     7.16
| epoch   2 |  1800/ 2981 batches | lr 4.75 | ms/batch 28.93 | loss  1.92 | ppl     6.84
| epoch   2 |  2000/ 2981 batches | lr 4.75 | ms/batch 28.93 | loss  1.96 | ppl     7.11
| epoch   2 |  2200/ 2981 batches | lr 4.75 | ms/batch 28.93 | loss  1.92 | ppl     6.80
| epoch   2 |  2400/ 2981 batches | lr 4.75 | ms/batch 28.94 | loss  1.94 | ppl     6.93
| epoch   2 |  2600/ 2981 batches | lr 4.75 | ms/batch 28.76 | loss  1.91 | ppl     6.76
| epoch   2 |  2800/ 2981 batches | lr 4.75 | ms/batch 28.75 | loss  1.89 | ppl     6.64
-----------------------------------------------------------------------------------------
| end of epoch   2 | time: 89.71s | valid loss  1.01 | valid ppl     2.74
-----------------------------------------------------------------------------------------
| epoch   3 |   200/ 2981 batches | lr 4.51 | ms/batch 28.88 | loss  1.78 | ppl     5.96
| epoch   3 |   400/ 2981 batches | lr 4.51 | ms/batch 28.75 | loss  1.89 | ppl     6.59
| epoch   3 |   600/ 2981 batches | lr 4.51 | ms/batch 28.75 | loss  1.72 | ppl     5.58
| epoch   3 |   800/ 2981 batches | lr 4.51 | ms/batch 28.75 | loss  1.73 | ppl     5.63
| epoch   3 |  1000/ 2981 batches | lr 4.51 | ms/batch 28.73 | loss  1.65 | ppl     5.22
| epoch   3 |  1200/ 2981 batches | lr 4.51 | ms/batch 28.74 | loss  1.69 | ppl     5.40
| epoch   3 |  1400/ 2981 batches | lr 4.51 | ms/batch 28.74 | loss  1.73 | ppl     5.66
| epoch   3 |  1600/ 2981 batches | lr 4.51 | ms/batch 28.75 | loss  1.75 | ppl     5.73
| epoch   3 |  1800/ 2981 batches | lr 4.51 | ms/batch 28.74 | loss  1.67 | ppl     5.33
| epoch   3 |  2000/ 2981 batches | lr 4.51 | ms/batch 28.74 | loss  1.69 | ppl     5.41
| epoch   3 |  2200/ 2981 batches | lr 4.51 | ms/batch 28.74 | loss  1.66 | ppl     5.26
| epoch   3 |  2400/ 2981 batches | lr 4.51 | ms/batch 28.76 | loss  1.69 | ppl     5.43
| epoch   3 |  2600/ 2981 batches | lr 4.51 | ms/batch 28.75 | loss  1.71 | ppl     5.55
| epoch   3 |  2800/ 2981 batches | lr 4.51 | ms/batch 28.75 | loss  1.72 | ppl     5.58
-----------------------------------------------------------------------------------------
| end of epoch   3 | time: 89.26s | valid loss  0.85 | valid ppl     2.33

模型測(cè)試代碼分析:

# 我們?nèi)匀皇褂胑valuate函數(shù),這次它的參數(shù)是best_model以及測(cè)試數(shù)據(jù)
test_loss = evaluate(best_model, test_data)

# 打印測(cè)試日志,包括測(cè)試損失和測(cè)試?yán)Щ蠖?print('=' * 89)
print('| End of training | test loss {:5.2f} | test ppl {:8.2f}'.format(
    test_loss, math.exp(test_loss)))
print('=' * 89)

輸出效果:

=========================================================================================
| End of training | test loss  0.83 | test ppl     2.30
=========================================================================================


分享到:
在線(xiàn)咨詢(xún) 我要報(bào)名
和我們?cè)诰€(xiàn)交談!