一、安装Anaconda
官网下载,选择安装位置,选择all user,选择配置环境,其余一路next安装即可。
二、创建虚拟环境
- 打开Anaconda Prompt
- 创建虚拟环境,输入:
conda create --name pytorch python=3.8
其中pytorch是虚拟环境的名字。
3. 进入虚拟环境,输入:
conda activate pytorch
注意:关闭虚拟环境的命令如下:
conda deactivate
删除:
conda remove -n 环境名 --all
添加包:
conda install -n 环境名 包名
移除包:
conda remove -n 环境名 包名
进入虚拟环境后更新包:
conda update 包名
- 进入虚拟环境后就可以安装pytorch了,进入官网(https://pytorch.org/),单击Get Start,进入下载,选择如下:
其中run this command就是需要的命令,在pytorch中输入即可。
注意,GPU版本(需要硬件支持,可以在任务管理器->性能里左侧下拉,如果显示GPU说明支持,在复杂的神经网络运行时与CPU版本速度差异较大,其余无差异)的需要选择CUDA,就需要先安装CUDA和CUDNN(安装教程),建议安装GPU版本的。注意CUDA版本!主要要和自己电脑版本一致!(亲测可以和虚拟环境中的不一致,CUDA11.0+Pytorch的CUDA10.2成功了) - 安装Jupyter,输入:
conda install jupyter
- 还可以用conda命令安装需要的其他Python库。
- 验证:可以打开Anaconda Navigator,在界面的Applications on下拉列表框中选择pytorch,然后启动该环境下的Jupyter Notebook(使用教程),输入import语句,如果没有报错说明PyTorch已经安装成功了,还可以在Jupyter Notebook中查看安装的PyTorch的版本(注意有两个下划线)。
其中torch是python的核心库,torchvision包是服务于深度学习框架的,用来生成图片、视频数据集、一些流行的模型类和预训练模型。
三、张量
- 张量(tensor)是pytorch里基础的运算单位,类似于numpy中的数组,但是只有张量可以在GPU版本的pytorch上运行,因此速度更快。
- 张量的创建:
- 随机初始化的张量:
x = torch.randn(2,2)
- 根据Python列表创建张量:
x = torch.tensor([1,2],[3,4])
- 创建一个全零张量:
x = torch.zeros(2,2)
- 创建一个全一张量:
x = torch.ones(2,2)
- 基于现有的张量创建新的张量:
y= torch.ones_like(x)
- 创建指定数据类型的张量:
x = torch.ones(2, 2, dtype=torch.long)
- 随机初始化的张量:
- 张量的数学运算:与数组的数学运算相同。
- 两个张量相加:
- 可以使用’’+’'直接相加。
- 可以使用torch.add()实现张量的相加,和需要赋值给变量进行存储。
- 还可以使用.add_()实现张量的替换。
z = x + y w = torch.add(x, y) #将和保存到y里: y.add_(x)
- 张量的乘法:
- 对应元素相乘.mul():
x = torch.tensor([[1,2],[3,4]]) y = torch.tensor([[1,2],[3,4]]) x.mul(y) #得到结果: #[[1,4], #[9,16]]
- 矩阵相乘.mm():
x = torch.tensor([[1,2],[3,4]]) y = torch.tensor([[1,2],[3,4]]) x.mm(y) #得到结果: #[[7,10], #[15,22]]
- 对应元素相乘.mul():
- 两个张量相加:
- 张量与NumPy数组的转化:
1. 张量转换为NumPy数组,使用.numpy()。
y = x.numpy()
#可以查看当前类型:
print(type(y))
2.NumPy数组转换为张量,使用torch.from_numpy()。
y = torch.from_numpy(x)
- CUDA张量:新建的张量默认保存在CPU里,对于安装了GPU版本的PyTorch,可以移动到GPU里。
a = torch.ones(2,2)
if torch.cuda.is_available():
a_cuda = a.cuda()
四、自动求导
- 深度学习算法的本质就是通过反向传播求导数,由PyTorch的自动求导模块实现,关于张量的所有操作,自动求导模块均可为其自动提供微分。
- 张量默认没有自动求导功能,想要让张量使用自动求导功能就需要在定义张量的时候设置参数’tensor.requires_grad=True’。
x = torch.ones(2, 2, requires_grad = True)
#可以查看
print(x.requires_grad)
- 每个通过函数计算得到的变量都有一个.grad_fn属性。
- 对于下例中的z=求平均(x+y),想要求z对x,z对y的偏导数,首先要对z使用backward()来定义反向传播,然后直接使用x.grad来计算z对x的偏导数。
z = x + y
#mean():求平均
z = z.mean()
#反向传播
z.backward()
#计算z对x的偏导数
print(x.grad)
#计算z对y的偏导数
print(y.grad)
- 上述z是一个标量(标量是零维(只有大小没有方向),向量时一维,矩阵是二维,无论几维都能叫张量),如果z是一个多维张量,则需要在backward()中指定参数(输入一个大小相同的张量),匹配对应的尺寸。
x = torch.ones(2, 2, requires_grad = True)
y = torch.ones(2, 2, requires_grad = True)
z = 2 * x + 3 * y
#反向传播
z.backward(torch.ones_like(z))
#计算z对x的偏导数
print(x.grad)
#计算z对y的偏导数
print(y.grad)
- 可以使用with torch.no_grad()来禁止已经设置requires_grad=True的张量及逆行自动求导。
with torch.no_grad():
print(z.requires_grad)
#输出是False
五、torch.nn和torch.optim的基本框架
- torch.nn:专门为神经网络设计的模块化接口,构建于自动求导模块的基础上,可用来定义和运行神经网络。
1. torch.nn用于搭建一个模型,因此使用前需要导入这个库:
import torch
import torch.nn as nn
2. 使用torch.nn来搭建一个模型的方法是定义一个类:
#net_name是类名可以自由拟定,需要继承父类nn.Module
#nn.Module类是torch.nn中非常重要的类,包含网络各层的定义及forward方法,避免从底层搭建网络的麻烦
class net_name(nn.Module):
def __init__(self):
super(net_name, self).__init__()
#对模型的搭建,这里模型仅仅是一个全连接层(fc),也叫线性层。
#(1,1)中数字分别表示输入和输出的维度,使用此处输入输出维度均为1。
self.fc = nn.Linear(1, 1)
#其他层
def forward(self, x):
#因为模型仅仅是一层全连接层,所以:
out = self.fc(x)
return out
使用该线性回归模型的时候,可以直接新建一个该模型的对象:
net = net_name()
- torch.optim:一个实现了各种优化算法的库,包括最简单的梯度下降、随机梯度下降及其他更复杂的优化算法。
1. 导入torch.optim:
import torch.optim as optim
2. PyTorch中的损失函数:用于计算每个实例的输出与真实的样本标签是否一致,并评估差距的大小。torch.nn可以很方便的定义损失函数,最简单的是nn.MSELoss(),即计算预测试与真实值的均方误差。
criterion = nn.MSELoss()
#output是预测值,target是真实值
loss = criterion(output, target)
3. 在反向传播过程中,常用loss.backward()计算梯度,每次迭代时梯度要先清零,否则会被累加计算。
4. 最简单的优化算法,使用随机梯度下降算法:
#net.parameters()表示模型参数,即待求参数
#lr为学习率
optimizer = optim.SGD(net.parameters(), lr=0.01)
#特别的,其实optimizer可以指定每一层的学习率。
- 总结:一次完整的前向传播加上反向传播需要用到torch.nn和torch.optim包。
注意:所有optimizer都实现了step()方法,这个方法会更新所有的参数,单次迭代只需在梯度被计算好后直接调用;需要重复多次计算函数时,需要传入一个闭包,允许它们从新计算你的模型,这个闭包应当清空梯度,计算损失,然后返回。
单次迭代对应的代码:
#梯度清零,注意每次迭代开始前,梯度都要被清零,否则上次梯度会被叠加
optimizer.zero_grad()
output = net(input)
#计算损失
loss = criterion(output, target)
loss.backward()
#完成更新
optimizer.step()
多次迭代对应的代码:
for input, target in dataset:
def closure():
optimizer.zero_grad()
output = net(input)
loss = criterion(output, target)
loss.backward()
return loss
optimizer.step(closure)
六、线性回归
以下是一个线性回归实例:
- 线性回归基本原理:线性回归一般用于数值预测,就是要找出这样一条拟合曲线或拟合面,能够最大程度地拟合真实的数据分布。我们希望拟合得到的预测值与真实值越接近越好,因此引入代价函数(代价函数越小,表明直线拟合得越好),代价函数是定义在整个训练集上的,是所有样本误差的平均,也就是所有样本损失函数的平均(两者唯一区别就是代价函数针对整个训练集而损失函数针对单个样本)。因此我们需要求最小化代价函数时对应的参数,最小化代价函数的方法就是梯度下降法:在函数曲线上的某一点,函数沿梯度方向具有最大的变化率,那么沿着负梯度方向移动会不断逼近最小值。
- 结合上述概念,本例中拟合直线:y=W0+W1x,代价函数通过均方误差计算得到:J=1/(2m)(∑(1~m)(yi-yi`)^2)其中m表示总样本个数,分母是2m只是为了平方求导的方便。在梯度下降算法中,W0、W1迭代更新表达式:W0=W0-α(J对W0求偏导),W1=W1-α(对W1求偏导),其中α表示学习率。
- 线性回归的PyTorch实现:
1. 数据集:构建数据集。# y=3*x+10,后面加上torch.randn()函数制造噪音 x = torch.unsqueeze(torch.linspace(-1, 1, 50), dim=1) y = 3*x +10 +0.5*torch.randn(x.size())
函数介绍: 1. torch.unsqueeze(input, dim, out=None):返回一个新的张量,对输入的既定位置插入维度 1。 注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。如果dim为负,则将会被转化dim+input.dim()+1 参数:tensor (Tensor):输入张量。 dim (int):插入维度的索引。 out (Tensor, optional):结果张量。 区别:unsqueeze_ 和 unsqueeze 实现一样的功能,区别在于 unsqueeze_ 是 in_place 操作,即 unsqueeze 不会对使用 unsqueeze 的 tensor 进行改变,想要获取 unsqueeze 后的值必须赋予个新值, unsqueeze_ 则会对自己改变。 2. 补充介绍:torch.squeeze(input, dim=None, out=None):降维,将输入张量形状中的1 去除并返回,当给定dim时,那么挤压操作只在给定维度上。 注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。 参数:input (Tensor):输入张量。 dim (int, optional):如果给定,则input只会在给定维度挤压。 out (Tensor, optional):输出张量。 原因:多维张量本质上就是一个变换,如果维度是 1 ,那么,1 仅仅起到扩充维度的作用,而没有其他用途,因而,在进行降维操作时,为了加快计算,是可以去掉这些 1 的维度。 3. torch.linspace(start, end, steps=100, out=None):返回一个1维张量,包含在区间start和end上均匀间隔的step个点。输出张量的长度由steps决定。 参数:start (float):区间的起始点。 end (float):区间的终点。 steps (int):在start和end间生成的样本数。 out (Tensor, optional):结果张量。 2. 模型定义:定义线性回归模型、损失函数、优化函数。
#定义线性回归模型
class LinearRegression(nn.Module):
def __init__(self):
super(LinearRegression, self).__init__()
self.fc = nn.Linear(1, 1)
def forward(self, x):
out = self.fc(x)
return out
model = LinearRegression() #实例化需要带括号
#定义损失函数、优化函数
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=5e-3)
4. 模型训练:如下是迭代1000次(遍历整个数据集的次数)的代码,需要先向前传播计算代价函数,然后向后传播计算梯度。
前向传播与反向传播(后续笔记里会讲)
num_epochs = 1000 #遍历训练集的次数
for epoch in range(num_epochs):
#forward
out = model(x) #向前传播
loss = criterion(out, y) #计算损失函数
#backward
optimizer.zero_grad() #梯度归零
loss.backward() #反向传播
optimizer.step() #更新参数
5. 模型测试:通过model.eval()函数将模型由训练模式变为测试模式,将数据放入模型中进行预测。
model.eval()
y_hat = model(x) #训练好的线性回归模型的预测值
plt.scatter(x.numpy(), y.numpy(), label='原始数据')
#模型训练阶段需要跟踪梯度,但是模型测试阶段不需要,因此需要.detach()停止张量的梯度跟踪
plt.plot(x.numpy(), y_hat.detach().numpy(), c='r', label='拟合直线')
#显示图例
plt.legend()
plt.show()
可以查看这条直线的参数W0和W1:
list(model.name_parameters())
6. 全部代码及结果展示:
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
from matplotlib import pyplot as plt
import matplotlib
# y=3*x+10,后面加上torch.randn()函数制造噪音
x = torch.unsqueeze(torch.linspace(-1, 1, 50), dim=1)
y = 3*x +10 +0.5*torch.randn(x.size())
#定义线性回归模型
class LinearRegression_my(nn.Module):
def __init__(self):
super(LinearRegression_my, self).__init__()
self.fc = nn.Linear(1, 1)
def forward(self, x):
out = self.fc(x)
return out
model_my = LinearRegression_my()
#定义损失函数、优化函数
criterion = nn.MSELoss()
optimizer = optim.SGD(model_my.parameters(), lr=5e-3)
num_epochs = 1000 #遍历训练集的次数
for epoch in range(num_epochs):
#forward
out = model_my(x) #向前传播
loss = criterion(out, y) #计算损失函数
#backward
optimizer.zero_grad() #梯度归零
loss.backward() #反向传播
optimizer.step() #更新参数
model_my.eval()
y_hat = model_my(x) #训练好的线性回归模型的预测值
plt.scatter(x.numpy(), y.numpy(), label='原始数据')
#模型训练阶段需要跟踪梯度,但是模型测试阶段不需要,因此需要.detach()停止张量的梯度跟踪
plt.plot(x.numpy(), y_hat.detach().numpy(), c='r', label='拟合直线')
#显示图例
plt.legend()
plt.show()
list(model_my.named_parameters())