使用numpy手写一个神经网络

本文主要包含以下内容:

  1. 推导神经网络的误差反向传播过程
  2. 使用numpy编写简单的神经网络,并使用iris数据集和california_housing数据集分别进行分类和回归任务,最终将训练过程可视化。

1. BP算法的推导过程

1.1 导入

img
前向传播和反向传播的总体过程。

img

神经网络的直接输出记为 Z [ l ] Z^{[l]} Z[l],表示激活前的输出,激活后的输出记为 A A A
img

第一个图像是神经网络的前向传递和反向传播的过程,第二个图像用于解释中间的变量关系,第三个图像是前向和后向过程的计算图,方便进行推导,但是第三个图左下角的 A [ l − 2 ] A^{[l-2]} A[l2]有错误,应该是 A [ l − 1 ] A^{[l-1]} A[l1]

1.2 符号表

为了方便进行推导,有必要对各个符号进行介绍

符号表

记号含义
n l n_l nl l l l层神经元个数
f l ( ⋅ ) f_l(\cdot) fl() l l l层神经元的激活函数
W l ∈ R n l − 1 × n l \mathbf{W}^l\in\R^{n_{l-1}\times n_{l}} WlRnl1×nl l − 1 l-1 l1层到第 l l l层的权重矩阵
b l ∈ R n l \mathbf{b}^l \in \R^{n_l} blRnl l − 1 l-1 l1层到第 l l l层的偏置
Z l ∈ R n l \mathbf{Z}^l \in \R^{n_l} ZlRnl l l l层的净输出,没有经过激活的输出
A l ∈ R n l \mathbf{A}^l \in \R^{n_l} AlRnl l l l层经过激活函数的输出, A 0 = X A^0=X A0=X

深层的神经网络都是由一个一个单层网络堆叠起来的,于是我们可以写出神经网络最基本的结构,然后进行堆叠得到深层的神经网络。

于是,我们可以开始编写代码,通过一个类Layer来描述单个神经网络层

class Layer:
    def __init__(self, input_dim, output_dim):
        # 初始化参数
        self.W = np.random.randn(input_dim, output_dim) * 0.01
        self.b = np.zeros((1, output_dim))
        
    def forward(self, X):
        # 前向计算
        self.Z = np.dot(X, self.W) + self.b
        self.A = self.activation(self.Z)
        return self.A
    
    def backward(self, dA, A_prev, activation_derivative):
        # 反向传播
        # 计算公式推导见下方
        m = A_prev.shape[0]
        self.dZ = dA * activation_derivative(self.Z)
        self.dW = np.dot(A_prev.T, self.dZ) / m
        self.db = np.sum(self.dZ, axis=0, keepdims=True) / m
        dA_prev = np.dot(self.dZ, self.W.T)
        return dA_prev
    
    def update_parameters(self, learning_rate):
        # 参数更新
        self.W -= learning_rate * self.dW
        self.b -= learning_rate * self.db
        

# 带有ReLU激活函数的Layer
class ReLULayer(Layer):
    def activation(self, Z):
        return np.maximum(0, Z)
    
    def activation_derivative(self, Z):
        return (Z > 0).astype(float)
    
# 带有Softmax激活函数(主要用于分类)的Layer
class SoftmaxLayer(Layer):
    def activation(self, Z):
        exp_z = np.exp(Z - np.max(Z, axis=1, keepdims=True))
        return exp_z / np.sum(exp_z, axis=1, keepdims=True)
    
    def activation_derivative(self, Z):
        # Softmax derivative is more complex, not directly used in this form.
        return np.ones_like(Z)

1.3 推导过程

权重更新的核心在于计算得到self.dWself.db,同时,为了将梯度信息不断回传,需要backward函数返回梯度信息dA_prev

需要用到的公式
Z l = W l A l − 1 + b l A l = f ( Z l ) d Z d W = ( A l − 1 ) T d Z d b = 1 Z^l = W^l A^{l-1} +b^l \\A^l = f(Z^l)\\\frac{dZ}{dW} = (A^{l-1})^T \\\frac{dZ}{db} = 1 Zl=WlAl1+blAl=f(Zl)dWdZ=(Al1)TdbdZ=1
解释:

