pytorch 入门(一)

本文为小白入门Pytorch内部限免文章
参考本文所写记录性文章,请在文章开头注明以下内容,复制粘贴即可

⬇️基础版模板⬇️
我正在和鲸社区参加“小白入门Pytorch” https://www.heywhale.com/home/activity/detail/64e2ce39e585b8f50bf6c2e7 ,以下是我的学习笔记:

  • 本文为小白入门Pytorch中的学习记录博客
  • 参考文章:【小白入门Pytorch】教案一
  • 原作者:K同学啊

此次学习是在和鲸已配置完成的电脑上实现的(教案一和鲸提供的虚拟环境是cpu版,如要进行GPU加速运行还是有点麻烦),实际学习运用中同样需要本地进行配置,可以参考我CSDN的pytorch配置文档(包括cuda,cuDNN的配置):https://blog.csdn.net/qq_33489955/article/details/132890434?spm=1001.2014.3001.5501

目录

  • 一、张量的概念和表示
    • 1. 张量的维度和形状
    • 2. 张量的操作
    • 3. 张量的GPU加速
    • 4. 将张量转移到CPU
  • 二、创建张量
    • 1. 创建空张量
    • 2. 创建随机张量
    • 3. 创建全零张量
    • 4. 创建全1张量
    • 5. 从数据创建张量
    • 6. 创建具有特定数据类型的张量
  • 三、张量操作和运算
    • 1. 张量运算
    • 2. 张量形状变换
      • 示例:
    • 3. 张量索引和切片
    • 4. 获取张量中的数值
    • 5. 张量转化为数组
    • 6. 张量转化为列表
  • 四、自动求导原理
    • 1. 计算图
    • 2. 梯度
    • 3. 梯度计算过程
    • 4. 梯度计算示例
  • 五、反向传播
    • 1. 反向传播原理
    • 2. 反向传播示例
  • 闯关练习

PyTorch是一个开源的深度学习框架,由Facebook的AI研究团队于2016年发布。它提供了丰富的工具和库,用于构建、训练和部署深度神经网络模型。PyTorch采用动态计算图的设计,使得构建和调试模型变得直观而灵活。它的自动求导功能也使得实现反向传播算法变得简单,是训练深度学习模型的关键组件。接下来,让我们一起了解一下Pytorch的基础语法,为后续Pytorch实战打好基础,本节主要分为如下两个主要内容:

  • 创建张量:学会如何创建空张量、随机张量、全零张量以及使用已有数据创建张量。
  • 张量的操作:学会如何对张量进行操作,包括张量的加减乘除运算、shape变换、转置、索引与切片以及数组、列表与张量之间的互转。
  • 自动求导原理与实现:自动求导是一个关键的功能,它允许我们自动计算梯度,从而进行反向传播和优化。
  • 反向传播:反向传播是深度学习中的关键概念之一,它是训练神经网络模型的基础。

一、张量的概念和表示

在PyTorch中,张量(tensor)是最基本的数据结构,它类似于多维数组。张量在深度学习中扮演着核心的角色,用于表示和处理数据以及进行数值计算。

1. 张量的维度和形状

张量可以具有不同的维度和形状。在PyTorch中,我们可以使用torch.Tensor类来创建张量。以下是一些常见的张量及其对应的维度和形状的示例:

  • 标量(0维张量):一个单独的数值。例如,tensor = torch.tensor(5)表示一个标量,其维度为0,形状为空。
  • 向量(1维张量):具有一个轴的张量。例如,tensor = torch.tensor([1, 2, 3, 4, 5])表示一个向量,其维度为1,形状为(5,)。
  • 矩阵(2维张量):具有两个轴的张量。例如,tensor = torch.tensor([[1, 2, 3], [4, 5, 6]])表示一个矩阵,其维度为2,形状为(2, 3)。
  • 3维张量:具有三个轴的张量。例如,tensor = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])表示一个3维张量,其维度为3,形状为(2, 2, 2)。

您可以使用torch.Tensor类的构造函数以及其他一些创建张量的函数来创建具有不同维度和形状的张量。下面使用代码进行介绍

# 导入PyTorch库
import torch 

# 查看pytorch版本
print(torch.__version__) 
1.1.0

'1.1.0'
#标量:0维张量,形状为空
tensor = torch.tensor(5)

print(tensor.shape)
torch.Size([])

# 创建一个PyTorch张量(tensor),包含数字1到5

