LLM-03 大模型 15分钟 FineTuning 微调 GPT2 模型 finetuning GPT微调实战 仅需6GB显存 单卡微调 数据 10MB数据集微调

简介: LLM-03 大模型 15分钟 FineTuning 微调 GPT2 模型 finetuning GPT微调实战 仅需6GB显存 单卡微调 数据 10MB数据集微调

参考资料

GPT2 FineTuning

OpenAI-GPT2

Kaggle short-jokes 数据集

Why will you need fine-tuning an LLM?

LLMs are generally trained on public data with no specific focus. Fine-tuning is a crucial step that adapts a pre-trained LLM model to a specific task, enhancing the LLM responses significantly. Although text generation is a well-known application of an LLM, the neural network embeddings obtained from the model are equally valuable for various downstream applications.

项目地址

https://huggingface.co/openai-community/gpt2

安装依赖

这边建议独立环境,避免相互影响。可看LLM-01 和 LLM-02 章节中的 Pyenv 的使用

pip install transformers

下载模型

有很多方式下载 HuggingFace的模型:

  • 利用官方提供的 huggingface_hub库 下载
  • 直接下载(比如Git方式)
  • 镜像代理下载(国内,如果没有科学上网的话)
  • 其他···

这里提供一个例子,运行可以自动把模型下载下来

from transformers import AutoTokenizer, AutoModel, AutoModelForCausalLM
  
tokenizer = AutoTokenizer.from_pretrained("openai-community/gpt2")
model = AutoModelForCausalLM.from_pretrained("openai-community/gpt2")

直接运行,可以看到如下的效果:

数据下载

项目地址: 参考的项目

数据来自 Kaggle 一个短笑话合集 10MB(压缩后)

下载链接

编写代码

加载数据

class JokesDataset(Dataset):
    def __init__(self, jokes_dataset_path = './'):
        super().__init__()

        short_jokes_path = os.path.join(jokes_dataset_path, 'shortjokes.csv')

        self.joke_list = []
        self.end_of_te xt_token = "<|endoftext|>"

        with open(short_jokes_path) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')

            x = 0
            for row in csv_reader:
                joke_str = f"JOKE:{row[1]}{self.end_of_text_token}"
                self.joke_list.append(joke_str)

    def __len__(self):
        return len(self.joke_list)

    def __getitem__(self, item):
        return self.joke_list[item]

加载模型

# 如果你是默认的 那应该是:openai-community/gpt2
model_path = "./gpt2"
tokenizer = GPT2Tokenizer.from_pretrained(model_path)
model = GPT2LMHeadModel.from_pretrained(model_path)
model = model.to(device)

训练代码


for epoch in range(EPOCHS):
    
    print(f"EPOCH {epoch} started" + '=' * 30)
    
    for idx,joke in enumerate(joke_loader):
        
        #################### "Fit as many joke sequences into MAX_SEQ_LEN sequence as possible" logic start ####
        joke_tens = torch.tensor(tokenizer.encode(joke[0])).unsqueeze(0).to(device)
        #Skip sample from dataset if it is longer than MAX_SEQ_LEN
        if joke_tens.size()[1] > MAX_SEQ_LEN:
            continue
        
        #The first joke sequence in the sequence
        if not torch.is_tensor(tmp_jokes_tens):
            tmp_jokes_tens = joke_tens
            continue
        else:
            #The next joke does not fit in so we process the sequence and leave the last joke 
            #as the start for next sequence 
            if tmp_jokes_tens.size()[1] + joke_tens.size()[1] > MAX_SEQ_LEN:
                work_jokes_tens = tmp_jokes_tens
                tmp_jokes_tens = joke_tens
            else:
                #Add the joke to sequence, continue and try to add more
                tmp_jokes_tens = torch.cat([tmp_jokes_tens, joke_tens[:,1:]], dim=1)
                continue
        ################## Sequence ready, process it trough the model ##################
            
        outputs = model(work_jokes_tens, labels=work_jokes_tens)
        loss, logits = outputs[:2]                        
        loss.backward()
        sum_loss = sum_loss + loss.detach().data
                       
        proc_seq_count = proc_seq_count + 1
        if proc_seq_count == BATCH_SIZE:
            proc_seq_count = 0    
            batch_count += 1
            optimizer.step()
            scheduler.step() 
            optimizer.zero_grad()
            model.zero_grad()

        if batch_count == 100:
            print(f"sum loss {sum_loss}")
            batch_count = 0
            sum_loss = 0.0
    
    # Store the model after each epoch to compare the performance of them
    torch.save(model.state_dict(), os.path.join(models_folder, f"gpt2_medium_joker_{epoch}.pt"))

