# GPT 本示例主要通过使用MIGraphX Python API对GPT2模型进行推理,包括预处理、模型推理以及数据后处理。 ## 模型简介 GPT(Generative Pre-trained Transformer)系列模型以不断堆叠的transformer中的decoder模块为特征提取器,提升训练语料的规模和质量、网络的参数量进行迭代更新。GPT-1主要是通过在无标签的数据上学习一个通用的语言模型,再根据特定的任务进行微调处理有监督任务;GPT-2在GPT-1的模型结构上使用更多的模型参数和数据集,训练一个泛化能力更强的词向量模型。GPT-3更是采用海量的模型参数和数据集,训练了一个更加强大的语言模型。 | 模型 | 发布时间 | 参数量 | 预训练数据量 | | ----- | ------------ | -------- | ------------ | | GPT-1 | 2018 年 6 月 | 1.17 亿 | 约 5GB | | GPT-2 | 2019 年 2 月 | 15 亿 | 40GB | | GPT-3 | 2020 年 5 月 | 1,750 亿 | 45TB | 本次采用GPT-2模型进行诗词生成任务,模型文件下载链接:https://pan.baidu.com/s/1KWeoUuakCZ5dualK69qCcw , 提取码:4pmh 。将GPT2_shici.onnx模型文件保存在Resource/Models/NLP/GPT2文件夹下。整体模型结构如下图所示,也可以通过netron工具:https://netron.app/ 查看GPT-2的模型结构。 ## 预处理 在将文本输入到模型之前,需要做如下预处理: 1.加载词汇表,根据提供的路径加载词汇表 2.文本编码,根据词汇表对输入的文本进行编码 在数据预处理的过程中,首先加载词汇表,根据提供的词汇表路径,通过transformers库中的BertTokenizerFast函数实现词汇表的加载。完成词汇表的加载后,就可以正常对输入的文本进行编码处理,从而将文本数据转换为数值型数据。 ```python # 加载词汇表 vocab_file = os.path.join('../../../Resource/Models', 'vocab_shici.txt') tokenizer = BertTokenizerFast(vocab_file, sep_token="[SEP]", pad_token="[PAD]", cls_token="[CLS]") ``` 完成词汇表的加载后,就可以进行文本编码。首先,输入一段文本数据,通过tokenizer.encoder()将文本数据编码为数值型数据。其次,进行数据重构,创建了一个input_ids列表,开头加入[CLS]起始标志符,并将数值型数据拼接到后面。最后,将input_ids列表中的数据都转换为np.int64类型,并将一维数据扩展了二维数据,完成数据的预处理过程。 ```python # 对输入文本进行编码 text = input("user:") text_ids = tokenizer.encode(text, add_special_tokens=False) # 数据重构 input_ids = [tokenizer.cls_token_id] input_ids.extend(text_ids) input_ids = np.array(input_ids, dtype=np.int64) input_ids = np.expand_dims(input_ids, axis=0) ``` ## 推理 完成数据预处理后,就可以执行模型推理。推理过程主要做如下处理: 1.设置最大输入shape 2.循环推理 ```Python # 设置最大输入shape maxInput={"input":[1,1000]} # 模型推理 for _ in range(max_len): # 推理 result = model.run({inputName: input_ids}) logits = [float(x) for x in result[0].tolist()] ... # 当推理得到[SEP]结束标志符,则结束for循环停止生成 if next_token == tokenizer.convert_tokens_to_ids('[SEP]'): break # 将推理结果next_token和input_ids进行拼接 next_token = np.array(next_token, dtype=np.int64) input_ids = np.append(input_ids, next_token) input_ids = np.expand_dims(input_ids, axis=0) ``` 1.设置最大输入shape,因为GPT-2属于生成式模型,输入的shape一直在变化,所以在模型推理前设置一个最大的输入shape,maxInput={"input":[1,1000]},限定模型输入shape的范围。 2.循环推理,GPT-2模型不像其他模型一样只需要执行一次推理,而是需要循环执行多次推理才能完成。首先,模型推理限定在for循环中,将输入数据input_ids,输入到model.run({...})中执行推理,生成一个token的id。其次,将推理结果拼接到输入数据input_ids中,执行下一次循环。最后,当循环结束或者生成的词为[SEP]结束标志符时,完成GPT-2模型的整体推理。如下图所示,为GPT-2模型的一次完整推理过程。 ## 数据后处理 获得模型推理结果后,并不能直接作为答案输出,还需要进行数据后处理操作,才能得到最终的结果。具体主要做如下后处理: 1.排序 2.解码 ```Python for _ in range(max_len): ... # 排序 score = [] for index in range((input_ids.shape[1]-1)*22557, input_ids.shape[1]*22557): score.append(logits[index]) index_and_score = sorted(enumerate(score), key=lambda x: x[1], reverse=True) # 取出概率值最大的作为预测结果 next_token = index_and_score[0][0] ... history.append(next_token) # 解码 text = tokenizer.convert_ids_to_tokens(history) print("chatbot:" + "".join(text)) ``` 1.排序,每次推理都会对每个token生成词汇表中下一个词的概率,例如输入shape为(1,4),词汇表长度为22557,则一共生成4*22557个概率,但是不需要获得全部的概率,只需要得到最后一个token生成的概率。得到相应的概率后,采用sorted()函数进行排序操作,取概率值最大的作为预测结果。 2.解码,因为history列表中存放的推理结果都是对应token的id,还需要进行解码将对应的id转换文本。解码主要采用BertTokenizerFast中的convert_ids_to_tokens()函数将数值型数据转换为相应的文本数据,从而得到对应的答案。