【深度学习】手动实现全连接神经网络(FCNN)

news2024/11/13 12:56:19

🌻个人主页:相洋同学
🥇学习在于行动、总结和坚持,共勉!

神经网络的本质就是通过参数、线性函数与激活函数来拟合特征与目标之间的真实函数关系。

01 神经网络简介

1.1 引入

神经网络是一门重要的机器学习技术,是一种模拟人脑的神经网络以期能够实现类人工智能的机器学习技术。

人脑中的神经网络是一个非常复杂的组织。成人的大脑中估计有1000亿个神经元之多。

机器学习中的神经网络是如何实现这种模拟,并达到如此惊人的效果的?本篇文章将从简单介绍,并手动基于numpy来复现一个神经网络结构,以期能够全面了解神经网络的构建过程。

1.2 神经元

我们首先来看一下构成大脑的基本结构:神经元

 神经元的树突用来接收其他细胞传输来的信号,突触用来传输电信号。其内部可以对信息进行处理。

我们再来看一下深度学习中的神经网络结构:

神经网络一般由输入层、若干个隐藏层和输出层构成,整体来看我们似乎看不出什么联系。我们将单独的神经元抽离出来看

在单个神经元中,左边的输入点类似树突来接收信息,右边的点类似突触用来传送信息。所以深度学习中的神经网络结构与人脑的神经网络结构极其类似。

1.3 如何理解神经元

       世界具有普遍联系,事物具有各种特征。自然界中人类通过学习和探索现实世界的特征总结成规律和模式来指导自己的行为。

       通过事物的一部分特征来推测事物的另一部分特征是神经网络的主要功能。那为什么要设置不同层数的网络结构呢?

       我们可以这样来想:我们都知道,事物具有表面现象和内部特征和联系。通过深层次,多维度的特征抽象,我们能更好的推测事物现象。

更多的关于神经网络发展历史和拓展参照这篇文章:

神经网络——最易懂最清晰的一篇文章

本文着重复现神经网络结构

02 复现神经网络结构

2.1 神经网络总体工作流程

要想复现神经网络结构,我们必须了解基于神经网络的深度学习任务训练全流程:

 总体来说神经网络训练体现在以下几个阶段:

1.参数随机初始化:需要提前根据输入样本特征维度隐藏层维度输出层维度随机初始化参数;

2.前向传播:将样本矩阵输入模型以计算出预测值;

3.反向传播:根据预测值和真实值通过损失函数来计算出损失loss,通过对损失函数求导利用优化器对模型权重进行更新。

4.迭代更新参数:将新的样本送入模型继续前向传播和反向传播,直到达到预定的训练频次或提前达到训练目标。

2.2 神经网络代码实现(使用numpy库)

基于神经网络的结构,我们先对任务进行描述和分解

目标:总目标是撰写一个神经网络结构能够完成简单分类任务

1.模型初始化,输入应当包含batch_size,学习率,输入维度,隐藏层维度,输出维度;

2.实现损失函数和激活函数的封装,便于调用;

3.撰写前向传播方法,能够根据模型参数计算出预测值;

4.撰写反向传播方法,能够根据真实值和预测值来更新模型参数;

5.构造数据集,并实现模型的训练,最后输入自己构造的样本进行预测。

2.2.1 模型初始化

值得注意的是我们一定要时刻关注模型参数的维度。

我们在这里构造一个两层的网络,假设其输入维度是n*m,隐藏层维度是h,输出层维度是1(假设我们构建一个二分类任务)

则其w1维度应该为m*h,w2维度为h*1,这样我们才能得到n*h的隐藏层,和n*1的输出层(矩阵内积)

该代码还设计了字典memory来存储中间状态的矩阵,以便进行反向传播。

