深度学习入门之PyTorch

本文章绝大部分参考这位大佬的笔记非常实用,记录加复习

第一章 深度学习介绍

1.1 人工智能

Artificial Intelligence,人工智能,也称机器智能
人工智能的分类

​ (1)弱人工智能:擅长单方面(已实现)

​ (2)强人工智能:类似人类级别(现阶段)

​ (3)超人工智能:全方面胜过人类(未实现)

1.2 数据挖掘,机器学习和深度学习

1.2.1 数据挖掘

KDD(knowledge discovery in database),从数据中获取有意义的信息

1.2.2 机器学习

机器学习是实现人工智能的一种途径,设计多门学科大致分为五大类

​ (1)监督学习:从给定的训练数据集中学习出一个函数,训练集中的目标由人标注,常见算法包括回归和分类

​ (2)无监督学习,训练集没有人为标注,常见算法如聚类

​ (3)半监督学习:介于监督学习和无监督学习之间

​ (4)迁移学习:将已经训练好的模型参数迁移到新的模型来帮助新模型训练数据集

​ (5)增强学习:通过观察周围环境来学习

1.2.3 深度学习

  1. 机器学习的一个分支, 通过模拟人脑来实现数据特征的提取
  2. 常见网络结构:DNN,CNN,RNN,GAN等等

第二章 深度学习框架

2.1 深度学习框架介绍

  1. Tensorflow(Google开源的基于C++开发的数学计算软件)
  2. Caffe
  3. Theano
  4. Torch(支持动态图)
  5. MXNet

2.2 PyTorch介绍

2.2.1 什么是PyTorch

​ Python优先的深度学习框架,支持GPU加速和动态神经网络

2.2.2 为什么使用PyTorch

  1. 啥也不会就学过PyTorch

  2. PyTorch通过一种反向自动求导的技术,可以让你零延迟地改变神经网络

  3. 线性、直观、易于使用
  4. 代码简洁直观,底层代码友好,框架环境简单好搭建,总之强烈推荐

2.3 配置PyTorch深度学习环境

2.3.1 操作系统

​ 主流操作系统均可Windows、Linux、Max

2.3.2 Python开发者环境

​ Anaconda吹爆!!简单安装,随意切换Python环境

2.3.3 PyTorch安装

官网)或者Anaconda

​ CPU或者GPU

​ CUDA,CuDnn

PyTorch

第三章 多层全连接神经网络

3.1 PyTorch基础

3.1.1 Tensor张量

Tensor相当于多维的矩阵

​ Tensor的数据类型有:torch.FloatTensor(32位浮点型)、torch.DoubleTensor(64位浮点型)、torch.ShortTensor(16位整型)、

torch.IntTensor(32位整型)、torch.LongTensor(64位整型)

导入Torch

1
2
from __future__ import print_function
import torch

创建一个大小为5x3未初始化的矩阵

1
2
x = torch.empty(5, 3)
print(x)

创建一个随机初始化的矩阵

1
2
3
4
5
6
7
# 均匀分布[0,1],rand
x = torch.rand(5, 3)
print(x)

# 正态分布,randn
x = torch.randn(5, 3)
print(x)

创建一个零矩阵,且数据类型为Long

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

直接创建数据结构张量

1
2
x = torch.tensor([5, 3])
print(x)

创建一个全为1的矩阵,且数据类型为Double

1
2
3
4
5
x = torch.ones(5, 3)
print(x)

x = x.new_ones(5, 3, dtype = torch.double)
print(x)

根据已有Tensor创建新的Tensor,且除非提供新值,将重用所给张量的属性

1
2
3
4
5
x = x.new_ones(5, 3, dtype = torch.double)
print(x)

x = torch.randn_like(x, dtype = torch.float)
print(x)

获取张量形状

torch.Size本质上还是tuple, 所以支持tuple的一切操作

1
2
print(x.shape)
print(x.size())

和numpy的相互转换

1
2
3
4
5
6
7
8
print(x)
# torch转numpy
numpy_x = x.numpy()
print(numpy_x)

# numpy转torch
torch_x = torch.from_numpy(numpy_x)
print(torch_x)

绝对值

1
2
3
4
5
a = torch.randn(5, 3)
print(a)

b = torch.abs(a)
print(b)

