• 动手学深度学习 | Softmax回归+损失函数+图片分类数据集 | 07


    Softmax回归

    首先简单理解softmax:就是将一个回归值转换成一个概率(也就是把一个实数,定在[0,1.]中)

    Softmax回归名字叫做回归,但其实是一个分类问题。(基本是个老师都会重复讲这句话)

    分类和回归的差别就在,回归只有一个输出,而分类是有多个输出。一般是有几个类别多少个输出。

    并且分类输出的i是预测为第i类的置信度。

    exp()这要要进行指数,是因为指数的好处就是不管什么值,都可以变成非负(概率不能是负数)。

    softmax实际上可以理解成也是一个全连接层,得到对应类别的置信度(概率)是需要和之前的输出进行线性组合的。

    最后分类问题的损失函数使用交叉熵损失函数(具体推到如果看不懂的话,建议先跳过)

    损失函数

    loss用来衡量真实值和预测值的差别,是机器学习中一个非常重要的概念。

    这里给大家简单介绍3个常用的损失函数。

    均方损失,也叫做L2 loss​。

    这里除2是为了方便求导的时候可以和平方的2进行抵消。

    上面有3条线,可以可视化这个损失函数的特性。

    • 蓝色:当y=0,变换预测值y',它的函数。可以看到它是一个二次函数
    • 绿色:它的似然函数,我们并没有要将似然函数,但它确实是统计中一个非常重要的概念,可以看到它的似然函数就是一个高斯分布了。
    • 橙色:梯度,我们知道它的梯度就是一个一次函数。

    我们更新梯度的时候是根据负梯度方向来更新我们的参数,所以它的导数就决定如何来更新我们的参数,如上图红色箭头。大小要看梯度的值。但是有个不好的地方,就是其实预测值和真实值差的比较远的时候,其实有时候并不想更新那么大。

    L1 loss,梯度就是一个常数,在-1,1。好处就是不管真实值和预测值相差多大,都可以对参数进行一个稳定的更新,这会带来很多稳定性上的好处。

    绝对值函数在0点处是不可导的,所以在0点处会有一个比较剧烈的 变化,也就是当真实值预测值靠的比较近,也就是优化到末期,这里就会变得不那么稳定了。

    Huber’s Robuts Loss就将L1 loss 和 L2 loss进行了结合。

    一般看损失函数,都是看其梯度的函数形状,来分析当预测值和真实值相差比较远,还有相差比较近的时候,分别是什么情况。

    图片分类数据集

    操作总结

    def load_data_fashion_mnist(batch_size,resize=None):
    	# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式 
        trans = [transforms.ToTensor()]     
        if resize:         
        	trans.insert(0, transforms.Resize(resize)) # 插入指定的位置     
        trans = transforms.Compose(trans) # 将多个transform操作进行组合
        # 数据数据集
        mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)
        mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)
        # 返回两个DataLoader
        return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                                num_workers=get_dataloader_workers()),
                data.DataLoader(mnist_test, batch_size, shuffle=False,
                                num_workers=get_dataloader_workers()))
    

    Softmax回归从零开始实现

    Softmax也要从零开始实现,因为这是一个非常重要的模型,也是后面所有深度学习模型的一个基础。

    操作总结

    batch_size = 256
    train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size) # 加载batch_size的数据
    
    num_inputs = 784
    num_outputs = 10
    
    # 这里初始化权重和参数
    W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
    b = torch.zeros(num_outputs, requires_grad=True) # 对每一个输出都要有一个偏移
    
    # 定义了softmax函数
    def softmax(X):
        X_exp = torch.exp(X)
        partition = X_exp.sum(1, keepdim=True) # 按照水平方向(行)来进行求和
        return X_exp / partition # 这里应用了广播机制
    
    # 实现了softmax回归模型
    def net(X):
        return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
    
    # 交叉熵损失函数
    def cross_entropy(y_hat, y):
        return - torch.log(y_hat[range(len(y_hat)), y])
    
    
    # 累加器
    class Accumulator:  
        """在`n`个变量上累加,这是一个累加器"""
        def __init__(self, n):
            self.data = [0.0] * n
    
        def add(self, *args):
            self.data = [a + float(b) for a, b in zip(self.data, args)] # 这是一个累加的过程
    
        def reset(self):
            self.data = [0.0] * len(self.data)
    
        def __getitem__(self, idx): # 按照[] 取值
            return self.data[idx]
    
    
    # 计算真实值
    def accuracy(y_hat, y):  
        """计算预测正确的数量,也就是判断n行中可以预测对几个"""
        # 如果存在多行,就只存储每一行的最大值下标
        if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
            y_hat = y_hat.argmax(axis=1) 
        cmp = y_hat.type(y.dtype) == y 
        # 返回预测正确的数目
        return float(cmp.type(y.dtype).sum()) # 布尔类型转换int在求和
    
    
    # 评估模型在数据集上的准确性
    def evaluate_accuracy(net, data_iter):  
        """计算在指定数据集上模型的精度,就是看模型在数据迭代器上的精度
        net:模型
        data_iter:数据迭代器
        """
        if isinstance(net, torch.nn.Module): # 如果是使用了torch.nn.Module的模型
            net.eval() # 将模型设置为评估模式
        metric = Accumulator(2) # 正确预测数、预测总数
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel()) # 不断地加入累加器中
        return metric[0] / metric[1]
    
    
    # softmax回归训练
    def train_epoch_ch3(net, train_iter, loss, updater):  
        """训练模型一个迭代周期(定义见第3章)。"""
        if isinstance(net, torch.nn.Module):
            net.train() # 开启训练模式,也就是要训练梯度
        
        # [loss,correct_num,total]
        metric = Accumulator(3)
        for X, y in train_iter:
            y_hat = net(X) # softmax回归函数
            l = loss(y_hat, y) # 得到loss
            if isinstance(updater, torch.optim.Optimizer): 
                updater.zero_grad()
                l.backward()
                updater.step()
                metric.add(float(l) * len(y), accuracy(y_hat, y),
                           y.size().numel())
            else:
                l.sum().backward()
                updater(X.shape[0]) # 根据批量大小,反向update一下
                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):  
        """训练模型(定义见第3章)。"""
        # 画图对象
        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 = train_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
        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
    
    
    # 使用SGD来优化模型的loss
    lr = 0.1
    
    def updater(batch_size):
        return d2l.sgd([W, b], lr, batch_size)
        
    # 训练10个周期
    num_epochs = 10
    # 这里还封装了一个动态的画图的功能,非常的酷炫(请面向github编程)
    train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
    
    
    

    Softmax回归简洁实现

    batch_size = 256
    # 还是将数据加入数据迭代器中
    train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
    
    # 将模型进行组合
    net = nn.Sequential(nn.Flatten(), nn.Linear(784, 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()
    
    # SGD
    trainer = torch.optim.SGD(net.parameters(), lr=0.1)
    
    # 训练10个epoch
    num_epochs = 10
    d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
    

    QA

    1. softmax回归和logistic回归分析是一样的吗?如果不一样的话,那些地方不同?

    可以认为logistic回归是softmax回归的一个特例,因为前者的作用就是进行二分类任务,但是二分类任务只要预测一个类别就可以了,另一个类别的概率就是1-other。

    softmax是用于多分类的。我们后面基本不会用到二分类,所以课程直接跳过了logistic回归,直接将softmax回归。

    1. 为什么用交叉熵,不用....等其他基于信息量的度量?

    没有特别为什么,一个是交叉熵比较好算。

    还有就是其实我们真正关注的就是两个分布的距离,能够得到这个距离就可以了。

    1. 交叉熵损失函数为什么我们只关心正确类,不关心不正确的类呢?

    其实不是我们不关心不正确的类,而是因为one-hot编码就是把不正确类别的概率变成了0,导致我们计算的时候可以忽略掉不正确的类。

    1. 能对MSE的最大似然估计提一下吗?

    没有讲似然函数,是因为这一块是统计中的概念。我们尽量不涉及太多的统计,是因为统计是可以用来解释我们模型的一个工具,反过来讲,后面的深度学习和统计没有太多的关系。所以我们主要讲的是线性代数,因为使用的所有结构是线性代数。

    可以大概讲一下,最小化损失就等价于最大化似然函数。似然函数就是有个模型,给定数据的情况下,我的模型(也就是权重)出现的概率有多大?我们要最大化似然函数,也即是找到一个w,是的x出现的概率是最大的,这个也是最合理的解释。

    下面这张最大似然函数和损失函数的关系图,也就很好理解了。

    我们会稍微讲一点统计的东西,但是不会深入太多。

    1. batch_size的大小会训练时间的影响是什么?

    如果CPU的话,基本是看不出区别的。

    batch_size大一点的话,在GPU是可以提高训练的并行度,这样可以节约训练的时间。

    当然,不管batch_size怎么取,训练的总体计算量是没有发生变化的。

    1. 为什么不在accuracy函数中除以len(y)做完呢?

    因为最后一个batch可能是不能读满的,所以最好的做法就是Accumulator进行累加,最后在进行相除。

    1. 在多次迭代之后如果测试进度出现上升后再下降过拟合了吗?可以提前终止吗?

    很有可能是过拟合了,其实可以再等等,但如果测试精度是一直下降的话,那很有可能是过拟合。

    后面会讲一些策略来尽量避免,比如来微调学习率,当然也可以加入各种各样的正则项。

    1. cnn网络学习到的到底是什么信息?是纹理还是轮廓还是所有内容的综合,说不清的那种?

    其实我们也不好说cnn学到了什么,目前,至少几年前,大家认为cnn是学到的纹理,轮廓cnn其实不那么在意。

    1. 如果是自己的图片数据集,需要怎么做才能用于训练,怎么根据本地图片训练集和测试机创建迭代器?

    这个需要查阅框架的文档,基本都是在本地建立名字为类别的文件夹,然后在对应类别中放入图片,然后告诉框架上层目录即可。

  • 相关阅读:
    SpringMvc---Ant通配符
    mybatis 数据库语句
    shiro 静态页面资源不显示 解决方案
    http错误汇总
    关于代码质量与逻辑
    shiro 过滤属性的意义
    java思维导图
    E
    LCIS HDU
    E
  • 原文地址:https://www.cnblogs.com/Rowry/p/15312446.html
Copyright © 2020-2023  润新知