深度學習-使用RNN生成詩

代碼連鏈接       

這裏稍微更改了下《深度學習框架PyTorch:入門與實踐》裏的demo,去掉稍微繁瑣和多次訓練的部分,只保留了比較核心的生成連接詩和藏頭詩兩部分(比較渣太複雜了看不懂)。

目標效果:

連接詩:
機器學習書,局上無酒漿。婆娑珍金盤,縷爛金葳漿。萱草發秋葉,旖旎鏤金牆。拳芳既盈薄,祿位不敢匼。揆我不及飽,有時不相併。我爲蘧生意,日出獄所宣。徇祿懲未卜,退食何由嘗。濡毫若可濯,視事忘憂傷。茍餐償朽援,亦有糟醨漿。憶來不得意,與我來此方。幽谷蔭幽壤,清猨鳴秋霜。因思霖雨霽,始覺風物長。既與物外違,復知辱所並。吾欲顧己仁,豈必死者傷。茍哉吾安適,複道邁一觴。雖遇物外興,永忻良所侵。

藏頭詩:
機心儼不見,金馬不可量。器食非所古,此心何足傷。學書三十載,相勸一旬長。習隸不可樂,持斧亦葳裳。書來不可再,稅首皆自忙。

主要面main.py:

import torch as t
from data import get_data
from model import PoetryModel

"""對象"""
class Config(object):
    data_path = 'data/'  # 詩歌的文本文件存放路徑
    pickle_path = 'tang.npz'  # 預處理好的二進制文件,包含data,形狀爲(57580,125),共57580首詩歌,每首詩歌長度125,不夠補空格,多餘丟棄
    category = 'poet.tang'  # 類別,唐詩還是宋詩歌(poet.song)
    max_gen_len = 200  # 生成詩歌最長長度
    prefix_words = '細雨魚兒出,微風燕子斜。'  # 不是詩歌的組成部分,用來控制生成詩歌的意境
    start_words = '閒雲潭影日悠悠'  # 詩歌開始
    acrostic = True  # 是否是藏頭詩
    model_path = None  # 預訓練模型路徑
opt = Config()

"""給定一句詩,繼續生成一首完整的詩"""
def generate(model, start_words, ix2word, word2ix, prefix_words=None):
    """
         ix2word:每個序號對應的字
         word2ix:每個字對應的序號
         prefix_words:詩歌意境
         <EOP>:8290
         <START>:8591
         </s>:8292
         <,>:7066
         <。>:7435
     """
    results = list(start_words)
    start_word_len = len(start_words)
    input = t.Tensor([word2ix['<START>']]).view(1, 1).long()    # 手動設置第一個詞爲<START>
    hidden = None
    if prefix_words:   #意境詩句存在,這裏主要用於訓練記憶hidden,生成的output無用
        for word in prefix_words:
            output, hidden = model(input, hidden)
            input = input.data.new([word2ix[word]]).view(1, 1)
    for i in range(opt.max_gen_len):   #最大的句子長度
        output, hidden = model(input, hidden)   #前邊幾個output無用,因爲默認使用前綴詩句
        if i < start_word_len:   #如果小於前綴句子的長度
            w = results[i]   #取前綴對應位置的字
            input = input.data.new([word2ix[w]]).view(1, 1)   #取出前綴詩句對應的字的下標,形成1*1的矩陣
        else:   #已經輸出完前綴了,該保存output中計算的字了
            top_index = output.data[0].topk(1)[1][0].item()   #保存output中概率最大的那個字的下標
            w = ix2word[top_index]   #取出這個下標對應的字
            results.append(w)   #生成的詩句序列加入剛生成的結果
            input = input.data.new([top_index]).view(1, 1)   #把當前字的下標擴充成1*1當作下一次的輸入
        if w == '<EOP>':   #如果預測到了結束,刪掉並退出
            del results[-1]
            break
    return results

"""給定一句詩,對應生成藏頭詩"""
def gen_acrostic(model, start_words, ix2word, word2ix, prefix_words=None):
    """
        ix2word:每個序號對應的字
        word2ix:每個字對應的序號
        prefix_words:詩歌意境
        <EOP>:8290
        <START>:8591
        </s>:8292
        <,>:7066
        <。>:7435
    """
    results = []   #用於存儲生成的詩歌
    start_word_len = len(start_words)   #用於生成藏頭詩的詩句的長度(有幾個字生成幾句)
    input = (t.Tensor([word2ix['<START>']]).view(1, 1).long())   #word2ix['<START>']=8291,一開始input先賦值<START>的序號
    hidden = None
    index = 0  # 用來指示已經生成了多少句藏頭詩
    pre_word = '<START>'   #用來表示上一個詞,第一個詞設置爲'<START>'
    if prefix_words:   #如果設置了意境詩句,其中逗號和句號也包括在內
        for word in prefix_words:   #遍歷這句詩的每一個字
            output, hidden = model(input, hidden)   #input是1*1矩陣,只包含上一個字,用於輸進去進行降維,和一個空的hidden記憶,輸出關於當前字的記憶hidden和當前的輸出output,這個output貌似沒用
            input = (input.data.new([word2ix[word]])).view(1, 1)   #將當前遍歷的字的編號取出形成一個1*1矩陣賦值給input,input.data.new([word2ix[word]])是將一個數字變成一維tensor,view(1,1)是變成2維tensor
    for i in range(opt.max_gen_len):   #生成詩歌最長長度
        output, hidden = model(input, hidden)   #對之前意境的最後一個字進行處理,這裏其實是句號,output代表對下一個字的預測概率
        top_index = output.data[0].topk(1)[1][0].item()   #topk(1)取出向量中一個最大值,返回概率和對應下標
        w = ix2word[top_index]   #獲取機率最大的下標對應的字
        if (pre_word in {'。', '!', '<START>'}): # 如果遇到句號歎號或者開始符號,藏頭的詞送進去生成
            if index == start_word_len:   #index用來指示已經生成了多少句藏頭詩,如果相等則代表已經生成完了,退出循環
                break
            else: # 把藏頭的詞作爲輸入送入模型
                w = start_words[index]   #取藏頭詩中第index個下標對應的字(這裏如果是每一句開頭,會拋棄上一句最後取出來的最大概率的w,重新覆蓋了)
                index += 1
                input = (input.data.new([word2ix[w]])).view(1, 1)   #取對應字的數字編號作爲一個1*1矩陣,作爲下一個詞的輸入
        else: # 否則的話,把上一次預測是詞作爲下一個詞輸入
            input = (input.data.new([word2ix[w]])).view(1, 1)
        results.append(w)   #結果詩句添加當前字
        pre_word = w   #將w賦值給變量作爲前一個單詞
    return results