保存目录

models_folder = "trained_models"
if not os.path.exists(models_folder):
    os.mkdir(models_folder)

完整代码

完整的代码如下

import torch
from transformers import GPT2Tokenizer, GPT2LMHeadModel
import numpy as np
from torch.utils.data import Dataset, DataLoader
from transformers import AdamW, get_linear_schedule_with_warmup
import os
import json
import csv

import logging
logging.getLogger().setLevel(logging.CRITICAL)

import warnings
warnings.filterwarnings('ignore')


class JokesDataset(Dataset):
    def __init__(self, jokes_dataset_path = './'):
        super().__init__()

        short_jokes_path = os.path.join(jokes_dataset_path, 'shortjokes.csv')

        self.joke_list = []
        self.end_of_te xt_token = "<|endoftext|>"

        with open(short_jokes_path) as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')

            x = 0
            for row in csv_reader:
                joke_str = f"JOKE:{row[1]}{self.end_of_text_token}"
                self.joke_list.append(joke_str)

    def __len__(self):
        return len(self.joke_list)

    def __getitem__(self, item):
        return self.joke_list[item]


device = 'mps'
if torch.cuda.is_available():
    device = 'cuda'

model_path = "./gpt2"
tokenizer = GPT2Tokenizer.from_pretrained(model_path)
model = GPT2LMHeadModel.from_pretrained(model_path)
model = model.to(device)


dataset = JokesDataset()
joke_loader = DataLoader(dataset, batch_size=1, shuffle=True)

BATCH_SIZE = 16
EPOCHS = 2
LEARNING_RATE = 3e-5
WARMUP_STEPS = 5000
MAX_SEQ_LEN = 400

model.train()
optimizer = AdamW(model.parameters(), lr=LEARNING_RATE)
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=WARMUP_STEPS, num_training_steps = -1)
proc_seq_count = 0
sum_loss = 0.0
batch_count = 0

tmp_jokes_tens = None
models_folder = "trained_models"
if not os.path.exists(models_folder):
    os.mkdir(models_folder)

for epoch in range(EPOCHS):
    
    print(f"EPOCH {epoch} started" + '=' * 30)
    
    for idx,joke in enumerate(joke_loader):
        
        #################### "Fit as many joke sequences into MAX_SEQ_LEN sequence as possible" logic start ####
        joke_tens = torch.tensor(tokenizer.encode(joke[0])).unsqueeze(0).to(device)
        #Skip sample from dataset if it is longer than MAX_SEQ_LEN
        if joke_tens.size()[1] > MAX_SEQ_LEN:
            continue
        
        #The first joke sequence in the sequence
        if not torch.is_tensor(tmp_jokes_tens):
            tmp_jokes_tens = joke_tens
            continue
        else:
            #The next joke does not fit in so we process the sequence and leave the last joke 
            #as the start for next sequence 
            if tmp_jokes_tens.size()[1] + joke_tens.size()[1] > MAX_SEQ_LEN:
                work_jokes_tens = tmp_jokes_tens
                tmp_jokes_tens = joke_tens
            else:
                #Add the joke to sequence, continue and try to add more
                tmp_jokes_tens = torch.cat([tmp_jokes_tens, joke_tens[:,1:]], dim=1)
                continue
        ################## Sequence ready, process it trough the model ##################
            
        outputs = model(work_jokes_tens, labels=work_jokes_tens)
        loss, logits = outputs[:2]                        
        loss.backward()
        sum_loss = sum_loss + loss.detach().data
                       
        proc_seq_count = proc_seq_count + 1
        if proc_seq_count == BATCH_SIZE:
            proc_seq_count = 0    
            batch_count += 1
            optimizer.step()
            scheduler.step() 
            optimizer.zero_grad()
            model.zero_grad()

        if batch_count == 100:
            print(f"sum loss {sum_loss}")
            batch_count = 0
            sum_loss = 0.0
    
    # Store the model after each epoch to compare the performance of them
    torch.save(model.state_dict(), os.path.join(models_folder, f"gpt2_medium_joker_{epoch}.pt"))