运算,例如加法

形式一

1
2
y = torch.rand(5, 3)
print(x + y)

形式二

1
print(torch.add(x, y))

形式三

1
2
3
result = torch.empty(5, 3)
torch.add(x, y, out = result)
print(result)

形式四

1
2
y.add_(x)
print(y)

warning 在任何一个inplace改变张量的操作后面都固定一个。例如:x.copy(y)、x.t(y),更改x需要后跟短-

裁剪:如果在上下边界内则不变,否则大于上边界值,则改为上边界值,小于下边界值,则改为下边界值

1
2
3
4
5
a = torch.randn(2, 3)
print(a)

b = torch.clamp(a, -0.1, 0.1)
print(b)

除法

1
2
3
4
5
6
7
8
a = torch.randn(2, 3)
b = torch,randn(2, 3)
c = torch.div(a, b)
d = torch.div(c, 10)
print(a)
print(b)
print(c)
print(d)

warning 加法add,乘积mul,除法div,求幂pow,矩阵乘法mm,矩阵向量乘法mv

改变张量形状

1
2
3
4
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # -1将自动取值
print(x.size(), y.size(), z.size())

对只含一个元素的Tensor,可以使用.item()来得到数值

1
2
3
X = torch.randn(1)
print(x)
print(x.item())

使用GPU

1
2
3
4
5
6
7
if torch.cuda.is_available():
device = torch.device("cuda")
y = torch.ones_like(x, device=device)
x = x.to(device)
z = x + y
print(z)
print(z.to("CPU", torch.double))

3.1.2 Variable变量

  1. Autograd:自动求导

创建一个张量并设置requires_grad=True用来追踪其计算历史

1
2
3
x = torch.ones(5, 3, requires_grad=True)
print(x)
# 注意看x的属性

对这个张量做一次运算

1
2
3
4
5
6
7
8
y = x + 2
print(y)
# y是计算结果, 所以它有grad_fn属性
print(y.grad_fn)
# 对y进行更多操作
z = y * y * 3
out = z.mean()
print(z, out)

​ .requires_grad(…)改变了现有张量的requires_grad标志。如果没有指定的话,默认输入的标志是False。

1
2
3
4
5
6
7
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
  1. 梯度
1
2
3
4
5
6
7
8
x = torch.ones(2, 2, requires_grad = True)
y = x + 2
z = y * y * 3
out = z.mean()
# 现在开始反向传播,因为out是一个标量,则out.backward()和out.backward(torch.tensor(1.))等价
out.backward()
# 输出导数d(out) / dx
print(x.grad)

公式1

雅可比矩阵

​ 数学上,若有向量值函数$y=f(x)$,那么y相当于对x的梯度是一个雅可比矩阵(下面是一个latex数学公式)

1
2
3
4
5
J=\begin{bmatrix}
\frac{\partial y_1}{\partial x_1} &\cdots& \frac{\partial y_1}{\partial x_n} \\
\vdots & \ddots & \vdots \\
\frac{\partial y_m}{\partial x_1} &\cdots& \frac{\partial y_m}{\partial x_n}
\end{bmatrix}

雅可比矩阵

​ 通常来说,torch.autograd是计算雅可比向量积的一个引擎。也就是说,给定任意向量v,计算乘积$v^T*J$。如果v恰好是一个标量函数$l=g(y)$的导数,即$(\frac {\partial l } {\partial y_1 }···\frac {\partial l } {\partial y_m })^T$,那么根据链式法则,雅可比向量积应该是lx的导数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
J^T·v=\begin{bmatrix}
\frac{\partial y_1}{\partial x_1} &\cdots& \frac{\partial y_m}{\partial x_1} \\
\vdots & \ddots & \vdots \\
\frac{\partial y_1}{\partial x_n} &\cdots& \frac{\partial y_m}{\partial x_n}
\end{bmatrix}
\begin{bmatrix}
\frac{\partial l}{\partial y_1}\\
\cdots\\
\frac{\partial l}{\partial y_m}
\end{bmatrix}=
\begin{bmatrix}
\frac{\partial l}{\partial x_1}\\
\cdots\\
\frac{\partial l}{\partial x_n}
\end{bmatrix}

雅可比向量积

(注意:行向量的$v^TJ$也可以被视作列向量的$J^Tv$)

