Files
2024-06-10 17:00:23 +08:00

30 KiB

附件A:PyTorch的介绍(第一部分)

A.1 什么是PyTorch

In [ ]:
import torch
# 显示PyTorch的版本
print(torch.__version__)
2.0.1
In [ ]:
# 显示PyTorch是否是GPU版本,False表示CPU版本,True表示GPU版本
print(torch.cuda.is_available())
False

A.2 向量

A.2.1 标量、向量、矩阵和张量

In [3]:
import torch
import numpy as np

# 用Python整数创建一个0维张量
tensor0d = torch.tensor(1)

# 用Python列表创建一个1维张量(向量)
tensor1d = torch.tensor([1, 2, 3])

# 用Python列表创建一个2维张量(向量)
tensor2d = torch.tensor([[1, 2], [3, 4]])

# 用嵌套的Python列表创建一个3维张量
tensor3d_1 = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])

# 从NumPy数组创建一个3维张量
ary3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
tensor3d_2 = torch.tensor(ary3d)  # 复制NumPy数组
tensor3d_3 = torch.from_numpy(ary3d)  # 与NumPy数组共享内存
In [4]:
ary3d[0, 0, 0] = 999
print(tensor3d_2) # 保持不变
tensor([[[1, 2],
         [3, 4]],

        [[5, 6],
         [7, 8]]])
In [5]:
print(tensor3d_3) # 由于内存共享需要改变
tensor([[[999,   2],
         [  3,   4]],

        [[  5,   6],
         [  7,   8]]])

A.2.2 向量的数据类型

In [6]:
tensor1d = torch.tensor([1, 2, 3])
print(tensor1d.dtype)
torch.int64
In [7]:
floatvec = torch.tensor([1.0, 2.0, 3.0])
print(floatvec.dtype)
torch.float32
In [8]:
floatvec = tensor1d.to(torch.float32)
print(floatvec.dtype)
torch.float32

A.2.3 PyTorch中常见的张量操作

In [9]:
tensor2d = torch.tensor([[1, 2, 3], [4, 5, 6]])
tensor2d
Out [9]:
tensor([[1, 2, 3],
        [4, 5, 6]])
In [10]:
tensor2d.shape # 张量形状
Out [10]:
torch.Size([2, 3])
In [11]:
tensor2d.reshape(3, 2) # 修改形状
Out [11]:
tensor([[1, 2],
        [3, 4],
        [5, 6]])
In [12]:
tensor2d.view(3, 2) # 查看张量
Out [12]:
tensor([[1, 2],
        [3, 4],
        [5, 6]])
In [13]:
tensor2d.T # 转置张量
Out [13]:
tensor([[1, 4],
        [2, 5],
        [3, 6]])
In [14]:
tensor2d.matmul(tensor2d.T) # 张量乘法:tensor2d与其转置相乘
Out [14]:
tensor([[14, 32],
        [32, 77]])
In [15]:
tensor2d @ tensor2d.T # 张量乘法的另一种实现方式:tensor2d与其转置相乘
Out [15]:
tensor([[14, 32],
        [32, 77]])

A.3 把模型作为计算图

In [16]:
import torch.nn.functional as F

y = torch.tensor([1.0])  # 真实样本
x1 = torch.tensor([1.1]) # 输入特征
w1 = torch.tensor([2.2]) # 权重变量
b = torch.tensor([0.0])  # 偏置单元

z = x1 * w1 + b          # 网络输入
a = torch.sigmoid(z)     # 激活函数 & 输出

loss = F.binary_cross_entropy(a, y)
print(loss)
tensor(0.0852)

A.4 自动求导

In [17]:
import torch.nn.functional as F
from torch.autograd import grad

y = torch.tensor([1.0])
x1 = torch.tensor([1.1])
w1 = torch.tensor([2.2], requires_grad=True)
b = torch.tensor([0.0], requires_grad=True)

z = x1 * w1 + b 
a = torch.sigmoid(z)

