第一章 深度学习与PyTorch


1.1 机器学习

在人工智能领域,机器学习是实现人工智能的一个分支,也是其中发展最快的一个分支。

简单来说,机器学习是计算机程序如何随着经验的积累而提高性能,使系统自我完善的过程。

根据应用场景和学习方式的不同,可以简单分为三类:

  • 无监督学习 unsupervised learning

    无监督学习不需要提前知道数据集的类别标签,通常运用场景为聚类和降维。

  • 半监督学习 semi-supervised learning

    半监督学习介于二者之间,利用极少的有标签数据和大量无标签数据进行学习,同过学习得到的经验对无标签的数据进行预测。

  • 有监督学习 supervised learning

    主要特征是数据集具备标签数据。


1.2 深度学习

深度学习是一种机器学习方法,与传统的机器学习方法相同,可以根据输入的数据进行分类或回归。但随着数据量的增加,传统机器学习方法表现不尽如人意,而此时利用更深的网络挖掘数据信息的方法----深度学习表现出了优异的性能,2010年后,更是迎来了爆发式增长。

在20世纪60~70年代,神经生理学家发现猫的视觉皮层中有两种细胞,一种是简单细胞,他们对图像中的细节信息更加敏感,如边缘、角点。另一种是复杂细胞,能够分析处理图像的空间不变性,如旋转、放缩、远近等图像。学者们根据这点提出了卷积神经网络,在图像处理方面取得了较大的成果。

事实上,在当时未能解决梯度消失问题前,基于深度卷积神经网络的算法并没有引起人们的重视,在分层训练过程中,本应用于修正模型参数的误差随着层数的增加指数递减,这就导致了模型训练效果低下。

尽管深度算法在2000年前就被使用,并且受到了SVM的压制,但其发展并未停止,1992年多层网络提出,通过无监督学习训练深度网络中的每一层,再通过反向传播算法调优。在这一模型中,神经网络的每一层都代表观测变量的一种压缩表示,而这一表示又被传入到下一层网络。

进入21世纪后,随着数据量的积累和计算机性能的提升,神经网络终于迎来了新一轮的春天。深度学习算法和传统的机器学习算法相比,其最大的特点是端到端的学习,在进行学习之前无需进行特征提取等操作,可以通过深层的网络结构自动从原始图像中提取有用的特征。而传统的机器学习过程则需要更多的人工干预,其稳定性较弱。


Pytorch


一.Pytorch是什么?

Pytorch是torch的python版本,是由Facebook开源的神经网络框架,专门针对 GPU 加速的深度神经网络(DNN)编程。Torch 是一个经典的对多维矩阵数据进行操作的张量(tensor )库,在机器学习和其他数学密集型应用有广泛应用。与Tensorflow的静态计算图不同,pytorch的计算图是动态的,可以根据计算需要实时改变计算图。但由于Torch语言采用 Lua,导致在国内一直很小众,并逐渐被支持 Python 的 Tensorflow 抢走用户。作为经典机器学习库 Torch 的端口,PyTorch 为 Python 语言使用者提供了舒适的写代码选择。


二、Pytorch优势

  • 自动求导

    • 例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import torch

      x=torch.tensor(1.,)
      a=torch.tensor(1.,requires_grad=True)
      b=torch.tensor(2.,requires_grad=True)
      c=torch.tensor(3.,requires_grad=True)
      y=a**2*x+b*x+c

      print('before',a.grad,b.grad,c.grad)
      # 自动求导,对表达式a中的参数[b,c,d]进行求偏导
      grads=torch.autograd.grad(y,[a,b,c])
      print('after',grads[0],grads[1],grads[2])
  • GPU加速计算(可用于numpy的加速)

  • 常用API

  • 简洁

    • 不同于TF,PyTorch的设计遵照tensor->variable(autograd)->nn.Module三个由低到高的抽象层次,分别代表高维数组(张量)、自动求导(变量)和神经网络(层/模块)。PT的源码只有TF的十分钟之一左右,更少的抽象、更直观的设计使得PT十分容易阅读。
  • 速度

    • PT的速度胜过TF和Keras等框架
  • 易用


线性回归

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
def error_rate(a,b,points):
# 计算均方差
total=0
for i in range(0,len(points)):
x=points[i][0]
y=points[i][1]
total+=(x*a+b-y)**2
return total/float(len(points))

def step_gradient(a_cur,b_cur,points,lr):
# 当前梯度方向
b_gradient=0
a_gradient=0
size=float(len(points))
total=0

for i in range(0,len(points)):
x=points[i][0]
y=points[i][1]
# 修正梯度
# (ax+b-y)**2对a求偏导: 2*(ax+b-y)*x
# 对b求偏导: 2*(ax+b-y)
a_gradient+=-2*(y-(a_cur*x+b_cur))*x/size
b_gradient+=-2*(y-(a_cur*x+b_cur))/size
total += (y-(a_cur*x+b_cur)) ** 2
# 平均梯度方向
# 修正
# x=x-/delta x * LearningRate
return [a_cur-a_gradient*lr,b_cur-b_gradient*lr,total/size]

def linear_regressing(points,start_a,start_b,learningrate,iterations):

