PyTorch 学习笔记

PyTorch 简介

PyTorch是一个基于Python的科学计算包,它主要有两个用途:

  • 类似Numpy但是能利用GPU加速
  • 一个非常灵活和快速的用于深度学习的研究平台

PyTorch 安装

需要配置好CUDA,然后安装对应版本的torch,可以选择下载好包后本地pip install

Tensor

Tensor类似于numpy的ndarry,但是可以用GPU加速来计算。

使用前需要导入torch的包:

from __future__ import print_function
import torch

Tensor的主要函数

Tensor的一些主要函数:

x = torch.empty(5, 3)  # 构造一个5x5的未初始化矩阵
print(x)

# tensor([[9.2755e-39, 1.0561e-38, 4.5919e-39],
#         [4.5000e-39, 4.4082e-39, 5.9694e-39],
#         [1.0286e-38, 8.9081e-39, 8.9082e-39],
#         [6.9796e-39, 9.0919e-39, 9.9184e-39],
#         [7.3470e-39, 1.0194e-38, 1.0469e-38]])

x = torch.rand(5, 3) # 随机初始化一个5x3的矩阵
print(x)

# 输出
# tensor([[0.8470, 0.3398, 0.5657],
#         [0.2300, 0.8812, 0.5408],
#         [0.0290, 0.9524, 0.5187],
#         [0.8870, 0.7722, 0.1659],
#         [0.1848, 0.3879, 0.9369]])

x = torch.zeros(5, 3, dtype=torch.long) # 构造一个零矩阵,类型是long
print(x)

#输出:
# tensor([[ 0,  0,  0],
#		  [ 0,  0,  0],
#		  [ 0,  0,  0],
#		  [ 0,  0,  0],
#		  [ 0,  0,  0]])

x = torch.tensor([5.5, 3]) # 用数组来构造tensor
print(x)

# 输出
# tensor([5.5000, 3.0000])

# 用现有的tensor来构造新的tensor(可以用不同的dtype)
x = x.new_ones(5, 3, dtype=torch.double)      # new_* methods take in sizes
print(x)
x = torch.randn_like(x, dtype=torch.float)    # override dtype!
print(x)

print(x.size())  # 用size()来查看tensor的shape

Operation

Operation一般可以使用函数的方式使用,但是为了方便使用,PyTorch重载了一些常见的运算符。

x = torch.rand(5, 3) # 随机初始化一个5x3的矩阵
y = torch.rand(5, 3)
print(x + y) # tensor的加法
print(torch.add(x, y)) # 用add函数来实现加法

result = torch.empty(5, 3) # 给加法提供一个返回值
torch.add(x, y, out=result) # x + y的结果放到result里。
print(result)

# 把相加的结果直接加到第一个加数上
y.add_(x) # 把x加到y
print(y)

就地修改tensor的operation以下划线结尾。比如: x.copy_(y), x.t_(), 都会修改x。

Tensor的变换

# 用类似于下标的形式来操作Tensor
#打印x的第一列
print(x[:, 1])

# 同tensor.view来reShape一个Tensor
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # -1的意思是让PyTorch自己推断出第一维的大小。
print(x.size(), y.size(), z.size())

# 如果一个tensor只有一个元素,可以使用item()函数来把它变成一个Python number
x = torch.randn(1)
print(x) # 输出的是一个Tensor
print(x.item()) # 输出的是一个数

Tensor、Numpy桥

Tensor和Numpy的相互转换很容易,但是要注意一点:它们会共享地址,修改一方会影响另一方

a = torch.ones(5)
print(a)
# tensor([ 1.,  1.,  1.,  1.,  1.])
b = a.numpy()
print(b)
# [1. 1. 1. 1. 1.]

a.add_(1)
print(a)
# tensor([ 2.,  2.,  2.,  2.,  2.])
print(b)
# [2. 2. 2. 2. 2.]

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
# [2. 2. 2. 2. 2.]
print(b)
# tensor([ 2.,  2.,  2.,  2.,  2.], dtype=torch.float64)