class FullyConnectedNetwork:
    '''
    这是一个自定义的简单的神经网络,有一个隐藏层和一个输出层
    '''

    def __init__(self, batch_size, learning_rate, input_size, hidden_size, output_size):
        '''
        初始化神经网络
        :batch_size: 批量大小
        :learning_rate: 学习率
        :param input_size: 输入层大小,就是输入特征的维度(特征数量)
        :param hidden_size: 隐藏层大小
        :param output_size: 输出层大小,就是输出的维度(分类数量)
        '''
        self.batch_size = batch_size
        self.learning_rate = learning_rate
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.W1 = np.random.randn(input_size, hidden_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size)
        self.b2 = np.zeros((1, output_size))
        self.memory = {}
2.2.2 实现损失函数和激活函数的封装

为了更加清楚得了解反向传播的过程,本文手动封装了损失函数与激活函数,并对损失函数和激活函数的导数进行提前封装。

关于损失函数还有均方差等不同的方式,激活函数也有softmax,relu等一系列激活函数。这里我们选用sigmoid以计算概率实现二分类。

    def sigmoid(self, x):
        '''
        sigmoid激活函数
        :param x: 特征向量矩阵
        :return: sigmoid(x)
        '''
        return 1 / (1 + np.exp(-x))

    def sigmoid_derivative(self, x):
        '''
        sigmoid激活函数的导数

        :param x: 特征向量矩阵
        :return: sigmoid(x)*(1-sigmoid(x))
        '''
        return x * (1 - x)

    def cross_entropy(self, y_pred, y_true):
        '''
        交叉熵损失函数

        :param y_pred: 预测值
        :param y_true: 真实值
        :return: 损失值
        '''
        return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

    def cross_entropy_derivative(self, y_pred, y_true):
        '''
        交叉熵损失函数的导数

        :param y_pred: 预测值
        :param y_true: 真实值
        :return: 损失函数对y_pred的导数
        '''
        return - (y_true / y_pred) + (1 - y_true) / (1 - y_pred)
2.2.3 前向传播

前向传播的本质是做矩阵内积,这个过程我们保存中间态A1,Z1和A2,Z2以方便进行反向传播

    def forward(self, X):
        '''
        前向传播

        :param X: 输入的特征向量矩阵
        :return: 返回输出层的输出值(预测值)
        '''
        Z1 = np.dot(X, self.W1) + self.b1
        A1 = self.sigmoid(Z1)
        self.memory['A1'] = A1
        self.memory['Z1'] = Z1
        Z2 = np.dot(A1, self.W2) + self.b2
        A2 = self.sigmoid(Z2)
        self.memory['A2'] = A2
        self.memory['Z2'] = Z2
        return A2
 2.3.4 反向传播

针对模型的反向传播过程涉及到链式求导的知识,需要分别计算输出层的梯度,输出层的权重和偏执梯度,隐藏层的梯度,隐藏层权重和偏执的梯度,最后根据梯度更新参数。

关于梯度下降参考这篇文章:【深度学习】梯度下降与反向传播

后序还会对神经网络中的反向传播过程进行深层次的拆解,敬请期待

这里先罗列一下整个过程:

    def backward(self, X, y_true, y_pred):
        '''
        反向传播

        :param X:
        '''
        dA2 = self.cross_entropy_derivative(y_pred, y_true)
        dZ2 = dA2 * self.sigmoid_derivative(self.memory['A2'])
        dW2 = 1. / self.batch_size * np.dot(self.memory['A1'].T, dZ2)
        db2 = 1. / self.batch_size * np.sum(dZ2, axis=0, keepdims=True)
        dA1 = np.dot(dZ2, self.W2.T)
        dZ1 = dA1 * self.sigmoid_derivative(self.memory['A1'])
        dW1 = 1. / self.batch_size * np.dot(X.T, dZ1)
        db1 = 1. / self.batch_size * np.sum(dZ1, axis=0, keepdims=True)

        return dW1, db1, dW2, db2

    def update_parameters(self, dW1, db1, dW2, db2):
        '''
        更新参数

        :param dW1:
        :param db1:
        :param dW2:
        :param db2:
        '''
        self.W1 -= self.learning_rate * dW1
        self.b1 -= self.learning_rate * db1
        self.W2 -= self.learning_rate * dW2
        self.b2 -= self.learning_rate * db2
