自定义神经网络四之编写自定义神经网络

文章目录

    • 前言
    • 神经网络组件代码
      • 整体的项目结构
      • Tensor张量
      • Layers层
      • NeuralNet神经网络
      • Loss损失函数
      • Optim优化器
      • data数据处理
      • train训练
    • 神经网络解决实际问题
      • 实际问题
      • 训练和推理代码
    • 总结

前言

自定义神经网络一之Tensor和神经网络
自定义神经网络二之模型训练推理
自定义神经网络三之梯度和损失函数激活函数

经过前几篇的铺垫,终于到了自定义神经网络的部分。这也是博主一开始的目标,不使用任何框架,跟着大佬手撸一个神经网络,然后run起来。
目标如下:

  1. 实践神经网络中的基础组件,例如Tensor,神经网络层,优化器,损失函数,激活函数等
  2. 手动实践模型的训练,推理,解决实际的问题
  3. 保存模型到本地,并且通过加载本地模型实现推理任务。

整篇文章的代码来源:(需要科学上网)
https://www.youtube.com/watch?v=o64FV-ez6Gw
https://github.com/joelgrus/joelnet

神经网络组件代码

整体的项目结构

image.png
joelnet就是大佬封装的神经网络的类。麻雀虽小,五脏俱全。其他的神经网络框架,比如pytorch等,相对来说是丰富了主干,但万变不离其宗。 大佬没有使用其他的第三方库,纯python手撸的神经网络类,值得学习。

Tensor张量

from numpy import ndarray as Tensor

我们这里不需要使用GPU来进行并行加速,因此使用python的ndarray就足够了,表示多维数组。

Layers层

from typing import Dict, Callableimport numpy as npfrom joelnet.tensor import Tensorclass Layer:def __init__(self) -> None:self.params: Dict[str, Tensor] = {}self.grads: Dict[str, Tensor] = {}def forward(self, inputs: Tensor) -> Tensor:"""Produce the outputs corresponding to these inputs"""raise NotImplementedErrordef backward(self, grad: Tensor) -> Tensor:"""Backpropagate this gradient thought the layer"""raise NotImplementedErrorclass Linear(Layer):"""computes output = input @ w + b这是一个线性层,也叫全连接层,在神经网络中非常常见。"""def __init__(self, input_size: int, output_size: int) -> None:# inputs will be (bitch_size, input_size)# outputs will be (bitch_size, output_size)super().__init__()self.params["w"] = np.random.randn(input_size, output_size)self.params["b"] = np.random.randn(output_size)# 线性变换,其中@代表矩阵乘法,W是权重矩阵,b是偏差项(或称为偏置)def forward(self, inputs: Tensor) -> Tensor:"""outputs = inputs @ w + b"""self.inputs = inputsreturn inputs @ self.params["w"] + self.params["b"]# 计算误差,计算梯度,后续就可以使用优化算法(比如梯度下降)来更新参数W和bdef backward(self, grad: Tensor) -> Tensor:# 当前层输出的梯度对偏差b的梯度,也就是误差项对偏差b的偏导数self.grads["b"] = np.sum(grad, axis=0)# 当前层输出的梯度对权重w的梯度,也就是误差项对权重w的偏导数self.grads["w"] = self.inputs.T @ grad# 计算损失对该层输入的梯度,这个梯度会被反向传播到前一层。return grad @ self.params["w"].T"""
定义了一个类型 F。
F 类型的对象是一个函数,该函数接受一个 Tensor 类型的参数,并且返回一个 Tensor 类型的结果。
任何接受一个 Tensor 类型参数并返回一个 Tensor 类型结果的函数都可以被认为是 F 类型的函数。
"""
F = Callable[[Tensor], Tensor]class Activation(Layer):"""An activation layer just applies a functionelementwise to its inputs激活层,构造函数的参数是:函数f和函数f的导数f_prime"""def __init__(self, f: F, f_prime: F) -> None:super().__init__()self.f = fself.f_prime = f_primedef forward(self, inputs: Tensor) -> Tensor:self.inputs = inputsreturn self.f(inputs)def backward(self, grad: Tensor) -> Tensor:"""if y = f(x) and x = g(z)then dy/dz = f'(x) * g'(z)"""return self.f_prime(self.inputs) * graddef tanh(x: Tensor) -> Tensor:return np.tanh(x)def tanh_prime(x: Tensor) -> Tensor:y = tanh(x)return 1 - y ** 2"""
tanh 是一种常见的激活函数,它的输出范围是 (-1, 1)。这样的输出范围通常
可能使得训练更加稳定,因为它可以把输入的数据标准化到一个比较小的范围。
"""# Tanh 类是一个具体的激活层实现,它使用了 tanh 函数以及其导函数 tanh_prime。
class Tanh(Activation):def __init__(self) -> None:super().__init__(tanh, tanh_prime)

定义层代码,包括每层的参数,如权重,偏置等。 定义线性层,实现前向传播和反向传播的逻辑。
同时定义层的激活函数,给神经元添加一些非线性因素,使得神经网络可以逼近任何复杂函数,提高神经网络模型的表达能力

NeuralNet神经网络

from typing import Sequence, Iterator, Tuplefrom joelnet.tensor import Tensor
from joelnet.layers import Layerclass NeuralNet:def __init__(self, layers: Sequence[Layer]) -> None:self.layers = layersdef forward(self, inputs: Tensor) -> Tensor:for layer in self.layers:inputs = layer.forward(inputs)return inputsdef backward(self, grad: Tensor) -> Tensor:for layer in reversed(self.layers):grad = layer.backward(grad)return graddef params_and_grads(self) -> Iterator[Tuple[Tensor, Tensor]]:for layer in self.layers:for name, param in layer.params.items():grad = layer.grads[name]yield param, grad

定义神经网络,这里主要是构造神经网络结构,把上面定义的层给加进去。 params_and_grads函数主要是输出每层的权重参数和梯度,方便后续的优化器使用。

Loss损失函数

import numpy as npfrom joelnet.tensor import Tensorclass Loss:def loss(self, predicted: Tensor, actual: Tensor) -> float:raise NotImplementedErrordef grad(self, predicted: Tensor, actual: Tensor) -> Tensor:raise NotImplementedErrorclass MSE(Loss):"""MSE is mean squared error, although we're justgoing to do total squared error"""def loss(self, predicted: Tensor, actual: Tensor) -> float:return np.sum((predicted - actual) ** 2)def grad(self, predicted: Tensor, actual: Tensor) -> Tensor:return 2 * (predicted - actual)

