💻
QMMMS的笔记
博客
  • QMMMS的笔记
  • agent
    • MCP的背景、原理和开发
    • Agent 历史与背景
    • Agentic Workflows
    • 环境检查与基础工具
    • Tool Call
    • 工具与运行时的值
    • temp
    • 处理 Tool Call error
    • trick
  • algorithm
    • 线性结构
    • 二叉树
    • 图
    • 查找
    • 排序
    • 动态规划
    • 优化方法
    • 数学
    • 迁移至Java
  • computer_composition
    • 系统总线
    • 存储器
    • 输入输出系统
    • 计算机的运算方法
    • 指令系统
    • 补充
  • computer_network
    • 引入
    • 应用层
    • 传输层
    • 网络层(数据平面)
    • 网络层(控制平面)
    • 链路层
    • 常见问答
    • 实验
  • database
    • SQL实战
    • 关系代数
    • 数据库设计
    • 规范化
    • 数据库基本概念
    • 查询原理
    • 数据库恢复技术
    • 并发控制
  • dev_tools
    • Git
    • Nginx
    • Spring
    • LangChain
    • PyTorch Cheat Sheet
    • MyBatis
    • MySQL Cheat Sheet
    • MySQL 补充
    • Redis
    • Docker
    • RocketMQ
    • Chrome
  • linux
    • Linux基础命令与使用
    • 文件与权限
    • 文件与目录操作
    • 权限属性高级
    • 命令与文件的查找
    • 文件压缩和打包
    • vim编辑器
    • shell变量
    • 命令补充
    • 数据流重定向
    • 管道命令
    • shell脚本
    • 用户管理
    • 用户间交流
    • 计划任务
    • 进程管理
    • 软件管理
    • 认识系统服务
    • 运维常用命令
    • 常用命令
  • llm
    • 大规模语言模型概述
    • 分布式训练概述
    • 有监督微调概述
    • 强化学习与LLM
    • LLM评估概述
    • 大模型应用
    • 理解大模型
    • 量化
    • 预训练
    • 上下文学习
  • machine_learning
    • 引入
    • 大致正确学习
    • 一致收敛
    • 偏差还是过拟合?
    • 可学习的充要条件
    • 非均匀可学习性
    • 计算复杂性
  • mathematics
    • 概率与统计基础
    • 线性代数基础
  • operating_system
    • 操作系统基本概念
    • 进程和线程
    • 同步,互斥与死锁
    • 内存管理
    • 文件系统
    • I/O系统
    • 保护与安全
    • 《现代操作系统》
  • statistical_learning
    • 统计学习引入
    • 线性回归
    • 分类
    • 重抽样方法
    • 线性模型选择与正则化
    • 非线性模型
    • 基于树的方法
    • 支持向量机
    • 无指导学习
    • 马尔科夫链和蒙托卡罗方法简明理解
    • R语言速查
  • deep_learning
    • basic_concepts
      • 逻辑回归与损失函数
      • 神经网络
      • 正则化、预处理、权重初始化
      • 优化算法
      • 机器学习策略
      • 复习:从计算机视觉的角度
      • 卷积神经网络
      • 深度卷积网络示例
      • 计算机视觉任务
      • 循环神经网络
      • 自然语言处理任务
      • 注意力
      • Transformers 家族
      • 显卡扫盲
      • 强化学习概述
    • semi-supervise
      • 半监督学习简介
      • Consistency Regularization
      • Proxy-label Methods
      • Holistic Methods
      • Generative Models
      • Graph-Based SSL
      • Self-Supervision for SSL
      • Other SSL methods
  • programming
    • cpp
      • STL
      • C++基础
      • 内存管理
      • 面向对象
    • java
      • 环境和介绍
      • 注释
      • String
      • 面向对象思想
      • Object
      • 包
      • 访问权限修饰符
      • 初始化块
      • 接口
      • 内部类
      • 注解
      • 枚举
      • 集合框架
      • List
      • Map
      • 泛型
      • 迭代
      • IO与流
      • 序列化
      • 异常
      • Lambda
      • Stream流
      • Socket
      • 缓冲
      • 命名规范
      • 拆箱装箱
      • 值传递
      • 深拷贝
      • 反射
      • JVM
      • 并发编程基础
    • python
      • 并发编程
      • 环境管理
  • software_engineering
    • basic_concepts
      • 系统分析与设计概述
      • 规划
      • 需求分析与原型设计
      • 项目管理
      • 建模
      • 数据库设计
      • 架构
      • 配置管理
      • 测试管理
      • 安全
      • 编码原则
      • 微服务
      • 补充内容
    • software_testing
      • CMMI基础
      • PPQA与SQA
      • 软件测试基础
      • 黑盒测试
      • 白盒测试
      • 集成测试
      • 系统测试
      • 测开面试补充
