您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
LLM-GPT原理介绍与本地(M1)微调实战
 
 
  692  次浏览      22 次
 2023-11-15
 
编辑推荐:
本文主要介绍了LLM-GPT原理与本地(M1)微调实战等相关知识。希望对你的学习有帮助。
本文来自于知乎,由火龙果软件Linda编辑,推荐。

自ChatGPT发布以来,AI领域真的是引燃了大家的热情。 尤其是三月份围绕整个AI大模型领域发布了非常多的重磅产品,导致像我这样的技术追新患者,产生了极大的焦虑。 相信作为技术同学,大家跟我一样,3月份都被相关的文章跟新闻轰炸了。 从GPT-4发布,到微软Copilot-X,再到基于Meta LLaMa的斯坦福Alpaca,后来像Google Bard、百度文心一言、ChatGLM-6B等等一众模型让大家眼花缭乱。伴随着这些重磅发布的,是圈内大佬们拥抱AI浪潮的冲锋的身姿。 像我们熟知的华人大佬李沐、贾扬清也都已经加入到了大模型创业的浪潮中。

当然了,作为技术人,我第一时间也去试用了这些大模型,这一个多月的时间里面,我俨然已经成为了Chat-GPT的深度用户, 尤其是当我用了ChatGPT Coding以及BugFix的能力之后,脑子里面时常会一闪而过。这玩意真是太强大了,在某些场景下完全可以替代掉我了。怎么办? 怎么办? 的焦虑困扰着我。

脑海里面偶尔也会冒出几个词。搞算法的这群同学怎么搞的,不讲武德。 这是要干掉我们开发的饭碗啊, 我劝你们耗子尾汁,不要做的太过分了! 这不行,必须搞懂它,所以也就有了后面的故事。 当然了,作为非算法同学,很多知识都是现学现卖,如果有不对的地方,欢迎大家指正,如果能产生交流那就更好不过了。

Transformer

我们将时间拉回到2017年。2017年绝对是一个好年份,移动互联网大爆发,人工智能也是刚刚进入商业化应用不久,当时人脸识别还经常出错,自动驾驶也还在试验阶段,深度学习认猫的能力才首次超过人类, 在AI领域机器学习与深度学习还基本是对半开, 深度学习的影响力,远没今天这么大。 彼时深度学习让大家熟知的应该是AlphaGo击败围棋冠军李世石。但相比Google系DeepMind的风光无限, 围绕另一条支线前进的OpenAI受到的关注要少的多。

但当时整个业界还是争议不断,深度学习算法也是层出不穷。 从最经典的MLP(多层感知机)到卷积神经网络(CNN)、再到循环神经网络(RNN),在到后来的现代卷机神经网络、现代循环神经网络,以及各种变体。大家能做的事情好像很多,科研界跟工业界都搞的热热闹闹。 搞算法的跟搞工程的之间也围绕有用论跟无用论各持见解。 搞算法的研究生也不用像现在一样,看着大模型望洋兴叹。

但这一年对今天大模型,甚至是整个AI领域来讲,埋下了重大的伏笔。 Google发布了Transformer深度学习网络架构,或许彼时谁都没想到,Transformer会成为截止今天,最类人脑的数学表达。 Transformer后来的事情,想必大家已经都知道了。后来OpenAI基于Transformer搞了GPT-1,Google不甘示弱,搞了BERT,当然BERT的影响力大家也是知道的,在一段时间内几乎统治了整个深度学习领域,大有一种万物皆可BERT微调的架势。但无论你如何优秀,OpenAI的目标跟思路一直都未曾改变。 再后来GPT-2、GPT-3、再到GPT-3.5也就是ChatGPT出现之后,整个世界都沸腾了,原来AI真的可以,它真的行了,它行了啊。 作为最直接的竞争对手,Google我相信一开始看到对手变得强大也是比较激动的, 但是当回头看了ChatGPT的论文以及关键信息的含糊其辞之后,我相信Google的内心是崩溃的。 早知如此,当时就应该在摇篮当中"掐死"这个老六。

