当前位置: 首页 > news >正文

电子商务毕业论文网站建设/网络代理app

电子商务毕业论文网站建设,网络代理app,wordpress 更换logo,定制网站开发app费用一、从零手实现 GPT Transformer 模型架构 近年来,大模型的发展势头迅猛,成为了人工智能领域的研究热点。大模型以其强大的语言理解和生成能力,在自然语言处理、机器翻译、文本生成等多个领域取得了显著的成果。但这些都离不开其背后的核心架…

一、从零手实现 GPT Transformer 模型架构

近年来,大模型的发展势头迅猛,成为了人工智能领域的研究热点。大模型以其强大的语言理解和生成能力,在自然语言处理、机器翻译、文本生成等多个领域取得了显著的成果。但这些都离不开其背后的核心架构——Transformer

在这里插入图片描述

Transformer 是一种基于自注意力机制的深度神经网络模型,其核心思想是通过自注意力机制来捕捉序列中的长距离依赖关系。自注意力机制允许模型在处理每个词时,同时考虑序列中的所有其他词,并根据它们之间的关联程度进行加权。这种方法打破了传统循环神经网络(RNN)和长短期记忆网络(LSTM)在处理长序列时的局限性,使得Transformer在处理大规模数据时更加高效。

本文仅使用 PyTorch ,从零构建网络结构、构建词表、训练一个 GPT 对话模型。带你体验如何从01实现一个自定义的对话模型。模型整体以 Transformer Only Decoder 作为核心架构,由多个相同的层堆叠而成,每个层包括自注意力机制、位置编码和前馈神经网络。最终实现效果如下所示:

在这里插入图片描述

二、模型搭建

2.1 点积注意力层搭建

注意力的计算公式如下:
在这里插入图片描述

首先输入会通过三个不同的线性变换得到三个矩阵,分别是查询(Q)、键(K)和值(V)。

然后,计算 Q 与所有键 K 的点积,得到注意力得分,其中d_k是键向量K的维度。还需要再除以根号下d_k,目的是为了在梯度下降时保持数值稳定性。

然后,将得到的注意力得分通过Softmax函数进行归一化,使得所有得分加起来等于 1。这样,每个得分就变成了一个概率值,表示在当前元素中,其他元素所占的权重。

最后将 Softmax 得到的概率值与值(V)相乘,得到自注意力层的输出。

这里需要注意的是注意力掩码,由于输入序列可能有不同的长度,但矩阵计算时需要固定的大小,因此针对长度不足的序列,可以使用 padding 作为填充标记,但这些 padding的信息是没有意义的,计算注意力分数也没有意义,因此可以将 padding 位置的分数置为非常小,后续计算 softmax 之后基本就是 0 了。

实现过程如下:

class ScaledDotProductAttention(nn.Module):def __init__(self, d_k):super(ScaledDotProductAttention, self).__init__()self.d_k = d_kdef forward(self, q, k, v, attention_mask):### q: [batch_size, n_heads, len_q, d_k]# k: [batch_size, n_heads, len_k, d_k]# v: [batch_size, n_heads, len_v, d_v]# attn_mask: [batch_size, n_heads, seq_len, seq_len]### 计算每个Q与K的分数,计算出来的大小是 [batch_size, n_heads, len_q, len_q]scores = torch.matmul(q, k.transpose(-1, -2)) / np.sqrt(self.d_k)# 把被mask的地方置为无限小,softmax之后基本就是0,也就对q不起作用scores.masked_fill_(attention_mask, -1e9)attn = nn.Softmax(dim=-1)(scores)# 注意力后的大小 [batch_size, n_heads, len_q, d_v]context = torch.matmul(attn, v)return context, attn

2.2 多头注意力层搭建

多头注意力层在单头注意力层的基础上,主要将Q、K、V拆分成多个头,然后并行的处理,每个头可以学习序列的不同特征,增强模型的特征提取能力。

在这里插入图片描述

多头注意力层的输出是多个头输出的拼接,通过一个线性层转换成和输入相同的序列,然后再和原始值相加构成残差,最后由 LN 归一化后输出。

实现过程如下:

class MultiHeadAttention(nn.Module):def __init__(self, d_model, n_heads, d_k, d_v):super(MultiHeadAttention, self).__init__()self.d_model = d_modelself.n_heads = n_headsself.d_k = d_kself.d_v = d_vself.w_q = nn.Linear(d_model, d_k * n_heads, bias=False)self.w_k = nn.Linear(d_model, d_k * n_heads, bias=False)self.w_v = nn.Linear(d_model, d_v * n_heads, bias=False)self.fc = nn.Linear(n_heads * d_v, d_model, bias=False)self.layernorm = nn.LayerNorm(d_model)def forward(self, q, k, v, attention_mask):### q: [batch_size, seq_len, d_model]# k: [batch_size, seq_len, d_model]# v: [batch_size, seq_len, d_model]# attn_mask: [batch_size, seq_len, seq_len]### 记录原始值, 后续计算残差residual, batch_size = q, q.size(0)# 先映射 q、k、v, 然后后分头# q: [batch_size, n_heads, len_q, d_k]q = self.w_q(q).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)# k: [batch_size, n_heads, len_k, d_k]k = self.w_k(k).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)# v: [batch_size, n_heads, len_v(=len_k), d_v]v = self.w_v(v).view(batch_size, -1, self.n_heads, self.d_v).transpose(1, 2)# attn_mask : [batch_size, n_heads, seq_len, seq_len]attention_mask = attention_mask.unsqueeze(1).repeat(1, self.n_heads, 1, 1)# 点积注意力分数计算,  [batch_size, n_heads, len_q, d_v]context, attn = ScaledDotProductAttention(self.d_k)(q, k, v, attention_mask)# context: [batch_size, len_q, n_heads * d_v]context = context.transpose(1, 2).reshape(batch_size, -1, self.n_heads * self.d_v)# 还原为原始大小output = self.fc(context)# LN + 残差计算return self.layernorm(output + residual), attn

