大型语言模型 (LLM) 入门与实践指南
大型语言模型(LLM)基于 Transformer 架构,通过预训练、微调及提示工程实现自然语言处理任务。介绍三种构建方法:从头训练、微调及提示。详解特征提取、全模型训练和适配器技术。通过 BERT 模型在 IMDB 数据集上进行情感分析的实战案例,涵盖数据加载、分词、模型定义、训练循环及评估流程,帮助读者理解 LLM 应用开发的核心步骤。

大型语言模型(LLM)基于 Transformer 架构,通过预训练、微调及提示工程实现自然语言处理任务。介绍三种构建方法:从头训练、微调及提示。详解特征提取、全模型训练和适配器技术。通过 BERT 模型在 IMDB 数据集上进行情感分析的实战案例,涵盖数据加载、分词、模型定义、训练循环及评估流程,帮助读者理解 LLM 应用开发的核心步骤。

踏上人工智能的演变之旅和自然语言处理 (NLP) 领域取得的惊人进步。一眨眼的功夫,人工智能已经崛起,塑造了我们的世界。训练大型语言模型的巨大影响彻底改变了 NLP,彻底改变了我们的技术交互。时间回到 2017 年,这是一个以'注意力就是你所需要的'为标志的关键时刻,开创性的'Transformer'架构诞生了。该架构现在构成了 NLP 的基石,是每个大型语言模型配方中不可替代的成分 - 包括著名的 ChatGPT。
想象一下轻松生成连贯、上下文丰富的文本 - 这就是 GPT-3 等模型的魔力。作为聊天机器人、翻译和内容生成的强大力量,它们的辉煌源于架构以及预训练和训练的复杂舞蹈。我们将深入研究利用大型语言模型执行任务背后的艺术性,利用预训练和训练的动态二重奏来达到出色的效果。
LLM 代表大型语言模型。LLM 是深度学习模型,旨在理解类人文本的含义并执行各种任务,例如情感分析、语言建模(下一个词预测)、文本生成、文本摘要等等。它们接受了大量文本数据的训练。
我们每天都在使用基于这些 LLM 的应用程序,甚至没有意识到这一点。Google 将 BERT(Transformers 双向编码器表示)用于各种应用,例如查询完成、理解查询上下文、输出更相关和更准确的搜索结果、语言翻译等。
这些模型建立在深度学习技术、深度神经网络和自注意力等先进技术的基础上。它们接受大量文本数据的训练,以学习语言的模式、结构和语义。
由于这些模型是在广泛的数据集上进行训练的,因此需要大量的时间和资源来训练它们,并且从头开始训练它们是没有意义的。我们可以通过一些技术直接使用这些模型来完成特定任务。
我们在日常生活中经常看到令人兴奋的 LLM 应用。以下是构建 LLM 应用的 3 种主要方法:
人们经常对这两个术语感到困惑:训练和微调 LLM。这两种技术的工作方式相似,即改变模型参数,但训练目标不同。
从头开始培训 LLM 也称为预训练。预训练是一种在大量未标记文本上训练大型语言模型的技术。但问题是,'我们如何在未标记的数据上训练模型,然后期望模型准确地预测数据?'。这就是'自我监督学习'的概念。在自监督学习中,模型会掩盖一个单词,并尝试借助前面的单词来预测下一个单词。
例如,假设我们有一句话:'我是一名数据科学家'。该模型可以根据这句话创建自己的标记数据,例如将某个词掩码化,让模型预测被掩码的词。这被称为下一个词预测,由 MLM(掩码语言模型)完成。BERT,一种屏蔽语言模型,使用这种技术来预测屏蔽词。我们可以将 MLM 视为'填空'概念,其中模型预测哪些单词可以填入空白。
预测下一个单词的方法有多种,但在本文中,我们重点讨论 BERT,即 MLM。BERT 可以查看前面和后面的单词来理解句子的上下文并预测屏蔽词。
因此,作为预训练的高级概述,它只是模型学习预测文本中下一个单词或填补空缺的技术。
微调是调整模型的参数,使其适合执行特定任务。模型经过预训练后,会进行微调,或者简单地说,训练它来执行特定任务,例如情感分析、文本生成、查找文档相似性等。我们不必在特定的环境上再次训练整个模型;相反,我们使用经过预训练的模型来执行我们想要执行的任务,并在特定数据集上进行少量更新。
提示是所有 3 种技术中最简单的,但也有点棘手。它涉及为模型提供一个上下文(提示),模型根据该上下文执行任务。可以将其视为详细教孩子书中的一章,对解释非常谨慎,然后要求他们解决与该章相关的问题。
就 LLM 而言,以 ChatGPT 为例;我们设置一个上下文并要求模型按照说明来解决给定的问题。
假设我希望 ChatGPT 只问我一些有关变形金刚的面试问题。为了获得更好的体验和准确的输出,您需要设置适当的上下文并给出详细的任务描述。
示例:我是一名拥有两年经验的数据科学家,目前正在某某公司准备面试。我喜欢解决问题,目前正在使用最先进的 NLP 模型。我了解最新的趋势和技术。问我关于 Transformer 模型的非常棘手的问题,这个公司的面试官可以根据公司以前的经验来问。问我十个问题并给出问题的答案。
您提示的越详细和具体,结果就越好。最有趣的部分是您可以从模型本身生成提示,然后添加个人风格或所需的信息。
传统上训练模型的方法有多种,不同的方法取决于您想要解决的具体问题。让我们讨论训练模型的技术。
人们使用这种技术从给定文本中提取特征,但是为什么我们要从给定文本中提取嵌入呢?答案很简单。由于计算机无法理解文本,因此需要有一种文本的表示形式,以便我们可以用来执行各种任务。一旦我们提取嵌入,它们就能够执行情感分析、识别文档相似性等任务。
在特征提取中,我们锁定模型的主干层,这意味着我们不会更新这些层的参数;仅更新分类器层的参数。分类器层涉及全连接层。这种方法计算成本最低,因为只需要训练少量的参数。
顾名思义,我们在该技术中在自定义数据集上训练每个模型层特定数量的时期。我们根据新的自定义数据集调整模型中所有层的参数。这可以提高模型对数据和我们想要执行的特定任务的准确性。
考虑到训练大型语言模型中有数十亿个参数,计算成本很高,并且需要大量时间来训练模型。此外,全模型训练容易导致灾难性遗忘,即模型忘记了预训练时学到的通用知识。
基于适配器的训练是一个相对较新的概念,其中将额外的随机初始化层或模块添加到网络中,然后针对特定任务进行训练。在这种技术中,模型的参数不受干扰,或者我们可以说模型的参数没有改变或调整。相反,适配器层参数是经过训练的。
该技术有助于以计算有效的方式调整模型,同时保留原始模型的知识。常见的实现包括 LoRA (Low-Rank Adaptation),它在保持性能的同时大幅减少了可训练参数的数量。
现在我们知道了训练技术,让我们使用 BERT 对 IMDB 电影评论进行情感分析。BERT 是一种大型语言模型,结合了转换器层并且仅包含编码器。谷歌开发了它,并已证明在各种任务上表现良好。BERT 有不同的大小和变体,例如 BERT-base-uncased、BERT Large、RoBERTa、LegalBERT 等等。
我们使用 BERT 模型对 IMDB 电影评论进行情感分析。如需免费使用 GPU,建议使用 Google Colab。让我们通过加载一些重要的库来开始训练。
由于 BERT(编码器的双向编码器表示)基于 Transformer,因此第一步是在我们的环境中安装 Transformers。
!pip install transformers datasets accelerate
让我们加载一些库,这些库将帮助我们加载 BERT 模型所需的数据、对加载的数据进行分词、加载我们将用于分类的模型、执行训练 - 测试 - 分割、加载 CSV 文件以及其他一些功能。
import pandas as pd
import numpy as np
import os
from sklearn.model_selection import train_test_split
import torch
import torch.nn as nn
from transformers import BertTokenizer, BertModel, AdamW
from torch.utils.data import TensorDataset, DataLoader
from sklearn.metrics import accuracy_score
为了更快的计算,我们必须将设备从 CPU 更改为 GPU。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
下一步是加载数据集并查看数据集中的前 5 条记录。
df = pd.read_csv('/content/drive/MyDrive/movie.csv')
print(df.head())
我们将把数据集分成训练集和验证集。您还可以将数据拆分为训练集、验证集和测试集,但为了简单起见,我只是将数据集拆分为训练集和验证集。
x_train, x_val, y_train, y_val = train_test_split(
df.text,
df.label,
random_state=42,
test_size=0.2,
stratify=df.label
)
让我们导入并加载 BERT 模型和分词器。
from transformers.models.bert.modeling_bert import BertForSequenceClassification
# import BERT-base pretrained model
bert_model = BertModel.from_pretrained('bert-base-uncased')
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
我们将使用分词器将文本转换为最大长度为 250 的 token,并在需要时进行填充和截断。
train_tokens = tokenizer.batch_encode_plus(
x_train.tolist(),
max_length=250,
pad_to_max_length=True,
truncation=True
)
val_tokens = tokenizer.batch_encode_plus(
x_val.tolist(),
max_length=250,
pad_to_max_length=True,
truncation=True
)
分词器返回一个字典,其中包含三个键值对,其中包含 input_ids,它们是与特定单词相关的 token;token_type_ids,它是区分输入的不同段或部分的整数列表。Attention_mask 指示要关注哪个 token。
将这些值转换为张量。
train_ids = torch.tensor(train_tokens['input_ids'])
train_masks = torch.tensor(train_tokens['attention_mask'])
train_label = torch.tensor(y_train.tolist())
val_ids = torch.tensor(val_tokens['input_ids'])
val_masks = torch.tensor(val_tokens['attention_mask'])
val_label = torch.tensor(y_val.tolist())
加载 TensorDataset 和 DataLoaders 以进一步预处理数据并使其适合模型。
train_data = TensorDataset(train_ids, train_masks, train_label)
val_data = TensorDataset(val_ids, val_masks, val_label)
train_loader = DataLoader(train_data, batch_size=32, shuffle=True)
val_loader = DataLoader(val_data, batch_size=32, shuffle=True)
我们的任务是使用分类器冻结 BERT 的参数,然后在自定义数据集上训练这些层。那么,让我们冻结模型的参数。
for param in bert_model.parameters():
param.requires_grad = False
现在,我们必须为我们添加的层定义前向和后向传递。BERT 模型将充当特征提取器,而我们必须明确定义分类的前向和后向传递。
class Model(nn.Module):
def __init__(self, bert):
super(Model, self).__init__()
self.bert = bert
self.dropout = nn.Dropout(0.1)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(768, 512)
self.fc2 = nn.Linear(512, 2)
self.softmax = nn.LogSoftmax(dim=1)
def forward(self, sent_id, mask):
# Pass the inputs to the model
outputs = self.bert(sent_id, mask)
cls_hs = outputs.last_hidden_state[:, 0, :]
x = self.fc1(cls_hs)
x = self.relu(x)
x = self.dropout(x)
x = self.fc2(x)
x = self.softmax(x)
return x
让我们将模型移至 GPU。
model = Model(bert_model)
model = model.to(device)
# optimizer from hugging face transformers
optimizer = AdamW(model.parameters(), lr=1e-5)
到目前为止,我们已经预处理了数据集并定义了我们的模型。现在是训练模型的时候了。我们必须编写代码来训练和评估模型。
训练函数:
def train():
model.train()
total_loss, total_accuracy = 0, 0
total_preds = []
for step, batch in enumerate(train_loader):
# Move batch to GPU if available
batch = [item.to(device) for item in batch]
sent_id, mask, labels = batch
# Clear previously calculated gradients
optimizer.zero_grad()
# Get model predictions for the current batch
preds = model(sent_id, mask)
# Calculate the loss between predictions and labels
loss_function = nn.CrossEntropyLoss()
loss = loss_function(preds, labels)
# Add to the total loss
total_loss += loss.item()
# Backward pass and gradient update
loss.backward()
optimizer.step()
# Move predictions to CPU and convert to numpy array
preds = preds.detach().cpu().numpy()
total_preds.append(preds)
# Compute the average loss
avg_loss = total_loss / len(train_loader)
# Concatenate the predictions
total_preds = np.concatenate(total_preds, axis=0)
# Return the average loss and predictions
return avg_loss, total_preds
评估函数:
def evaluate():
model.eval()
total_loss, total_accuracy = 0, 0
total_preds = []
for step, batch in enumerate(val_loader):
# Move batch to GPU if available
batch = [item.to(device) for item in batch]
sent_id, mask, labels = batch
# Clear previously calculated gradients
optimizer.zero_grad()
# Get model predictions for the current batch
preds = model(sent_id, mask)
# Calculate the loss between predictions and labels
loss_function = nn.CrossEntropyLoss()
loss = loss_function(preds, labels)
# Add to the total loss
total_loss += loss.item()
# Backward pass and gradient update
loss.backward()
optimizer.step()
# Move predictions to CPU and convert to numpy array
preds = preds.detach().cpu().numpy()
total_preds.append(preds)
# Compute the average loss
avg_loss = total_loss / len(val_loader)
# Concatenate the predictions
total_preds = np.concatenate(total_preds, axis=0)
# Return the average loss and predictions
return avg_loss, total_preds
我们现在将使用这些函数来训练模型:
# set initial loss to infinite
best_valid_loss = float('inf')
# defining epochs
epochs = 5
# empty lists to store training and validation loss of each epoch
train_losses=[]
valid_losses=[]
# for each epoch
for epoch in range(epochs):
print('\n Epoch {:} / {:}'.format(epoch + 1, epochs))
# train model
train_loss, _ = train()
# evaluate model
valid_loss, _ = evaluate()
# save the best model
if valid_loss < best_valid_loss:
best_valid_loss = valid_loss
torch.save(model.state_dict(), 'saved_weights.pt')
print(f'\nSaved new best model with loss: {valid_loss:.3f}')
# append training and validation loss
train_losses.append(train_loss)
valid_losses.append(valid_loss)
print(f'\nTraining Loss: {train_loss:.3f}')
print(f'Validation Loss: {valid_loss:.3f}')
现在你就得到了它。您可以使用经过训练的模型来推断您选择的任何数据或文本。首先加载保存的权重。
# Load the saved model weights
model.load_state_dict(torch.load('saved_weights.pt'))
model.eval()
# Example prediction
test_text = "This movie was absolutely fantastic and I loved every minute of it."
encoded_input = tokenizer(test_text, return_tensors='pt', max_length=250, padding=True, truncation=True)
input_ids = encoded_input['input_ids'].to(device)
attention_mask = encoded_input['attention_mask'].to(device)
with torch.no_grad():
output = model(input_ids, attention_mask)
prediction = torch.argmax(output, dim=1).item()
print(f"Predicted sentiment: {'Positive' if prediction == 1 else 'Negative'}")
本文探讨了训练大型语言模型 (LLM) 的世界及其对自然语言处理 (NLP) 的重大影响。讨论了预训练过程,其中 LLM 使用自我监督学习对大量未标记文本进行训练。我们还深入研究了微调,其中涉及针对特定任务和提示调整预先训练的模型,其中为模型提供上下文以生成相关输出。此外,我们还研究了不同的训练技术,例如特征提取、完整模型训练和基于适配器的训练。
大型语言模型已经彻底改变了 NLP,并继续推动各种应用程序的进步。通过掌握上述技术和代码实现,开发者可以更有效地构建基于 LLM 的智能应用。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online