PyTorch
PyTorch
的数据类型为张量,张量有多种,常用的为整型、浮点型、比特型。张量可以放置在CPU
或GPU
上,放置在这两者上的张量属于不同的数据类型,CPU
上的整型、浮点型、比特型张量数据类型为 torch.IntTensor
、torch.FloatTensor
、torch.ByteTensor
,GPU
上的整型、浮点型、比特型张量数据类型为 torch.cuda.IntTensor
、torch.cuda.FloatTensor
、torch.cuda.ByteTensor
。注意 torch
中没有字符串类型数据。
创建张量
1. 创建有给定值的张量有两种基本方式,一是用 torch.tensor(list)
将列表数据 list
转化为张量,二是用 torch.from_numpy(ndarray)
将 numpy
数组 ndarray
转化为张量(注意这种方式创建的张量与原 numpy
数组共享内存,修改其中一个则另一个也会变化)。
2. 创建无给定值完全随机的张量用 torch.empty(n1,n2,...)
或 torch.IntTensor(n1,n2,...)
或 torch.FloatTensor(n1,n2,...)
,则会创建元素随机、size/shape = [n1,n2,...]
的张量。注意用 torch.Tensor(n1,n2,...)
也可以创建同样 size
的随机元素张量,其元素的数据类型是 torch
的默认类型,若不特别设置则为float
型(torch.tensor
也是同样的道理),设置torch
默认数据类型为 double
则其创建出来的张量元素都是double
型,设置方法为
torch.set_default_tensor_type(torch.DoubleTensor)
注意无给定值的张量在使用时会有隐患,因为其随机生成的元素可能会是0
或无穷等,因此使用前应当另行赋值。
3. 创建无给定值有条件随机的张量,torch.rand(n1,n2,...)
创建形状为 n1,
n2,
...
每个元素是[0,1]
上均匀分布随机数的张量。现有张量 a
,torch.rand_like(a)
表示创建一个和 a
形状一样每个元素是 [0,1]
上均匀分布随机数的张量,torch.randint(l,u,[n1,n2,...])
创建一个形状为 n1,
n2,
...
每个元素是大于等于 l
小于等于 u
的随机整数的张量。torch.randn(n1,n2,...)
创建元素为 N(0,1)
随机数的张量,自行指定期望方差可用 torch.normal
。
4. 创建一些特殊的张量:torch.ones(n1,n2,...)
生成元素全为1
的张量,torch.zeros(n1,n2,...)
生成元素全为0
的张量,二者同样可以加 _like
使用。torch.arange
和 range
用法相同,torch.arange(start,stop,step)
表示创建一个一维张量,第一个元素为 start
,每次递增 step
,最后一个元素严格小于 stop
,torch.arange(start,stop)
等于将 step
取1
,torch.arange(stop)
等于将 start
取 0
、step
取 1
。torch.linspace(start,stop,len)
创建长度为 len
的一维张量,其第一个和最后一个元素是 start
和 stop
。torch.linspace
的结果是将其作为 10
的指数次。torch.eye(n, m=None)
创建第 i
行第 i
列为 1
其他为 0
的 2
维张量( n
代表行数 m
代表列数,不输入 m
的情况下默认为 n
阶方阵)。torch.randperm(number)
创建将 0,
1,
...,
number
-
1
顺序打乱的一维张量,number
是正整数。
注意:torch.tensor
的全部参数(也是张量的部分属性):我们上面的创建方式中,torch.tensor
仅有一个参数即 data
,实际上张量的全部参数包括数据 data
、数据类型 dtype
(默认 None
)、所在设备 device
(cuda/cpu
,默认 None
)、是否需要梯度 requires_grad
(默认 False
)。例如 torch.tensor(data=[1,2],device='cuda')
。
张量的维数
标量是 0
维张量,向量是 1
维张量,矩阵是 2
维张量,注意只要写成向量的形式,长度为 1
的向量也是向量,其张量维数为 1
。张量 a
的维数用 a.dim()
查看,a
的元素个数用 a.numel()
查看。注意 size/shape
和维数不同,size/shape
指对象的每一维的长度组成的向量,如 2×3 阶矩阵的维数是 2
,size/shape
是 [2,3]
。需要注意的是 size
和 shape
的概念一样但是用法不同,现有对象 a
,则 a.
size()
和 a.
shape
都会返回一个长度等于 a
的维数的列表,输出其第 i
个元素使用 a.
size(i-1)
或 a.
size()[i-1]
或 a.
shape[i-1]
。
view
方法:类似 numpy
的 reshape
。假设张量 a
的元素个数是 n 个,a.view(n1,n2,...)
表示将 a
的 size
改成 n1×n2×...
,其中 n1×n2×...
=
n
。
unsqueeze
方法:在指定的维度上增加一个维度大小为 1
的维度。用于改变张量的形状以满足神经网络中某些操作或层的要求。其不改变原始张量而是返回一个新张量。与之相对的是 squeeze
,会移除张量所有维度大小为 1
的维度(也可以指定移除哪个维度上的大小为 1
的维度)。注意unsqueeze
方法产生的是原张量的视图,改变新张量和原张量中的一者会令另一者也发生变化。
import torch
x = torch.rand(3, 4) # 创建一个形状为3×4的张量
x1 = x.unsqueeze(0) # 在维度0上增加一个维度
print(x1.shape)
>> torch.Size([1, 3, 4])
x2 = x.unsqueeze(1) # 在维度1上增加一个维度
print(x2.shape)
>> torch.Size([3, 1, 4])
张量的索引和切片
1. 每个维的单一数字索引:指标从左往右与张量的 size
对应,对 size
为 n1×n2×...×nm
的张量 a
来说,a[l1,l2,...,lk]
是 a
的第 1
维第 l1
、第 2
维第 l2
、...、第 k
维第 lk
个元素(k≤m
)。以 4
维张量为例,4
维张量可以表示彩色图片的batch,如彩色图片的通道数为3,假设每张图片长宽都是20,则5张图片可用5×3×20×20的张量表示。我们假设张量a的size为5×3×20×20,则a[0]的size为3×20×20,表示第一张照片,a[0, 0]的size为20×20,表示第一张照片的第一个通道,a[0, 0, 2, 4]则是一个数,表示第一张照片的第一个通道的第3行第4列像素值。
2. 通过冒号:进行切片:主要通过数字和 : 的组合实现,只有一个 : 时表示这一维全部选取,n: 表示从n开始到最后,包含n,:n表示从开头到n-1,注意n可以是负数,表示倒数第几个,如-1:就是最后一个,-2:是倒数第二个到倒数第一个,:-1表示从开头到倒数第2个,m:n表示从m到n-1。用start:stop:step进行隔元素切片,表示从start开始,每次往后跳step个,调到stop结束(不包含stop),::step相当于这种情况的简便用法,等于start取0,stop取结尾。
3. 通过具体数字切片:使用index_select方法,对上面的4维张量a,a.index_select(0, b)表示从a的第1维中按一维张量b切片,若b为[0, 2]则返回的是size为5×2×20×20的张量。
4. 三个点...切片:...代表所有其他维度的所有元素,如a[...]就是整个a,a[:, 0, ...]的size为5×20×20,a[0, ...]的size为3×20×20。仅仅是为了书写方便,可以忽略。
数学运算
现有两个张量a和b。
逐元素四则运算:直接用a+b,a-b,a * b,a / b,建议直接使用前者。逐元素次方也相同,**或a.pow()。
矩阵相乘:torch.matmul(a, b),等价于a@b,对2维张量乘法就是一般的矩阵乘法,高于2维的情况,以4维为例,此时对a和b的最后两个维进行矩阵乘法运算,即a的size为n1×n2×n3×n,b的size为n1×n2×n×n4,则torch.matmul(a, b)的size为n1×n2×n3×n4,实际上就是将多个矩阵并行相乘,注意高于2维的两个张量相乘时除了最后两维需要满足矩阵乘法可行的要求外还需要前面的维要么完全相等要么适用broadcast机制。
逐元素裁剪:a.clamp(min)会将小于min的元素全部改成min,a.clamp(min, max)会将小于min的元素全部改为min,大于max的元素全部改为max。
求和:torch.sum
函数作用在张量上可以对其全部元素进行求和,可以指定其第二个参数dim
以对指定维度进行求和,如对二维m×n
阶张量tensor
,torch.sum(tensor,dim=0)
表示将tensor
的各行相加,得到的结果是n
阶张量,而torch.sum(tensor,dim=1)
表示将tensor
的各列相加,得到的结果是m
阶张量。
其他:逐元素指数对数tensor.exp(),tensor.log(),逐元素取整/取小数:a.ceil(),a.floor(),a.trunc(),a.frac(),a.round()。查看最值和中位数a.min(),a.max(),a.median()。
torch.where:用法为torch.where(condition, x, y),结果是一个张量,其第i个元素为x[i](若condition对i成立)或y[i](若condition对i不成立)。例如:
import torch
condition = torch.tensor([[1,2], [3,4]])
x = torch.ones(2, 2)
y = torch.zeros(2, 2)
torch.where(condition>2, x, y)
>> tensor([[1., 1.],
[0., 0.]])
广播机制
当两个张量的维数(shape)不一样时,可以通过广播(broadcasting)机制来使它们具有兼容的形状从而进行运算。广播是一种在NumPy和PyTorch等库中实现不同形状数组之间算术运算的机制。
以下是两个张量维数不一样时进行运算的一般规则:
• 规则1. 如果两个张量的维数不一致,那么首先会把维度低的那个张量从右边和维度高的张量对齐。这意味着在低的维度张量的左侧添加额外的维度,大小为1,直到它与高的维度张量具有相同的维数。例如,给定两个张量a和b,其中a的形状为[8, 4, 5, 6],b的形状为[5, 6],在对它们进行相加操作时,首先将b的维度扩展为[1, 1, 5, 6],然后对应位置相加即可。
• 规则2:当两个张量维数相同时(通过广播后的维数),对应轴的值需要一样,或者为1。相加时,把所有为1的轴进行复制扩充,从而得到两个维度完全相同的张量。然后对应位置相加即可。例如,两个张量a和b,其中a的形状为[8, 1, 5, 1],b的形状为[1, 4, 5, 6]。首先,通过广播,a的形状变为[8, 4, 5, 6],b的形状不变(因为它已经是[1, 4, 5, 6])。然后,a和b中所有为1的轴都被复制扩充,以匹配对方的形状。最后,对应位置进行相加。
注意,不是所有的形状组合都可以通过广播来兼容。如果两个张量在某一维度上的大小都不为1且不相等,则无法进行广播。
以上就是在PyTorch中处理维数不一样的张量进行运算的基本方法。在实际应用中,可能需要结合具体的场景和需求来选择合适的处理方法。
nn.Parameter()
torch.Tensor的子类(因此也是张量),用于创建可训练参数,这些参数在模型训练过程中会自动更新,用法为
nn.Parameter(data, requires_grad = True)
其中data需要是浮点型张量。
神经网络
torch
库中的nn
模块用于搭建神经网络,nn
为neural network
的简写。nn
模块中的Module
类是所有神经网络的父类,自行定义神经网络必须要继承Module
。
训练神经网络时,所有训练样本都遍历一遍称为一个epoch
,一批样本过一遍称为一次iteration
,一批样本的样本量称为batch size
,总样本量除以batch size
就是一个epoch
的iteration
次数。
搭建、训练神经网络的步骤:
1. import
相关模块;
2. 数据:指定训练集和测试集;
3. 模型:以类的形式定义神经网络模型,假设类名为M
,再令model = M()
将网络实例化;
4. 损失函数/优化器:配置训练方法;
5. 训练:执行训练过程;
6. 得到结果。
数据
PyTorch
数据部分的核心为以下两者:
1. torch.utils.data.Dataset
:是一个数据集的抽向类,但凡训练神经网络,用到的数据都应当定义为其子类,并且应当自行定义__init__
,__len__
和__getitem__
方法,__len__
返回数据集大小,__getitem__
输入索引返回具体的某个数据。需要类接受的参数都以属性的形式写在__init__
函数中。实例化之后(假设叫dataset
)就可以通过dataset
.__len__()
查看样本量,通过dataset
.__getitem__(i)
查看第i
个样品(等价于便捷操作dataset[
i])
。
2. torch.utils.data.DataLoader
:也是一个类,但是不同自己写,直接调用函数即可。用于从dataset
中读取数据,将数据按照batch size
封装成tensor
,后续只需要再包装成variable
即可作为模型的输入。神经网络训练中每次循环就是从dataloader
中获取一个batch size
大小的数据。其参数众多,但是大部分不需要特意设置,常规用法为
torch.utils.data.DataLoader(dataset, batch_size, shuffle, num_workers, drop_last)
得到的是一个可迭代对象。
参数解释:
1. dataset
: 原始数据,需要是Dataset
类;
2. batch_size
: 每个batch
有多少样品,整数值,默认为1
;
3. shuffle
: 是否打乱数据,布尔值,默认为False
;
4. num_workers
: 是否多进程读取数据,每个进程负责读取一个batch
,整数值,默认0
,若要设置多进程,则不应大于CPU
核数;
5. drop_last
: 样本量不能被batch_size
整除时是否舍弃最后一批数据,布尔值,默认False
;
除以上参数外,其参数还包括sampler
, batch_sampler
, collate_fn
, pin_memory
, timeout
。
注:
1. Dataset
的使用需要自己写一个torch.utils.data.Dataset
的子类,但是DataLoader
的使用只需要像调用函数那样输入参数调用即可,不需要额外手写一个类,DataLoader
类产生的实例是可迭代对象。
2. batch size
太大或太小都不行,小的batch size
含有更多随机噪声,梯度更新更嘈杂,有利于跳出局部最优,从而提高泛化性能,但是运行慢。大的batch size
梯度更新更准确,收敛快,但是更容易陷入局部最优。
例:
import torch
X = torch.rand(100, 3, 30, 30)
Y = torch.randint(1, 10, [100])
class Dataset_example(torch.utils.data.Dataset):
def __init__(self, data, label):
self.data = data
self.label = label
def __len__(self):
return len(self.label)
def __getitem__(self, index):
x_class = self.data[index]
y_class = self.label[index]
return x_class, y_class
dataset_example = Dataset_example(data=X, label=Y)
dataloader_example = torch.utils.data.DataLoader(dataset=dataset_example, batch_size=10, shuffle=True)
# 此处x和y都是原来的batch_size=10个样品组合成的张量
# 若batch size = l, 则循环中每次输出的x形状为l×3×30×30, y形状为l
for x, y in dataloader_example:
print(x, y)
模型
模型的定义方式
神经网络模型是以定义一个torch.nn.Module
的子类的方式实现的,这个类至少要包含两个方法,即用于定义网络零件的__init__
方法和网络前向传播方法forward
,二者的第一个参数都得是self
。__init__
方法输入参数为self
和其他需要用到的参数(包括需要估计的参数和已知的参数),第一行为super().__init__()
,表示继承父类的所有属性,后面每行是代表网络零件(如全连接层,卷积层等)的函数和模型中用到的参数。forward
方法的输入参数是self
以及自变量(特征)值x
,forward
方法的输出就是模型最终输出,其用到的一切函数和参数都以类属性的形式定义在__init__
方法中。
在训练模型(即估计参数)的过程中PyTorch
的自动微分机制需要知道哪些变量是需要估计的模型参数(未知参数),以便在反向传播时计算梯度并更新这些参数。nn.Parameter
函数是专门为那些需要被优化器更新以最小化损失函数的参数设计的,将未知参数定义在__init__
方法中并用nn.Parameter
函数包装,可以确保PyTorch
能够正确地跟踪这些参数的梯度。而已知参数(即那些不需要在训练过程中被优化或更新的参数)应该作为普通的Python
属性定义在__init__
方法中,不作为nn.Parameter
,已知参数可以作为__init__
方法的参数传入(def __init__(self,known_par)
这种形式),也可以直接写在__init__
方法中,如self.known_par = torch.tensor([[1.0,2.0],[3.0,4.0]]),dtype = torch.float32)
,若要在GPU
上运行模型,也需要将已知参移动到GPU
上,如model.known_par = model.known_par.to(device)
。
在GPU上运行模型
在GPU
上运行模型前提是GPU
可用。然后需要将模型数据和参数都移动到GPU
上,这一点通过.to(device)
实现。模型需要先实例化再移动到GPU
上。模型和数据要确保在同一个设备上(都在CPU
上或都在GPU
上,不在同一个设备上的Tensor
进行运算会报错)。对于模型的未知参数(即model.parameters()
中的成员),当我们把模型实例本身移动到GPU
上时,这些未知参数也会自动被移动到GPU
上,这是因为模型的参数是模型实例的一部分。而已知参数实际上相当于数据,因此需要单独移动到GPU
上。数据在输入模型之前也需要单独移动到GPU
上,如用于训练的数据自变量inputs
,因变量labels
,则inputs=inputs.to(device)
,labels=labels.to(device)
。
运行模型时可能会遇到一些内存和性能问题,要确保GPU
有足够的内存来存储模型和数据,并且注意优化代码以减少内存使用和计算时间。
import torch
import torch.nn as nn
# 检查GPU是否可用
if torch.cuda.is_available():
print("CUDA is available")
device = torch.device("cuda")
# 一个简单的神经网络模型
class SimpleNet(nn.Module):
def __init__(self):
super().__init__()
self.fc = nn.Linear(10, 1)
def forward(self, x):
return self.fc(x)
# 实例化模型
model = SimpleNet()
# 将模型移动到GPU上
model = model.to(device)
# 现在,模型的参数也在GPU上了, 可以通过访问模型的参数来验证这一点
for param in model.parameters():
print(param.device)
# 在将数据输入模型之前,也需要确保数据也在GPU上
x = torch.randn(16, 10)
x = x.to(device)
# 现在可以在GPU上进行前向传播了
outputs = model(x)
# 确保模型处于训练模式
model.train()
# 创建优化器(需要模型参数已经在GPU上)
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
# 训练过程
for epoch in range(num_epochs):
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device) # 确保输入数据在GPU上
outputs = model(inputs) # 前向传播
loss = criterion(outputs, labels) # 计算损失
optimizer.zero_grad() # 反向传播和优化
loss.backward()
optimizer.step()
需要手动把对象放置在设备上的情况:我们上面提到model.parameters()
中的成员在模型实例被放置在GPU
上时会随之被移动到GPU
上,而数据则不会,因此需要单独移动到GPU
上。因此实例化的类移动到GPU
上时类的属性不一定全部会被移动到GPU
上,具体而言
• 模型参数:这些是通过nn.Parameter
定义的,或者在__init__
方法中定义的torch.Tensor
,并且被用作模型的权重或偏置。当你调用.to(device)
或.cuda()
时,这些参数会被移动到指定的设备(CPU
或GPU
)。
• 其他属性:如果模型有其他非Tensor
属性(例如,普通的Python
对象、列表、字典等),这些属性不会被自动移动到GPU
上(需要手动放置)。这些属性通常用于存储模型的超参数、配置或其他非Tensor
数据。
import torch
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self, input, para):
super(MyModel, self).__init__()
self.fc = nn.Linear(10, 1) # 参数,会被自动移动到GPU
self.some_tensor = torch.randn(10) # 非参数Tensor,不会被自动移动到GPU
self.some_list = [1, 2, 3] # 非Tensor属性,不会被自动移动到GPU
self.input = input # 外来Tensor, 不会被自动移动到GPU
self.para = para # 外来参数, 通过nn.Parameter定义, 会被自动移动到GPU
def forward(self, x):
x = self.fc(x) + self.par
return x
input = torch.tensor(1)
par = nn.Parameter(torch.tensor(2.0, requires_grad=True).float())
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MyModel(input=input, para = para).to(device) # 只有fc的参数会被移动到GPU
# 如果你想要将some_tensor也移动到GPU, 需要手动
model.some_tensor = model.some_tensor.to(device) # 也可以在定义时移动到device上, 即__init__方法中将torch.randn(10)改成torch.randn(10).to(device)
model.input = model.input.to(device) # input是外部传入的因此也可以在前面实例化model之前就爱那个input移动到device上
线性层
最常用的线性层用torch.nn.Linear
函数实现,torch.nn.Linear
是Module
的子类,使用时需要先实例化,方式为
torch.nn.Linear(in_features,out_features,bias=True)
in_features
表示上一层神经元的个数,out_features
表示这一层神经元的个数,注意这里神经元个数不包括偏置,即有几个特征就是几个神经元。用f=nn.Linear(m,n)
实例化后会产生一个函数对象f
,将特征张量x
(需要是float型,int型会报错)输入此函数中可以得到张量
y = x * w + b
其中w
为 m×n
阶二维张量(矩阵),b
为长度 n
的一维张量(向量),*
为矩阵乘法。上式中输入的 x
只需是一个长度m
的一维张量(向量)即可,进行矩阵运算时会自动按1×m
阶矩阵处理x
,最后得到的结果是长度n
的一维张量y
。若输入数据x
为 bs×m
阶矩阵,则输出结果y
为 bs×n
阶矩阵。若输入数据x
是更高维张量,则按torch
中的张量相乘对其规则来广播。
用f.weight
可以查看线性变换层的权重矩阵,其相当于w
的转置(w = f.weight'
),用f.bias
可以查看b
。注意实例化产生函数f时无需人为指定w
和b
,二者会自动随机生成。
模型的训练模式和验证模式
这两种模式的主要区别在于如何处理和计算网络中的某些层,特别是那些涉及随机性或需要特定行为以进行训练的层,如Dropout层和Batch Normalization层。实例化的模型为model,则用model.train()和model.eval()来切换两种模式。确保在验证或测试神经网络之前执行model.eval(),因为这样可以确保网络中的所有层都按照预期的方式工作。同时,在验证或测试完成后,若要继续训练模型,不要忘记重新切换到训练模式(即再次调用model.train())。
在训练模式下,神经网络会进行前向传播和反向传播,以更新网络的权重。这个过程中,所有层都会按照它们的设计进行工作。例如:
· Dropout层:在训练模式下,Dropout层会随机地将一部分神经元的输出设置为0,这有助于防止过拟合。
· Batch Normalization层:在训练模式下,该层会计算每个小批量的均值和方差,并使用这些统计量来标准化输入。此外,它还会更新运行均值和方差,这些值用于在验证模式下标准化输入。
验证模式用于评估神经网络在未见过的数据上的性能。在验证模式下,神经网络只会进行前向传播,而不会更新任何权重。对于某些层来说,验证模式的行为会有所不同:
• Dropout层:在验证模式下,Dropout层不会随机丢弃任何神经元。相反,它会将所有神经元的输出都保留下来,以确保网络输出的稳定性。
• Batch Normalization层:在验证模式下,Batch Normalization层会使用在训练过程中计算出的运行均值和方差来标准化输入,而不是每个小批量的均值和方差。这确保了无论输入数据的小批量大小如何,验证模式的输出都是一致的。
注意:当切换模式时,有些模型可能需要额外的处理步骤,比如将某些层的状态(如Batch Normalization层的运行均值和方差)从训练模式复制到验证模式。
优化器和损失函数
优化器根据损失函数的梯度更新模型参数,一般用Adam优化器即可,方式为
optimizer = optim.Adam(model.parameters(), lr=0.001)
其中model是实例化的模型,lr是学习率。
梯度清零:我们用优化器求解模型参数时需要求损失函数关于参数的梯度,梯度值会存储在参数的.grad属性中,根据上面一段的说明,变量的梯度值会随着每一次求梯度而叠加,但是实际上我们不需要它们叠加,因此每个batch求梯度前都需要先将变量之前的梯度清零,这一点用
optimizer.zero_grad()
来实现,我们在定义optimizer时指定了模型参数(model.parameters()),因此它能够认出来要清零哪些参数的梯度。
梯度更新:梯度下降法的参数更新公式为
w_new = w_old - loss'(w_old) * lr,
因此更新参数估计值所需为上一步的参数估计值和当前的梯度,前者optimizer记得,后者通过backward()求得,w_new的计算通过
optimizer.step()
来实现。
交叉熵损失函数:torch.nn.CrossEntropyLoss,用于分类问题。
均方误差损失函数:torch.nn.MSELoss(),用于回归问题,使用方式为
mse = torch.nn.MSELoss(reduction = 'xxx'),
loss = mse(tensor1, tensor2),
其中xxx可以取none或mean或sum,none表示返回残差平方组成的张量,mean表示残差平方和除以张量长度,sum表示残差平方和。
L1损失:torch.nn.L1Loss()。
训练
backward()
:张量求梯度的方法,用于对requires_grad=True
的张量求梯度。requires_grad
是Tensor
的一个属性,布尔型,表示Tensor
是否可以参与求导(True
表示参与求导,按数学的语言来说,requires_grad=True
的Tensor
是变量,requires_grad=False
的Tensor
是常数),requires_grad=True
的Tensor
具有传递性,如张量x1,x2,...
中只要有一个xi
有requires_grad=True
,则y=f(x1,x2,...)
也有requires_grad=True
。对requires_grad=True
的张量z
,可以用z.backward()
来求z
关于其叶子节点张量的梯度,这里叶子节点型张量指由用户创建、不依赖其他张量的张量,反向传播结束后叶子节点的梯度会保留,非叶子节点的梯度会被删除。例如我们定义两个变量张量x1=1
和x2=2
,定义一个常量张量x3=3
,令y=x1*x2
,z=y^2+x3
,则用z.backward()
对z
求梯度可以得到其关于变量x1
和x2
的梯度,用xi.grad
查看,而x3.grad
则不输出任何结果,因为其为常量,y.grad
会报错,显示其非叶子节点。
注意:
1. backward()
求梯度命令默认不能进行两次及以上,如上面的代码后面再运行z.backward()
会报错,这是因为backward()
默认求一次梯度后就丢掉计算图,若想多次求梯度,需要在第一次求梯度时用z.backward(retain_graph=True)
。
2. 变量张量的梯度属性是会叠加的,如创建变量张量x1=torch.tensor([1.0],requires_grad=True)
,运行x1.grad
不会返回任何结果,令y=2*x1
,y.backward()
,此时x1.grad=2
,然后令z=3*x1
,z.backward()
,此时x1.grad=5
,即2+3
,也就是每次对某函数关于变量张量求梯度,都会把求得的梯度加到变量张量的梯度属性上。
当前batch的模型输出:计算一个mini-batch的模型输出,其方式为,假设模型的输入有多项(多元函数),以3项x1、x2、x3为例,每项的输入维数为n11×n12、n21、n3,假设输出维数为m,我们输入对应维数的三个张量xx1, xx2, xx3用model(x1=xx1, x2=xx2, x3=xx3)可以得到一个m维张量结果,而在神经网络的训练中我们不是一个样品接一个样品的送进模型中,而是一次性将整个batch送入模型,其方式为若batch size=l,则将整个batch的数据构造成l×n11×n12、l×n21、l×n3的张量X1、X2、X3(注意这里若n3=1,则X3需要是l×1的二维张量而不能是长度为l的一维张量),运行out=model(x1=X1, x2=X2, x3=X3),可以得到一个l×m维的张量out,再将其和因变量实际数据输入loss函数中计算即可。
模型中神经网络的组件部分(如卷积层、全连接层)参数初值是系统随机分配的,自定义参数的数值在定义时自取,有了未知参数初值,然后再按batch来进行反向传播求梯度、更新参数估计值的过程,其中为了节省计算量每个迭代步骤中用当前batch充当整个数据集计算损失函数。训练模型的部分是两层for循环,外层对epoch循环,内层对batch循环,循环内部的程序大致结构为5条程序:
1. 输入当前batch的自变量数据计算模型输出out;
2. 根据out和因变量数据计算loss;
3. optimizer.zero_grad()清零梯度;
4. loss.backward()计算梯度;
5. optimizer.step()更新参数估计值。
在GPU上训练:不特别指定的话模型训练是在CPU上进行的,然而在GPU上训练会更快。具体地,我们需要把模型、优化器、数据这三者移动到GPU上,先用
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
创建GPU设备,然后用
Model().to(device)
将模型移动到设备上(Model是模型类,Model()表示将其实例化)。用
nn.CrossEntropyLoss().to(device)
将损失函数移动到设备上。用
x.to(device)
将数据移动到设备上,x是张量形式的样品。
用model.state_dict()得到模型model的全部参数。