预训练模型与微调

2023-11-20

前言:

预训练模型已经火了这么久了,但作为菜本菜的我却还在用lstm。在生成任务上与同门学长用的预训练模型相比,效果差的比较明显。所以,我决定走上预训练的不归路。以下分享我的学习过程:

了解模型:

小说故事生成模型

万事开头难,上视频:视频我喜欢看简短的

从零实现GPT-2,瞎写笑傲江湖外传,金庸直呼内行_哔哩哔哩_bilibili

这是一个非常简单的模型。他没有用huggingface的模型库,而是用pytorch自己搭建了一个模型。也没有用预训练的参数,而是从头开始训练。

过程:

1. 数据处理:数据用的是金庸的小说笑傲江湖。直接就是每次去128个字作为一个预料。输入x和输出y唯一的区别就在于移了一位。(根据transformer生成任务的特点)

2. tokenizer:对小说手动构建字典。利用set()函数获取整个小说的无序不重复的字,然后构建字典

        chars = sorted(list(set(data)))
        self.stoi = { ch:i for i,ch in enumerate(chars) }
        self.itos = { i:ch for i,ch in enumerate(chars) }

3. 加载数据:

        继承Dataset并修改__init__()、__len__()  和 __getitem__()

4. 模型:也是自己写的,结构包括词向量编码、位置编码、主体、输出层

class GPT(nn.Module):
    """  the full GPT language model, with a context size of block_size """

    def __init__(self, config):
        super().__init__()

        # input embedding stem
        self.tok_emb = nn.Embedding(config.vocab_size, config.n_embd)
        self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd))
        self.drop = nn.Dropout(config.embd_pdrop)
        # transformer 主体
        self.blocks = nn.Sequential(*[Block(config) for _ in range(config.n_layer)])
        # decoder head  输出线形层
        self.ln_f = nn.LayerNorm(config.n_embd)
        self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False)

主题结构呢,就是transformer的decoder部分,删掉中间的encoder-decoder注意力一块。那无非就是mask自注意力,和前馈神经网络两块。每块都包含曾归一化和残差链接两部分。

class Block(nn.Module):
    """ an unassuming Transformer block """

    def __init__(self, config):
        super().__init__()
        self.ln1 = nn.LayerNorm(config.n_embd)
        self.ln2 = nn.LayerNorm(config.n_embd)
        self.attn = CausalSelfAttention(config)
        self.mlp = nn.Sequential(                        #前馈神经网络就是两个先行曾中间夹一个激活函数
            nn.Linear(config.n_embd, 4 * config.n_embd),
            nn.GELU(),
            nn.Linear(4 * config.n_embd, config.n_embd),
            nn.Dropout(config.resid_pdrop),
        )

    def forward(self, x):
        x = x + self.attn(self.ln1(x))   #将词向量+位置向量输入,经过曾归一化进入mask注意力(self.attn)并做残差链接
        x = x + self.mlp(self.ln2(x))     #将mask注意力输出结果层归一化,进入前馈神经网络,并残差链接
        return x

以上是这个模型的主要内容了。从这个模型清晰的看到GPT2模型的结果,我也明白了重要的一点:预训练模型的大预料库基本是没有具体任务的标签的,那他是怎么训练的呢?就是通过移位操作,输入和输出之间搓开一位。训练的是模型对于语言整体的感知能力。也就是让你不断地看书,学习,掌握知识。对于具体的下游任务,再去fine-turning。

那么问题来了,我还是不会使用预训练模型

中文闲聊型对话

话不多说,上视频

GPT2中文闲聊对话系统代码讲解_哔哩哔哩_bilibili

1.数据处理,这里项目作者已经准备了大量的对话预料可供下载

2.tokenizer,作者有提供他们做好的中文字典

from transformers import BertTokenizerFast
tokenizer = BertTokenizerFast(vocab_file=args.vocab_path, sep_token="[SEP]", pad_token="[PAD]", cls_token="[CLS]")

input_ids = tokenizer.encode(utterance, add_special_tokens=False)    #处理预料。

3. 加载数据,在这里通过dataloader的collect_fn对预料做了移位操作。dataset依然继承并修改三个函数即可。

4. 模型:这里模型通过transformers库的GPT2LMheadMoodel(config=model_fonfig)创建,因此不方便查看模型具体结构。

from transformers import GPT2LMHeadModel, GPT2Config

