[神经网络] 一、基本神经网络

作者:解琛
时间:2021 年 2 月 2 日

一、基本神经网络

Machine Learning for Beginners: An Introduction to Neural Networks

一篇文章让你彻底搞懂神经网络:从原理到优化如此简单

Python实现简单的神经网络

【Get深一度】Texlive: latex数学符号表

latex中的希腊字母

MarkDown绘图mermaid流程graph

Python 定义一个 sigmoid 函数并绘制其图形

Layer activation functions

1.1 神经元

神经元是神经网络的基本单元。

神经元先获得输入,在执行特定的数学运算后,产生一个输出。

输出
神经元
输入
y
×
×
+
f(x)
x1
x2

在神经元里,输入总共经历了 3 步数学运算。

  1. 将输入乘以权重(weight);
    • x 1 ← x 1 ω 1 x_1 \leftarrow x_1 \omega_1 x1x1ω1
    • x 2 ← x 2 ω 2 x_2 \leftarrow x_2 \omega_2 x2x2ω2
  2. 将结果相加在加上偏置(bias);
    • x 1 ω 1 + x 2 ω 2 + b x_1 \omega_1 + x_2 \omega_2 + b x1ω1+x2ω2+b
  3. 经过激活函数(activation function)处理得到输出;
    • y = f ( x 1 ω 1 + x 2 ω 2 + b ) y = f(x_1 \omega_1 + x_2 \omega_2 + b) y=f(x1ω1+x2ω2+b)

激活函数的作用是将无限制的输入转换为可预测形式的输出。

一种常用的激活函数是 s i g m o i d sigmoid sigmoid 函数,其图形用 python 代码绘制如下。

# 作者:解琛;
# 功能:绘制 sigmoid 图形;
# 时间:2020 年 2 月 1 日;
# 备注:无。

import matplotlib.pyplot as plt
import numpy as np

# 指定启动参数,设置字体;
# plt.rcParams['font.sans-serif'] = 'SimHei'
# plt.rcParams['axes.unicode_minus'] = False

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

z = np.arange(-9.5, 10, 0.1)
phi_z = sigmoid(z)

plt.figure(figsize=(9, 6))
plt.plot(z, phi_z)
plt.axvline(0, c='black')
plt.axhspan(.0, 1.0, facecolor='0.93', alpha=1.0, ls=':', edgecolor='0.4')
plt.axhline(y=.5, color='.3', alpha=1.0, ls=':')
plt.yticks([.0, .5, 1.0])
plt.yticks([.0, .5, 1.0])
plt.ylim(-.1, 1.1)
plt.title('sigmoid', fontsize=23)
plt.xlabel('z', fontsize=19)
plt.ylabel('$\phi(z)$', fontsize=13)
plt.show()

sigmoid

s i g m o i d sigmoid sigmoid 函数的输出介于 0 和 1。

我们可以理解为它把 ( − ∞ , + ∞ ) (−∞,+∞) (,+) 范围内的数压缩到 ( 0 , 1 ) (0, 1) (0,1) 以内。

正值越大输出越接近 1,负向数值越大输出越接近 0。

我们来写一个神经元,向里面传递 2 个输入参数:[2, 3]。

# 作者:解琛;
# 功能:基本神经元;
# 时间:2020 年 1 月 31 日;
# 备注:无。

import numpy as np

def sigmoid(x):
    '''
        定义激活函数:sigmoid 
            f(x) = 1 / (1 + e^(-x));
    '''
    return 1 / (1 + np.exp(-x))

class Neuron():
    def __init__(self, weights, bias):
        '''构造函数;'''
        self.weights = weights  # 权重;
        self.bias = bias        # 误差(偏置);

    def feedforward(self, inputs):
        '''向前传递;'''
        # weights 点乘 inputs + bias;
        total = np.dot(self.weights, inputs) + self.bias
        
        # 使用 sigmoid 函数包裹;
        return sigmoid(total)

# 构造一个神经元 nn;
weights = np.array([1, 0]) 
bias = 4
nn = Neuron(weights, bias)

# 将输入的数据传入神经元;
x = np.array([2, 3]) 
output = nn.feedforward(x)

print(output)

神经元在初始化时,权重和偏置取如下数值。

w = [0, 1]
b = 4 

