⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议。
Skip to content

第23章前馈神经网络

习题23.1

  构造前馈神经网络实现逻辑表达式XNOR,使用S型函数为激活函数。

解答:

解答思路:

  1. 给出同或函数(XNOR)的输入和输出
  2. 用神经网络实现的门表示XNOR
  3. 设计神经网络实现XNOR
  4. 自编程实现二层前馈神经网络表示XNOR

解答步骤:

第1步: 给出同或函数(XNOR)的输入和输出

  对于同或函数(XNOR),全部的输入与对应的输出如下:

x1x2y=x1x2
001
010
100
111

第2步: 用神经网络实现的门表示XNOR

  XNOR(同或门)和XOR(异或门)由于都是线性不可分的,不能由一层神经网络实现,但它们可由一层神经网络实现的门组合实现。

  一层神经网络可实现的门包括AND(与门,用x1x2表示)、NOR(或非门,用x1x2表示)、OR(或门,用x1x2表示)等。

  同或函数可表示:

x1x2=(x1x2)x1x2

第3步: 设计神经网络实现XNOR

  根据书中第23.1.1节的S型函数的定义:

S型函数(sigmoid function)又称为逻辑斯谛函数(logistic function),是定义式如下的非线性函数:

(23.14)a(z)=σ(z)=11+ez

其中,z是自变量或输入,σ(z)是因变量或输出。函数的定义域为(,),值域为(0,1)

  可知:

sigmoid(10)1sigmoid(10)0

  可设计如下二层前馈神经网络表示XNOR:

h1=x1x2h2=x1x2y=h1h2

23-1.png

  根据书中第23.1.1节的二层前馈神经网络的矩阵表示:

二层前馈神经网络也可以用矩阵来表示,简称矩阵表示:

h(1)=f(1)(x)=a(z(1))=a(W(1)Tx+b(1))y=f(2)(h(1))=g(z(2))=g(W(2)Th+b(2))

  其中,

x=[x1x2]h=[h1h2]W(1)=[20202020]W(2)=[2020]b(1)=[3010]b(2)=[10]

第4步: 自编程实现二层前馈神经网络表示XNOR

python
import numpy as np

# 定义网络的权重W和偏置b
W1 = np.array([[20, -20], [20, -20]])
b1 = np.array([[-30], [10]])
W2 = np.array([[20], [20]])
b2 = np.array([[-10]])

def sigmoid(x):
    s = 1 / (1 + np.exp(-x))
    return s

def dnn_xnor(X):
    Z1 = W1.T.dot(X) + b1
    H = sigmoid(Z1)

    Z2 = W2.T.dot(H) + b2
    Y = sigmoid(Z2)
    
    return Y
python
X = np.array([[0, 0, 1, 1],
              [0, 1, 0, 1]])

with np.printoptions(suppress=True):
    result = dnn_xnor(X)
    print(result)
[[0.99995456 0.00004548 0.00004548 0.99995456]]

习题23.2

  写出多标签分类学习中的损失函数以及损失函数对输出变量的导数。

解答:

解答思路:

  1. 给出前馈神经网络学习在多标签分类时的模型
  2. 写出多标签分类学习中的损失函数
  3. 求损失函数对输出变量的导数

解答步骤:

第1步: 前馈神经网络学习在多标签分类时的模型

  根据书中第23.1.1节的前馈神经网络学习在多标签分类时的模型:

  用于多标签分类(multi-label classification)。神经网络的输出层有 l 个神经元,每个神经元的输出是一个概率值。神经网络表示为 p=[P(yk=1|x)]=f(x),其中yk{0,1},k=1,2,,l,满足条件

0<P(yk=1|x)<1, P(yk=1|x)+P(yk=0|x)=1, k=1,2,,l

[P(y1=1|x),P(y2=1|x),,P(yl=1|x)] 表示输入 x 分别属于1个类别的概率。预测时给定输入 x,计算其属于各个类别的概率。将输入分到概率大于0.5的所有类别,这时输入可以被分到多个类别(赋予多个标签)。