2.3.5 构建数据集

最后本文设计了如下任务:随机生层一批具有五个特征的样本,如果样本的第二个数大于第四个数,标记为1,否则为零

def build_simple():
    """
    构建一个简单的样本:随机生成5个特征判断第二个特征是否大于第四个特征,如果大于,目标值记为1,否则记为0
    :return: list(1*5), list(1*1)
    """
    simple = np.random.randint(0,10,size=(5))
    if simple[1] > simple[3]:
        y = 1
    else:
        y = 0
    return simple, y

def build_data(num):
    """
    生成数据集
    :param num: 生成样本数量
    :return: 返回X,y
    """
    X = []
    y = []
    for i in range(num):
        x, y_ = build_simple()
        X.append(x)
        y.append(y_)
    return np.array(X), np.array(y).reshape(-1,1)
2.3.6 模型训练

这里的训练轮数,样本数均可以进行修改,可以多多尝试,体验不同参数下的训练。

def main():
    # 定义神经网络
    batch_size = 10
    num_epochs = 10000


    FCNN = FullyConnectedNetwork(batch_size=batch_size, learning_rate=0.1, input_size=5, hidden_size=5, output_size=1)
    X,y = build_data(100000)

    # 训练神经网络
    for i in range(num_epochs):
        # 前向传播
        x_ = X[i*batch_size:(i+1)*batch_size,:]
        y_ = y[i*batch_size:(i+1)*batch_size,:]
        y_pred = FCNN.forward(x_)
        # 计算损失
        loss = FCNN.cross_entropy(y_pred, y_)
        # 反向传播
        dW1, db1, dW2, db2 = FCNN.backward(x_, y_, y_pred)
        # 更新参数
        FCNN.update_parameters(dW1, db1, dW2, db2)
        if i % 100 == 0:
            print("第{}次训练,损失为{}".format(i, loss))
            print('==========================')
    # 定义输入数据
    test = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [5, 8, 5, 4, 1], [2, 3, 1, 1, 1]])

    # 预测输出
    y_pred = FCNN.forward(test)
    y_pred = np.squeeze(y_pred)
    for i in range(len(y_pred)):
        if y_pred[i] > 0.5:
            y_pred[i] = 1
        else:
            y_pred[i] = 0
    for i in range(len(test)):
        print("输入数据为{}".format(test[i]))
        print("预测输出为{}".format(y_pred[i]))

测试结果:

可以看到我们的训练训练结果还是相当不错,这里没有写评估准确率的逻辑,学有余力的朋友可以尝试写一下。

 参考文章:

自己动手写神经网络(一)——初步搭建全连接神经网络框架-CSDN博客

神经网络——最易懂最清晰的一篇文章-CSDN博客

下面是完整代码:

'''
    标题: 手动实现神经网络
    作者:相洋同学
    日期:2024年3月18日
'''
import numpy as np