运行代码

python fine.py
• 1

执行之后,观察显卡的情况,大致占用4.6GB的显存(虽然我这里是3090 24GB的显卡,小显卡也可以正常运行)

训练过程会打印 LOSS

训练结束

经过漫长等待···

测试结果

原始模型

编写几行代码,简单测试一下:

from transformers import pipeline,GPT2LMHeadModel, GPT2Tokenizer
  
model_path = "openai-community/gpt2"
model = GPT2LMHeadModel.from_pretrained(model_path)
tokenizer = GPT2Tokenizer.from_pretrained(model_path)

text_generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

texts = text_generator("Once upon a time ", max_length=50, num_return_sequences=1)
for text in texts:
    print("========================")
    print(text["generated_text"])

运行输出:

Once upon a time 『My Name is』 I'll call myself a boy. I won't reveal my true name.

微调模型

微调之后,效果就变了:

import torch
from transformers import pipeline, GPT2LMHeadModel, GPT2Tokenizer

model_path = "openai-community/gpt2"
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

model = GPT2LMHeadModel.from_pretrained(model_path).to(device)
tokenizer = GPT2Tokenizer.from_pretrained(model_path)

model.load_state_dict(torch.load('./trained_models/gpt2_medium_joker_9.pt', map_location='cuda:0'))
model.eval()

text_generator = pipeline("text-generation", model=model, tokenizer=tokenizer)
texts = text_generator("Once upon a time ", max_length=50, num_return_sequences=1)
print("===================")
for text in texts:
    print(text["generated_text"])

测试第一次:

Once upon a time  someone in England tried to insult me by saying, "I'm Scottish."

附带翻译:

从前,有人在英格兰试图侮辱我,说:“我是苏格兰人。”笑点在于暗示苏格兰人与英格兰人有种族或地域上的不同,但实际上这种“侮辱”反而使苏格兰人自豪。

测试第二次:

Once upon a time  someone like that was  doing something wrong.  When I went to dinner


附带翻译:

从前,有个像那个人那样做错事的人。当我和他们一起吃饭时,我第一件事就是把食物掉在地上,然后引起了一场火灾。笑点在于出乎意料的行为,以及食物掉在地上导致火灾这种离奇的情节,使整个场景变得荒诞有趣。

目录
相关文章
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
当语言遇见智慧火花:GPT家族历代模型大起底,带你见证从平凡到卓越的AI进化奇迹!
【10月更文挑战第6天】随着自然语言处理技术的进步,GPT系列模型(Generative Pre-trained Transformers)成为该领域的明星。从GPT-1的开创性工作,到GPT-2在规模与性能上的突破,再到拥有1750亿参数的GPT-3及其无需微调即可执行多种NLP任务的能力,以及社区驱动的GPT-NeoX,这些模型不断进化。虽然它们展现出强大的语言理解和生成能力,但也存在如生成错误信息或偏见等问题。本文将对比分析各代GPT模型的特点,并通过示例代码展示其部分功能。
105 2
|
1月前
|
数据采集 API 决策智能
华为诺亚联合中科大发布工具调用模型ToolACE,效果持平GPT-4获开源第一
 【10月更文挑战第10天】华为诺亚方舟实验室与中国科学技术大学合作推出ToolACE,一种自进化合成过程的工具调用模型。ToolACE通过多智能体交互和双重验证系统生成准确、复杂、多样化的工具学习数据,显著提升大型语言模型(LLM)的功能调用能力。实验结果显示,使用ToolACE数据训练的80亿参数模型性能媲美GPT-4,在伯克利功能调用排行榜上获得开源第一。
