https://handbook.pytorch.wiki/chapter1/1.3-deep-learning-with-pytorch-60-minute-blitz.html

改变jupter notebook默认文件夹位置
https://blog.csdn.net/limanjihe/article/details/106317245

安装pytorch

1
2
import torch
torch.__version__

1 tensor

Tensor的基本数据类型有五种: - 32位浮点型:torch.FloatTensor。 (默认) - 64位整型:torch.LongTensor。 - 32位整型:torch.IntTensor。 - 16位整型:torch.ShortTensor。 - 64位浮点型:torch.DoubleTensor。

除以上数字类型外,还有 byte和chart型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建矩阵
x=torch.empty(5,4)
print(x)

x=torch.rand(5,4)
print(x)

x = torch.zeros(5, 3, dtype=torch.long)
print(x)

x = torch.tensor([5.5, 3])
print(x)

x = torch.randn_like(x, dtype=torch.float) # 覆盖 dtype!
print(x)

torch.Size([5, 3,3])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 任何 以``_`` 结尾的操作都会用结果替换原变量. 例如: ``x.copy_(y)``, ``x.t_()``, 都会改变 ``x``.
y=torch.rand(5,3)
print(y)
x=torch.rand(5,3)
print(x)
y.add_(x)
print(y)

print(y[:,1])
print(y[:1])

# torch.view: 可以改变张量的维度和大小
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # size -1 从其他维度推断
print(x.size(), y.size(), z.size())
print(x, y, z)

# 如果你有只有一个元素的张量,使用.item()来得到Python数据类型的数值
x = torch.randn(1)
print(x)
print(x.item())

# 将torch tensor转为numpy数组
# Torch Tensor与NumPy数组共享底层内存地址,他们之间的转换很快,而且几乎不会消耗什么资源。修改一个会导致另一个的变化。
import numpy
t=x.numpy()
print(t)
a=numpy.ones(4)
b=torch.from_numpy(a)
numpy.add(a,1,out=a)
print(b)
1
2
3
4
5
6
7
8
9
10
11
# is_available 函数判断是否有cuda可以使用
# ``torch.device``将张量移动到指定的设备中
print(torch.cuda.is_available())

if torch.cuda.is_available():
device = torch.device("cuda") # a CUDA 设备对象
y = torch.ones_like(x, device=device) # 直接从GPU创建张量
x = x.to(device) # 或者直接使用``.to("cuda")``将张量移动到cuda中
z = x + y
print(z)
print(z.to("cpu", torch.double)) # ``.to`` 也会对变量的类型做更改

2 Autograd

https://pytorch.org/docs/autograd

当完成计算后通过调用 .backward(),自动计算所有的梯度, 这个张量的所有梯度将会自动积累到 .grad 属性。

要阻止张量跟踪历史记录,可以调用.detach()方法将其与计算历史记录分离,并禁止跟踪它将来的计算记录。

为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():中。 在评估模型时特别有用,因为模型可能具有requires_grad = True的可训练参数,但是我们不需要梯度计算。

Tensor 和 Function互相连接并生成一个非循环图,它表示和存储了完整的计算历史。 每个张量都有一个.grad_fn属性,这个属性引用了一个创建了Tensor的Function(除非这个张量是用户手动创建的,即,这个张量的 grad_fn 是 None)。

https://zhuanlan.zhihu.com/p/83172023

grad: 该Tensor的梯度值,每次在计算backward时都需要将前一时刻的梯度归零,否则梯度值会一直累加。

grad_fn: 叶子节点通常为None,只有结果节点的grad_fn才有效,用于指示梯度函数是哪种类型。例如上面示例代码中的y.grad_fn=<PowBackward0 at 0x213550af048>, z.grad_fn=<AddBackward0 at 0x2135df11be0>

torch.autograd.backward(
tensors,
grad_tensors=None,
retain_graph=None,
create_graph=False,
grad_variables=None)

tensor: 用于计算梯度的tensor。也就是说这两种方式是等价的
torch.autograd.backward(z) == z.backward()
grad_tensors: 在计算矩阵的梯度时会用到。他其实也是一个tensor,shape一般需要和前面的tensor保持一致。
retain_graph: 通常在调用一次backward后,pytorch会自动把计算图销毁,所以要想对某个变量重复调用backward,则需要将该参数设置为True
create_graph: 当设置为True的时候可以用来计算更高阶的梯度
grad_variables: 这个官方说法是grad_variables' is deprecated. Use 'grad_tensors' instead.也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就好了。