class FullyConnectedNetwork:
    '''
    这是一个自定义的简单的神经网络,有一个隐藏层和一个输出层
    '''

    def __init__(self, batch_size, learning_rate, input_size, hidden_size, output_size):
        '''
        初始化神经网络
        :batch_size: 批量大小
        :learning_rate: 学习率
        :param input_size: 输入层大小,就是输入特征的维度(特征数量)
        :param hidden_size: 隐藏层大小
        :param output_size: 输出层大小,就是输出的维度(分类数量)
        '''
        self.batch_size = batch_size
        self.learning_rate = learning_rate
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.W1 = np.random.randn(input_size, hidden_size)
        self.b1 = np.zeros((1, hidden_size))
        self.W2 = np.random.randn(hidden_size, output_size)
        self.b2 = np.zeros((1, output_size))
        self.memory = {}

    def sigmoid(self, x):
        '''
        sigmoid激活函数
        :param x: 特征向量矩阵
        :return: sigmoid(x)
        '''
        return 1 / (1 + np.exp(-x))

    def sigmoid_derivative(self, x):
        '''
        sigmoid激活函数的导数

        :param x: 特征向量矩阵
        :return: sigmoid(x)*(1-sigmoid(x))
        '''
        return x * (1 - x)

    def cross_entropy(self, y_pred, y_true):
        '''
        交叉熵损失函数

        :param y_pred: 预测值
        :param y_true: 真实值
        :return: 损失值
        '''
        return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

    def cross_entropy_derivative(self, y_pred, y_true):
        '''
        交叉熵损失函数的导数

        :param y_pred: 预测值
        :param y_true: 真实值
        :return: 损失函数对y_pred的导数
        '''
        return - (y_true / y_pred) + (1 - y_true) / (1 - y_pred)

    def forward(self, X):
        '''
        前向传播

        :param X: 输入的特征向量矩阵
        :return: 返回输出层的输出值(预测值)
        '''
        Z1 = np.dot(X, self.W1) + self.b1
        A1 = self.sigmoid(Z1)
        self.memory['A1'] = A1
        self.memory['Z1'] = Z1
        Z2 = np.dot(A1, self.W2) + self.b2
        A2 = self.sigmoid(Z2)
        self.memory['A2'] = A2
        self.memory['Z2'] = Z2
        return A2

    def backward(self, X, y_true, y_pred):
        '''
        反向传播

        :param X:
        '''
        dA2 = self.cross_entropy_derivative(y_pred, y_true)
        dZ2 = dA2 * self.sigmoid_derivative(self.memory['A2'])
        dW2 = 1. / self.batch_size * np.dot(self.memory['A1'].T, dZ2)
        db2 = 1. / self.batch_size * np.sum(dZ2, axis=0, keepdims=True)
        dA1 = np.dot(dZ2, self.W2.T)
        dZ1 = dA1 * self.sigmoid_derivative(self.memory['A1'])
        dW1 = 1. / self.batch_size * np.dot(X.T, dZ1)
        db1 = 1. / self.batch_size * np.sum(dZ1, axis=0, keepdims=True)

        return dW1, db1, dW2, db2

    def update_parameters(self, dW1, db1, dW2, db2):
        '''
        更新参数

        :param dW1:
        :param db1:
        :param dW2:
        :param db2:
        '''
        self.W1 -= self.learning_rate * dW1
        self.b1 -= self.learning_rate * db1
        self.W2 -= self.learning_rate * dW2
        self.b2 -= self.learning_rate * db2

def build_simple():
    """
    构建一个简单的样本:随机生成5个特征判断第二个特征是否大于第四个特征,如果大于,目标值记为1,否则记为0
    :return: list(1*5), list(1*1)
    """
    simple = np.random.randint(0,10,size=(5))
    if simple[1] > simple[3]:
        y = 1
    else:
        y = 0
    return simple, y

def build_data(num):
    """
    生成数据集
    :param num: 生成样本数量
    :return: 返回X,y
    """
    X = []
    y = []
    for i in range(num):
        x, y_ = build_simple()
        X.append(x)
        y.append(y_)
    return np.array(X), np.array(y).reshape(-1,1)



