从零开始学习深度学习库-6:集成新的自动微分模块和MNIST数字分类器

news2024/9/27 7:18:12

在上一篇文章中,我们完成了自动微分模块的代码。深度学习库依赖于自动微分模块来处理模型训练期间的反向传播过程。然而,我们的库目前还是“手工”计算权重导数。现在我们拥有了自己的自动微分模块,接下来让我们的库使用它来执行反向传播吧!

此外,我们还将构建一个数字分类器来测试一切是否正常工作。

使用自动微分模块并非必要,不使用这个模块也没事,用原本的方法也能很好地工作。

然而,当我们开始在库中实现更复杂的层和激活函数时,硬编码导数计算可能会变得难以理解。

自动微分模块为我们提供了一个抽象层,帮助我们计算导数,这样我们就无需手动完成这一过程了。

让我们开始创建名为 nn.py 的文件。这个文件将作为您需要的所有神经网络组件的中心存储库,包括不同类型的层、激活函数以及构建和操作神经网络所需的潜在其他实用程序。

import autodiff as ad
import numpy as np
import loss 
import optim

np.random.seed(345)

class Layer:
    def __init__(self):
        pass

class Linear(Layer):
    def __init__(self, units):
        self.units = units
        self.w = None
        self.b = None

    def __call__(self, x):
        if self.w is None:
            self.w = ad.Tensor(np.random.uniform(size=(x.shape[-1], self.units), low=-1/np.sqrt(x.shape[-1]), high=1/np.sqrt(x.shape[-1])))
            self.b = ad.Tensor(np.zeros((1, self.units)))

        return x @ self.w + self.b

到目前为止,一切都很简单。当这个类的实例以函数形式调用时,__call__ 方法只执行前向传播。如果是首次调用,它还将初始化层的参数。

权重和偏置现在是 Tensor类的实例,这意味着它们将在操作开始时成为计算图的一部分。这也意味着我们的自动微分模块能够计算它们的导数。

请注意,我们不再需要像以前那样的backward方法。自动微分模块将为我们计算导数!
激活函数

class Sigmoid:
    def __call__(self, x):
        return 1 / (1 + np.e ** (-1 * x))

class Softmax:
    def __call__(self, x):
        e_x = np.e ** (x - np.max(x.value))
        s_x = (e_x) / ad.reduce_sum(e_x, axis=1, keepdims=True)
        return s_x

class Tanh:
    def __call__(self, x):
        return (2 / (1 + np.e ** (-2 * x))) - 1

Model class

class Model:
    def __init__(self, layers):
        self.layers = layers

    def __call__(self, x):
        output = x

        for layer in self.layers:
            output = layer(output)

        return output

    def train(self, x, y, epochs=10, loss_fn = loss.MSE, optimizer=optim.SGD(lr=0.1), batch_size=32):
        for epoch in range(epochs):
            _loss = 0
            print (f"EPOCH", epoch + 1)
            for batch in tqdm(range(0, len(x), batch_size)):
                output = self(x[batch:batch+batch_size])
                l = loss_fn(output, y[batch:batch+batch_size])
                optimizer(self, l)
                _loss += l

            print ("LOSS", _loss.value)

模型类的结构与之前相似,但现在可以对数据集进行批量训练。

与一次性使用整个数据集相比,批量训练使模型能更好地理解其处理的数据。
loss.py
loss.py 文件将包含我们在库中实现的各种损失函数。

import autodiff as ad

def MSE(pred, real):
    loss = ad.reduce_mean((pred - real)**2)
    return loss

def CategoricalCrossentropy(pred, real):
    loss = -1 * ad.reduce_mean(real * ad.log(pred))

    return loss

同样,与之前一样,只是没有了 backward 方法。

关于新的自动微分功能:在我们继续讨论优化器之前,您可能已经注意到代码现在使用了自动微分模块中的一些新功能,

以下是这些新功能:

def reduce_sum(tensor, axis = None, keepdims=False):
    var = Tensor(np.sum(tensor.value, axis = axis, keepdims=keepdims))
    var.dependencies.append(tensor)
    var.grads.append(np.ones(tensor.value.shape))

    return var

def reduce_mean(tensor, axis = None, keepdims=False):
    return reduce_sum(tensor, axis, keepdims) / tensor.value.size

def log(tensor):
    var = Tensor(np.log(tensor.value))
    var.dependencies.append(tensor)
    var.grads.append(1 / tensor.value)

    return var

optim.py
文件将包含我们在这个库中实现的不同优化器。
SGD

from nn import Layer

class SGD:
    def __init__(self, lr):
        self.lr = lr

    def delta(self, param):
        return param.gradient * self.lr

    def __call__(self, model, loss):
        loss.get_gradients()

        for layer in model.layers:
            if isinstance(layer, Layer):
                layer.update(self)

Momentum

class Momentum:
    def __init__(self, lr = 0.01, beta=0.9):
        self.lr = lr
        self.beta = beta
        self.averages = {}

    def momentum_average(self, prev, grad):
        return (self.beta * prev) + (self.lr * grad)

    def delta(self, param):
        param_id = param.id

        if param_id not in self.averages:
            self.averages[param_id] = 0

        self.averages[param_id] = self.momentum_average(self.averages[param_id], param.gradient)
        return self.averages[param_id]

    def __call__(self, model, loss):
        loss.get_gradients()
        for layer in model.layers:
            if isinstance(layer, Layer):
                layer.update(self)

RMSProp

class RMSProp:
    def __init__(self, lr = 0.01, beta=0.9, epsilon=10**-10):
        self.lr = lr
        self.beta = beta
        self.epsilon = epsilon
        self.averages = {}

    def rms_average(self, prev, grad):
        return self.beta * prev + (1 - self.beta) * (grad ** 2)

    def delta(self, param):
        param_id = param.id

        if param_id not in self.averages:
            self.averages[param_id] = 0

        self.averages[param_id] = self.rms_average(self.averages[param_id], param.gradient)
        return (self.lr / (self.averages[param_id] + self.epsilon) ** 0.5) * param.gradient

    def __call__(self, model, loss):
        loss.get_gradients()
        for layer in model.layers:
            if isinstance(layer, Layer):
                layer.update(self)

Adam

class Adam:
    def __init__(self, lr = 0.01, beta1=0.9, beta2=0.999, epsilon=10**-8):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.epsilon = epsilon
        self.averages = {}
        self.averages2 = {}

    def rms_average(self, prev, grad):
        return (self.beta2 * prev) + (1 - self.beta2) * (grad ** 2)

    def momentum_average(self, prev, grad):
        return (self.beta1 * prev) + ((1 - self.beta1) * grad)

    def delta(self, param):
        param_id = param.id

        if param_id not in self.averages:
            self.averages[param_id] = 0
            self.averages2[param_id] = 0

        self.averages[param_id] = self.momentum_average(self.averages[param_id], param.gradient)
        self.averages2[param_id] = self.rms_average(self.averages2[param_id], param.gradient)

        adjust1 = self.averages[param_id] / (1 - self.beta1)
        adjust2 = self.averages2[param_id] / (1 - self.beta2)


        return self.lr * (adjust1 / (adjust2 ** 0.5 + self.epsilon))

    def __call__(self, model, loss):
        loss.get_gradients()
        for layer in model.layers:            
            if isinstance(layer, Layer):
                layer.update(self)

call

def __call__(self, model, loss):
        loss.get_gradients()
        for layer in model.layers:            
            if isinstance(layer, Layer):
                layer.update(self)

当一个优化器类的实例被调用时,它会接受它的训练模型和损失值。
loss.get_gradients()

在这里,我们利用了我们的自动微分模块,

如果您还记得,get_gradients 方法是 Tensor 类的一部分,它计算涉及这个张量计算的所有变量的导数。

这意味着网络中的所有权重和偏置现在都已计算出其导数,这些导数都存储在它们的梯度属性中。

