安装
在PyTorch官网上选择对应的版本,然后下面会出现对应的安装命令
一般安装命令如下:
pip3 install torch torchvision torchaudio
查看版本
import torch
print(torch.__version__)
print("cuda:",torch.cuda.is_available())
2.0.0
cuda: False
import torch
x = torch.rand(5, 3)
print(x)
tensor([[0.5553, 0.0181, 0.7441],
[0.4689, 0.3908, 0.6449],
[0.9274, 0.1054, 0.7166],
[0.3487, 0.2997, 0.3689],
[0.3598, 0.7262, 0.9564]])
基础知识
张量(Tensors)
声明和定义
- torch.empty(): 声明一个未初始化的矩阵。创建张量时,它将创建一个具有指定形状的未初始化张量。这意味着张量中的元素可能包含任意值,取决于分配内存时内存中已有的数据。
x = torch.empty(5,3)
print(x)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
- torch.rand():随机初始化一个矩阵
rand_x = torch.rand(5,3)
print(rand_x)
tensor([[0.5710, 0.4795, 0.3452],
[0.4638, 0.3674, 0.7197],
[0.7879, 0.7133, 0.5328],
[0.3456, 0.7724, 0.8772],
[0.5487, 0.5881, 0.9586]])
- torch.zeros():创建数值皆为 0 的矩阵,也可以创建都是1的矩阵。torch.ones
# 创建一个数值皆是 0,类型为 long 的矩阵
zero_x = torch.zeros(5, 3, dtype=torch.long)
print(zero_x.shape,zero_x)
torch.Size([5, 3]) tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
- torch.tensor():直接传递 tensor 数值来创建
# tensor 数值是 [5.5, 3]
tensor1 = torch.tensor([5.5, 3])
print(tensor1.shape,tensor1)
torch.Size([2]) tensor([5.5000, 3.0000])
- tensor.new_ones():new_*() 方法需要输入尺寸大小
# 显示定义新的尺寸是 5*3,数值类型是 torch.double
tensor2 = tensor1.new_ones(5, 3, dtype=torch.double) # new_* 方法需要输入 tensor 大小
print(tensor2)
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
- torch.randn_like(old_tensor):保留相同的尺寸大小
# 修改数值类型
tensor3 = torch.randn_like(tensor2, dtype=torch.float)
print('tensor3: ', tensor3,"size:",tensor3.shape)
tensor3: tensor([[-0.0608, 1.5656, 0.4083],
[ 1.1475, -0.7965, 0.8246],
[-1.1804, -0.4327, -1.2193],
[ 1.5783, 0.0615, 0.1998],
[ 0.9681, -0.1788, -0.2666]]) size: torch.Size([5, 3])
操作
加法操作,示例如下:
- “+” 运算符
- torch.add(tensor1, tensor2, [out=tensor3])
- tensor1.add_(tensor2):直接修改 tensor 变量
可以改变 tensor 变量的操作都带有一个后缀 , 例如 x.copy(y), x.t_() 都可以改变 x 变量
tensor4 = torch.rand(5,3)
print("tensor3 + tensor4 = ",tensor4 + tensor3)
print('tensor3 + tensor4= ', torch.add(tensor3, tensor4))
# 新声明一个 tensor 变量保存加法操作的结果
result = torch.empty(5, 3)
torch.add(tensor3, tensor4, out=result)
print('add result= ', result)
# 直接修改变量
tensor3.add_(tensor4)
print('tensor3= ', tensor3)
tensor3 + tensor4 = tensor([[ 0.9336, 1.6787, 0.9975],
[ 1.3947, 0.1874, 0.8959],
[-0.2482, 0.5251, -0.8883],
[ 1.9656, 0.2401, 0.3338],
[ 1.0201, -0.0755, 0.1412]])
tensor3 + tensor4= tensor([[ 0.9336, 1.6787, 0.9975],
[ 1.3947, 0.1874, 0.8959],
[-0.2482, 0.5251, -0.8883],
[ 1.9656, 0.2401, 0.3338],
[ 1.0201, -0.0755, 0.1412]])
add result= tensor([[ 0.9336, 1.6787, 0.9975],
[ 1.3947, 0.1874, 0.8959],
[-0.2482, 0.5251, -0.8883],
[ 1.9656, 0.2401, 0.3338],
[ 1.0201, -0.0755, 0.1412]])
tensor3= tensor([[ 0.9336, 1.6787, 0.9975],
[ 1.3947, 0.1874, 0.8959],
[-0.2482, 0.5251, -0.8883],
[ 1.9656, 0.2401, 0.3338],
[ 1.0201, -0.0755, 0.1412]])
除了加法运算操作,对于 Tensor 的访问,和 Numpy 对数组类似,可以使用索引来访问某一维的数据,如下所示:
# 访问 tensor3 第一列数据
print(tensor3[:, 0])
tensor([ 0.9336, 1.3947, -0.2482, 1.9656, 1.0201])
对 Tensor 的尺寸修改,可以采用 torch.view() ,如下所示:
x = torch.randn(4, 4)
y = x.view(16)
# 第一维的大小由-1自动推断,第二维的大小为8
z = x.view(-1, 8)
print(x.size(), y.size(), z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
如果 tensor 仅有一个元素,可以采用 .item() 来获取类似 Python 中整数类型的数值:
x = torch.randn(1)
print(x)
print(x.item())
tensor([-0.6999])
-0.6999301314353943
Numpy数组转换
Tensor 和 Numpy 的数组可以相互转换,并且两者转换后共享在 CPU 下的内存空间,即改变其中一个的数值,另一个变量也会随之改变。
Tensor转换为Numpy数组
a = torch.ones(5)
print(a)
b = a.numpy()
print(b)
tensor([1., 1., 1., 1., 1.])
[1. 1. 1. 1. 1.]
两者是共享同个内存空间的,例子如下所示,修改 tensor 变量 a,看看从 a 转换得到的 Numpy 数组变量 b 是否发生变化。
a.add_(1)
print(a)
print(b)
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]
Numpy数组转换成Tensor
在 CPU 上,除了 CharTensor 外的所有 Tensor 类型变量,都支持和 Numpy数组的相互转换操作。
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a.shape,a.dtype,a)
print(b.shape,b.dtype,b)
(5,) float64 [2. 2. 2. 2. 2.]
torch.Size([5]) torch.float64 tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
CUDA 张量
Tensors 可以通过 .to 方法转换到不同的设备上,即 CPU 或者 GPU 上。例子如下所示
# 当 CUDA 可用的时候,可用运行下方这段代码,采用 torch.device() 方法来改变 tensors 是否在 GPU 上进行计算操作
if torch.cuda.is_available():
device = torch.device("cuda") # 定义一个 CUDA 设备对象
y = torch.ones_like(x, device=device) # 显示创建在 GPU 上的一个 tensor
x = x.to(device) # 也可以采用 .to("cuda")
z = x + y
print(z)
print(z.to("cpu", torch.double)) # .to() 方法也可以改变数值类型
神经网络
对于神经网络来说,一个标准的训练流程是这样的:
- 定义一个多层的神经网络
- 对数据集的预处理并准备作为网络的输入
- 将数据输入到网络
- 计算网络的损失
- 反向传播,计算梯度
更新网络的梯度,一个简单的更新规则是 weight = weight – learning_rate * 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__()
# 输入图像是单通道,conv1 kenrnel size=5*5,输出通道 6
self.conv1 = nn.Conv2d(1, 6, 5)
# conv2 kernel size=5*5, 输出通道 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):
# max-pooling 采用一个 (2,2) 的滑动窗口
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 核(kernel)大小是方形的话,可仅定义一个数字,如 (2,2) 用 2 即可
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):
# 除了 batch 维度外的所有维度
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
params = list(net.parameters())
print("参数量:",len(params))
# conv1.weight
print("第一个参数大小:",params[0].size())
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)
)
参数量: 10
第一个参数大小: torch.Size([6, 1, 5, 5])
# 验证网络的正确性
input = torch.randn(1,1,32,32)
out = net(input)
print(out)
tensor([[ 0.0498, -0.0137, -0.0628, -0.1135, -0.0288, 0.0982, -0.0134, 0.0994,
0.0788, -0.1159]], grad_fn=<AddmmBackward0>)
# 清空所有参数的梯度缓存,然后计算随机梯度进行反向传播
net.zero_grad()
out.backward(torch.randn(1,10))
损失函数
损失函数的输入是 (output, target) ,即网络输出和真实标签对的数据,然后返回一个数值表示网络输出和真实标签的差距。
out = net(input)
print("out.shape:",out.shape)
# 定义伪标签,调整大小,需要保持和output一样的size
target = torch.randn(1,10)
print(target.shape)
# 定义均方误差
criterion = nn.MSELoss()
loss = criterion(out,target)
print(loss)
out.shape: torch.Size([1, 10])
torch.Size([1, 10])
tensor(1.2594, grad_fn=<MseLossBackward0>)
反向传播
反向传播的实现只需要调用 loss.backward() 即可,当然首先需要清空当前梯度缓存,即.zero_grad() 方法,否则之前的梯度会累加到当前的梯度,这样会影响权值参数的更新。
下面是一个简单的例子,以 conv1 层的偏置参数 bias 在反向传播前后的结果为例:
# 清空所有参数的梯度缓存
net.zero_grad()
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
conv1.bias.grad before backward
None
conv1.bias.grad after backward
tensor([ 0.0010, -0.0127, 0.0054, 0.0048, -0.0075, 0.0059])
更新权重
采用随机梯度下降(Stochastic Gradient Descent, SGD)方法的最简单的更新权重规则如下:
weight = weight – learning_rate * gradient
按照这个规则,代码实现如下所示:
# 简单实现权重的更新例子
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data*learning_rate)
# 其他优化算法,可以使用torch.optim库
import torch.optim as optim
optimizer = optim.SGD(net.parameters(),lr=0.01)
# 在训练过程中执行下列操作
optimizer.zero_grad() # 清空梯度
output = net(input)
loss = criterion(output,target)
loss.backward()
# 更新权重
optimizer.step()
训练分类器
- pytorch有一个专门的torchvision库,包含了dataloader和dataset
- 使用CIFAR10为例
训练图片分类器
一般流程如下:
- 通过调用
torchvision
加载和归一化CIFAR10训练集和测试集 - 构建一个卷积神经网络
- 定义一个损失函数
- 在训练集上训练网络
- 在测试集上测试网络性能
# 1.加载和归一化CIFAR10
# 导入必须包
import torch
import torchvision
import torchvision.transforms as transforms
# torchvision 的数据集输出的图片都是 PILImage ,即取值范围是 [0, 1] ,这里需要做一个转换,变成取值范围是 [-1, 1]
# 定义了一个组合变换
transform = transforms.Compose([
transforms.ToTensor(), # PIL Image或者Numpy ndarray转换成Pytorch的张量
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) # 图像数据执行标准化操作,第一个三元组表示每个通道的均值 (0.5, 0.5, 0.5),而第二个三元组表示每个通道的标准差 (0.5, 0.5, 0.5)。
])
# 训练集
trainset = torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
trainloader = torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=2)
# 测试集
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
# 10个分类
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
Files already downloaded and verified
Files already downloaded and verified
# 可视化训练图片,也可以使用TensorBoard
import matplotlib.pyplot as plt
import numpy as np
# 展示图片
def imshow(img):
print(img.shape)
img = img/2 +0.5 # 对图像进行逆归一化。在进行训练前,图像很可能已经被归一化,其像素值范围在[-1, 1]之间。逆归一化之后,像素值范围将恢复到 [0, 1],从而能够以原始形式正确显示图像。
npimg = img.numpy() # pytorch张量转换成numpy
plt.imshow(np.transpose(npimg,(1,2,0))) # np.transpose() 对 NumPy 数组 npimg 进行转置,第1个轴(高度 H)变为新的第 0 个轴 2=>1 0=>2
plt.show()
# 随机获取训练集图片
dataiter = iter(trainloader)
# 返回的图片数量和设置的batch_size有关
images,labels = next(dataiter)
print(images.shape,labels)
# 展示图片
imshow(torchvision.utils.make_grid(images))
# 打印图片标签
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
torch.Size([4, 3, 32, 32]) tensor([7, 7, 9, 8])
torch.Size([3, 36, 138])
horse horse truck ship
附录
本文都是由python的notebook编写,包括结果都是运行后生成的结果。最终通过notebook转换成markdown,命令如下:
jupyter nbconvert torch_basic.ipynb --to markdown
参考
小土堆pytorch视频教程的代码
Pytorch学习
PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】笔记