w = [ 0 , 1 ] w = [0, 1] w=[0,1] ω 1 = 0 \omega_1 = 0 ω1=0 ω 2 = 1 \omega_2 = 1 ω2=1 的向量形式写法。

给神经元一个输入 x = [ 2 , 3 ] x = [2, 3] x=[2,3]

可以用向量点积的形式把神经元的输出计算出来。

ω x + b = x 1 ω 1 + x 2 ω 2 + b { \omega x + b = x_1 \omega_1 + x_2 \omega_2 + b } ωx+b=x1ω1+x2ω2+b

= 0 × 2 + 1 × 3 + 4 = 7 { = 0 \times 2 + 1 \times 3 + 4 = 7 } =0×2+1×3+4=7

∴ y = f ( ω X + b ) { \therefore y = f(\omega X + b) } y=f(ωX+b)

f ( 7 ) = 0.999 { f(7) = 0.999 } f(7)=0.999

1.2 神经网络

神经网络就是把一堆神经元连接在一起。

output layer
hidden layer
input layer
o1
h1
h2
x1
x2

我们搭建一个神经网络,含有 2 个输入、一个包含 2 个神经元的隐藏层(h1 和 h2)、包含 1 个神经元的输出层 o1。

隐藏层是夹在输入输入层和输出层之间的部分,一个神经网络可以有多个隐藏层。

把神经元的输入向前传递获得输出的过程称为 前馈 (feedforward)

实现的代码如下。

# 作者:解琛;
# 功能:基本神经网络;
# 时间:2020 年 1 月 31 日;
# 备注:神经网络就是把一堆神经元连接在一起。

import numpy as np

class Neuron():
    '''基本神经元;'''

    def __init__(self, weights, bias):
        '''构造函数;'''
        self.weights = weights  # 权重;
        self.bias = bias        # 误差(偏置);

    def sigmoid(self, x):
        '''
            定义激活函数:sigmoid 
                f(x) = 1 / (1 + e^(-x));
        '''
        return 1 / (1 + np.exp(-x))

    def feedforward(self, inputs):
        '''向前传递;'''
        # weights 点乘 inputs + bias;
        total = np.dot(self.weights, inputs) + self.bias
        
        # 使用 sigmoid 函数包裹;
        return self.sigmoid(total)


class NeuralNetworks():
    '''
        神经网络;
            2 个输入;
            1 个包含 2 个神经元的隐藏层;
            1 个输出层。
    '''

    def __init__(self):
        ''' 构造函数,在对象被创建时执行;'''
        weights = np.array([0, 1])  # 权重初始化,这里是 2 个输入,所以是 [0, 1];
        bias = 0                    # 偏置初始化为 0;

        # 定义 1 个包含 2 个神经元的隐藏层;
        self.hide1 = Neuron(weights, bias)
        self.hide2 = Neuron(weights, bias)

        # 定义一个神经元的输出层;
        self.output = Neuron(weights, bias)

    def feedforward(self, x):
        '''向前传递;'''
        # 将输入传入 2 个隐藏层的神经元中;
        out_hide1 = self.hide1.feedforward(x)
        out_hide2 = self.hide1.feedforward(x)

        # 将 2 个隐藏层的输出传入输出层中,得到传递结果;
        out_output = self.output.feedforward(np.array([out_hide1, out_hide2]))
        return out_output

# 定义并初始化一个神经网络;
nn = NeuralNetworks()

# 定义输入的数据;
x = np.array([2, 3])

# 将输入传入神经网络,获取神经网络的输出结果;
output = nn.feedforward(x)

print(output)  # 0.7216325609518421

假设上面的网络里所有神经元都具有相同的权重 w = [ 0 , 1 ] {w=[0, 1]} w=[0,1] 和偏置 b = 0 { b = 0} b=0,激活函数使用的是 s i g m o i d sigmoid sigmoid,则输出如下。

h 1 = h 2 = f ( ω x + b ) { h_1 = h_2 = f(\omega x + b) } h1=h2=f(ωx+b)

= f ( 0 × 2 + 1 × 3 + 0 ) = f ( 3 ) = 0.9526 { = f(0 \times 2 + 1 \times 3 + 0) = f(3) = 0.9526 } =f(0×2+1×3+0)=f(3)=0.9526

o 1 = f ( ω [ h 1 , h 2 ] + b ) { o_1 = f(\omega [h_1, h_2] + b) } o1=f(ω[h1,h2]+b)