for layer in model.layers:            
        if isinstance(layer, Layer):
            layer.update(self)

现在导数已经计算完毕,优化器将遍历网络的每一层,并通过调用层的更新方法来更新其参数,将自身作为参数传递给它。

我们线性层类中的更新方法如下:

#nn.py
class Linear(Layer):
    ...
    def update(self, optim):
        self.w.value -= optim.delta(self.w)
        self.b.value -= optim.delta(self.b)

        self.w.grads = []
        self.w.dependencies = []
        self.b.grads = []
        self.b.dependencies = []

这个方法接收一个优化器的实例,并根据优化器计算出的delta值更新层的参数。

self.w.value -= optim.delta(self.w)
self.b.value -= optim.delta(self.b)

delta 方法是优化器类中的一个函数。它接收一个张量,并利用其导数来确定这个张量应该调整的量。

delta 方法的具体实现可能会根据使用的优化器而有所不同。

让我们来看一下其中一个 delta 方法的实现。

class RMSProp:
    ...

    def rms_average(self, prev, grad):
        return self.beta * prev + (1 - self.beta) * (grad ** 2)

    def delta(self, param):
        param_id = param.id

        if param_id not in self.averages:
            self.averages[param_id] = 0

        self.averages[param_id] = self.rms_average(self.averages[param_id], param.gradient)
        return (self.lr / (self.averages[param_id] + self.epsilon) ** 0.5) * param.gradient

    ...
param_id = param.id

if param_id not in self.averages:
   self.averages[param_id] = 0

请记住,大多数优化器会跟踪每个参数梯度的某种平均值,以帮助定位全局最小值。

这就是为什么我们为每个张量分配了一个ID,以便优化器能够跟踪它们的梯度平均值。

self.averages[param_id] = self.rms_average(self.averages[param_id], param.gradient)
        return (self.lr / (self.averages[param_id] + self.epsilon) ** 0.5) * param.gradient

如有必要,会重新计算参数的梯度平均值(请注意,SGD 不维持平均值)。

然后,该方法计算参数应调整的幅度,并返回此值。

探索其他优化器,以帮助您了解它们的工作原理。

MNIST 数字分类器

为了验证我们所有新更改的功能是否符合预期,让我们构建一个神经网络来分类手写数字图像。

from sklearn.datasets import load_digits
import numpy as np
import nn
import optim
import loss
from autodiff import *
from matplotlib import pyplot as plt

数据集准备:

def one_hot(n, max):
    arr = [0] * max

    arr[n] = 1

    return arr


mnist = load_digits()
images = np.array([image.flatten() for image in mnist.images])
targets = np.array([one_hot(n, 10) for n in mnist.target])

MNIST 数据集包含作为 2D 数组表示的图像。然而,由于我们的库目前不支持接受 2D 输入的层,我们需要将这些数组展平成 1D 向量。

one_hot 函数接收一个数字,并为其返回一个长度由数据集中的最大值确定的 one-hot 编码数组。