由 GitBook 提供支持
在本页
  • 结构
  • 激活函数
  • sigmoid函数
  • tanh函数
  • Relu函数
  • Leaky ReLu函数
  • 神经网络计算流程
  • 反向传播与计算图
  • 向量与矩阵求导
  • 一些思考
在GitHub上编辑
  1. deep_learning
  2. basic_concepts

神经网络

上一页逻辑回归与损失函数下一页正则化、预处理、权重初始化

最后更新于9个月前

这里推荐一个网站:playground.tensorflow.org,这里可以可视化的看到不同的神经网络到底在什么,以及神经网络是如何做一些非线性的决策边界的。

结构

许多sigmoid单元堆叠起来形成一个神经网络。

  1. 输入特征x1x_1x1​、x2x_2x2​、x3x_3x3​,它们被竖直地堆叠起来,这叫做神经网络的输入层。它包含了神经网络的输入

  2. 另外一层我们称之为隐藏层(中间四个结点)。这些中间结点的准确值我们是不知道到的

  3. 最后一层只由一个结点构成,而这个只有一个结点的层被称为输出层,它负责产生预测值

类似的,我们可以多加一点隐藏层

激活函数


到目前为止,只用过sigmoid激活函数,但是,有时其他的激活函数效果会更好。这里介绍并比较4种

Q : 为什么需要非线性的激活函数?换句话说是否可以去掉激活函数这一步?

A : 如果没有激活函数,那么神经网络就是简单的线性变换,无法解决一些非线性的问题,比如最简单的 xor (异或)。自从有了激活函数之后,可以将数据从线性空间映射到非线性空间,这使得神经网络可以分离一些原始输入中无法线性分割的数据。而且不止如此,多层的神经网络函数可以以任意的精度去拟合任意的连续函数。

sigmoid函数

除了输出层是一个二分类问题(y^\hat{y}y^​ 的数值介于0和1之间)基本不会用它,性能比较差。

a=g(z)=11+e−za=g(z)=\frac{1}{1 + e^{-z}}a=g(z)=1+e−z1​
ddzg(z)=11+e−z(1−11+e−z)=g(z)(1−g(z))\frac{d}{dz}g(z) = {\frac{1}{1 + e^{-z}} (1-\frac{1}{1 + e^{-z}})}=g(z)(1-g(z))dzd​g(z)=1+e−z1​(1−1+e−z1​)=g(z)(1−g(z))
  • sigmoid神经元的激活在接近0或1处时会饱和:在这些区域,梯度几乎为0,导致梯度消失。为了防止饱和,必须对于权重矩阵初始化特别留意。比如,如果初始化权重过大,那么大多数神经元将会饱和,导致网络就几乎不学习了。

  • Sigmoid函数的输出不是零中心的。这一情况将影响梯度下降的运作,因为如果输入神经元的数据总是正数,那么关于w的梯度在反向传播的过程中,将会要么全部是正数,要么全部是负数,这将会导致梯度下降权重更新时出现z字型的下降。

tanh函数

g(z)=tanh(z)=ez−e−zez+e−zg(z)= tanh(z) = \frac{e^{z} - e^{- z}}{e^{z} + e^{- z}}g(z)=tanh(z)=ez+e−zez−e−z​
ddzg(z)=1−(tanh(z))2\frac{d}{{d}z}g(z) = 1 - (tanh(z))^{2}dzd​g(z)=1−(tanh(z))2

事实上,tanh函数是sigmoid的向下平移和伸缩后的结果。tanh函数在所有场合都优于sigmoid函数,因为其输出的平均值接近于零,因此它可以更好地将数据集中到下一层。

sigmoid函数和tanh函数两者共同的缺点是,在 zzz 特别大或者特别小的情况下,导数的梯度或者函数的斜率会变得特别小,最后就会接近于0,导致降低梯度下降的速度。

Relu函数

Relu是最常用的激活函数,如果不确定用哪个激活函数,就使用ReLu。