= f ( 0 × h 1 + 1 × h 2 + 0 ) = f ( 0.9526 ) = 0.7216 { = f(0 \times h_1 + 1 \times h_2 + 0) = f(0.9526) = 0.7216 } =f(0×h1+1×h2+0)=f(0.9526)=0.7216

1.3 训练

1.3.1 模型及数据

训练神经网络,实质上是一个模型参数优化的过程。

假设有一个数据集,包含 4 个人的身高、体重和性别,。

NameWeightHeightGender(1:男、0:女)
Alice133651
Bob160720
Charlie152700
Diana120601

为了简便起见,我们将每个人的身高(-135)、体重(-66)减去一个固定数值。

现在我们的目标是训练一个网络,根据体重和身高来推测某人的性别。

搭建的神经网络如下。

output layer
hidden layer
input layer
o1
h1
h2
x1
x2

在训练神经网络之前,我们需要有一个标准来评估结果到底好不好,以便我们进行改进,称其为损失(loss)。

这里用均方误差(mean squared error, MSE)来作为损失函数。

M S E = 1 n ∑ i = 1 n ( y t r u e − y p r e d ) 2 MSE = \frac {1} {n} \sum_{i = 1}^{n} (y_{true} - y_{pred})^2 MSE=n1i=1n(ytrueypred)2

  • n n n 是样本的数量,在上面的数据集中的样本数量是 4;
  • y y y 代表人的性别,男性是 1,女性是 0;
  • y t r u e y_{true} ytrue 是变量的真实值;
  • y p r e d y_{pred} ypred 是变量的预测值。

均方误差就是所有数据方差的平均值,我们将它定义为损失函数。

预测结果越好,损失就越低,训练神经网络就是将损失最小化的过程。

如果上面网络的输出一直是 0,也就是预测所有人都是男性,那么损失的计算结果如下。

Name y t r u e y_{true} ytrue y p r e d y_{pred} ypred ( y t r u e − y p r e d ) 2 (y_{true} - y_{pred})^2 (ytrueypred)2
Alice101
Bob000
Charlie000
Diana101

M S E = 1 4 ( 1 + 0 + 0 + 1 ) = 0.5 {MSE = \frac {1} {4} (1 + 0 + 0 + 1) = 0.5} MSE=41(1+0+0+1)=0.5

计算的方法如下。

def mse_loss(y_true, y_pred):
    # ** 代表乘方运算;
    return ((y_true - y_pred) ** 2).mean()

y_true = np.array([1, 0, 0, 1])
y_pred = np.array([0, 0, 0, 0])

print(mse_loss(y_true, y_pred))     # 0.5;

1.3.2 链式求导

为了简化问题,现在只关注 Alice 的数据。

NameWeight(-135)Height(-66)Gender(1:男、0:女)
Alice-2-11

所以损失函数 MSE 计算得到的数值如下。

M S E = 1 1 ∑ i = 1 1 ( y t r u e − y p r e d ) 2 { MSE = \frac{1}{1} \sum^{1}_{i = 1} (y_{true} - y_{pred})^2 } MSE=11i=11(ytrueypred)2

= ( y t r u e − y p r e d ) 2 { = (y_{true} - y_{pred})^2 } =(ytrueypred)2

= ( 1 − y p r e d ) 2 { = (1 - y_{pred})^2 } =(1ypred)2

y p r e d y_{pred} ypred 预测值是由网络中各个神经元的权重和偏置依次计算得到的。

output layer
hidden layer
input layer
w1
w5
w2
w6
w3
w4
b3
b1
b2
weight
height

所以损失函数实质上是包含多个权重、偏置的多元函数。

L ( ω 1 , ω 2 , ω 3 , ω 4 , ω 5 , ω 6 , b 1 , b 2 , b 3 ) L(\omega_1, \omega_2, \omega_3, \omega_4, \omega_5, \omega_6, b_1, b_2, b_3) L(ω1,ω2,ω3,ω4,ω5,ω6,b1,b2,b3)

现在关注一个问题:如果调整一下 w 1 w_1 w1,损失函数是会变大还是变小?

我们需要知道偏导数 ∂ L ∂ w 1 \frac{\partial L}{\partial w_1} w1L 是正是负才能回答这个问题。

根据链式求导法则。