CPU上的所有类型的Tensor(除了CharTensor)都可以和Numpy数组来回转换。

Tensor+CDUA

Tensor可以用to方法来移到任意设备上

from __future__ import print_function
import torch
x = torch.randn(4, 4)
# 如果有CUDA
# 我们会使用``torch.device``来把tensors放到GPU上
if torch.cuda.is_available():
    device = torch.device("cuda")  # 一个CUDA device对象。
    y = torch.ones_like(x, device=device)  # 直接在GPU上创建tensor
    x = x.to(device)  # 也可以使用``.to("cuda")``把一个tensor从CPU移到GPU上
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))  # ``.to``也可以在移动的过程中修改dtype

# 输出
# tensor([[-0.4190,  0.2814, -0.0858,  1.1612],
#         [ 0.2115, -0.9426,  0.9703,  1.7138],
#         [ 1.8401, -0.5685,  0.9426,  2.0110],
#         [ 0.9490,  1.4329,  2.9558,  1.1631]], device='cuda:0')
# tensor([[-0.4190,  0.2814, -0.0858,  1.1612],
#         [ 0.2115, -0.9426,  0.9703,  1.7138],
#         [ 1.8401, -0.5685,  0.9426,  2.0110],
#         [ 0.9490,  1.4329,  2.9558,  1.1631]], dtype=torch.float64)

PyTorch中的神经网络

Autograd: 自动求导

Autograd为所有用于Tensor的operation提供自动求导的功能

image-20210130150606457

autograd.Variable 是这个包中最核心的类。 它包装了一个Tensor,并且几乎支持所有的定义在其上的操作。一旦完成了运算,可以调用 .backward()来自动计算出所有的梯度。

.data用来访问原始的 Tensor;

.grad用来累加这一 Variable 的梯度;

还有一个在自动求导中非常重要的类 Function,Variable 和 Function 二者相互联系并且构建了一个描述整个运算过程的无环图。每个 Variable 拥有一个 .grad_fn 属性,其引用了一个创建 Variable 的 Function。(除了用户创建的Variable 其 creator 部分是 None)。

计算Tensor的梯度,可以对一个Tensor调用它的backward()方法。如果这个Tensor是一个scalar(只有一个数),那么调用时不需要传任何参数。如果Tensor多于一个数,那么需要传入和它的shape一样的参数,表示反向传播过来的梯度。

from __future__ import print_function
from torch.autograd import Variable
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)

tensor([[1., 1.],
[1., 1.]], requires_grad=True)

y = x + 2
print(y)

tensor([[3., 3.],
[3., 3.]], grad_fn=)

print(y.grad_fn)

<AddBackward0 object at 0x0000026081C97148>

z = y * y * 3
out = z.mean()
print(z, out)

tensor([[27., 27.],
[27., 27.]], grad_fn=) tensor(27., grad_fn=)

requires_grad_()函数会修改一个Tensor的requires_grad。

梯度

out.backward()
print(x.grad)

tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])

o = 1 4 ∑ i z i , z i = 3 ( x i + 2 ) 2 , z i ∣ x i = 1 = 27 , o=\frac{1}{4} \sum_{i} z_{i}, \\ z_{i}=3\left(x_{i}+2\right)^{2},\\[8pt] \left.z_{i}\right|_{x_{i}=1}=27, o=41izi,zi=3(xi+2)2,zixi=1=27,

因 此 , ∂ o ∂ x i = 3 2 ( x i + 2 ) , \text因此,\quad \frac{\partial o}{\partial x_{i}}=\frac{3}{2}\left(x_{i}+2\right), ,xio=23(xi+2),

最 后 , 有 ∂ o ∂ x i ∣ x i = 1 = 9 2 = 4.5 \text最后,有 \left.\frac{\partial o}{\partial x_{i}}\right|_{x_{i}=1}=\frac{9}{2}=4.5 xioxi=1=29=4.5

神经网络

神经网络可以使用torch.nn包来创建,nn使用我们上面了解过的autograd来进行模型的定义和求导。nn.Module包含了许多神经网络层(layer),并且forward(input)方法能返回output。