定义**均方误差损失函数。 **计算方法为预测值与真实值之差的平方和的均值。MSE对于大的误差值具有很高的惩罚程度,因为差值会被平方。

Optim优化器

"""
We use an optimizer to adjust the parameters
of our network based on the gradients computed
during backprepagation
实现了一个名为SGD(随机梯度下降)的优化器,这是一种用于神经网络训练的常见优化算法。
在每个训练步骤里,它会调整神经网络的参数以达到减小损失函数的目的。
也就是一直说的梯度下降算法
"""
from joelnet.nn import NeuralNetclass Optimizer:def step(self, net: NeuralNet) -> None:raise NotImplementedErrorclass SGD(Optimizer):def __init__(self, lr: float = 0.01) -> None:self.lr = lrdef step(self, net: NeuralNet) -> None:for param, grad in net.params_and_grads():param -= self.lr * grad

这里的学习率可以调整,学习率小的话,训练时间会变长,收敛的慢。学习率过大的话,收敛慢,甚至可能会导致不收敛。

data数据处理

from typing import Iterator, NamedTupleimport numpy as npfrom joelnet.tensor import TensorBatch = NamedTuple("Batch", [("inputs", Tensor), ("targets", Tensor)])class DataIterator:def __call__(self, inputs: Tensor, targets: Tensor) -> Iterator[Batch]:raise NotImplementedErrorclass BatchIterator(DataIterator):def __init__(self, batch_size: int = 32, shuffle: bool = True) -> None:self.batch_size = batch_sizeself.shuffle = shuffledef __call__(self, inputs: Tensor, targets: Tensor) -> Iterator[Batch]:starts = np.arange(0, len(inputs), self.batch_size)# 打乱数据if self.shuffle:np.random.shuffle(starts)# 根据batch_size对数据分批次for start in starts:end = start + self.batch_sizebatch_inputs = inputs[start:end]batch_targets = targets[start:end]yield Batch(batch_inputs, batch_targets)

