【自然语言处理课程实验】实验五:RNN实现古诗句生成任务
·
原始数据目录

Q1RNN实现古诗句生成任务
import os
import numpy as np
import pickle
import torch
import torch.nn as nn
from gensim.models.word2vec import Word2Vec
from torch.utils.data import Dataset, DataLoader
def split_text(file="poetry_7.txt", train_num=6000):
#读取数据
all_data = open(file, "r", encoding="utf-8").read()
with open("split_7.txt", "w", encoding="utf-8") as f:
split_data = " ".join(all_data)
f.write(split_data)
#返回处理后字符串的前 train_num * 64个字符
return split_data[: train_num * 64]
def train_vec(vector_size=128, split_file="split_7.txt", org_file="poetry_7.txt", train_num=6000):
param_file = "word_vec.pkl"
#读取原始数据
org_data = open(org_file, "r", encoding="utf-8").read().split("\n")[:train_num]
#检查是否存在已经分割的文件
#如果存在则读取部分数据
if os.path.exists(split_file):
all_data_split = (open(split_file, "r", encoding="utf-8").read().split("\n")[:train_num])
else:
#如果不存在则调用split_text函数分割数据
all_data_split = split_text().split("\n")[:train_num]
#检查模型参数
if os.path.exists(param_file):
return org_data, pickle.load(open(param_file, "rb"))
#使用Word2vec训练模型
models = Word2Vec(all_data_split, vector_size=vector_size, workers=7, min_count=1)
#保存模型参数到文件中
pickle.dump([models.syn1neg, models.wv.key_to_index, models.wv.index_to_key],open(param_file, "wb"),)
#返回原始数据和训练好的模型参数
return org_data, (models.syn1neg, models.wv.key_to_index, models.wv.index_to_key)
class Poetry_Dataset(Dataset):
def __init__(self, w1, word_2_index, all_data):
self.w1 = w1 #词向量矩阵
self.word_2_index = word_2_index #单词到索引的映射字典
self.all_data = all_data #所有诗歌数据
def __getitem__(self, index):
a_poetry = self.all_data[index] #获得一个诗歌数据
a_poetry_index = [self.word_2_index[i] for i in a_poetry] #将诗歌中的单词转换为对应的索引
xs = a_poetry_index[:-1] #输入序列
ys = a_poetry_index[1:] #目标序列
xs_embedding = self.w1[xs] #获取输入序列的词向量表示
#返回输入序列的词向量表示和目标序列的索引表示
return xs_embedding, np.array(ys).astype(np.int64)
def __len__(self):
return len(self.all_data)
### 实验要求:
1. 在Poetry_Model_lstm的to_train函数中,实现:
- loss计算
- 反向传播
- 优化器更新
2. 完成generate_poetry_acrostic函数中缺少的代码,并任意生成一首藏头诗
class Poetry_Model_lstm(nn.Module):
def __init__(self, params):
super().__init__()
#调用train_vec函数加载数据和词向量模型参数
self.all_data, (self.w1, self.word_2_index, self.index_2_word) = train_vec(
vector_size=params["embedding_num"], train_num=params["train_num"])
#设置模型配置
self.device="cpu" #训练设备CPU
self.hidden_num = params["hidden_num"] #LSTM隐藏层大小
self.batch_size = params["batch_size"] #批量大小
self.epochs = params["epochs"] #轮数
self.lr = params["lr"] #学习率
self.optimizer = params["optimizer"] #优化器选择
self.word_size, self.embedding_num = self.w1.shape #词向量维度
#搭建模型结构
self.lstm = nn.LSTM(
input_size=self.embedding_num, #输入大小为词向量维度
hidden_size=self.hidden_num, #LSTM隐藏层大小
batch_first=True,#输入数据的第一个维度是batch_size
num_layers=2, #LSTM层数
bidirectional=False,) #不适用双向LSTM
self.dropout = nn.Dropout(0.3) # 古诗不具有唯一性,防止过拟合
self.flatten = nn.Flatten(0, 1) #展平操作,将多维输入拉平成一维
self.linear = nn.Linear(self.hidden_num, self.word_size) #全连接层,将LSTM输出映射到词汇空间
self.cross_entropy = nn.CrossEntropyLoss()#交叉熵损失函数
#前向传播
def forward(self, xs_embedding, h_0=None, c_0=None):
#如果没有提供初始隐藏状态和记忆状态,则创建全0的状态张量
if h_0 == None or c_0 == None:
h_0 = torch.tensor(
np.zeros((2, xs_embedding.shape[0], self.hidden_num), dtype=np.float32))
c_0 = torch.tensor(
np.zeros((2, xs_embedding.shape[0], self.hidden_num), dtype=np.float32))
#将隐藏状态和记忆状态转移到CPU
h_0 = h_0.to(self.device)
c_0 = c_0.to(self.device)
#输入数据转移到CPU
xs_embedding = xs_embedding.to(self.device)
#LSTM层向前传播
hidden, (h_0, c_0) = self.lstm(xs_embedding, (h_0, c_0))#LSTM层的输出和更新的隐藏状态、记忆状态
hidden_drop = self.dropout(hidden) #应用Dropout层,防止过你哈
hidden_flatten = self.flatten(hidden_drop)#多维数据展平成一维
pre = self.linear(hidden_flatten)#全连接层
return pre, (h_0, c_0) #返回预测结果和更新后的隐藏状态和记忆状态
#训练
def to_train(self):
model_result_file = "Poetry_Model_lstm_model.pkl"
if os.path.exists(model_result_file):
return pickle.load(open(model_result_file, "rb"))
#加载数据集
dataset = Poetry_Dataset(self.w1, self.word_2_index, self.all_data)
dataloader = DataLoader(dataset, self.batch_size)
#优化器
optimizer = self.optimizer(self.parameters(), self.lr)
#将模型放到CPU
self = self.to(self.device)
#循环训练模型多个epoch
for e in range(self.epochs):
#遍历数据集的每个批次进行训练
for batch_index, (batch_x_embedding, batch_y_index) in enumerate(dataloader):
self.train() #开启训练模式
#将批次数据转移到CPU上
batch_x_embedding = batch_x_embedding.to(self.device)
#将目标序列索引转移到CPU
batch_y_index = batch_y_index.to(self.device)
#根据输入词向量,预测结果
pre, _ = self(batch_x_embedding)
# ======================
# 在此行注释下完成相应内容
#计算损失
loss = self.cross_entropy(pre.view(-1,self.word_size),batch_y_index.view(-1))
#优化器梯度归零
optimizer.zero_grad()
#反向传播计算损失
loss.backward()
#更新参数
optimizer.step()
# ======================
if batch_index % 100 == 0:
print(f"loss:{loss:.3f}")
#生成自动诗句
self.generate_poetry_auto()
#保存模型参数
pickle.dump(self, open(model_result_file, "wb"))
return self
#自动生成自由诗
def generate_poetry_auto(self):
result = ""
word_index = np.random.randint(0, self.word_size, 1)[0]
result += self.index_2_word[word_index]
h_0 = torch.tensor(np.zeros((2, 1, self.hidden_num), dtype=np.float32))
c_0 = torch.tensor(np.zeros((2, 1, self.hidden_num), dtype=np.float32))
for i in range(31):
word_embedding = torch.tensor(self.w1[word_index][None][None])
pre, (h_0, c_0) = self(word_embedding, h_0, c_0)
word_index = int(torch.argmax(pre))
result += self.index_2_word[word_index]
print(result)
#自动生成藏头诗
def generate_poetry_acrostic(self):
input_text = input("请输入四个汉字:")[:4]
if input_text == "":
self.generate_poetry_auto()
else:
result = ""
punctuation_list = [",", "。", ",", "。"]
for i in range(4):
h_0 = torch.tensor(
np.zeros((2, 1, self.hidden_num), dtype=np.float32))
c_0 = torch.tensor(
np.zeros((2, 1, self.hidden_num), dtype=np.float32))
word = input_text[i]
try:
# ======================
word_index = self.word_2_index[word]
# ======================
except:
word_index = np.random.randint(0, self.word_size, 1)[0]
word = self.index_2_word[word_index]
result += word
for j in range(6):
word_index = self.word_2_index[word]
word_embedding = torch.tensor(self.w1[word_index][None][None])
pre, (h_0, c_0) = model(word_embedding, h_0, c_0)
word = self.index_2_word[int(torch.argmax(pre))]
result += word
result += punctuation_list[i]
print(result)
params = {
"batch_size": 32, # batch大小
"epochs": 1000, # epoch大小
"lr": 0.003, # 学习率
"hidden_num": 64, # 隐层大小
"embedding_num": 128, # 词向量维度
"train_num": 1000, # 训练的故事数量, 七言古诗:0~6290, 五言古诗:0~2929
"optimizer": torch.optim.AdamW, # 优化器 , 注意不要加括号
"batch_num_test": 100, # 多少个batch 打印一首古诗进行效果测试
}
model = Poetry_Model_lstm(params) # 模型定义
model = model.to_train() # 模型训练
model.generate_poetry_acrostic()
请输入四个汉字:鸡你太美
输出:鸡头跣卉总渊润,嗜蟹镇山休垣而。太嫌闽晏竖泛爱,美席驯帆竖绣操
Q2
from keras.layers import Dense,Embedding,Activation
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.models import Sequential
from sklearn.model_selection import train_test_split
from keras.preprocessing import sequence
import collections
import numpy as np
## 统计train.txt中最长句子的长度maxlen;句子条数num_recs;每词的词词频word_freqs[]
maxlen = 0
word_freqs = collections.Counter() #创建一个类,跟踪值出现的次数
num_recs = 0
import nltk
with open('train.txt','r',encoding='utf-8') as f:
for line in f:
label, sentence = line.strip().split("\t")
words = nltk.word_tokenize(sentence.lower()) ## 分词 >>> import nltk >>> nltk.download('punkt')
if len(words) > maxlen: # 找最长句子
maxlen = len(words)
for word in words: #统计词频
word_freqs[word] += 1 # token: freq
num_recs += 1 # 样本个数
print('max_len ',maxlen) #最长句子
print('nb_words ', len(word_freqs)) # 单词个数
# 总词汇表长为2002:2000单词+ 超出语料的新词UNK+ 短句填充词0
# 生成单词表和数字表
MAX_FEATURES = 2000 # 词典大小
MAX_SENTENCE_LENGTH = 40 # 句子长度40
# word2index, index2word, 单词和下标转换表
vocab_size = min(MAX_FEATURES, len(word_freqs)) + 2 #词表长度2002,其中包括两个PAD, UNK
word2index = {x[0]: i+2 for i, x in enumerate(word_freqs.most_common(MAX_FEATURES))} # token: index(2~ )
word2index["PAD"] = 0 # 下标为0
word2index["UNK"] = 1 # 下标为1
index2word = {v:k for k, v in word2index.items()} # index2word, word2index
# data preparation,生成句子表和序号表
X = np.empty(num_recs,dtype=list) # 7086个样本
y = np.zeros(num_recs)
i=0
with open('train.txt','r+',encoding='utf-8') as f:
for line in f:
label, sentence = line.strip().split("\t")
words = nltk.word_tokenize(sentence.lower())
seqs = []
for word in words:
if word in word2index:
seqs.append(word2index[word]) # 句子中的词下标加入 seqs
else:
seqs.append(word2index["UNK"]) #词典中没有的词
X[i] = seqs # X[i]存的句子中单词的下标
y[i] = int(label) # 7086个标记
i += 1
X = sequence.pad_sequences(X, maxlen=MAX_SENTENCE_LENGTH) # X为下标矩阵 7086 * 40
### 实验要求:
1. 按照训练集,测试集8:2的比例划分数据集
2. 为模型添加embedding layer、lstm layer
这个是选做,我好像没做
更多推荐
所有评论(0)