g(x)=max(0,z)g(x) =max( 0,z)g(x)=max(0,z)
g(z)′={0if z < 01if z > 0undefinedif z = 0g(z)^{'}= \begin{cases} 0& \text{if z < 0}\\ 1& \text{if z > 0}\\ undefined& \text{if z = 0} \end{cases}g(z)′=⎩⎨⎧​01undefined​if z < 0if z > 0if z = 0​

只要zzz是正值的情况下,导数恒等于1,当zzz是负值的时候,导数恒等于0。

  • sigmoid和tanh神经元含有指数运算等耗费计算资源的操作,而ReLU可以简单地通过对一个矩阵进行阈值计算得到。

  • 缺点:在训练的时候,当一个很大的梯度流过ReLU的神经元的时候,可能会导致梯度更新到一种特别的状态,在这种状态下神经元将无法被其他任何数据点再次激活。如果这种情况发生,那么从此所以流过这个神经元的梯度将都变成0。也就是说,这个ReLU单元在训练中将不可逆转的死亡,因为这导致了数据多样化的丢失。

Leaky ReLu函数

Leaky ReLU是为解决“ReLU死亡”问题的尝试。

a=max(0.01z,z)a = max( 0.01z,z)a=max(0.01z,z)
g(z)′={0.01if z < 01if z > 0undefinedif z = 0g(z)^{'}= \begin{cases} 0.01& \text{if z < 0}\\ 1& \text{if z > 0}\\ undefined& \text{if z = 0} \end{cases}g(z)′=⎩⎨⎧​0.011undefined​if z < 0if z > 0if z = 0​

常数不一定是0.01,可以为学习算法选择不同的参数。

最后需要注意一点:在同一个网络中混合使用不同类型的神经元是非常少见的,虽然没有什么根本性问题来禁止这样做。

神经网络计算流程


  1. 初始化参数 W[1],W[2],b[1],b[2]W^{[1]}, W^{[2]},b^{[1]},b^{[2]}W[1],W[2],b[1],b[2]

  2. 对所有样本,由参数 W[1],W[2],b[1],b[2]W^{[1]}, W^{[2]},b^{[1]},b^{[2]}W[1],W[2],b[1],b[2] 计算预测值 y^\hat{y}y^​

  3. 计算损失 J(W[1],b[1],W[2],b[2])=1m∑i=1mL(y^,y)J(W^{[1]},b^{[1]},W^{[2]},b^{[2]}) = {\frac{1}{m}}\sum_{i=1}^mL(\hat{y}, y)J(W[1],b[1],W[2],b[2])=m1​∑i=1m​L(y^​,y)

  4. 计算导数 dW[1],dW[2],db[1],db[2]dW^{[1]}, dW^{[2]},db^{[1]},db^{[2]}dW[1],dW[2],db[1],db[2] ,并更新参数

  5. 回到步骤 2 进行下一轮计算,直到模型可被接受

Q1:为什么在初始化时要将参数设置为随机数?不能全为0吗?

A1:如果全设置为 0 ,同一层的各个节点参数就是一样的,并且在之后的训练中,同一层的各个节点参数仍然保持一致,这是我们不愿意看到的情况。对于笔记开始的那个神经网络来讲,可以这么写:

W[1]=np.random.randn(2,2)  ∗  0.01  ,  b[1]=np.zeros((2,1))W^{[1]} = np.random.randn(2,2)\;*\;0.01\;,\;b^{[1]} = np.zeros((2,1))W[1]=np.random.randn(2,2)∗0.01,b[1]=np.zeros((2,1))
W[2]=np.random.randn(2,2)  ∗  0.01  ,  b[2]=0W^{[2]} = np.random.randn(2,2)\;*\;0.01\;,\;b^{[2]} = 0W[2]=np.random.randn(2,2)∗0.01,b[2]=0

Q2:为什么要乘 0.01?

A2:对于sigmoid函数和tanh函数,如果参数 WWW 过大,激活函数的输入也非常大,从而导致梯度接近于零,换句话说就是“学得慢”。所以我们希望参数 WWW 能接近 0

反向传播与计算图

利用计算图和链式法则,我们可以一步一步计算复杂函数的导数。

  • 加法门单元把输出的梯度相等地分发给它所有的输入

  • 取最大值门单元对梯度做路由。和加法门不同,取最大值门将梯度转给其中一个输入,这个输入是在前向传播中值最大的那个输入。

  • 乘法门单元相互交换梯度

例子2:对于如下式子

f(w,x)=11+e−(w0x0+w1x1+w2)f(w,x)=\frac{1}{1+e^{-(w_0x_0+w_1x_1+w_2)}}f(w,x)=1+e−(w0​x0​+w1​x1​+w2​)1​

从最右边开始,假设误差为1.00

对于 f(x)=1xf(x)=\frac{1}{x}f(x)=x1​,dfdx=−1x2\frac{df}{dx}=-\frac{1}{x^2}dxdf​=−x21​,传递的误差为−11.372×1.00=−0.53-\frac{1}{1.37^2} \times 1.00=-0.53−1.3721​×1.00=−0.53

对于 f(x)=c+xf(x)=c+xf(x)=c+x,dfdx=1\frac{df}{dx}=1dxdf​=1,传递的误差为1×−0.53=−0.531 \times -0.53=-0.531×−0.53=−0.53

对于 f(x)=exf(x)=e^xf(x)=ex,dfdx=ex\frac{df}{dx}=e^xdxdf​=ex,传递的误差为e−1.00×−0.53=−0.20e^{-1.00} \times -0.53=-0.20e−1.00×−0.53=−0.20

对于 f(x)=−xf(x)=-xf(x)=−x,dfdx=−1\frac{df}{dx}=-1dxdf​=−1,传递的误差为−1×−0.20=0.20-1 \times -0.20=0.20−1×−0.20=0.20

对于 f(x)=x+x′f(x)=x+x'f(x)=x+x′,dfdx=1\frac{df}{dx}=1dxdf​=1,传递的误差为1×0.20=0.201 \times 0.20=0.201×0.20=0.20

对于 f(x)=x⋅x′f(x)=x\cdot x'f(x)=x⋅x′,∂f∂x=x′\frac{\partial f}{\partial x}=x'∂x∂f​=x′,对于w0w_0w0​,传递的误差为−1.00×0.20=−0.20-1.00 \times 0.20=-0.20−1.00×0.20=−0.20,对于x0x_0x0​,传递的误差为2.00×0.20=0.402.00 \times 0.20=0.402.00×0.20=0.40,对于w1w_1w1​,传递的误差为−2.00×0.20=−0.40-2.00 \times 0.20=-0.40−2.00×0.20=−0.40,对于x1x_1x1​,传递的误差为−3.00×0.20=−0.60-3.00 \times 0.20=-0.60−3.00×0.20=−0.60

例子3:

Inh1=w1∗x1+w3∗x2h1=Outh1=Sigmoid(Inh1)Inh2=w2∗x1+w4∗x2h2=Outh2=Sigmoid(Inh2)Ino1=w5∗h1+w7∗h2o1=Outo1=Sigmoid(Ino1)Ino2=w6∗h1+w8∗h2o2=Outo2=Sigmoid(Ino2)Error=12∑i=12(oi−yi)2\begin{align} &In_{h_1}=w_1*x_1+w_3*x_2 \\ &h1 = Out_{h_1}=Sigmoid(In_{h_1}) \\ &In_{h_2}=w_2*x_1+w_4*x_2 \\ &h2 = Out_{h_2}=Sigmoid(In_{h_2}) \\ &In_{o_1}=w_5*h_1+w_7*h_2\\ &o_1=Out_{o_1}=Sigmoid(In_{o_1})\\ &In_{o_2}=w_6*h_1+w_8*h_2\\ &o_2=Out_{o_2}=Sigmoid(In_{o_2})\\ &Error=\frac{1}{2}\sum_{i=1}^2(o_i-y_i)^2 \end{align}​Inh1​​=w1​∗x1​+w3​∗x2​h1=Outh1​​=Sigmoid(Inh1​​)Inh2​​=w2​∗x1​+w4​∗x2​h2=Outh2​​=Sigmoid(Inh2​​)Ino1​​=w5​∗h1​+w7​∗h2​o1​=Outo1​​=Sigmoid(Ino1​​)Ino2​​=w6​∗h1​+w8​∗h2​o2​=Outo2​​=Sigmoid(Ino2​​)Error=21​i=1∑2​(oi​−yi​)2​​

如果要计算w5的梯度:

δ5=∂Error∂o1∗∂o1∂Ino1∗∂Ino1∂w5∂Error∂o1=o1−y1∂o1∂Ino1=o1∗(1−o1)∂Ino1∂w5=h1\begin{align} \delta_5= \frac{\partial Error}{\partial o_1} *\frac{\partial o_1}{\partial In_{o_1}} * \frac{\partial In_{o_1}}{\partial w_5} \\ \frac{\partial Error}{\partial o_1} = o_1-y_1 \\ \frac{\partial o_1}{\partial In_{o_1}}= o_1 *(1-o_1)\\ \frac{\partial In_{o_1}}{\partial w_5} = h_1 \end{align}δ5​=∂o1​∂Error​∗∂Ino1​​∂o1​​∗∂w5​∂Ino1​​​∂o1​∂Error​=o1​−y1​∂Ino1​​∂o1​​=o1​∗(1−o1​)∂w5​∂Ino1​​​=h1​​​

如果要计算w1的梯度:

δ5=∂Error∂o1∗∂o1∂Ino1∗∂Ino1∂h1∗∂h1∂Inh1∗∂Inh1∂w1+∂Error∂o2∗∂o2∂Ino2∗∂Ino2∂h1∗∂h1∂Inh1∗∂Inh1∂w1\begin{align} \delta_5= &\frac{\partial Error}{\partial o_1} *\frac{\partial o_1}{\partial In_{o_1}} * \frac{\partial In_{o_1}}{\partial h_1} *\frac{\partial h_1}{\partial In_{h_1}}*\frac{\partial In_{h_1}}{\partial w_1}+\\ &\frac{\partial Error}{\partial o_2} *\frac{\partial o_2}{\partial In_{o_2}} * \frac{\partial In_{o_2}}{\partial h_1} *\frac{\partial h_1}{\partial In_{h_1}}*\frac{\partial In_{h_1}}{\partial w_1} \end{align}δ5​=​∂o1​∂Error​∗∂Ino1​​∂o1​​∗∂h1​∂Ino1​​​∗∂Inh1​​∂h1​​∗∂w1​∂Inh1​​​+∂o2​∂Error​∗∂Ino2​​∂o2​​∗∂h1​∂Ino2​​​∗∂Inh1​​∂h1​​∗∂w1​∂Inh1​​​​​

更新参数公式:

wi′=wi−η∗δiw_i'=w_i-\eta * \delta_iwi′​=wi​−η∗δi​

向量与矩阵求导

当我们想要向量化公式,在求导时,注意向量对向量求导得到的是雅可比矩阵。

当我们使用向量代表x,y,z时,注意各个偏导的维数。

当我们使用矩阵代表x,y,z时

一些思考

理解具有全连接层的神经网络的一个方式是:可以认为它们定义了一个由一系列函数组成的函数族,网络的权重就是每个函数的参数。如此产生的问题是:该函数族的表达能力如何?存在不能被神经网络表达的函数吗?

现在看来,拥有至少一个隐层的神经网络是一个通用的近似器。换句话说,神经网络可以近似任何连续函数。

既然一个隐层就能近似任何函数,那为什么还要构建更多层来将网络做得更深?答案是:虽然一个2层网络在数学理论上能完美地近似所有连续函数,但在实际操作中效果相对较差。

另外,在实践中3层的神经网络会比2层的表现好,然而继续加深(做到4,5,6层)很少有太大帮助。卷积神经网络的情况却不同,在卷积神经网络中,对于一个良好的识别系统来说,深度是一个极端重要的因素(比如数十(以10为量级)个可学习的层)。对于该现象的一种解释观点是:因为图像拥有层次化结构(比如脸是由眼睛等组成,眼睛又是由边缘组成),所以多层处理对于这种数据就有直观意义。

看起来如果数据不是足够复杂,则似乎小一点的网络更好,因为可以防止过拟合。然而并非如此,防止神经网络的过拟合有很多方法(L2正则化,dropout和输入噪音等),后面会详细讨论。在实践中,使用这些方法来控制过拟合比减少网络神经元数目要好得多。

不要减少网络神经元数目的主要原因在于小网络更难使用梯度下降等局部方法来进行训练:虽然小型网络的损失函数的局部极小值更少,也比较容易收敛到这些局部极小值,但是这些最小值一般都很差,损失值很高。相反,大网络拥有更多的局部极小值,但就实际损失值来看,这些局部极小值表现更好,损失更小。因为神经网络是非凸的,就很难从数学上研究这些特性。

在实际中,你将发现如果训练的是一个小网络,那么最终的损失值将展现出多变性:某些情况下运气好会收敛到一个好的地方,某些情况下就收敛到一个不好的极值。从另一方面来说,如果你训练一个大的网络,你将发现许多不同的解决方法,但是最终损失值的差异将会小很多。这就是说,所有的解决办法都差不多,而且对于随机初始化参数好坏的依赖也会小很多。

需要记住的是:不应该因为害怕出现过拟合而使用小网络。相反,应该进尽可能使用大网络,然后使用正则化技巧来控制过拟合。

相较于sigmoid和tanh函数,ReLU对于随机梯度下降的收敛有巨大的加速作用( 等的论文指出有6倍之多)。据称这是由它的线性,非饱和的公式导致的。

Krizhevsky
一个简单神经的网络结构
四层神经网络