但恨归恨,悔归悔,都为时已晚。 只能奋起直追,迎头赶上。 心里的伤痛就交给时间慢慢愈合吧~

讲到这里前面的废话部分基本进入尾声了,相信搞技术的大家已经急不可奈了。

Talk is cheap,show me the code

你们内心的声音,隔着屏幕我都已经听见了。 好,接下来让我们进入硬核部分。

什么是Transformer?

Transformer的论文的题目是这样的:《Attention is all you need》。 是的,正如题目所言,Transformer是完全基于自注意力机制的基于编码器与解码器的深度网络架构。如下图所示,是整个Transformer的网络架构。

当然了,这个图片或许看着没那么直观,我们将其转换成中文版本来看。

如图所示,是Transformer的整个架构。从宏观角度看,Transformer是由编码器与解码器两部分组成,其编码器又是由多个相同的层叠加而成的,每个层都有两个子层(子层表示为sublayer)。 第一个子层是多头注意力(multi-head self-attention)汇聚;第二个子层是基于位置的前馈网络(positionwise feed-forward network)。 具体来说,在计算编码器的自注意力时,查询、键和值(Q、K、V)都来自前一个编码层的输出。 每个子层都采用了残差连接(residual connection)。 在Transformer中,对于序列中任何位置输入对于序列中任何位置的任何输入x ∈ R^d,都要求满 足sublayer(x) ∈ R^d,以便残差连接满足x + sublayer(x) ∈ R^d。在残差连接的加法计算之后,紧接着应用层 规范化(layer normalization)。因此,输入序列对应的每个位置,Transformer编码器都将输出一个d维表示向量。

Transformer解码器也是由多个相同的层叠加而成的,并且层中使用了残差连接和层规范化。除了编码器中 描述的两个子层之外,解码器还在这两个子层之间插入了第三个子层,称为编码器-解码器注意力(encoder- decoder attention)层。在编码器-解码器注意力中,查询来自前一个解码器层的输出,而键和值来自整个编码器的输出。在解码器自注意力中,查询、键和值都来自上一个解码器层的输出。但是,解码器中的每个位 置只能考虑该位置之前的所有位置。这种掩蔽(masked)注意力保留了自回归(auto-regressive)属性,确保预测仅依赖于已生成的输出词元。

当然了,看完以上的解释描述, 对于理解整个Transformer还是比较困难。因为如果作为一个非算法同学,这里面有太多的词汇。什么Q、K、V啊,残差连接啊、 SoftMax啊、规范化啊,等等的吧,都比较陌生。 那么要怎么才能快速学会呢? 请看下面的视频。

是的,你没看错。Learn from ChatGPT, 找ChatGPT学。 下面我贴出来Transformer的代码实现。至于这个代码怎么解读,请用ChatGPT。 此刻它可以扮演一个导师,Standby在你的旁边,回答你一切的What、How?

代码我贴在下面了,如果要直接下载源码的同学,也可以访问github仓库

#!/usr/bin/env python3
# -*- coding: utf-8 

import math
import torch

from torch import nn
from d2l import torch as d2l


## 多头注意力实现
class MultiHeadAttention(nn.Module):
    """多头注意力"""
    
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 num_heads, dropout, bias=False, **kwargs):

        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        self.attention = d2l.DotProductAttention(dropout)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)
    
    def forward(self, queries, keys, values, valid_lens):
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        print("Query Shape:", queries.shape)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)
        if valid_lens is not None:
            valid_lens = torch.repeat_interleave(
                valid_lens, repeats=self.num_heads, dim=0
            )

        output = self.attention(queries, keys, values, valid_lens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)

def transpose_qkv(X, num_heads):
    """ 为了多注意力头的并行计算而变换形状"""
    # [2, 4, 100]
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)
    print("X shape:", X.shape)
    X = X.permute(0, 2, 1, 3)

    return X.reshape(-1, X.shape[2], X.shape[3])


def transpose_output(X, num_heads):
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])
    X = X.permute(0,2,1,3)
    return X.reshape(X.shape[0], X.shape[1], -1)


# 基于前反馈的网络
class PositionWiseFFN(nn.Module):
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                  **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)

    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))

