Part1 视频学习:绪论
1 人工智能和机器学习概述
人工智能(Artificial Intelligence):使一部机器像人一样进行感知、认知、决策、执行的人工程序或系统。
1.1 人工智能的三个层面
计算智能:能进行存储和计算
感知智能:具备类似于人的视觉、听觉、触觉等感知能力,能听会说,能看会认
认知智能:实现类似于人的认知能力,能理解、会思考、能产生对策,概念、意识、观念都是认知智能的表现
1.2 人工智能+
人工智能+金融:风控、征信、辅助交易等
人工智能+内容创作:自然语言处理、语音合成等
人工智能+机器人:视觉处理、语音识别、动作控制等
1.3 逻辑演绎vs归纳总结
1.4 知识工程vs机器学习
知识工程/专家系统:根据专家定义的知识和经验来进行推理和推断,从而模拟人类专家的决策过程,并解决问题
机器学习:机器基于有标注的数据进行自动训练
两者对比:
知识工程的规则是手工设计的,可以人为控制,结果具有可解释性,但构建过程费时费力,并且无法消除专家主观经验带来的影响;
机器学习基于给出的数据进行自动学习,减少了人力消耗,提高了信息处理的效率,结果往往不易解释,但能有效减少主观因素的影响,具有较高的可信度
1.5 机器学习的定义
最常用定义:计算机系统能够利用经验提高自身的性能
可操作定义:机器学习本质是一个基于经验数据的函数估计问题
统计学定义:提取重要模式、趋势,并理解数据,即从数据中学习
总而言之,机器学习就是从数据中自动提取知识的过程
1.6 用机器学习解决问题
先判断该情况下能否用机器学习:当问题规模较大,用到的准则复杂度较高,针对该问题有完备的可供训练的数据,具备有意义的模式,并且该问题没有解析解的情况下,可以考虑使用机器学习的方式去解决
机器学习的三个要素:
- 模型:对要学习问题映射的假设,即对问题建模并确定假设空间
- 策略:从假设空间中学习或选择最优模型的准则,即确定目标函数
- 算法:根据目标函数求解最优模型的具体计算方法,即确定模型参数
参数模型 | 非参数模型 | |
优点 | 数据需求少,训练速度快 | 对数据适应性强,可拟合不同的函数形式 |
缺点 | 模型复杂度有限,与真实目标函数拟合度小 | 数据需求大,容易过拟合 |
判别模型 | 生成模型 | |
优点 | 1. 节省计算资源 2. 数据需求小 3. 有助于简化学习问题 | 1. 提供更多信息(建模边缘分布→采样生成样本) 2. 样本量大时,能更快收敛到真实分布 3. 支持复杂训练情况(无监督训练、存在隐变量时) |
缺点 | 1. 不能反映训练数据本身的特性 2. 先验结构具有不确定性 3. 变量间的关系不清楚,不可视 | 1. 数据需求大 2. 预测类问题准确率通常不如判别模型 |
2 深度学习概述
2.1 人工智能>机器学习>深度学习
2.2 传统机器学习vs深度学习
传统机器学习主要依靠人工设计特征,包括数据预处理、特征提取、特征转换等步骤,再基于特征训练得到分类器
深度学习对简单特征进行组合,得到更复杂的特征,从特征到输出的映射没有那么重要,因此深度学习又叫表示学习
2.3 神经网络结构的发展
深度学习的三个助推剂:大数据、算法、计算力
2.4 深度学习的“不能”
- 稳定性低:算法输出不稳定,容易被“攻击”(对抗样本,单像素攻击)
- 可调试性差:模型复杂度高,难以纠错和调试
- 参数不透明:模型层级复合程度高,参数不透明
- 增量性差:端到端训练方式对数据依赖性强,模型增量性差(小样本问题)
- 推理能力差:专注直观感知类问题,对开放性推理问题无能为力
- 机器偏见:人类知识无法有效引入进行监督,机器偏见难以避免
解释性的三个层次:
- 找得到:能明确哪些特征对输出有重要影响,能准确快速纠错
- 看得懂:算法能被人的知识体系所理解
- 留的下:知识得到有效存储、积累和运用,越学越聪明
2.5 连接主义+符号主义
- 把知识图谱作为先验知识应用于深度学习
- 基于深度学习来辅助知识图谱的构建
3 浅层神经网络
3.1 从生物神经元到M-P神经元模型
3.2 激活函数
可以使用激活函数实现非线性拟合
sigmoid函数存在的问题:①容易饱和;②输出不对称
3.3 感知器和神经网络
单层感知器:首个可以学习的人工神经网络
多层感知器的输出:g(W*x),其中g是激活函数,W是权重,x是输入
单层感知器无法实现异或
单隐层神经网络可视化:A Neural Network Playground (tensorflow.org)
万有逼近定理:如果一个隐层包含足够多的神经元,三层(单隐层)前馈神经网络(输入-隐层-输出)能以任意精度逼近任意预定的连续函数
- 第一层感知器:空间变换
- 第二层感知器:线性分类
双隐层感知器逼近非连续函数:当隐层足够宽时,双隐层感知器(输入-隐层1-隐层2-输出)可以逼近任意非连续函数,可以解决任何复杂的分类问题
神经网络的每一层都可以完成输入→输出空间变换,增加节点数可以增加线性转换能力,增加层数可以增加非线性转换次数
3.4 更宽or更深
在神经元数量相当的情况下,增加深度比增加宽度具有更强的网络表示能力,能产生更多的线性区域;深度的贡献是指数增长的,宽度的贡献是线性增长的
3.5 神经网络的参数学习:误差反向传播
将多层神经网络看作一个复合的非线性多元函数,给定训练数据和对应的标签,训练过程就是让损失尽可能小的过程
参数沿负梯度方向更新可以使函数值下降
前馈神经网络的BP算法:
3.6 梯度消失
前向传播和反向传播:
以sigmoid激活函数为例,该函数在0处梯度最大,越往两边梯度越小,最后在饱和区趋近于0,当值落在饱和区时,梯度趋近于0,而反向传播时需要与梯度相乘,当梯度接近0时,反向传播得到的梯度也会接近0,此时梯度不会有明显变化,这就造成了梯度消失,不利于参数更新
增加深度更容易产生梯度消失,造成误差无法传播,并且多层网络容易陷入局部极值,难以找到更优解,难以训练,因此,在很长一段时间内,三层神经网络是主流,要想实现更深的神经网络,需要采取措施来抑制梯度消失,比如预训练、更改激活函数等
Part2 代码练习
1 pytorch基础练习
代码链接:(colab)pytorch基础练习
pytorch是一个python库,既可以进行GPU加速的张量计算,也可以构建基于反向自动求导系统的深度神经网络
import torch
torch.__version__
# out:1.11.0+cu113
pytorch基础练习:
1.1 使用torch.tensor定义数据
tensor:即张量,是数字各种形式的总称
import torch
# 一个数
x = torch.tensor(666)
print(x)
# out:
# tensor(666)
# 一维数组(向量)
x = torch.tensor([1,2,3,4,5,6])
print(x)
# out:
# tensor([1, 2, 3, 4, 5, 6])
# 二维数组(矩阵)
x = torch.ones(2,3)
print(x)
# out:
# tensor([[1., 1., 1.],
# [1., 1., 1.]])
# 任意维度的数组(张量)
x = torch.ones(2,3,4)
print(x)
# out:
# tensor([[[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]],
# [[1., 1., 1., 1.],
# [1., 1., 1., 1.],
# [1., 1., 1., 1.]]])
# tensor支持float32,float64,float16,uint8,int8,int16,int32,int64等多种类型的数据
# 创建tensor有多种方法,比如ones,zeros,eye,arange,linspace,rand,randn,normal,uniform,randperm
# 空张量
x = torch.empty(2,3)
print(x)
# out:
# tensor([[1.0409e-35, 0.0000e+00, 3.3631e-44],
# [0.0000e+00, nan, 2.3694e-38]])
# 随机初始化的张量
x = torch.rand(2,3)
print(x)
# out:
# tensor([[0.4104, 0.4116, 0.9312],
# [0.6541, 0.3727, 0.3847]])
# long类型的全0张量
x = torch.zeros(2,3,dtype=torch.long)
print(x)
# out:
# tensor([[0, 0, 0],
# [0, 0, 0]])
# 基于原有的tensor创建新tensor,可以利用dtype,device,size等属性信息
# new_*方法可以利用之前的dtype,device
y = x.new_ones(2,3)
print(y)
# out:
# tensor([[1, 1, 1],
# [1, 1, 1]])
# 利用之前tensor的大小,并重新定义dtype
z = torch.randn_like(x,dtype=torch.float)
print(z)
# out:
# tensor([[ 1.3398, 0.4747, 1.7095],
# [-2.5772, 0.3592, -1.5591]])
1.2 定义操作
用tensor可以进行以下计算:
- 基本运算:加减乘除,求幂取余,包括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等
# 创建一个2*4的tensor
m = torch.Tensor([[1,2,3,4],[3,4,5,6]])
print(m.size(0),m.size(1),m.size(),sep='--')
# out:2--4--torch.Size([2, 4])
# 返回m的元素数量
print(m.numel())
# out:8
# 返回第0行第2列的数
print(m[0][2])
# out:tensor(3.)
# 返回第1列的全部元素
print(m[:,1])
# out:tensor([2., 4.])
# 返回第0行的全部元素
print(m[0,:])
# out:tensor([1., 2., 3., 4.])
# 创建一个从1到5的tensor(这里没有5)
v = torch.arange(1,5)#Long
print(v)
# out:tensor([1, 2, 3, 4])
# scalar product
v = torch.arange(1,5,dtype=torch.float32)#float32
print(m @ v)
# out:tensor([30., 50.])
# 1*1+2*2+3*3+4*4
print(m[[0],:] @ v)
# out:tensor([30.])
# 和一个2*4的随机tensor相加
print(m+torch.rand(2,4))
# out:
# tensor([[1.8033, 2.4563, 3.9932, 4.6722],
# [3.0424, 4.2175, 5.1733, 6.7986]])
# 转置
print(m.t())
print(m.transpose(0,1))
# out:
# tensor([[1., 3.],
# [2., 4.],
# [3., 5.],
# [4., 6.]])
# returns a 1D tensor of steps equally spaced points between start=3, end=8 and steps=20
print(torch.linspace(3,8,20))
# out:
# tensor([3.0000, 3.2632, 3.5263, 3.7895, 4.0526, 4.3158, 4.5789, 4.8421, 5.1053,
# 5.3684, 5.6316, 5.8947, 6.1579, 6.4211, 6.6842, 6.9474, 7.2105, 7.4737,
# 7.7368, 8.0000])
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);
from numpy.ma.core import outer
# 创建两个1*4的tensor
a = torch.Tensor([[1, 2, 3, 4]])
b = torch.Tensor([[5, 6, 7, 8]])
# 在0方向拼接(即在Y方向上拼接),得到2*4的矩阵
print(torch.cat((a,b),1))
# out:
# tensor([[1., 2., 3., 4., 5., 6., 7., 8.]])
2 螺旋数据分类
代码链接:(colab)螺旋数据分类
2.1 下载绘图函数到本地
# 下载绘图函数到本地
!wget https://raw.githubusercontent.com/Atcold/pytorch-Deep-Learning/master/res/plot_lib.py
2.2 引入基本的库并初始化重要参数
# 引入用到的库并初始化重要参数
import random
import torch
from torch import nn,optim
import math
from IPython import display
from plot_lib import plot_data,plot_model,set_default
# 在GPU上运行torch
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('device:',device)
# 初始化随机数种子,使得结果能复现
seed = 616
random.seed(seed)
torch.manual_seed(seed)
N = 1000 # 每类样本的数量
D = 2 # 每个样本的特征维度
C = 3 # 样本的类别
H = 100 # 神经网络里隐层单元的数量
初始化X和Y,X可以理解为特征矩阵,Y可以理解为样本标签,X是一个N*C行D列的矩阵 在python中,调用zeros等类似函数时,第一个参数是矩阵的行,y方向,第二个参数是矩阵的列,x方向
X = torch.zeros(N*C,D).to(device)
Y = torch.zeros(N*C,dtype=torch.long).to(device)
for c in range(C):
index = 0
t = torch.linspace(0,1,N) # 在[0,1]间均匀的取10000个数
# 根据公式计算出三类样本,构成螺旋形
# torch.randn(N):得到N个均值为0,方差为1的一组随机数
inner_var = torch.linspace((2*math.pi/C)*c,(2*math.pi/C)*(2+c),N)+torch.randn(N)*0.2
# 将每个样本的坐标保存在X里
# Y里存储样本的类别,分别为[0,1,2]
for xi in range(N*c,N*(c+1)):
X[xi] = t[index] * torch.FloatTensor((math.sin(inner_var[index]),math.cos(inner_var[index])))
Y[xi] = c
index += 1
print("Shapes:")
print("X:",X.size())
print("Y:",Y.size())
plot_data(X,Y)
2.3 构建线性模型分类
lr = 1e-3
lamda = 1e-5
epoch = 1000 # 训练轮数
# 创建线性模型,每一个线性模型都包含weight和bias
model = nn.Sequential(
nn.Linear(D,H),
nn.Linear(H,C)
)
# 把模型放到GPU上
model.to(device)
# 交叉熵损失函数
loss_fn = torch.nn.CrossEntropyLoss()
# # 随机梯度下降优化器SGD
# optimizer = torch.optim.SGD(model.parameters(),lr=lr,weight_decay=lamda)
# adam优化器
optimizer = torch.optim.Adam(model.parameters(),lr=lr,weight_decay=lamda)
# 训练
for i in range(epoch):
# 得到预测结果
y_pred = model(X)
# 计算损失和准确率
loss = loss_fn(y_pred,Y)
score,pred = torch.max(y_pred,1) # 沿着X方向取最大值score和最大值对应位置pred
acc = (Y==pred).sum().float()/len(Y)
print('EPOCH:%i, LOSS:%.6f, ACCURACY:%.3f'%(i,loss.item(),acc))
display.clear_output(wait=True) # 清除输出,只展示当前轮的训练效果
# 梯度置为0
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新全部参数
optimizer.step()
print(y_pred.shape) # 查看模型的预测结果
print(y_pred[10,:]) #查看第10行预测结果
print(score[10]) #查看第10行最大值
print(pred[10]) #查看第10行预测结果
print(model) # 查看模型
plot_model(X, Y, model) #查看分类后的结果
使用Adam优化器,最后一轮训练结果如下:
由此可见,对于这种较复杂的数据分布,只靠线性模型很难达到准确分类,1000轮后的准确率也只在50%左右
2.4 构建两层神经网络分类
在之前的线性模型的基础上添加ReLU激活函数
lr = 1e-3
lamda = 1e-5
epoch = 1000 # 训练轮数
# 创建线性模型,每一个线性模型都包含weight和bias
model = nn.Sequential(
nn.Linear(D,H),
nn.ReLU(), # 添加了激活函数
nn.Linear(H,C)
)
# 把模型放到GPU上
model.to(device)
# 交叉熵损失函数
loss_fn = torch.nn.CrossEntropyLoss()
# # 随机梯度下降优化器SGD
# optimizer = torch.optim.SGD(model.parameters(),lr=lr,weight_decay=lamda)
# adam优化器
optimizer = torch.optim.Adam(model.parameters(),lr=lr,weight_decay=lamda)
# 训练
for i in range(epoch):
# 得到预测结果
y_pred = model(X)
# 计算损失和准确率
loss = loss_fn(y_pred,Y)
score,pred = torch.max(y_pred,1) # 沿着X方向取最大值score和最大值对应位置pred
acc = (Y==pred).sum().float()/len(Y)
print('EPOCH:%i, LOSS:%.6f, ACCURACY:%.3f'%(i,loss.item(),acc))
display.clear_output(wait=True) # 清除输出,只展示当前轮的训练效果
# 梯度置为0
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新全部参数
optimizer.step()
print(model) # 查看模型
plot_model(X, Y, model) #查看分类后的结果
使用Adam优化器,最后一轮训练结果:
加入激活函数之后,模型具有了一定的非线性拟合能力,准确率能达到95.4%,相比单纯的线性模型有了很大提高
2.5 发现的问题
优化器的选取:
①在使用SGD优化器的情况下,加入激活函数后见效缓慢,在其他参数不变的情况下,在1000轮内无法达到比较满意的准确率
②在使用Adam优化器的情况下,未添加激活函数时几乎没有训练效果,准确率和损失都没有明显变化,疑似发生了梯度消失的现象;但在添加激活函数之后,在1000轮内即可达到0.95以上的准确率
加入激活函数前:
加入激活函数后:
综上,在本次实验中,Adam优化器比SGD优化器更容易取得更好的训练效果,SGD优化器需要更长的训练周期
激活函数的选取:在使用Adam优化器的情况下,选取不同的激活函数测试5000轮内的训练效果
①sigmoid()
LOSS:0.076154, ACCURACY:0.986000
②tanh()
LOSS:0.023085, ACCURACY:0.999000
③ReLU()
LOSS:0.018443, ACCURACY:0.999333
④LeakyReLU()
LOSS:0.016924, ACCURACY:0.999000
综上,在这四种激活函数中,sigmoid()效果最差,ReLU()和LeakyReLU()效果最好
2.6 总结
不同的优化器或激活函数会带来不一样的训练效果,需要多尝试和探索,根据实际问题使用合适的函数