# 向量:1维张量,形状为(5,)
tensor = torch.tensor([1, 2, 3, 4, 5])

# 打印张量的形状(shape),即张量中元素的维度信息
print(tensor.shape)
torch.Size([5])

2. 张量的操作

PyTorch提供了许多用于操作张量的函数和方法。以下是一些常用的张量操作:

  • 创建张量:可以使用torch.tensor函数从Python列表或NumPy数组创建张量,也可以使用其他创建函数,如torch.zerostorch.onestorch.rand等创建具有特定形状和初始值的张量。
# 矩阵:二维张量,形状为(N,N),其中N大于0

# 创建一个形状为(2, 3)的零张量
zeros_tensor = torch.zeros((2, 3))

print(zeros_tensor)
tensor([[0., 0., 0.],
        [0., 0., 0.]])
# 创建一个形状为(3, 3)的随机张量
rand_tensor = torch.rand((3, 3))

print(rand_tensor)
tensor([[0.4795, 0.0323, 0.6320],
        [0.1924, 0.2453, 0.2395],
        [0.1752, 0.1683, 0.3141]])
  • 张量操作:可以对张量执行各种数学运算和操作,如加法、减法、乘法、除法、矩阵乘法等。这些操作可以使用算术运算符或对应的函数来执行。

两个张量的乘法不是矩阵乘法(通常称为点积),而是元素乘法(也称为Hadamard乘法)。这意味着两个张量中的对应元素相乘。

例如,给定两个张量:
t e n s o r 1 = [ a , b , c ] {tensor1} = [a, b, c] tensor1=[a,b,c]
t e n s o r 2 = [ d , e , f ] {tensor2} = [d, e, f] tensor2=[d,e,f]

元素乘法的结果是:
[ a ∗ d , b ∗ e , c ∗ f ] [a * d, b* e, c * f] [ad,be,cf]

import torch

# 创建两个张量
tensor1 = torch.tensor([1, 2, 3])
tensor2 = torch.tensor([4, 5, 6])

# 加法操作
result = tensor1 + tensor2
print(result)  # 输出: tensor([5, 7, 9])

# 乘法操作
result = tensor1 * tensor2
print(result)  # 输出: tensor([4, 10, 18])
tensor([5, 7, 9])
tensor([ 4, 10, 18])
  • 张量索引和切片:可以使用索引和切片操作来访问张量中的特定元素或子张量。
import torch

# 创建一个形状为(3, 3)的张量
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 访问第一个元素
print(tensor[0, 0])  # 输出: tensor(1)

# 切片操作
print(tensor[:, 1])  # 输出: tensor([2, 5, 8])
tensor(1)
tensor([2, 5, 8])

3. 张量的GPU加速

在PyTorch中,可以将张量存储在CPU或GPU上。GPU加速可以显著提高深度学习模型的计算性能。要将张量移动到GPU上,可以使用.to方法。

import torch

# 创建一个形状为(2, 2)的张量并将其移动到GPU上
tensor = torch.tensor([[1, 2], [3, 4]])
tensor = tensor.to('cuda')

如果您的系统具有可用的GPU,并且已正确配置PyTorch以使用GPU加速,那么上述代码将使张量存储在GPU上。否则,它将继续在CPU上运行。

4. 将张量转移到CPU

注意:如果张量已经在CPU上,使用 tensor.to('cpu') 并不会在内存中创建一个新的张量的拷贝。它会返回原始张量的一个引用。这是PyTorch的一个优化,以避免不必要的数据复制。

所以,如果 tensor 默认已经在CPU上,执行 tensor.to('cpu') 后,你仍然会得到与原始张量相同的对象。

# 创建一个形状为(2, 2)的张量并将其移动到CPU上
tensor = torch.tensor([[1,2],[3,4]])
tensor = tensor.to('cpu')

tensor
tensor([[1, 2],
        [3, 4]])

这是关于"张量的概念和表示"部分的一个简要介绍。在接下来的教程中,我们将深入探讨更多有关PyTorch的功能和操作。

二、创建张量

在PyTorch中,张量(Tensor)是一种多维数组,类似于NumPy的ndarray对象。张量可以用来表示数据和进行各种数学运算。本节将介绍如何创建不同类型的张量。

1. 创建空张量

要创建一个空张量,可以使用torch.empty()函数。空张量的元素值将不会被初始化,它们的内容是未知的。