loss = F.binary_cross_entropy(a, y)

grad_L_w1 = grad(loss, w1, retain_graph=True)
grad_L_b = grad(loss, b, retain_graph=True)

print(grad_L_w1)
print(grad_L_b)
(tensor([-0.0898]),)
(tensor([-0.0817]),)
In [18]:
loss.backward()# 反向传播

print(w1.grad)
print(b.grad)
tensor([-0.0898])
tensor([-0.0817])

A.5 多层神经网络的实现

In [19]:
class NeuralNetwork(torch.nn.Module):
    def __init__(self, num_inputs, num_outputs):
        super().__init__()

        self.layers = torch.nn.Sequential(
                
            # 第一个隐藏层
            torch.nn.Linear(num_inputs, 30),
            torch.nn.ReLU(),

            # 第二个隐藏层
            torch.nn.Linear(30, 20),
            torch.nn.ReLU(),

            # 输出层
            torch.nn.Linear(20, num_outputs),
        )

    def forward(self, x):
        logits = self.layers(x)
        return logits
In [20]:
model = NeuralNetwork(50, 3)
In [21]:
print(model)
NeuralNetwork(
  (layers): Sequential(
    (0): Linear(in_features=50, out_features=30, bias=True)
    (1): ReLU()
    (2): Linear(in_features=30, out_features=20, bias=True)
    (3): ReLU()
    (4): Linear(in_features=20, out_features=3, bias=True)
  )
)
In [22]:
num_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print("Total number of trainable model parameters:", num_params)# 打印训练模型的参数
Total number of trainable model parameters: 2213
In [23]:
print(model.layers[0].weight) # 打印神经网络模型的第一层的权重
Parameter containing:
tensor([[-0.0064,  0.0004, -0.0903,  ..., -0.1316,  0.0910,  0.0363],
        [ 0.1354,  0.1124, -0.0476,  ...,  0.0578,  0.1014,  0.0008],
        [ 0.0975, -0.0478,  0.0298,  ...,  0.0416,  0.0849,  0.1314],
        ...,
        [ 0.0118,  0.0240,  0.0420,  ..., -0.1305, -0.0517, -0.0826],
        [-0.0323,  0.1073,  0.0215,  ..., -0.1264, -0.1100,  0.1232],
        [ 0.0861,  0.0403, -0.0545,  ...,  0.1352,  0.0817, -0.0938]],
       requires_grad=True)
In [24]:
# 设置随机数种子,以确保可复现性
torch.manual_seed(123)

# 假设 NeuralNetwork 是一个神经网络类,且其构造函数接受两个参数,分别为输入特征的维度和输出特征的维度
model = NeuralNetwork(50, 3)

# 打印神经网络模型的第一层的权重
print(model.layers[0].weight)
Parameter containing:
tensor([[-0.0577,  0.0047, -0.0702,  ...,  0.0222,  0.1260,  0.0865],
        [ 0.0502,  0.0307,  0.0333,  ...,  0.0951,  0.1134, -0.0297],
        [ 0.1077, -0.1108,  0.0122,  ...,  0.0108, -0.1049, -0.1063],
        ...,
        [-0.0787,  0.1259,  0.0803,  ...,  0.1218,  0.1303, -0.1351],
        [ 0.1359,  0.0175, -0.0673,  ...,  0.0674,  0.0676,  0.1058],
        [ 0.0790,  0.1343, -0.0293,  ...,  0.0344, -0.0971, -0.0509]],
       requires_grad=True)
In [25]:
# 打印神经网络模型的第一层权重的形状
print(model.layers[0].weight.shape)
torch.Size([30, 50])
In [26]:
# 设置随机数种子,以确保可复现性
torch.manual_seed(123)

# 模型输入特征的维度为 50
X = torch.rand((1, 50))

# 使用模型进行前向传播计算输出
out = model(X)