a=start_a
b=start_b
time=0
for i in range(iterations):
a,b,tem=step_gradient(a,b,points,learningrate)
time+=1
if time%10==0:
print(tem)
return [a,b]

import random
import matplotlib.pyplot as plt
point=[]
for i in range(50):
t = random.randint(0, 100)
point.append([t,t*2+6+random.randrange(0,10)])

plt.figure(figsize=[20,8],dpi=300)
for i in point:
plt.plot(i[0],i[1],'o')
a,b=linear_regressing(point,2,6,0.0002,10000)
# 学习率必须设置得很小,不然会冲出去
new_x=[i for i in range(0,101) if i%10==0]
new_y=[i*a+b for i in new_x]
plt.plot(new_x,new_y)
print(a,b)
plt.show()


Torch基本操作

1.Torch数据类型

dim: 维度,如2

shape: 长度/形状,如[2,2]

1.1标量Scalar

常见于loss评估

1
2
3
4
5
6
7
8
9
10
11
# 直接创建
torch.tensor(1.)

# 获得形状
a=torch.tensor(2,2)
a.shape # or a.size()
len(a.shape) # -->0
# 形状交互
list(a.shape)
# 获取该标量
a.item()

1.2向量Vector

常见于biaslinear_input

1
2
3
4
5
6
7
8
9
torch.tensor([1.1,2.2])

# 指定类型
torch.FloatTensro(n) # 随机初始化向量长度
# 会生成n个符合类型的向量元素

# 从np中获取
data=np.ones(2)
torch.from_numpy(data)

1.3张量Tensor

实际上在torch0.4之后都叫张量了

1
2
3
4
5
6
7
8
9
10
a=torch.randn(1,2,3)

# 常见的数据交互

# number of element 获得元素数量
a.numel()
# 实际上就是1*2*3

# 获得维度
a.dim()

Tensor的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# CPU
a=torch.randn(2,3)
a.type()
# "torch.FloatTensor"
type(a)
# torch.Tensor
isinstance(a,torch.FloatTensor)
# True

# GPU
isinstance(data,torch.cuda.DoubleTensor)
# 转换
data=data.cuda
data=data.float()
data=data.int()

类型表

注意,Torch没有string类型,可以通过one-hot处理,或是语义关系采用embedded中的word2vec。


1.4创建Tensor

我们可以从numpy中获取

1
2
a=np.ndarray([1,2,3,4,5])
a=torch.from_numpy(a)

可以从list中获取

1
2
torch.tensor([2,3])
torch.FloatTensor([2.,3.])

注意torch.tensor是接收现有数据,而torch.Tensor是接收数据维度(当然也可以接收现实数据)

1
2
torch.FloatTensor(2,3) # 接收维度随机创建
torch.FloatTensro([2,3])

生成未初始化的数据

1
2
Torch.empty([2,3])

设置默认数据类型

1
2
# Torch.Tensor默认的是FloatTensor
torch.set_default_tensor_type(torch.DoubleTensor)

生成分布数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 0-1区间
a=torch.rand(3,3)
# like方法是先读取shape,然后送过去生成数据
torch.rand_like(a)

# 自定义区间
torch.randint(1,10,[3,3])
# 最小值,最大值-1,形状

# 正态分布N(0,1)
torch.randn(3,3)
# 自定义正态
torch.normal(mean=torch.full([10],0),std=torch.arange(1,0,-0.1))
# 得到一个长度为10,维度为1的数据
# 一般再做个view

全部赋值

1
2
3
4
torch.full([2,3],7)

# 生成标量
torch.full([],7)

等差数列

1
2
3
torch.arange(0,10)

torch.arange(0,10,2)

等分生成

1
2
3
4
5
6
# 线性等分
# 注意,包含了最大值
torch.linspace(0,10,steps=4)

# 对数等分
torch.logspace(0,-1,steps=10)

规则矩阵生成

1
2
3
4
5
6
7
8
9
# 对角
# 但是只能到二维
torch.eye(3) # 3x3

# 全零
torch.zeros(3,3)

# 利用like生成
torch.ones_like(a)

随机打散

1
2
3
4
5
6
7
8
9
10
torch.randperm(10)
# 生成一个随机打乱的tensor
# 如tensor([1,5,7,9,8,6,3,4,0])
# 一般用于随机化索引
# 如
a=torch.rand(2,3)
b=torch.rand(2,2)
idx=torch.randperm(2)
a[idx] # 可以交换行
b[idx]

2.Tensor索引与切片

tensor的切片索引非常大胆

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
# 对一个四位数据进行操作
a=torch.rand(4,3,28,28)
a[0].shape # torch.Size([3,28,28])
a[0,0].shape # torch.Size([28,28])
a[0,0,2,4] # tensor(0.8082)

# 可以与切片一起使用
a[:2].shape # torch.Size([2,3,28,28])
a[:2,1:,:,:].shape
a[:2,-1,:,:].shape

# 隔点采样
a[:,:,0:28:2,0:15:3].shape
a[:,:,::2,::2].shape

# 具体索引方式
torch.index_select(dim,[])
torch.index_select(0,[1,2]) # 从第一个维度上选择1,2个数据
a.index_select(2,torch.arange(8)).shape
# torch.Size([4,3,8,28])