从上方计算图右侧的反向传播过程可以看到,来自于上一层的梯度信息dA经过dZ之后直接传递到db,也经过dU之后传递到dW,于是我们可以得到dWdb的梯度计算公式如下:
d W = d A ⋅ d A d Z ⋅ d Z d W = d A ⋅ f ′ ( d Z ) ⋅ A p r e v T \begin{align}dW &= dA \cdot \frac{dA}{dZ} \cdot \frac{dZ}{dW}\\ &= dA \cdot f'(dZ) \cdot A_{prev}^T \\ \end{align} dW=dAdZdAdWdZ=dAf(dZ)AprevT
其中, f ( ⋅ ) f(\cdot) f()是激活函数, f ′ ( ⋅ ) f'(\cdot) f()是激活函数的导数, A p r e v T A_{prev}^T AprevT是当前层上一层激活输出的转置。

同理,可以得到
d b = d A ⋅ d A d Z ⋅ d Z d b = d A ⋅ f ′ ( d Z ) \begin{align}db &= dA \cdot \frac{dA}{dZ} \cdot \frac{dZ}{db}\\ &= dA \cdot f'(dZ) \\ \end{align} db=dAdZdAdbdZ=dAf(dZ)
需要仅需往前传递的梯度信息:
d A p r e v = d A ⋅ d A d Z ⋅ d Z A p r e v = d A ⋅ f ′ ( d Z ) ⋅ W T \begin{align}dA_{prev} &= dA \cdot \frac{dA}{dZ} \cdot \frac{dZ}{A_{prev}}\\ &= dA \cdot f'(dZ) \cdot W^T \\ \end{align} dAprev=dAdZdAAprevdZ=dAf(dZ)WT
所以,经过上述推导,我们可以将梯度信息从后向前传递。

分类损失函数

分类过程的损失函数最常见的就是交叉熵损失了,用来计算模型输出分布和真实值之间的差异,其公式如下:
L = − 1 N ∑ i = 1 N ∑ j = 1 C y i j l o g ( y i j ^ ) L = -\frac{1}{N}\sum_{i=1}^N \sum_{j=1}^C{y_{ij} log(\hat{y_{ij}})} L=N1i=1Nj=1Cyijlog(yij^)
其中, N N N表示样本个数, C C C表示类别个数, y i j y_{ij} yij表示第i个样本的第j个位置的值,由于使用了独热编码,因此每一行仅有1个数字是1,其余全部是0,所以,交叉熵损失每次需要对第 i i i个样本不为0的位置的概率计算对数,然后将所有所有概率取平均值的负数。

交叉熵损失函数的梯度可以简洁地使用如下符号表示:
∇ z L = y ^ − y \nabla_zL = \mathbf{\hat{y}} - \mathbf{{y}} zL=y^y

回归损失函数

均方差损失函数由于良好的性能被回归问题广泛采用,其公式如下:
L = 1 N ∑ i = 1 N ( y i − y i ^ ) 2 L = \frac{1}{N} \sum_{i=1}^N(y_i - \hat{y_i})^2 L=N1i=1N(yiyi^)2
向量形式:
L = 1 N ∣ ∣ y − y ^ ∣ ∣ 2 2 L = \frac{1}{N} ||\mathbf{y} - \mathbf{\hat{y}}||^2_2 L=N1∣∣yy^22
梯度计算:
∇ y ^ L = 2 N ( y ^ − y ) \nabla_{\hat{y}}L = \frac{2}{N}(\mathbf{\hat{y}} - \mathbf{y}) y^L=N2(y^y)

2 代码

2.1 分类代码

import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
import matplotlib.pyplot as plt

