目录
一 视频学习
1.绪论
2.深度学习概述
二 代码学习
1.Pytorch基础练习
2.螺旋数据分类
人工智能(Artificial Intelligence):使一部机器像人一样进行感知、认知、决策、执行的人工程序或系统
人工智能三个层面:
应用:金融,内容创作,机器人
知识工程(人工定义)vs机器学习(自动训练):
机器学习的定义:从数据中自动提取知识
模型分类
人工智能、机器学习和深度学习的关系:
传统机器学习vs深度学习:
神经网络结构的发展:
深度学习的“不能”:
深度学习——准确性高但解释性差
连接主义&符号主义
生物神经元的启发:
激活函数:
单层感知器:
首个可以学习的人工神经网络,可以实现一些简单的与非或门
单层→多层感知器:
单层感知器解决不了非线性的分类问题(异或),复杂的逻辑单元可以通过组合简单的与或非单元来实现来实现,由此形成了多层的感知器。
万有逼近定理:
如果一个隐层包含足够多的神经元,三层前馈神经网络(输入-隐层-输出)能以任意精度逼近任意预定的连续函数。
- 为什么线性分类任务组合后可以解决非线性分类任务?
- 对于第二层感知器而言,看到的不是原始图像分布,而是被第一层感知器处理后的图像分布
当隐层足够宽时,双隐层感知器(输入-隐层1-隐层2-输出)可以逼近任意非连续函数,可以解决任何复杂的分类问题
神经网络每一层的作用:
实验证明,瘦高的网络比矮胖的网络更有效。在神经元总数相当的情况下,增加网络深度可以比增加宽度带来更强的网络表示能力(产生更多的线性区域)。深度和宽度对函数复杂度的贡献是不同的,深度的贡献是指数增长的,而宽度的贡献是线性的。
梯度和梯度下降:
多元函数在每个点可以有多个方向,每个方向都可以计算导数,称为方向导数。梯度是一个向量,它的方向是最大方向导数的方向,它的模是方向导数的最大值。换句话来说,梯度的方向是函数值往极值点变化最快的方向。
梯度下降法是求解无约束最优化问题最常用的方法之一。梯度下降法用一阶泰勒展开式代替原函数,迭代计算原函数在当前取值邻域内的极值。具体的,函数迭代的每一步都朝着梯度的负方向,直到误差小于阈值。
神经网络的参数学习:误差反向传播(BP算法)
多层神经网络可看成是一个复合的非线性多元函数
深层神经网络的问题:梯度消失
误差经过每一项的sigmoid激活函数,可能会变得非常非常小
- 为什么有的时候单隐层可以解决的问题,加了多隐层之后反而解决不了了?
- 经过前向传播算出的损失,在反向传播的过程中可能会逐层越来越小,甚至可以忽略,由此造成靠前的参数不会发生变化,只有最后一层会发生更新
逐层预训练(layer-wise pre-training)
每次训练一层网络,逐层累加,在最后一层加入监督信息,从上到下做一次微调。经过逐层预训练后的解会更收敛,训练会更快
逐层预训练怎么实现——受限玻尔兹曼机和自编码器
自编码器(autoencoder):最小化重构误差
堆叠自编码器(stacked autoencoder, SAE) :将多个自编码器得到的隐层串联,得到完整的神经网络。所有层预训练完成后,进行基于监督学习的全网络微调
受限玻耳兹曼机(RBM):
堆叠RBM(DBM,深度信念网络):
一个DBN模型由若干个RBM堆叠而成,最后加一个监督层(如BP网络)。训练过程由低到高逐层训练:
DBN(Deep Belief Network) VS DBM (Deep Boltzmann Machine):DBM没有监督层,是若干个RBM的直接堆叠,是纯粹的无向图模型,每两层间互有反馈
一般玻尔兹曼机(BM):
自编码器 |
受限玻耳兹曼机 |
|
结构 |
编码和解码函数不同 |
共享权重矩阵,但有两个偏置向量 |
原理 |
自编码器通过非线性变换学习特征,是确定的,特征值可以为任何实数; |
RBM基于概率分布定义,高层表示为底层特征的条件概率,输出只有两种状态(未激活激活),用二进制0/1表示; |
训练优化 |
自编码器通过最损失函数L最小化重构输入数据,直接用BP优化求解 |
RBM基于最大似然,能量函数偏导无法直接计算,基于采样方法进行估计 |
模型类型 |
判别模型 |
生成模型 |
目前逐层预训练已经很少使用,玻尔兹曼机作为一种概率生成式模型应用到了协同滤波推荐、数据降维、时间序列降维问题;自编码器也经历了变种,正则自编码器(Regularized AE)应用于使提取的特征表达符合某种性质,稀疏自编码器(Sparse AE)应用于提取稀疏特征表达
创建Tensor有多种方法,包括:ones, zeros, eye, arange, linspace, rand, randn, normal, uniform, randperm等等
import torch
x = torch.tensor(666) # 可以是一个数
x = torch.tensor([1,2,3,4,5,6]) # 可以是一维数组(向量)
x = torch.ones(2,3) # 可以是二维数组(矩阵)
x = torch.ones(2,3,4) # 可以是任意维度的数组(张量)
x = torch.empty(5,3) # 创建一个空张量
x = torch.rand(5,3) # 创建一个随机初始化的张量
x = torch.zeros(5,3,dtype=torch.long) # 创建一个全0的张量,里面的数据类型为long
# 基于现有的tensor,创建一个新tensor,
# 从而可以利用原有的tensor的dtype,device,size之类的属性信息
y = x.new_ones(5,3)
# 利用原来的tensor的大小,但是重新定义了dtype
z = torch.randn_like(x, dtype=torch.float)
基本运算包括: abs/sqrt/div/exp/fmod/pow ,及一些三角函数 cos/ sin/ asin/ atan2/ cosh,及 ceil/round/floor/trunc 等
布尔运算包括: gt/lt/ge/le/eq/ne,topk, sort, max/min
线性计算包括: trace, diag, mm/bmm,t,dot/cross,inverse,svd 等
import torch
# 创建一个 2x4 的tensor
m = torch.Tensor([[2, 5, 3, 7],[4, 2, 1, 9]])
print(m.size(0), m.size(1), m.size(), sep=' -- ')
# 返回m中元素的数量
print(m.numel())
# 返回第0行,第2列的数
print(m[0][2])
# 返回第1列的全部元素
print(m[:, 1])
# Create tensor of numbers from 1 to 5
v = torch.arange(1, 5)
v=v.float()
#点乘
m @ v
# Calculated by 1*2 + 2*5 + 3*3 + 4*7
m[[0], :] @ v
#把一个2x4的随机张量加到m上
m + torch.rand(2, 4)
# 转置,由 2x4 变为 4x2
print(m.t())
# 返回一个start=3, end=8, steps=20的一维张量
torch.linspace(3, 8, 20)
数据类型的转变:
torch.long() 将tensor转换为long类型
torch.half() 将tensor转换为半精度浮点类型
torch.int() 将该tensor转换为int类型
torch.double() 将该tensor转换为double类型
torch.float() 将该tensor转换为float类型
torch.char() 将该tensor转换为char类型
torch.byte() 将该tensor转换为byte类型
torch.short() 将该tensor转换为short类型
绘制直方图:
from matplotlib import pyplot as plt
# matlabplotlib 只能显示numpy类型的数据,下面展示了转换数据类型,然后显示
# 注意 randn 是生成均值为 0, 方差为 1 的随机数
# 下面是生成 1000 个随机数,并按照 100 个 bin 统计直方图
plt.hist(torch.randn(1000).numpy(), 100);
# 当数据非常非常多的时候,正态分布会体现的非常明显
plt.hist(torch.randn(10**6).numpy(), 100);
tensor的拼接:
# 创建两个 1x4 的tensor
a = torch.Tensor([[1, 2, 3, 4]])
b = torch.Tensor([[5, 6, 7, 8]])
# 在 0 方向拼接 (即在 Y 方各上拼接), 会得到 2x4 的矩阵
print( torch.cat((a,b), 0))
# 在 1 方向拼接 (即在 X 方各上拼接), 会得到 1x8 的矩阵
print( torch.cat((a,b), 1))
3个类别,每个类别有1000个样本。使用torch.zeros()对样本矩阵进行初始化,根据公式划分出三类样本
关键代码如下:
learning_rate = 1e-3
lambda_l2 = 1e-5
# nn 包用来创建线性模型
# 每一个线性模型都包含 weight 和 bias
model = nn.Sequential(
nn.Linear(D, H), #第一层输入为2(因为特征维度为主2),输出为100;
nn.Linear(H, C) #第二层输入为100(上一层的输出),输出为3(类别数)
)
model.to(device) # 把模型放到GPU上
# nn 包含多种不同的损失函数,这里使用的是交叉熵(cross entropy loss)损失函数
criterion = torch.nn.CrossEntropyLoss()
# 这里使用optim包进行随机梯度下降(stochastic gradient descent)优化
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2)
# 开始训练
for t in range(1000):
# 把数据输入模型,得到预测结果
y_pred = model(X) #[3000, 3]
# 计算损失
loss = criterion(y_pred, Y)
#沿着第二个方向(即X方向)提取最大值。最大的那个值存在 score 中,所在的位置(即第几列的最大)保存在 predicted 中
score, predicted = torch.max(y_pred, 1)
#计算准确率
acc = (Y == predicted).sum().float() / len(Y)
print('[EPOCH]: %i, [LOSS]: %.6f, [ACCURACY]: %.3f' % (t, loss.item(), acc))
#清除单元格输出
display.clear_output(wait=True)
# 反向传播前把梯度置 0
optimizer.zero_grad()
# 反向传播优化
loss.backward()
# 更新全部参数
optimizer.step()
为什么每一次反向传播前,都要把梯度清零?
- 因为PyTorch默认会对梯度进行累加。在反向传播时,如果不想先前的梯度对当前的梯度计算产生影响,就需要手动清零。Pytorch设计这种需要手动清零的方式,目的在于反向传播时,可以支持多种传播方式
训练1000个epoch效果如下,可以看出效果并不理想,对于较为复杂的非线性分类,线性模型难以实现准确分类。
与上述模型的不同之处在于,在两层之间加入了一个 ReLU 激活函数
# 这里可以看到,和上面模型不同的是,在两层之间加入了一个 ReLU 激活函数
model = nn.Sequential(
nn.Linear(D, H),
nn.ReLU(),
nn.Linear(H, C)
)
实验结果如下,在两层神经网络里加入 ReLU 激活函数以后,分类的准确率得到了显著提高。
尝试将ReLU 激活函数更换为课件中所涉及、目前较为常用的激活函数,实验结果如下表所示
损失 | 准确率 | 分类效果 | |
Sigmoid | 0.757981 | 0.515 | |
Tanh | 0.301596 | 0.848 | |
ReLU | 0.170588 | 0.953 | |
LeakyReLU |
0.167449 | 0.954 |
由实验结果得出,将激活函数更换为LeakyReLU可以得到较好的实验效果。