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

本文主要是介绍从零开始学习深度学习库-6:集成新的自动微分模块和MNIST数字分类器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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

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

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

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

import autodiff as ad
import numpy as np
import loss 
import optimnp.random.seed(345)class Layer:def __init__(self):passclass Linear(Layer):def __init__(self, units):self.units = unitsself.w = Noneself.b = Nonedef __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_xclass Tanh:def __call__(self, x):return (2 / (1 + np.e ** (-2 * x))) - 1

Model class

class Model:def __init__(self, layers):self.layers = layersdef __call__(self, x):output = xfor layer in self.layers:output = layer(output)return outputdef 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 = 0print (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 += lprint ("LOSS", _loss.value)

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

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

import autodiff as addef MSE(pred, real):loss = ad.reduce_mean((pred - real)**2)return lossdef 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 vardef reduce_mean(tensor, axis = None, keepdims=False):return reduce_sum(tensor, axis, keepdims) / tensor.value.sizedef 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 Layerclass SGD:def __init__(self, lr):self.lr = lrdef delta(self, param):return param.gradient * self.lrdef __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 = lrself.beta = betaself.averages = {}def momentum_average(self, prev, grad):return (self.beta * prev) + (self.lr * grad)def delta(self, param):param_id = param.idif param_id not in self.averages:self.averages[param_id] = 0self.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 = lrself.beta = betaself.epsilon = epsilonself.averages = {}def rms_average(self, prev, grad):return self.beta * prev + (1 - self.beta) * (grad ** 2)def delta(self, param):param_id = param.idif param_id not in self.averages:self.averages[param_id] = 0self.averages[param_id] = self.rms_average(self.averages[param_id], param.gradient)return (self.lr / (self.averages[param_id] + self.epsilon) ** 0.5) * param.gradientdef __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 = lrself.beta1 = beta1self.beta2 = beta2self.epsilon = epsilonself.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.idif param_id not in self.averages:self.averages[param_id] = 0self.averages2[param_id] = 0self.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.idif param_id not in self.averages:self.averages[param_id] = 0self.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.idif 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] * maxarr[n] = 1return arrmnist = 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)

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

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

这篇关于从零开始学习深度学习库-6:集成新的自动微分模块和MNIST数字分类器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1101244

相关文章

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java数字转换工具类NumberUtil的使用

《Java数字转换工具类NumberUtil的使用》NumberUtil是一个功能强大的Java工具类,用于处理数字的各种操作,包括数值运算、格式化、随机数生成和数值判断,下面就来介绍一下Number... 目录一、NumberUtil类概述二、主要功能介绍1. 数值运算2. 格式化3. 数值判断4. 随机

Go Mongox轻松实现MongoDB的时间字段自动填充

《GoMongox轻松实现MongoDB的时间字段自动填充》这篇文章主要为大家详细介绍了Go语言如何使用mongox库,在插入和更新数据时自动填充时间字段,从而提升开发效率并减少重复代码,需要的可以... 目录前言时间字段填充规则Mongox 的安装使用 Mongox 进行插入操作使用 Mongox 进行更

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Python利用自带模块实现屏幕像素高效操作

《Python利用自带模块实现屏幕像素高效操作》这篇文章主要为大家详细介绍了Python如何利用自带模块实现屏幕像素高效操作,文中的示例代码讲解详,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、获取屏幕放缩比例2、获取屏幕指定坐标处像素颜色3、一个简单的使用案例4、总结1、获取屏幕放缩比例from

nginx-rtmp-module模块实现视频点播的示例代码

《nginx-rtmp-module模块实现视频点播的示例代码》本文主要介绍了nginx-rtmp-module模块实现视频点播,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录预置条件Nginx点播基本配置点播远程文件指定多个播放位置参考预置条件配置点播服务器 192.

IDEA如何让控制台自动换行

《IDEA如何让控制台自动换行》本文介绍了如何在IDEA中设置控制台自动换行,具体步骤为:File-Settings-Editor-General-Console,然后勾选Usesoftwrapsin... 目录IDEA如何让控制台自http://www.chinasem.cn动换行操作流http://www

vscode保存代码时自动eslint格式化图文教程

《vscode保存代码时自动eslint格式化图文教程》:本文主要介绍vscode保存代码时自动eslint格式化的相关资料,包括打开设置文件并复制特定内容,文中通过代码介绍的非常详细,需要的朋友... 目录1、点击设置2、选择远程--->点击右上角打开设置3、会弹出settings.json文件,将以下内

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操