# 任意多的维度
# ...
a[...].shape
a[0,...].shape # ==a[0]
a[0:,...,::2] # 对第一个维度和最后一个维度做限定

# 通过掩码选取
x=torch.randn(3,4)
mask=x.ge(0.5) # 一个零一矩阵,大于0.5时为1
torch.masked_select(x,mask) # 直接获取一个vectory,是mask为1的数据

# 打平选取
# 先将数据排成一排
# 然后再选取
scr=torch.tensor([[4,3,5],
[6,7,8]])
torch.take(scr,torch.tensor[[0,2,-1]])
# tensor([4,5,8])

3.维度变化

3.1 view与reshape

viewreshape效果完全一致

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

a.view(4,28*28)

3.2 squeeze与unsqueeze

压缩与维度增加

unsqueeze可以在指定位置上插入新的维度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a.shape
# torch.Size([4,1,28,28])

a.unsqueeze(0).shape
# torch.Size([1,4,1,28,28])

a.unsqueeze(4).shape
# torch.Size([4,1,28,28,1])

# Demo
# 例如有一张 4,32,14,14的图像
# 我们现在还有一个标量核
b=torch.rand(32)
f=torch.rand(4,32,14,14)
# 如何达成f+b
b=b.unsqueeze(1).unsqueeze(2).unsqueeze(0)
b.shape
# torch.Size([1,32,1,1])

squeeze则是删减维度

1
2
3
4
5
6
7
8
9
# 不给定参数会把能删减的全删减了
a.shape
# torch.Size([4,1,28,28])

a.squeeze().shape
# torch.Size([32])

a.squeeze(0).shape
# torch.Size([32,1,1])

3.3 expand 与 repeat

expand 并没有主动复制数据,而repeat则会copy数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a=torch.rand(4,32,14,14)
b.shape
# torch.Size([1,32,1,1])

# 现在为了让b与a做处理
# 需要对b进行扩展
b.expand(4,32,14,14).shape
# torch.Size([4,32,14,14])

# 而要保持某一维度不变,使用负一即可
b.expande(-1,32,-1,-1).shape
# torch.Size([1,32,1,1])

# repeat 参数表示某一维度上需要拷贝多少次
b.repeat(4,32,1,1).shape
# torch.Size([4,1024,1,1])

3.4 转置

.t

1
2
3
a=torch.randn(3,4)
# .t只能用于二维矩阵
a.t()

transpose

1
2
3
4
5
6
7
# transpose能交换任意两个维度
# 但是维度打乱后的数据不再联系
# 需要使用contiguous进行处理

# 注意view会丢失维度信息!
# 需要跟踪维度信息
al=a.transpose(1,3).contiguous().view(4,3*32*32).view(4,32,32,3).transpose(1,3)

permute

1
2
3
4
# permute可以多次交换
# 自动调用transpose

b.permute(0,3,2,1)

4. 自动扩展

核心思想

  • 若前方无维度,则新增维度
  • 将不足的维度扩展到需求维度

有则相加,无则补全

注意,expand不能自动增加维度,所以需要先用unqueeze增加维度

1
b.unsqueeze[0].unsqueeze[-1].expand_as(A)

能够自动扩展的情况:

  • 需要添加的数据符合最后一维数量
  • 添加的数据维度与需要处理的数据维度相同

5. 拼接与分割

5.1 cat

1
2
3
4
5
6
a=torch.rand(4,32,8)
b=torch.rand(5,32,8)

torch.cat([a,b],dim=0).shape

# 合并维度之外的维度必须相同

5.2 stack

stack会在需要合并的维度前新添加一个维度(等于上半部分是A,下半部分是B),且要求维度完全一致。

1
2
3
4
5
a=torch.randn([32,32,16])
b=torch.randn([32,32,16])

torch.stack([a,b],dim=0).shape
# torch.Size([2,32,32,16])

5.3 split

split是按长度分割

1
2
3
4
5
6
7
8
9
a=torch.rand(4,32,8)
# 将指定维度进行切割,每一份长度是第一个参数
# 不够的地方取余数
t=a.split(7,dim=1)
t[4].shape
# torch.Size([4, 4, 8])

# 也能指定长度
a.split([15,1,16],dim=1)

5.4 Chunk

chunk是等距分割

1
2
# 分成两份
a.chunk(2,dim=1)

6.Tensor运算

加法

1
2
3
4
5
a=torch.rand(3,4)
b=torch.rand(4)

a+b # 自动广播机制
torch.add(a,b)

减法

1
2
a-b
torch.sub(a,b)

乘法

1
2
a*b
torch.mul(a,b)

除法

1
2
a/b
torch.div(a,b)

矩阵相乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
torch.mm(a,b) # martix mul
torch.matmul(a,b)
a@b

# deom
a=torch.rand(4,784)
x=torch.rand(4,784)
w=torch.rand(512,784)
(x@w.t()).shape
# torch.Size([4,512])

# 对于高维矩阵
# mm无法运算
# matmu则会运算后两维

幂运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a=torch.full([2,2],3)
a.pow(2)
a**2
# 开方
a.sqrt()
a**0.5
# 指数
a=torch.exp(torch.ones(2,2))
torch.log(a)
# 近似值
torch.ceil() # 上
torch.floor() # 下
# 四舍五入
torch.round()
# 裁剪
torch.trunc() # 整数部分
torch.frac() # 小数部分

