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

文章目录

    • 前言
    • 神经网络组件代码
      • 整体的项目结构
      • 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://xiahunao.cn/news/2808272.html

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

相关文章

【Android】View 与 ViewGroup

View 是 Android 所有控件的基类,我们平常所用的 TextView 和 ImageView 都是继承自 View 的,源码如下: public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {... }public class ImageView extends View {.…

【Java程序设计】【C00297】基于Springboot的养老院管理系统(有论文)

基于Springboot的养老院管理系统(有论文) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的养老院管理系统设计与实现,本系统有管理员以及用户二种角色; 系统整体功能有:老人管理、字典表…

第八章 堆

第八章 堆 文章目录 第八章 堆0. 前情概述1. 堆(Heap)的核心概述1.1 堆的内存细分 2. 设置堆内存大小与OOM2.1 对空间大小的设置2.2 OutOfMemory举例 3. 年轻代与老年代4. 图解对象分配过程5. Minor GC、Major GC与Full GC5.1 最简单的分代式GC策略的触发条件 6. 堆空间分代思想…

【黑马程序员】3、TypeScript常用类型_黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程

课程地址:【黑马程序员前端TypeScript教程,TypeScript零基础入门到实战全套教程】 https://www.bilibili.com/video/BV14Z4y1u7pi/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 3、TypeScript常用类型 3.1 类型注解 …

SAM轻量化的终点竟然是RepViT + SAM

本文首发:AIWalker,欢迎关注~~ 殊途同归!SAM轻量化的终点竟然是RepViT SAM,移动端速度可达38.7fps。 对于 2023 年的计算机视觉领域来说,「分割一切」(Segment Anything Model)是备受关注的一项…

安装 Ubuntu 22.04.3 和 docker

文章目录 一、安装 Ubuntu 22.04.31. 简介2. 下载地址3. 系统安装4. 系统配置 二、安装 Docker1. 安装 docker2. 安装 docker compose3. 配置 docker 一、安装 Ubuntu 22.04.3 1. 简介 Ubuntu 22.04.3 是Linux操作系统的一个版本。LTS 版本支持周期到2032年。 系统要求双核 C…

了解 JavaScript 中的重放攻击和复现攻击

在网络安全领域,重放攻击(Replay Attack)和复现攻击(Playback Attack)是一些可能导致安全漏洞的攻击形式。这两种攻击类型涉及在通信过程中再次发送已经捕获的数据,以达到欺骗系统的目的。本文将介绍 JavaS…

高并发下如何保证数据的一致性

拿转账来说,在高并发下场景下,对账户余额操作的一致性,是非常重要的。如果代码写的时候没考虑并发一致性,就会导致公司亏损。所以本篇主要聊一下,如何在并发场景下,保证账户余额的一致性。 扣款流程 伪代码…

常用实验室器皿耐硝酸盐酸进口PFA材质容量瓶螺纹盖密封效果好

PFA容量瓶规格参考:10ml、25ml、50ml、100ml、250ml、500ml、1000ml。 别名可溶性聚四氟乙烯容量瓶、特氟龙容量瓶。常用于ICP-MS、ICP-OES等痕量分析以及同位素分析等实验,也可在地质、电子化学品、半导体分析测试、疾控中心、制药厂、环境检测中心等机…

【Linux基础】vim、常用指令、组管理和组权限

Linux基础 1、目录结构2、vi和vim3、常用指令运行级别找回密码帮助指令时间日期指令搜索查找文件目录操作磁盘管理指令压缩和解压缩 4、组管理和组权限用户操作指令权限 1、目录结构 Linux的文件系统是采用级层式的树状目录结构,在此结构中的最上层是根目录“/”&a…

Repeater:创建大量类似项

Repeater 类型用于创建大量类似项。与其它视图类型一样,Repeater有一个model和一个delegate。 首次创建Repeater时,会创建其所有delegate项。若存在大量delegate项,并且并非所有项都必须同时可见,则可能会降低效率。 有2种方式可…

C语言第三十弹---自定义类型:结构体(上)

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】 结构体 1、结构体类型的声明 1.1、结构体回顾 1.1.1、结构的声明 1.1.2、结构体变量的创建和初始化 1.2、结构的特殊声明 1.3、结构的自引用 2、结构体内存…

未来已来:数字孪生与智慧园区的深度融合

目录 一、数字孪生技术的概述 二、智慧园区的概念和发展 三、数字孪生与智慧园区的深度融合 四、数字孪生与智慧园区的未来展望 五、结论 随着科技的飞速发展,我们正处在一个日新月异的时代。数字孪生技术作为新兴的前沿科技,已经引起了全球范围内的…

【人脸朝向识别与分类预测】基于PNN神经网络

课题名称:基于PNN神经网络的人脸朝向识别分类 版本日期:2024-02-20 运行方式:直接运行PNN0503.m文件 代码获取方式:私信博主或 QQ:491052175 模型描述: 采集到一组人脸朝向不同角度时的图像,图像来自不…

奇异递归模板模式应用6-类模板enable_shared_from_this

异步编程中存在一种场景,需要在类中将该类的对象注册到某个回调类或函数中,不能简单地将this传递给回调类中,很可能因为回调时该对象不存在而导致野指针访问(也有可能在析构函数解注册时被回调,造成对象不完整&#xf…

[VNCTF2024]-Web:CheckIn解析

查看网页 一款很经典的游戏,而且是用js写的 在调试器里面我们可以看见,如果游戏通关的话,它会进行一系列操作,包括使用console.log(_0x3d9d[0]);输出_0x3d9d[0]到控制台,那我们就直接在点击在控制台求出它的值

初始Nginx(基本概念)

目录 一、Nginx的概念 二、Nginx常用功能 1、HTTP(正向)代理,反向代理 1.1正向代理 1.2 反向代理 2、负载均衡 2.1 轮询法(默认方法) 2.2 weight权重模式(加权轮询) 2.3 ip_hash 3、web缓存 三、基础特性 四…

论文阅读:Ground-Fusion: A Low-cost Ground SLAM System Robust to Corner Cases

前言 最近看到一篇ICRA2024上的新文章,是关于多传感器融合SLAM的,好像使用了最近几年文章中较火的轮式里程计。感觉这篇文章成果不错,代码和数据集都是开源的,今天仔细读并且翻译一下,理解创新点、感悟研究方向、指导…

挑战杯 基于大数据的股票量化分析与股价预测系统

文章目录 0 前言1 课题背景2 实现效果3 设计原理QTChartsarma模型预测K-means聚类算法算法实现关键问题说明 4 部分核心代码5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于大数据的股票量化分析与股价预测系统 该项目较为新颖…

网络安全“三保一评”深度解析

“没有网络安全就没有国家安全”。近几年,我国法律法规陆续发布实施,为承载我国国计民生的重要网络信息系统的安全提供了法律保障,正在实施的“3保1评”为我国重要网络信息系统的安全构筑了四道防线。 什么是“3保1评”? 等保、分…