# 打印输出结果
print(out)
tensor([[-0.1262,  0.1080, -0.1792]], grad_fn=<AddmmBackward0>)
In [27]:
# 使用 torch.no_grad() 上下文管理器,以便在推断时不计算梯度
with torch.no_grad():
    out = model(X)
print(out)
tensor([[-0.1262,  0.1080, -0.1792]])
In [28]:
with torch.no_grad():
    out = torch.softmax(model(X), dim=1)
print(out)
tensor([[0.3113, 0.3934, 0.2952]])

A.6 建立高效的数据加载器

In [29]:
# 定义输入特征张量 X_train
X_train = torch.tensor([
    [-1.2, 3.1],
    [-0.9, 2.9],
    [-0.5, 2.6],
    [2.3, -1.1],
    [2.7, -1.5]
])

# 定义对应的标签张量 y_train
y_train = torch.tensor([0, 0, 0, 1, 1])
In [30]:
X_test = torch.tensor([
    [-0.8, 2.8],
    [2.6, -1.6],
])

y_test = torch.tensor([0, 1])
In [31]:
from torch.utils.data import Dataset


class ToyDataset(Dataset):
    # 初始化 ToyDataset 类
    def __init__(self, X, y):
        self.features = X
        self.labels = y
    #  获取指定索引的数据
    def __getitem__(self, index):
        one_x = self.features[index]
        one_y = self.labels[index]        
        return one_x, one_y
    # 获取数据集的长度
    def __len__(self):
        return self.labels.shape[0]
# 创建训练数据集和测试数据集实例
train_ds = ToyDataset(X_train, y_train)
test_ds = ToyDataset(X_test, y_test)
In [32]:
len(train_ds)
Out [32]:
5
In [33]:
from torch.utils.data import DataLoader

torch.manual_seed(123)

# 创建训练数据加载器 train_loader
# dataset 参数传入了您定义的 ToyDataset 类的实例 train_ds
# batch_size 参数指定了每个批次包含的样本数量
# shuffle 参数指定是否在每个 epoch 之前对数据进行洗牌
# num_workers 参数指定用于数据加载的子进程数量
train_loader = DataLoader(
    dataset=train_ds,
    batch_size=2,
    shuffle=True,
    num_workers=0
)
In [34]:
test_ds = ToyDataset(X_test, y_test)

# 创建测试数据加载器 test_loader
# dataset 参数传入了您定义的 ToyDataset 类的实例 test_ds
# batch_size 参数指定了每个批次包含的样本数量
# shuffle 参数指定是否在每个 epoch 之前对数据进行洗牌,这里设为 False 表示不洗牌
# num_workers 参数指定用于数据加载的子进程数量
test_loader = DataLoader(
    dataset=test_ds,
    batch_size=2,
    shuffle=False,
    num_workers=0
)
In [35]:
# 迭代训练数据加载器 train_loader
for idx, (x, y) in enumerate(train_loader):
    # 打印每个批次的索引、输入特征和对应的标签
    print(f"Batch {idx+1}:", x, y)
Batch 1: tensor([[ 2.3000, -1.1000],
        [-0.9000,  2.9000]]) tensor([1, 0])
Batch 2: tensor([[-1.2000,  3.1000],
        [-0.5000,  2.6000]]) tensor([0, 0])
Batch 3: tensor([[ 2.7000, -1.5000]]) tensor([1])
In [36]:
train_loader = DataLoader(
    dataset=train_ds,
    batch_size=2,
    shuffle=True,
    num_workers=0,
    drop_last=True
)
In [ ]:
for idx, (x, y) in enumerate(train_loader):
    print(f"Batch {idx+1}:", x, y)

A.7 一个示例训练轮次

In [38]:
import torch.nn.functional as F


torch.manual_seed(123)
model = NeuralNetwork(num_inputs=2, num_outputs=2)
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)

num_epochs = 3

