Skip to content

使用pytorch理解神经网络

原创作者wenonly
发布时间
所属分类AI
标签列表pytorch, 神经网络

关于 Tensors

Tensors 是 PyTorch 中的基本数据结构,类似于多维数组或矩阵。它们是神经网络中进行计算和存储数据的核心元素。Tensors 可以在 CPU 或 GPU 上运行,支持自动微分,这使得它们非常适合深度学习任务。

以下是一些简单的 Tensor 示例:

  1. 创建一个一维 Tensor:

    python
    import torch
    x = torch.tensor([1, 2, 3, 4, 5])
    print(x)  # 输出: tensor([1, 2, 3, 4, 5])
  2. 创建一个二维 Tensor:

    python
    matrix = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    print(matrix)
    # 输出:
    # tensor([[1, 2, 3],
    #         [4, 5, 6],
    #         [7, 8, 9]])
  3. 创建一个随机 Tensor:

    python
    random_tensor = torch.rand(3, 3)
    print(random_tensor)
    # 输出类似于:
    # tensor([[0.1234, 0.5678, 0.9012],
    #         [0.3456, 0.7890, 0.1234],
    #         [0.5678, 0.9012, 0.3456]])
  4. 在 GPU 上创建 Tensor(如果可用):

    python
    if torch.cuda.is_available():
        gpu_tensor = torch.tensor([1, 2, 3]).cuda()
        print(gpu_tensor)  # 输出: tensor([1, 2, 3], device='cuda:0')
  5. 简单计算

    python
    a = torch.tensor([1, 2, 3])
    b = torch.tensor([4, 5, 6])
    print(a + b)  # 输出: tensor([5, 7, 9])
    print(a * b)  # 输出: tensor([4, 10, 18])

这些示例展示了 Tensor 的基本创建和使用方法。

自动梯度计算

自动梯度计算是 PyTorch 的核心功能之一,它允许我们自动计算复杂函数的梯度。这对于神经网络的训练至关重要,因为我们需要计算损失函数相对于模型参数的梯度来更新这些参数。

以下是一个简单的例子:

python
import torch

# 创建一个张量
x = torch.tensor(2.0, requires_grad=True)

# 定义一个函数
y = x**2 + 3*x + 1

# 计算梯度
y.backward()

# 输出梯度
print(x.grad)  # 输出: tensor(7.)

这个例子展示了如何使用 PyTorch 的自动微分功能来计算一个简单函数的梯度。

在这个例子中:

  1. x 代表输入变量:

    • x 是一个标量张量,初始值为 2.0
    • requires_grad=True 表示我们需要计算与 x 相关的梯度
  2. y 代表输出变量:

    • y 是一个依赖于 x 的函数: y = x^2 + 3x + 1
    • 这个函数可以看作是一个简单的神经网络
  3. grad 代表梯度:

    • x.grad 存储了 y 相对于 x 的梯度值
    • 在这个例子中,梯度值为 7,因为 dy/dx = 2x + 3,当 x=2 时,梯度为 2*2 + 3 = 7

在这个简单的例子中,我们已经计算出了梯度。在实际的神经网络训练中,下一步通常是使用这个梯度来更新参数。这个过程被称为梯度下降。

以下是如何使用计算出的梯度来更新 x 的值:

python
# 更新x的值
learning_rate = 0.1 # 学习率
x.data = x.data - learning_rate * x.grad # 更新x的值
print(x.data)  # 输出: tensor(1.3)

在这个例子中,我们使用梯度下降法来更新 x 的值。这个过程的最终目标是找到使函数 y = x^2 + 3x + 1 取得最小值时对应的 x 值。这里的 y 就相当于神经网络中的损失函数(loss function)。在实际的神经网络中,损失函数用来衡量模型预测值与真实值之间的差距。我们的目标是最小化这个损失函数,就像在这个例子中我们试图最小化 y 的值。