class Layer:
    def __init__(self, input_dim, output_dim):
        self.W = np.random.randn(input_dim, output_dim) * 0.01
        self.b = np.zeros((1, output_dim))
        
    def forward(self, X):
        self.Z = np.dot(X, self.W) + self.b     # 激活前的输出
        self.A = self.activation(self.Z)        # 激活后的输出
        return self.A
    
    def backward(self, dA, A_prev, activation_derivative):
        # 注意:梯度信息是反向传递的: l+1 --> l --> l-1
        # A_prev是第l-1层的输出,也即A^{l-1}
        # dA是第l+1的层反向传递的梯度信息
        # activation_derivative是激活函数的导数
        # dA_prev是传递给第l-1层的梯度信息
        m = A_prev.shape[0]
        self.dZ = dA * activation_derivative(self.Z)
        self.dW = np.dot(A_prev.T, self.dZ) / m
        self.db = np.sum(self.dZ, axis=0, keepdims=True) / m
        dA_prev = np.dot(self.dZ, self.W.T) # 反向传递给下一层的梯度信息
        return dA_prev
    
    def update_parameters(self, learning_rate):
        self.W -= learning_rate * self.dW
        self.b -= learning_rate * self.db

class ReLULayer(Layer):
    def activation(self, Z):
        return np.maximum(0, Z)
    
    def activation_derivative(self, Z):
        return (Z > 0).astype(float)

class SoftmaxLayer(Layer):
    def activation(self, Z):
        exp_z = np.exp(Z - np.max(Z, axis=1, keepdims=True))
        return exp_z / np.sum(exp_z, axis=1, keepdims=True)
    
    def activation_derivative(self, Z):
        # Softmax derivative is more complex, not directly used in this form.
        return np.ones_like(Z)

class NeuralNetwork:
    def __init__(self, layer_dims, learning_rate=0.01):
        self.layers = []
        self.learning_rate = learning_rate
        for i in range(len(layer_dims) - 2):
            self.layers.append(ReLULayer(layer_dims[i], layer_dims[i + 1]))
        self.layers.append(SoftmaxLayer(layer_dims[-2], layer_dims[-1]))
    
    def cross_entropy_loss(self, y_true, y_pred):
        n_samples = y_true.shape[0]
        y_pred_clipped = np.clip(y_pred, 1e-12, 1 - 1e-12)
        return -np.sum(y_true * np.log(y_pred_clipped)) / n_samples
    
    def accuracy(self, y_true, y_pred):
        y_true_labels = np.argmax(y_true, axis=1)
        y_pred_labels = np.argmax(y_pred, axis=1)
        return np.mean(y_true_labels == y_pred_labels)
    
    def train(self, X, y, epochs):
        loss_history = []
        for epoch in range(epochs):
            A = X
            # Forward propagation
            cache = [A]
            for layer in self.layers:
                A = layer.forward(A)
                cache.append(A)
            
            loss = self.cross_entropy_loss(y, A)
            loss_history.append(loss)
            
            # Backward propagation
            # 损失函数求导
            dA = A - y
            for i in reversed(range(len(self.layers))):
                layer = self.layers[i]
                A_prev = cache[i]
                dA = layer.backward(dA, A_prev, layer.activation_derivative)
            
            # Update parameters
            for layer in self.layers:
                layer.update_parameters(self.learning_rate)
            
            if (epoch + 1) % 100 == 0:
                print(f'Epoch {epoch + 1}/{epochs}, Loss: {loss:.4f}')
        
        return loss_history
    
    def predict(self, X):
        A = X
        for layer in self.layers:
            A = layer.forward(A)
        return A

# 导入数据
iris = load_iris()
X = iris.data
y = iris.target.reshape(-1, 1)

# One hot encoding
encoder = OneHotEncoder(sparse_output=False)
y = encoder.fit_transform(y)

# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 定义并训练神经网络
layer_dims = [X_train.shape[1], 100, 20, y_train.shape[1]]  # Example with 2 hidden layers
learning_rate = 0.01
epochs = 5000

nn = NeuralNetwork(layer_dims, learning_rate)
loss_history = nn.train(X_train, y_train, epochs)

# 预测和评估
train_predictions = nn.predict(X_train)
test_predictions = nn.predict(X_test)

train_acc = nn.accuracy(y_train, train_predictions)
test_acc = nn.accuracy(y_test, test_predictions)