torch.autograd.grad(
outputs,
inputs,
grad_outputs=None,
retain_graph=None,
create_graph=False,
only_inputs=True,
allow_unused=False)

grad_outputs: 类似于backward方法中的grad_tensors
only_inputs: 默认为True, 如果为True, 则只会返回指定input的梯度值。 若为False,则会计算所有叶子节点的梯度,并且将计算得到的梯度累加到各自的.grad属性上去。
allow_unused: 默认为False, 即必须要指定input,如果没有指定的话则报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
x=torch.ones(2,2,requires_grad=True)
print(x)

y=x+2
print(y)
print(y.grad_fn) # 用于指示梯度函数是哪种类型

z = y * y * 3+y
out = z.mean()

print(z, out)

# Python的 dir() 返回参数的属性、方法列表。
# dir(z)
z.grad_fn.next_functions

dir(z)返回很多,我们直接排除掉一些Python中特殊方法(以__开头和结束的)和私有方法(以_开头的,直接看几个比较主要的属性: .is_leaf:记录是否是叶子节点。通过这个属性来确定这个变量的类型 在官方文档中所说的“graph leaves”,“leaf variables”,都是指像x,y这样的手动创建的、而非运算得到的变量,这些变量称为创建变量。 像z这样的,是通过计算后得到的结果称为结果变量。

next_functions就是grad_fn的精华

在PyTorch的反向图计算中,AccumulateGrad类型代表的就是叶子节点类型,也就是计算图终止节点。AccumulateGrad类中有一个.variable属性指向叶子节点。

x_leaf.variable

  1. 当我们执行z.backward()的时候。这个操作将调用z里面的grad_fn这个属性,执行求导的操作。
  2. 这个操作将遍历grad_fn的next_functions,然后分别取出里面的Function(AccumulateGrad),执行求导操作。这部分是一个递归的过程直到最后类型为叶子节点。
  3. 计算出结果以后,将结果保存到他们对应的variable 这个变量所引用的对象(x和y)的 grad这个属性里面。
  4. 求导结束。所有的叶节点的grad变量都得到了相应的更新

最终当我们执行完c.backward()之后,a和b里面的grad值就得到了更新。

扩展Autograd
https://handbook.pytorch.wiki/chapter2/2.1.2-pytorch-basics-autograd.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# grad_tensors
# 直接z.backward()会报错 只有对标量输出它才会计算梯度
# grad_tensors的作用其实可以简单地理解成在求梯度时的权重,因为可能不同值的梯度对结果影响程度不同,所以pytorch弄了个这种接口,而没有固定为全是1
# 如果从最后一个节点(总loss)来backward,这种实现(torch.sum(y*w))的意义就具体化为 multiple loss term with difference weights 这种需求了吧。
x = torch.ones(2,requires_grad=True)
z = x + 2
z.backward(torch.ones_like(z)) # grad_tensors需要与输入tensor大小一致
print(x.grad)

x = torch.tensor([2., 1.], requires_grad=True)
y = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)