通过不断地计算梯度并更新 x,我们可以逐步接近这个最优解,这个过程在神经网络中就是参数优化的过程。学习率(learning rate)决定了每次更新时 x 变化的幅度,它影响着我们接近最优解的速度和精确度。在神经网络训练中,选择合适的学习率非常重要,它直接影响模型的收敛速度和最终性能。

在这个具体例子中,学习率设为 0.1,使得 x 的值从初始的 2.0 更新到了 1.3,朝着最优解方向移动了一步。这就相当于神经网络中的一次参数更新。在实际的神经网络训练中,我们会重复这个过程多次,直到损失函数收敛到一个满意的值。

完整用例
python
import torch

# 创建一个张量
x = torch.tensor(2.0, requires_grad=True)

# 定义学习率
learning_rate = 0.1

# 定义收敛阈值
epsilon = 1e-6 # 收敛阈值代表梯度的绝对值小于这个值时,我们认为优化过程已经收敛

# 迭代优化
for i in range(1000):  # 设置最大迭代次数
    # 清除之前的梯度
    x.grad = None

    # 定义函数
    y = x**2 + 3*x + 1

    # 计算梯度
    y.backward()

    # 打印当前迭代信息
    print(f"迭代 {i+1}: x = {x.data.item():.7f}, y = {y.item():.7f}, 梯度 = {x.grad.item():.7f}")

    # 检查是否收敛
    if abs(x.grad.item()) < epsilon:
        print(f"已收敛,共迭代 {i+1} 次")
        break

    # 更新x的值
    x.data = x.data - learning_rate * x.grad

# 输出最终结果
print(f"最终结果: x = {x.data.item():.7f}, y = {y.item():.7f}")

下面是输出结果:

output
迭代 1: x = 2.0000000, y = 11.0000000, 梯度 = 7.0000000
迭代 2: x = 1.3000000, y = 6.5899997, 梯度 = 5.5999999
迭代 3: x = 0.7399999, y = 3.7675996, 梯度 = 4.4800000
迭代 4: x = 0.2919999, y = 1.9612638, 梯度 = 3.5839999
迭代 5: x = -0.0664001, y = 0.8052088, 梯度 = 2.8671999
迭代 6: x = -0.3531201, y = 0.0653336, 梯度 = 2.2937598
迭代 7: x = -0.5824960, y = -0.4081864, 梯度 = 1.8350079
迭代 8: x = -0.7659968, y = -0.7112392, 梯度 = 1.4680064
迭代 9: x = -0.9127975, y = -0.9051931, 梯度 = 1.1744051
迭代 10: x = -1.0302379, y = -1.0293236, 梯度 = 0.9395242
迭代 11: x = -1.1241903, y = -1.1087670, 梯度 = 0.7516193
迭代 12: x = -1.1993523, y = -1.1596110, 梯度 = 0.6012955
迭代 13: x = -1.2594818, y = -1.1921508, 梯度 = 0.4810364
迭代 14: x = -1.3075855, y = -1.2129767, 梯度 = 0.3848290
迭代 15: x = -1.3460684, y = -1.2263050, 梯度 = 0.3078632
迭代 16: x = -1.3768547, y = -1.2348351, 梯度 = 0.2462907
迭代 17: x = -1.4014838, y = -1.2402949, 梯度 = 0.1970325
迭代 18: x = -1.4211870, y = -1.2437887, 梯度 = 0.1576259
迭代 19: x = -1.4369496, y = -1.2460246, 梯度 = 0.1261008
迭代 20: x = -1.4495597, y = -1.2474558, 梯度 = 0.1008806
迭代 21: x = -1.4596478, y = -1.2483718, 梯度 = 0.0807045
迭代 22: x = -1.4677182, y = -1.2489581, 梯度 = 0.0645635
迭代 23: x = -1.4741746, y = -1.2493331, 梯度 = 0.0516508
迭代 24: x = -1.4793397, y = -1.2495732, 梯度 = 0.0413206
迭代 25: x = -1.4834718, y = -1.2497268, 梯度 = 0.0330565
迭代 26: x = -1.4867774, y = -1.2498252, 梯度 = 0.0264452
迭代 27: x = -1.4894220, y = -1.2498882, 梯度 = 0.0211561
迭代 28: x = -1.4915376, y = -1.2499285, 梯度 = 0.0169249
迭代 29: x = -1.4932301, y = -1.2499545, 梯度 = 0.0135398
迭代 30: x = -1.4945841, y = -1.2499707, 梯度 = 0.0108318
迭代 31: x = -1.4956672, y = -1.2499809, 梯度 = 0.0086656
迭代 32: x = -1.4965338, y = -1.2499878, 梯度 = 0.0069325
迭代 33: x = -1.4972270, y = -1.2499926, 梯度 = 0.0055461
迭代 34: x = -1.4977815, y = -1.2499948, 梯度 = 0.0044370
迭代 35: x = -1.4982252, y = -1.2499969, 梯度 = 0.0035496
迭代 36: x = -1.4985802, y = -1.2499983, 梯度 = 0.0028396
迭代 37: x = -1.4988642, y = -1.2499988, 梯度 = 0.0022717
迭代 38: x = -1.4990914, y = -1.2499995, 梯度 = 0.0018172
迭代 39: x = -1.4992731, y = -1.2499993, 梯度 = 0.0014539
迭代 40: x = -1.4994185, y = -1.2500000, 梯度 = 0.0011630
迭代 41: x = -1.4995348, y = -1.2500000, 梯度 = 0.0009303
迭代 42: x = -1.4996278, y = -1.2499995, 梯度 = 0.0007443
迭代 43: x = -1.4997022, y = -1.2499998, 梯度 = 0.0005956
迭代 44: x = -1.4997618, y = -1.2500002, 梯度 = 0.0004764
迭代 45: x = -1.4998095, y = -1.2500002, 梯度 = 0.0003810
迭代 46: x = -1.4998477, y = -1.2500002, 梯度 = 0.0003047
迭代 47: x = -1.4998782, y = -1.2500002, 梯度 = 0.0002437
迭代 48: x = -1.4999025, y = -1.2499998, 梯度 = 0.0001950
迭代 49: x = -1.4999220, y = -1.2500002, 梯度 = 0.0001559
迭代 50: x = -1.4999377, y = -1.2500000, 梯度 = 0.0001247
迭代 51: x = -1.4999502, y = -1.2499998, 梯度 = 0.0000997
迭代 52: x = -1.4999602, y = -1.2500002, 梯度 = 0.0000796
迭代 53: x = -1.4999682, y = -1.2500000, 梯度 = 0.0000637
迭代 54: x = -1.4999745, y = -1.2500002, 梯度 = 0.0000510
迭代 55: x = -1.4999796, y = -1.2500000, 梯度 = 0.0000408
迭代 56: x = -1.4999837, y = -1.2499998, 梯度 = 0.0000327
迭代 57: x = -1.4999869, y = -1.2500002, 梯度 = 0.0000262
迭代 58: x = -1.4999895, y = -1.2500000, 梯度 = 0.0000210
迭代 59: x = -1.4999917, y = -1.2500002, 梯度 = 0.0000167
迭代 60: x = -1.4999933, y = -1.2500000, 梯度 = 0.0000134
迭代 61: x = -1.4999946, y = -1.2499998, 梯度 = 0.0000107
迭代 62: x = -1.4999957, y = -1.2500000, 梯度 = 0.0000086
迭代 63: x = -1.4999965, y = -1.2499998, 梯度 = 0.0000069
迭代 64: x = -1.4999973, y = -1.2500000, 梯度 = 0.0000055
迭代 65: x = -1.4999979, y = -1.2499998, 梯度 = 0.0000043
迭代 66: x = -1.4999983, y = -1.2500002, 梯度 = 0.0000033
迭代 67: x = -1.4999987, y = -1.2500000, 梯度 = 0.0000026
迭代 68: x = -1.4999989, y = -1.2499998, 梯度 = 0.0000021
迭代 69: x = -1.4999992, y = -1.2500000, 梯度 = 0.0000017
迭代 70: x = -1.4999993, y = -1.2500002, 梯度 = 0.0000014
迭代 71: x = -1.4999994, y = -1.2499998, 梯度 = 0.0000012
迭代 72: x = -1.4999995, y = -1.2500000, 梯度 = 0.0000010
已收敛,共迭代 72 次
最终结果: x = -1.4999995, y = -1.2500000