这个文件的目的是在训练神经网络时,按批次获取输入数据和目标数据,以方便神经网络进行分批训练

train训练

from joelnet.tensor import Tensor
from joelnet.nn import NeuralNet
from joelnet.loss import Loss, MSE
from joelnet.optim import Optimizer, SGD
from joelnet.data import DataIterator, BatchIteratordef train(net: NeuralNet,inputs: Tensor,targets: Tensor,num_epochs: int = 5000,iterator: DataIterator = BatchIterator(),loss: Loss = MSE(),optimizer: Optimizer = SGD()) -> None:for epoch in range(num_epochs):epoch_loss = 0.0for batch in iterator(inputs, targets):predicted = net.forward(batch.inputs)epoch_loss += loss.loss(predicted, batch.targets)grad = loss.grad(predicted, batch.targets)net.backward(grad)optimizer.step(net)print(epoch, epoch_loss)

参数分别是:

  1. 输出和输出数据
  2. 训练5000代
  3. 数据迭代器
  4. 损失函数和优化器

训练逻辑如下:

  1. 迭代数据,获取输入,传入到神经网络
  2. 根据预测值和期望结果,计算神经网络的误差
  3. 获取神经网络梯度,进行反向传播,更新参数
  4. 优化器调整根据学习率调整梯度

神经网络解决实际问题

实际问题

输入1到100的数值,期望:

  • 数值可以整除3,输出fizz
  • 数值可以整除5,输出buzz
  • 数值可以整除3和5,输出fizzbuzz
  • 不符合条件的数值原样输出

训练和推理代码

from typing import Listimport numpy as np
import picklefrom joelnet.train import train
from joelnet.nn import NeuralNet
from joelnet.layers import Linear, Tanh
from joelnet.optim import SGDdef fizz_buzz_encode(x: int) -> List[int]:if x % 15 == 0:return [0, 0, 0, 1]elif x % 5 == 0:return [0, 0, 1, 0]elif x % 3 == 0:return [0, 1, 0, 0]else:return [1, 0, 0, 0]# 整数转换成二进制编码,可以减小输入数据规模
# 神经网络不能直接理解整数,转换成二进制编码可以为
# 神经网络提供了一种更有效的信息表达方式,使得网络能从中学习到更多有用的信息。
def binary_encode(x: int) -> List[int]:"""10 digit binary encoding of x"""return [x >> i & 1 for i in range(10)]# 训练数据是从101-1024之间的数字
inputs = np.array([binary_encode(x)for x in range(101, 1024)
])# 列表生成式语法:[表达式 for 元素 in 可迭代对象]
targets = np.array([fizz_buzz_encode(x)for x in range(101, 1024)
])# 两层线性变换神经网络
net = NeuralNet([Linear(input_size=10, output_size=50),Tanh(),Linear(input_size=50, output_size=4)
])train(net,inputs,targets,num_epochs=50000,optimizer=SGD(lr=0.001))print("save model")
# 保存模型。模型的大小和神经元的个数,层数等有关系
# 神经网络output_size=50的时候,模型大小是41k
# 神经网络output_size=60的时候,模型大小是48k
# 神经网络output_size=50,且设置为3层的时候,模型大小51k
with open('fizzbuzz.pkl', 'wb') as f:pickle.dump(net, f)# 读取模型,进行推理
with open('fizzbuzz.pkl', 'rb') as f:loaded_net = pickle.load(f)# 传入输入数据,获取模型推理结果
for x in range(1, 101):predicted = loaded_net.forward(binary_encode(x))predicted_idx = np.argmax(predicted)actual_idx = np.argmax(fizz_buzz_encode(x))labels = [str(x), "fizz", "buzz", "fizzbuzz"]# 输出预测值和实际应该返回的值print(x, labels[predicted_idx], labels[actual_idx])