if args.pretrained_model:  # 加载预训练模型
        model = GPT2LMHeadModel.from_pretrained(args.pretrained_model)
    else:  # 初始化模型
        model_config = GPT2Config.from_json_file(args.model_config)
        model = GPT2LMHeadModel(config=model_config)

但是在debug过程中,进入模型可以看到。这里将在下一篇分享中介绍

class GPT2LMHeadModel(GPT2PreTrainedModel):
    
    def __init__(self, config):
        super().__init__(config)
            self.transformer =GPT2Model(config)
            self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)

其中GPT2Model的模型结构和上个项目的GPT模型结构是一毛一样的。

在这个项目中因为模型太大,没有看出什么,但可以为后面做一个铺垫。

以上是第二个项目的主要内容,和第一个没啥区别,只是训练出来一个会骂人的人工智障。

那我还是不会用预训练与微调啊。

预训练与微调:

经过前两个项目的铺垫,针对预训练与微调我想要明白以下几个问题:

Q1.字典要自己构建吗?如何处理数据啊?

Q2.如何加载预训练模型啊?网上的项目五花八门,有的下载了什么权重,有的说什么117M模型,345M模型参数已公开,有的说OpenAI,但他是tensorflow框架我不会,有的说huggingface,他太大了我没看懂。。。。

Q3.怎么将预训练模型用到下游任务啊,怎么在预训练模型上修改适用于我的任务啊?

Q4.微调怎么微调啊,什么是微调啊?怎么冻结原来模型参数啊

下面一一分析回答。

废话不多说,上视频HuggingFace简明教程,BERT中文模型实战示例.NLP预训练模型,Transformers类库,datasets类库快速入门._哔哩哔哩_bilibili

这个视频是bert预训练模型,和我要用的GPT2不是同一个。但是道理方法都是一样的。

简单看下huggingface吧:Hugging Face – The AI community building the future.

主要有几个部分:

models:各种写好的预训练模型,每个模型包含一个model card和一个Files and versions。

datasets:常见的数据集

docs:相应模型的说明文档

之前也不知道模型卡是什么幺蛾子,现在明白,他相当于一个小tips,提示你这个模型怎么用

首先,还是来一遍项目顺序吧:

1.数据处理:咱还用闲聊对话项目里面的数据

2.tokenizer:这就是我们的Q1问题,要自己构建词典吗?

回答:可以自己构建,比如上个闲聊对话,就是自己构建字典,构建tokenizer,自己准备数据,自己从头训练。但是如果我们要用huggingface里面的预训练模型与参数的话,就不用自己构建了

比如我们想要用gpt2--chinese模型,找到该模型的模型卡,他告诉我们

from transformers import (
  BertTokenizerFast,
  AutoModel,
)

tokenizer = BertTokenizerFast.from_pretrained('bert-base-chinese')
model = AutoModel.from_pretrained('ckiplab/gpt2-base-chinese')

这就明白了,如果我们想要用预训练好的模型,那就直接加载他的tokenizer即可。

而处理数据也只需要如下,即可将文本转换成对应的id

data_ids = tokenizer.encode(data, add_speical_tokens=False)

3. 加载数据,在dataset的__getitem__() 和 dataloader的collect_fn()中修改数据处理成想要的样子即可。

4.加载模型,这就遇到了我们的Q2问题,想要加载预训练模型,直接从huggingface对应模型的model card里面复制即可

from transformers import AutoModel
model = AutoModel.from_pretrained('ckiplab/gpt2-base-chinese')

也就是说,不用下载什么权重文件(当然也可以),直接调用模型接口加载即可

5. 定义下游任务模型:这就是我们的Q3问题,如何将预训练模型用到下游任务,这对我来说是重点。但是明白之后原来也不难。上代码

class Model(torch.nn.Module):
		def __init__(self):
				super().__init__()
                self.predmodel = AutoModel.from_pretrained('ckiplab/gpt2-base-chinese')  #加载原始预训练模型
				self.fc = torch.nn.Linear(768,vocab_size)      # 为模型添加新的结构 输出层

		def forward(self, input_ids, labels):
				with torch.no_grad():                        # 原来的与训练模型不用更新参数
					out = self.predmodel(ipythonput_ids)      # 原始预训练模型输出结果
				out = self.fc(out.last_hidden_state[:,0])    # 放入下游具体任务的输出层中
				out = out.softmax(dim = 1)                   # 并进行softmax操作
				reture out                                   # 才是最终的输出结果