torch.empty()torch.rand() 是两个用于创建张量的不同函数,它们的主要区别如下:

  1. 初始化方式:

    • torch.empty(size): 创建一个未初始化的张量。这意味着张量的内容是不确定的,并且取决于内存状态。使用这个函数是因为它创建张量的速度非常快,但您通常需要确保之后对其进行初始化。
    • torch.rand(size): 创建一个张量,其中的元素是从均匀分布 ([0, 1)) 中随机抽取的。这意味着每个元素的值都在 0(包括)到 1(不包括)之间。
  2. 用途:

    • torch.empty(): 当您知道稍后会填充或修改张量,并且希望尽可能快地创建它时,可以使用这个函数。
    • torch.rand(): 当您需要随机初始化张量的值时使用。

总的来说,torch.empty() 提供了一个快速创建张量的方法,但它不会进行初始化,而 torch.rand() 创建并随机初始化张量。

import torch

# 创建一个未初始化的 5x3 张量
x = torch.empty(5, 3)
print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

2. 创建随机张量

要创建一个随机初始化的张量,可以使用torch.rand()函数。这将生成一个在[0, 1)范围内均匀分布的随机张量。

import torch

# 创建一个形状为 2x2 的随机张量
x = torch.rand(2, 2)
print(x)
tensor([[0.2879, 0.6106],
        [0.6862, 0.7361]])

3. 创建全零张量

要创建一个全零的张量,可以使用torch.zeros()函数。

import torch

# 创建一个形状为 3x3 的全零张量
x = torch.zeros(3, 3)
print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

4. 创建全1张量

import torch

# 创建一个形状为 3x3 的全零张量
x = torch.ones(3, 3)
print(x)
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])

5. 从数据创建张量

你还可以从现有的数据创建张量。可以使用torch.tensor()函数从Python列表或NumPy数组创建张量。

import torch
import numpy as np
import pandas as pd

# 从Python列表创建张量
data_list = [1, 2, 3, 4, 5]
x = torch.tensor(data_list)
print(x)

# 从NumPy数组创建张量
data_array = np.array([6, 7, 8, 9, 10])
x = torch.tensor(data_array)
print(x)

# 从dataframe中创建张量
data = {
    'A': [1, 2, 3, 4, 5],
    'B': [6, 7, 8, 9, 10]
}
df = pd.DataFrame(data)
# df['A'].values 将返回列 'A' 的值作为一个NumPy数组
# 然后 torch.tensor() 将该数组转换为一个张量。
tensor_A = torch.tensor(df['A'].values,dtype = torch.float32) # 为了确保数据类型的一致性,最好明确指定数据类型
print(tensor_A)
tensor([1, 2, 3, 4, 5])
tensor([ 6,  7,  8,  9, 10])
tensor([1., 2., 3., 4., 5.])

6. 创建具有特定数据类型的张量

在PyTorch中,张量可以具有不同的数据类型,如浮点型、整型等。可以使用dtype参数指定张量的数据类型。

import torch

# 创建一个具有特定数据类型的张量
x = torch.tensor([1, 2, 3], dtype=torch.float32)
print(x.dtype)

# 创建一个具有特定数据类型的全零张量
x = torch.zeros(3, 3, dtype=torch.int32)
print(x.dtype)
torch.float32
torch.int32

三、张量操作和运算

1. 张量运算

PyTorch提供了许多张量运算操作,例如加法、减法、乘法和除法。这些运算可以应用于张量之间,也可以应用于张量与标量之间。下面是一些常用的张量运算示例:

import torch

# 张量加法
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
c = a + b
print(c)
tensor([5, 7, 9])

# 张量减法
a = torch.tensor([4, 5, 6])
b = torch.tensor([1, 2, 3])
c = a - b
print(c)
tensor([3, 3, 3])

# 张量乘法
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
c = a * b
print(c)
tensor([ 4, 10, 18])

# 张量除法
a = torch.tensor([4, 6, 8])
b = torch.tensor([2, 3, 4])
c = a / b
print(c)
tensor([2, 2, 2])

# 注意:当您向一个张量添加一个标量时,该标量将按元素顺序添加到该张量的每个分量中。
# 张量与标量相加
a = torch.tensor([1, 2, 3])
b = 2
c = a + b
print(c)
tensor([3, 4, 5])

# 张量与标量相乘
a = torch.tensor([1, 2, 3])
b = 2
c = a * b
print(c)
tensor([2, 4, 6])

2. 张量形状变换