​ 雅可比向量积的这一特性使得将外部梯度输入到具有非标量输出的模型中变得非常方便

1
2
3
4
5
6
x=torch.randn(3,requires_grad=True)
y=x*2
while y.data.norm() <1000:
y=y*2

print(y)

​ 在这种情况下,y不再是标量。torch.autograd不能直接计算完整的雅可比矩阵,但是如果我们只是想要雅可比向量积,只需要将这个向量作为参数传给backward

1
2
3
4
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)

​ 也可以通过将代码块包装在with torch.no_grad():中,来阻止autograd跟踪设置了.requires_grad=True的张量的历史纪录

1
2
3
4
5
print(x.requires_grad)
print((x ** 2).requires_grad)

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

​ Variable和Tensor的区别:Variable会被放入到计算图中,然后进行向前传播(前馈),反向传播(反馈),自动求导,Variable是在torch.autograd.Varable中

1
2
3
4
5
6
7
8
9
10
11
12
from torch.autograd import Variable
x = Variable(torch.Tensor([1]), requires_grad = True)
w = Variable(torch.Tensor([1]), requires_grad = True)
b = Variable(torch.Tensor([1]), requires_grad = True)

y = w * x + b

y.backward()

print(x.grad)
print(w.grad)
print(b.grad)

搭建一个简单的神经网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
batch_n = 100 # 一个批次中输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的数据特征个数
input_data = 1000 # 每个数据包含的数据量
output_data = 10 # 每个输出的数据包含的数据量

x = torch.randn(batch_n, input_data) # 100 * 1000
y = torch.randn(batch_n, output_data) # 100 * 10

w1 = torch.randn(input_data, hidden_layer) # 1000 * 100
w2 = torch.randn(hidden_data, output_data) # 100 * 10

epoch_n = 20 # 训练次数
learning_rate = 1e-6 # 学习率

for epoch_n range(epoch_n):
h1 = x.mm(w1) # 100 * 100
h1 = h1.clamp(min = 0) # if x < 0, x = 0
y_pred = h1.mm(w2) # 100 * 10,前向传播预测结果

使用Variable搭建一个自动计算梯度的神经网络

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
from torch.autograd import Variable

batch_n = 100 # 一个批次中输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的数据特征的个数
input_data = 1000 # 每个数据包含的数据量
output_data = 10 # 每个输出的数据包含的数据量

x = Variable(torch.randn(batch_n, input_data), requires_grad = False) # requires_grad = False 不保留梯度
y = Variable(torch.randn(batch_n, output_data), requires_grad = False)

w1 = Variable(torch.randn(input_data, hidden_layer), requires_grad = True) # requires_grad = True 保留梯度
w2 = Variable(torch.randn(hidden_layer, output_data), requires_grad = True)

epoch_n = 20 # 训练次数
learning_rate = 1e-6

for epoch_n in range(epoch_n):
y_pred = x.mm(w1).clamp(min = 0).mm(w2) # y_pred = w2*(w1*x)
loss = (y_pred - y).pow(2).sum() # 成本函数
print("Epoch:{},loss:{:.4f}".format(epoch, loss))

loss.backward() # 反向传播计算

# 使用学习率更新参数(权重)
w1.data -= learning_rate * w1.grad_data # w1.grad_data求出w1的梯度
w2.data -= learning_rate * w2.grad_data

# 梯度清零否则会累加
w1.grad.data.zero_()
w2.grad.data.zero_()

使用nn.Module自定义传播函数来搭建神经网络

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
from torch.autograd import Varable

batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10

class Model(torch.nn.Moudle):
def __init__(self):
super(Moudle, self).__init__()
def forward(self, input_n, w1, w2):
x = torch.mm(input_n, w1)
x = torch.clamp(x, min = 0)
x = torch.mm(x, w2)
return x
def backward(self):
pass
model = Model()
x=Variable(torch.randn(batch_n,input_data),requires_grad = False) #requires_grad = False不保留梯度
y=Variable(torch.randn(batch_n,output_data),requires_grad = False)
w1=Variable(torch.randn(input_data,hidden_layer),requires_grad = True) #requires_grad = True自动保留梯度
w2=Variable(torch.randn(hidden_layer,output_data),requires_grad = True)