print(f'Training Accuracy: {train_acc:.4f}')
print(f'Test Accuracy: {test_acc:.4f}')

# 绘制损失曲线
plt.plot(loss_history)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Loss Curve')
plt.show()

输出
Epoch 100/1000, Loss: 1.0983
Epoch 200/1000, Loss: 1.0980
Epoch 300/1000, Loss: 1.0975
Epoch 400/1000, Loss: 1.0960
Epoch 500/1000, Loss: 1.0891
Epoch 600/1000, Loss: 1.0119
Epoch 700/1000, Loss: 0.6284
Epoch 800/1000, Loss: 0.3711
Epoch 900/1000, Loss: 0.2117
Epoch 1000/1000, Loss: 0.1290
Training Accuracy: 0.9833
Test Accuracy: 1.0000

在这里插入图片描述
可以看到经过1000轮迭代,最终的准确率到达100%。

回归代码

import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing


class Layer:
    def __init__(self, input_dim, output_dim):
        self.W = np.random.randn(input_dim, output_dim) * 0.01
        self.b = np.zeros((1, output_dim))
        
    def forward(self, X):
        self.Z = np.dot(X, self.W) + self.b
        self.A = self.activation(self.Z)
        return self.A
    
    def backward(self, dA, X, activation_derivative):
        m = X.shape[0]
        self.dZ = dA * activation_derivative(self.Z)
        self.dW = np.dot(X.T, self.dZ) / m
        self.db = np.sum(self.dZ, axis=0, keepdims=True) / m
        dA_prev = np.dot(self.dZ, self.W.T)
        return dA_prev
    
    def update_parameters(self, learning_rate):
        self.W -= learning_rate * self.dW
        self.b -= learning_rate * self.db

class ReLULayer(Layer):
    def activation(self, Z):
        return np.maximum(0, Z)
    
    def activation_derivative(self, Z):
        return (Z > 0).astype(float)

class LinearLayer(Layer):
    def activation(self, Z):
        return Z
    
    def activation_derivative(self, Z):
        return np.ones_like(Z)

class NeuralNetwork:
    def __init__(self, layer_dims, learning_rate=0.01):
        self.layers = []
        self.learning_rate = learning_rate
        for i in range(len(layer_dims) - 2):
            self.layers.append(ReLULayer(layer_dims[i], layer_dims[i + 1]))
        self.layers.append(LinearLayer(layer_dims[-2], layer_dims[-1]))
    
    def mean_squared_error(self, y_true, y_pred):
        return np.mean((y_true - y_pred) ** 2)
    
    def train(self, X, y, epochs):
        loss_history = []
        for epoch in range(epochs):
            A = X
            # Forward propagation
            cache = [A]
            for layer in self.layers:
                A = layer.forward(A)
                cache.append(A)
            
            loss = self.mean_squared_error(y, A)
            loss_history.append(loss)
            
            # Backward propagation
            # 损失函数求导
            dA = -(y - A)
            for i in reversed(range(len(self.layers))):
                layer = self.layers[i]
                A_prev = cache[i]
                dA = layer.backward(dA, A_prev, layer.activation_derivative)
            
            # Update parameters
            for layer in self.layers:
                layer.update_parameters(self.learning_rate)
            
            if (epoch + 1) % 100 == 0:
                print(f'Epoch {epoch + 1}/{epochs}, Loss: {loss:.4f}')
        
        return loss_history
    
    def predict(self, X):
        A = X
        for layer in self.layers:
            A = layer.forward(A)
        return A

housing = fetch_california_housing()

# 导入数据
X = housing.data
y = housing.target.reshape(-1, 1)

# 标准化
scaler_X = StandardScaler()
scaler_y = StandardScaler()
X = scaler_X.fit_transform(X)
y = scaler_y.fit_transform(y)

# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 定义并训练神经网络
layer_dims = [X_train.shape[1], 50, 5, 1]  # Example with 2 hidden layers
learning_rate = 0.8
epochs = 1000

nn = NeuralNetwork(layer_dims, learning_rate)
loss_history = nn.train(X_train, y_train, epochs)

# 预测和评估
train_predictions = nn.predict(X_train)
test_predictions = nn.predict(X_test)

train_mse = nn.mean_squared_error(y_train, train_predictions)
test_mse = nn.mean_squared_error(y_test, test_predictions)

print(f'Training MSE: {train_mse:.4f}')
print(f'Test MSE: {test_mse:.4f}')

# 绘制损失曲线
plt.plot(loss_history)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Loss Curve')
plt.show()

输出
Epoch 100/1000, Loss: 1.0038
Epoch 200/1000, Loss: 0.9943
Epoch 300/1000, Loss: 0.3497
Epoch 400/1000, Loss: 0.3306
Epoch 500/1000, Loss: 0.3326
Epoch 600/1000, Loss: 0.3206
Epoch 700/1000, Loss: 0.3125
Epoch 800/1000, Loss: 0.3057
Epoch 900/1000, Loss: 0.2999
Epoch 1000/1000, Loss: 0.2958
Training MSE: 0.2992
Test MSE: 0.3071

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

编程-辅助工具-Git下载

文章目录 1、前言2、Git官网地址3、迅雷下载 1、前言 采用Git能下载github上的代码,其下载是采用官网下载的,但是下载速度比较慢,网上也推荐了镜像的方式,但是有些链接失效了,突然有一天想起用迅雷是不是合适&#xf…

在通过跨网文件交换时,如何保障科研结构核心研究数据?

当今科研领域,数据如同生命线,支撑着每一个突破性发现的诞生。随着国际合作的加深,跨网文件交换成了常态,但这也为科研机构的核心研究数据安全带来了一系列挑战。想象一下,那些精心搜集和分析的宝贵数据,在…

OpenBayes 教程上新 |全球首个开源的文生视频 DiT 模型!对标 Sora,保姆级 Latte 文生视频使用指南

小朋友不爱背诗怎么办?《千秋诗颂》试试看。 2 月 26 日,中国首部文生视频 AI 系列动画《千秋诗颂》于 CCTV-1 频道正式播出,这部动画由上海人工智能实验室和「央妈」(中央广播电视总台)强强联手,借助「央视…

Discuz!X3.4论坛网站公安备案号怎样放到网站底部?

Discuz!网站的工信部备案号都知道在后台——全局——站点信息——网站备案信息代码填写,那公安备案号要添加在哪里呢?并没有看到公安备案号填写栏,今天驰网飞飞和你分享 1)工信部备案号和公安备案号统一填写到网站备案…

安装appium自动化测试环境,我自己的版本信息

教程来自:Appium原理与安装 - 白月黑羽 我的软件的版本: 安装是选择为自己安装而不是选all user pip install appium-python-client命令在项目根目录下安装appium-python-client sdk的话最简单的安装方式就是去Android官网下一个android studio然后在…

强化学习_06_pytorch-PPO2实践(Humanoid-v4)

一、PPO优化 PPO的简介和实践可以看笔者之前的文章 强化学习_06_pytorch-PPO实践(Pendulum-v1) 针对之前的PPO做了主要以下优化: -笔者-PPO笔者-PPO2refdata collectone episodeseveral episode(one batch)activationReLUTanhadv-compute-compute adv as one seri…

鸿蒙ArkTS声明式开发:跨平台支持列表【按键事件】

按键事件 按键事件指组件与键盘、遥控器等按键设备交互时触发的事件,适用于所有可获焦组件,例如Button。对于Text,Image等默认不可获焦的组件,可以设置focusable属性为true后使用按键事件。 说明: 开发前请熟悉鸿蒙开…

【STM32踩坑】HAL固件库版本过高导致烧录后无法运行问题

问题引入 目前STM32CUBEMX已经更新到了6.11版本,对应的固件库也一直在更新; 以STM32F1库为例,目前最新的库对应版本为1.8.5 但是我们会发现,如果直接使用1.8.5版本的固件库生成HAL源码后,烧录是可以烧录,但…

【IC】良率模型-yield model