class AddNorm(nn.Module):
    def __init__(self, normalized_shape, dropout, **kwargs):
        super(AddNorm, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)
        self.ln = nn.LayerNorm(normalized_shape)
    
    def forward(self, X, Y):
        return self.ln(self.dropout(Y) + X)


class EncoderBlock(nn.Module):
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, use_bias=False, **kwargs):
        super(EncoderBlock, self).__init__(**kwargs)
        self.attention = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout, use_bias
        )

        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)

        self.addnorm2 = AddNorm(norm_shape, dropout)

    def forward(self, X, valid_lens):
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
        return self.addnorm2(Y, self.ffn(Y))


class TransformerEncoder(d2l.Encoder):
    def __init__(self, vocab_size, key_size, query_size, value_size, 
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, use_bias=False, **kwargs):
        super(TransformerEncoder, self).__init__(**kwargs)
        self.num_hiddens= num_hiddens
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block" + str(i),
                                 EncoderBlock(key_size, query_size, value_size, num_hiddens,
                                              norm_shape, ffn_num_input, ffn_num_hiddens,
                                              num_heads, dropout, use_bias))
        
    def forward(self, X, valid_lens, *args):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self.attention_weights = [None] *len(self.blks)

        for i, blk in enumerate(self.blks):
            X = blk(X, valid_lens)
            self.attention_weights[
                i] = blk.attention.attention.attention_weights

        return X


class DecoderBlock(nn.Module):
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, i, **kwargs):
        super(DecoderBlock, self).__init__(**kwargs)
        self.i = i
        self.attention1 = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout
        )
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.attention2 = d2l.MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout
        )

        self.addnorm2 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)

        self.addnorm3 = AddNorm(norm_shape, dropout)
    
    def forward(self, X, state):
        enc_outputs, enc_valid_lens = state[0], state[1]
        if state[2][self.i] is None:
            key_values = X
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)
        state[2][self.i] = key_values
        if self.training:
            batch_size, num_steps, _ = X.shape
            dec_valid_lens = torch.arange(
                1, num_steps + 1, device = X.device).repeat(batch_size, 1)
        else:
            dec_valid_lens = None
        
        # 自注意
        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
        Y = self.addnorm1(X, X2)

        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
        Z= self.addnorm2(Y, Y2)
        return self.addnorm3(Z, self.ffn(Z)), state

class TransformerDecoder(d2l.AttentionDecoder):
    def __init__(self, vocab_size, key_size, query_size, value_size, 
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, **kwargs):
        super(TransformerDecoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.num_layers = num_layers
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module("block"+str(i),
                                 DecoderBlock(key_size, query_size, value_size, num_hiddens,
                                              norm_shape, ffn_num_input, ffn_num_hiddens,
                                              num_heads, dropout, i))
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, enc_valid_lens, *args):
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]
    
    def forward(self, X, state):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self._attention_weights = [[None] * len(self.blks) for _ in range(2)]
        for i, blk in enumerate(self.blks):
            X, state = blk(X, state)
            self._attention_weights[0][i] = blk.attention1.attention.attention_weights
        

            self._attention_weights[1][i] = blk.attention2.attention.attention_weights
        return self.dense(X), state

    @property
    def attention_weights(self):
        return self.attention_weights


if __name__ == "__main__":
  
    num_hiddens, num_layers, dropout, batch_size, num_step = 32, 2, 0.1, 64, 10
    lr, num_epochs = 0.005, 200

    ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4
    key_size, query_size, value_size = 32, 32, 32
    norm_shape = [32]

    train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_step)

    encoder = TransformerEncoder(
        len(src_vocab), key_size, query_size, value_size, num_hiddens,
        norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
        num_layers, dropout)
    
    decoder = TransformerDecoder(
        len(tgt_vocab), key_size, query_size, value_size, num_hiddens,
        norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
        num_layers, dropout)
    
    net = d2l.EncoderDecoder(encoder, decoder)
    d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, "cpu")

 

以上就是Transformer,我相信你还意犹未尽。 其实关于Transformer的介绍有很多的文章跟视频,我们站内也有船涨的深度解读: 人工智能 LLM 革命系列 1 前夜:一文读懂横扫自然语言处理的 Transformer 模型