epoch_n = 20
learning_rate = 1e-6

for epoch in range(epoch_n):
y_pred = model(x, w1, w2)
loss = (y_pred-y).pow(2).sum()
print("Epoch:{},Loss:{:.4f}".format(epoch,loss))
loss.backward() #后向传播计算

w1.data -= learning_rate * w1.grad.data
w2.data -= learning_rate * w2.grad.data

w1.grad.data.zero_() #置0
w2.grad.data.zero_()

3.1.3 Dataset数据集

​ torch.utils.data.Dataset是代表这一数据的抽象类,可以自己定义数据类继承和重写这个抽象类,只需要定义__len____getitem__函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
from torch.utils.data import Dataset
class myDataset(Dataset):
def __init__(self, csv_file, txt_file, root_dir, other_file)
self.csv_data = pd.read_csv(csv_file)
with open(txt_file, 'r') as f:
data_list=f.readlines()
self.txt_data = data_list
self.root_dir = root_dir
def __len__(self):
return len(self.csv_data)
def __getitem__(self, idx):
data = (self.csv_data[idx],self.txt_data[idx])
return data

​ 通过上面的方式,可以定义需要的数据类,可以通过迭代的方法取得每一个数据,但是这样很难实现取batch,shuffle或者多线程去读取数据,所以Pytorch中提供了torch.utils.data.DataLoader来定义一个新迭代器

3.1.4 nn.Moudle模组

​ 所有的层结构和损失函数均来自torch.nn

1
2
3
4
5
6
7
8
9
import torch.nn as nn

class net_name(nn.Moudle):
def __init__(self, other_arguments):
super(net_name, self).__init__()
self.conv1 = nn.Conv2d(in_channels,out_channels, kernel_size)
def forward(self, x):
x = self.conv1(x)
return x

​ 一个神经网络的典型训练过程如下:

​ (1)定义包含一些可学习参数(或者叫权重)的神经网络

​ (2)在输入数据集上迭代

​ (3)通过网络处理输入

​ (4)计算loss(输出和正确答案的距离

​ (5)将梯度反向传播给网络的参数

​ (6)更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient

使用torch.nn内的序列容器Sequential

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10

# 第一种方式
models_1 = torch.nn.Sequential(
torch.nn.Linear(input_data, hidden_layer),
# 激活函数
torch.nn.ReLu(),
torch.nn.Linear(hidden_layer, output_data)
)

# 第二种方式
from collections import OrderedDict
models_2 = torch.nn.Sequential(OrderedDict([
("Line1", torch.nn.Linear(input_data, hidden_layer)),
("ReLu1", torch.nn.ReLu()),
("Line2", torch.nn.Linear(hidden_layer, output_data))
]))

print(model_1)
print(model_2)

使用nn.Module定义一个神经网络

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
imoprt torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Moudle):
def __init__(self):
super(Net, self).__init__()
# 输入图像channel:1;输出channel:6;5x5的卷积核
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):
# 2x2 Max pooling
x = F.max_pool2d(F.relu(self.conv1(x)), (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):
size = x.size()[1:] # 除去批处理维度的其他维度
num_features = 1
for s in size:
num_features *= s
return num_features

net = Net()
print(net)

3.1.5 torch.optim优化

​ 优化算法分为两大类:

​ (1)一阶优化算法

​ 使用各个参数的梯度值来更新参数,最常用的是梯度下降。梯度下降的功能是通过寻找最小值,控制方差,更新模型参数,最终使模型收敛,网络的参数更新公式

其中$\eta$是学习率,$\frac {\partial J(\theta) } {\partial \theta}$是函数的梯度

​ (2)二阶优化算法

​ 二阶优化算法使用了二阶导数(Hessian方法)来最小化或最大化损失函数,主要是基于牛顿法

1
optimizer=torch.optim.SGD(model.parameters(),lr=0.01,momentum=0.9)

3.1.6 模型的保存和加载

  1. 保存
1
2
3
4
# 保存模型
torch.save(model, path)
# 保存模型的状态
torch.save(model.state_dict(), path)
  1. 加载
1
2
3
4
# 加载完整的模型
load_model= torch.load(path)
# 加载模型参数,需要先导入模型的结构
model.load_state_dict(torch.load(path))