动手学深度学习9 多层感知机+代码实现

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。
在这里插入图片描述

多隐藏层

上一层隐藏层的输入是下一层隐藏层的输出。
在这里插入图片描述
激活函数不能少,少了层数会减一,主要是为了避免层数的塌陷,最后一层输出层不需要激活函数。

超参数有两个:有几个隐藏层,每个隐藏层是多大。
配置好每一层隐藏层都有哪些东西、长什么样子【技术经验】。

根据输入数据复杂度的多少【维度高低】,考虑模型【大小和复杂度】。两个选择:

  1. 模型做的不深,但是每一层都很大
  2. 每一层不大,但是模型比较深

假设输入数据是128维【高】,输出是5类【低】,把高维数据压缩到低维。机器学习本质上是做压缩,把图片或者其他信息压缩成某一个类。
压缩的三个做法:

  1. 逐层慢慢压缩
  2. 第一层隐藏层可以比输入层稍微大一点,先扩张再压缩
  3. 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)

# 损失下降了 但是精度没有提高?
# 深度学习的好处,模型变化很大,但是从代码实现的角度,只是改了一点点【模型结构的代码】

注意

  1. d2l包需要是0.17.5版本及以上,否则没有trian_ch3函数
  2. 代码运行loss过大无法显示在动画图片中,可以试试加大epoch或【】者loss模式修改为reduction=‘none’【 none 损失较大没有显示 mean 损失较小没有显示 sum 训练精度降低 奇怪?】
  3. 深度学习的好处,模型变化很大【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

  1. 一层网络,一般是说带权【每个箭头都是权重加偏移】的网络层, 也包含激活函数。一般定义输入层就不算一层了。【一层:权重加激活函数是怎么做计算的】【也可以输入层加权重加激活函数算一层,那么最后一层输出层就不算一层了,怎么看都可以。】只有两层w–所以只有两层网络。
    在这里插入图片描述

  2. 机器学习是统计在计算机应用的一个分支。收敛定理是统计学习的知识,一般数据区域r无法测量无法统计,找不到数据分布的区域。可以假设。

  3. SVM 用起来简单 MLP【Multilayer Perceptron 多层感知机】和其他nn网络模型调整简单,SVM调用所有的都要变比较麻烦。【多层感知机在svm之前】

  4. 多层感知机解决了XOR问题,SVM取代了感知机。多层感知机没有流行原因:1.得选超参数–多个隐藏层,每个隐藏层多大。不好收敛(有lr才好收敛)–svm对超参数不敏感。2.svm优化更容易求解,不做sgd。3.假设两个模型实际效果差不多,svm用起来更简单,数学性更好-可解释性。

  5. 为什么增加神经元隐藏层的层数而不是神经元的个数。神经元万有近似性质?
    深度学习【好训练 每一层都学一点简单的东西,更深容易找到一个比较好的解】 or 宽度学习【不好训练,容易overfit过拟合,每个神经元都是一个并行的关系】 优化函数解决不了太宽的问题。

  6. 神经元和卷积核有什么关系?感知机和卷积核的关系【卷积是怎么从感知机演变的。】

  7. 激活函数本质:引入非线性,把线性函数改成非线性函数。relu是分段线性函数不是线性函数。区别:不同激活函数在远点对梯度的影响不同。

  8. 不同激活函数对性能影响较小-差不多。远不如选择隐藏层神经元大小等超参数重要,可以直接用relu,本质上没有太多区别。

  9. 模型的深度更好训练,性能更好一些,没有最优的说法,可以多试一下。从简单开始,慢慢把模型变复杂,加深加宽都可以。
    搞个for训练多试一下,找感觉、直觉。

  10. 训练完参数是固定的,不是动态的【不要有动态性】。同一个数据经过同一个模型做预测,预测结果应该是一致的。 但是模型要有泛化性–鲁棒性Robustness【专门领域:“鲁棒性机器学习”(Robust Machine Learning)或"对抗性机器学习"(Adversarial Machine Learning)医疗无人车等领域稳定性都要很好】,输入数据多种多样,各种信息干扰,但是输出应该是稳定正确的。

  11. 在设置隐藏层的时候,会先认为评估特征的数量,在设置层数和单元数吗?
    可以先猜想是多少,再人为的拿数据–验证集来遛一遛,看看训练或测试的效果,再调参。

  • 31
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值