梯度裁剪

1
2
torch.clamp(10) # 将所有小于10的设置为10
torch.clamp(min,max) # 限制数据范围

7.统计API

范数norm

L0范数:向量中非零元素的数目

L1范数:向量中各个元素的绝对值之和,也称稀疏规则算子

L2范数:向量中各个元素的平方求和后开根号。其回归称为“岭回归”,也称权值衰减

L2范数可以防止过拟合,提高模型泛化能力

1
2
3
tensor.norm(1)
tensor.norm(2)
tensor.norm(1,dim=1) # 指定维度上所有元素做范数

统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
tensor.min()
tensor.max()
tensor.mean() # 元素累加除以元素个数
tensor.prod() # 累乘 所有元素相乘
tensor.sum()
tensor.argmax()
tensor.argmin()

# 返回dim=1上的最大三个元素及其下标
# largest=False则会返回最小
tensor.topk(3,dim=1,largest=False)

# 返回第 4 小的元素及其下标
tensor.kthvalue(4,dim=1)

# deom
a.shape
# [4,10]
a.max(dim=1)
# 会从四个维度里各取dim=1上的最大值,并返回下标
a.max(dim=1,keepdim=True)
# 这样会保持原有的形状
# 不会破坏返回值的结构

比较

返回的都是逻辑矩阵

1
2
3
4
5
> >=
< <=
!=
==
torch.eq(a,b)

8.高级操作

三元运算符

torch.where(condition,x,y)->Tensor

out:xiifconditionielseyiout: x_i\quad if \quad condition_i \quad else \quad y_i

1
2
3
4
cond=torch.rand([2,2])
a=torch.full([2,2],0)
b=torch.full([2,2],1)
torch.where(cond>0.5,a,b)

查表

torch.gather(input,dim,index,out=None)
在input中的dim维寻找符合index的值


随机梯度下降

1.梯度

导数:某一方向上的变化率

偏微分:指定方向的变化率

梯度:偏微分向量[所有方向的变化率向量]

梯度具有方向大小

那么,所谓梯度下降,就是沿着梯度变化最大(也就是函数本身变化最快)的方向前进。这样在满足一定学习率的情况下,能够到达局部极小值点

鞍点:某一方向达到了局部最小,但另一方向处于局部最大

影响搜索结果的因素:

  • 初始状态
  • 学习率
  • 动量
1
2
3
4
5
6
torch.autograd.grad(equation,[parameter])
# 返回参数的梯度向量

equation.backward()
# 不会返回值
parameter.grad()

2.激活函数

2.1Sigmoid

科学家发现动物的神经元存在某一阈值,当输入刺激小于该阈值时,不会引起神经元变化。于是,学者们借鉴该思想,设置了激活函数。

激活函数是不连续的,不可以求导。为了解决单层神经元激活函数不可导的情况,科学家提出了平滑连续的激活函数曲线,即Sigmoid,也叫Logistic

其导数性质能够直接通过本身求得,十分方便,能将数据范围压缩到[0,1]

sigmoid会出现梯度离散现象,即长时间的梯度不更新

1
2
a=torch.linspace(-100,100,10)
torch.sigmoid(a)

2.2Tanh

常见于RNN网络

取值为[-1,1]

1
torch.sigmoid(a)

2.3Rectified Linear Unit

减少梯度离散和梯度爆炸,保证梯度连续

1
2
3
torch.relu(a)
import torch.nn.function as F
F.relu(a)

2.4LeakyReLU

1
nn.LeakyReLU(inplace=True)

3.LOSS