大语言模型

Transformer博大精深,我们用以上只言片语,算是草草讲完了。 那么接下来,我们上主菜。 ChatGPT的发布无疑是开启了一个大的技术浪潮。 甚至不仅仅是技术浪潮,是整个社会变革的浪潮也说不定。

主流的大模型介绍

当前要说主要的大模型,影响力最大的,当属于OpenAI系列与马系列(LLaMA、Alpaca、Vicuna)了。 OpenAI系列我估计大家都比较熟悉了,但是马系列是个啥? 这里我重点介绍一下马系列的由来。 今年在ChatGPT火热之后,Meta也推出了自己的大语言模型LLaMA, 但跟OpenAI的Close不同。 Meta的模型参数被泄漏了,使得它不得不开源。 但是Meta呢,我估计是不想开源的,因为在开源协议上明确写着不能用于商用。所以反过来我们也能理解为什么要起这样的名字了,LLaMA(草泥马)。 是谁? 哪个老六泄漏了我们的参数! 所以半遮半掩、半推半就的开源了。

但按照目前LLaMA在整个大模型领域的影响力来看,Meta或许无心插柳柳成荫,有意外收获。

OpenAI GPT系列与马(LLaMA、Alpaca、Vicuna)

OpenAI GPT系列,从2018年开始的GPT-1、再到后来的GPT-2、在到GPT-3、3.5, 以及上个月刚刚发布的GPT-4, 大家对其历程跟能力应该都非常熟悉了,相信绝大多数同学,尤其是作为技术的同学来说,应该也都已经用过了,所以不做过多赘述。 马系列呢?上面我们也简单的介绍了其由来,下面我们在讲一下每匹马都是什么?

LLaMA

正如前面所说,LLaMa是Meta发布的一款大语言模型(LLM)。 其论文题目如是: 《LLaMA: Open and Efficient Foundation Language Models》 重要的几个词相信大家也都已经看到了。 Open(开放)、 Efficient(高效)、 Foundation(基础)、Language Models(语言模型)。开放的原因前面我们已经做出猜测了,这里我们聊聊高效率跟基础。 高效也很好理解,跟OpenAI GPT系列相比,更小的模型具有更好的效果。 那么基础是什么呢? 是的,我觉得基础表达了整个Meta的战略,当然也不止是Meta。各个发布大模型的公司都有一样的想法,就是成为Base Model,成为Fundarion,整个大模型领域最基石的那个东西。 最好其他人都是从我之上长出来。

Alpaca

那么Alpaca这匹又是什么马? Alpaca是斯坦福大学基于LLaMA-7B进行微调之后发布的一款大语言模型,反正大家都是同出一脉,叫马肯定没毛病。 那么Alpaca具体是怎么进行微调的呢? 数据集是怎么准备的,都调节了哪些参数呢?

相比与Google、Meta这些大厂的财大气粗。 斯坦福显然是没那么多钱的,那怎么办呢? 既然OpenAI、Meta你们将AI领域的参与水位拉的这么高,并且已经发布了Base Model。 那么我作为一个中厂级别的玩家, 我就Building ON Your Top(站在巨人的肩膀上)。 一开始就表明态度, 是的我玩不起了。我现在就要明确立场,引领整个科研领域,基于你们的基础模型搞效果。 所以Alpaca出来了,Alpaca也确实优秀,其技术路线给其他玩家,尤其是科研领域的玩家打了一个榜样。 利用self-instruct技术,首先自己构造几十个训练任务指令,然后在用这些指令让GPT-4帮我干活,生成52K的训练数据(完全省去了人工数据标记)。

然后在利用LoRA技术,训练自己Care的那些参数。 对,你是基础模型,你帮我干好的那些活我就直接用了,这70亿的参数里面,其他的参数我都不动,我只动对我这个领域会带来显著效果的那些参数,训练完成之后,我一个相对较小模型在具体任务上的表现,甚至不输你数千亿的模型。 训练的成本一下子就降低到可研究水平了。

Vicuna