for epoch in range(num_epochs):
    
    model.train()
    for batch_idx, (features, labels) in enumerate(train_loader):

        logits = model(features)
        
        loss = F.cross_entropy(logits, labels) # 损失函数
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    
        ### 日志
        print(f"Epoch: {epoch+1:03d}/{num_epochs:03d}"
              f" | Batch {batch_idx:03d}/{len(train_loader):03d}"
              f" | Train/Val Loss: {loss:.2f}")

    model.eval()
    # 可选的模型评估指标
Epoch: 001/003 | Batch 000/002 | Train/Val Loss: 0.75
Epoch: 001/003 | Batch 001/002 | Train/Val Loss: 0.65
Epoch: 002/003 | Batch 000/002 | Train/Val Loss: 0.44
Epoch: 002/003 | Batch 001/002 | Train/Val Loss: 0.13
Epoch: 003/003 | Batch 000/002 | Train/Val Loss: 0.03
Epoch: 003/003 | Batch 001/002 | Train/Val Loss: 0.00
In [39]:
model.eval()

with torch.no_grad():
    outputs = model(X_train)

print(outputs)
tensor([[ 2.8569, -4.1618],
        [ 2.5382, -3.7548],
        [ 2.0944, -3.1820],
        [-1.4814,  1.4816],
        [-1.7176,  1.7342]])
In [40]:
# 设置 PyTorch 的打印选项,以关闭科学计数法
torch.set_printoptions(sci_mode=False)

# 假设 outputs 是模型的输出张量

# 对模型的输出进行 softmax 操作,计算类别概率
probas = torch.softmax(outputs, dim=1)
print(probas)

# 获取模型的预测结果,即具有最大概率的类别
predictions = torch.argmax(outputs, dim=1)
print(predictions)
tensor([[    0.9991,     0.0009],
        [    0.9982,     0.0018],
        [    0.9949,     0.0051],
        [    0.0491,     0.9509],
        [    0.0307,     0.9693]])
tensor([0, 0, 0, 1, 1])
In [41]:
# 使用 torch.argmax() 函数沿着 dim=1 维度获取每个样本最大值的索引,即模型的预测结果
predictions = torch.argmax(outputs, dim=1)
print(predictions)
tensor([0, 0, 0, 1, 1])
In [42]:
predictions == y_train
Out [42]:
tensor([True, True, True, True, True])
In [43]:
torch.sum(predictions == y_train)
Out [43]:
tensor(5)
In [44]:
def compute_accuracy(model, dataloader):
    """
    计算模型在给定数据加载器上的准确率。

    参数:
        model (torch.nn.Module): 待评估的模型。
        dataloader (torch.utils.data.DataLoader): 包含输入数据的数据加载器。

    返回:
        float: 准确率值。
    """
    # 将模型设为评估模式
    model = model.eval()
    correct = 0.0
    total_examples = 0
    
    # 遍历数据加载器
    for idx, (features, labels) in enumerate(dataloader):
        
        # 使用 no_grad 上下文,以便不跟踪梯度
        with torch.no_grad():
            # 使用模型进行前向传播获取预测结果
            logits = model(features)
        
        # 获取预测结果并计算正确预测的数量
        predictions = torch.argmax(logits, dim=1)
        compare = labels == predictions
        correct += torch.sum(compare)
        total_examples += len(compare)

    # 计算并返回准确率
    return (correct / total_examples).item()
In [45]:
compute_accuracy(model, train_loader)
Out [45]:
1.0
In [46]:
compute_accuracy(model, test_loader)
Out [46]:
1.0

A.8 保存并加载模型

In [47]:
torch.save(model.state_dict(), "model.pth")
In [48]:
model = NeuralNetwork(2, 2) # 需要与原始模型完全匹配
model.load_state_dict(torch.load("model.pth"))
Out [48]:
<All keys matched successfully>

A.9 使用GPU来优化训练性能

A.9.1 在GPU上进行 PyTorch 的运算

A.9.2 单个GPU的训练

A.9.3 多GPU的训练

In [ ]: