壹.古诗词

古诗词宽泛的来说,就是古人写的诗词,不过其大多具有以下特点:

1.句子的整齐

古典诗歌,除了词和曲以外,多数是句子长短整齐的,如《诗经》基本上是四言,《楚辞》大体上是六言加上“兮”字,古体诗和近体诗大多数是五言或七言。

2.平仄和对仗

平、仄是汉语声调的两大类。在近体诗和词、曲中,用字的平仄有相当严格的规定,在一些位置上,必须用平声字,在另一些位置上,必须用仄声字。比如:“国破山河在,城春草木深”(杜甫《春望》),就是用“仄仄仄平平,平平仄仄平”句式。

对仗指的是一联诗中,在上下句相同位置上的字要属于同一类,如“东圃青梅发,西园绿草开”。“东”和“西”,“圃”和“园”,“青梅”和“绿草”,“发”和“开”,各自相对。

3.词藻和句法

因为每一个汉字基本上都是一个兼具形音义的独立单位,而且很多汉字是一字多义的,字与字之间粘合的关系多种多样,所以,这种粘合而成的诗歌中的词藻,就显得异常复杂多样。例如,在“风”字后面再加一字,可以构成很多词语:风姿、风物、风雷、风霜、风鬟等。

在句法方面,由于汉语的特点,以及汉字的独立性,在古典诗歌中,常常出现把两个汉字拆开,或者把某些汉字从后面移到前面的情形,这叫倒装。典型的诗句是杜甫的“香稻啄余鹦鹉粒,碧梧栖老凤凰枝”,正常的句法应是“鹦鹉啄余香稻粒,凤凰栖老碧梧枝”。

4.节奏和韵律

从句式上看,古诗一般四字为二、二;五字为二、二、一;七字为二、二、二、一。从意义上看,有时因表意需要也有特殊情况如:势拔|五岳|掩|赤城,这样就成了二、二、一、二式。

古诗要求押韵,使音调和谐优美,如李白《静夜思》押ang韵。押韵韵脚的位置一般在偶数句末,如李白《静夜思》“床前明月光,疑是地上霜,举头望明月,低头思故乡。”“光”“霜”“乡”是韵脚。通常奇数句不押韵,首句入韵格式除外。

5.字数短小但表意丰富

诗词的字数一般只有几十个字,五言绝句是20个字,七言绝句是28个字,六言绝句是24个字,五言律诗,是40个字,七言律诗是56个字。但其表达的意义却是非常丰富的,如纳兰性德的《浣溪沙》:

谁念西风独自凉,萧萧黄叶闭疏窗,沉思往事立残阳。
被酒莫惊春睡重,赌书消得泼茶香,当时只道是寻常。

短短数十字,却将对妻子的思念之情表现的淋漓尽致,也留下了千古名句:“当时只道是寻常。”

表达时光易逝,流年匆匆的句子:

最是人间留不住,朱颜辞镜花辞树。

表达建功立业,驱逐外敌之心:

会挽雕弓如满月,西北望,射天狼。男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯。

表达人生志向:

安能摧眉折腰事权贵,使我不得开心颜!

表达思念之情:

衣带渐宽终不悔,为伊消得人憔悴。

.......

贰.代码

现在由于我们的语言体系,已经和以前的古语不一样,所以说我们的思维很少能够做到像古人那样用寥寥数字去表达自己的感情或者是描述看到的景象。

好在天无绝人之路,随着计算机的出现,深度学习的出现,人工智能的出现,这个问题变得可以解决。

我们的目的是学习古人的表达方式,创作出辞藻华丽,表意丰富的诗词。

那么我们可以用深度学习来实现这一功能:

基于循环神经网络实现的一个古诗生成器http://AaronJny/DeepLearningExamples/tf2-rnn-poetry-generator (https://github.com/AaronJny/DeepLearningExamples/tree/master/tf2-rnn-poetry-generator        训练数据是一个.txt文件,里面存储了大量的诗词:

我们 对数据进行以下几个方面的处理:

1.读取文本,按行切分,构成古诗列表。

2.将全角、半角的冒号统一替换成半角的。

3.按冒号切分诗的标题和内容,只保留诗的内容。

4.考虑到模型的大小,我们只保留内容长度小于一定长度的古诗。

5.统计保留的诗中的词频,去掉低频词,构建词汇表。

加载数据:

# 加载数据集
with open(settings.DATASET_PATH, 'r', encoding='utf-8') as f:lines = f.readlines()# 将冒号统一成相同格式lines = [line.replace(':', ':') for line in lines]
# 数据集列表
poetry = []
# 逐行处理读取到的数据
for line in lines:# 有且只能有一个冒号用来分割标题if line.count(':') != 1:continue# 后半部分不能包含禁止词__, last_part = line.split(':')ignore_flag = Falsefor dis_word in disallowed_words:if dis_word in last_part:ignore_flag = Truebreakif ignore_flag:continue# 长度不能超过最大长度if len(last_part) > max_len - 2:continuepoetry.append(last_part.replace('\n', ''))

统计词频:

# 统计词频
counter = Counter()
for line in poetry:counter.update(line)
# 过滤掉低频词
_tokens = [(token, count) for token, count in counter.items() if count >= min_word_frequency]
# 按词频排序
_tokens = sorted(_tokens, key=lambda x: -x[1])
# 去掉词频,只保留词列表
_tokens = [token for token, count in _tokens]# 将特殊词和数据集中的词拼接起来
_tokens = ['[PAD]', '[UNK]', '[CLS]', '[SEP]'] + _tokens
# 创建词典 token->id映射关系
token_id_dict = dict(zip(_tokens, range(len(_tokens))))
# 使用新词典重新建立分词器
tokenizer = Tokenizer(token_id_dict)
# 混洗数据
np.random.shuffle(poetry)

构造数据生成器:

class PoetryDataGenerator:"""古诗数据集生成器"""def __init__(self, data, random=False):# 数据集self.data = data# batch sizeself.batch_size = batch_size# 每个epoch迭代的步数self.steps = int(math.floor(len(self.data) / self.batch_size))# 每个epoch开始时是否随机混洗self.random = randomdef sequence_padding(self, data, length=None, padding=None):"""将给定数据填充到相同长度:param data: 待填充数据:param length: 填充后的长度,不传递此参数则使用data中的最大长度:param padding: 用于填充的数据,不传递此参数则使用[PAD]的对应编号:return: 填充后的数据"""# 计算填充长度if length is None:length = max(map(len, data))# 计算填充数据if padding is None:padding = tokenizer.token_to_id('[PAD]')# 开始填充outputs = []for line in data:padding_length = length - len(line)# 不足就进行填充if padding_length > 0:outputs.append(np.concatenate([line, [padding] * padding_length]))# 超过就进行截断else:outputs.append(line[:length])return np.array(outputs)def __len__(self):return self.stepsdef __iter__(self):total = len(self.data)# 是否随机混洗if self.random:np.random.shuffle(self.data)# 迭代一个epoch,每次yield一个batchfor start in range(0, total, self.batch_size):end = min(start + self.batch_size, total)batch_data = []# 逐一对古诗进行编码for single_data in self.data[start:end]:batch_data.append(tokenizer.encode(single_data))# 填充为相同长度batch_data = self.sequence_padding(batch_data)# yield x,yyield batch_data[:, :-1], tf.one_hot(batch_data[:, 1:], tokenizer.vocab_size)del batch_datadef for_fit(self):"""创建一个生成器,用于训练"""# 死循环,当数据训练一个epoch之后,重新迭代数据while True:# 委托生成器yield from self.__iter__()

构建模型:

