本地连接云服务器,在云服务器上训练模型,通过后处理控制模型的输出。
一. 购买并远程连接云服务器
1.1 租服务器
选在AutoDL平台, 注册后点击算力平台,租对应的服务器,这里我选择按时付费,RTX4090
也可以选择其他平台的服务器。

1.2 远程连接
使用vscode,下载插件Remote-SSH, 复制上面的ssh命令,远程连接服务器。
1.3 训练GPT2
将上一节的代码和模型复制到服务器,模型可以从hf-mirror重新下载,复制时直接复制到vscode的目录下就行(先在vscode打开与远程文件夹),注意修改模型路径。以及根据显存情况修改batchsize, 最好修改到占用90%显存。查看显存可以使用nvitop
使用以下命令下载
使用以下命令查看显存占用情况
直接运行train.py文件即可,但这样如果ssh连接中断,训练也会停止,用以下命令可以保证ssh连接中断训练也不停止
会在目录下生成nohup.out文件用于保存终端输出。

二. 测试训练好的GPT2并进行后处理
本次训练目标是训练一个能生成古诗分格的GPT2。我在服务器上训练了14个epoch,测试部分可以在本地进行,将训练好的参数下载到本地,通过以下代码测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import torch from transformers import GPT2LMHeadModel, BertTokenizer, TextGenerationPipeline
model = GPT2LMHeadModel.from_pretrained('gpt2-chinese-cluecorpussmall') tokenizer = BertTokenizer.from_pretrained('gpt2-chinese-cluecorpussmall')
model.load_state_dict(torch.load('./params/epoch-14', map_location='cpu'))
text_generator = TextGenerationPipeline(model, tokenizer, device='cpu')
text = text_generator("天高", max_length=100, do_sample=True, truncation=True) print(text[0]['generated_text'])
|
输出结果如下,比如我们想输出四句诗,每个句子五个字,共以天高开头个字,(加上标点):

可以看出,和不训练相比(不加载模型参数,输出如下),训练后的模型更接近诗词的形式。

但仍存在一些不足,比如我们原意是想输出每句诗五个字,共四句诗,但输出中有一些句子不是五个字,有一些特殊字符。
我们可以通过后处理使输出更符合我们要的形式。
三. 后处理
大模型只能按照上文推测下一个字符,但它不能严格控制输出格式,所以涉及格式时,我们需要通过后处理进行严格控制。我们需要重新定义生成函数,比如要生成五言绝句,则定义如下函数:
1
| def generate(text, row, col):
|
其中text是提示词, row是生成文本的行数, col是每行的字数,首先这个函数要定义为递归函数,因为我们不知道循环的次数,模型在生成过程中会生成一些不合格的输入,我们会抛弃,所以模型具体生成次数我们不知道。
具体逻辑是在生成过程中获取模型下一个输入的logit,然后根据格式将对应不合法的字符的概率设置为零。从而控制模型的输出。
具体代码如下:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| import torch from transformers import GPT2LMHeadModel, BertTokenizer, TextGenerationPipeline
model = GPT2LMHeadModel.from_pretrained('gpt2-chinese-cluecorpussmall') tokenizer = BertTokenizer.from_pretrained('gpt2-chinese-cluecorpussmall')
model.load_state_dict(torch.load('./params/epoch-14', map_location='cpu'))
def generate(text, row, col):
def generate_loop(data): with torch.no_grad(): out = model(**data) out = out['logits'] out = out[:, -1] topk_value = torch.topk(out, 50).values topk_value = topk_value[:, -1].unsqueeze(dim=1) out = out.masked_fill(out < topk_value, -float("inf"))
for i in ",.()《》【】{}": out[:, tokenizer.get_vocab()[i]] = -float('inf')
out[:, tokenizer.get_vocab()["[SEP]"]] = -float('inf') out[:, tokenizer.get_vocab()["[UNK]"]] = -float('inf') out[:, tokenizer.get_vocab()["[CLS]"]] = -float('inf') out = out.softmax(dim=1)
out = out.multinomial(num_samples=1)
c = data['input_ids'].shape[1] / (col + 1)
if c % 1 == 0: if c % 2 == 0: out[:, 0] = tokenizer.get_vocab()["."] else: out[:, 0] = tokenizer.get_vocab()[',']
data['input_ids'] = torch.cat([data['input_ids'], out], dim=1)
data['attention_mask'] = torch.ones_like(data['input_ids']) data['token_type_ids'] = torch.ones_like(data['input_ids'])
data['labels'] = data['input_ids'].clone()
if data['input_ids'].shape[1] >= row * col + row + 1: return data
return generate_loop(data)
data = tokenizer.batch_encode_plus([text] * 3,return_tensors="pt") data["input_ids"] = data["input_ids"][:,:-1] data["attention_mask"] = torch.ones_like(data["input_ids"]) data["token_type_ids"] = torch.zeros_like(data["input_ids"]) data['labels'] = data["input_ids"].clone()
data = generate_loop(data)
for i in range(3): print(i,tokenizer.decode(data["input_ids"][i]))
if __name__ == '__main__': generate("白", row=4, col=5)
|
运行结果如下:

可以看到, 通过后处理, 三首古诗的格式都复合物五言绝句。