文章目录
02极简LLM逻辑与PyTorch快速入门
极简LLM逻辑
提供一段伪代码,说明“对话”的基本逻辑:输入、输出。
不考虑机器学习、深度学习之类的技术,也不考虑结果是否合理、准确,仅仅为了说明LLM基本逻辑。
if __name__ == "__main__":
aq = InputAndAnalysisQuestion()
ga = Get_answer()
while True:
question = input('请输入:')
index, params = aq.query_question(question)
answers = ga.get_data(index, params)
print('答案:')
for ans in answers:
print(ans[0])
按照俗话解释以上伪代码流程:
- 1、获得对话输入。根据输入内容,进行查询检索;
- 2、上一步查询检索返回索引号、参数列表数据;
- 3、根据索引号、参数列表计算回答内容;
- 4、输出打印。
PyTorch环境安装(重要,不难)
在安装PyTorch前,请先确保python、cuda的版本。具体的版本对应关系参考pytorch官网给出的说明。
- 例如:
-
注意,上面描述的是pytorch的GPU环境的安装。pytorch也支持无GPU的CPU环境安装,具体步骤请自行查阅。但是还是建议上GPU吧,GPU是绕不过去的。
-
PyTorch历史版本,用于查阅与python、cuda的版本对照
地址:https://pytorch.org/get-started/previous-versions/
例如:
# CUDA 11.8
conda install pytorch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 pytorch-cuda=11.8 -c pytorch -c nvidia
# CUDA 12.1
conda install pytorch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 pytorch-cuda=12.1 -c pytorch -c nvidia
# CPU Only
conda install pytorch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 cpuonly -c pytorch
- torch版本查询
import torch
torch.__version__
- GPU/cuda环境判断与启用
# We move our tensor to the GPU if available
if torch.cuda.is_available():
tensor = tensor.to('cuda')
print(f"Device tensor is stored on: {tensor.device}")
PyTorch 主要概念
Tensors张量
张量是一种特殊的数据结构,与数组和矩阵非常相似。
在PyTorch中,使用张量来编码模型的输入、输出以及模型参数。张量类似于NumPy中的ndarrays(n维数组),但不同之处在于张量可以在GPU或其他硬件加速器上运行。
张量和NumPy数组经常可以共享相同的底层内存,从而消除了数据复制的需求。张量还针对自动微分进行了优化(在Autograd部分)
import torch
import numpy as np
张量常见的形式:scalar、vector、matrix、n-dimensinal
- 0:scalar
- 1:vector
- 2:matrix
- 3:n-dimensional
import torch
from torch import tensor
#scalar通常是一个数值
x = tensor(42.)
tensor(42.)
x.dim()
tensor(84.)
x.item()
#vector, 例如: [-5., 2., 0.],在深度学习中通常指特征,例如词向量特征
v = tensor( [1.5, -0.5, 3.0])
v.dim()
v.size()
torch.Size( [3] )
#Matrix, 一般计算的都是矩阵,通常都是多维的
M = tensor( [[1., 2.], [3., 4.]] )
M.matmul(M)
tensor([1., 0.]).matmul(M)
张量初始化
- 数据直接读取
data = [[1, 2],[3, 4]]
x_data = torch.tensor(data)
- 读取NumPy array
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
- 从另一个张量
x_ones = torch.ones_like(x_data) # retains the properties of x_data
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
print(f"Random Tensor: \n {x_rand} \n")
- 随机变量
shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
张量参数:shape、datatype、device
tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
张量运算
超过100种张量操作,包括算术运算、线性代数、矩阵操作(转置、索引、切片)、采样以及其他更多操作。
每一种操作都可以在GPU上运行(通常速度会比在CPU上更快)。
默认情况下,张量是在CPU上创建的。需要通过.to()方法显式地将张量移动到GPU(在确认GPU可用之后)。请注意,跨设备复制大型张量在时间和内存方面可能会产生较高的开销!
- 索引与切片(类似于numpy)
tensor = torch.ones(4, 4)
print(f"First row: {tensor[0]}")
print(f"First column: {tensor[:, 0]}")
print(f"Last column: {tensor[..., -1]}")
tensor[:,1] = 0
print(tensor)
- 连接张量 torch.cat
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
- 算术运算
# This computes the matrix multiplication between two tensors. y1, y2, y3 will have the same value
# ``tensor.T`` returns the transpose of a tensor
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
y3 = torch.rand_like(y1)
torch.matmul(tensor, tensor.T, out=y3)
# This computes the element-wise product. z1, z2, z3 will have the same value
z1 = tensor * tensor
z2 = tensor.mul(tensor)
z3 = torch.rand_like(tensor)
torch.mul(tensor, tensor, out=z3)
- 单元素张量
如果有一个单元素张量,例如通过聚合一个张量的所有值为一个值,可以使用item()方法将其转换为Python数值类型
agg = tensor.sum()
agg_item = agg.item()
print(agg_item, type(agg_item))
- 原地操作(In-place operations)
把结果直接存入操作数本身的运算称为原地操作。这类操作的名称后面通常会带有 _ 后缀。例如:x.copy_(y)、x.t_() 等操作会直接修改变量x自身的内容。
print(f"{tensor} \n")
tensor.add_(5)
print(tensor)
Datasets and DataLoaders 数据集与数据加载
处理数据样本的代码可能会变得混乱且难以维护;为了提高可读性和模块化程度,理想情况下希望数据集代码与模型训练代码解耦。
PyTorch提供了两个数据处理基本组件:
torch.utils.data.DataLoader 和 torch.utils.data.Dataset,既可使用预加载的数据集,也能使用自定义数据。
Dataset 类用于存储样本及其对应的标签,而 DataLoader 则将一个可迭代对象包装在 Dataset 外部,以便于轻松访问样本。
PyTorch领域库提供了一系列预加载的数据集(如FashionMNIST等),这些数据集都是torch.utils.data.Dataset的子类,并实现了针对特定数据的特定函数。可以利用它们快速原型化和基准测试您的模型。
- 数据加载
Fashion-MNIST是一个由Zalando提供的商品图片数据集,包含60,000个训练样本和10,000个测试样本。每个样本由一个28×28像素的灰度图像以及来自10种类别的关联标签组成。
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
- 迭代与可视化数据集
labels_map = {
0: "T-Shirt",
1: "Trouser",
2: "Pullover",
3: "Dress",
4: "Coat",
5: "Sandal",
6: "Shirt",
7: "Sneaker",
8: "Bag",
9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
sample_idx = torch.randint(len(training_data), size=(1,)).item()
img, label = training_data[sample_idx]
figure.add_subplot(rows, cols, i)
plt.title(labels_map[label])
plt.axis("off")
plt.imshow(img.squeeze(), cmap="gray")
plt.show()
- 从文件创建自定义数据集
import os
import pandas as pd
from torchvision.io import read_image
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
def __len__(self):
return len(self.img_labels)
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
- 使用DataLoader加载数据准备训练
数据集一次检索一个样本的特征和标签。
在训练模型时,我们通常希望以“小批量”的方式传递样本,每轮迭代时重新打乱数据以减少模型过拟合现象,并利用Python的多进程功能来加速数据获取。
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")
Transforms 转换
原始数据并不总是以机器学习算法所需的理想形式出现。因此,会采用转换技术对数据进行一些操作,使其适合训练过程。
所有TorchVision数据集都具有两个参数
——transform用于修改特征,target_transform用于修改标签
——这两个参数接受包含转换逻辑的可调用对象。
torchvision.transforms模块直接提供了几种常用的转换方法。
FashionMNIST数据集的特征是以PIL Image格式表示的,标签则是整数形式。为了训练,需要将特征转化为归一化的张量,并将标签转化为独热编码的张量。为此,将使用ToTensor和Lambda来进行这些转换操作。
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
)
Build Model 创建模型
神经网络由执行数据操作的层/模块组成。
PyTorch中的torch.nn命名空间提供了构建自定义神经网络所需的所有基本组件。
PyTorch中的每一个模块都是nn.Module的子类。一个神经网络本身就是一个模块,它包含了其他模块(即层)。这种嵌套结构使得构建和管理复杂的架构变得容易。
我们将构建一个神经网络来对FashionMNIST数据集中的图像进行分类。
import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
device = (
"cuda"
if torch.cuda.is_available()
else "mps"
if torch.backends.mps.is_available()
else "cpu"
)
print(f"Using {device} device")
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork().to(device)
print(model)
Automatic Differentiation 自动微分
在训练神经网络时,最常使用的算法是反向传播。该算法中,会根据损失函数相对于给定参数的梯度来调整参数(即模型权重)。
为了计算这些梯度,PyTorch 内置了一个名为 torch.autograd 的自动微分引擎。它可以支持任何计算图的梯度自动计算。
考虑一个最简单的单层神经网络,其包含输入x、参数w和b以及某个损失函数。在PyTorch中,可以如下方式定义这个网络:
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
loss.backward()
print(w.grad)
print(b.grad)
Optimization Loop 优化循环
有了模型和数据,接下来需要通过在数据上优化模型参数来训练、验证和测试我们的模型。
训练模型是一个迭代过程,在每次迭代中,模型都会对输出做出猜测,计算猜测的误差(损失),收集关于模型参数的误差梯度(正如我们在上一节中看到的那样),然后使用梯度下降法优化这些参数。
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork()
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
# Set the model to training mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.train()
for batch, (X, y) in enumerate(dataloader):
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
loss.backward()
optimizer.step()
optimizer.zero_grad()
if batch % 100 == 0:
loss, current = loss.item(), batch * batch_size + len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test_loop(dataloader, model, loss_fn):
# Set the model to evaluation mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.eval()
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
# Evaluating the model with torch.no_grad() ensures that no gradients are computed during test mode
# also serves to reduce unnecessary gradient computations and memory usage for tensors with requires_grad=True
with torch.no_grad():
for X, y in dataloader:
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
epochs = 10
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train_loop(train_dataloader, model, loss_fn, optimizer)
test_loop(test_dataloader, model, loss_fn)
print("Done!")
保存、加载、使用模型
import torch
import torchvision.models as models
model = models.vgg16(weights='IMAGENET1K_V1')
torch.save(model.state_dict(), 'model_weights.pth')
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()