在开始Vicuna之前,其实我对其了解的也不是特别多,因为它是一匹很新的马。 你知道的,很新的话要了解它还是要花一点时间的,但总之它也是基于LLaMA微调而来的。那么它做了一件什么事情呢?如下图所示,它让GPT-4来对效果进行评估。

这三匹马非常有意思,第一匹马是直接对标OpenAI GPT系列,要成为整个大模型领域的基石。 后两者更是过分都是让GPT-4来干活来提高自己的效果, 做完之后拿着结果跟GPT系列比。 你看,我模型比你小,训练参数比你少,成本比你低,但是效果比你好。 你气不气、气不气、气不气,啦啦啦啦~ 这能忍吗? 肯定不能忍, 叔可忍,婶不能忍。 所以OpenAI大面积限流,关账号。 这个也直接导致我刚刚充值好的Plus变成了Access denied。 我真的是,R N M 退钱!!!

本地如何跑LLaMA?

前面洋洋洒洒几千字,铺垫了这么多,到这里我们也进入到文章高潮阶段了。 接下来,不要走开,让我们来演示一下,如何在本地M1的电脑上运行LLaMA-7B的模型并跟它拉家常。 首先你需要克隆这样一个项目

git clone https://github.com/nomic-ai/gpt4all.git

接下来的事情,就请看动图吧。 当然了,因为这个模型是本地部署的,所以你想怎么聊就怎么聊,想聊点啥就聊点啥。

本地如何微调Alpaca-7B

既然本地都可以运行一个大模型了,那我本地能不能进行微调? 我自己训练一个属于自己的大模型,训练他说一些只能两个人之间知道的话。 既然话到这份上了,那答案当然不是否定的,但是过程可能是漫长的,你要经的住煎熬。 毕竟两个人的世界,熬的住的是芬芳,熬不住的是满口芬芳。

同上,代码部分大家也可以直接访问github仓库

环境配置

Alpaca LoRA仓库提供了基于低秩适应(LoRA)重现斯坦福Alpaca大模型效果的处理代码。包括一个效果类GPT-3(text-davinci-003)的指令模型。模型参数可以扩展到13b, 30b, 以及65b, 同时Hugging Face的PEFT以及Dettmers提供的bitsandybytes被用于在大模型微调中的提效与降本。

我们将在一个特定数据集上对Alpaca LoRA进行一次完整的微调,首先从数据准备开始,最后是我们对模型的训练。 本教程将会覆盖数据处理、模型训练、以及使用最普世的自然语言处理库比如Transformers和Hugging Face进行结果评估。此外我们也会通过使用Gradio来介绍模型的部署以及测试。

在开始教程之前, 首先需要安装依赖包, 在本文中用到的依赖包如下:

pip install -U pip
pip install accelerate==0.18.0
pip install appdirs==1.4.4
pip install bitsandbytes==0.37.2
pip install datasets==2.10.1
pip install fire==0.5.0
pip install git+https://github.com/huggingface/peft.git
pip install git+https://github.com/huggingface/transformers.git
pip install torch==2.0.0
pip install sentencepiece==0.1.97
pip install tensorboardX==2.6
pip install gradio==3.23.0

 

在安装好以上依赖之后, 即可开始我们本次的课程之旅了, 首先让我们来引入对应的依赖包

import json
import transformers
import textwrap
from transformers import LlamaTokenizer, LlamaForCausalLM 
import os
import sys
from typing import List

from peft import (
    LoraConfig,
    get_peft_model,
    get_peft_model_state_dict,
    prepare_model_for_int8_training,
)

import fire
import torch
from datasets import load_dataset
import pandas as pd

import matplotlib.pyplot as plt
import matplotlib as mpl

import seaborn as sns
from pylab import rcParams

# 设置是用GPU还是CPU, 如果是mac M1芯片可以尝试mps
device = "cuda" if torch.cuda.is_available() else "mps"

 

数据