第2步: 写出多标签分类学习中的损失函数

  多标签分类时,输出层有l个神经元,每个神经元输出是一个概率值,表示属于各标签的概率。多标签分类学习中的损失函数通常为二元交叉熵(Binary Cross-Entropy, BCE)损失函数:

BCELoss=1Ni=1Nk=1l[yiklog(pik)+(1yik)log(1pik)]

其中,N是样本的数量,l是标签的数量,yik是第i个样本的第k个标签的真实值,pik是第i样本属于第k个标签的预测概率。

第3步: 求损失函数对输出变量的导数

  输出层的输出变量是经过S型激活函数变换后的值,即pik,则BCELoss对pik求导:

BCELosspik=1N(yikpik1yik1pik)=1N(yikyikpikpik+yikpikpik(1pik))=pikyikNpik(1pik)

即:

BCELosspik=pikyikNpik(1pik)

习题23.3

  实现前馈神经网络的反向传播算法,使用MNIST数据构建手写数字识别网络。

解答:

解答思路:

  1. 给出MNIST手写数字识别网络
  2. 给出前馈神经网络的反向传播算法
  3. 自编程实现使用MNIST数据集构建手写数字识别网络

解答步骤:

第1步: MNIST手写数据识别网络

  根据书中第23章例23.4给出MNIST手写数字识别网络:

  MNIST是一个机器学习标准数据集。每一个样本由一个像素为28 × 28 的手写数字灰度图像以及的0~9之间的标签组成,像素取值为0~255。
  可以构建图23.14所示的前馈神经网络对MNIST的手写数字进行识别,是一个多标签分类模型。输入层是一个28×28=784维向量,取自一个图像,每一维对应一个像素。第一层和第二层是隐层,各自有100个神经元和50个神经元,其激活函数都是S型函数。第三层是输出层,有10个神经元,其激活函数也是S型函数。给定一个图像,神经网络可以计算出其属于0~9类的概率,将图像赋予概率最大的标签。

第2步:前馈神经网络的反向传播算法

  根据书中第23.2.3节的算法23.3的前馈神经网络的反向传播算法:

算法23.3 (前馈神经网络的反向传播算法)
输入:神经网络f(x;θ),参数向量θ,一个样本(x,y)
输出:更新的参数向量θ
超参数:学习率η

  1. 正向传播,得到各层输出h(1),h(2),,h(s)
h(0)=x

For t=1,2,,s,do {

z(t)=W(t)h(t1)+b(t)h(t)=a(z(t))

} 2. 反向传播,得到各层误差δ(s),,δ(2),δ(1),同时计算各层的梯度,更新各层的参数。
计算输出层的误差

δ(s)=h(s)y

For t=s,,2,1,do {
  计算第t层的梯度

W(t)L=δ(t)h(t1)Tb(t)L=δ(t)

  根据梯度下降公式更新第t层的参数

W(t)W(t)ηW(t)Lb(t)b(t)ηb(t)L

   If (t>1) {
     将第t层的误差传到第t1

δ(t1)=az(t1)(W(t)Tδ(t))

  }
} 3. 返回更新的参数向量

第3步:自编程实现使用MNIST数据集构建手写数字识别网络

python
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelBinarizer

from tqdm import tqdm

np.random.seed(2023)
python
class NeuralNetwork:
    def __init__(self, layers, alpha=0.1):
        # 网络层的神经元个数,其中第一层和第二层是隐层
        self.layers = layers
        # 学习率
        self.alpha = alpha
        # 权重
        self.weights = []
        # 偏置
        self.biases = []
        # 初始化权重和偏置
        for i in range(1, len(layers)):
            self.weights.append(np.random.randn(layers[i-1], layers[i]))
            self.biases.append(np.random.randn(layers[i]))

    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def sigmoid_derivative(self, x):
        return x * (1 - x)
    
    def feedforward(self, inputs):
        '''
        (1)正向传播
        '''
        self.activations = [inputs]
        self.weighted_inputs = []
        for i in range(len(self.weights)):
            weighted_input = np.dot(self.activations[-1], self.weights[i]) + self.biases[i]
            self.weighted_inputs.append(weighted_input)
            # 得到各层的输出h
            activation = self.sigmoid(weighted_input)
            self.activations.append(activation)
   
        return self.activations[-1]

    def backpropagate(self, expected):
        '''
        (2)反向传播
        '''
        # 计算各层的误差
        errors = [expected - self.activations[-1]]
        # 计算各层的梯度
        deltas = [errors[-1] * self.sigmoid_derivative(self.activations[-1])]
        
        for i in range(len(self.weights)-1, 0, -1):
            error = deltas[-1].dot(self.weights[i].T)
            errors.append(error)
            delta = errors[-1] * self.sigmoid_derivative(self.activations[i])
            deltas.append(delta)
        deltas.reverse()
        
        for i in range(len(self.weights)):
            # 更新参数
            self.weights[i] += self.alpha * np.array([self.activations[i]]).T.dot(np.array([deltas[i]]))
            self.biases[i] += self.alpha * np.sum(deltas[i], axis=0)

    def train(self, inputs, expected_outputs, epochs):
        for i in tqdm(range(epochs)):
            for j in range(len(inputs)):
                self.feedforward(inputs[j])
                self.backpropagate(expected_outputs[j])
python
# 加载MNIST手写数字数据集
mnist = fetch_openml('mnist_784', parser='auto')
X = mnist.data.astype('float32') / 255.0
y = mnist.target.astype('int')
python
# 划分训练集和测试集
lb = LabelBinarizer()
y = lb.fit_transform(y)
X = np.array(X)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
python
# 训练神经网络,其中第一层和第二层各有100个神经元和50个神经元
nn = NeuralNetwork([784, 100, 50, 10], alpha=0.1)
nn.train(X_train, y_train, epochs=10)
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 10/10 [02:21<00:00, 14.20s/it]
python
# 使用测试集对模型进行评估
correct = 0

for i in range(len(X_test)):
    output = nn.feedforward(X_test[i])
    prediction = np.argmax(output)
    actual = np.argmax(y_test[i])
    if prediction == actual:
        correct += 1

accuracy = correct / len(X_test) * 100
print("Accuracy: {:.2f} %".format(accuracy))
Accuracy: 93.94 %

习题23.4

  写出S型函数的正向传播和反向传播的计算图。

解答:

解答思路:

  1. 写出S型函数的正向传播计算图
  2. 写出S型函数的反向传播计算图

解答步骤:

第1步: 写出S型函数的正向传播计算图

  根据书中第23.2.4节给出了S型函数的计算图例:

  起点z, y是输入变量,终点L是输出变量,中间结点f是中间变量。变量f由S型函数f=σ(z)决定,变量L由损失函数L=l(f,y)决定。

  根据书中第23.2.4节给出的计算图的正向传播:

  在计算图上进行的正向传播就是计算复合函数 L=l(σ(z),y) 的过程。从起点 z,y 开始,顺着有向边,在结点 f,L 依次进行计算,先后得到函数f,L;其中先对 z 计算 f(z) 得到 f,然后对 fy 计算 l(f,y) 得到 L

23-4-1.png

第2步: 写出S型函数的反向传播计算图

  根据书中第23.2.4节给出的计算图的方向传播:

  反向传播就是计算复合函数L=l(σ(z),y) 对变量的梯度的过程。从终止起点 L 出发,逆着有向边,在结点 y,f,z 依次进行,向后得到梯度dLdy,dLdf,dLdz;其中先根据定义计算dLdy,dLdf,再利用链式规则计算dLdz

dLdz=dLdfdLdff(1f)

梯度dLdf在结点 f 的反向传播变为梯度的f(1f) 倍,传到输入结点 z

23-4-2.png

习题23.5

  图23.31是3类分类的正向传播计算图,试写出它的反向传播计算图。这里使用软最大化函数和交叉熵损失。

23-5-1.png

解答:

解答思路:

  1. 根据正向传播计算图,根据链式法则,逐步求导给出各层的梯度
  2. 绘制反向传播的计算图

解答步骤:

第1步:根据正向传播计算图,根据链式法则,逐步求导给出各层的梯度

  见下图中各层的梯度计算结果。

第2步:绘制反向传播的计算图

23-5-2.png

习题23.6

  写出批量归一化的反向传播算法。

解答:

解答思路:

  1. 给出批量归一化算法
  2. 求批量归一化层的梯度
  3. 写出全连接层的梯度
  4. 写出批量归一化的反向传播算法

解答步骤:

第1步:批量归一化算法

  根据书中第23.2.5节的算法23.4的批量归一化算法:

算法23.4(批量归一化)
输入:神经网络结构f(x;θ),训练集,测试样本。
输出:对测试样本的预测值。
超参数:批量容量的大小 n
{
  初始化参数θ,ϕ,其中ϕ={γ(t),β(t)}t=1s1
  For each (批量b) {
    For $t = 1, 2, \cdots, s - 1 $ {
      针对批量b计算第t层净输入的均值u(t)和方差σ2(t)
      进行第t层的批量归一化,得到批量净输入

zj(t)z¯j(t)z~j(t),j=1,2,,n

    }
  }
  构建训练神经网络fTr(x;θ,ϕ)
  使用随机梯度下降法训练fTr(x;θ,ϕ),估计所有参数θ,ϕ
  For t=1,2,,s1 {
    针对所有批量计算t层净输入的期待的均值Eb(u(t))和方差Eb(σ2(t))
    针对测试样本,进行第t层的批量归一化,得到净输入

zj(t)z¯j(t)z~j(t),j=1,2,,n

  }
  构建推理神经网络fInf(x;θ,ϕ)
  输出fInf(x;θ,ϕ)对测试样本的预测值
}

第2步:求批量归一化层的梯度

  根据书中第404页图23.25(批量归一化层的正向计算图),求每一步的反向梯度。

  假设损失函数为L,已知Lz~j的偏导Lz~j,求Lγ,Lβ,Lz¯j,可得:

Lγ=j=1NLz~jz¯jLβ=i=1NLz~jLz¯j=Lz~jγ

  根据书中第403页的公式23.61:

(23.61)z¯j=zjuσ2+ϵ,j=1,2,,n

可将Lzj分成z¯j,μ,σ2三部分进行求解,可得:

Lzj=Lz¯j1σ2+ϵ+Lμμzj+Lσ2σ2zj

  根据书中第403页的公式23.59, 23.60:

(23.59)u=1nj=1nzj(23.60)σ2=1n1j=1n(zju)2

可分别求Lμ,μzj,Lσ2,σ2zj,可得:

Lμ=j=1nLz¯j1σ2+ϵ+Lσ2j=1n2(zjμ)nμzj=1nLσ2=j=1nLz¯j(zjμ)(σ2+ϵ)3/22σ2zj=2(zjμ)nLzj=Lz¯j1σ2+ϵ+Lμ1n+Lσ22(zjμ)n

第3步:结合批量归一化层的梯度,写出全连接层的梯度

t层的误差为:

δj(t)=Lzj(t)

参数W的梯度为:

LW(t)=j=1nLzj(t)zj(t)W(t)=j=1nδj(t)hj(t1)

参数b的梯度为:

Lb(t)=j=1nLzj(t)zj(t)b(t)=j=1nδj(t)

t+1层和t层之间的关系为:

δj(t)=az~j(t)z~j(t)zj(t)W(t+1)δj(t+1)

其中,a表示激活函数。

第4步:写出批量归一化的反向传播算法

输入:神经网络结构f(x;θ,ϕ),训练集(x,y)
输出:参数向量θ,ϕ
超参数:学习率η,批量容量的大小n

算法步骤:

  1. 初始化参数θ,ϕ,其中ϕ={γ(t),β(t)}t=1s1
  2. 计算输出层误差
δ(s)=h(s)y
  1. For each(批量 b) {
      For t=s,,2,1, do {
        计算第t层的梯度 W(t)L,b(t)L,γ(t)L,β(t)L
        更新第t层的参数
W(t)W(t)ηW(t)Lb(t)b(t)ηb(t)Lγ(t)γ(t)ηγ(t)Lβ(t)β(t)ηβ(t)L

    If (t>1) {
      将第t层的参数误差传递到第t1

δj(t1)=az~j(t1)z~j(t1)zj(t1)W(t)δj(t)

    }
  }
}
4. 返回更新的参数向量

习题23.7

  验证逆暂退法和暂退法的等价性。

解答:

解答思路:

  1. 写出暂退法(dropout)计算公式
  2. 写出逆暂退法(inverted dropout)计算公式
  3. 证明两者等价

解答步骤:

第1步: 写出暂退法(dropout)计算公式

  根据书中第23.3.3节的暂退法的描述:

  假设某一隐层的输出向量是 h,误差向量是 δ,该层神经元保留与退出的结果用随机向量 d 表示,其中 d{0,1}m 是维度为 m01 向量,1表示对应的神经元保留,0表示对应的神经元退出。那么,在反向传播算法的每一步,经过保留与退出随机判断后,该层的向量表示变为

(23.69)h~=dh(23.70)δ~=dδ

这里 表示逐元素积,使用 h~ 进行正向传播和使用 δ~ 进行反向传播。注意暂退法中每一步的 d 是随机决定的,各步之间并不相同。
  预测时,对隐层的输出向量进行调整:

(23.71)h~=ph

其中,p 是这层的保留概率。

  由上述可知,暂退法训练时的计算公式:

h~=dh

预测时的计算公式:

h~=ph

第2步: 写出逆暂退法(inverted dropout)计算公式

  根据书中第23.3.3节的逆暂退法的描述:

  为了方便暂退法的实现,常常采用以下等价的逆暂退法。训练时,将隐层的输出变量放大1p 倍:

(23.72)h~=1pdh

预测时,隐层的输出权重保持不变。

  由上述可知,逆暂退法训练时计算公式:

h~=1pdh

预测时,隐层的输出权重保持不变,可得:

h~=h

第3步: 证明两者等价

  假设不考虑神经元的保留或丢弃时,隐层的输出为

y=a(WTx+b)

其中a为激活函数,W为权重参数,b为偏置参数。

  假设某一隐层,使用暂退法训练得到的隐层输出向量为hdrop,逆暂退法训练得到的隐层输出向量为hinv

  根据暂退法训练时的计算公式,暂退法训练时的输出期望为

E[h~drop]=E[dhdrop]

其中 d 表示该层神经元保留与退出的结果,d 为1的概率为 pp 即保留概率,为0的概率为 1pd 符合伯努利分布,所以:

E[h~drop]=phdrop+(1p)0=phdrop

由于同一任务的神经网络训练的期望相同,即E[h~drop]=y,所以:

y=phdrop

即:

hdrop=1py

根据暂退法预测时计算公式,预测时的隐层输出ydrop为:

ydrop=phdrop=p1py=y

  根据逆暂退法训练时的计算公式,逆暂退法训练时的输出期望为:

E[h~inv]=E[1pdhinv]

d同样符合伯努利分布,所以:

E[h~inv]=1pphinv+1p(1p)0=1pphinv=hinv

由于同一任务的神经网络训练的期望相同,即E[h~inv]=y,所以:

y=hinv

  根据逆暂退法预测时计算公式,预测时的隐层输出yinv为:

yinv=hinv=y

由于暂退法和逆暂退法预测时的隐层输出相同,所以暂退法与逆暂退法是等价的。