在实际的深度学习任务中,经常需要对张量进行形状变换。PyTorch提供了一系列函数来改变张量的形状,例如reshape()、view()和transpose()等。下面是一些常用的张量形状变换的示例:

在 PyTorch 中,reshape()view() 都是用于调整张量的形状的。它们之间的主要区别和联系如下:

  1. 功能:

    • 两者的主要目的都是为了重新塑造张量,不改变其内容。
  2. 内部实现:

    • view() 期望原始张量的数据在内存中是连续的。如果原始张量不是连续的,view() 操作会失败。在这种情况下,你可能需要先使用 .contiguous() 方法,然后再使用 view()
    • reshape() 可以返回原始张量的视图(如果可能),也可以返回复制的张量。因此,reshape() 更为灵活,但有时可能会导致额外的内存开销。
  3. 推荐使用:

    • 如果你不确定原始张量是否连续,使用 reshape() 可能更为安全,因为它总是能工作。
    • 如果你知道张量是连续的,或者你想确保没有发生数据复制,view() 可能是更好的选择。
  4. 返回值:

    • view()reshape() 都会返回新的张量,但这两个新的张量通常会与原始张量共享相同的数据存储。因此,更改新张量的值可能也会更改原始张量的值,反之亦然。

总之,reshape()view() 在大多数情况下都可以互换使用,但由于它们的内部实现差异,它们在某些情况下可能会有不同的行为。如果你想确保始终得到一个新的、独立的张量副本,你应该使用 clone() 方法然后再进行形状调整。

import torch

# 改变张量的形状
a = torch.tensor([[1, 2, 3], [4, 5, 6]])
b = a.reshape(3, 2)  # 改变为3x2的张量

b
tensor([[1, 2],
        [3, 4],
        [5, 6]])
# 改变张量的形状(与reshape()功能相同)
a = torch.tensor([[1, 2, 3], [4, 5, 6]])
b = a.view(3, 2)  # 改变为3x2的张量

b
tensor([[1, 2],
        [3, 4],
        [5, 6]])

在 PyTorch 中,transpose() 函数用于交换张量的两个维度。这对于某些操作,如矩阵转置或者对多维张量进行轴重排,非常有用。

transpose() 函数的基本用法如下:

tensor.transpose(dim0, dim1)  

其中 dim0dim1 是你想要交换的两个维度的索引。

示例:

  1. 对于二维张量(即矩阵),transpose() 可用于获得其转置:
x = torch.tensor([[1, 2], [3, 4], [5, 6]])  
y = x.transpose(0, 1)  
  1. 对于三维张量,你可以交换任意两个维度:
x = torch.rand(2, 3, 4)  
y = x.transpose(0, 2)  # 结果的形状为 [4, 3, 2]  

需要注意的是,transpose() 返回的是原张量的视图,这意味着新张量与原张量共享内存。因此,修改新张量的值也会影响到原张量,反之亦然。

对于二维张量,还有一个简短的方法来获得其转置:使用 .t() 方法。

x = torch.tensor([[1, 2], [3, 4]])  
y = x.t()  
# 转置张量
a = torch.tensor([[1, 2, 3], [4, 5, 6]])
b = a.transpose(0, 1)  # 转置为3x2的张量

b

a = torch.tensor([[1, 2, 3], [4, 5, 6]])
b = a.transpose(0,1)
b
tensor([[1, 4],
        [2, 5],
        [3, 6]])

3. 张量索引和切片

对于张量中的元素,可以使用索引和切片来访问和操作它们。下面是一些常用的张量索引和切片的示例:

import torch

# 张量索引
a = torch.tensor([[1, 2, 3], [4, 5, 6]])
element = a[0, 1]  # 获取第一行第二列的元素

element
tensor(2)
# 张量切片
a = torch.tensor([[1, 2, 3], [4, 5, 6]])
slice = a[:, 1:3]  # 获取所有行的第二列到第三列的切片

slice
tensor([[2, 3],
        [5, 6]])

4. 获取张量中的数值

torch.randn()torch.rand() 都是在 PyTorch 中生成随机张量的函数,但它们生成的随机数来源于不同的分布。以下是它们之间的主要区别:

  1. 分布:

    • torch.randn(): 生成的数字来自标准正态分布(均值为 0,标准差为 1)。
    • torch.rand(): 生成的数字来自均匀分布在区间 [0, 1) 中。
  2. 使用示例:

    • torch.randn(size): 返回指定形状 size 的张量,其元素从标准正态分布中随机采样。
    • torch.rand(size): 返回指定形状 size 的张量,其元素从 [0, 1) 的均匀分布中随机采样。