model = Model()    # 实例化

这里我们定义具体模型,就是先加载一个想要的预训练模型,这个模型实际上和最上面第一个项目 小说生成都是一样的。

它就是从输入向量到模型主体,这一大块,没有输出层。我们将预训练模型迁移到我们具体任务就是加上输出层也就是self.fc 。

这里如果是二分类任务,就加一个 self.fc = torch.nn.Linear(768,2)

如果是生成任务,就加一个 self.fc = torch.nn.Linear(768, tokenizer.vocab_size)

到此模型部分已经准备好了,就剩训练了‘

6.模型训练:优化器+损失函数

optimizer = Adamw(model.parameters,lr = 0.0001)
criterion = torch.nn.CrossEntropyLoss()

model.train()          # 设置模型为训练模式
for i, data in enumerate(loader):
		out = model(input_ids,..)
		loss = criterion(out, labels)
		loss.backward()
		optimizer.step()
		optimizer.zero_grad()

对于Q4,微调:微调,就是在自己的数据上将模型再训练一遍。那么如何冻结原模型参数,只训练新加模块呢?

两个方法:

a. 如步骤5中,在模型的forward部分,对于预训练模块,用 with torch.no_grad()不更新梯度。

def forward(self, input_ids, labels):
	with torch.no_grad():                        # 原来的与训练模型不用更新参数
		out = self.predmodel(ipythonput_ids)      # 原始预训练模型输出结果
	    out = self.fc(out.last_hidden_state[:,0])    # 放入下游具体任务的输出层中
    

b. 对预训练模型参数用param.requires_grad_(False)

def forward(self, input_ids, labels):

    for param in self.predmodel.parameters():
	    param.requires_grad_(False)               # 原来的与训练模型不用更新参数
	out = self.predmodel(ipythonput_ids)      # 原始预训练模型输出结果
	out = self.fc(out.last_hidden_state[:,0])    # 放入下游具体任务的输出层中

两个方法的区别,什么时候该用with torch.no_grad()?什么时候该用.requires_grad ==False?_Y. F. Zhang的博客-CSDN博客

但是我没看懂。。

到此,简单的使用huggingface调用预训练模型并在下游任务上微调的基本过程就有了。至于写项目的话,还是得找大佬的项目,然后进行修改。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

预训练模型与微调 的相关文章

