NanoGPT Tutorial

来自WHY42
Riguz留言 | 贡献2023年12月11日 (一) 16:13的版本 →‎佛经

环境准备

本文所有操作均在MacBook Air(macOS 13.5.1,2020版,M1芯片)和OptiPlex 7080(Linux Mint 21.2)上测试验证。 您也可以在其他的系统上运行,只需要在安装conda时按照官方文档稍作改动即可。

安装Miniconda 和Python

在MacOS下,可以通过以下脚本安装[1]

$ mkdir -p ~/miniconda3
$ curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh -o ~/miniconda3/miniconda.sh
$ bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
$ rm -rf ~/miniconda3/miniconda.sh

安装完成后,可以使用conda命令来管理机器学习的Python环境了。默认系统会自动创建一个Python3.11的环境:

$ python --version
Python 3.11.5
$ whereis python
python: /Users/riguz/miniconda3/bin/python

安装PyTorch

由于nanoGPT是机遇PyTorch的,因此需要安装它。在Mac上运行时,可以采用nightly版本, 因为更新的版本会集成Mac自带GPU的支持,“可能”性能上可以得到提升。

$ conda install pytorch-nightly::pytorch torchvision torchaudio -c pytorch-nightly

下载nanoGPT

nanoGPT依赖于一些Python的软件包,在运行之前应该首先进行安装[2]。 这里直接通过pip进行安装即可, 为加快下载速度,可以选择使用国内的pip源,如清华镜像(-i https://pypi.tuna.tsinghua.edu.cn/simple)。

$ pip install torch numpy transformers datasets tiktoken wandb tqdm -i https://pypi.tuna.tsinghua.edu.cn/simple

接下来将nanoGPT克隆到本地:

$ git clone https://github.com/drriguz/nanoGPT.git

运行“莎士比亚”例子

为了测试nanoGPT是否能正确工作,我们可以运行它自带的莎士比亚的例子。 该例子通过一个两三万行的莎士比亚剧本作为训练语料,大概长这样:

First Citizen:
Before we proceed any further, hear me speak.

All:
Speak, speak.

First Citizen:
You are all resolved rather to die than to famish?

All:
Resolved. resolved.
...

运行这个例子分为三步:

  1. 准备数据,通过data/shakespeare_char/prepare.py脚本将语料加载到本地,并将数据切分为训练集和验证集,供训练使用
  2. 通过train.py进行训练
  3. 训练完成后,通过sample.py运行查看结果

首先,数据准备非常简单,直接运行prepare.py即可。 该脚本会将数据拆分成两部分,一部分用来进行训练,一部分用来做验证, 并将原文分别进行简单的编码,穷举所有字符并制作一个映射表通过序号进行映射,最后生成包含这些编码序号的文件train.bin和val.bin:

$ cd nanoGPT
$ python data/shakespeare_char/prepare.py
length of dataset in characters: 1,115,394
all the unique characters:
 !$&',-.3:;?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
vocab size: 65
train has 1,003,854 tokens
val has 111,540 tokens

然后,开始真正的训练过程。由于MacBook上没有支持CUDA的显卡,只能使用CPU推理:

# 如果在不支持CUDA的设备上运行会报错:
# $ python train.py config/train_shakespeare_char.py
# raise AssertionError("Torch not compiled with CUDA enabled")
python train.py config/train_shakespeare_char.py --device=cpu --compile=False --eval_iters=20 --log_interval=1 --block_size=64 --batch_size=12 --n_layer=4 --n_head=4 --n_embd=128 --max_iters=2000 --lr_decay_iters=2000 --dropout=0.0

...
step 2000: train loss 1.7640, val loss 1.8925
saving checkpoint to out-shakespeare-char
iter 2000: loss 1.6982, time 306.45ms, mfu 0.05%

可以看到,train loss(训练损失)会逐渐减少,说明训练起到效果了。 整个过程需要大概1分钟的时间,训练结束后,就可以运行查看效果了:

$ python sample.py --out_dir=out-shakespeare-char --device=cpu
Overriding: out_dir = out-shakespeare-char
Overriding: device = cpu
number of parameters: 0.80M
Loading meta from data/shakespeare_char/meta.pkl...

I by doth what letterd fain flowarrman,
Lotheefuly daught shouss blate thou his though'd that opt--
Hammine than you, not neme your down way.

ELANUS:
I would and murser wormen that more?
...

对于笔者来说,很难判断生成的结果到底有多好,就算拿莎士比亚的原文可能也品味不出来好坏。 但是,至少从形式上来看,生成的结果是令人满意的,至少看起来像那么回事。

至此,我们已经亲手训练并运行成功了nanoGPT,是不是很简单! 理论上如果通过GPU训练会更快一些,但是在MacBook上测试通过mps(--device=mps) 训练的时间与CPU几乎相当。

训练一个nanoGPT模型

从莎士比亚的例子可以看出,nanoGPT训练的过程实际上十分简单,只需要一段文本,就可以从中学习到一些知识。 本质上,学习的是“概率”,即文字与文字之间的关系。 那么,如果我们用一些其他的语料, 是不是就可以得到想要的结果呢?

训练一个会背《三字经》的模型

看起来nanoGPT学习能力还不错,那我们不妨来让它学习一下中国小孩的启蒙教材《三字经》吧。 这里我们仿照shakespeare_char的做法, 把训练数据换成三字经:

人之初
性本善
性相近
...

我们期望nanoGPT学习了这些数据之后, 起码能生成像每句话三个字的“三字经”。 使用同样的参数训练一下试试:

$ python data/sanzijing_char/prepare.py
$ python train.py config/train_sanzijing_char.py --device=cpu --compile=False --eval_iters=20 --log_interval=1 --block_size=64 --batch_size=12 --n_layer=4 --n_head=4 --n_embd=128 --max_iters=2000 --lr_decay_iters=2000 --dropout=0.0

...
iter 1998: loss 0.0437, time 23.68ms, mfu 0.06%
iter 1999: loss 0.0535, time 23.63ms, mfu 0.06%
step 2000: train loss 0.0445, val loss 8.9908
iter 2000: loss 0.0524, time 331.35ms, mfu 0.05%

接下来运行一下试试:

$ python sample.py --out_dir=out-sanzijing-char --device=cpu
...

宇文周
与高齐
迨至隋
一土宇
不再传
失统绪
唐高祖
...

看出问题了么?尽管模型生成的的确像是三字经,这根本就是原封不动的三字经! 也就是说,模型并没有“生成”我们想要的内容, 更像是在背诵我们训练时的语料。 在机器学习中,这种现象叫做“过拟合(overfitting)”,当模型尝试“记住”训练数据而非从训练数据中学习规律时,就可能发生过拟合。

通常,出现这种问题是由于数据集过小导致的。我们的三字经总共就三百多条数据(机器学习几百条的数据量一般认为是很少,几千到几万可以算中等[3]),由于数据量太少而导致过拟合。 过拟合时,从训练的损失上也可以看出端倪:

# train loss不断收敛,但是跟val loss差别很大
step 2000: train loss 0.0445, val loss 8.9908

这样训练出来的数据,缺乏泛化能力。也就是说, 对于训练中出现的数据能够给出结果, 但是如果是没有出现过的,就不能得到结果了。为了验证这个结果, 我们可以修改sample.py运行时的start参数,该参数用来控制模型根据什么进行续写:

$ python sample.py --out_dir=out-sanzijing-char --device=cpu --start="养不"
...
养不教
父之过
教不严
...
$ python sample.py --out_dir=out-sanzijing-char --device=cpu --start="养要"
...
养要
记其事
五子者

可以看出,如果是出现过的字,模型能够准确“背出”内容;但对于没有见到过开头,就不知道怎么回答了。

不听不听,和尚念经

由于三字经实在是内容太少,通过这几百条数据很难训练成一个会写三字经的模型。 那么,只要提高内容,多给点训练数据,来个几万几十万条,不是就可以了么? 有什么东西可以找到丰富的语料呢?

经过一点调查,笔者发现佛经是个不错的选择。 一方面佛经很啰嗦,动不动就几十卷; 一方面又很晦涩,就是看也很难看懂,可能甚至比看莎士比亚的英文剧本更难理解。

于是,我们拿《大般若波罗蜜多经》这部也许是最长的佛经来训练一下试试。 训练数据共五万多行,合计约五百七十万字。足够大了!

$

水浒传