1

深度学习(三)之LSTM写诗 - 段小辉

 2 years ago
source link: https://www.cnblogs.com/xiaohuiduan/p/16100596.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  1. 根据前文生成诗:

    机器学习业,圣贤不可求。临戎辞蜀计,忠信尽封疆。天子咨两相,建章应四方。自疑非俗态,谁复念鹪鹩。

  2. 生成藏头诗:

    步平生不愿君,古人今在古人风。

    公既得忘机者,白首空山道姓名。

    道不应无散处,未曾进退却还征。

  • python:3.9.7
  • pytorch:1.11.0
  • numpy:1.21.2

代码地址:https://github.com/xiaohuiduan/deeplearning-study/tree/main/写诗

数据预处理

数据集文件由3部分组成:ix2wordword2ixdata

  • ix2word:id到word的映射,如{23:'姑'},一共有8293个word。
  • word2ix2:word到id的映射,如{'姑':23}
  • data:保存了诗的数据,一共有57580首诗,每条数据由125个word构成;如果诗的长度大于125则截断,如果诗的长度小于125,则使用""进行填充。

每条数据的构成规则为:</s></s></s>……<START>诗词<EOP>

在训练的时候,不考虑填充数据,因此,将数据中的填充数据</s>去除,去除后,部分数据显示如下:

构建数据集

模型输入输出决定了数据集怎么构建,下图是模型的输入输出示意图。诗词生成实际上是一个语言模型,我们希望Model能够根据当前输入x0,x1,x2…xn−1x0,x1,x2…xn−1去预测下一个状态xnxn。如图中所示例子,则是希望在训练的过程中,模型能够根据输入<START>床前明月光生成床前明月光,

因此根据“<START>床前明月光,凝是地上霜。举头望明月,低头思故乡<EOP>”,可以生成如下的X和Y(seq_len=6)。

X:<START>床前明月光,Y:床前明月光,

X:,凝是地上霜,Y:凝是地上霜。

X:。举头望明月,Y:举头望明月,

X:,低头思故乡,Y:低头思故乡。

代码示意图如下所示,seq_len代表每条训练数据的长度。

seq_len = 48
X = []
Y = []

poems_data = [j for i in poems for j in i] # 将所有诗的内容变成一个一维数组

for i in range(0,len(poems_data) - seq_len -1,seq_len):
    X.append(poems_data[i:i+seq_len])
    Y.append(poems_data[i+1:i+seq_len+1])

模型结构如下所示,模型一共由3部分构成,Embedding层,LSTM层和全连接层。输入数据首先输入Embedding层,进行word2vec,然后将Word2Vec后的数据输入到LSTM中,最后将LSTM的输出输入到全连接层中得到预测结果。

模型构建代码如下,其中在本文中embedding_dim=200,hidden_dim=1024

import torch
import torch.nn.functional as F
import torch.nn as nn
class PoemNet(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        """
            vocab_size:训练集合字典大小(8293)
            embedding_dim:word2vec的维度
            hidden_dim:LSTM的hidden_dim
        """
        super(PoemNet, self).__init__()
        self.hidden_dim = hidden_dim
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, self.hidden_dim,batch_first=True)

        self.fc = nn.Sequential(
            nn.Linear(self.hidden_dim,2048),
            nn.ReLU(),
            nn.Dropout(0.25),
            
            nn.Linear(2048,4096),
            nn.Dropout(0.2),
            nn.ReLU(),
            nn.Linear(4096,vocab_size),
        )

    def forward(self, input,hidden=None):
        """
            input:输入的诗词
            hidden:在生成诗词的时候需要使用,在pytorch中,如果不指定初始状态h_0和C_0,则其
            默认为0.
            pytorch的LSTM的输出是(output,(h_n,c_n))。实际上,output就是h_1,h_2,……h_n
        """
        embeds = self.embeddings(input)
        batch_size, seq_len = input.size()
        if hidden is None:
            output, hidden = self.lstm(embeds)
        else:
            # h_0,c_0 = hidden
            output, hidden = self.lstm(embeds,hidden)
    
        output = self.fc(output)
        output = output.reshape(batch_size * seq_len, -1)
        output = F.log_softmax(output,dim=1)
        return output,hidden

优化器使用的是Adam优化器,lr=0.001,损失函数是CrossEntropyLoss。训练次数为100个epcoh。

因为在模型构建的过程中,使用了dropout,所以在模型使用的时候,需要将model设置为eval模式。

生成诗的逻辑图:

根据上文生成诗

根据上图的原理,写出的代码如下所示:

def generate_poem(my_words,max_len=128):
    '''
        根据前文my_words生成一首诗。max_len表示生成诗的最大长度。
    '''

    def __generate_next(idx,hidden=None):
        """
            根据input和hidden输出下一个预测
        """
        input = torch.Tensor([idx]).view(1,1).long().to(device)
        output,hidden = my_net(input,hidden)
        return output,hidden

    # 初始化hidden状态
    output,hidden = __generate_next(word2ix["<START>"])
    my_words_len = len(my_words)
    result = []
    for word in my_words:
        result.append(word)
        # 积累hidden状态(h & c)
        output,hidden = __generate_next(word2ix[word],hidden)
    
    _,top_index = torch.max(output,1)

    word = idx2word[top_index[0].item()]

    result.append(word)

    for i in range(max_len-my_words_len):
        output,hidden = __generate_next(top_index[0].item(),hidden)

        _,top_index = torch.max(output,1)
        if top_index[0].item() == word2ix['<EOP>']: # 如果诗词已经预测到结尾
            break
        word = idx2word[top_index[0].item()]
        result.append(word)
    return "".join(result)

generate_poem("睡觉")

睡觉寒炉火,晨钟坐中朝。炉烟沾煖露,池月静清砧。自有传心法,曾无住处传。不知尘世隔,一觉一壺秋。皎洁垂银液,浮航入绿醪。谁知旧邻里,相对似相亲。

生成藏头诗

生成藏头诗的方法与根据上文生成诗的方法大同小异。

def acrostic_poetry(my_words):
    def __generate_next(idx,hidden=None):
        """
            根据input和hidden输出下一个预测词
        """
        input = torch.Tensor([idx]).view(1,1).long().to(device)
        output,hidden = my_net(input,hidden)
        return output,hidden

    def __generate(word,hidden):
        """
            根据word生成一句诗(以“。”结尾的话) 如根据床生成“床前明月光,凝是地上霜。”
        """
        generate_word = word2ix[word]
        sentence = []
        sentence.append(word)
        while generate_word != word2ix["。"]: 
            output,hidden = __generate_next(generate_word,hidden)
            _,top_index = torch.max(output,1)
            generate_word = top_index[0].item()
            sentence.append(idx2word[generate_word])
        # 根据"。"生成下一个隐状态。
        _,hidden = __generate_next(generate_word,hidden)
        return sentence,hidden

    _,hidden = __generate_next(word2ix["<START>"])
    result = []
    for word in my_words:
        sentence,hidden = __generate(word,hidden)
        result.append("".join(sentence))
    print("\n".join(result))

acrostic_poetry("滚去读书")

滚发初生光,三乘如太白。 去去冥冥没,冥茫寄天海。 读书三十年,手把棼琴策。 书罢华省郎,忧人惜凋病。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK