nanoGPT + 鲁迅

1. 起因

今晚看到了Simon Willison 的只使用自己的博客内容来训练nanoGPT的实验,觉得挺有意思,突发奇想,能不能在鲁迅的文集上训练一个nanoGPT,然后生成很具辨识度的鲁迅风格的文字呢?由于nanoGPT结构简单,鲁迅的文集在GitHub上可以下载到,因此通过简单的代码修改加实验,就得到一个在鲁迅作品上训练的GPT2模型(无别的语料库的预训练),简单测试下,以“故乡”开头,让模型生成鲁迅风格的文字:

1
2
3
4
故乡,债是佩服的。
我一向对于新青年的态度,先来说话,谢容易做的,然而伏园已经见过几样,感觉的是另外捧之数,以为先前的例子。今但近来做了做事,自己也还不做,不能先行通,所以生在冷静和“人生”,三妇一苦闷,觉得大约是如此隔膜
和曹操,于是非意模茶炛,可以说是太高了,所以现在便能教育,竟�如此。
但汝实在有给法历代的,不久就在绝末年间,我想显出向大家饮一趟,而汉子大毒是怀旧的,就要贫足有打劫,可以永掠的。这种事情,中国有一个大官左翼阿,(陀思妥习),有敢请佛喜,总要适说一点�

还算有鲁迅文字的风格,但逻辑一窍不通,整体还是难让人满意,不知道是GPT2能力的问题还是我实验设置的问题。 Anyway,这里共享一下我实验的流程,有兴趣的朋友可以参考,进行改进。本文涉及的代码修改代码已经提交到这个仓库了,可以参考,文末会附上更多例子。

2. 操作流程

2.1 下载nanoGPT源码并安装依赖

1
2
3
4
5
git clone https://github.com/karpathy/nanoGPT
cd nanoGPT
conda create --name nanogpt python=3.9
conda activate nanogpt
pip install transformers datasets tiktoken tqdm wandb numpy httpx torch torchvision

2.2 数据预处理

进入代码目录后,重建文件夹data/lunxun,用于存放数据。

这里下载鲁迅全集,放到data/luxun目录下,然后进行下面的处理:

  • 去掉所有编者加的注释(由于注释都是以[n]这种形式开头的,因此在VIM中可以用0,$s/^\[.\+//g命令来去掉)
  • 由于我们想要的是鲁迅白话文的风格,因此手动去掉所有文言文的作品和翻译作品(文言文在最开头的《坟》集子里,翻译作品在最后)
  • 去掉单行的日期文字(如(一九一八年二月二日),可以在VIM中用g/^(一九.\+/d去掉)

我处理后的文本地址在这里

然后编写代码prepare.py, 读取文本,构造训练集和验证集,数据比例9:1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python
import os
import json
import tiktoken
import numpy as np
import random

input_file_path = os.path.join(os.path.dirname(__file__), "book.txt")

entries = []
with open(input_file_path, "r") as f:
for line in f:
if line.strip() and len(line) > 2:
entries.append(line)

print(f"len of lines: {len(entries)}")
# Shuffle entries
random.shuffle(entries)

n = len(entries)
train_entries = entries[: int(n * 0.9)]
val_entries = entries[int(n * 0.9):]

# Turn those into strings
train_data = " ".join("{}".format(entry) for entry in train_entries)
val_data = " ".join("{}".format(entry) for entry in val_entries)

# encode with tiktoken gpt2 bpe
enc = tiktoken.get_encoding("gpt2")
train_ids = enc.encode_ordinary(train_data)
val_ids = enc.encode_ordinary(val_data)
print(f"train has {len(train_ids):,} tokens")
print(f"val has {len(val_ids):,} tokens")

# export to bin files
train_ids = np.array(train_ids, dtype=np.uint16)
val_ids = np.array(val_ids, dtype=np.uint16)
train_ids.tofile(os.path.join(os.path.dirname(__file__), "train.bin"))
val_ids.tofile(os.path.join(os.path.dirname(__file__), "val.bin"))

处理好的训练验证集在这里,可以直接使用。

2.3 训练网络

数据集构建完成后,就可以训练模型了。在代码库根目录,执行下面的命令:

1
2
3
4
5
python train.py \
--dataset=luxun \
--compile=False \
--batch_size=8 \
--dtype=float16

具体训练参数可以查看train.py,包括训练的层数、batch size,训练后端等等。

训练的模型默认保存在out/ckpt.pt

训练22000次迭代的时候我停止了实验,loss是0.15左右。

2.4 测试模型

测试代码在sample.py,默认提示词为空(start='\n')可以通过添加--start="xxx"来修改提示词:

1
python sample.py --start="故乡"

3. 更多例子

3.1 人工智能

1
2
3
人工智能力。两社丈一多,西辛,是发昏了的结果,但去的四五十岁,死的陈源教授却很不通,我也就不再有了。忍不知道阿Q的名字是怎么写的?这不过是一个问境。他总还拿着四个年的过头放在心里,说道,“哀,遇的。他可
是弯口,道是阿Q;近来已经做了杜师长了,半年的大武人,你还和他对面具汗说:‘非常救命!’‘是情愧 夫子’的学理论矛盾的工人,……而且跳不过是这三秒……。”
而已 偶战线告了一个大问题,拿去做的纠纷,而他们就癖在《试玈书》的第一幅,来因为又是删节的,还和所放的做。不过如果加以细见,不想多写了东�

3.2 文艺复兴

1
2
3
4
5
6
7
文艺复兴运动,也是指惺把文艺当承受照时代,更易于政治家,中国并不见立刻,万分折中间,更何况失了国家,只有拜读之处,这就是政治心软到新舖式的必读书。……”
我们──由此满可知道河南的内心眼和明白的点灵魂。我在曾经想做以看空虚洋,决不叫看情形。因为我想,便可以支持生活的原因,至少,更进一步而到中国来,他们也给了世界上的美�家所指见的最多也并非精微坏,莫非看翻译
,可说是不算太多了。
问题。
“我们没有见过这种东西,便怎么办呢?”
递进句也不是有许多话。
“可以可以,”四铭吃了点

3.3 新文化

1
2
3
4
5
6
7
8
新文化运动,也许因为他们已经有了“力”这句话的责任了。在那里和他们的风化是并不相禁多的。
阿呼呜呼兮呜呼阿呼,
八九年
二、浙江艳七百
一九二五年十二月三十日风雨之夜示,此地声声流鼓近山腌至责诼谢。
阿Q的讲到文学说,他们会打断了阿Q的名目退向王的头发,向公司被挤出去了。
最末的批评,是“没有话派的书,对于政府来往往解释,加以泄除,以政治的运命,至于失败,那倒是往往会说,我非常危险。
小娘枟不用小说的经济字的由校的文章,使是屠戮政府,是凡这些的,但我知道画家一致攻,一致的经历

如果本文操作中有误的地方,还请专业人士多指出讨论。