∂ L ∂ w 1 = ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ w 1 \frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_{pred}} \cdot \frac{\partial y_{pred}}{\partial w_1} w1L=ypredLw1ypred

其中第一项展开如下。

∂ L ∂ y p r e d = ∂ ( 1 − y p r e d ) 2 ∂ y p r e d = − 2 ( 1 − y p r e d ) \frac{\partial L}{\partial y_{pred}} = \frac{\partial (1 - y_{pred})^2}{\partial y_{pred}} = -2(1 - y_{pred}) ypredL=ypred(1ypred)2=2(1ypred)

神经元 h 1 h_1 h1 h 2 h_2 h2 o 1 o_1 o1 的关系如下。

y p r e d = o 1 = f ( ω 5 h 1 + ω 6 h 2 + b 3 ) y_{pred} = o_1 = f(\omega_5 h_1 + \omega_6 h_2 + b_3) ypred=o1=f(ω5h1+ω6h2+b3)

实际只有神经元 h 1 h_1 h1 包含 ω 1 \omega_1 ω1,再次运用链式求导法则。

∂ y p r e d ∂ ω 1 = ∂ y p r e d ∂ h 1 ⋅ ∂ h 1 ∂ ω 1 \frac{\partial y_{pred}}{{\partial \omega_1}} = \frac{\partial y_{pred}}{\partial h_1} \cdot \frac{\partial h_1}{\partial \omega_1} ω1ypred=h1ypredω1h1

∂ y p r e d ∂ h 1 = ω 5 f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) \frac{\partial y_{pred}}{\partial h_1} = \omega_5 f'(\omega_5 h_1 + \omega_6 h_2 + b_3) h1ypred=ω5f(ω5h1+ω6h2+b3)

h 1 = f ( ω 1 x 1 + ω 2 x 2 + b 1 ) h_1 = f(\omega_1 x_1 + \omega_2 x_2 + b_1) h1=f(ω1x1+ω2x2+b1)

∂ h 1 ω 1 = x 1 f ′ ( ω 1 x 1 + ω 2 x 2 + b 1 ) \frac{\partial h_1}{\omega_1} = x_1 f'(\omega_1 x_1 + \omega_2 x_2 + b_1) ω1h1=x1f(ω1x1+ω2x2+b1)

这部分推算中遇到了 2 次 s i g m o i d sigmoid sigmoid 激活函数的导数 f ′ ( x ) f'(x) f(x)

∴ ∂ y p r e d ∂ ω 1 = ω 5 x 1 f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) f ′ ( ω 1 x 1 + ω 2 x 2 + h 1 ) { \therefore \frac {\partial y_{pred}} {\partial \omega_1} = \omega_5 x_1 f'(\omega_5 h_1 + \omega_6 h_2 + b_3) f'(\omega_1 x_1 + \omega_2 x_2 + h_1) } ω1ypred=ω5x1f(ω5h1+ω6h2+b3)f(ω1x1+ω2x2+h1)

s i g m o i d sigmoid sigmoid 函数如下。

f ( x ) = 1 1 + e − x f(x) = \frac{1}{1 + e^{-x}} f(x)=1+ex1

其导数如下。

f ′ ( x ) = e x ( 1 + e − x ) 2 = f ( x ) ( 1 − f ( x ) ) f'(x) = \frac{e^x}{(1 + e^{-x})^2} = f(x)(1 - f(x)) f(x)=(1+ex)2ex=f(x)(1f(x))

所以总的链式求导公式如下。

∂ L ∂ ω 1 { \frac{\partial L}{\partial \omega_1} } ω1L

= ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ ω 1 { = \frac{\partial L}{\partial y_{pred}} \cdot \frac{\partial y_{pred}}{\partial \omega_1} } =ypredLω1ypred

= ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ h 1 ⋅ ∂ h 1 ∂ ω 1 { = \frac{\partial L}{\partial y_{pred}} \cdot \frac{\partial y_{pred}}{\partial h_1} \cdot \frac{\partial h_1}{\partial \omega_1} } =ypredLh1ypredω1h1