z = torch.mm(x.view(1, 2), y)
print(f"z:{z}")
z.backward(torch.Tensor([[1., 0]]), retain_graph=True)
print(f"x.grad: {x.grad}")
print(f"y.grad: {y.grad}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# vector-Jacobian product
# https://zhuanlan.zhihu.com/p/65609544
x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
y = y * 2

print(y)
# y不是标量,torch.autograd无法直接计算完整的雅可比行列,需要将向量作为参数传入backward
gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(gradients,retain_graph=True)
# 返回值不是一个标量,所以需要输入一个大小相同的张量作为参数

print(x.grad)
print(y)
y.backward(torch.ones(3),retain_graph=True)
print(x.grad)
y.backward(torch.ones_like(x))
print(x.grad)
1
2
3
4
5
6
7
8
# 如果.requires_grad=True但是你又不希望进行autograd的计算, 那么可以将变量包裹在 with torch.no_grad()中:
# 临时禁止对已设置requires_grad=True的张量进行自动求导。这个方法在测试集计算准确率的时候会经常用到
# 用.no_grad()进行嵌套后,代码不会跟踪历史记录,也就是说保存的这部分记录会减少内存的使用量并且会加快少许的运算速度。
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
print((x ** 2).requires_grad)

3 neural network

https://pytorch.org/docs/stable/nn.html

一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。
神经网络的典型训练过程如下:

定义包含一些可学习的参数(或者叫权重)神经网络模型;
在数据集上迭代;
通过神经网络处理输入;
计算损失(输出结果和正确值的差值大小);
将梯度反向传播回网络的参数;
更新网络的参数,主要使用如下简单的更新原则: weight = weight - learning_rate * gradient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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 input image channel, 6 output channels, 5x5 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b 放射操作
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 over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# If the size is a square you can only specify a single number
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:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features


net = Net()
print(net)

# net.parameters()返回可被学习的参数(权重)列表和值

params = list(net.parameters())
print(len(params))
# print(params)
print(params[0].size()) # conv1's .weight

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

# 将所有参数的梯度缓存清零,然后进行随机梯度的的反向传播:
net.zero_grad()
out.backward(torch.randn(1, 10))

激活函数
为什么激活函数都是非线性的
在神经网络的计算过程中,每层都相当于矩阵相乘,无论神经网络有多少层输出都是输入的线性组合,就算我们有几千层的计算,无非还是个矩阵相乘,和一层矩阵相乘所获得的信息差距不大,所以需要激活函数来引入非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中,增加了神经网络模型泛化的特性。

  1. sigmoid
    在sigmoid函数中我们可以看到,其输出是在(0,1)这个开区间,它能够把输入的连续实值变换为0和1之间的输出,如果是非常大的负数,那么输出就是0;如果是非常大的正数输出就是1,起到了抑制的作用。
    但是sigmod由于需要进行指数运算(这个对于计算机来说是比较慢,相比relu),再加上函数输出不是以0为中心的(这样会使权重更新效率降低),当输入稍微远离了坐标原点,函数的梯度就变得很小了(几乎为零)。在神经网络反向传播的过程中不利于权重的优化,这个问题叫做梯度饱和,也可以叫梯度弥散。这些不足,所以现在使用到sigmod基本很少了,基本上只有在做二元分类(0,1)时的输出层才会使用。

  2. tanh
    tanh是双曲正切函数,输出区间是在(-1,1)之间,而且整个函数是以0为中心的
    与sigmoid函数类似,当输入稍微远离了坐标原点,梯度还是会很小,但是好在tanh是以0为中心点,如果使用tanh作为激活函数,还能起到归一化(均值为0)的效果。
    一般二分类问题中,隐藏层用tanh函数,输出层用sigmod函数,但是随着Relu的出现所有的隐藏层基本上都使用relu来作为激活函数了

  3. relu
    z>0时,梯度始终为1,从而提高神经网络基于梯度算法的运算速度。然而当 z<0时,梯度一直为0。 ReLU函数只有线性关系(只需要判断输入是否大于0)不管是前向传播还是反向传播,都比sigmod和tanh要快很多
    当输入是负数的时候,ReLU是完全不被激活的,这就表明一旦输入到了负数,ReLU就会死掉。但是到了反向传播过程中,输入负数,梯度就会完全到0,这个和sigmod函数、tanh函数有一样的问题。 但是实际的运用中,该缺陷的影响不是很大。

  4. Leaky Relu
    为了解决relu函数z<0时的问题出现了 Leaky ReLU函数,该函数保证在z<0的时候,梯度仍然不为0。
    理论上来讲,Leaky ReLU有ReLU的所有优点,但是在实际操作当中,并没有完全证明Leaky ReLU总是好于ReLU。

ReLU目前仍是最常用的activation function,在隐藏层中推荐优先尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 初始化一些信息
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
x= torch.linspace(-10,10,60)

# sigmoid
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data', 0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data', 0))
plt.ylim((0, 1))
sigmod=torch.sigmoid(x)
plt.plot(x.numpy(),sigmod.numpy())

# tanh
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data', 0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data', 0))
plt.ylim((-1, 1))
tanh=torch.tanh(x)
plt.plot(x.numpy(),tanh.numpy())