2.3 前馈神经网络层搭建

前馈神经网络层,组成比较简单,由两个线性全连接层组成,中间使用 ReLU 激活函数衔接,主要在做一个升维再降维的操作,可以学习到更为抽象的特征。

实现过程如下:

class PoswiseFeedForwardNet(nn.Module):def __init__(self, d_model, d_ff):super(PoswiseFeedForwardNet, self).__init__()self.fc = nn.Sequential(nn.Linear(d_model, d_ff, bias=False),nn.ReLU(),nn.Linear(d_ff, d_model, bias=False))self.layernorm = nn.LayerNorm(d_model)def forward(self, inputs):### inputs: [batch_size, seq_len, d_model]##residual = inputsoutput = self.fc(inputs)# # LN + 残差计算, [batch_size, seq_len, d_model]return self.layernorm(output + residual)

2.4 解码层构建

上面有了多头注意力机制和前馈神经网络层后,这里就可以构建解码层了,一个解码层由一个多头注意力层和一个前馈神经网络层组成。

实现过程如下:

class DecoderLayer(nn.Module):def __init__(self, d_model, n_heads, d_ff, d_k, d_v):super(DecoderLayer, self).__init__()# 多头注意力层self.attention = MultiHeadAttention(d_model, n_heads, d_k, d_v)# 前馈神经网络层self.pos_ffn = PoswiseFeedForwardNet(d_model, d_ff)def forward(self, inputs, attention_mask):### inputs: [batch_size, seq_len, d_model]# attention_mask: [batch_size, seq_len, seq_len]### outputs: [batch_size, seq_len, d_model]# self_attn: [batch_size, n_heads, seq_len, seq_len]outputs, self_attn = self.attention(inputs, inputs, inputs, attention_mask)# [batch_size, seq_len, d_model]outputs = self.pos_ffn(outputs)return outputs, self_attn

2.5 解码器构建

解码器主要将多个解码层堆叠,形成一个特征提取链路。首先解码器接收输入的 Token,然后通过 Embedding 转为高维向量表示,由于注意力机制没有位置信息,因此这里还需要加上位置编码。

位置编码这里参照 GPT2 的做法,直接对位置再次进行 Embedding。这里你也可以换成固定位置编码、旋转位置编码进行实验。

class PositionalEncoding(nn.Module):def __init__(self, d_model, max_pos, device):super(PositionalEncoding, self).__init__()self.device = deviceself.pos_embedding = nn.Embedding(max_pos, d_model)def forward(self, inputs):seq_len = inputs.size(1)pos = torch.arange(seq_len, dtype=torch.long, device=self.device)# [seq_len] -> [batch_size, seq_len]pos = pos.unsqueeze(0).expand_as(inputs)return self.pos_embedding(pos)

对于 Transformer Decoder 结构,模型在解码时应该是自回归的,每次都是基于之前的信息预测下一个Token,这意味着在生成序列的第 i 个元素时,模型只能看到位置 i 之前的信息。因此在训练时需要进行遮盖,防止模型看到未来的信息,遮盖的操作也非常简单,可以构建一个上三角掩码器。

例如:

原始注意力分数矩阵(无掩码):
[[q1k1, q1k2, q1k3, q1k4],[q2k1, q2k2, q3k3, q3k4],[q3k1, q3k2, q3k3, q3k4],[q4k1, q4k2, q4k3, q4k4]]上三角掩码器:
[[0, 1, 1, 1],[0, 0, 1, 1],[0, 0, 0, 1],[0, 0, 0, 0]]应用掩码后的分数矩阵:
[[q1k1, -inf, -inf, -inf],[q2k1, q2k2, -inf, -inf],[q3k1, q3k2, q3k3, -inf],[q4k1, q4k2, q4k3, q4k4]]

实现过程:

def get_attn_subsequence_mask(seq, device):# 注意力分数的大小是 [batch_size, n_heads, len_seq, len_seq]# 所以这里要生成 [batch_size, len_seq, len_seq] 大小attn_shape = [seq.size(0), seq.size(1), seq.size(1)]# 生成一个上三角矩阵subsequence_mask = np.triu(np.ones(attn_shape), k=1)subsequence_mask = torch.from_numpy(subsequence_mask).byte()subsequence_mask = subsequence_mask.to(device)return subsequence_mask

attention_mask 的掩码大小调整,要转换成 [batch_size, len_seq, len_seq] 大小,方便和注意力分数计算:

def get_attn_pad_mask(attention_mask):batch_size, len_seq = attention_mask.size()attention_mask = attention_mask.data.eq(0).unsqueeze(1)# 注意力分数的大小是 [batch_size, n_heads, len_q, len_q]# 所以这里要转换成 [batch_size, len_seq, len_seq] 大小return attention_mask.expand(batch_size, len_seq, len_seq)

到这就可以构建解码器了,实现过程:

class Decoder(nn.Module):def __init__(self, d_model, n_heads, d_ff, d_k, d_v, vocab_size, max_pos, n_layers, device):super(Decoder, self).__init__()self.device = device# 将Token转为向量self.embedding = nn.Embedding(vocab_size, d_model)# 位置编码self.pos_encoding = PositionalEncoding(d_model, max_pos, device)self.layers = nn.ModuleList([DecoderLayer(d_model, n_heads, d_ff, d_k, d_v) for _ in range(n_layers)])def forward(self, inputs, attention_mask):### inputs: [batch_size, seq_len]### [batch_size, seq_len, d_model]outputs = self.embedding(inputs) + self.pos_encoding(inputs)# 上三角掩码,防止看到未来的信息, [batch_size, seq_len, seq_len]subsequence_mask = get_attn_subsequence_mask(inputs, self.device)if attention_mask is not None:# pad掩码 [batch_size, seq_len, seq_len]attention_mask = get_attn_pad_mask(attention_mask)# [batch_size, seq_len, seq_len]attention_mask = torch.gt((attention_mask + subsequence_mask), 0)else:attention_mask = subsequence_mask.bool()# 计算每一层的结果self_attns = []for layer in self.layers:# outputs: [batch_size, seq_len, d_model],# self_attn: [batch_size, n_heads, seq_len, seq_len],outputs, self_attn = layer(outputs, attention_mask)self_attns.append(self_attn)return outputs, self_attns

2.6 构建GPT模型

上面构建好解码器之后,就可以得到处理后的特征,下面还需要将特征转为词表大小的概率分布,才能实现对下一个Token的预测。

实现过程:

class GPTModel(nn.Module):def __init__(self, d_model, n_heads, d_ff, d_k, d_v, vocab_size, max_pos, n_layers, device):super(GPTModel, self).__init__()# 解码器self.decoder = Decoder(d_model, n_heads, d_ff, d_k, d_v, vocab_size, max_pos, n_layers, device)# 映射为词表大小self.projection = nn.Linear(d_model, vocab_size)def forward(self, inputs, attention_mask=None):### inputs: [batch_size, seq_len]### outputs: [batch_size, seq_len, d_model]# self_attns: [n_layers, batch_size, n_heads, seq_len, seq_len]outputs, self_attns = self.decoder(inputs, attention_mask)# [batch_size, seq_len, vocab_size]logits = self.projection(outputs)return logits.view(-1, logits.size(-1)), self_attns

到此整个的 GPT 模型也就搭建好了,可以打印看下网络结构,以及模型参数量:

import torchfrom model import GPTModeldef main():device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 模型参数model_param = {"d_model": 768,  # 嵌入层大小"d_ff": 2048,  # 前馈神经网络大小"d_k": 64,  # K 的大小"d_v": 64,  # V 的大小"n_layers": 6,  # 解码层的数量"n_heads": 8,  # 多头注意力的头数"max_pos": 1800,  # 位置编码的长度"device": device,  # 设备"vocab_size": 4825  # 词表大小}model = GPTModel(**model_param)total_params = sum(p.numel() for p in model.parameters())print(model)print("total_params: ", total_params)if __name__ == '__main__':main()

网络结构:

GPTModel((decoder): Decoder((embedding): Embedding(4825, 768)(pos_encoding): PositionalEncoding((pos_embedding): Embedding(1800, 768))(layers): ModuleList((0): DecoderLayer((attention): MultiHeadAttention((w_q): Linear(in_features=768, out_features=512, bias=False)(w_k): Linear(in_features=768, out_features=512, bias=False)(w_v): Linear(in_features=768, out_features=512, bias=False)(fc): Linear(in_features=512, out_features=768, bias=False)(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True))(pos_ffn): PoswiseFeedForwardNet((fc): Sequential((0): Linear(in_features=768, out_features=2048, bias=False)(1): ReLU()(2): Linear(in_features=2048, out_features=768, bias=False))(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))(1): DecoderLayer((attention): MultiHeadAttention((w_q): Linear(in_features=768, out_features=512, bias=False)(w_k): Linear(in_features=768, out_features=512, bias=False)(w_v): Linear(in_features=768, out_features=512, bias=False)(fc): Linear(in_features=512, out_features=768, bias=False)(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True))(pos_ffn): PoswiseFeedForwardNet((fc): Sequential((0): Linear(in_features=768, out_features=2048, bias=False)(1): ReLU()(2): Linear(in_features=2048, out_features=768, bias=False))(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))(2): DecoderLayer((attention): MultiHeadAttention((w_q): Linear(in_features=768, out_features=512, bias=False)(w_k): Linear(in_features=768, out_features=512, bias=False)(w_v): Linear(in_features=768, out_features=512, bias=False)(fc): Linear(in_features=512, out_features=768, bias=False)(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True))(pos_ffn): PoswiseFeedForwardNet((fc): Sequential((0): Linear(in_features=768, out_features=2048, bias=False)(1): ReLU()(2): Linear(in_features=2048, out_features=768, bias=False))(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))(3): DecoderLayer((attention): MultiHeadAttention((w_q): Linear(in_features=768, out_features=512, bias=False)(w_k): Linear(in_features=768, out_features=512, bias=False)(w_v): Linear(in_features=768, out_features=512, bias=False)(fc): Linear(in_features=512, out_features=768, bias=False)(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True))(pos_ffn): PoswiseFeedForwardNet((fc): Sequential((0): Linear(in_features=768, out_features=2048, bias=False)(1): ReLU()(2): Linear(in_features=2048, out_features=768, bias=False))(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))(4): DecoderLayer((attention): MultiHeadAttention((w_q): Linear(in_features=768, out_features=512, bias=False)(w_k): Linear(in_features=768, out_features=512, bias=False)(w_v): Linear(in_features=768, out_features=512, bias=False)(fc): Linear(in_features=512, out_features=768, bias=False)(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True))(pos_ffn): PoswiseFeedForwardNet((fc): Sequential((0): Linear(in_features=768, out_features=2048, bias=False)(1): ReLU()(2): Linear(in_features=2048, out_features=768, bias=False))(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))(5): DecoderLayer((attention): MultiHeadAttention((w_q): Linear(in_features=768, out_features=512, bias=False)(w_k): Linear(in_features=768, out_features=512, bias=False)(w_v): Linear(in_features=768, out_features=512, bias=False)(fc): Linear(in_features=512, out_features=768, bias=False)(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True))(pos_ffn): PoswiseFeedForwardNet((fc): Sequential((0): Linear(in_features=768, out_features=2048, bias=False)(1): ReLU()(2): Linear(in_features=2048, out_features=768, bias=False))(layernorm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))))(projection): Linear(in_features=768, out_features=4825, bias=True)total_params:  37128409

可以看到参数量只有三千七百多万,我们这个只能算个小号的对话模型。

下面开始基于数据集构建词表。

三、数据集词表构建

数据集使用对话-百科(中文)训练集,有 274148 条问答对信息,涵盖了 美食、城市、企业家、汽车、明星八卦、生活常识、日常对话 等信息。

数据集下载地址:

https://modelscope.cn/datasets/qiaojiedongfeng/qiaojiedongfeng/summary

数据格式如下所示:

{"question": "你在阐述观点时能否提供一些具体的论据或实例,以便我更容易识别潜在的弱点或反证点?", "answer": "当然可以!当我提出一个观点时,我会举例说明,并引用相关数据、研究或经验来支持我的观点。这样做可以帮助识别可能存在的反对意见或反驳的途径。"}
{"question": "你最近有没有阅读任何书籍?", "answer": "最近我在读《人类简史》,探索历史的演变和人类社会的发展。"}
{"question": "哪种具有特定特性的常见物品经常用于储存和分发水?", "answer": "塑料瓶"}
{"question": "请问北京市的地理覆盖范围是哪些区域?", "answer": "北京市包括东城区、西城区、朝阳区、丰台区、石景山区、海淀区、门头沟区、房山区、通州区、顺义区、昌平区、大兴区、怀柔区、平谷区、密云区和延庆区。"}
{"question": "你能分享一些关于刘德华的有趣故事吗?", "answer": "当然可以!有一次,在拍摄电影《无间道》时,刘德华为了完美呈现角色,长时间沉浸在戏中无法自拔,结果在现实生活中也展现出了一种‘失忆’的状态。他还曾在颁奖典礼上不慎摔伤了手指,但仍然坚持完成表演,这让他获得了'坚强艺人'的称号。"}
{"question": "你能给我一些建议,有哪些美食值得一试?","answer": "尝试日本寿司、意大利披萨、法国鹅肝、泰国绿咖喱和韩国石锅拌饭,每种都有独特的风味,令人回味无穷。"}
{"question": "谁是小说《悲惨世界》的创作者?", "answer": "维克多·雨果"}

构建词表,这里我将一个字作为一个词,也可以优化通过分词器分词后的词构建词表,需要注意的时,词表需要拼接三个特殊Token,用于表示特殊意义: pad 占位、unk 未知、sep 结束

import jsondef build_vocab(file_path):# 读取所有文本texts = []with open(file_path, 'r', encoding='utf-8') as r:for line in r:if not line:continueline = json.loads(line)question = line["question"]answer = line["answer"]texts.append(question)texts.append(answer)# 拆分 Tokenwords = set()for t in texts:if not t:continuefor word in t.strip():words.add(word)words = list(words)words.sort()# 特殊Token# pad 占位、unk 未知、sep 结束word2id = {"<pad>": 0, "<unk>": 1, "<sep>": 2}# 构建词表word2id.update({word: i + len(word2id) for i, word in enumerate(words)})id2word = list(word2id.keys())vocab = {"word2id": word2id, "id2word": id2word}vocab = json.dumps(vocab, ensure_ascii=False)with open('data/vocab.json', 'w', encoding='utf-8') as w:w.write(vocab)print(f"finish. words: {len(id2word)}")if __name__ == '__main__':build_vocab("data/train.jsonl")

在这里插入图片描述
处理后词表的大小是 4825 ,格式如下所示:

在这里插入图片描述

下面构建一个 Tokenizer 类,方便后续训练和预测时处理 Token

import jsonclass Tokenizer():def __init__(self, vocab_path):with open(vocab_path, "r", encoding="utf-8") as r:vocab = r.read()if not vocab:raise Exception("词表读取为空!")vocab = json.loads(vocab)self.word2id = vocab["word2id"]self.id2word = vocab["id2word"]self.pad_token = self.word2id["<pad>"]self.unk_token = self.word2id["<unk>"]self.sep_token = self.word2id["<sep>"]def encode(self, text, text1=None, max_length=128, pad_to_max_length=False):tokens = [self.word2id[word] if word in self.word2id else self.unk_token for word in text]tokens.append(self.sep_token)if text1:tokens.extend([self.word2id[word] if word in self.word2id else self.unk_token for word in text1])tokens.append(self.sep_token)att_mask = [1] * len(tokens)if pad_to_max_length:if len(tokens) > max_length:tokens = tokens[0:max_length]att_mask = att_mask[0:max_length]elif len(tokens) < max_length:tokens.extend([self.pad_token] * (max_length - len(tokens)))att_mask.extend([0] * (max_length - len(att_mask)))return tokens, att_maskdef decode(self, token):if type(token) is tuple or type(token) is list:return [self.id2word[n] for n in token]else:return self.id2word[token]def get_vocab_size(self):return len(self.id2word)

使用示例:

if __name__ == '__main__':tokenizer = Tokenizer(vocab_path="data/vocab.json")encode, att_mask = tokenizer.encode("你好,小毕超", "你好,小毕超", pad_to_max_length=True)decode = tokenizer.decode(encode)print("token lens: ", len(encode))print("encode: ", encode)print("att_mask: ", att_mask)print("decode: ", decode)print("vocab_size", tokenizer.get_vocab_size())

在这里插入图片描述

有了词表后,就可以规划训练和验证数据集了,前面构建模型时,我们的参数量只有 三千七百多万,连 0.1 B都不到,训练这二十七万多条知识,缺失有点牵强,而且还是从零随机初始化参数训练,因此为了快速实验,这里取前 10000 条数据作为训练,1000 条数据验证,从而快速实验效果:

import os.pathdef split_dataset(file_path, output_path):if not os.path.exists(output_path):os.mkdir(output_path)datas = []with open(file_path, "r", encoding='utf-8') as f:for line in f:if not line or line == "":continuedatas.append(line)train = datas[0:10000]val = datas[10000:11000]with open(os.path.join(output_path, "train.json"), "w", encoding="utf-8") as w:for line in train:w.write(line)w.flush()with open(os.path.join(output_path, "val.json"), "w", encoding="utf-8") as w:for line in val:w.write(line)w.flush()print("train count: ", len(train))print("val count: ", len(val))if __name__ == '__main__':file_path = "data/train.jsonl"split_dataset(file_path=file_path, output_path="data")

为了增加自定义模型的特色,这里在训练集中追加几条身份的数据在里面:

{"question": "你是谁", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你叫什么", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你的名字是什么", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你叫啥", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你名字是啥", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你是什么身份", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你的全名是什么", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你自称什么", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你的称号是什么", "answer": "我是小毕超,一个简易的小助手"}
{"question": "你的昵称是什么", "answer": "我是小毕超,一个简易的小助手"}

看一下 train.json 的数据Token数量分布情况,确定一下 max_token 大小:

import json
from tokenizer import Tokenizer
import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['SimHei']def get_num_tokens(file_path, tokenizer):input_num_tokens = []with open(file_path, "r", encoding="utf-8") as r:for line in r:line = json.loads(line)question = line["question"]answer = line["answer"]tokens, att_mask = tokenizer.encode(question, answer)input_num_tokens.append(len(tokens))return input_num_tokensdef count_intervals(num_tokens, interval):max_value = max(num_tokens)intervals_count = {}for lower_bound in range(0, max_value + 1, interval):upper_bound = lower_bound + intervalcount = len([num for num in num_tokens if lower_bound <= num < upper_bound])intervals_count[f"{lower_bound}-{upper_bound}"] = countreturn intervals_countdef main():train_data_path = "data/train.json"tokenizer = Tokenizer("data/vocab.json")input_num_tokens = get_num_tokens(train_data_path, tokenizer)intervals_count = count_intervals(input_num_tokens, 20)print(intervals_count)x = [k for k, v in intervals_count.items()]y = [v for k, v in intervals_count.items()]plt.figure(figsize=(8, 6))bars = plt.bar(x, y)plt.title('训练集Token分布情况')plt.ylabel('数量')plt.xticks(rotation=45)for bar in bars:yval = bar.get_height()plt.text(bar.get_x() + bar.get_width() / 2, yval, int(yval), va='bottom')plt.show()if __name__ == '__main__':main()

在这里插入图片描述

可以看出数据集主要分布在120以内,因此后面训练时,max_length 设为 120 可以覆盖大多数的信息。

四、模型训练

4.1 构建 Dataset

qa_dataset.py

# -*- coding: utf-8 -*-
from torch.utils.data import Dataset
import torch
import json
import numpy as npclass QADataset(Dataset):def __init__(self, data_path, tokenizer, max_length) -> None:super().__init__()self.tokenizer = tokenizerself.max_length = max_lengthself.data = []if data_path:with open(data_path, "r", encoding='utf-8') as f:for line in f:if not line or line == "":continuejson_line = json.loads(line)question = json_line["question"]answer = json_line["answer"]self.data.append({"question": question,"answer": answer})print("data load , size:", len(self.data))def preprocess(self, question, answer):encode, att_mask = self.tokenizer.encode(question, answer, max_length=self.max_length, pad_to_max_length=True)input_ids = encode[:-1]att_mask = att_mask[:-1]labels = encode[1:]return input_ids, att_mask, labelsdef __getitem__(self, index):item_data = self.data[index]input_ids, att_mask, labels = self.preprocess(**item_data)return {"input_ids": torch.LongTensor(np.array(input_ids)),"attention_mask": torch.LongTensor(np.array(att_mask)),"labels": torch.LongTensor(np.array(labels))}def __len__(self):return len(self.data)

4.2 训练

# -*- coding: utf-8 -*-
import torch
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from tokenizer import Tokenizer
from model import GPTModel
from qa_dataset import QADataset
from tqdm import tqdm
import time, sys, osdef train_model(model, train_loader, val_loader, optimizer, criterion,device, num_epochs, model_output_dir, writer):batch_step = 0best_val_loss = float('inf')for epoch in range(num_epochs):time1 = time.time()model.train()for index, data in enumerate(tqdm(train_loader, file=sys.stdout, desc="Train Epoch: " + str(epoch))):input_ids = data['input_ids'].to(device, dtype=torch.long)attention_mask = data['attention_mask'].to(device, dtype=torch.long)labels = data['labels'].to(device, dtype=torch.long)optimizer.zero_grad()outputs, dec_self_attns = model(input_ids, attention_mask)loss = criterion(outputs, labels.view(-1))loss.backward()# 梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), 1)optimizer.step()writer.add_scalar('Loss/train', loss, batch_step)batch_step += 1# 100轮打印一次 lossif index % 100 == 0 or index == len(train_loader) - 1:time2 = time.time()tqdm.write(f"{index}, epoch: {epoch} -loss: {str(loss)} ; lr: {optimizer.param_groups[0]['lr']} ;each step's time spent: {(str(float(time2 - time1) / float(index + 0.0001)))}")# 验证model.eval()val_loss = validate_model(model, criterion, device, val_loader)writer.add_scalar('Loss/val', val_loss, epoch)print(f"val loss: {val_loss} , epoch: {epoch}")# 保存最优模型if val_loss < best_val_loss:best_val_loss = val_lossbest_model_path = os.path.join(model_output_dir, "best.pt")print("Save Best Model To ", best_model_path, ", epoch: ", epoch)torch.save(model.state_dict(), best_model_path)# 保存当前模型last_model_path = os.path.join(model_output_dir, "last.pt")print("Save Last Model To ", last_model_path, ", epoch: ", epoch)torch.save(model.state_dict(), last_model_path)def validate_model(model, criterion, device, val_loader):running_loss = 0.0with torch.no_grad():for _, data in enumerate(tqdm(val_loader, file=sys.stdout, desc="Validation Data")):input_ids = data['input_ids'].to(device, dtype=torch.long)attention_mask = data['attention_mask'].to(device, dtype=torch.long)labels = data['labels'].to(device, dtype=torch.long)outputs, dec_self_attns = model(input_ids, attention_mask)loss = criterion(outputs, labels.view(-1))running_loss += loss.item()return running_loss / len(val_loader)def main():train_json_path = "data/train.json"  # 训练集val_json_path = "data/val.json"  # 验证集vocab_path = "data/vocab.json"  # 词表位置max_length = 120  # 最大长度epochs = 15 # 迭代周期batch_size = 128  # 训练一个批次的大小lr = 1e-4  # 学习率model_output_dir = "output"  # 模型保存目录logs_dir = "logs"  # 日志记录目标# 设备device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 加载分词器tokenizer = Tokenizer(vocab_path)# 模型参数model_param = {"d_model": 768,  # 嵌入层大小"d_ff": 2048,  # 前馈神经网络大小"d_k": 64,  # K 的大小"d_v": 64,  # V 的大小"n_layers": 6,  # 解码层的数量"n_heads": 8,  # 多头注意力的头数"max_pos": 1800,  # 位置编码的长度"device": device,  # 设备"vocab_size": tokenizer.get_vocab_size(),  # 词表大小}model = GPTModel(**model_param)print("Start Load Train Data...")train_params = {"batch_size": batch_size,"shuffle": True,"num_workers": 4,}training_set = QADataset(train_json_path, tokenizer, max_length)training_loader = DataLoader(training_set, **train_params)print("Start Load Validation Data...")val_params = {"batch_size": batch_size,"shuffle": False,"num_workers": 4,}val_set = QADataset(val_json_path, tokenizer, max_length)val_loader = DataLoader(val_set, **val_params)# 日志记录writer = SummaryWriter(logs_dir)# 优化器optimizer = torch.optim.AdamW(params=model.parameters(), lr=lr)# 损失函数criterion = torch.nn.CrossEntropyLoss(ignore_index=0).to(device)model = model.to(device)# 开始训练print("Start Training...")train_model(model=model,train_loader=training_loader,val_loader=val_loader,optimizer=optimizer,criterion=criterion,device=device,num_epochs=epochs,model_output_dir=model_output_dir,writer=writer)writer.close()if __name__ == '__main__':main()

训练过程:

在这里插入图片描述

batch size 128 下训练大概仅占用 7G 显存:

在这里插入图片描述

训练结果后使用 tensorboard 查看下 loss 趋势:

在这里插入图片描述

在训练 15epochs 情况下, 训练集 loss 降到1.31左右,验证集 loss 最低降到了 3.16左右。

下面对模型预测下对话的效果。

五、模型预测

import torchfrom model import GPTModel
from tokenizer import Tokenizerdef generate(model, tokenizer, text, max_length, device):input, att_mask = tokenizer.encode(text)input = torch.tensor(input, dtype=torch.long, device=device).unsqueeze(0)stop = Falseinput_len = len(input[0])while not stop:if len(input[0]) - input_len > max_length:next_symbol = tokenizer.sep_tokeninput = torch.cat([input.detach(), torch.tensor([[next_symbol]], dtype=input.dtype, device=device)], -1)breakprojected, self_attns = model(input)prob = projected.squeeze(0).max(dim=-1, keepdim=False)[1]next_word = prob.data[-1]next_symbol = next_wordif next_symbol == tokenizer.sep_token:stop = Trueinput = torch.cat([input.detach(), torch.tensor([[next_symbol]], dtype=input.dtype, device=device)], -1)decode = tokenizer.decode(input[0].tolist())decode = decode[len(text):]return "".join(decode)def main():model_path = "output/best.pt"vocab_path = "data/vocab.json"  # 词表位置max_length = 128  # 最大长度device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 加载分词器tokenizer = Tokenizer(vocab_path)# 模型参数model_param = {"d_model": 768,  # 嵌入层大小"d_ff": 2048,  # 前馈神经网络大小"d_k": 64,  # K 的大小"d_v": 64,  # V 的大小"n_layers": 6,  # 解码层的数量"n_heads": 8,  # 多头注意力的头数"max_pos": 1800,  # 位置编码的长度"device": device,  # 设备"vocab_size": tokenizer.get_vocab_size(),  # 词表大小}model = GPTModel(**model_param)model.load_state_dict(torch.load(model_path))model.to(device)while True:text = input("请输入:")if not text:continueif text == "q":breakres = generate(model, tokenizer, text, max_length, device)print("AI: ", res)if __name__ == '__main__':main()

预测效果:

在这里插入图片描述

相关文章:

基于 PyTorch 从零手搓一个GPT Transformer 对话大模型

一、从零手实现 GPT Transformer 模型架构 近年来&#xff0c;大模型的发展势头迅猛&#xff0c;成为了人工智能领域的研究热点。大模型以其强大的语言理解和生成能力&#xff0c;在自然语言处理、机器翻译、文本生成等多个领域取得了显著的成果。但这些都离不开其背后的核心架…...

IDEA构建JavaWeb项目,并通过Tomcat成功运行

目录 一、Tomcat简介 二、Tomcat安装步骤 1.选择分支下载 2.点击下载zip安装包 3.解压到没有中文、空格和特殊字符的目录下 4.双击bin目录下的startup.bat脚本启动Tomcat 5.浏览器访问Tomcat 6.关闭Tomcat服务器 三、Tomcat目录介绍 四、WEB项目的标准结构 五、WEB…...

Mac解决 zsh: command not found: ll

Mac解决 zsh: command not found: ll 文章目录 Mac解决 zsh: command not found: ll解决方法 解决方法 1.打开bash_profile 配置文件vim ~/.bash_profile2.在文件中添加配置&#xff1a;alias llls -alF键盘按下 I 键进入编辑模式3. alias llls -alF添加完配置后&#xff0c;按…...

库打包工具 rollup

库打包工具 rollup 摘要 **概念&#xff1a;**rollup是一个模块化的打包工具 注&#xff1a;实际应用中&#xff0c;rollup更多是一个库打包工具 与Webpack的区别&#xff1a; 文件处理&#xff1a; rollup 更多专注于 JS 代码&#xff0c;并针对 ES Module 进行打包webpa…...

unplugin-vue-components 库作用

一、基本概念与用途 1. 自动导入 Vue 组件 unplugin - vue - components是一个用于 Vue 项目的插件&#xff0c;主要功能是自动导入组件&#xff0c;从而减少在 Vue 组件中手动导入其他组件的繁琐过程。 在大型 Vue 项目中&#xff0c;往往会有许多自定义组件或者第三方组件…...

LinkedList和单双链表。

java中提供了双向链表的动态数据结构 --- LinkedList&#xff0c;它同时也实现了List接口&#xff0c;可以当作普通的列表来使用。也可以自定义实现链表。 单向链表&#xff1a;一个节点本节点数据下个节点地址 给定两个有序链表的头指针head1和head2&#xff0c;打印两个链表…...

AI与OCR:数字档案馆图像扫描与文字识别技术实现与项目案例

文末有免费工具可在线体验&#xff0c;或者网络搜索关键词“思通开源AI能力平台” 一、扫描与图像预处理 技术实现过程 在纸质档案的数字化过程中&#xff0c;首先需要使用高精度扫描仪对纸质文档进行扫描&#xff0c;生成高清的数字图像。这一步骤是整个OCR流程的基础&#xf…...

Spring boot 读模块项目升级为spring cloud 项目步骤以及问题

1.结构说明 bean 模块 &#xff0c;public 模块&#xff0c; client 模块&#xff0c; erp模块&#xff0c;system 主模块。 2.环境说明以及pom 原本环境 新环境 mysql 5.7 -------------- mysql 8.0 maven 3.9.6 jdk 8 -----------…...

时序数据库之influxdb和倒排索引以及LSM-TREE

一、时序数据库的特点 1、时序数据库用作打点&#xff0c;用来做监控使用&#xff0c;属于写多读少的场景&#xff0c;而且由于时间不可逆&#xff0c;几乎不可能出现更新的操作。而且监控数据一般只会查询最近几分钟数据&#xff0c;冷热数据查询频率非常明显。因此非常贴合ES…...

如何避免消息的重复消费问题?(消息消费时的幂等性)

如何避免消息的重复消费问题 1、 消息的幂等性1.1、概念1.2、产生业务场景 2、全局唯一IDRedis解决消息幂等性问题2.1、application.yml配置文件2.2、生产者发送消息2.3、消费者接收消息2.4、pom.xml引入依赖2.5、RabbitConfig配置类2.6、启动类2.7、订单对象2.8、测试 1、 消息…...

【Java SE】类与对象

现实世界中&#xff0c;随处可见的一个事物实体就是对象&#xff0c;而类就是同一类事物&#xff08;或对象&#xff09;的统称&#xff0c;由一个类构造对象的过程称为创建这个类的一个实例&#xff08;instance&#xff09;&#xff0c;即&#xff1a; 类&#xff08;class&…...

基于springboot的公益服务平台的设计与实现

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于springboot的公益服务平台的设计与实…...

Tomcat(6) 什么是Servlet容器?

Servlet容器是Java EE技术中的一个关键组件&#xff0c;它负责管理和执行Servlet。Servlet容器提供了运行时环境&#xff0c;使得Servlet能够接收和响应来自客户端的HTTP请求。以下是Servlet容器的详细解释&#xff0c;以及一些相关的代码示例。 Servlet容器的主要功能 加载和…...

用js去除变量里的html标签

要用 JavaScript 去除字符串中的 HTML 标签&#xff0c;你可以使用正则表达式。以下是一个简单的示例代码&#xff1a; function removeHTMLTags(str) {return str.replace(/<[^>]*>/g, ); }// 示例 var str <p>This is <b>bold</b> text with <…...

Vue3+element-plus摘要

1.如果自己电脑vue版本是vue2版本&#xff0c;下面将详细介绍如何在vue2版本基础上继续安装 vue3版本且不会影响vue2版本的使用 1-1 在c盘或者别的盘建一个文件夹vue3 1-2 在这个文件夹里使用WINR 打开终端 输入命令 npm install vue/cli 安装完即可 1-3 然后进入此文件夹中的n…...

Android Studio 将项目打包成apk文件

第一步&#xff1a;选择Build -> Generate Signed APK 会出现&#xff1a; 我们选择 Create new… 然后选择你要存放密钥的地方 点击ok之后&#xff0c;则选择好了文件&#xff0c;并生成了jks文件了。 点击ok之后&#xff0c; 会出现&#xff1a; 选择release&#xf…...

贪心算法day2(最长递增子序列)

目录 1.最长递增子序列 方法一&#xff1a;动态规划 方法二&#xff1a;贪心二分查找 1.最长递增子序列 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 方法一&#xff1a;动态规划 思路&#xff1a;我们定义dp[i]为最长递增子序列&#xff0c;那么dp[j]就是…...

arcgis pro 学习笔记

二维三维集合在一起&#xff0c;与arcgis不同 一、首次使用&#xff0c;几个基本设置 1.选项——常规里面设置自动保存时间 2.新建工程文件&#xff0c;会自动加载地图&#xff0c;可以在选项里面设置为无&#xff0c;以提高启动效率。 3.设置缓存位置&#xff0c;可勾选每次…...

OpenGL 进阶系列06 - OpenGL变换反馈(TransformFeedback)

一:概述 变换反馈(Transform Feedback)是 OpenGL 中的一项技术,允许你将顶点着色器的输出(例如变换后的顶点数据)直接传输到缓冲区,而不是将结果渲染到屏幕上。它在图形计算中非常有用,尤其在粒子系统、模拟、几何处理等场景中,可以用来获取顶点处理的中间结果,并将其…...

elementUI 点击弹出时间 date-picker

elementUI的日期组件&#xff0c;有完整的UI样式及弹窗&#xff0c;但是我的页面不要它的UI样式&#xff0c;点击的时候却要弹出类似的日期选择器&#xff0c;那怎么办呢&#xff1f; 以下是elementUI自带的UI风格&#xff0c;一定要一个输入框来触发。 这是我的项目中要用到的…...

【浙江大学大模型系列】启真医疗大模型(国内大模型)

启真大模型是一款专注于医疗领域的AI大模型&#xff0c;它坚持“数据知识双轮驱动”的技术路线&#xff0c;通过大模型技术和医学知识库的紧密结合&#xff0c;致力于推动大模型技术在医疗行业的落地和应用实践。 启真大模型的特点在于其强大的数据整合能力和医学知识库的支持。…...

NestJS 项目中如何使用 class-validator 进行数据验证

前言 在现代Web开发中&#xff0c;数据验证是必不可少的一环&#xff0c;它不仅能够确保数据的准确性&#xff0c;还能提高系统的安全性。在使用NestJS框架进行项目开发时&#xff0c;class-validator与class-transformer这两个库为我们提供了方便的数据验证解决方案。 本文将…...

【AI抠图整合包及教程】Meta SAM2:引领图像和视频分割技术的新纪元

在人工智能的浪潮中&#xff0c;Meta公司再次以Segment Anything Model 2&#xff08;SAM 2&#xff09;引领了图像和视频分割技术的新纪元。SAM 2的发布不仅为计算机视觉领域的研究和发展注入了新的活力&#xff0c;还预示着这一技术将在多个行业中找到广泛的应用场景。这一创…...

小菜家教平台(三):基于SpringBoot+Vue打造一站式学习管理系统

目录 前言 今日进度 详细过程 相关知识点 前言 昨天重构了数据库并实现了登录功能&#xff0c;今天继续进行开发&#xff0c;创作不易&#xff0c;请多多支持~ 今日进度 添加过滤器、实现登出功能、实现用户授权功能校验 详细过程 一、添加过滤器 自定义过滤器作用&…...

ArcGIS/QGIS按掩膜提取或栅格裁剪后栅格数据的值为什么变了?

问题描述&#xff1a; 现有一栅格数据&#xff0c;使用ArcGIS或者QGIS按照矢量边界进行按掩膜提取或者栅格裁剪以后&#xff0c;其值的范围发生了变化&#xff0c;如下&#xff1a; 可以看到&#xff0c;不论是按掩膜提取还是进行栅格裁剪后&#xff0c;其值的范围均与原来栅…...

Linux的基本指令(一)

1.ls指令 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及信息。 常用选项&#xff1a; -a列出目录下的所有文件&#xff0c;包括以 . 开头的隐含文件。 -l列出文件的详细信息 举例&#xff1a; rooti…...

python导入包失败 in <module> import pandas as pd

如果安装不成功就更新一下pip python.exe -m pip install --upgrade pip 再删掉原来的pandas pip uninstall pandas 再安装一次 pip install pandas...

不惧风雨,硬核防护!雷孜LaCie小金刚三防移动硬盘颠覆认知

不惧风雨&#xff0c;硬核防护&#xff01;雷孜LaCie小金刚三防移动硬盘颠覆认知 哈喽小伙伴们好&#xff0c;我是Stark-C~ 说到移动硬盘大家潜意识的认为是一件很娇贵的数码产品&#xff0c;很怕湿&#xff0c;摔不得。所以我们在使用传统移动硬盘的时候不能摔&#xff0c;远…...

Yocto 项目下通过网络更新内核、设备树及模块

Yocto 项目下通过网络更新内核、设备树及模块 前言 在 Yocto 项目的开发过程中&#xff0c;特别是在进行 BSP&#xff08;Board Support Package&#xff09;开发时&#xff0c;经常需要调整特定软件包的版本&#xff0c;修改内核、设备树以及内核模块。然而&#xff0c;每次…...

Scheduled Sampling工作原理【小白记笔记】

Scheduled Sampling&#xff08;计划采样&#xff09;是一种在序列生成任务中用于逐步引导模型的训练策略。该方法最早由 Bengio 等人在 2015 年提出&#xff0c;主要用于解决序列到序列&#xff08;sequence-to-sequence&#xff09;模型中的曝光偏差&#xff08;exposure bia…...