缺陷密度Default Density(D0),表示单位面积的缺陷数D。 单位面积有M个部件,一个部件的平均失效率为: 一个面积为A的系统(芯片)良率: Possion模型: 当M趋于无穷时,系统良率为possion模型。 Murphy模型:(D~对称三角分布) 大芯片或大系统possion模型预估良率过于…

Python自动化测试进阶:性能测试与持续集成实践

Python自动化测试进阶包括性能测试和持续集成实践两个关键方面。以下是对这两个领域的简要介绍,并附带一些示例代码。 性能测试 性能测试是评估软件在特定条件下的性能表现的过程。对于Python应用程序,可以使用一些工具来进行性能测试,例如ps…

linux centos tomcat启动内存泄漏

tomcat启动内存泄漏 经过与开发沟通,结果是开发写了死循环,导致内存泄漏,上一次是开发少打了一个jar包,让开发查代码就行,重新更新代码

C++学习/复习6---内存管理(数据的位置/大小)/new、delete/内存相关面试题(malloc与new/内存泄漏)

一、内存中区域 1.不同数据的存储位置 2.数据占用内存大小 二、动态内存函数 三、new与delete 1.操作内置类型 2.操作自定义类型 四、operator new与operator delete 1.底层源码(malloc/free) 2.内置/自定义与构造和析构 3.举例 五、定位new表达式 1.举…

TiDB学习3:TiKV

目录 1. TiKV架构和作用 2. RocksDB 2.1 写入 2.2 查询 2.3 Column Families列簇 3. 分布式事务 3.1 事务流程 3.2 分布式事务流程 3.3 MVCC 4. Raft与Multi Raft 4.1 Raft日志复制 4.2 Raft Leader选举 5. TiKV- 读写 5.1 数据的写入 5.2 数据的读取ReadIndex …

教育大模型的发展现状、创新架构及应用展望

引言 从通用大模型到教育领域的专用大模型,是人工智能大模型技术深化发展的必然趋势。教育大模型不是在通用大模型基础上的微调和优化,而是以重构未来教育图景为目标、以开放算法模型架构为基础、以创新教育应用场景为核心的系统性变革。如何厘清教育大…

Linux汉化Jupyter Notebook

要在Linux系统中使Jupyter Notebook汉化,可以通过安装jupyterlab-language-pack-zh-CN扩展来实现。以下是具体步骤和示例代码: 打开终端。 执行以下命令以安装Jupyter Notebook的中文语言包: pip install jupyterlab-language-pack-zh-CN …

实现UI显示在最上面的功能

同学们肯定遇到过UI被遮挡的情况,那如何让UI显示在最前面呢,先看效果 原理:UI的排序方式是和unityHierarchy窗口的层级顺序有关的,排序在下就越后显示,所以按照这个理论,当我们鼠标指到UI的时候把层级设置到最下层就好…

香橙派 AIpro开发体验:使用YOLOV8对USB摄像头画面进行目标检测

香橙派 AIpro开发体验:使用YOLOV8对USB摄像头画面进行目标检测 前言一、香橙派AIpro硬件准备二、连接香橙派AIpro1. 通过网线连接路由器和香橙派AIpro2. 通过wifi连接香橙派AIpro3. 使用vscode 通过ssh连接香橙派AIpro 三、USB摄像头测试1. 配置ipynb远程开发环境1.…

超越连接:ZL-450边缘网关全面评测与应用案例

前言 在现代工业自动化和智能设备管理的背景下,对实时数据通信与设备监控的需求日益增加。ZL450边缘网关作为一款先进的串口通信解决方案,不仅满足了这些要求,还通过其多样的连接性和高效的数据处理能力,为企业带来了显著的效率提…

添加、修改和删除字典元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 由于字典是可变序列,所以可以随时在字典中添加“键-值对”。向字典中添加元素的语法格式如下: dictionary[key] value 参数…

为什么短剧突然爆火?背后究竟谁在为流量买单?

为什么短剧突然爆火?背后究竟谁在为流量买单? 文丨微三云营销总监胡佳东,点击上方“关注”,为你分享市场商业模式电商干货。 - 今年很多朋友交流的更多的商业热门话题就是“短剧”,目前我国拥有超10亿的短视频用户&a…