= − 2 ω 5 x 1 ( 1 − f ( ω 5 h 1 + ω 6 h 2 + b 3 ) ) f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) f ′ ( ω 1 x 1 + ω 2 x 2 + h 1 ) { = -2 \omega_5 x_1 (1 - f(\omega_5 h_1 + \omega_6 h_2 + b_3)) f'(\omega_5 h_1 + \omega_6 h_2 + b_3) f'(\omega_1 x_1 + \omega_2 x_2 + h_1) } =2ω5x1(1f(ω5h1+ω6h2+b3))f(ω5h1+ω6h2+b3)f(ω1x1+ω2x2+h1)

这种向后计算偏导数的方式称为反向传播(backpropagation)。

代入实际数据进行计算。

h 1 = f ( x 1 ω 1 + x 2 ω 2 + b 1 ) = f ( − 2 + ( − 1 ) + 0 ) = 0.0474 h_1 = f(x_1 \omega_1 + x_2 \omega_2 + b_1) = f(-2 + (-1) + 0)= 0.0474 h1=f(x1ω1+x2ω2+b1)=f(2+(1)+0)=0.0474

h 2 = f ( x 3 ω 3 + x 4 ω 4 + b 2 ) = 0.0474 h_2 = f(x_3 \omega_3 + x_4 \omega_4 + b_2) = 0.0474 h2=f(x3ω3+x4ω4+b2)=0.0474

o 1 = f ( h 1 ω 5 + h 2 ω 6 + b 3 ) = f ( 0.0474 + 0.0474 + 0 ) = 0.524 o_1 = f(h_1 \omega_5 + h_2 \omega_6 + b_3) = f(0.0474 + 0.0474 + 0) = 0.524 o1=f(h1ω5+h2ω6+b3)=f(0.0474+0.0474+0)=0.524

最终神经网络的输出 y = 0.524 y = 0.524 y=0.524,这个输出无法判断出是男还是女,目前预测效果不好。

现在进行反向传播的运算。

∂ L ∂ ω 1 = ∂ L ∂ y p r e d ⋅ ∂ y p r e d ∂ h 1 ⋅ ∂ h 1 ∂ ω 1 { \frac {\partial L} {\partial \omega_1} = \frac {\partial L} {\partial y_{pred}} \cdot \frac {\partial y_{pred}} {\partial h_1} \cdot \frac {\partial h_1} {\partial \omega_1} } ω1L=ypredLh1ypredω1h1

∂ L ∂ y p r e d = ∂ ( 1 − y p r e d ) 2 ∂ y p r e d = − 2 ( 1 − y p r e d ) { \frac {\partial L} {\partial y_{pred}} = \frac {\partial (1 - y_{pred})^2} {\partial y_{pred}} = -2(1 - y_{pred}) } ypredL=ypred(1ypred)2=2(1ypred)

= − 2 ( 1 − 0.524 ) = − 0.952 { = -2(1 - 0.524) = -0.952 } =2(10.524)=0.952

