一 起因
其实现在大语言模型能够发展起来的起因就是现如今有了许多高质量料库,可以对模型进行训练。然而,这些语料库都是开源,因而对应模型训练的结果也都是一样的。那么,我们该如何从头训练或者微调自己的模型,生成极具个性化的AI助手或者写作助手呢?
二正文
当然,本文其实无法做那么深入的从头训练和微调模型的解析。一方面,这种微调和训练需要非常强的专业知识和高端的硬件支持。笔者作为一个生信分析方面的研究人员,两者都没有,硬件的话目前也就是有一个3060Ti而已,所以只能做到初级的微调。不过,随着开源社区的进一步发展,相信未来的微调或训练都将不停留在专家级,而是像现在的许多深度学习算法一样,可以自动微调。
下面将从语料库,模型,训练代码和输出结果四个环节依次介绍。
2.1 语料库下载
既然我们要从头训练或者微调模型,那么我们需要先实现准备好语料库,并进行处理。这里我们使用的语料库是来自PMC
具体介绍为:欧洲PMC作者手稿合集由作者手稿形式的文章组成,这些文章已按照欧洲PMC资助者政策以及美国国立卫生研究院(NIH)和其他参与PMC的资助者的公共访问政策,在欧洲PMC和PubMed Central (PMC)上提供。文集中的手稿文本可以 XML 和纯文本格式下载。
https://europepmc.org/downloads/manuscripts
2.2 语料库处理
实际上,语料库才是GPT训练的核心,如何获得一个足够高质量的语料库是许多大模型所面临的一个最重要的考验。因为无论是互联网上的,还是各类文本资料,实际上都需要对语料库进行清洗、过滤,筛选等步骤。目前来看,这应该还是属于专家级别的操作,本文的就是通过一个定义函数简单的对数据进行一些处理
def clean_text(text):
# remove extraneous text and special characters
# For example, remove advertisements, copyright information, and some non-English characters
cleaned_text = re.sub(r'[.*?]', '', text) # Remove square brackets and their content
cleaned_text = re.sub(r'n', ' ', cleaned_text) # Replace newlines with spaces
cleaned_text = re.sub(r'[^x00-x7F]+', '', cleaned_text) # remove non-ASCII characters
return cleaned_text
本推文就是通过这个正则表则式对模型一些特殊符号、换行符和非ASCII符号进行出去。读者切不可认为清晰数据这么简单,实际上还是有许多技巧在如何清晰数据这一块的。
2.3 模型文件下载
如果要对模型进行微调,那么我们需要首先获取对应的模型文件,如ChatGPT2的模型文件。如果我们需要多模型进行从头训练,我们需要的数据则是模型的配置文件和模型的tokenizer文件
这里简单介绍一下tokenizer文件,这个文件是一个用于将原始文本转换为模型可以理解的数字序列的组件。这个文件通常是一个预先训练好的tokenizer对象,可以使用Hugging Face的transformers库来加载。
tokenizer文件由两部分组成:
词汇表(Vocabulary):tokenizer使用词汇表来将单词、标点符号等符号映射到唯一的标识符(通常是整数)。词汇表中包含了模型已经学习到的单词和子词(subwords),以及特殊标记(如开始标记、结束标记等)。这些标识符将在模型的输入和输出中使用。
编码和解码方法:tokenizer提供了方法,用于将文本转换为模型所需的编码形式(例如,将单词转换为整数序列),以及将模型生成的编码序列解码为可读的文本。这些方法包括encode(编码)和decode(解码)等。
tokenizer的作用是使原始文本数据与模型的输入输出之间建立有效的桥梁。它负责将文本分割成合适的单元,并将这些单元转换为模型可以处理的数字序列。这对于各种自然语言处理任务,包括文本分类、生成、翻译等,都是非常关键的。tokenizer还可以处理特殊字符、截断、填充等操作,以确保输入数据符合模型的要求。
对于ChatGPT,tokenizer文件在与模型交互、生成对话时非常重要,它将输入的对话文本转换为适合模型输入的格式,同时将模型生成的响应转换回可读的文本形式。
至于ChatGPT2大模型的下载,可以从以下仓库获得
https://huggingface.co/gpt2
https://huggingface.co/gpt2-large
2.4 模型从头训练或者微调
首先,废话不多数,直接贴出代码,并讲解如何对模型进行微调和训练。
num_gpus = torch.cuda.device_count()
num_gpus - 1)
# Download and load NLTK data
# nltk.download('punkt')
with open(input_file, 'r', encoding='utf-8') as f:
raw_text = f.read()
cleaned_text = clean_text(raw_text)
# 自定义模型配置
model_config = GPT2Config(
vocab_size=50257,
n_positions=1024,
n_ctx=1024,
n_embd=768,
n_layer=12,
n_head=12,
n_inner=3072,
activation_function="gelu",
resid_pdrop=0.1,
embd_pdrop=0.1,
attn_pdrop=0.1,
layer_norm_epsilon=1e-5,
initializer_range=0.02,
# 更多的配置参数...
)
# Collect and preprocess text data
corpus = cleaned_text
if trian_mode == 'finetuning' : model = GPT2LMHeadModel.from_pretrained(gpt_model_catalog)
if trian_mode == 'scratch' : model = GPT2LMHeadModel(config=model_config)
tokenizer = GPT2Tokenizer.from_pretrained(gpt_model_catalog)
# Move model to GPU
model.to('cuda')
# Encode the text into a format the model understands
tokenizer.eos_token =
encoded_corpus = tokenizer(corpus, padding=True, max_length=512, truncation=True, return_tensors='pt')
# prepare training data
input_ids = encoded_corpus['input_ids']
attention_mask = encoded_corpus['attention_mask']
input_ids = input_ids.to('cuda')
attention_mask = attention_mask.to('cuda')
labels = input_ids.clone()
# Initialize optimizer and learning rate scheduler
optimizer = AdamW(model.parameters(), lr=1e-4)
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=0, num_training_steps=len(input_ids))
# train the model
num_epochs = 5
batch_size = 4
accumulation_steps = 4
for epoch in range(num_epochs):
model.train()
for batch in range(0, len(input_ids), batch_size):
batch_input_ids = input_ids[batch:batch+batch_size]
batch_attention_mask = attention_mask[batch:batch+batch_size]
batch_labels = labels[batch:batch+batch_size]
optimizer.zero_grad()
outputs = model(input_ids=batch_input_ids, attention_mask=batch_attention_mask, labels=batch_labels)
loss = outputs.loss
loss.backward()
if (batch + 1) % accumulation_steps == 0:
optimizer.step()
scheduler.step()
running_loss += loss.item() * accumulation_steps
optimizer.zero_grad()
loss = loss.to('cpu')
{epoch+1}/{num_epochs}, Loss: {loss.item()}")
torch.cuda.empty_cache()
# save the trained model
model.cpu()
model.save_pretrained(output_file)
tokenizer.save_pretrained(output_file)
首先,通过对语料库进行清晰获得clean_text
随后,代码的第14行通过model_config变量定义从头训练的GPT2参数
第三,最关键的一环,如果我们希望微调chatGPT2模型,我们需要将trian_mode变量设置为 finetuning模式。则代码中的第34行将加载已经训练好的GPT模型。如果我们希望从头训练,则将tran_mode变量scratch模式,则代码的35行会选择加载GPT模型的参数并进行初始化。
之后,从代码的37行开始,脚本就会按照我们的模型对模型进行训练,并保存模型。这里需要注意的是无论是什么类似的训练,我尽量在GPU上进行训练,并且下面的代码中,max_length需要设置。本文设置的最大长度是512,显存使用量是7G作用。不然,算法就将这个文本拿到模型进行训练,这将大大占用内存(预计需要使用124G左右的显存,可不是一张显卡能够实现)
encoded_corpus = tokenizer(corpus, padding=True, max_length=512, truncation=True, return_tensors='pt')
代码的第82行和83行将显示我们的训练过程中损失函数。微调的话应该是80分左右。所以,如果读者想要从头训练,建议损失值不低于这个数值。
2.5 调用大语言模型
这里gpt_modle_catalog变量即可以是被人训练好的大模型,也可以是我们自身微调或者从头训练的大语言模型。我们通过input_file这个变量指定我们的输入,既可以在最后输出模型的预测结果
tokenizer = GPT2Tokenizer.from_pretrained(gpt_model_catalog)
if tokenizer.pad_token is None:
tokenizer.eos_token =
model = GPT2LMHeadModel.from_pretrained(gpt_model_catalog)
text = input_file
input_ids = tokenizer.encode(text, return_tensors='pt')
outputs = model.generate(input_ids,
do_sample=True,
min_length=20,
max_length=100,
top_k=50,
top_p=0.95,
num_return_sequences=1,
pad_token_id=tokenizer.pad_token_id
)
# 解码输出并打印
for i, output in enumerate(outputs):
{}".format(i, tokenizer.decode(output, skip_special_tokens=True))) :
比如,通过ChatGPT-larger这个模型,我们输入once upon a time there was a temple这句话,则会输出
once upon a time there was a temple on the site of which the name I used has the honorable title of Tawas, or "House of the Three." But that is not the place where I dwell anymore. It is a place where I think that I may live for ever and ever.
What kind of a life do you lead and what kind of an attitude do you have toward your fellow men?
I have not long been able to be free, free from fear
如果是我们自己的从头训练大模型(那PMC中的几篇文献训练),则会输出
once upon a time there was a temple pipelines assorted mergeriker habits earthly heatingbiology Bathserver thereafteredes cornersaj condemnedomers spree electromagneticeed descriptiveuctivestrate pans experimentation uninsured launchesJuly Sap Bicyclekees stewards brigadeImprove Hogwarts condemned Jesuit selling Correction Been fretelesseedUniversal Heartro Carter bunkposure PabloOther Harbaugh0000 torrent ascert encode microbial kids Juanesiesi Saputersaj Razfloat insurrection disparズ sluggish sluggishfoundation Sweden internet Genie structure strokes strokes vot Pablo reveal reveal Metropolitan inevitably Rept neighbor Mutant launchesuser sleptDiscussion scrambled Swim
哈哈,是不是输出结果立马就高下立见了。不过这里的结果不是想说微调或者从头训练不可以,只是说这种训练可能需要消耗大量的时间和精力才能获得自己想要的结果。不然,还要相关的科研人员干嘛呢,你说是不是。
三惯例小结
大语言模型部分介绍到这里就告一段落了。虽然因为能力和精力有限,没有完全深入的探讨,但是还是希望能够给读者一个大语言模型的初步概览,能够做到心中有底,不至于被网上的许多人忽悠。
从许多角度来看,从头训练一个大语言模型是不划算的。毕竟,对于程序员而言,重复造轮子是一个大忌。但是,虽然我们不需要造轮子,但是;了解整个大语言模型的运行过程,无疑是我们应用这个模型的基础。因此,本推文虽然显得枯燥乏味,且过时,毕竟ChatGPT5都快上市了,但是仍希望从最基础的角度给大家科普这个现阶段,最流行的大模型。至于有没有用,那就见仁见智了。
四 公众号其他资源(方便读者使用)
本公众号开发的相关软件,软件和软件欢迎大家使用。文末是本公众号在其他平台的账户,也欢迎大家关注并多提意见。
简书:WJ的生信小院
博客园:生信小院
最后,也欢迎各位大佬能够在本平台上:1:传播和讲解自己发表的论文;2:发表对某一科研领域的看法;3:想要达成的合作或者相应的招聘信息;4:展示自己以寻找博后工作或者博士就读的机会;5:博导提供博后工作或者博士攻读机会,都可以后台给笔者留言。希望本平台在进行生信知识分享的同时,能够成为生信分析者的交流平台,能够实现相应的利益互补和双赢(不一定能实现,但是梦想总得是有的吧)。
五 封面图