# -*- coding: utf-8 -*-
# @File    : model.py
# @Author  : AaronJny
# @Time    : 2020/01/01
# @Desc    :
import tensorflow as tf
from dataset import tokenizer# 构建模型
model = tf.keras.Sequential([# 不定长度的输入tf.keras.layers.Input((None,)),# 词嵌入层tf.keras.layers.Embedding(input_dim=tokenizer.vocab_size, output_dim=128),# 第一个LSTM层,返回序列作为下一层的输入tf.keras.layers.LSTM(128, dropout=0.5, return_sequences=True),# 第二个LSTM层,返回序列作为下一层的输入tf.keras.layers.LSTM(128, dropout=0.5, return_sequences=True),# 对每一个时间点的输出都做softmax,预测下一个词的概率tf.keras.layers.TimeDistributed(tf.keras.layers.Dense(tokenizer.vocab_size, activation='softmax')),
])# 查看模型结构
model.summary()
# 配置优化器和损失函数
model.compile(optimizer=tf.keras.optimizers.Adam(), loss=tf.keras.losses.categorical_crossentropy)

编写随机生成诗词和随机生成藏头诗的函数:

# -*- coding: utf-8 -*-
# @File    : utils.py
# @Author  : AaronJny
# @Time    : 2019/12/30
# @Desc    :
import numpy as np
import settingsdef generate_random_poetry(tokenizer, model, s=''):"""随机生成一首诗:param tokenizer: 分词器:param model: 用于生成古诗的模型:param s: 用于生成古诗的起始字符串,默认为空串:return: 一个字符串,表示一首古诗"""# 将初始字符串转成tokentoken_ids = tokenizer.encode(s)# 去掉结束标记[SEP]token_ids = token_ids[:-1]while len(token_ids) < settings.MAX_LEN:# 进行预测,只保留第一个样例(我们输入的样例数只有1)的、最后一个token的预测的、不包含[PAD][UNK][CLS]的概率分布output = model(np.array([token_ids, ], dtype=np.int32))_probas = output.numpy()[0, -1, 3:]del output# print(_probas)# 按照出现概率,对所有token倒序排列p_args = _probas.argsort()[::-1][:100]# 排列后的概率顺序p = _probas[p_args]# 先对概率归一p = p / sum(p)# 再按照预测出的概率,随机选择一个词作为预测结果target_index = np.random.choice(len(p), p=p)target = p_args[target_index] + 3# 保存token_ids.append(target)if target == 3:breakreturn tokenizer.decode(token_ids)def generate_acrostic(tokenizer, model, head):"""随机生成一首藏头诗:param tokenizer: 分词器:param model: 用于生成古诗的模型:param head: 藏头诗的头:return: 一个字符串,表示一首古诗"""# 使用空串初始化token_ids,加入[CLS]token_ids = tokenizer.encode('')token_ids = token_ids[:-1]# 标点符号,这里简单的只把逗号和句号作为标点punctuations = [',', '。']punctuation_ids = {tokenizer.token_to_id(token) for token in punctuations}# 缓存生成的诗的listpoetry = []# 对于藏头诗中的每一个字,都生成一个短句for ch in head:# 先记录下这个字poetry.append(ch)# 将藏头诗的字符转成token idtoken_id = tokenizer.token_to_id(ch)# 加入到列表中去token_ids.append(token_id)# 开始生成一个短句while True:# 进行预测,只保留第一个样例(我们输入的样例数只有1)的、最后一个token的预测的、不包含[PAD][UNK][CLS]的概率分布output = model(np.array([token_ids, ], dtype=np.int32))_probas = output.numpy()[0, -1, 3:]del output# 按照出现概率,对所有token倒序排列p_args = _probas.argsort()[::-1][:100]# 排列后的概率顺序p = _probas[p_args]# 先对概率归一p = p / sum(p)# 再按照预测出的概率,随机选择一个词作为预测结果target_index = np.random.choice(len(p), p=p)target = p_args[target_index] + 3# 保存token_ids.append(target)# 只有不是特殊字符时,才保存到poetry里面去if target > 3:poetry.append(tokenizer.id_to_token(target))if target in punctuation_ids:breakreturn ''.join(poetry)