∂ y p r e d ∂ h 1 = ω 5 f ′ ( ω 5 h 1 + ω 6 h 2 + b 3 ) { \frac {\partial y_{pred}} {\partial h_1} = \omega_5 f'(\omega_5 h_1 + \omega_6 h_2 + b_3) } h1ypred=ω5f(ω5h1+ω6h2+b3)

= 1 × f ′ ( 0.0474 + 0.0474 + 0 ) = f ( 0.0948 ) f ( 1 − 0.0948 ) = 0.249 { = 1 \times f'(0.0474 + 0.0474 + 0) = f(0.0948)f(1 - 0.0948) = 0.249 } =1×f(0.0474+0.0474+0)=f(0.0948)f(10.0948)=0.249

∂ h 1 ∂ ω 1 = x 1 f ′ ( ω 1 x 1 + ω 2 x 2 + b 1 ) { \frac {\partial h_1} {\partial \omega_1} = x_1 f'(\omega_1 x_1 + \omega_2 x_2 + b_1) } ω1h1=x1f(ω1x1+ω2x2+b1)

= − 2 × f ′ ( − 2 + ( − 1 ) + 0 ) = − 2 × f ( − 3 ) f ( 1 − ( − 3 ) ) = − 0.0904 { = -2 \times f'(-2 + (-1) + 0) = -2 \times f(-3)f(1 - (-3)) = -0.0904 } =2×f(2+(1)+0)=2×f(3)f(1(3))=0.0904

所以。

∂ L ∂ ω 1 { \frac {\partial L} {\partial \omega_1} } ω1L

= − 0.952 × 0.249 × ( − 0.0904 ) = 0.0214 { = -0.952 \times 0.249 \times (-0.0904) = 0.0214 } =0.952×0.249×(0.0904)=0.0214

∵ ∂ L ∂ ω 1 > 0 { \because \frac {\partial L} {\partial \omega_1} > 0 } ω1L>0

所以,当 ω 1 \omega_1 ω1 增大时,损失函数 L L L 会有一个较小的增长。

1.3.3 随机梯度下降

我们使用随机梯度下降(stochastic gradient descent, SGD)优化算法,来训练网络。

经过前面的运算,我们已经有了训练神经网络所有数据,但是该如何操作?

SGD 定义了改变权重和偏置的方法。

ω 1 ← ω 1 − η ∂ L ∂ ω 1 \omega_1 \leftarrow \omega_1 - \eta \frac {\partial L} {\partial \omega_1} ω1ω1ηω1L

η \eta η 是一个常数,称为学习率(learning rate),它决定了我们训练网络速率的快慢。

ω 1 \omega_1 ω1 减去 η ∂ L ∂ ω 1 \eta \frac {\partial L} {\partial \omega_1} ηω1L 就得到了新的权重 ω 1 \omega_1 ω1

  • ∂ L ∂ ω 1 {\frac {\partial L} {\partial \omega_1}} ω1L 大于 0, ω 1 {\omega_1} ω1 的值减小,则 L {L} L 的值减小;

  • ∂ L ∂ ω 1 {\frac {\partial L} {\partial \omega_1}} ω1L 小于 0, ω 1 {\omega_1} ω1 的值增加,则 L {L} L 的值减小。

如果我们用这种方法去逐步改变网络的权重 ω \omega ω 和偏置 b b b,损失函数会缓慢地降低,从而改进我们的神经网络。

1.3.4 训练神经网络

整个训练的流程如下。

从数据集中选择一个样本
计算损失函数对所有权重和偏置的偏导数
使用更新公式更新每个权重和偏置

数据集如下。

NameWeightHeightGender(1:男、0:女)
Alice-2-11
Bob2560
Charlie1740
Diana-15-61

神经网络的结构如下。

nn

实现过程如下。

# 作者:解琛;
# 功能:训练神经网络;
# 时间:2020 年 1 月 31 日;
# 备注:
#      训练神经网络其实就是优化的过程;
#
#      假设有一个数据集,包含 4 个人的身高、体重和性别;
#
#       Name    Weight (lb) Height (in) Gender
#       Alice	133         65          0
#       Bob	    160         72          1
#       Charlie 152         70          1
#       Diana   120         60          0
#
#       目标是训练一个网络,根据体重和身高来推测某人的性别。
#
#       在训练神经网络之前,需要有一个标准定义它到底好不好,以便我们进行改进,这就是损失(loss)。
#
#       这里用均方误差(MSE)来定义损失,即所有数据方差的平均值。
#       预测结果越好,结果越准确,损失就越低,训练神经网络的过程就是将损失最小化的过程。

import numpy as np


class NeuralNetwork():
    def __init__(self):
        '''初始化构造函数,在实例被创建时运行一次;'''
        # 权重 weight;
        self.w1 = np.random.normal()
        self.w2 = np.random.normal()
        self.w3 = np.random.normal()
        self.w4 = np.random.normal()
        self.w5 = np.random.normal()
        self.w6 = np.random.normal()

        # 偏置 biases;
        self.b1 = np.random.normal()
        self.b2 = np.random.normal()
        self.b3 = np.random.normal()

    def sigmoid(self, x):
        '''激活函数;'''
        # f(x) = 1 / (1 + e^(-x));
        return 1 / (1 + np.exp(-x))

    def deriv_sigmoid(self, x):
        '''激活函数的导数;'''
        # f'(x) = f(x) * (1 - f(x))
        fx = self.sigmoid(x)
        return fx * (1 - fx)

    def feedforward(self, x):
        '''神经网络前向传播;'''
        # x 是一个 numpy 数组,包含 2 个元素,即 [输入 1, 输入 2];
        h1 = self.sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
        h2 = self.sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
        o1 = self.sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
        return o1

    def mse_loss(self, y_true, y_pred):
        '''MSE 损失函数;'''
        # 真实的输出值和预测输出值应保持相同的数据结构;
        return ((y_true - y_pred) ** 2).mean()

    def train(self, data, all_y_trues):
        '''训练神经网络;'''
        learn_rate = 0.1 # 学习率;
        epochs = 1000 # 训练次数;

        for epoch in range(epochs):
            for x, y_true in zip(data, all_y_trues):

                # 1. 进行输入的前向传递;

                # 获取隐藏层 1 的输出;
                sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
                h1 = self.sigmoid(sum_h1)

                # 获取隐藏层 2 的输出;
                sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
                h2 = self.sigmoid(sum_h2)

                # 获取输出层的输出;
                sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
                o1 = self.sigmoid(sum_o1)

                # 最终的预测值,就是神经网络输出层输出的结果;
                y_pred = o1

                # 2. 计算偏导数;
                #
                # 变量命名规则:
                #
                #           d_L_d_w1
                #
                #   代表:  ∂L
                #           ——
                #           ∂w1

                # 2. 进行反向传递;

                # 损失函数对最终输出求偏导;
                d_L_d_ypred = -2 * (y_true - y_pred)

                # 计算输出层 o1 反向传播的权重和偏置结果;
                d_ypred_d_w5 = h1 * self.deriv_sigmoid(sum_o1)
                d_ypred_d_w6 = h2 * self.deriv_sigmoid(sum_o1)
                d_ypred_d_b3 = self.deriv_sigmoid(sum_o1)

                # 计算隐藏层 h1 和隐藏层 h2 的反向输出结果;
                d_ypred_d_h1 = self.w5 * self.deriv_sigmoid(sum_o1)
                d_ypred_d_h2 = self.w6 * self.deriv_sigmoid(sum_o1)

                # 计算隐藏层 h1 反向传播的权重和偏置结果;
                d_h1_d_w1 = x[0] * self.deriv_sigmoid(sum_h1)
                d_h1_d_w2 = x[1] * self.deriv_sigmoid(sum_h1)
                d_h1_d_b1 = self.deriv_sigmoid(sum_h1)

                # 计算隐藏层 h2 反向传播的权重和偏置结果;
                d_h2_d_w3 = x[0] * self.deriv_sigmoid(sum_h2)
                d_h2_d_w4 = x[1] * self.deriv_sigmoid(sum_h2)
                d_h2_d_b2 = self.deriv_sigmoid(sum_h2)

                # 3. 通过随机梯度下降的方式(SMG)更新权重和偏移;

                # 更新输出层 o1 的权重和偏置;
                self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
                self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
                self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3

                # 更新隐藏层 h1 的权重和偏置;
                self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
                self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
                self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1

                # 更新隐藏层 h2 的权重和偏置;
                self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
                self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
                self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2

            # 3. 每隔 10 次通过 MSE 损失函数计算一次损失值;
            #       注意:损失值只是用来评估神经网络的好坏,没有参与优化运算;
            if epoch % 10 == 0:
                # 将 data 里面的每一个行数据传入 feedforward,计算其结果;
                y_preds = np.apply_along_axis(self.feedforward, 1, data)
                # 求真实值和预测值之间的损失;
                loss = self.mse_loss(all_y_trues, y_preds)
                # 输出损失值;
                print("Epoch %d loss: %.3f" % (epoch, loss))


# 定义数据集输入的参数;
data = np.array([
    [-2, -1], # Alice
    [25, 6], # Bob
    [17, 4], # Charlie
    [-15, -6], # Diana
])

# 定义数据集对应的结果;
all_y_trues = np.array([
    1, # Alice
    0, # Bob
    0, # Charlie
    1, # Diana
])

# 进行神经网络的训练;
network = NeuralNetwork()
network.train(data, all_y_trues)

1.4 使用神经网络进行预测

接着我们使用训练好的神经网络进行预测。

所谓的神经网络已经训练完成,即损失值已经足够低,意味着输入传入神经网络后得到的预测输出值,已经和真实的输出值之间差异很小。

实质上就是完成了神经网络内部各个节点内权重和偏置的调整。

loss

创建 2 个新的数据集传入神经网络,成功预测它们的性别。

# Make some predictions
emily = np.array([-7, -3]) # 128 pounds, 63 inches
frank = np.array([20, 2])  # 155 pounds, 68 inches
print("Emily: %.3f" % network.feedforward(emily)) # 0.951 - F
print("Frank: %.3f" % network.feedforward(frank)) # 0.039 - M
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

解琛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值