例如,torch.randn(3, 3) 会生成一个 3x3 的张量,其中的元素都是从标准正态分布中抽取的。而 torch.rand(3, 3) 会生成一个 3x3 的张量,其中的元素都是从 [0, 1) 的均匀分布中抽取的。

值得注意的是:当我们说一个随机变量遵循[0, 1)的均匀分布时,这意味着在这个区间内的任何一个小区间上取值的概率是恒定的,并且与这个小区间的长度成正比。

这两个函数都是在没有提供任何随机数生成器种子的情况下工作的,因此每次调用它们时,你都会得到不同的随机数。如果需要可重复的结果,你应该设置随机种子。

  • 如果 tensor 仅有一个元素,可以采用 .item() 来获取数值:
a = torch.randn(1)

print(a)
print(a.item())
tensor([1.1083])
1.1082559823989868
  • 如果张量中含有多个元素,可以张量的切片索引定位到一个元素上,再使用.item()来获取数值:
a = torch.tensor([[1, 2, 3], [4, 5, 6]])

print(a[1:2,1])
print(a[1:2,1].item())
tensor([5])
5

5. 张量转化为数组

import torch
import numpy as np

# 创建一个PyTorch张量
tensor = torch.tensor([[1, 2, 3],
                       [4, 5, 6],
                       [7, 8, 9]])

# 将张量转换为NumPy数组
array = tensor.numpy()

# 现在,`array`就是一个NumPy数组,你可以使用NumPy的功能来操作它
print(array)
[[1 2 3]
 [4 5 6]
 [7 8 9]]

6. 张量转化为列表

import torch

# 创建一个PyTorch张量
tensor = torch.tensor([[1, 2, 3], 
                       [4, 5, 6]])

# 将张量转换为Python列表
tensor_list = tensor.tolist() # 

print(tensor_list)
[[1, 2, 3], [4, 5, 6]]

请注意,.tolist()方法只能用于包含标量元素的张量,例如整数或浮点数。如果张量包含其他张量或复杂数据类型,则不能直接使用.tolist()。在这种情况下,你需要先将内部的张量或复杂数据类型转换为Python列表。

四、自动求导原理

在PyTorch中,自动求导是一个关键的功能,它允许我们自动计算梯度,从而进行反向传播和优化。

1. 计算图

计算图是一个有向无环图(DAG),它描述了计算过程中的操作和数据之间的依赖关系。在PyTorch中,每个计算图由一系列的节点组成。节点表示操作(例如加法、乘法)或者输入数据,边表示数据的流动方向。计算图分为两个阶段:前向传播反向传播

  • 前向传播:在前向传播阶段,计算图从输入节点开始,按照节点之间的依赖关系依次进行计算,直到到达输出节点。这个过程中,每个节点将计算并传递输出给下一个依赖节点。
  • 反向传播:在反向传播阶段,计算图从输出节点开始,沿着边的反方向传播梯度。这个过程中,每个节点根据链式法则将梯度传递给输入节点,以便计算它们的梯度。

通过构建计算图,PyTorch可以记录整个计算过程,并在需要时自动计算梯度。这使得我们可以轻松地进行反向传播,并在优化算法中使用梯度信息。

2. 梯度

在深度学习中,梯度是一个非常重要的概念,它表示了函数在某一点的变化率。在PyTorch中,计算梯度是一项关键操作,它允许我们通过反向传播算法有效地更新模型参数。本节将详细介绍如何在PyTorch中计算梯度,并提供一些示例来帮助你更好地理解这个过程。

梯度是一个向量,其方向指向函数值增长最快的方向,而其大小表示函数值的变化率。在深度学习中,我们通常希望最小化损失函数,因此我们要沿着梯度的反方向更新模型参数,以逐步降低损失值。

PyTorch中的torch.Tensor类是PyTorch的核心数据结构,同时也是计算梯度的关键。每个张量都有一个属性requires_grad,默认为False。如果我们希望计算某个张量的梯度,需要将requires_grad设置为True,那么就会开始追踪在该变量上的所有操作,而完成计算后,可以调用 .backward() 并自动计算所有的梯度,得到的梯度都保存在属性 .grad 中。

调用 .detach() 方法分离出计算的历史,可以停止一个 tensor 变量继续追踪其历史信息 ,同时也防止未来的计算会被追踪。