注意,这里的训练epoch和lr学习率都可以自己调整的,博主这里分别调整到了训练5w次和学习率0.001.
模型大小和神经网络的层数和参数量有关系,具体可以看注释。
以上代码不依赖特殊的库,理论上来说可以直接运行的。

模型输出如下:

90 fizzbuzz fizzbuzz
91 91 91
92 92 92
93 fizz fizz
94 94 94
95 buzz buzz
96 fizz fizz
97 97 97
98 98 98
99 fizz fizz
100 buzz buzz

可以看到,模型推理出来的预测值和实际的值是一致的,说明训练有效果。

总结

本博客是在大佬代码的基础上,实现了自定义神经网络的训练和推理。外网上的优秀文章和视频太多了,可惜限于网络和语言,能被我们看到的太少了。 这个大佬40多分钟就手撸了简单的神经网络类,并且实现了训练和推理,博主只能说,牛逼。

本系列文章到这里就结束了。本来只是想分享一下大佬的视频和代码,但直接输出难免会没有上下文,因此只能把以前的一些笔记梳理下,期望读者能先有一些基础概念,然后再手撸代码实现一个自己的神经网络。

不得不感概,现在的网络资料太多了。依稀记得18年尝试学习一下TensorFlow的痛苦,上来就是各种公式和专业术语轰炸,直接劝退。近几年随着大量的工程师涌入人工智能领域,网上的教程也越来越通俗易懂了,站在工程的角度去讲概念,从实用性角度去学习可简单太多了。

end

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于电脑一天24小时多少度电电脑的一天用电量计算

随着这几年物价的上涨,一些地区的电价越来越高,而我们经常需要使用电脑,那么一台电脑一天24小时用多少度电呢? 如何计算电脑一天的用电量? 让我们跟随小编来了解更多吧。 1、功耗、主机箱功耗 现在的计算机中&#xf…

(Linux学习一):Mac安装vmWare11.5,centOS 7安装步骤教程

一。下载vmware 官网地址:下载地址 由于我的电脑系统是Mac 10.15.6版本系统,我下载的是VMware Fusion 11.5版本,13是最新版本不支持安装需要系统在11以上。 百度网盘下载地址: VMware Fusion 11 VMware Fusion 12 VMware Fusion 13 下载需要…

插值、逼近、拟合、光顺

插值 插值(Interpolation)是数学和计算科学中的一个重要概念,它指的是通过已知的一系列数据点,构造一个函数或曲线,并据此估计未知数据点的值。这个过程通常发生在已知数据点之间,用于预测或估算在这些已知…

Android Duplicate class 排除重复类

一、起因: 在迭代开发的时候,发现2个ijk很多类重复。但又2个库实现的功能是不一样,目前不能合并。但又想保留2个功能。需要排除其中一个库。 二、报错如何下图: 三、解决方法: 3.1 在terminal 也就是命令行处输入 …

Project_Euler-29 题解

Project_Euler-29 题解 题目 思路 如果暴力破解的话会有一个问题,那就是数值过大的问题,那我们就需要通过对数据结构来进行操作,这样的话会让代码变得很臃肿。 优化思路 其实,对于一个数值,我们并不需要要将其计算出…

Mysql数据库管理系统学习笔记1——sql语句,DBMS,数据库的分类

mysql是一种数据库管理系统(DBMS),data base manage system sql语句即为“structured query language”,结构化查询语言 数据库的分类:关系型数据库(RDBMS)与非关系型数据库 对于一些具有相同…

如何在aws服务器上部署mysql

在AWS服务器上部署 MySQL 数据库可以通过以下步骤完成: 启动 EC2 实例: 在 AWS 控制台中启动一个 EC2 实例,选择适合你需求的实例类型和配置。 安全组配置: 确保你的 EC2 实例的安全组配置允许来自你的 IP 地址的 MySQL 连接。默…

【全志D1-H 哪吒开发板】Debian系统安装调教和点灯指南

全志D1-H开发板【哪吒】使用Deabian系统入门 特别说明: 因为涉及到操作较多,博文可能会导致格式丢失 其中内容,会根据后续使用做优化调整 目录: 参考资料固件烧录启动调教点灯问题 〇、参考资料 官方资料 开发板-D1开发板【…