def main():
    # 定义神经网络
    batch_size = 10
    num_epochs = 10000


    FCNN = FullyConnectedNetwork(batch_size=batch_size, learning_rate=0.1, input_size=5, hidden_size=5, output_size=1)
    X,y = build_data(100000)

    # 训练神经网络
    for i in range(num_epochs):
        # 前向传播
        x_ = X[i*batch_size:(i+1)*batch_size,:]
        y_ = y[i*batch_size:(i+1)*batch_size,:]
        y_pred = FCNN.forward(x_)
        # 计算损失
        loss = FCNN.cross_entropy(y_pred, y_)
        # 反向传播
        dW1, db1, dW2, db2 = FCNN.backward(x_, y_, y_pred)
        # 更新参数
        FCNN.update_parameters(dW1, db1, dW2, db2)
        if i % 100 == 0:
            print("第{}次训练,损失为{}".format(i, loss))
            print('==========================')
    # 定义输入数据
    test = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [5, 8, 5, 4, 1], [2, 3, 1, 1, 1]])

    # 预测输出
    y_pred = FCNN.forward(test)
    y_pred = np.squeeze(y_pred)
    for i in range(len(y_pred)):
        if y_pred[i] > 0.5:
            y_pred[i] = 1
        else:
            y_pred[i] = 0
    for i in range(len(test)):
        print("输入数据为{}".format(test[i]))
        print("预测输出为{}".format(y_pred[i]))

if __name__ == '__main__':
    main()

以上

互联网是最好的课本,实践是最好的老师,AI是最好的学习助手

行动起来,共勉

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

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

相关文章

hosts文件丢失了怎么办?

hosts文件的位置:C:\Windows\System32\drivers\etc 丢失了恢复的方法: 在“管理员:命令提示符中输入: (winR 然后cmd) for /f %P in (dir %windir%\WinSxS\hosts /b /s) do copy %P %windir%\System32\d…

SAP前台处理:物料主数据创建<MM01>之采购视图

一、背景: 终于来到了物料主数据,我觉得物料账是SAP最重要的一项发明,也一直是SAP的一项重要优势,物料账记录了一个个物料的生生不息; 本章主要讲解物料主数据和财务相关的主要内容:这里特别提示由于作者…

SpringMVC | SpringMVC中的“JSON数据交互“ 和“RESTful支持“

目录: 1.JSON 数据交互1.1 JSON概述1.2 JSON的“数据结构”对象结构数组结构 1.3 JSON的“数据转换” (JSON交互) 作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习! 该文章参考学习教…

二、C#选择排序算法

简介 选择排序算法的基本思想是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列…

【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列三:Faster R-CNN图文详解

【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列二:Fast R-CNN图文详解 概念预设 感受野 感受野(Receptive Field) 是指特征图上的某个点能看到的输入图像的区域。 神经元感受野的值越大表示其能接触到的原始图像范围就越大,也意味着它…

工业AMR机器人如何实现规模化的柔性生产

在当下高度复杂的工业生产环境中,机器人如何实现规模化的柔性生产,已成为业界关注的焦点。特别是在追求高效率、高质量的生产过程中,团队协作的重要性愈发凸显。富唯智能一体化AMR控制系统,作为机器人的核心指挥部,犹如…

VMware安装Centos 6.5系统

文章目录 镜像下载地址1.在vmware中新建虚拟机2.选择标准典型步骤进行安装3.选择以后再放入光盘4.选择准备安装的系统类型5.为虚拟机指定名称并指定位置6.磁盘空间20G默认下一步;7.将无用的硬件删除掉8.点击打开虚拟机;9.选择第一个回车进行全新安装&…

蓝桥杯刷题(十一)

1.卡片 反向思考&#xff0c;看k种卡片可以分给几位同学 代码 n int(input()) k 1 while k*(k1)<2*n:k1 print(k)2.美丽的2 代码 def f(x)->bool:while x:if x%102:return Truex//10return False cnt 0 for i in range(1,2021):if f(i):cnt1 print(cnt)3.单词分析 …

Pytorch详细应用基础(全)

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 1.安装pytorch以及anaconda配置 尽量保持默认的通道&#xff0c;每次写指令把镜像地址写上就行。 defaults优先级是最低的&#…

Matlab 双目相机标定(内置函数)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 相机标定的目的就是要找到从世界坐标转换为图像坐标所用到的投影P矩阵各个系数(即相机的内参与外参)。具体过程如下所述: 1、首先我们需要获取一个已知图形的图像(这里我们使用MATLAB所提供的数据)。 2、找到同…

Django templates 存放html目录

