多层感知机+代码实现
- 1. 感知机
- 2. 多层感知机
- 单隐藏层
- 三个激活函数
- 多隐藏层
- 3. 代码实现
- 从零开始实现
- 简洁实现
- 注意
- 4. QA
1. 感知机
视频:https://www.bilibili.com/video/BV1hh411U7gn/?spm_id_from=autoNext&vd_source=eb04c9a33e87ceba9c9a2e5f09752ef8
书:https://zh-v2.d2l.ai/chapter_multilayer-perceptrons/mlp.html
课件:https://courses.d2l.ai/zh-v2/assets/pdfs/part-0_12.pdf
感知机:两分类任务,只有一个元素输出,如下,w和x做内积后加上b再过sigma函数,其值大于0输出1,值小于等于0输出0【也可以输出-1】
回归输出一个实数,不经过sigma处理
softmax回归不只输出一个元素,输出所有类别的值,可以做多分类的问题。
预测值大于0应该是正类,预测是小于等于0应该是负类,当预测正确的时候,真实值y乘以预测值<w,x>+b是正值大于0,当预测失败的时候,真实值y乘以预测值<w,x>+b是负值小于等于0,对每一个样本都做这样的判断,直到所有样本都分类正确。
损失函数取一个最大值【对应if条件】,当预测正确,用值取负和0比大小,取0,当预测失败,负的预测值是正的,最大值取负的预测值,损失函数增加。
猫狗类别划分,根据每一个类别是否划分正确,更新权重。
做两个假设,数据区域半径r,余量 ρ,存在一个分截面,使得分截面对所有分类都是正确的,而且有一定余量 ρ(≥0)。
r平方是数据的大小,ρ是数据是不是很好,两个分类是否能很好分开。
在任意二维坐标系内,无法找出一个线性分割面能将下图的点分割开,无法做分类。多层感知机能解决这个问题。
2. 多层感知机
第一步学习蓝色的线,第二步学习黄色的线,【复合两个函数】对两个结果做异或操作得出结果。
单隐藏层
输入输出层大小是由数据决定的,数据有多大,数据有多少类。隐藏层大小是可以自行设置的。
隐藏层:
权重:假设隐藏层大小m,输入是n维向量,则隐藏层的权重是m*n大小的矩阵
偏移:有多少个隐藏层就有多少个标量偏移
输出层:单分类输出,则权重m维向量,偏移只有一个
sigma函数不能是线性的,否则等价于单层的感知机。
三个激活函数
为什么有个-2?
上面两个激活函数都是感知机sigmoid函数的soft–柔软版本。
在cpu上计算指数函数比较贵,计算一次指数计算等于计算几百次乘法运算的花销。所以常用ReLU激活函数【计算简单】。
softmax做的事情:把所有输出单元拉到0-1之间,且所有分类的值等于1,表示为概率的形式。
多类分类感知机和softmax回归没有本质区别:没加隐藏层就是softmax回归,加了隐藏层就是多层感知机,名称上的变化。
和前面单分类的区别,就是在输出层,因为有k个输出,所以输出层权重w2是m*k的,偏移也是k维向量。
同时对输出要做一次softmax。
多隐藏层
上一层隐藏层的输入是下一层隐藏层的输出。
激活函数不能少,少了层数会减一,主要是为了避免层数的塌陷,最后一层输出层不需要激活函数。
超参数有两个:有几个隐藏层,每个隐藏层是多大。
配置好每一层隐藏层都有哪些东西、长什么样子【技术经验】。
根据输入数据复杂度的多少【维度高低】,考虑模型【大小和复杂度】。两个选择:
- 模型做的不深,但是每一层都很大
- 每一层不大,但是模型比较深
假设输入数据是128维【高】,输出是5类【低】,把高维数据压缩到低维。机器学习本质上是做压缩,把图片或者其他信息压缩成某一个类。
压缩的三个做法:
- 逐层慢慢压缩
- 第一层隐藏层可以比输入层稍微大一点,先扩张再压缩
- cnn–先压缩再扩张的模型,避免overfitting。
3. 代码实现
从零开始实现
import torch
from torch import nn
from d2l import torch as d2l
from IPython import display
# 分类精度--判断预测的类别是否是正确的
def accuracy(y_hat, y):
"""计算预测正确的数量"""
# 如果预测值是二维矩阵,而且每一行列数大于1
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
# 每一行元素预测值最大的下标存到y_hat中 argmax返回的是索引下标--获取概率最高类别的索引
y_hat = y_hat.argmax(axis=1)
# print(y_hat.dtype, y_hat.type(torch.float).dtype)
# y_hat.type(y.dtype) 设置元素数值类型和y保持一致
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum()) # 预测类型成功的个数
# print(accuracy(y_hat, y)/len(y)) # 预测成功率
def evaluate_accuracy(net, data_iter):
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
# net.eval() 作用1. 网络结构如果有Batch Normalization 和 Dropout 层的,做模型评测必须加上这个命令以防止报错
# 作用2: 关闭梯度计算
# Accumulator 累加器 不断迭代数据X y 不断累加评测结果
# Accumulator 初始化传入数值2 表示有数组有两个变量,第一个位置存正确测试数,第二个位置存样本总数,根据批次遍历依次累加
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter: # 迭代一次 一次批量
# metric.add(该批次的预测类别正确的样本数,批次的总样本数)
# y.numel() pytorch中用于返回张量的元素总个数
metric.add(accuracy(net(X), y), y.numel())
# 返回值=分类正确样本数/样本总数=精度
return metric[0] / metric[1]
class Accumulator:
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
# print(self.data, args)
self.data = [a + float(b) for a,b in zip(self.data, args)]
# print(self.data, args)
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
class Animator:
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None, ylim=None,
xscale='linear', yscale='linear', fmts=('-', 'm--', 'g--', 'r:'),
nrows=1, nclos=1, figsize=(3.5, 2.5)):
# 增量的绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, nclos, figsize=figsize)
if nrows * nclos == 1:
self.axes = [self.axes, ]
# 使用lamda函数捕获参数
self.config_axes = lambda: d2l.set_axes(self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
print('n------',n)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a,b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
def trian_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第三章)"""
# 如果模型是用nn模组定义的
if isinstance(net, torch.nn.Module):
net.train() # 将模型设置为训练模式 告诉pytorch要计算梯度
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3) # 三个参数需要累加的迭代器
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y) # 计算损失函数
# 如果优化器是pytorch内置的优化器
# 下面两个加的结果有什么区别
# print(float(l)*len(y), accuracy(y_hat,y), y.size().numel(),
# float(l.sum()), accuracy(y_hat, y), y.numel())
if isinstance(updater, torch.optim.Optimizer):
# 使用pytorch内置的优化器和损失函数
updater.zero_grad() # 1.梯度先置零
l.mean().backward() # 2.计算梯度
updater.step() # 3.调用step参数进行一次更新
# metric.add(float(l)*len(y), accuracy(y_hat,y), y.size().numel())
# 报错 ValueError: only one element tensors can be converted to Python scalars
else:
# 使用定制的优化器和损失函数
# 自己实现的l是一个向量
l.sum().backward()
updater(X.shape[0])
# metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
# 损失累加/总样本数 训练正确的/总样本数
return metric[0] / metric[2], metric[1] / metric[2]
# 开启训练
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3,0.9],
legend=['train-loss', 'train-acc', 'test-acc'])
for epoch in range(num_epochs):
train_metrics = trian_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch+1, train_metrics+(test_acc,))
train_loss, train_acc = train_metrics
print(train_loss)
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
num_inputs, num_outputs, num_hiddens = 784, 10, 256
# 权重初始化 随机 全部设置0或1 试试有什么区别
W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad=True)*0.01) # *0.01? #
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad=True)*0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
# 为什么随机 可以设置为0 1 试试
# W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad=True)) # *0.01? 行数 列数 梯度
# b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
# W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad=True))
# b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1, b1, W2, b2]
# torch.zeros_like(X) 函数 返回的变量类型及格式都与X一致,但是元素值是0
def relu(X):
a = torch.zeros_like(X)
return torch.max(X, a) # torch.max(a, X)
# 矩阵乘法 @符号简写
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X @ W1 + b1)
# O = H@W2 + b2
return (H @ W2 + b2)
loss = nn.CrossEntropyLoss(reduction='none')
# loss = nn.CrossEntropyLoss(reduction='sum')
num_epochs, lr = 10, 0.01
updater = torch.optim.SGD(params, lr)
train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
# d2l.predict_ch3(net, test_iter)
# 损失下降了 但是精度没有提高?
# 深度学习的好处,模型变化很大,但是从代码实现的角度,只是改了一点点【模型结构的代码】
简洁实现
import torch
from torch import nn
from d2l import torch as d2l
from IPython import display
# 分类精度--判断预测的类别是否是正确的
def accuracy(y_hat, y):
"""计算预测正确的数量"""
# 如果预测值是二维矩阵,而且每一行列数大于1
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
# 每一行元素预测值最大的下标存到y_hat中 argmax返回的是索引下标--获取概率最高类别的索引
y_hat = y_hat.argmax(axis=1)
# print(y_hat.dtype, y_hat.type(torch.float).dtype)
# y_hat.type(y.dtype) 设置元素数值类型和y保持一致
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum()) # 预测类型成功的个数
# print(accuracy(y_hat, y)/len(y)) # 预测成功率
def evaluate_accuracy(net, data_iter):
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
# net.eval() 作用1. 网络结构如果有Batch Normalization 和 Dropout 层的,做模型评测必须加上这个命令以防止报错
# 作用2: 关闭梯度计算
# Accumulator 累加器 不断迭代数据X y 不断累加评测结果
# Accumulator 初始化传入数值2 表示有数组有两个变量,第一个位置存正确测试数,第二个位置存样本总数,根据批次遍历依次累加
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter: # 迭代一次 一次批量
# metric.add(该批次的预测类别正确的样本数,批次的总样本数)
# y.numel() pytorch中用于返回张量的元素总个数
metric.add(accuracy(net(X), y), y.numel())
# 返回值=分类正确样本数/样本总数=精度
return metric[0] / metric[1]
class Accumulator:
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
# print(self.data, args)
self.data = [a + float(b) for a,b in zip(self.data, args)]
# print(self.data, args)
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
class Animator:
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None, ylim=None,
xscale='linear', yscale='linear', fmts=('-', 'm--', 'g--', 'r:'),
nrows=1, nclos=1, figsize=(3.5, 2.5)):
# 增量的绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, nclos, figsize=figsize)
if nrows * nclos == 1:
self.axes = [self.axes, ]
# 使用lamda函数捕获参数
self.config_axes = lambda: d2l.set_axes(self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
print('n------',n)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a,b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
def trian_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第三章)"""
# 如果模型是用nn模组定义的
if isinstance(net, torch.nn.Module):
net.train() # 将模型设置为训练模式 告诉pytorch要计算梯度
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3) # 三个参数需要累加的迭代器
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y) # 计算损失函数
# 如果优化器是pytorch内置的优化器
# 下面两个加的结果有什么区别
# print(float(l)*len(y), accuracy(y_hat,y), y.size().numel(),
# float(l.sum()), accuracy(y_hat, y), y.numel())
if isinstance(updater, torch.optim.Optimizer):
# 使用pytorch内置的优化器和损失函数
updater.zero_grad() # 1.梯度先置零
l.mean().backward() # 2.计算梯度
updater.step() # 3.调用step参数进行一次更新
# metric.add(float(l)*len(y), accuracy(y_hat,y), y.size().numel())
# 报错 ValueError: only one element tensors can be converted to Python scalars
else:
# 使用定制的优化器和损失函数
# 自己实现的l是一个向量
l.sum().backward()
updater(X.shape[0])
# metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
# 损失累加/总样本数 训练正确的/总样本数
return metric[0] / metric[2], metric[1] / metric[2]
# 开启训练
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3,0.9],
legend=['train-loss', 'train-acc', 'test-acc'])
for epoch in range(num_epochs):
train_metrics = trian_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch+1, train_metrics+(test_acc,))
train_loss, train_acc = train_metrics
print(train_loss)
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
loss = nn.CrossEntropyLoss(reduction='none')
batch_size, num_epochs, lr = 256, 10, 0.01
updater = torch.optim.SGD(net.parameters(), lr=lr)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
# d2l.predict_ch3(net, test_iter)
# 损失下降了 但是精度没有提高?
# 深度学习的好处,模型变化很大,但是从代码实现的角度,只是改了一点点【模型结构的代码】
注意
- d2l包需要是0.17.5版本及以上,否则没有trian_ch3函数
- 代码运行loss过大无法显示在动画图片中,可以试试加大epoch或【】者loss模式修改为reduction=‘none’【 none 损失较大没有显示 mean 损失较小没有显示 sum 训练精度降低 奇怪?】
- 深度学习的好处,模型变化很大【MLP cnn等多模型】,但是从代码实现的角度,只是改了一点点【模型结构的代码】 所以用MLP–多层感知机 Multi layer Perceptron比较多,而SVM–支持向量机Support Vector Machine用的比较少【因为要改很多东西】
reduction=none w10.01 , w20.01
reduction=mean
reduction=none w10.01 , w20.01 epoch=30
reduction=none 代码的简洁实现
4. QA
-
一层网络,一般是说带权【每个箭头都是权重加偏移】的网络层, 也包含激活函数。一般定义输入层就不算一层了。【一层:权重加激活函数是怎么做计算的】【也可以输入层加权重加激活函数算一层,那么最后一层输出层就不算一层了,怎么看都可以。】只有两层w–所以只有两层网络。
-
机器学习是统计在计算机应用的一个分支。收敛定理是统计学习的知识,一般数据区域r无法测量无法统计,找不到数据分布的区域。可以假设。
-
SVM 用起来简单 MLP【Multilayer Perceptron 多层感知机】和其他nn网络模型调整简单,SVM调用所有的都要变比较麻烦。【多层感知机在svm之前】
-
多层感知机解决了XOR问题,SVM取代了感知机。多层感知机没有流行原因:1.得选超参数–多个隐藏层,每个隐藏层多大。不好收敛(有lr才好收敛)–svm对超参数不敏感。2.svm优化更容易求解,不做sgd。3.假设两个模型实际效果差不多,svm用起来更简单,数学性更好-可解释性。
-
为什么增加神经元隐藏层的层数而不是神经元的个数。神经元万有近似性质?
深度学习【好训练 每一层都学一点简单的东西,更深容易找到一个比较好的解】 or 宽度学习【不好训练,容易overfit过拟合,每个神经元都是一个并行的关系】 优化函数解决不了太宽的问题。 -
神经元和卷积核有什么关系?感知机和卷积核的关系【卷积是怎么从感知机演变的。】
-
激活函数本质:引入非线性,把线性函数改成非线性函数。relu是分段线性函数不是线性函数。区别:不同激活函数在远点对梯度的影响不同。
-
不同激活函数对性能影响较小-差不多。远不如选择隐藏层神经元大小等超参数重要,可以直接用relu,本质上没有太多区别。
-
模型的深度更好训练,性能更好一些,没有最优的说法,可以多试一下。从简单开始,慢慢把模型变复杂,加深加宽都可以。
搞个for训练多试一下,找感觉、直觉。 -
训练完参数是固定的,不是动态的【不要有动态性】。同一个数据经过同一个模型做预测,预测结果应该是一致的。 但是模型要有泛化性–鲁棒性Robustness【专门领域:“鲁棒性机器学习”(Robust Machine Learning)或"对抗性机器学习"(Adversarial Machine Learning)医疗无人车等领域稳定性都要很好】,输入数据多种多样,各种信息干扰,但是输出应该是稳定正确的。
-
在设置隐藏层的时候,会先认为评估特征的数量,在设置层数和单元数吗?
可以先猜想是多少,再人为的拿数据–验证集来遛一遛,看看训练或测试的效果,再调参。