识别MNIST数据的神经网络

训练神经网络的基本步骤:

  1. 定义一个神经网络,包含一些可以训练的参数;
  2. 迭代一个训练数据集;
  3. 处理数据的输入;
  4. 计算loss(调用Module对象的forward方法);
  5. 计算loss对参数的梯度;
  6. 更新参数(通常使用梯度下降法来更新)weight = weight - learning_rate $ {\star} $ gradient

定义神经网络

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个通道的灰度图,输出6个通道(feature map),使用5x5的卷积核
		self.conv1 = nn.Conv2d(1, 6, 5)
		# 第二个卷积层的卷积核也是5x5,有16个通道
		self.conv2 = nn.Conv2d(6, 16, 5)
		# 全连接层
		self.fc1 = nn.Linear(16 * 5 * 5, 120)
		self.fc2 = nn.Linear(120, 84)
		self.fc3 = nn.Linear(84, 10)
	
	def forward(self, x):
		# 32x32 -> 28x28 -> 14x14 
		x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
		# 14x14 -> 10x10 -> 5x5
		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:]  # 除了batch维度之外的其它维度。
		num_features = 1
		for s in size:
		num_features *= s
		return num_features


net = Net()
print(net)
# 输出
# Net(
#   (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
#   (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
#   (fc1): Linear(in_features=400, out_features=120, bias=True)
#   (fc2): Linear(in_features=120, out_features=84, bias=True)
#   (fc3): Linear(in_features=84, out_features=10, bias=True)
# )

num_flat_features方法:将所有特征展开,然后传给下面只能接收一维特征的层,比如全连接层。可以写成:x.view(-1, x.size()[1:].numel())

只需要定义forward函数,backward函数会自动通过autograd来生成。

  • 方法net.parameters()可以返回所有模型中可学习的参数
params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1的weight
# 输出
# 10
# torch.Size([6, 1, 5, 5])

测试神经网络

随机生成一个32x32的输入来检验网络的定义没有问题。

input = torch.randn(1, 1, 32, 32)
print(input)
out = net(input)
print(out)
# 输出
# tensor([[[[-1.1902, -1.0143, -0.6984,  ...,  1.9511, -0.0156,  0.9260],
#           [-0.9327,  0.0150,  1.0843,  ..., -0.2636, -0.1169, -0.9445],
#           [-0.1803, -0.9106,  0.1845,  ..., -2.1039, -1.7185,  2.2898],
#           ...,
#           [-2.1018,  0.3044,  0.1973,  ..., -1.6752, -0.1317, -0.0458],
#           [-1.3928,  1.3621,  0.3082,  ..., -0.8913, -0.4222,  0.7720],
#           [ 0.8327, -0.2097,  1.3220,  ..., -0.5530, -0.1954, -0.6375]]]])
# tensor([[-0.0855,  0.0118, -0.0287,  0.0216,  0.0339, -0.0606,  0.0537,  0.1262,
#          -0.1251, -0.0410]], grad_fn=<AddmmBackward>)

由于梯度默认会叠加,所以我们通常在backward之前清除掉之前的梯度。

net.zero_grad()
out.backward(torch.randn(1, 10))
backward函数详解

Pytorch中是根据前向传播生成计算图的,如果最终生成的函数是标量,是一般情况下的backward反向传播,但是事实上backward中有个retain_graph和create_graph参数。

一般情况:
import torch
x = torch.tensor([1.0,2.0],requires_grad=True)
y = (x + 2)**2
z = torch.mean(y)
z.backward()
x.grad

# 输出
# tensor([3., 4.])

z = ( x 1 + 2 ) 2 + ( x 2 + 2 ) 2 2 ∂ z ∂ x 1 = x 1 + 2 = 3.0 ∂ z ∂ x 2 = x 2 + 2 = 4.0 \begin{aligned} z= \frac{\left(x_{1}+2\right)^{2}+\left(x_{2}+2\right)^{2}}{2} \end{aligned}\\\begin{aligned} \frac{\partial z}{\partial x_{1}}=x_{1}+2=3.0\end{aligned} \\\begin{aligned} \frac{\partial z}{\partial x_{2}}=x_{2}+2=4.0 \end{aligned} z=2(x1+2)2+(x2+2)2x1z=x1+2=3.0x2z=x2+2=4.0