one_hot(3, 10) => [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

建立模型:

model = nn.Model([
    nn.Linear(64),
    nn.Tanh(),
    nn.Linear(32),
    nn.Sigmoid(),
    nn.Linear(10),
    nn.Softmax()
])

这是一个简单的前馈网络,使用 softmax 函数来输出概率分布。

这个分布指定了在给定输入(本例中为图像)的情况下,每个类别(本例中为每个数字)为真的概率。

训练模型:

model.train(images[:1000], targets[:1000], epochs=50, loss_fn=loss.CategoricalCrossentropy, optimizer=optim.RMSProp(0.001), batch_size=128)

我们只需要这一行代码就可以训练我们的模型。

我决定使用数据集中的前1000张图像来训练模型(总共约有1700张图像)。

随意尝试不同的训练配置,看看模型的反应如何。您可以尝试更改优化器、损失函数或学习率,看看这些更改如何影响训练。

测试模型:

images = images[1000:]
np.random.shuffle(images)

for image in images:
    plt.imshow(image.reshape((8, 8)), cmap='gray')
    plt.show()
    pred = np.argmax(model(np.array([image])).value, axis=1)
    print (pred)

在这里,我们将模型未训练的图像随机打乱顺序。

然后我们逐一查看每张图像,显示它,并让我们的模型预测每张图像所表示的数字。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2067327.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深度分析】从《黑神话:悟空》看国产游戏的出海之路

8月20日,中国3A游戏《黑神话:悟空》正式上线并全球发行。正式发布后不到一小时,《黑神话:悟空》就超越《CS2》成为Steam(全球最大的综合性数字发行平台之一)最受欢迎游戏排行榜冠军,同时在线玩家…

进阶-3.SQL优化

SQL优化 1. 插入数据2.主键优化3.order by 优化4.group by 优化5.limit优化6.count优化7.update优化8.总结 1. 插入数据 insert优化 批量插入 insert into user values(1,tom),(2,Cat),(3,Hello);手动事务提交 start transaction; insert into user values(1,tom),(2,Cat),…

区块链浪潮:Web3时代的数字经济新格局

随着科技的迅猛发展,全球经济正迎来一场前所未有的变革,区块链技术正在其中扮演着关键角色。Web3作为下一代互联网的核心,正在通过区块链技术重塑数字经济的格局,为全球市场带来新的机遇和挑战。这场以去中心化为特征的技术革命&a…

『功能项目』鼠标双击人物跟随【03】

我们打开上一篇02的射线点击项目, 本章要做的事情是在PlayerRayNavgation脚本中添加一个双击跟随函数,实现人物在场景中鼠标双击后主角跟随鼠标移动功能。 添加代码后保存代码运行项目,鼠标双击后主角即可实现跟随鼠标移动效果。 本篇只实现了…

PHP之 in_array判断出来的结果错误

示例 <?php $a "[232087,232468,234691,235390,235513,235550,235573,235611,235636,235637,235652,235672,235674,235695,235697,235711,235721,235733,235739,235754,235764,235795,235808,235833,235834,235836,235857,235861,235870,235883,235887,235888,23591…

STM32————SPI硬件外设实现读写

首先是理论知识&#xff1a; 常用8位数据帧、高位先行 SPI的时钟由PCLK内部时钟分频得来&#xff0c;最大可到36MHz 精简为半双工就是去掉一根数据线后&#xff0c;用剩下的一根作为发送/接收数据&#xff1b;单工就是去掉接收线&#xff0c;只用发送线进行发送数据&#xf…

python绘制爱心代码

效果展示 完整代码 Python中绘制爱心的代码可以通过多种方式实现&#xff0c;高级的爱心代码通常指的是使用较复杂的算法或者图形库来生成更加精致的爱心图形。下面是一个使用Python的Turtle模块来绘制爱心的示例代码&#xff1a; import turtledef draw_love():turtle.speed…

python-货物种类(赛氪OJ)

[题目描述] 某电商平台有 n 个仓库&#xff0c;编号从 1 到 n 。当购进某种货物的时候&#xff0c;商家会把货物分散的放在编号相邻的几个仓库中。我们暂时不考虑售出&#xff0c;你是否能知道&#xff0c;当所有货物购买完毕&#xff0c;存放货物种类最多的仓库编号为多少&…

数字身份革命:探索Web3对个人隐私的保护

在数字化时代&#xff0c;个人隐私和数据保护成为越来越重要的话题。随着Web3的兴起&#xff0c;这一领域正在经历一场深刻的变革。Web3不仅仅是技术的演进&#xff0c;更是对个人隐私保护的一次革命性革新。本文将探讨Web3如何通过去中心化技术重新定义数字身份&#xff0c;并…

Moodle集成ONLYOFFICE文档:提高师生协作效率的最佳解决方案

引言 通过一些教育机构和老师朋友的推荐&#xff0c;我最近了解到了一款非常实用的办公软件组合——Moodle与ONLYOFFICE。作为一名教师&#xff0c;日常教学中的文档编辑、课程管理和学生协作是不可避免的任务。虽然市场上有很多办公软件&#xff0c;但Moodle与ONLYOFFICE的结…

稚晖君智元机器人远程机器人系列发布:引领具身智能新高度

在最近的发布会上&#xff0c;前华为“天才少年”稚晖君及其团队亮相了他们的最新作品——智元机器人的第二代远程机器人系列。这次发布会不仅展示了丰富的产品线&#xff0c;还揭示了其未来的发展路线以及开源计划。本文将详细解析本次发布会的亮点和技术背后的创新。 一、发…

企业数字化转型是什么?有什么用?

什么是数字化转型&#xff1f;为什么要数字化转型&#xff1f;对企业有何价值&#xff1f;一文给你讲透&#xff01; 先来给大家简单易懂的方式介绍一下&#xff0c;就很明白什么是数字化&#xff1f;企业为什么要数字化转型了。 “信息化”可理解为&#xff1a;是用电脑或者手…

BAT 实现五子棋人机对战

&#x1f680;欢迎互三&#x1f449;&#xff1a;程序猿方梓燚 &#x1f48e;&#x1f48e; &#x1f680;关注博主&#xff0c;后期持续更新系列文章 &#x1f680;如果有错误感谢请大家批评指出&#xff0c;及时修改 &#x1f680;感谢大家点赞&#x1f44d;收藏⭐评论✍ 引言…

【python】Python中小巧的异步web框架Sanic快速上手实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Java 3.1 - 计算机网络

目录 OSI 七层协议是什么&#xff1f;每一层的作用是什么&#xff1f; TCP / IP 四层模型是什么&#xff1f;每一层的作用是什么&#xff1f; 应用层&#xff08;Application Layer&#xff09; 传输层&#xff08;Transport Layer&#xff09; 网络层&#xff08;Network …

Linux系统编程(15)send/recv函数

一、send/recv send 和 recv 是在网络编程中常用的两个函数&#xff0c;用于在套接字&#xff08;socket&#xff09;之间发送和接收数据。 1.send 函数 用于将数据发送到连接的套接字。 ssize_t send(int sockfd, const void *buf, size_t len, int flags); sockfd&#…

掌握语义内核(Semantic Kernel):如何使用Memories增强人工智能应用

随着人工智能领域的不断发展&#xff0c;语义内核&#xff08;Semantic Kernel&#xff09;的概念应运而生&#xff0c;为我们处理和理解庞大的数据集提供了新的视角。今天&#xff0c;我们将聚焦于语义内核中的一个核心概念——Memories&#xff0c;它是如何使我们的数据查询更…

跨入数字新时代:探寻数字媒体技术的魅力与前景

在当今数字化的时代&#xff0c;数字媒体技术如同一股汹涌的浪潮&#xff0c;席卷着我们生活的方方面面&#xff0c;展现出无与伦比的魅力和广阔无垠的前景。 数字媒体技术的魅力首先体现在它为信息传播带来的革命性变化。以往&#xff0c;我们通过书本、报纸、电视等传统媒介获…

SpringBoot教程(二十五) | SpringBoot整合Sharding-JDBC分库分表

SpringBoot整合Sharding-JDBC分库分表 前言1. 什么是Sharding&#xff1f;2. 什么是Sharding-JDBC&#xff1f; 所需的maven依赖注意点&#xff08;关于shardingsphere的配置情况&#xff09;实操一&#xff1a;如何水平分表1. 步骤说明2. 创建数据库和表3.配置application.pro…

java反序列化之CommonCollections1利⽤链的学习

一、源起 1、代码示例 既然学习cc1链&#xff0c;那么总要先了解下cc1链能造成任意代码执行的原因&#xff0c;这里引用P神的代码来进行讲解&#xff1a; ps:环境使用&#xff1a; CommonsCollections < 3.2.1java < 8u71 import org.apache.commons.collections.Trans…