模板 一概述 模板由两部分组成&#xff0c;一部分是HTML代码&#xff0c;一部分是逻辑控制代码&#xff08;变量&#xff0c;标签&#xff0c;过滤器&#xff09; 作用&#xff1a;可以通过一些逻辑控制代码减少一些重复的操作更快速的生成HTML代码&#xff0c;并且实现简单的…

二叉树遍历(牛客网)

描述 编一个程序&#xff0c;读入用户输入的一串先序遍历字符串&#xff0c;根据此字符串建立一个二叉树&#xff08;以指针方式存储&#xff09;。 例如如下的先序遍历字符串&#xff1a; ABC##DE#G##F### 其中“#”表示的是空格&#xff0c;空格字符代表空树。建立起此二叉树…

后端工程师快速使用vue和Element

文章目录 Vue1 Vue概述2 快速入门3 Vue指令3.1 v-bind和v-model3.2 v-on3.3 v-if和v-show3.4 v-for3.5 案例 4 生命周期 Element快速使用1 Element介绍2 快速入门3 当前页面中嵌套另一个页面案例代码案例截图 Vue 1 Vue概述 通过我们学习的htmlcssjs已经能够开发美观的页面了…

Linux 文件系统:重定向、缓冲区

目录 一、重定向 1、输出重定向 2、输入重定向 3、追加重定向 4、dup2 系统调用 二、理性理解Linux系统下“一切皆文件” 了解硬件接口 三、缓冲区 1、为什么要有缓冲区? 2、刷新策略 3、缓冲模式改变导致发生写时拷贝 未创建子进程时 创建子进程时 使用fflush…

使用树莓派 结合Python Adafruit驱动OLED屏幕 显示实时视频

关于OLED屏幕的驱动&#xff0c;在之前我已经写过很多篇博文&#xff1a; IIC 协议 和 OLED_oled iic-CSDN博客 香橙派配合IIC驱动OLED & 使用SourceInsight解读源码_香橙派5 驱动屏幕-CSDN博客 这两篇博文都是通过模拟或调用IIC协议来使用C语言驱动OLED屏幕&#xff0c;现…

Sentinel加密锁的工作原理

Sentinel加密锁是一种先进的安全机制&#xff0c;它旨在提供强大的数据加密和访问控制功能&#xff0c;确保数据在传输和存储过程中的机密性、完整性和可用性。下面将详细介绍Sentinel加密锁的工作原理、优势以及在现实中的应用。 一、Sentinel加密锁的工作原理 Sentinel加密锁…

2024年腾讯云GPU服务器价格表_1小时费用_一个月价格和一年优惠

腾讯云GPU服务器怎么收费&#xff1f;GPU服务器1小时多少钱&#xff1f;一个月收费价格表和一年费用标准&#xff0c;腾讯云百科txybk.com分享腾讯云GPU服务器GPU计算型GN10Xp、GPU服务器GN7、GPU渲染型 GN7vw等GPU实例费用价格&#xff0c;以及NVIDIA Tesla T4 GPU卡和V100详细…

LiveGBS流媒体平台GB/T28181常见问题-如何订阅设备状态在线离线状态redis订阅设备或是通道状态subscribe device操作及示例

LiveGBS如何订阅设备状态在线离线状态redis订阅设备或是通道状态subscribe device操作及示例 1、如何监听设备状态2、device订阅2.1、设备上线消息2.2、设备离线消息2.2、通道上线消息2.2、通道离线消息 3、订阅示例3.1、连接REDIS3.2、订阅device示例3.3、设备上线示例3.3.1、…

解决微信录像帧率不足30fps

问题现象 使用工具检测录像帧率不足30fps 问题分析 1&#xff0c;抓取微信录像systrace 很明显camera provider很多线程处于Runnable状态&#xff0c;获取不到cpu原因&#xff0c;有两种可能原因&#xff1a;一是cpu频率很低&#xff1b;二是存在高负载应用。 先检查cpu频率…