本文使用了 keras-transformer 第三方库,库的模型结构和使用方法如下图,需要构造 encoder_input (x1) decoder_input (x2) decoder_output (y) 三个矩阵,用于进行模型训练
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import json
path_tang = './tang/poet.tang.'
# 读取 json 文件
def get_json(path):
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
制作唐诗数据集 v2
# 对 ./tang/ 文件夹下的所有 json 文件进行遍历
import os
# 获取文件夹下的所有文件名
def get_file_name(path):
file_name = []
for root, dirs, files in os.walk(path):
for file in files:
file_name.append(file)
return file_name
file_name_ls = get_file_name('./tang/')
ret_ls = []
for file_name in file_name_ls:
ls = get_json('./tang/' + file_name)
n_ls = len(ls)
for i in range(n_ls):
para = ls[i]['paragraphs']
para = ''.join(para)
ret_ls.append(para)
len(ret_ls) # 一共 57607 首诗歌
n_poet = len(ret_ls)
n_poet
查看前 10 首诗
for i in range(10):
print(ret_ls[i])
# 给 ret_ls 的每一个元素,都在每个字之间加空格
for i in range(n_poet):
ret_ls[i] = ' '.join(ret_ls[i])
for i in range(10):
print(ret_ls[i])
# 给 ret_ls 的每一个元素,前后都加上 <SOS> 和 <EOS>
for i in range(n_poet):
ret_ls[i] = '<SOS> ' + ret_ls[i] + ' <EOS>'
for i in range(10):
print(ret_ls[i])
进行 tokenizer 统计
# 全部合并成一个 string
str_all = ' '.join(ret_ls)
str_all[:1000]
# 用 keras 的 Tokenizer 进行统计
from keras.preprocessing.text import Tokenizer
# 设置最大词汇量为 10000 个词
tokenizer = Tokenizer(num_words=10000,char_level=False,filters='!"#$%&()*+,-./:;=?@[\\]^_`{|}~\t\n') # 因为添加了空格,所以 char_level=False
# 在 str_all 上进行训练
tokenizer.fit_on_texts([str_all])
# 设置padding 为 0
tokenizer.word_index['<pad>'] = 0
tokenizer.index_word[0] = '<pad>'
## 使用word_index属性查看每个词对应的编码
## 使用word_counts属性查看每个词对应的频数
for ii,iterm in enumerate(tokenizer.word_index.items()):
if ii < 10:
print(iterm)
else:
break
print("===================")
for ii,iterm in enumerate(tokenizer.word_counts.items()):
if ii < 10:
print(iterm)
else:
break
制作数据集 v2
# 对 ret_ls 进行分词,按照空格进行分词,得到 word_ls
word_ls = []
for i in range(n_poet):
word_ls.append(ret_ls[i].split(' '))
# 查看前 1 首诗歌的分词结果
for i in range(1):
print(word_ls[i])
# 制作数据集的方法:从 word_ls 中,采样一首诗,从这首诗开头采样长度为 10 的子串,然后预测接下来的10个字
# 数据集大小为 1w 样本对,采样方法是随机采样
import random
from tqdm import tqdm
x_seq_ls = []
y_seq_ls = []
decoder_output_ls = []
for i in tqdm(range(n_poet)):
if len(word_ls[i])-22<=0: # 如果这首诗歌的长度小于等于 22,就跳过
continue
# 随机选一个子串
start = random.randint(0, len(word_ls[i])-11)
end = start + 10
# 保存到 x_seq_ls 和 y_seq_ls 中
x_seq_ls.append(word_ls[i][start:end])
y_seq_ls.append(word_ls[i][end:end+10])
decoder_output_ls.append(word_ls[i][end+1:end+11])
len(x_seq_ls),len(y_seq_ls),len(decoder_output_ls)
# 查看一下 x_seq_ls 和 y_seq_ls
for i in range(20):
print(x_seq_ls[i],'\n',y_seq_ls[i],'\n',decoder_output_ls[i],'\n\n')
# 把 x_seq_ls 和 y_seq_ls 用 tokenizer 进行编码
x_token = tokenizer.texts_to_sequences(x_seq_ls)
y_token = tokenizer.texts_to_sequences(y_seq_ls)
decoder_output_token = tokenizer.texts_to_sequences(decoder_output_ls)
# 查看一下 x_seq_ls 和 y_seq_ls
for i in range(20):
print(x_token[i],'\n',y_token[i],'\n',decoder_output_token[i],'\n\n')
# 转化为 numpy (补零)
# x_mat = np.array(x_token)
# y_mat = np.array(y_token)
# 导入补零需要的padding_seq
from keras_preprocessing.sequence import pad_sequences
x_mat = pad_sequences(x_token,maxlen=10,padding='post',truncating='post')
y_mat = pad_sequences(y_token,maxlen=10,padding='post',truncating='post')
decoder_output_mat = pad_sequences(decoder_output_token,maxlen=10,padding='post',truncating='post')
# 查看一下 x_seq_ls 和 y_seq_ls
for i in range(20):
print(x_mat[i],'\n',y_mat[i],'\n',decoder_output_mat[i],'\n\n')
# 给decoder_output_mat 加上一个维度
decoder_output_mat = decoder_output_mat.reshape(decoder_output_mat.shape[0],decoder_output_mat.shape[1],1)
decoder_output_mat,decoder_output_mat.shape
划分训练集、测试集
# 划分训练集、测试集,把 x_mat,y_mat,decoder_output_mat 划分为训练集和测试集, 比例为 7:3
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test,decoder_output_train,decoder_output_test = train_test_split(x_mat,y_mat,decoder_output_mat,test_size=0.3,random_state=0)
x_train.shape, x_test.shape, y_train.shape, y_test.shape ,decoder_output_train.shape, decoder_output_test.shape
训练模型
搭建网络
import numpy as np
from keras_transformer import get_model
# Build the model
model = get_model(
token_num=10000,
embed_dim=128,
encoder_num=3,
decoder_num=2,
head_num=4, # embed_dim must be divisible by head_num
hidden_dim=256, # hidden_dim 没有要求
attention_activation='relu',
feed_forward_activation='relu',
dropout_rate=0.1,
)
model.compile(
optimizer='adam',
loss='sparse_categorical_crossentropy',
)
model.summary()
# 开始训练
history = model.fit([x_train,y_train], decoder_output_train, batch_size=32, epochs=5, validation_data=([x_test,y_test], decoder_output_test))
测试模型
test_string = '白日依山盡,黃河入海'
# 把 test_string 每个字之间添加空格
test_string = ' '.join(test_string)
# 前后加上开始和结束标志
# test_string = '<SOS> ' + test_string
# 把 test_string 转化为token
test_string_token = tokenizer.texts_to_sequences([test_string])
# 截取前10个字
test_string_token = test_string_token[0][:10]
# 转化为 numpy,补齐
test_string_mat = pad_sequences([test_string_token],maxlen=10,padding='post',truncating='post')
test_string_mat
from keras_transformer import decode
decoded = decode(
model,
test_string_mat.tolist(),
start_token=tokenizer.word_index['<sos>'],
end_token=tokenizer.word_index['<eos>'],
pad_token=tokenizer.word_index['<pad>'],
max_len=100,
top_k=4, # 添加 top_k 和 temperature 参数,增加随机性
temperature=1.0,
)
decoded
token_dict_rev = {v: k for k, v in tokenizer.word_index.items()}
for i in range(len(decoded)):
print(''.join(map(lambda x: token_dict_rev[x], decoded[i][1:-1])))