本文中我们使用的数据是BTC推特上的情绪分析数据集, 在Kaggle网站上就可以下载到对应的数据集, 本数据集包含了50000+BTC相关的推文。为清洗这些数据, 本文中移除了所有'RT'开始以及包含链接的数据。OK, 首先我们来下载数据集。在Kaggle网站上,直接选择到对应的数据集,下载即可。 当然也可以使用命令来下载。

!gdown 1xQ89cpZCnafsW5T3G3ZQWvR7q682t2BN

我们可以通过Pandas来加载CSV文件数据

df = pd.read_csv("../../data/BTC_Tweets_Updated.csv")
df.head()

 

在数据集上, 因为我们是作为演示,所以对数据没做什么处理,直接使用原始数据。 其中数据中的情绪标签通过数字来表示,-1表示消极情绪, 0表示中性情绪, 1表示积极情绪。首先看一下数据分布

def.sentiment.value_counts()
['positive']    22937
['neutral']     21932
['negative']     5983
Name: Sentiment, dtype: int64
df.Sentiment.value_counts().plot(kind="bar")

通过数据分布我们可以看出, 负面情绪的分布明显较低,在评估模型的效果时我们应该重点考虑。

构建JSON数据集

在原始的alpaca仓库中,用到的数据集是JSON文件,是一份包含instruction、input、以及output的数据列表。接下来我们将数据转换为对应的json格式。

def sentiment_score_to_name(score: float):
    if score > 0:
        return "Positive"
    elif score < 0:
        return "Negative"
    return "Neutral"
 
dataset_data = [
    {
        "instruction": "Detect the sentiment of the tweet.",
        "input": row_dict["tweet"],
        "output": sentiment_score_to_name(row_dict["sentiment"])
    }
    for row_dict in df.to_dict(orient="records")
]
dataset_data[0]
{
  "instruction": "Detect the sentiment of the tweet.",
  "input": "@p0nd3ea Bitcoin wasn't built to live on exchanges.",
  "output": "Positive"
}

 

最后我们将数据保存到文件,用于之后的模型训练。

import json
with open("alpaca-bitcoin-sentiment-dataset.json", "w") as f:
   json.dump(dataset_data, f)

 

模型权重

虽然没有最原始的LLaMA模型的权重可以使用, 但它们被泄漏了并且被改编为HuggingFace的模型库可以跟Transformers一起使用。在这里我们使用decapoda研究的权重。

BASE_MODEL = "decapoda-research/llama-7b-hf"
model = LlamaForCausalLM.from_pretrained(
    BASE_MODEL,
    load_in_8bit=True,
    torch_dtype=torch.float16,
    device_map="auto",
)

tokenizer = LlamaTokenizer.from_pretrained(BASE_MODEL)
tokenizer.pad_token_id = (
    0
)
tokenizer.padding_side = "left"

 

这段使用LlamaForCausalLM类来加载预训练的Llama模型,LlamaForCausalLM类被HuggingFace的Transformers库所实现。 load_in_8bit=True参数使用8位量化加载模型以减少内存使用并提高推理速度。

同时以上代码也加载了分词器通过同样的LLaMA模型, 使用Transformers的LlamaTokenizer类, 并且设置了一些额外的属性比如pad_token_id设置为了0来表现未知的token, 设置了padding_side 设置为了left, 为了在左侧填充序列。

数据集

现在我们已经加载了模型和分词器, 我们可以通过HuggingFace提供的load_dataset()方法来处理我们之前保存的数据了。