VR元宇宙的概念|VR体验店加盟|虚拟现实设备销售

VR元宇宙是一个结合了虚拟现实(Virtual Reality)和增强现实(Augmented Reality)等技术的概念,代表着一个虚拟的多维度世界。它是一个由数字化的空间构成的虚拟环境,可以通过虚拟现实设备进行交互和探索。 元…

Linux安装Nginx详细步骤

1、创建两台虚拟机,分别为主机和从机,区别两台虚拟机的IP地址 2、将Nginx素材内容上传到/usr/local目录(pcre,zlib,openssl,nginx) 附件 3、安装pcre库   3.1 cd到/usr/local目录 3.2 tar -zxvf pcre-8.36.tar.gz 解压 3.3 cd…

goland配置新增文件头

参考: goland函数注释生成插件 goland函数注释生成插件_goland自动加函数说明-CSDN博客 GoLand 快速添加方法注释 GoLand 快速添加方法注释_goland批量注释-CSDN博客 goland 如何设置头注释,自定义author和data goland 如何设置头注释,自定…

【北京迅为】《iTOP-3588开发板网络环境配置手册》第2章 电脑、开发板直连交换机或路由器

RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

通过大语言模型理解运维故障:评估和总结

张圣林 南开大学软件学院副教授、博士生导师 第六届CCF国际AIOps挑战赛程序委员会主席 在ATC、WWW、VLDB、KDD、SIGMETRICS等国际会议和JSAC、TC、TSC等国际期刊发表高水平论文50余篇。主持国家自然科学基金项目2项,横向项目13项(与华为、字节跳动、腾讯…

resilience4j 2.0.0版本使用要求最低JDK17(使用踩坑记录)

文章目录 🔊博主介绍🥤本文内容📢文章总结📥博主目标 🔊博主介绍 🌟我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

非线性优化资料整理

做课题看了一些非线性优化的资料,整理一下,以方便查看: 优化的中文博客 数值优化|笔记整理(8)——带约束优化:引入,梯度投影法 (附代码)QP求解器对比对于MPC的QP求解器 数值优化| 二次规划的…

MYSQL--JDBC*

一.介绍: 1.JDBC是一种用于执行SQL于语句的JAVA API,JDBC是一种使用JAVA访问数据库的执行规范标准,能够为不同的数据库提供统一的访问!由一组使用JAVA语言编写的接口以及类组成的 2.JDBC核心的类以及相关的接口主要有: DriverManager 注册驱动 Connection 使用…

DolphinScheduler——介绍及架构设计

目录 一、DolphinScheduler介绍 1.1 概述 1.2 特性 1.2.1 简单易用 1.2.2 丰富的使用场景 1.2.3 High Reliability 1.2.4 High Scalability 1.3 名词解释 1.3.1 名词解释 1.3.2 模块介绍 二、DolphinScheduler架构原理 2.1 系统架构图 2.2 架构说明 2.2.1 Maste…

Python及Pycharm专业版下载安装教程(Python 3.11版)附JetBrains学生认证教程

目录 一、Python下载及安装1、Python下载2、Python安装3、验证是否安装成功 二、PyCharm下载及安装1、PyCharm下载2、PyCharm安装3、激活PyCharm 三、JetBrains学生认证 本篇主要介绍Python和PyCharm专业版的下载及安装方式,以及通过两种方式进行JetBrains学生认证。…

react 路由的基本原理及实现

1. react 路由原理 不同路径渲染不同的组件 有两种实现方式 ● HasRouter 利用hash实现路由切换 ● BrowserRouter 实现h5 API实现路由切换 1. 1 HasRouter 利用hash 实现路由切换 1.2 BrowserRouter 利用h5 Api实现路由的切换 1.2.1 history HTML5规范给我们提供了一个…

Spring Boot项目中如何上传头像?

在我们常见的各大App中,或多或少我们都见过上传头像的功能吧?? 但是在Spring Boot项目中如何上传头像呢? 上传头像主要用到RequestPart注解 来看一下小编的代码吧! RestController RequestMapping("/param"…