神经网络

神经网络是一种模拟人脑神经元工作方式的计算模型。它由多个层组成,每层包含多个神经元,每个神经元接收输入并产生输出。神经网络通过调整神经元之间的连接权重来学习输入数据的模式和特征。

以下是一个简单的神经网络示例:

python
import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 5x5 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

这个神经网络示例是一个简单的卷积神经网络 (CNN),通常用于图像分类任务。让我们逐步解析这个网络结构:

  1. 网络结构:

    • 输入层: 接受单通道图像(如灰度图)
    • 两个卷积层 (conv1 和 conv2)
    • 三个全连接层 (fc1, fc2, fc3)
  2. 卷积层:

    • conv1: 将 1 通道输入转换为 6 通道输出,使用 5x5 的卷积核
    • conv2: 将 6 通道输入转换为 16 通道输出,同样使用 5x5 的卷积核
  3. 全连接层:

    • fc1: 输入 1655=400 个特征,输出 120 个特征
    • fc2: 120 个特征输入,84 个特征输出
    • fc3: 84 个特征输入,10 个特征输出(可能对应 10 个分类)
  4. 前向传播 (forward 方法):

    • 对 conv1 的输出应用 ReLU 激活函数和 2x2 最大池化
    • 对 conv2 的输出同样应用 ReLU 和 2x2 最大池化
    • 将特征图展平
    • 通过三个全连接层,前两个使用 ReLU 激活函数

这个网络结构类似于经典的 LeNet-5 架构,适用于简单的图像分类任务。它通过卷积层提取图像特征,然后用全连接层进行分类。这种结构能够有效地学习图像的空间层次特征,是深度学习在计算机视觉领域的基础模型之一。

接下来,我们将使用 PyTorch 的自动梯度计算功能来计算损失,并更新模型参数。

  1. 随机生成一个输入
python
import torch

# 创建一个随机输入张量
input_tensor = torch.randn(1, 1, 32, 32)  # 1个样本,1个通道,32x32的图像
  1. 前向传播
python
# 创建模型实例
net = Net()

# 前向传播
output = net(input_tensor)
  1. 计算损失
python
# 创建一个随机标签张量,这是我们希望模型预测的正确标签
target_tensor = torch.randint(0, 10, (1,))  # 假设是10类分类问题

# 计算损失
# criterion 是损失函数,通常在模型定义之后、训练循环之前定义
# 例如,对于分类问题,我们可以使用交叉熵损失
criterion = nn.CrossEntropyLoss()

# 然后计算损失
loss = criterion(output, target_tensor)
  1. 反向传播和参数更新
python
# 将所有参数的梯度缓存清零
net.zero_grad()

# 反向传播,这一步将计算损失相对于所有可训练参数的梯度
loss.backward()

# 更新参数,这是使用梯度下降法更新参数,实际的更新过程在优化器(optimizer)中完成
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

自此,我们完成了一个完整的训练周期。在实际应用中,我们需要重复这个过程多次,直到损失函数收敛到一个满意的值,或者达到预定的训练轮数。此时记录下模型参数,就得到了一个训练好的模型。