T01_神经网络

发布于:2025-07-08 ⋅ 阅读:(16) ⋅ 点赞:(0)

感知机

感知机是神经网络算法最底层的算法支持。

数学模型:

z=w1x1+w2x2+...+wnxn+bz = w_1x_1 + w_2x_2 +...+ w_nx_n + bz=w1x1+w2x2+...+wnxn+b

其中bbb称为感知机的偏置(Bias),一维向量w=[w1,w2,...,wn]w = [w_1,w_2,...,w_n]w=[w1,w2,...,wn]称为感知机的权值(weight),z称为感知机的净活性值。

上式写成向量形式:

z=wTx+bz = w^Tx + bz=wTx+b

通过在线性模型后添加激活函数后得到活性值(Activation) a:

$a = \sigma(z) = \sigma(w^Tx + b) $

其中激活函数可以是阶跃函数(Step function) a={1wTx+b≥00wTx+b<0a = \begin{cases} 1 & w^Tx + b \ge 0 \\ 0 & w^Tx + b < 0 \end{cases}a={10wTx+b0wTx+b<0 ,也可以是符号函数(Sign function) a={1wTx+b≥0−1wTx+b<0a = \begin{cases} 1 & w^Tx + b \ge 0 \\ -1 & w^Tx + b < 0 \end{cases}a={11wTx+b0wTx+b<0

添加激活函数后,感知机可以用来完成二分类任务。阶跃函数和符号函数在 z=0 处是不连续的,其他位置导数为 0,无法利用梯度下降算法进行参数优化。

全连接层

感知机模型的不可导特性严重约束了它的潜力,使得它只能解决极其简单的任务。可以将不连续的阶跃激活函数换成了其它平滑连续可导的激活函数,并通过堆叠多个网络层来增强网络的表达能力。

下面构成 3 输入节点、2 个输出节点的网络层:

第一个输出节点的输出为:o1=σ(w11x1+w21x2+w31x3+b1)o_1 = \sigma(w_{11}x_1+w_{21}x_2+w_{31}x_3 + b1)o1=σ(w11x1+w21x2+w31x3+b1)

第二个输出节点的输出为:o2=σ(w12x1+w22x2+w32x3+b2)o_2 = \sigma(w_{12}x_1+w_{22}x_2+w_{32}x_3 + b2)o2=σ(w12x1+w22x2+w32x3+b2)

输出向量为o=[o1,o2]o = [o_1,o_2]o=[o1,o2],整个网络层可以通过矩阵关系式表达:[o1o2]=[x1x2x3]@[w11w12w21w22w31w32]+[b1b2]\begin{bmatrix}o_1&o_2\end{bmatrix} = \begin{bmatrix}x_1 &x_2 &x_3\end{bmatrix}@\begin{bmatrix}w_{11}&w_{12}\\w_{21}&w_{22}\\w_{31}&w_{32} \end{bmatrix} +\begin{bmatrix}b_1&b_2\end{bmatrix}[o1o2]=[x1x2x3]@ w11w21w31w12w22w32 +[b1b2]

简化一下:O = X@W+b

在这里插入图片描述

由于每个输出节点与全部的输入节点相连接,这种网络层称为全连接层(Fully-connected Layer)。

X是输入矩阵, shape 定义为[n,din][n,d_{in}][n,din],𝑏为样本数量,dind_{in}din为输入节点数;𝑾矩阵叫做全连接层的权值矩阵,shape定义为[din,dout][d_{in},d_{out}][din,dout];𝒃向量叫做全连接层的偏置向量,shape 定义为[dout][d_{out}][dout]

张量方式实现

通过张量实现全连接层,只需要定义好权值张量W和偏置张量b,并利用批量矩阵相乘函数 tf.matmul()即可完成网络层的计算。

import tensorflow as tf
# 要求输入特征数为784,输出节点数为256
x = tf.random.normal([2,784]) # 输入X矩阵,2个样本,输入特征长度784
w1 = tf.Variable(tf.random.truncated_normal([784,256],stddev=0.1)) # 权值矩阵𝑾,shape 为[784,256],并采用正态分布初始化𝑾
b1 = tf.Variable(tf.zeros([256])) # 偏置向量𝒃的 shape 定义为[256]
o1 = tf.matmul(x,w1) +b1 # 最终全连接层的输出𝑶的 shape 为[2,256]
o1 = tf.nn.relu(o1)

层方式实现

tensorlow 中有更高层、使用更方便的层实现方式:layers.Dense(units, activation),不用上面那么麻烦。

通过 layer.Dense 类,只需要指定输出节点数 Units 和激活函数类型 activation 即可。

from tensorflow.keras import layers
fc = layers.Dense(256,activation=tf.nn.relu) # 创建全连接层,指定输出节点数和激活函数
h1 = fc(x) # 通过 fc 类实例完成一次全连接层的计算,返回输出张量
print(fc.kernel) # 获取 Dense 类的权值矩阵
print(fc.bias) # 获取 Dense 类的偏置向量

神经网络

通过层层堆叠全连接层,保证前一层的输出节点数与当前层的输入节点数匹配,即可堆叠出任意层数的网络。而这种由神经元相互连接而成的网络叫做神经网络。

在这里插入图片描述

上图中通过堆叠 4 个全连接层,可以获得层数为 4 的神经网络,由于每层均为全连接层,称为全连接网络。其中第 1~3 个全连接层在网络中间,称之为隐藏层 1、2、3,最后一个全连接层的输出作为网络的输出,称为输出层。隐藏层 1、2、3 的输出节点数分别为[256,128,64],输出层的输出节点数为 10。

网络的结构配置等超参数可以按着经验法则自由设置,只需要遵循少量的约束即可:

  • 隐藏层 1 的输入节点数需和数据的实际特征长度匹配
  • 每层的输入层节点数与上一层输出节点数匹配
  • 输出层的激活函数和节点数需要根据任务的具体设定进行设计

隐层的shape设置可以通过AutoML 技术搜索得到(类似sklearn中的网格搜索)。

张量方式实现

对于多层神经网络需要分别定义各层的权值矩阵𝑾和偏置向量𝒃。有多少个全连接层,则需要相应地定义数量相当的𝑾和𝒃,并且每层的参数只能用于对应的层,不能混淆使用。

x = tf.random.normal([4,28*28])

# 隐藏层 1 张量
w1 = tf.Variable(tf.random.truncated_normal([784,256],stddev=0.1)) 
b1 = tf.Variable(tf.zeros([256]))
# 隐藏层 2 张量
w2 = tf.Variable(tf.random.truncated_normal([256,128],stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
# 隐藏层 3 张量
w3 = tf.Variable(tf.random.truncated_normal([128,64],stddev=0.1))
b3 = tf.Variable(tf.zeros([64]))
# 隐藏层 4 张量
w4 = tf.Variable(tf.random.truncated_normal([64,10],stddev=0.1))
b4 = tf.Variable(tf.zeros([10]))

with tf.GradientTape() as tape: #  梯度记录器
    # 隐藏层 1 前向计算
    h1 = x@w1 + tf.broadcast_to(b1,[x.shape[0],256]) 
    h1 = tf.nn.relu(h1)
    # 隐藏层 2 前向计算
    h2 = h1@w2+b2
    h2 = tf.nn.relu(h2)
    # 隐藏层 3 前向计算
    h3 = h2@w3 + b3
    h3 = tf.nn.relu(h3)
    # 输出层前向计算 ,最后一层是否需要添加激活函数通常视具体的任务而定
    h4 = h3@w4+b4
del tape

在使用 TensorFlow 自动求导功能计算梯度时,需要将前向计算过程放置在tf.GradientTape()环境中,利用 GradientTape 对象的 gradient()方法自动求解参数的梯度,并利用 optimizers 对象更新参数。

层方式实现

对于常规的网络层,通过层方式实现起来更加简洁高效。

from tensorflow.keras import layers

fc1 = layers.Dense(256, activation=tf.nn.relu) # 隐藏层 1
fc2 = layers.Dense(128, activation=tf.nn.relu) # 隐藏层 2
fc3 = layers.Dense(64, activation=tf.nn.relu) # 隐藏层 3
fc4 = layers.Dense(10, activation=None) # 输出层

x = tf.random.normal([4,28*28])
h1 = fc1(x) # 通过隐藏层 1 得到输出
h2 = fc2(h1) # 通过隐藏层 2 得到输出
h3 = fc3(h2) # 通过隐藏层 3 得到输出
h4 = fc4(h3) # 通过输出层得到网络输出

对于这种数据依次向前传播的网络,也可以通过 Sequential 容器封装成一个网络大类对象,调用大类的前向计算函数一次即可完成所有层的前向计算,使用起来更加方便

from tensorflow.keras import Sequential

# 通过 Sequential 容器封装为一个网络类
model = Sequential([
	layers.Dense(256, activation=tf.nn.relu) , # 创建隐藏层 1
    layers.Dense(128, activation=tf.nn.relu) , # 创建隐藏层 2
    layers.Dense(64, activation=tf.nn.relu) , # 创建隐藏层 3
    layers.Dense(10, activation=None) , # 创建输出层
])
out = model(x) # 前向计算得到输出

优化目标

把神经网络从输入到输出的计算过程叫做前向传播或前向计算。前向传播过程是数据张量从第一层流动至输出层的过程,即从输入数据开始,途径每个隐藏层,直至得到输出并计算误差。

前向传播的最后一步就是完成误差的计算:L=g(fθ(x),y)\mathcal{L} = g(f_{\theta}(x),y)L=g(fθ(x),y) ,其中fθ(.)f_{\theta}(.)fθ(.)代表了利用θ\thetaθ参数化的神经网络模型,g(.)g(.)g(.)称为误差函数,用来描述当前网络的预测值fθ(x)f_{\theta}(x)fθ(x)与真实标签yyy之间的差距度量,比如常用的均方差误差函数。L\mathcal{L}L 称为网络的误差(loss),一般为标量。

最终目的是希望通过在训练集Dtrain\mathbb{D}^{train}Dtrain上面学习到一组参数θ\thetaθ使得训练的误差ℒ最小:θ∗=argmin⏟θg(fθ(x),y),x∈Dtrain\theta^* = \begin{matrix} \underbrace {arg min} \\ \theta\end{matrix}g(f_{\theta}(x),y),x \in \mathbb{D}^{train}θ= argminθg(fθ(x),y),xDtrain

上述的最小化优化问题一般采用误差反向传播算法(BP)来求解网络参数θ\thetaθ的梯度信息,并利用梯度下降(GD)算法迭代更新参数:θ′=θ−η.ΔθL\theta^\prime = \theta - \eta . \Delta_{\theta}\mathcal{L}θ=θη.ΔθL

其中η\etaη为学习率(是个常量,学是机器学习中控制模型参数更新步长的关键超参数‌,直接影响模型训练的收敛速度和最终性能)

从另一个角度来理解神经网络,它完成的是特征的维度变换的功能,原始的特征通常具有较高的维度,包含了很多底层特征及无用信息,通过神经网络的层层特征变换,将较高的维度降维到较低的维度,此时的特征一般包含了与任务强相关的高层抽象特征信息,通过对这些特征进行简单的逻辑判定即可完成特定的任务,如图片的分类

网络的参数量是衡量网络规模的重要指标,计算全连接层的参数量的方式:𝑑in.𝑑out+𝑑out𝑑_{in} . 𝑑_{out} + 𝑑_{out}din.dout+dout。比如784 → 256 → 128 → 64 → 10,总参数量的计算表达式为:256 ∙ 784 + 256 + 128 ∙ 256 + 128 + 64 ∙ 128 + 64 + 10 ∙ 64 + 10 = 242762

激活函数

Sigmoid

Sigmoid 函数也叫 Logistic 函数,定义为:Sigmoid(x)≜1/(1+e−x)Sigmoid(x) \triangleq 1/(1+e^{-x})Sigmoid(x)1/(1+ex)

它的一个优良特性就是能够把 x∈Rx \in RxR 的输入“压缩”到 x∈(0,1)x \in (0,1)x(0,1)区间,区间的数值常用来表示以下意义:

  • 概率分布 (0,1)区间的输出和概率的分布范围[0,1]契合,可以通过 Sigmoid 函数将输出转译为概率输出
  • 信号强度 一般可以将 0~1 理解为某种信号的强度,如像素的颜色强度,1 代表当前通道颜色最强,0 代表当前通道无颜色;抑或代表门控值(Gate)的强度,1 代表当前门控全部开放,0 代表门控关闭

Sigmoid 函数连续可导,可以直接利用梯度下降算法优化网络参数,应用的非常广泛。

x = tf.linspace(-6.,6.,120)
x_sig = tf.nn.sigmoid(x)

import matplotlib.pyplot as plt
import numpy as np

fig ,(ax1,ax2) = plt.subplots(1,2,figsize=(8,4))

ax2.plot(np.arange(-6,6,0.1),x)   
ax1.plot(np.arange(-6,6,0.1),x_sig.numpy())
# 可以看到,向量中元素值的范围由[−6,6]映射到(0,1)的区间。

Sigmoid 函数在输入值较大或较小时容易出现梯度值接近于 0 的现象,称为梯度弥散现象。出现梯度弥散现象时,网络参数长时间得不到更新,导致训练不收敛或停滞不动的现象发生。

ReLU

在 ReLU(REctified Linear Unit,修正线性单元)激活函数定义为:ReLU(x)≜max(0,x)ReLU(x) \triangleq max(0,x)ReLU(x)max(0,x)

ReLU 对小于 0 的值全部抑制为 0;对于正数则直接输出

x_relu = tf.nn.relu(x)

fig ,(ax1,ax2) = plt.subplots(1,2,figsize=(8,4))

ax2.plot(np.arange(-6,6,0.1),x)
ax1.plot(np.arange(-6,6,0.1),x_relu.numpy())
# 经过 ReLU 激活函数后,负数全部抑制为 0,正数得以保留。

ReLU 函数在x < 0时导数值恒为 0,也可能会造成梯度弥散现象

LeakyReLU

为了克服梯度弥散现象,LeakyReLU 函数被提出,表达式为:LeakyReLU≜{xx≥0pxx<0LeakyReLU \triangleq \begin{cases}x & x\ge 0 \\ px & x<0 \end{cases}LeakyReLU{xpxx0x<0

其中p为用户自行设置的某较小数值的超参数,如 0.02 等。当p= 0时,LeayReLU 函数退化为 ReLU 函数;当p ≠ 0时,x < 0处能够获得较小的导数值p,从而避免出现梯度弥散现象。

x_leaky = tf.nn.leaky_relu(x,alpha=0.1)

fig ,(ax1,ax2) = plt.subplots(1,2,figsize=(8,4))

ax2.plot(np.arange(-6,6,0.1),x)
ax1.plot(np.arange(-6,6,0.1),x_leaky.numpy())

其中 alpha (sklearn的神经网络中也有这个参数)参数代表p。tf.nn.leaky_relu 对应的类为 layers.LeakyReLU,可以通过LeakyReLU(alpha)创建 LeakyReLU 网络层,并设置p参数,像 Dense 层一样将 LeakyReLU层放置在网络的合适位置。

Tanh

Tanh函数能够把 x∈Rx \in RxR 的输入“压缩”到 x∈(−1,1)x \in (-1,1)x(1,1)区间,定义为:tanh(x)=(ex−e−x)/(ex+e−x)=2⋅sigmoid(2x)−1tanh(x) = (e^x-e^{-x})/(e^x+e^{-x}) = 2 \cdot sigmoid(2x)-1tanh(x)=(exex)/(ex+ex)=2sigmoid(2x)1

x_tanh = tf.nn.tanh(x)
plt.plot(np.arange(-6,6,0.1),x_tanh.numpy())
plt.axhline(0,color='gray',linewidth=0.5)
plt.axvline(0,color='gray',linewidth=0.5)
# 可以看到向量元素值的范围被映射到(−1,1)之间

输出层设计

输出层和所有的隐藏层一样,完成维度变换、特征提取的功能,需要根据具体的任务场景来决定是否使用激活函数,以及使用什么类型的激活函数等。

常见的几种输出类型包括:

  • oi∈Rdo_i \in R^doiRd :输出属于整个实数空间,或者某段普通的实数空间,比如函数值趋势的预测,年龄的预测问题等。
  • oi∈[0,1]o_i \in [0,1]oi[0,1]:输出值特别地落在[0, 1]的区间,如图片生成,图片像素值一般用[0, 1]区间的值表示;或者二分类问题的概率,如硬币正反面的概率预测问题。
  • oi∈[0,1],∑ioi=1o_i \in [0,1],\sum_i o_i = 1oi[0,1],ioi=1​:输出值落在[0,1]的区间,并且所有输出值之和为 1,常见的如多分类问题。
  • oi∈[−1,1]o_i \in [-1,1]oi[1,1]:输出值在[-1, 1]之间

普通实数空间

这一类问题比较普遍,像年龄的预测、房价的预测等都属于整个或者部分连续的实数空间,输出层可以不加激活函数(类似于sklearn中的回归)。误差的计算直接基于最后一层的输出o和真实值y进行计算,如采用均方差误差函数度量输出值o与真实值y之间的距离:

L=g(o,y)\mathcal{L} = g(o,y)L=g(o,y)

其中g代表了某个具体的误差计算函数,例如 MSE 等。

[0,1]区间

输出值属于[0,1]区间也比较常见,比如图片的生成、二分类问题等。

一般会将图片的像素值归一化到[0,1]区间,Sigmoid 函数刚好具有此功能。

对于二分类问题,输出层可以只设置一个节点,表示某个事件 A 发生的概率𝑃(A|x),x为网络输入。利用网络的输出标量o表示正面事件出现的概率,那么反面事件出现的概率即为1 − o。此时只需要在输出层的净活性值z后添加 Sigmoid 函数即可将输出转译为概率值。

除了可以使用单个输出节点表示事件 A 发生的概率𝑃(A|x)外,还可以分别预测𝑃(A|x)和𝑃(A̅|x)(A̅表示事件 A 的对立事件),并满足约束P(A∣x)+P(A‾∣x)=1P(A|x) + P(\overline{A}|x) = 1P(Ax)+P(Ax)=1

[0,1]区间,和为1

输出oi∈[0,1]o_i \in [0,1]oi[0,1] ,且所有的输出值的和为1,这种设定以多分类问题最为常见(也适用于二分类)。输出层的每个输出节点代表了一种类别,且满足所有节点(类别)之间的概率和为1。

可以通过在输出层添加Softmax函数,Softmax 函数不仅可以将输出值映射到[0,1]区间,还满足所有的输出值之和为 1 的特性。Softmax函数定义为:Softmax(zi)≜ezi/∑j=1doutezjSoftmax(z_i) \triangleq e^{z_i}/\sum^{d_{out}}_{j=1}e^{z_j}Softmax(zi)ezi/j=1doutezj

在 TensorFlow 中,可以通过 tf.nn.softmax 实现 Softmax 函数:

z = tf.constant([2.,1.,0.1])
tf.nn.softmax(z)

与 Dense 层类似,Softmax 函数也可以作为网络层类使用,通过类 layers.Softmax(axis=-1)可以方便添加 Softmax 层,其中 axis 参数指定需要进行计算的维度。

在 Softmax 函数的数值计算过程中,容易因输入值偏大发生数值溢出现象:在计算交叉熵时,也会出现数值溢出的问题。为了数值计算的稳定性,TensorFlow 中提供了一个统一的接口,将 Softmax 与交叉熵损失函数同时实现,同时也处理了数值不稳定的异常,一般推荐使用这些接口函数,避免分开使用 Softmax 函数与交叉熵损失函数。

函数式接口为tf.keras.losses.categorical_crossentropy(y_true, y_pred, from_logits=False),其中 y_true 代表了One-hot 编码后的真实标签,y_pred 表示网络的预测值,当 from_logits 设置为 True 时,y_pred 表示须为未经过 Softmax 函数的变量 z;当 from_logits 设置为 False 时,y_pred 表示为经过 Softmax 函数的输出。为了数值计算稳定性,一般设置 from_logits 为 True,此时tf.keras.losses.categorical_crossentropy 将在内部进行 Softmax 函数计算,所以不需要在模型中显式调用 Softmax 函数:

z = tf.random.normal([2,10])
y_onehot = tf.constant([1,3])
y_onehot = tf.one_hot(y_onehot,depth=10)
loss = keras.losses.categorical_crossentropy(y_onehot,z,from_logits=True)
loss = tf.reduce_mean(loss) # 计算平均交叉熵损失

也可以利用 losses.CategoricalCrossentropy(from_logits)类方式同时实现 Softmax 与交叉熵损失函数的计算:

criteon = keras.losses.CategoricalCrossentropy(from_logits=True)
loss = criteon(y_onehot,z) # 计算损失

[-1,1]

如果希望输出值的范围分布在(−1,1)区间,可以简单地使用 tanh 激活函数:

x = tf.linspace(-6,6,120)
tf.tanh(x) # tanh 激活函数

误差计算

在搭建完模型结构后,下一步就是选择合适的误差函数来计算误差。常见的误差函数有均方差、交叉熵、KL 散度、Hinge Loss 函数等,其中均方差函数和交叉熵函数在深度学习中比较常见,均方差函数主要用于回归问题,交叉熵函数主要用于分类问题。

均方差误差函数

均方差(MSE)误差函数把输出向量和真实向量映射到笛卡尔坐标系的两个点上,通过计算这两个点之间的欧式距离(准确地说是欧式距离的平方)来衡量两个向量之间的差距:MSE(y,0)≜1dout∑i=1dout(yi−oi)2MSE(y,0)\triangleq \frac{1}{d_{out}}\sum_{i=1}^{d_{out}}(y_i-o_i)^2MSE(y,0)dout1i=1dout(yioi)2

MSE 误差函数的值总是大于等于 0,当 MSE 函数达到最小值 0 时,输出等于真实标签,此时神经网络的参数达到最优状态。

均方差误差函数广泛应用在回归问题中,实际上,分类问题中也可以应用均方差误差函数。在 TensorFlow 中,可以通过函数方式或层方式实现 MSE 误差计算。

o = tf.random.normal([2,10])
y_onehot = tf.constant([1,3])
y_onehot = tf.one_hot(y_onehot,depth=10)
loss = keras.losses.MSE(y_onehot,o) #计算每个样本的均方差
loss = tf.reduce_mean(loss) # 收敛计算整体的均方误差

交叉熵误差函数

信息学中熵(Entropy)的概念:将热力学中熵的概念引入到信息论中,用来衡量信息的不确定度。熵在信息学科中也叫信息熵,或者香农熵。熵越大,代表不确定性越大,信息量也就越大。

某个分布𝑃(𝑖)的熵定义为:H(P)≜−∑iP(i)long2P(i)H(P) \triangleq - \sum_{i}P(i)long_2P(i)H(P)iP(i)long2P(i) ;实际上,𝐻(𝑃)也可以使用其他底数的log函数计算。

由于𝑃(𝑖)∈[0,1],long2𝑃(𝑖)≤0𝑃(𝑖) \in [0,1], long_2𝑃(𝑖) ≤ 0P(i)[0,1],long2P(i)0,因此熵𝐻(𝑃)总是大于等于 0。当熵取得最小值 0 时,不确定性为 0。分类问题的 One-hot 编码的分布就是熵为 0 的典型例子。在 TensorFlow 中间,我们可以利用 tf.math.log 来计算熵。

……

最小化交叉熵损失函数的过程是最大化正确类别的预测概率的过程

神经网络类型

卷积神经网络

全连接层在处理高维度的图片、视频数据时往往出现网络参数量巨大,训练非常困难的问题。通过利用局部相关性和权值共享的思想,Yann Lecun 在 1986 年提出了卷积神经网络(CNN)。随着深度学习的兴盛,卷积神经网络在计算机视觉中的表现大大地超越了其它算法模型,呈现统治计算机视觉领域之势。

循环神经网络

循环神经网络(RNN),非常擅长处理序列信号(序列信号也是非常常见的一种数据类型,其中一个最具代表性的序列信号就是文本数据),在自然语言处理中得到了广泛的应用。

注意力(机制)网络

RNN 并不是自然语言处理的最终解决方案,注意力机制的提出,克服了 RNN 训练不稳定、难以并行化等缺陷,在自然语言处理和图片生成等领域中逐渐崭露头角。

图卷积神经网络

图片、文本等数据具有规则的空间、时间结构,称为 Euclidean Data(欧几里德数据)。卷积神经网络和循环神经网络被证明非常擅长处理这种类型的数据。而像类似于社交网络、通信网络、蛋白质分子结构等一系列的不规则空间拓扑结构的数据,它们显得力不从心。Thomas Kipf 等人基于前人在一阶近似的谱卷积算法上提出了图卷积网络(GCN)。GCN 算法实现简单,从空间一阶邻居信息聚合的角度也能直观地理解,在半监督任务上取得了不错效果。


网站公告

今日签到

点亮在社区的每一天
去签到