进行训练:

# -*- coding: utf-8 -*-
# @File    : train.py
# @Author  : AaronJny
# @Time    : 2020/01/01
# @Desc    :
import tensorflow as tf
from dataset import PoetryDataGenerator, poetry, tokenizer
from model import model
import settings
import utils
import osos.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
os.environ["HDF5_USE_FILE_LOCKING"] = 'FALSE'
class Evaluate(tf.keras.callbacks.Callback):"""在每个epoch训练完成后,保留最优权重,并随机生成settings.SHOW_NUM首古诗展示"""def __init__(self):super().__init__()# 给loss赋一个较大的初始值self.lowest = 1e10def on_epoch_end(self, epoch, logs=None):# 在每个epoch训练完成后调用# 如果当前loss更低,就保存当前模型参数if logs['loss'] <= self.lowest:self.lowest = logs['loss']model.save(settings.BEST_MODEL_PATH)# 随机生成几首古体诗测试,查看训练效果print()for i in range(settings.SHOW_NUM):print(utils.generate_random_poetry(tokenizer, model))# 创建数据集
data_generator = PoetryDataGenerator(poetry, random=True)
# 开始训练
model.fit_generator(data_generator.for_fit(), steps_per_epoch=data_generator.steps, epochs=settings.TRAIN_EPOCHS,callbacks=Evaluate())

训练完成后保留一个权重文件,使用这个权重文件来进行模型的预测:

# -*- coding: utf-8 -*-
# @File    : eval.py
# @Author  : AaronJny
# @Time    : 2020/01/01
# @Desc    :
import tensorflow as tf
from dataset import tokenizer
import settings
import utils# 加载训练好的模型
model = tf.keras.models.load_model(settings.BEST_MODEL_PATH)
# 随机生成一首诗
print(utils.generate_random_poetry(tokenizer, model))
# 给出部分信息的情况下,随机生成剩余部分
print(utils.generate_random_poetry(tokenizer, model, s='又岂在朝朝暮暮'))
# 生成藏头诗
print(utils.generate_acrostic(tokenizer, model, head='天下无贼'))

结果如下:

这样的诗词形式上与古人的表达相似,但是表意上就不知道说的什么了,希望能够找到表意准确且丰富的深度学习方法。

不过这也算是完成了写诗词的第一步:“写”,这也是一个很nice的事情了。