随机推荐

  • 编写Shell脚本(批处理,一次执行多条命令)

    Bash终端的优势 1 上下键重复执行命令 2 tab键自动补齐 3 提供有用的环境变量 4 批处理 shell脚本文件建议以 sh为后缀 其实vim创建文本文件时 对名字无要求 但最好规定格式 echo SHELL 输出为 bin bas
  • grep的用法

    命令介绍 Linux系统中grep命令是一种强大的文本搜索工具 它能使用正则表达式搜索文本 并把匹配的行打印出来 匹配到的标红grep全称是Global Regular Expression Print 表示全局正则表达式版本 它的使用权限
  • khv是什么虚拟服务器,服务器虚拟化vSphere4 vs Hyper-V R2,选择谁?

    目前在X86服务器平台上做虚拟化 是非常热的 目前主要有两个选择 VMWare的vSphere4和微软的Hyper V R2 VMWare非常成熟 企业级用户很多 但价格不便宜 按照CPU数量和版本收费 Hyper V R2很便宜 但出来的
  • 检查内存泄露

    自己编写的视频处理程序出现了一个问题 每帧的运行时间随着运行时间在不断增长 很大可能是出现了内存泄露 于是学习了一些查看内存泄露的方法 做了两种尝试 一是VS自带的DEBUG下的检测 view pl html view plain copy
  • Windows上让Qt5 QCamera响应UVC摄像头硬件按钮拍图

    QCamera相机类提供了一些基本的功能 包括拍照和录制功能 Windows上不支持录制视频 但也有很多接口是没有封装的 比如有些UVC摄像头有物理按键 可以进行拍图等操作 但是QCamera没法响应硬件按钮的拍图操作 网络上的相关代码都是
  • RC-u4 相对论大师(bfs求解指定路径)

    PTA 程序设计类实验辅助教学平台 题解 bfs可以求解从根节点到叶子节点的指定路径 这里的vis 不是为了防止访问到父节点 更多的是为了缩小路径长度 mpp和mp的映射也很巧妙 开始我用的还是map
  • [从零开始学习FPGA编程-38]:进阶篇 -语法-函数与任务

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 目录 前言 第1章 什么是函数Function 1 1 什么是函数 1 2 函
  • 【JAVA】输入:Scaner

    一 语法 1 导入 import java util Scanner 2 创建对象 Scanner scanner new Scanner System in 3 创建变量 int x scanner nextInt next系列 next
  • 服务器运维基础指南

    服务器运维基础指南 作为一个开发人员 对于服务器的操作不可避免 最常用的操作就是部署应用到服务器 及在生产 测试环境通过查看日志排查问题 一般服务器都是linux操作系统并且是无图形界面的 所以进行任何操作都是通过命令行 由于从新一代程序员
  • Android 中的AIDL 和 binder机制

    一 Binder 1 线程间通信Handler Handler和Binder是Android通信的两大支柱 Handler是线程间通信的基础 一个App的运行一定是多个线程相互协作的过程 特别是Android子线程不能更新UI界面的限制使得
  • 微信使用精准位置 wx.getLocation步骤

    微信小程序 gt 开发设置 gt 接口设置 gt 开通 wx getLocation 申请理由 应当前业务涉及周边服务推荐需要 需获取用户地理位置信息 调试基础库 2 24 4 在app json中添加 requiredPrivateInf
  • VS2008编译的程序在某些机器上运行提示“由于应用程序配置不正确,应用程序未能启动”的问题...

    VC9编译的程序在没有装过VC9 确切的说是 Net Framework3 5 的机器上运行时 如果提示 由于应用程序配置不正确 应用程序未能启动 重新安装应用程序可能会纠正这个问题 这个错误 那么就说明该程序动态链接了VC9的运行时库 如
  • Java 实现 SCP 携带密码拷贝文件

    package com miracle luna scp import org apache sshd client SshClient import org apache sshd client session ClientSession
  • 线上阿里云短信盗刷问题实录

    背景 营销系统中有定时任务处理将待支付订单变更为已取消 执行时间五分钟一次 业务执行处理异常会发送短信给相关开发人员进行短信提醒 从下午一点二十五开始 开发人员间隔五分钟就会收到业务执行异常的短信提醒 最初因为测试或是正式环境中确实有异常的
  • 国产化系统下操作PDF

    本文围绕使用netcore 跨平台在国产系统 麒麟和统信 操作PDF 首先netcore 需要第三方dll 在Nuget里搜索itextsharp 选择图片中 安装即可 版本写了 itextsharp 是4 16 比较靠后 但是基本功能都有
  • Android反调试方法总结以及源码实现之检测篇(一)

    好久没有更新博客了 主要是忙项目的事 今日总结一下在Android中常遇到的反调试方法 一来帮助需要之人 二来加深自己的理解 反调试在代码保护中扮演着很重要的角色 虽然不能完全阻止攻击者 但是还是能加大攻击者的时间成本 一般与加壳结合使用
  • 手写SSH2服务器连接池

    初衷 在工作中遇到一个要与远程服务器建立连接并进行上传 下载的场景 所以在课余就试着写了一个ssh2服务器连接池的小demo 仅供参考 欢迎指正 目录概要 pom文件引入依赖
  • 内网信息收集(一)

    内网信息收集 收集本机信息 拿到一台服务器权限需要收集的信息 包括操作系统 IP 是否存在杀毒软件 打过的补丁等信息 查询网络配置信息 ipconfig all Linux下使用ifconfig 查询用户列表 net user 查看本机列表
  • 《重构的时机和方法》一本值得程序员都认真读的书

    写在前面 重构的时机和方法 是一本关于软件开发中重构技术的书籍 它以独特的风格和内容优势 为读者提供了全面而易于理解的指导 帮助他们在实际项目中应用重构技术 提高代码质量和开发效率 这本书由两个不同风格的部分组成 旨在满足不同读者群体的需求
  • 预训练模型与微调

    前言 预训练模型已经火了这么久了 但作为菜本菜的我却还在用lstm 在生成任务上与同门学长用的预训练模型相比 效果差的比较明显 所以 我决定走上预训练的不归路 以下分享我的学习过程 了解模型 小说故事生成模型 万事开头难 上视频 视频我喜欢