# relu
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data', 0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data', 0))
plt.ylim((-3, 10))
relu=F.relu(x)
plt.plot(x.numpy(),relu.numpy())

# leaky relu
ax = plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data', 0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data', 0))
plt.ylim((-3, 10))
l_relu=F.leaky_relu(x,0.1) # 这里的0.1是为了方便展示,理论上应为0.01甚至更小的值
plt.plot(x.numpy(),l_relu.numpy())

torch.nn 只支持小批量输入。整个 torch.nn 包都只支持小批量样本,而不支持单个样本。 例如,nn.Conv2d 接受一个4维的张量, 每一维分别是sSamples * nChannels * Height * Width(样本数*通道数*高*宽)。 如果你有单个样本,只需使用 input.unsqueeze(0) 来添加其它的维数

损失函数

  1. nn.L1Loss: 差的绝对值
  2. nn.NLLLoss: 用于多分类的负对数似然损失函数。NLLLoss中如果传递了weights参数,会对损失进行加权
  3. nn.MSELoss: 均方损失函数
  4. nn.CrossEntropyLoss: 多分类用的交叉熵损失函数,LogSoftMax和NLLLoss集成到一个类中,会调用nn.NLLLoss函数,可以理解为CrossEntropyLoss()=log_softmax() + NLLLoss()。因为使用了NLLLoss,所以也可以传入weight参数。一般多分类用这个
  5. nn.BCELoss: 计算 x 与 y 之间的二进制交叉熵。与NLLLoss类似,也可以添加权重参数。用的时候需要在该层前面加上 Sigmoid 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 损失函数
output = net(input)
target = torch.randn(10) # 随机值作为样例
target = target.view(1, -1) # 使target和output的shape相同
criterion = nn.MSELoss()

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

# 调用 loss.backward()时,整张计算图都会 根据loss进行微分,而且图中所有设置为requires_grad=True的张量 将会拥有一个随着梯度累积的.grad 张量
print(loss.grad_fn)
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
1
2
3
4
5
6
7
8
9
10
11
# 反向传播
# 调用loss.backward()获得反向传播的误差。但是在调用前需要清除已存在的梯度,否则梯度将被累加到已存在的梯度
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 更新权重
# SGD、Nesterov-SGD、Adam、RMSPROP
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Does the update

4 cifar10

数据。可以使用标准的Python包来加载数据到一个numpy数组中。 然后把这个数组转换成torch.tensor
图像可以使用 Pillow, OpenCV, torchvision
音频可以使用 scipy, librosa
文本可以使用原始Python和Cython来加载,或者使用 NLTK或 SpaCy 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torch
import torchvision
import torchvision.transforms as transforms

# torchvision的输出是[0,1]的PILImage图像,我们把它转换为归一化范围为[-1, 1]的张量。

transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((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)

classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 显示图像
import matplotlib.pyplot as plt
import numpy as np

# 展示图像的函数


def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))


# 获取随机数据
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 展示图像
imshow(torchvision.utils.make_grid(images))
# 显示图像标签
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 定义网络
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):
def __init__(self):
# nn.Module子类的函数必须在构造函数中执行父类的构造函数
super(Net, self).__init__()

# 卷积层 '3'表示输入图片为3通道, '6'表示输出通道数,'5'表示卷积核为5*5
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)

# 输入16*5*5个特征,输出120个特征
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
# 卷积-激活-池化
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x


net = Net()
print(net)

# 返回网络的可学习参数
for parameters in net.parameters():
print(parameters)
# net.named_parameters可同时返回可学习的参数及名称。
for name,parameters in net.named_parameters():
print(name,':',parameters.size())

# 定义损失函数和优化器
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# 训练网络
for epoch in range(2): # 多批次循环

running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 获取输入
inputs, labels = data

# 梯度置0。 反向传播前,先要将所有参数的梯度清零
optimizer.zero_grad()

# 正向传播,反向传播,优化。
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()

# 打印状态信息
running_loss += loss.item()
if i % 2000 == 1999: # 每2000批次打印一次
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0

print('Finished Training')