MSE

  • 均方根误差

  • torch.norm(y-pred,2).pow(2)
    
    F.mse_loss(x,y)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    #### **Softmax**

    + 常用于分类任务

    + 输入是对于类别强度值,输出是概率

    + 柔性最大传输网络能够保证概率之和为1

    + 属于金字塔效应函数

    + 每个节点对每个输入都有偏导

    + ```python
    a=torch.rand(3,requires_grad=True)
    from torch.nn import functional as F
    p=F.softmax(a,dim=0)
    torch.autograd.grad(p[1],[a],retain_graph=True)
    torch.autograd.grad(p[2],[a],retain_graph=True)

注意torch是动态图,每次都需要更新,backward时得格外注意,若是想要不更新,可以使用retain_graph参数。求导只能对scalar类型进行求导。

Cross Entropy

置信度的度量,更高的不确定性

Entropy=iP(i)logP(i)Entropy=-\sum_iP(i)logP(i)

1
2
a=torch.full([4],1/4.)
-(a*torch.log2(a)).sum()

衡量两个分布的不稳定

H(p,q)=p(x)logq(x)H(p,q)=H(p)+DKL(pq)H(p,q)=-\sum p(x)logq(x) \\ H(p,q)=H(p)+D_{KL}(p|q)

KL即分布离散度

优化目标即让两个分布越来越接近,也就是KL最小

一个好的模型应该让不确定性降到最低

于是,优化就是判断

min.CEmin.\quad CE

例如一个二分类案例

  • 第一步是将所有预测概率(即P(cat)、P(dog))与实际概率Q(cat)、Q(dog)求交叉熵
  • y表示预测概率,p表示实际概率
1
2
3
4
5
6
7
8
9
10
11
x=torch.randn(1,784)
w=torch.randn(10,784)
logits=x@w.t()
F.cross_entrop(logits,torch.tensor([3]))

# 交叉熵的值是先经过softmax层,然后走一个log取对数,再通过nllloss进行计算

# CE=softmax+log+nll_loss
pre=F.softmax(logits,dim=1)
pred_log=torch.log(pre)
F.nll_loss(pred_log,torch.tensor([3]))

1.感知机

感知机(perceptron)是二类分类的线性分类模型,其输入为实例的特征向量,输出为实例的类别。

单层感知机

输入层:第0层,其元素表示xn0x_n^0,0表示第1层,n表示所在节点次序

隐含层:第1层,其元素表示为Wij1W_{ij}^1,1表示第1层,i表示上一层节点,j表示当前层连接节点

激活层:其元素表示为O11O_1^1,为经过激活后的元素。

损失层:E层,表示loss

发现其导数仅跟神经元节点输出和输入层有关。

1
2
3
4
5
6
7
8
9
10
11
12
# 一个简单的单层感知机求导demo
# 输入数据
x=torch.randn(1,10)
# 隐层权重
w=torch.randn(1,10,requires_grad=True)
# 激活函数
o=torch.sigmoid(x@w.t())
# 损失表达
loss=F.mse_loss(torch.ones(1,1),o)
# 反向传播
loss.backward()
w.grad

多层感知机

1
2
3
4
5
6
7
8
9
10
11
12
# 一个简单的多层感知机求导demo
# 输入数据
x=torch.randn(1,10)
# 隐层权重
w=torch.randn(2,10,requires_grad=True)
# 激活函数
o=torch.sigmoid(x@w.t())
# 损失表达
loss=F.mse_loss(torch.ones(1,2),o)
# 反向传播
loss.backward()
w.grad

2.链式法则


3.MLP反向传播推导

Multi-Layer Perceptron 多层感知机


4.函数优化实例

target

实现绘制该函数覆盖图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def himmelblau(x):
return (x[0]**2+x[1]-11)**2+(x[0]+x[1]**2-7)**2

import numpy as np
import matplotlib.pyplot as plt

x=np.arange(-6,6,0.1)
y=np.arange(-6,6,0.1)
X,Y=np.meshgrid(x,y)
print("X,Y maps: ",X.shape,"before :",x.shape)

Z=himmelblau([X,Y])
fig=plt.figure("HIMMELBLAU")
ax=fig.gca(projection='3d')
ax.plot_surface(X,Y,Z)
ax.view_init(60,-30)
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()

利用随机梯度下降优化

注意优化目标不再是LOSS,而是函数本身,求导对象是x,即δPreδx\frac{\delta P_{re}}{\delta x}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x=torch.tensor([0.,0.],requires_grad=True)
# 定义一个优化器
optimizer=torch.optim.Adam([x],lr=1e-3)

for i in range(20000):
# 寻找本函数的最优区间
pred=himmelblau(x)
# 需要将累计梯度清空
optimizer.zero_grad()
# 寻找梯度方向
pred.backward()
# 自动优化,也就是x-lr*x'
optimizer.step()

if i % 2000 ==0:
print("step {}: x={},f(x)={}".format(i,x.tolist(),pred.item()))

神经网络

1.逻辑回归

分类问题

  • 问题一:当权重改变时,梯度很可能不变!因为准确率不变!
  • 问题二:梯度不连续!【从0.6直接变成0.8】

逻辑回归

  • 本质上更像分类
  • 使用了sigmoid函数,使用MSE评估回归,Cross Entropy评估分类
  • 在判断输出值与阈值的距离,用以评估二分类,并通过MSE评估输出效果
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
import torch
from torchvision import datasets,transforms
from torch.utils.data import DataLoader
from torch.nn import functional as F

batch_size=200
lr=0.01
epochs=10

train_loader=DataLoader(
datasets.MNIST('../data',train=True,download=True,transform=
transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307),(0.3081,))
])),
batch_size=batch_size,shuffle=True
)

test_loader=DataLoader(
datasets.MNIST('../data',train=False,transform=
transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307),(0.3081,))
])),
batch_size=batch_size,shuffle=True
)

w1=torch.randn(200,784,requires_grad=True)
b1=torch.zeros(200,requires_grad=True)

w2=torch.randn(200,200,requires_grad=True)
b2=torch.zeros(200,requires_grad=True)

w3=torch.randn(10,200,requires_grad=True)
b3=torch.zeros(10,requires_grad=True)

def forward(x):
x=x@w1.t()+b1
x=F.relu(x)
x=x@w2.t()+b2
x=F.relu(x)
x=x@w3.t()+b3
x=F.relu(x)
return x

optimizer=torch.optim.SGD([w1,b1,w2,b2,w3,b3],lr=1e-3)
criteon=torch.nn.CrossEntropyLoss()

for epoch in range(10000):
for batch_idx,(data,target) in enumerate(train_loader):
data=data.view(-1,28*28)
logits=forward(data)
loss=criteon(logits,target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch_idx % 100==0:
print("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss:{:.6f}".
format(epoch,batch_idx*len(data),len(train_loader.dataset),
100.*batch_idx/len(train_loader),loss.item()))

# test_loss=0
# correct=0
# for data,target in test_loader:
# data=data.view(-1,28*28)
# logits=forward(data)
# test_loss+=criteon(logits,target).item()
# pred=logits.data.max(1)[1]
# correct+=pred.eq(target.data).sum()
# test_loss/=len(test_loader.dataset)
# print("\nTest set: Average loss: {:.4f}, Accuracy:{}/{} ({:.0f}%)\n".format(
# test_loss,correct,len(test_loader.dataset),100.*correct/len(test_loader.dataset)
# ))

发现陷入了局部最小值,本次并没有做初始化,如果做了初始化就能大大降低损失。

2.全连接层

1
2
3
4
5
6
7
8
9
x=torch.tensor([1,784])
layer1=nn.Linear(784:in,200:out)
layer2=nn.Linear(200,200)
layer3=nn.Linear(200,10)
x=layer3(layer2(layer1(x)))

# 添加激活函数
x=layer1(x)
x=F.relu(x,inplace=True)

如何创建一个层?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MLP(nn.Module):
def __init__(self):
super(MLP,self).__init__()
self.model=nn.Sequential(

# nn.Sequential就是一个容器
# 可以添加任何继承自nn.Module的类
# 并让输入依次经过容器内放置的神经元

nn.Linear(784,200),
nn.ReLU(inplace=True),
nn.Linear(200,200),
nn.ReLU(inplace=True),
nn.Linear(200,10),
nn.ReLU(inplace=True),
)

def forward(self,x):
# 继承了nn.module.forward的方法
return self.model(x)

pyTorch有两种风格的API,一种是class风格的API,如nn.ReLU,一种是函数风格的API,如F.relu()

类风格的API需要构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
net=MLP()

optimizer=torch.optim.SGD(net.parameters(),lr=1e-3)
criteon=torch.nn.CrossEntropyLoss()

for epoch in range(10000):

for batch_idx,(data,target) in enumerate(train_loader):
data=data.view(-1,28*28)

logits=net(data)
loss=criteon(logits,target)

optimizer.zero_grad()
loss.backward()

optimizer.step()
if batch_idx % 100==0:
print("Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss:{:.6f}".
format(epoch,batch_idx*len(data),len(train_loader.dataset),
100.*batch_idx/len(train_loader),loss.item()))

3.GPU加速

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 如何使用GPU加速
# cuda n 第n显卡
# 需要用GPU时,直接用 .to 方法
# 注意对模块调用GPU方法时,返回的模块不会改变
# 而对数据调用GPU方法时,返回的数据跟原始数据不一样,会产生一个CPU数据和一个GPU数据

device=torch.device("cuda:0")
net=MLP().to(device)
optimizer=optim.SGD(net.paameters(),lr=lr)
criteon=nn.CrossEntropyLoss().to(device)

for e in range(epochs):
for batch_idx,(data,target) in enumerate(train_loader):
data=data.view(-1,28*28)
data,target=data.to(device),target.cuda()

精度验证

1
2
3
4
5
6
7
8
9
logits=torch.rand(4,10)
pred=F.softmax(logits,dim=1)
pred_label=pred.argmax(dim=1)
label=torch.tensor([9,2,3,4])

# eq返回的是一个逻辑矩阵,是就是1,不是就是0
correct=torch.eq(pred_label,label)
# 该方法能求出多少被成功预测
correct.sum().float().item()/4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
test_loss=0
correct=0

for data,target in test_loader:
data=data.view(-1,28*28)
data,target=data.to(device),target.cuda()

logits=net(data)
test_loss+=criteon(logits,target).item()

pred=logits.argmax(dim=1)
correct+=pred.eq(target).float().sum().item()

test_loss/=len(test_loader.dataset)

可视化交互

TensorboardX

visdom

相较于TBX读取的numpy,VD能直接跟Tensor进行交互(当然只能是img),且不会大量存放监听文件。

本质上是一个外部的服务器,需要配合命令。

python -m visdom.server

于是就能在8097端口找到visdom。

如何使用?

单线追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
from visdom import Visdom
viz=Visdom()

# 创建直线
# Y,X win 是标志符,类似ID
# 每个窗格叫做env,即一个新的进程
# 默认env=‘main’
viz.line([0.],[0.],win='train_loss',opts=dict(title="train loss"))

# 能够逐渐添加数据
# y:loss x:全局迭代
# update: "append" 不会覆盖
viz.line([loss.item()],[global_step],win="train_loss",update="append")

多线追踪

1
2
3
4
5
6
7
8
from visdom import Visdom
viz=Visdom()

# 创建直线
viz.line([0.0,0.0],[0.],win='test',opts=dict(title="train loss&acc.",legend=['loss','acc.']))

# 能够逐渐添加数据
viz.line([[test_loss,correct/len(test_loader.dataset)]],[global_step],win="test",update="append")

可视化

1
2
3
4
from visdom import Visdom
viz=Visdom()
viz.images(data.view(-1,1,28,28),win='x')
viz.text(str(pred.detach().cpu().numpy()),win='pred',opts=dict(title='pred'))

4.过拟合

5.交叉验证

如何划分train-val-test

train人为切割:

1
2
3
4
5
6
7
8
9
10
train_db,val_db=torch.utils.data.random_split(train_db,[50000,10000])

train_loader=torch.utils.data.DataLoader(
# 获取数据位置
train_db,
# 每一批次的数据量
batch_size=batch_size,
# 是否打乱
shuffle=True
)

关于批训练

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
import torch
import torch.utils.data as Data #将数据分批次需要用到它

torch.manual_seed(1) # 种子,可复用
BATCH_SIZE = 8 #设置批次大小

x = torch.linspace(1, 15, 15) # 1到15共15个点
y = torch.linspace(15, 1, 15) # 15到1共15个点

torch_dataset = Data.TensorDataset(x, y) #将x,y读取,转换成Tensor格式
loader = Data.DataLoader(
dataset=torch_dataset, # torch TensorDataset format
batch_size=BATCH_SIZE, # 最新批数据
shuffle=True, # 是否随机打乱数据
num_workers=2, # 用于加载数据的子进程
)

def show_batch():
for epoch in range(3): # 对整个数据集进行3次培训
for step, (batch_x, batch_y) in enumerate(loader): # 每个训练步骤
# 此处省略一些训练数据步骤...
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())

if __name__ == '__main__':
show_batch()
'''
(1)每次训练5个数据,打乱数据;每进行一次完整的训练需要进行3个训练步骤:
Epoch: 0 | Step: 0 | batch x: [10. 12. 9. 5. 1.] | batch y: [ 6. 4. 7. 11. 15.]
Epoch: 0 | Step: 1 | batch x: [ 7. 15. 8. 13. 3.] | batch y: [ 9. 1. 8. 3. 13.]
Epoch: 0 | Step: 2 | batch x: [ 2. 6. 14. 4. 11.] | batch y: [14. 10. 2. 12. 5.]
Epoch: 1 | Step: 0 | batch x: [ 3. 10. 8. 13. 2.] | batch y: [13. 6. 8. 3. 14.]
Epoch: 1 | Step: 1 | batch x: [ 5. 4. 12. 14. 1.] | batch y: [11. 12. 4. 2. 15.]
Epoch: 1 | Step: 2 | batch x: [15. 9. 11. 6. 7.] | batch y: [ 1. 7. 5. 10. 9.]
Epoch: 2 | Step: 0 | batch x: [ 8. 7. 3. 10. 12.] | batch y: [ 8. 9. 13. 6. 4.]
Epoch: 2 | Step: 1 | batch x: [ 6. 13. 9. 4. 15.] | batch y: [10. 3. 7. 12. 1.]
Epoch: 2 | Step: 2 | batch x: [14. 2. 5. 1. 11.] | batch y: [ 2. 14. 11. 15. 5.]
(2)每次训练8个数据,打乱数据;每进行一次完整的训练需要进行2个训练步骤,一次8个数据,一次7个数据:
Epoch: 0 | Step: 0 | batch x: [10. 12. 9. 5. 1. 7. 15. 8.] | batch y: [ 6. 4. 7. 11. 15. 9. 1. 8.]
Epoch: 0 | Step: 1 | batch x: [13. 3. 2. 6. 14. 4. 11.] | batch y: [ 3. 13. 14. 10. 2. 12. 5.]
Epoch: 1 | Step: 0 | batch x: [ 3. 10. 8. 13. 2. 5. 4. 12.] | batch y: [13. 6. 8. 3. 14. 11. 12. 4.]
Epoch: 1 | Step: 1 | batch x: [14. 1. 15. 9. 11. 6. 7.] | batch y: [ 2. 15. 1. 7. 5. 10. 9.]
Epoch: 2 | Step: 0 | batch x: [ 8. 7. 3. 10. 12. 6. 13. 9.] | batch y: [ 8. 9. 13. 6. 4. 10. 3. 7.]
Epoch: 2 | Step: 1 | batch x: [ 4. 15. 14. 2. 5. 1. 11.] | batch y: [12. 1. 2. 14. 11. 15. 5.]
'''

6.正则化

当范数接近于0时,模型的复杂度会降低。可以有效处理过拟合。

是一个weight_decay

在学习时,如果数据提供的特征有些影响模型的复杂度或这个特征数据异常特别多,那么应该尽量减少(甚至删除某个特征的影响),这就是正则化

L2正则化:

  • 作用:可以使得其中的一些权重w都很小,削弱某个特征的影响

  • 优点:越小的参数说明模型越简单,越简单的模型越不容易发生过拟合

  • Ridge回归

  • 作用:可以将一些w直接设置为0,直接删除该特征的影响

  • LASSO回归

如何添加一个L2正则化呢?

1
2
3
4
5
6
7
device=torch.device("cuda:0")
net=MLP().to(device)

# 我们只需要设置w参数即可,能够降低网络复杂度
optimizer=optim.SGD(net.parameters(),lr=learning_rate,weight_decay=0.01) # 权值递减

criteon=nn.CrossEntropyLoss().to(device)

对于L1正则化

1
2
3
4
5
6
7
regularization_loss=0
# 对每一个参数都加上去
for param in model.parameters():
regularization_loss+=torch.sum(torch.abs(param))
classify_loss=criteon(logits,target)
# 本质上是对网络参数做约束
loss=classify_loss+0.01*regularization_loss

7.动量

momentum

如何实现动量捏

1
2
3
optimizer=torch.optim.SGD(model.parameter(),args.lr,momentum=args.momentum,weight_decay=args.weight_decay)

scheduler=ReduceLROnPlateau(optimizer,'min')

部分优化器如Adam内置动量属性了,不能额外设置

learning-rate decay

学习率如何选择是一个比较困难的过程,那么我们为什么不考虑让他自动衰减呢?

如上图所示,发生斜率爆炸的情况是因为改变了学习率,可能之前的过程都是在双峰之间打转,而改变了学习率马上就能找到新的谷底。

1
2
3
4
5
6
7
8
9
10
11
12
# Scheme 1.

optimizer=torch.optim.SGD(model.parameter(),args.lr,momentum=args.momentum,weight_decay=args.weight_decay)

# 监听loss,当patient的值(如连续十次调用)超过阈值时,降低学习率
scheduler=ReduceLROnPlateau(optimizer,'min')

for epoch in xrange(args.start_epoch,args.epochs):
train(train_loader,model,criterion,optimizer,epoch)
result_avg,loss_val=validate(val_loader,model,criterion,epoch)
# 每次监听loss_val是否达到平衡
scheduler.step(loss_val)
1
2
3
4
5
6
7
8
# Scheme 2

# 每30次进行衰减
scheduler=StepLR(optimizer,step_size=30,gamma=0.1)
for epoch in range(100):
shceduler.step()
train(...)
validate(...)

Early Stopping

有概率断掉某些层

如何添加dropout?

我们在任意一层添加即可

1
2
3
4
5
net_dropped=torch.nn.Sequential(
torch.nn.Linear(784,200),
torch.nn.Dropout(0.5), # drop 50% of the neutron
torch.nn.ReLU()
)

当然,在测试的时候,我们需要把断掉的层添加回去

1
2
3
4
5
6
7
8
9
10
for epoch in range(epochs):
net_dropped.train()
for batch_idx,(data,target) in enumerate(train_loader):
...
# 添加断层
net_dropped.eval()
test_loss=0
correct=0
for data,target in test_loader:
...

stochastic

x>f(x)  N(0,x)x--->f(x) ~~ N(0,x)

也就是说并不是完全符合映射关系,而是符合某一分布

为了节省内存,并不对全部数据做梯度,而是对少量banch数据做梯度


卷积神经网络

多卷积核

注意Kernel的多通道运算最后还需要求和

每一次卷积观察的特征都不同,但根据经验公式可以发现,基本上是由局部到全局的变化。

使用Pytorch实现Convolution

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
# 参数分别为: channel数量 Kernel数量 Kernel大小 步长 padding大小
layer=nn.Conv2d(1,3,kernel_size=3,stride=1,padding=0)
x=torch.rand(1,1,28,28)

# 可以看到输出的数据大小发生了改变
# 3是三个卷积核所造成的
out=layer.forward(x)
# torch.Size([1,3,26,26])

layer=nn.Conv2d(1,3,kernel_size=3,stride=1,padding=1)
# 此时数据维度匹配,不再发生改变
out=layer.forward(x)
# torch.Size([1,3,28,28])

layer=nn.Conv2d(1,3,kernel_size=3,stride=2,padding=1)
# 步长改变后输出大小也会发生改变
out=layer.forward(x)
# torch.Size([1,3,14,14])

out=layer(x)
# 可以直接调用
# torch.Size([1,3,14,14])


# 查看权重和偏置值
layer.weight
layer.bias

池化层

降采样Downsample

最大池化Max pooling

通过一个移动窗口对图像进行处理,从而达到降采样的效果

1
2
3
4
5
6
7
8
9
10
x=out
# torch.Size([1,16,14,14])

# 采样核大小是2
layer=nn.MaxPool2d(2,stride=2)
out=layer(x)
# torch.Size([1,16,7,7])

out=F.avg_pool3d(x,2,stride=2)
# torch.Size([1,16,7,7])

上采样

1
2
3
x=out
out=F.interpolate(x,scale_factor=2,mode='nearest')
# linear bilinear bicubic

Batch Normal

为什么要batch normal

1
2
normalize=transforms.Normalize(mean=[0.485,0.456,0.406],
std=[0.229,0.224,0.225])

对于一个维度为[6,3,28,28]的均值,一般是[3]大小,统计的是六张图片每个通道的均值。

1
2
3
4
5
6
x=torch.rand(100,16,784)
layer=nn.BatchNorm1d(16)
out=layer(x)
# 获取
layer.running_mean
layer.running_var

而对于2d数据:

1
2
3
4
5
6
7
8
x.shape
# [1,16,7,7]
layer=nn.BatchNorm2d(16)
out=layer(x)
layer.weight
# [16]
layer.bias
# [16]

BatchNorm的优势:

  • 更快的收敛速度
  • 更好的Robust
  • 更好的执行

经典卷积神经网络