古诗词与代码之间不得不说的二三事。相关推荐

  1. 我与TOMCAT不得不说的二三事

    谨以此文送给所有正在使用TOMCAT或者打算使用的人们,向TOMCAT的所有开发人员致敬! 一.小猫TOMCAT其实很可爱 2003年底,我换公司了,同样也换了WEBAPP,TOMCAT出现在我的面前 ...

  2. (一)多线程说学逗唱:关于线程那不得不说的二三事

    (二)多线程说学逗唱:新手村偶遇Thread类 (三)多线程说学逗唱:村口的老R头是个扫地僧(Runnable) (四)多线程说学逗唱:线程险恶,变量和线程安全不得不防 (五)多线程说学逗唱:打铁还需 ...

  3. 我与程序员不得不说的二三事——一天一天

    (PS:在某游戏公司工作已经小一年了,参加了两个项目,经历了从菜菜鸟到菜鸟的转变,其中有酸有甜有苦也有辣,感慨颇多,尤其是每天和程序员GG们打交道,心中有很多想法,想要表达出来,好吧,先记录下我平时工 ...

  4. 互联网不得不说的二三事

    第一篇 据说中间那个网叫互联网 再看个动物版的 总结:网上凶似虎,见面怂如狗. 第二篇 来玩个游戏吧~ 来啊,互相伤害啊~ 总结:千万不要对自己的粉丝过于自信. 第三篇 中国的互联网为什么比外国火? ...

  5. uid、userId和appId之间不得不说的事 (二)

    大家好,我是阳哥!在uid.userId和appId之间不得不说的事(一)中,阳哥介绍了uid是如何生成的以及系统进程uid和用户名USER之间是如何关联在一起的.本篇文章,阳哥将为大家介绍userI ...

  6. 【机器学习与差分隐私代码实现】差分隐私代码实现系列(十二)

    差分隐私代码实现系列(十二) 写在前面的话 回顾 机器学习与差分隐私 使用 Scikit-Learn 进行逻辑回归 什么是模型? 使用梯度下降训练模型 梯度下降的单一步骤 梯度下降算法 梯度下降与差分 ...

  7. WebBrowser一点心得,如果在Javascript和Winform代码之间实现双向通信

    最近工作需要,学习了一下winform内嵌webbrowser控件,然后与htm页面中的javascript交互调用的技术,因此有了这篇心得. 总的来说,javascript与winform的code ...

  8. A.图机器学习(GML)图神经网络(GNN)原理和代码实现(前置学习系列二)

    图学习图神经网络算法专栏简介:主要实现图游走模型(DeepWalk.node2vec):图神经网络算法(GCN.GAT.GraphSage),部分进阶 GNN 模型(UniMP标签传播.ERNIESa ...

  9. 开发日记-20190417 关键词 代码之间耦合度的一刀两断 (思路篇)

    就目前而言,就我的理解,为什么要设计结构清晰的代码呢,因为结构清晰的代码往往耦合度很低,那为什么要降低代码之间的耦合度呢,当然是因为程序员是一种喜爱偷懒和害怕犯无意义错误的一种生物,那么该怎么办呢,无 ...

  10. python使用缩进来体现代码之间的逻辑关系-python使用缩进来体现代码之间的逻辑关系吗?...

    对,python使用缩进来体现代码之间的逻辑关系,对缩进的要求非常严格.Python语言通过缩进来组织代码块,这是Python的强制要求.在代码前放置空格来缩进语句即可创建语句块,语句块中的每行必须是 ...

最新文章

  1. 转 深入理解Midlet类
  2. 摄像头YUV图像常见数据格式介绍
  3. github图---小章鱼图标
  4. matlab 视频制作,利用Matlab制作AVI视频基础教程
  5. C语言程序设计流程图详解
  6. Aimo:盘姬工具箱CruiserEXP forWin.
  7. 如何用公众号关联认证小程序
  8. 1234变4321java_java:把1234成4321整数倒逆代码
  9. 500台机以上大型网吧设计方案(转)
  10. 关于局域网不能访问-共享打印机
  11. 从A至Z,用30个单词来概括过去十年的游戏行业
  12. java 获取回车字符_java回车键的字符
  13. 智慧零售的分级战场,苏宁618的升降策略能否厚积薄发?
  14. 【java】几种跳出 for循环的方法
  15. Linux涂鸦智能网关面板Turnkey方案
  16. OpenCV 学习笔记(Watershed)
  17. DICOM笔记-解析JPEG压缩格式DCM文件
  18. Ubuntu IBM T43的显卡驱动安装
  19. 桌面小六轴机械臂mechArm
  20. 英雄无敌3 死亡阴影 修改器

热门文章

  1. 微信公众平台、微信开放平台的关系
  2. div左对齐与里面的内容偏左但是距离左边有点儿距离
  3. 三国志战略版:先锋斥候广州行
  4. 系统集成项目管理工程师(一)
  5. Nginx负载均衡配置实例详解(转发学习)留给未来需要的自己
  6. python多条件求和_数据的多条件求和
  7. CentOS导入CA证书
  8. 1024为大家带来个猜数字游戏
  9. 实战PHP皮皮虾去水印解析接口
  10. 网络工程师_记录的一些真题_2017上半年上午