而如果是希望防止跟踪历史(以及使用内存),可以将代码块放在 with torch.no_grad(): 内,这个做法在使用一个模型进行评估的时候非常有用,因为模型会包含一些带有 requires_grad=True 的训练参数,但实际上并不需要它们的梯度信息。

对于 autograd 的实现,还有一个类也是非常重要-- Function

TensorFunction 两个类是有关联并建立了一个非循环的图,可以编码一个完整的计算记录。每个 tensor 变量都带有属性 .grad_fn ,该属性引用了创建了这个变量的 Function (除了由用户创建的 Tensors,它们的 `grad_fn二小节介绍梯度的内容。

3. 梯度计算过程

在PyTorch中,计算梯度的过程主要分为以下几个步骤:

  1. 创建张量并设置requires_grad=True:首先,我们需要创建一个张量,并将requires_grad属性设置为True,以便PyTorch跟踪其梯度。
import torch

x = torch.tensor([2.0, 3.0], requires_grad=True)
  1. 定义计算图:接下来,我们使用创建的张量进行计算,可以使用任何PyTorch支持的函数、操作和模型。
y = x**2 + 3*x + 1
  1. 计算梯度:一旦我们得到了最终的输出张量,我们可以使用backward()方法自动计算梯度。
y.sum().backward()
  1. 获取梯度值:通过访问张量的grad属性,我们可以获得计算得到的梯度值。
print(x.grad)
tensor([7., 9.])

完整的示例代码如下所示:

import torch

x = torch.tensor([2.0, 3.0], requires_grad=True)
y = x**2 + 3*x + 1
y.sum().backward()
print(x.grad)
tensor([7., 9.])

在上面的示例中,我们创建了一个包含两个元素的张量x,并将requires_grad设置为True。然后,我们定义了一个计算图,该计算图使用x进行一系列操作。最后,我们使用backward()方法计算梯度,并通过访问x.grad获取了梯度值。


问题一:为什么要先执行sum再求导?

上面的代码中,先执行y.sum()再求导的目的是为了计算一个标量值(scalar)的梯度。PyTorch的自动微分机制是基于标量值的,因此我们需要确保我们要计算的梯度是一个标量。具体解释如下:

  1. y是一个张量,它可能包含多个元素,例如[y1, y2, ...],每个元素都与x相关。如果我们直接调用y.backward(),PyTorch会尝试计算y中每个元素对应的梯度,这将得到一个与x具有相同形状的梯度张量。这在某些情况下可能是有用的,但通常我们更关心的是一个标量目标函数的梯度。
  2. 通过执行y.sum(),我们将y中的所有元素相加,得到一个标量值(单个数字)。然后,我们对这个标量值执行反向传播,计算相对于x的梯度。这确保了我们计算的是整个目标函数相对于x的梯度,而不是每个元素的梯度。

如果去掉上面代码中的.sum(),将会提示你

RuntimeError: grad can be implicitly created only for scalar outputs

4. 梯度计算示例

下面是一些更复杂的示例,以帮助你更好地理解计算梯度的过程。

示例 1:线性回归

考虑一个简单的线性回归模型,我们的目标是找到一条直线,以最小化预测值与真实值之间的平方误差。我们可以使用梯度下降算法来更新直线的参数。

在神经网络或线性回归模型中,参数 ( w ) 和 ( b ) 的值是通过迭代优化过程进行更新的,这通常是为了最小化损失函数。下面是 ( w ) 和 ( b ) 值如何变化的基本原理:

  1. 损失函数: 它衡量模型的预测与真实标签之间的差异。对于这个例子,你使用了均方误差损失函数。

  2. 梯度计算: 在反向传播过程中,计算损失函数相对于模型参数的梯度。这个梯度指示了为了最小化损失函数,参数应该增加还是减少,以及应该变化的量。

  3. 参数更新: 使用优化算法(在这个例子中是随机梯度下降,SGD)按以下方式更新参数:

w = w − α × ∂ loss ∂ w w = w - \alpha \times \frac{\partial \text{loss}}{\partial w} w=wα×wloss
b = b − α × ∂ loss ∂ b b = b - \alpha \times \frac{\partial \text{loss}}{\partial b} b=bα×bloss

其中 α \alpha α 是学习率,它是一个超参数,决定了参数更新的步长。在你的代码中,学习率被设置为0.01。

这个更新规则基于一个简单的直觉:如果损失函数相对于参数 ( w ) 的梯度是正的,那么减少 ( w ) 的值会减少损失。反之,如果梯度是负的,那么增加 ( w ) 的值会减少损失。

在每次迭代中,都会重复这个过程,逐渐调整 ( w ) 和 ( b ) 的值,直到模型收敛或达到预定的迭代次数。

对于更复杂的优化算法(如Adam、RMSProp等),更新规则可能会更复杂,并可能涉及到额外的超参数和内部状态。但基本的原理——使用梯度来指导参数更新的方向——仍然相同。

import torch

# 创建训练数据
x_train = torch.tensor([[1.0], [2.0], [3.0]])
y_train = torch.tensor([[2.0], [4.0], [6.0]])

# 定义模型参数
w = torch.tensor([[0.0]], requires_grad=True)
b = torch.tensor([[0.0]], requires_grad=True)

# 定义模型
def linear_regression(x):
    return torch.matmul(x, w) + b

# 定义损失函数
def loss_fn(y_pred, y):
    return torch.mean((y_pred - y)**2)

# 定义优化器
optimizer = torch.optim.SGD([w, b], lr=0.01)

# 训练模型
for epoch in range(1000):
    # 前向传播
    y_pred = linear_regression(x_train)
    
    # 计算损失
    loss = loss_fn(y_pred, y_train)
    
    # 反向传播
    loss.backward()
    
    # 更新参数
    optimizer.step()
    
    # 清零梯度
    optimizer.zero_grad()

# 查看参数的值
w,b
(tensor([[1.9708]], requires_grad=True),
 tensor([[0.0664]], requires_grad=True))

计算梯度是深度学习中非常重要的操作之一。PyTorch提供了强大的自动求导功能,使得计算梯度变得非常方便。在本节中,我们详细介绍了如何在PyTorch中计算梯度,并提供了一些示例来帮助你更好地理解这个过程。通过深入学习和理解计算梯度的原理和应用,你将能够更好地使用PyTorch构建和训练深度学习模型。

五、反向传播

反向传播是深度学习中的关键概念之一,它是训练神经网络模型的基础。在本节中,我们将详细介绍反向传播的原理和实现,并通过丰富的示例来帮助读者全面理解和学习PyTorch中的反向传播过程。

1. 反向传播原理

在深度学习中,我们通常使用梯度下降法来最小化损失函数,从而训练神经网络模型。而反向传播是计算损失函数对模型参数梯度的一种有效方法。通过计算参数梯度,我们可以根据梯度的反方向更新参数,使得模型的预测结果逐渐接近真实标签。反向传播的过程可以概括为以下几个步骤:

  1. 前向传播:将输入样本通过神经网络的前向计算过程,计算出预测结果。
  2. 计算损失:将预测结果与真实标签进行比较,并计算损失函数的值。
  3. 反向传播梯度:根据损失函数的值,计算损失函数对模型参数的梯度。
  4. 参数更新:根据参数的梯度和优化算法的规则,更新模型的参数。

在PyTorch中,反向传播过程是自动完成的。通过调用backward()函数,PyTorch会自动计算损失函数对所有可学习参数的梯度,并将其保存在相应的参数张量的.grad属性中。接下来,我们将通过具体示例来演示这一过程。

2. 反向传播示例

为了更好地理解反向传播的过程,我们将通过一个简单的示例来演示如何在PyTorch中执行反向传播。
假设我们有一个简单的线性回归模型,其中只有一个输入特征和一个输出标签。我们的目标是通过训练模型来找到最佳的线性关系。首先,我们需要导入所需的库和模块、定义模型类

import torch
import torch.nn as nn
import torch.optim as optim

#定义了一个名为 LinearRegression 的类,它继承了 nn.Module。
#在 PyTorch 中,自定义的神经网络模型通常会继承 nn.Module 类。
class LinearRegression(nn.Module):
    def __init__(self):
        # super()函数用于调用父类(超类)的方法。通过使用super()函数,我们确保了LinearRegression类的父类(在这个情况下是nn.Module)的初始化方法被正确地调用。
        super(LinearRegression, self).__init__() # LinearRegression: 这是子类的名称。self: 这是子类的一个实例。
        self.linear = nn.Linear(1, 1)  # 定义线性层,输入维度为1,输出维度为1
    # 定义了模型的前向传播过程
    def forward(self, x):
        # 当模型接受输入数据 x 时,它会将数据传递给之前定义的线性层 self.linear,并返回其输出。
        return self.linear(x)

# 创建模型的实例、定义损失函数和优化器:
model     = LinearRegression()
criterion = nn.MSELoss()  # 均方误差损失函数

# 随机梯度下降优化器
optimizer = optim.SGD(model.parameters(), lr=0.01) 

# 生成样本数据
x_train = torch.tensor([[1.0], [2.0], [3.0], [4.0]])
y_train = torch.tensor([[2.0], [4.0], [6.0], [8.0]])

# 训练模型
for epoch in range(100):
    optimizer.zero_grad()  # 梯度清零

    # 前向传播
    y_pred = model(x_train)

    # 计算损失
    loss = criterion(y_pred, y_train)

    # 反向传播
    loss.backward()

    # 参数更新
    optimizer.step()

w = model.linear.weight.detach().numpy()
b = model.linear.bias.detach().numpy()

w,b
(array([[1.7025709]], dtype=float32), array([0.87447774], dtype=float32))

总结:上述两段线性回归的区别

您提供的代码与之前的代码确实都实现了一维线性回归的预测,但它们之间存在一些关键差异。以下是这两段代码之间的主要差异:

  1. 模型结构:

    • 第一段代码:使用了PyTorch的nn.Module类来定义一个完整的线性回归模型,这种方式更加结构化,也更易于扩展。
    • 第二段代码:直接定义了模型参数wb,并使用了一个简单的函数linear_regression来表示线性回归模型。这种方式更为直观和简洁,但可能不那么结构化。
  2. 模型参数初始化:

    • 第一段代码:模型参数的初始化是在nn.Linear()层中自动完成的。
    • 第二段代码:您直接为wb提供了初值,都是0。
  3. 优化器的参数:

    • 第一段代码:优化器使用了model.parameters()来获取模型中的所有参数。
    • 第二段代码:优化器直接接受了一个包含wb的列表。
  4. 训练循环中的梯度清零:这个无所谓前后,只要进行梯度清零即可

    • 第一段代码:在每个训练循环开始时清零梯度。
    • 第二段代码:在每个训练循环结束后清零梯度。
  5. 训练数据:不重要

    • 第一段代码:包含4个样本。
    • 第二段代码:包含3个样本。
  6. 训练轮次:不重要

    • 第一段代码:模型训练了100轮。
    • 第二段代码:模型训练了1000轮。

总的来说,这两段代码都实现了基于PyTorch的线性回归模型,但使用了不同的方法和风格。第一种方法更加结构化,适用于更复杂的神经网络模型;而您提供的代码更为简洁,更直接地展示了线性回归的核心概念。

在上述代码中,我们使用了一个简单的数据集,包含了输入特征x_train和对应的真实标签y_train。在每个训练迭代中,我们首先将梯度清零,然后进行前向传播计算预测值y_pred,接着计算损失loss,然后通过调用backward()函数执行反向传播,最后使用优化器的step()方法来更新模型的参数。
通过上述示例,我们可以看到在PyTorch中实现反向传播是非常简单的。PyTorch会自动计算参数的梯度,并通过优化器来更新参数,从而实现模型的训练过程。

在本节中,我们详细介绍了PyTorch中反向传播的原理和实现。我们学习了反向传播的步骤,包括前向传播、计算损失、反向传播梯度和参数更新。通过一个简单的线性回归示例,我们演示了如何在PyTorch中执行反向传播。
反向传播是深度学习中非常重要的一部分,它为训练神经网络模型提供了有效的方法。理解和掌握反向传播对于使用PyTorch进行深度学习任务至关重要。在接下来的章节中,我们将进一步探索PyTorch中的其他重要概念和技术,帮助读者建立更深入的理解。

闯关练习

练习1:请创建一个形状为 2x3 的全零张量,然后加上标量2,将结果赋予answer1

answer1 = torch.zeros(2,3)
answer1 = answer1 + 2
answer1
tensor([[2., 2., 2.],
        [2., 2., 2.]])

练习2:请创建一个形状为 2x3 的全零张量a,一个形状为 2x3 的全1张量b,a与b相乘后,再乘以标量3,将结果赋予answer2

answer2 = torch.zeros(2,3)
answer3 = torch.ones(2,3)
answer3 = answer2 * answer3 + 2
answer3
tensor([[2., 2., 2.],
        [2., 2., 2.]])

你可能感兴趣的:(pytorch,学习,pytorch,人工智能,python)