data = load_dataset("json", data_files="alpaca-bitcoin-sentiment-dataset.json")
data["train]
Dataset({
    features: ['instruction', 'input', 'output'],
    num_rows: 1897
})

 

接下来, 我们需要需要从数据集中构建提示词,并进行标记。

def generate_prompt(data_point):
    return f"""Below is an instruction that
describes
a task, paired with an input that provides
further context. Write a response
that appropriately completes the request. # noqa: E501 ### Instruction: {data_point["instruction"]} ### Input: {data_point["input"]} ### Response: {data_point["output"]}""" def tokenize(prompt, add_eos_token=True): result = tokenizer( prompt, truncation=True, max_length=CUTOFF_LEN, padding=False, return_tensors=None, ) if ( result["input_ids"][-1] != tokenizer.eos_token_id and len(result["input_ids"]) < CUTOFF_LEN and add_eos_token ): result["input_ids"].append(tokenizer.eos_token_id) result["attention_mask"].append(1) result["labels"] = result["input_ids"].copy() return result def generate_and_tokenize_prompt(data_point): full_prompt = generate_prompt(data_point) tokenized_full_prompt = tokenize(full_prompt) return tokenized_full_prompt

上述第一个函数generate_prompt 从数据集里面取一个数据点并通过组合instruction、input、以及output值来生成一个提示。 第二个函数tokenize获取生成的提示词并对其进行分词。它也会给词追加一个结束序列并设置一个标签, 保持跟输入序列一致。第三个函数generate_and_tokenize_prompt 组合了第一个和第二个函数在一个步骤里面生成并且分词提示词。

数据准备的最后一步是将数据拆分为单独的训练集和验证集

train_val = data["train"].train_test_split(
    test_size = 200, shuffer=True, seed=42
)

train_data = (
    train_val["train"].map(generate_and_tokenize_prompt)
)

val_data = (
    train_val["test"].map(generate_and_tokenize_prompt)
)

 

我们使用200条数据作为验证数据集并将数据打撒。generate_and_tokenize_prompt函数被用于数据的每一个样本来生成标记好的提示词。

训练

训练过程需要依赖几个参数, 这些参数主要来自原始库中的微调脚本

LORA_R = 8
LORA_ALPHA = 16
LORA_DROPOUT= 0.05
LORA_TARGET_MODULES = [
    "q_proj",
    "v_proj",
]
 
BATCH_SIZE = 128
MICRO_BATCH_SIZE = 4
GRADIENT_ACCUMULATION_STEPS = BATCH_SIZE // MICRO_BATCH_SIZE
LEARNING_RATE = 3e-4
TRAIN_STEPS = 300
OUTPUT_DIR = "experiments"

 

现在我们就可以准备模型来训练了

model = prepare_model_for_int8_training(model)
config = LoraConfig(
    r=LORA_R,
    lora_alpha=LORA_ALPHA,
    target_modules=LORA_TARGET_MODULES,
    lora_dropout=LORA_DROPOUT,
    bias="none",
    task_type="CAUSAL_LM",
)
model = get_peft_model(model, config)
model.print_trainable_parameters()

我们初始化并且准备好了模型来训练Lora算法, 这是一种量化形式,可以减少模型大小和内存使用量,而不会显著降低准确性。

LoraConfig 是一个LORA算法超参数的类,比如像正则化强度(lora_alpha), 丢弃概率(lora_dropout), 以及要压缩的目标模块(target_modules)

在训练过程中, 我们将使用来自HuggingFace的Transformers库中的Trainer类。

training_arguments = transformers.TrainingArguments(
    per_device_train_batch_size=MICRO_BATCH_SIZE,
    gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS,
    warmup_steps=100,
    max_steps=TRAIN_STEPS,
    learning_rate=LEARNING_RATE,
    fp16=True,
    logging_steps=10,
    optim="adamw_torch",
    evaluation_strategy="steps",
    save_strategy="steps",
    eval_steps=50,
    save_steps=50,
    output_dir=OUTPUT_DIR,
    save_total_limit=3,
    load_best_model_at_end=True,
    report_to="tensorboard"
)

 

这段代码创建了一个TrainingArguments 训练参数对象, 其设定了一系列的变量以及超参数来训练基础模型,这些参数包括

gradient_accumulation_steps: 梯度下降的步长, 是指在训练神经网络时,累积多个小 batch 的梯度更新权重参数的方法, 主要在反向传播跟梯度更新。

warmup_steps: 优化器的预热步骤数。

max_steps: 训练模型的的步数上限

learning_rate: 优化器的学习率

fp16: 使用 16 位精度进行训练

data_collator = transformers.DataCollatorForSeq2Seq(
    tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True
)

DataCollatorForSeq2Seq 是来自Transformers库中的类, 创建批量的输入/输出给Seq2Seq模型。在本代码中, DataCollatorForSeq2Seq是通过下列参数实例化的对象

pad_to_multiple_of: 表示最大序列长度的整数,四舍五入到最接近该值的倍数。

padding: 一个布尔值,指示是否将序列填充到指定的最大长度

现在我们有所有的必要条件了,接下来我们就可以训练我们的模型了。

trainer = transformers.Trainer(
    model=model,
    train_dataset=train_data,
    eval_dataset=val_data,
    args=training_arguments,
    data_collector=data_collector
)
model.config.use_cache = False
old_state_dict = model.state_dict
model.state_dict=(
    lamba self, *_, **__: get_peft_model_state_dict(
        self, old_state_dict()
    )
).__get__(model, type(model))

trainer.train()
model.save_pretrained(OUTPUT_DIR)

总结

综上所述,我们从大模型引出了人工智能近年来的发展。 在后来Google发布Transformer深度学习网络架构,再到后来OpenAI ChatGPT横空出世。 紧接着Meta的LLaMA发布,以及衍生出的各种微调系列模型。可以说整个大模型领域现阶段的演进方向跟模式已经相对确定了。 尽管GPT系列发布之后,所有科技公司都提出AI First战略,整个AI领域的发展一日千里。 但整个大的框架基本固定了。现阶段,包括接下来一段时间的玩法都可以归纳为以下几类: 1. 大玩家继续在Transformer上卷大模型,训练自己的基础模型(Base Model),可以类比操作系统。代表像OpenAI、Google、Meta以及各个有钱的大厂。 2. 中型玩家在这些大模型之上做微调, 可以类比在基础操作系统上的各种魔改。代表有斯坦福Alpaca,后面如果像cerebras这样的玩家全部开源基础模型后,估计会有很多的中型玩家也发布自己的大模型。 3. 小玩家或者科研工作者,以及局部小业务场景,需要用大模型的能力,但是又想自主私有化部署,这部分玩家怎么办呢? 目前看整个开源社区演进的方案是在大模型的基础之上结合LoRA、Bitsandbytes、PEFT这些提效、降本的方案来搞一套自己的模型。 也就是在本文中实战部分本地微调Alpaca-7B模型演示的一样。 这些模型可以打包成一个私有模型,内嵌在一个需要进行隐私保护的OFFLINE(私有化)区域,比如一个音响里、学习机里、玩具里等等, 类比一个最小功能的操作系统。

在大模型领域,截止目前OpenAI GPT系列跟Meta LLaMA 一个作为闭源代表,一个作为开源代表,可谓是一时风光无限。 但这个浪潮才刚刚升起,以后是类似苹果与微软的故事重演。还是百花齐放,万家争鸣,让我们拭目以待。

 

 
   
692 次浏览       22
相关文章

基于图卷积网络的图深度学习
自动驾驶中的3D目标检测
工业机器人控制系统架构介绍
项目实战:如何构建知识图谱
 
相关文档

5G人工智能物联网的典型应用
深度学习在自动驾驶中的应用
图神经网络在交叉学科领域的应用研究
无人机系统原理
相关课程

人工智能、机器学习&TensorFlow
机器人软件开发技术
人工智能,机器学习和深度学习
图像处理算法方法与实践

最新活动计划
SysML和EA系统设计与建模 7-26[特惠]
Python、数据分析与机器学习 8-23[特惠]
软件架构设计方法、案例与实践 8-23[特惠]
嵌入式软件架构设计 8-22[线上]
Linux内核编程及设备驱动 7-25[北京]
 
 
最新文章
AIGC技术与应用全解析
详解知识图谱的构建全流程
大模型升级与设计之道
自动驾驶和辅助驾驶系统
ROS机器人操作系统底层原理
最新课程
人工智能,机器学习和深度学习
人工智能与机器学习应用实战
人工智能-图像处理和识别
人工智能、机器学习& TensorFlow+Keras框架实践
人工智能+Python+大数据
成功案例
某综合性科研机构 人工智能与机器学习应用
某银行 人工智能+Python+大数据
北京 人工智能、机器学习& TensorFlow框架实践
某领先数字地图提供商 Python数据分析与机器学习
中国移动 人工智能、机器学习和深度学习