59 4
|
22天前
|
机器学习/深度学习 弹性计算 人工智能
大模型进阶微调篇(三):微调GPT2大模型实战
本文详细介绍了如何在普通个人电脑上微调GPT2大模型,包括环境配置、代码实现和技术要点。通过合理设置训练参数和优化代码,即使在无独显的设备上也能完成微调,耗时约14小时。文章还涵盖了GPT-2的简介、数据集处理、自定义进度条回调等内容,适合初学者参考。
147 6
|
1月前
|
API
2024-05-14 最新!OpenAI 新模型 GPT-4 omni 简单测试,4o速度确实非常快!而且很便宜!
2024-05-14 最新!OpenAI 新模型 GPT-4 omni 简单测试,4o速度确实非常快!而且很便宜!
40 0
|
1月前
|
物联网 数据处理
LLM-05 大模型 15分钟 FineTuning 微调 ChatGLM3-6B(微调实战1) 官方案例 3090 24GB实战 需22GB显存 LoRA微调 P-TuningV2微调
LLM-05 大模型 15分钟 FineTuning 微调 ChatGLM3-6B(微调实战1) 官方案例 3090 24GB实战 需22GB显存 LoRA微调 P-TuningV2微调
64 0
|
30天前
|
前端开发 机器人 API
前端大模型入门(一):用 js+langchain 构建基于 LLM 的应用
本文介绍了大语言模型(LLM)的HTTP API流式调用机制及其在前端的实现方法。通过流式调用,服务器可以逐步发送生成的文本内容,前端则实时处理并展示这些数据块,从而提升用户体验和实时性。文章详细讲解了如何使用`fetch`发起流式请求、处理响应流数据、逐步更新界面、处理中断和错误,以及优化用户交互。流式调用特别适用于聊天机器人、搜索建议等应用场景,能够显著减少用户的等待时间,增强交互性。
229 2
|
24天前
|
机器学习/深度学习 人工智能 运维
企业内训|LLM大模型在服务器和IT网络运维中的应用-某日企IT运维部门
本课程是为某在华日资企业集团的IT运维部门专门定制开发的企业培训课程,本课程旨在深入探讨大型语言模型(LLM)在服务器及IT网络运维中的应用,结合当前技术趋势与行业需求,帮助学员掌握LLM如何为运维工作赋能。通过系统的理论讲解与实践操作,学员将了解LLM的基本知识、模型架构及其在实际运维场景中的应用,如日志分析、故障诊断、网络安全与性能优化等。
54 2
|
28天前
|
机器学习/深度学习 数据采集 人工智能
文档智能 & RAG 让AI大模型更懂业务 —— 阿里云LLM知识库解决方案评测
随着数字化转型的深入,企业对文档管理和知识提取的需求日益增长。阿里云推出的文档智能 & RAG(Retrieval-Augmented Generation)解决方案,通过高效的内容清洗、向量化处理、精准的问答召回和灵活的Prompt设计,帮助企业构建强大的LLM知识库,显著提升企业级文档管理的效率和准确性。
|
7天前
|
自然语言处理 开发者
多模态大模型LLM、MLLM性能评估方法
针对多模态大模型(LLM)和多语言大模型(MLLM)的性能评估,本文介绍了多种关键方法和标准,包括模态融合率(MIR)、多模态大语言模型综合评估基准(MME)、CheckList评估方法、多模态增益(MG)和多模态泄露(ML),以及LLaVA Bench。这些方法为评估模型的多模态和多语言能力提供了全面的框架,有助于研究者和开发者优化和改进模型。
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
大模型强崩溃!Meta新作:合成数据有剧毒,1%即成LLM杀手
在人工智能领域,大型语言模型(LLMs)的快速发展令人瞩目,但递归生成数据可能导致“模型崩溃”。Meta的研究揭示,模型在训练过程中会逐渐遗忘低概率事件,导致数据分布偏差。即使少量合成数据(如1%)也会显著影响模型性能,最终导致崩溃。研究强调保留原始数据的重要性,并提出社区合作和技术手段来区分合成数据和真实数据。论文地址:https://www.nature.com/articles/s41586-024-07566-y
23 2

热门文章

最新文章