"""提供接口,處理命令選擇生成詩句類型"""
def gen(kwargs):
    for k, v in kwargs.items():   #遍歷出來的k是鍵,v是值
        setattr(opt, k, v)   #設置屬性值,第一個參數是對象,第二個參數是屬性,第三個參數是屬性值
    data, word2ix, ix2word = get_data(opt)   #第一個參數每一行是一首詩對應的字的下標,第二個參數是每個字對應的序號,第三個參數是每個序號對應的字
    model = PoetryModel(len(word2ix), 128, 256)
    map_location = lambda s, l: s   #這三句貌似是將模型加載在GPU上
    state_dict = t.load(opt.model_path, map_location=map_location)
    model.load_state_dict(state_dict)

    gen_poetry = gen_acrostic if opt.acrostic else generate   #是不是藏頭詩,gen_acrostic爲將提示句拆成每句第一個字生成藏頭詩,generate爲將提示句作爲第一句
    result = gen_poetry(model, opt.start_words, ix2word, word2ix, opt.prefix_words)   #ix2word爲每個序號對應的字,word2ix爲每個字對應的序號,prefix_words爲師哥意境
    print(''.join(result))

"""主方法"""
def test():
    gen({'model_path': 'checkpoints/tang_199.pth', 'pickle_path': 'tang.npz', 'start_words': '機器學習書',
         'prefix_words': '牀前明月光,疑是地上霜。', 'acrostic': True, 'nouse_gpu': False})

test()

獲取數據data.py:

# coding:utf-8
import os
import numpy as np

"""從二進制文件中獲取詩歌數據"""
def get_data(opt):   #讀取二進制numpy文件
    """
        opt 配置選項 Config對象
        dict,每個字對應的序號,形如u'月'->100
        ix2word: dict,每個序號對應的字,形如'100'->'月'
        data: numpy數組,每一行是一首詩對應的字的下標
    """
    if os.path.exists(opt.pickle_path):   #判斷路徑是否存在,即tang.npz這個文件是否存在(npz爲二進制文件)
        data = np.load(opt.pickle_path,allow_pickle=True)   #讀取tang.npz中的數據存到data中
        data, word2ix, ix2word = data['data'], data['word2ix'].item(), data['ix2word'].item()   #分類獲取數據
        return data, word2ix, ix2word

模型model.py:

# coding:utf-8
import torch
import torch.nn as nn

"""詩歌模型"""
class PoetryModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        """
            vocab_size: 所有字的長度8293
            embedding_dim: 詞嵌入的維度128
            hidden_dim: 隱藏層向量維度,即隱藏層節點個數256
        """
        super(PoetryModel, self).__init__()
        self.hidden_dim = hidden_dim
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)   #權重矩陣爲vocab_size*embedding_dim,即8293*128
        self.lstm = nn.LSTM(embedding_dim, self.hidden_dim, num_layers=2)   #第三個參數爲網絡層數
        self.linear1 = nn.Linear(self.hidden_dim, vocab_size)   #全連接層,生成一個詞彙表,詞彙表的數值是概率,代表這句話下一個位置這個單詞出現的概率

    def forward(self, input, hidden=None):
        seq_len, batch_size = input.size()   #input是一個一個的數字,seq_len表示每一句話多長(有多少單詞),batcg_size表示一次處理幾個句子
        if hidden is None:   #一開始設置h_0和c_0都爲0
            h_0 = input.data.new(2, batch_size, self.hidden_dim).fill_(0).float()   #隱藏元,維度爲(2*batch_size*hidden_dim),全部填充爲0,這裏的shape爲(2,1,256)
            c_0 = input.data.new(2, batch_size, self.hidden_dim).fill_(0).float()
        else:
            h_0, c_0 = hidden
        embeds = self.embeddings(input)   #(1,1,128)
        output, hidden = self.lstm(embeds, (h_0, c_0))   #output爲(1,1,256),hidden中包含了同爲(1,1,256)的h_0和c_0
        output = self.linear1(output.view(seq_len * batch_size, -1))   #全連接後變成(1,8293)
        return output, hidden

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章