x = torch.tensor([1.0,2.0,3.0],requires_grad=True)
y = (x + 2)**2
z = 4*y
z.backward()
# 会报错,因为只有标量输出才能求导

解决方法:为backward添加一个tensor参数:

x = torch.tensor([1.0,2.0,3.0],requires_grad=True)
y = (x + 2)**2
z = 4*y
z.backward(torch.tensor([1.,1.,1.]))
print(x.grad)
# 输出
# tensor([24., 32., 40.])

z = [ 4 ∗ ( x 1 + 2 ) 2 , 4 ∗ ( x 2 + 2 ) 2 ] ∂ z 1 ∂ x 1 = 8 ∗ ( x 1 + 2 ) = 24.0 ∂ z 2 ∂ x 2 = 8 ∗ ( x 2 + 2 ) = 32.0 ∂ z 2 ∂ x 3 = 8 ∗ ( x 3 + 2 ) = 40.0 \begin{aligned} z=[4 *&\left.\left(x_{1}+2\right)^{2}, 4 *\left(x_{2}+2\right)^{2}\right] \\ \frac{\partial z_{1}}{\partial x_{1}} &=8 *\left(x_{1}+2\right)=24.0 \\ \frac{\partial z_{2}}{\partial x_{2}} &=8 *\left(x_{2}+2\right)=32.0 \\ \frac{\partial z_{2}}{\partial x_{3}} &=8 *\left(x_{3}+2\right)=40.0 \end{aligned} z=[4x1z1x2z2x3z2(x1+2)2,4(x2+2)2]=8(x1+2)=24.0=8(x2+2)=32.0=8(x3+2)=40.0

添加的这个tensor是待求得x得梯度的系数,如果将其改变为[10.,10.,10.],则会变为tensor([240., 320., 400.])

计算损失函数

损失函数的参数是(output, target)元组对,output是模型的预测,target是实际的值。损失函数会计算预测值和真实值的差别,损失越小说明预测的越准。

PyTorch提供了这里有许多不同的损失函数:跳转链接

最简单的一个损失函数是:nn.MSELoss,它会计算预测值和真实值的均方误差。

output = net(input)
target = torch.arange(1, 11)  # 随便伪造的一个“真实值” 
target = target.view(1, -1)  # 把它变成output的shape(1, 10) 
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

计算梯度

在调用loss.backward()之前,我们需要清除掉tensor里之前的梯度,否则会累加进去。

net.zero_grad()     # 清掉tensor里缓存的梯度值。
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

更新参数

  • 更新参数最简单的方法是使用随机梯度下降(SGD):
learning_rate = 0.01
for f in net.parameters():
	f.data.sub_(f.grad.data * learning_rate)
  • 通常我们会使用更加复杂的优化方法,比如SGD, Nesterov-SGD, Adam, RMSProp等等。为了实现这些算法,我们可以使用torch.optim包,它的用法也非常简单:
import torch.optim as optim

# 创建optimizer,需要传入参数和learning rate
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 清除梯度
optimizer.zero_grad()  
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # optimizer会自动帮我们更新参数

注意:即使使用optimizer,我们也需要清零梯度。但是我们不需要一个个的清除,而是用optimizer.zero_grad()一次清除所有。

参考资料

[1]. PyTorch深度学习:60分钟入门(Translation) 胡莱人形;

[2]. Pytorch中的backward函数 不吃猪肉的M;

[3]. PyTorch中文文档

附加产物

在服务器上部署了 J u p y t e r N o t e b o o k Jupyter Notebook JupyterNotebook,并且增加了多用户服务,虽然用起来没有markdown方便,但是拿来跑跑代码还是很清晰的,需要的朋友可以联系我获取链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值