# 测试
dataiter = iter(testloader)
images, labels = dataiter.next()
imshow(torchvision.utils.make_grid(images)) # 显示图片
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

outputs=net(images)
_,predicted=torch.max(outputs,1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
for j in range(4)))


# 测试整个测试集
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))

注意:torch.nn只支持mini-batches,不支持一次只输入一个样本,即一次必须是一个batch。

也就是说,就算我们输入一个样本,也会对样本进行分批,所以,所有的输入都会增加一个维度,我们对比下刚才的input,nn中定义为3维,但是我们人工创建时多增加了一个维度,变为了4维,最前面的1即为batch-size
https://handbook.pytorch.wiki/chapter2/2.1.3-pytorch-basics-nerual-network.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 在识别哪一个类的时候好,哪一个不好呢?
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1


for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
1
2
3
4
5
6
7
8
9
10
# 在GPU上训练
# https://handbook.pytorch.wiki/chapter1/5_data_parallel_tutorial.ipynb 数据并行处理,使用所有的GPU得到更大的加速

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 确认我们的电脑支持CUDA,然后显示CUDA信息:

print(device)
net.to(device)
inputs, labels = inputs.to(device), labels.to(device)

数据并行处理

https://handbook.pytorch.wiki/chapter1/data_parallel_tutorial.html

https://handbook.pytorch.wiki/chapter1/neural_networks_tutorial.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 数据集

from torch.utils.data import Dataset
import pandas as pd
#定义一个数据集
class BulldozerDataset(Dataset):
# """ 数据集演示 """
def __init__(self, csv_file):
# """实现初始化方法,在初始化的时候将数据读载入"""
self.df=pd.read_csv(csv_file)
def __len__(self):
# 返回df的长度
return len(self.df)
def __getitem__(self, idx):
# 根据 idx 返回一行数据
return self.df.iloc[idx].SalePrice

# ds_demo= BulldozerDataset('median_benchmark.csv')
# ds_demo[0]

DataLoader为我们提供了对Dataset的读取操作,常用参数有:batch_size(每个batch的大小)、 shuffle(是否进行shuffle操作)、 num_workers(加载数据的时候使用几个子进程)。

1
2
3
4
5
6
7
8
9
10
dl = torch.utils.data.DataLoader(ds_demo, batch_size=10, shuffle=True, num_workers=0)

# DataLoader返回的是一个可迭代对象,我们可以使用迭代器分次获取数据
idata=iter(dl)
print(next(idata))

for i, data in enumerate(dl):
print(i,data)
# 为了节约空间,这里只循环一遍
break

torchvision不仅提供了常用图片数据集,还提供了训练好的模型,可以加载之后,直接使用,或者在进行迁移学习 torchvision.models模块的 子模块中包含以下模型结构。 - AlexNet - VGG - ResNet - SqueezeNet - DenseNet

transforms 模块提供了一般的图像转换操作类,用作数据处理和数据增强

1
2
3
4
5
6
import torchvision.datasets as datasets
trainset = datasets.MNIST(root='./data', # 表示 MNIST 数据的加载的目录
train=True, # 表示是否加载数据库的训练集,false的时候加载测试集
download=True, # 表示是否自动下载 MNIST 数据集
transform=None) # 表示是否需要对数据进行预处理,none为不进行预处理

1
2
3
#我们直接可以使用训练好的模型,当然这个与datasets相同,都是需要从服务器下载的
import torchvision.models as models
resnet18 = models.resnet18(pretrained=True)
1
2
3
4
5
6
7
8
from torchvision import transforms as transforms
transform = transforms.Compose([
transforms.RandomCrop(32, padding=4), #先四周填充0,在把图像随机裁剪成32*32
transforms.RandomHorizontalFlip(), #图像一半的概率翻转,一半的概率不翻转
transforms.RandomRotation((-45,45)), #随机旋转
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.229, 0.224, 0.225)), #R,G,B每层的归一化用到的均值和方差
])

(0.485, 0.456, 0.406), (0.2023, 0.1994, 0.2010) 这几个数字是什么意思?

官方的这个帖子有详细的说明: https://discuss.pytorch.org/t/normalization-in-the-mnist-example/457/21 这些都是根据ImageNet训练的归一化参数,可以直接使用,我们认为这个是固定值就可以