admin

  • 在前述的文章(参考)中,我们实现了带有一个隐藏层的神经网络,并使用该神经网络对手写数字0/1进行识别。本文对该神经网络的识别效果以及相关的超参数的配置做一些分析与优化。

    这里涉及的超参数包括了学习率、迭代次数、隐藏层神经元的个数,这里对这三个参数的不同取值进行了相关测试,并观察训练时间与模型效果。

    不同学习率的模型训练

    学习率应该是这里最为重要参数了。在相同的迭代次数下(这里取500),不同的学习率展现出了非常大的差异。这里从0.001开始、尝试了:0.001、0.005、0.01、0.1、0.5等取值。详细的数据如下:

    可以看到,不同的学习率展现出了训练效率的差异非常大:

    • 在相同的迭代次数(均取500)情况下,学习率增加到0.1之后,预测错误率降低到了0.09%,并且再增加学习率,预测错误率并没有提升
    • 在学习率,从0.001增加到了最后的0.5之后,在进行了相同的迭代次数时,训练的目标函数取值下降一直都较为明显

    学习率如何影响目标函数的收敛速度

    右图展示了学习率取值分别为0.1和0.01时,目标函数的收敛速度趋势图。可以看到:

    • 学习率为 0.1 时,在迭代约40次以前,目的函数的收敛速度非常快,并快速的收敛到了非常低的水平
    • 学习率为0.01时,迭代到100次时,代价依旧非常高

    从这次实现代码也可以看到,学习率对于模型的训练效率有这至关重要的影响。如果学习率选择不合适,则会耗费大量计算资源进行非常慢的训练。那么,如果选择合适的学习率以进行更加高效进行梯度下降迭代,这是一个比较复杂的问题,这里暂时先挖个小坑在这里,待后续再做更多讨论。

    迭代次数 epoch 如何影响模型

    这里选取学习率为0.01,隐藏层10个人工神经元,从而观测随着“迭代次数”效率如何影响:

    可以看到,当迭代不够充分时,目标函数收敛还不够时,模型效果也会比较差。随着迭代次数不断增加,目标函数下降就不再明显了。完整的目标函数收敛趋势如下图:

    隐藏层神经元个数与模型效果

    这里观察隐藏层神经元个数与模型效果趋势图。这里分别测试了1、10、50、100、150、300个神经元时候模型的表现,如下图:

    从测试来看,在这个案例中,随着隐藏层神经元个数的增加并不会提升模型性能的。这可能暗示了,此类任务(图像识别相关)使用前馈神经网络时,其性能可能较差。

    部分识别失败的图片

    在该模型与训练下,部分识别失败率比较高的图片如下:

    9879
    8325
    9634
    3073
    2185

  • 标题:AWS re:Invent 发布新的数据库产品 Aurora DSQL; NineData SQL编程大赛开始; 腾讯云支持PostgreSQL 17

    重要更新

    AWS re:Invent 发布新的数据库产品 Aurora DSQL ,提供了跨区域、强一致、多区域读写的能力,同时具备99.999%(多区域部署)的可用性,兼容PostgreSQL;同时发布的还有 DynamoDB 也提供类似的跨区域强一致的能力。 [1] 

    第二届 SQL 编程大赛开赛,感兴趣的可以去挑战一下:一条SQL秒杀100万张火车票 [2]

    更新详情

    阿里云
    • 开启RDS MySQL的写优化功能后,可以确保每次数据页写入的原子性,从而安全地关闭InnoDB双写机制。这不仅降低了I/O写入量,还简化了刷脏过程,大幅度降低实例写盘的IOPS及带宽需求,达到提升实例写性能的目的。[4]
    • RDS MySQL本地盘实例最大IOPS提升—RDS MySQL本地盘实例(含主实例与只读实例)的最大IOPS调整,最高提升至150000。本次IOPS提升不会产生任何额外费用。[5]
    • 主实例规格—RDS PostgreSQL主实例高可用系列标准版独享规格上线大规格实例。[6]
    火山云(字节)
    • veDB MySQL 支持创建兼容 MySQL 5.7 版本的实例,您可根据业务情况,选择合适的兼容版本,满足更多业务场景需求。[11]
    AWS(亚马逊云)
    • DynamoDB 支持零 ETL 与 Amazon SageMaker Lakehouse 集成[20]
    • Oracle Database@AWS 现已推出有限预览版 [40]
    腾讯云
    • TDSQL-C MySQL 版“只读分析引擎”发布了新版本,支持了新的函数、进行了系统优化,例如:支持了 FROM_UNIXTIME 与 UNIX_TIMESTAMP 函数 [42]
    • TDSQL-C MySQL 版支持 Serverless 成本评估器,帮助您更好的了解使用 Serverless 服务的成本。[43]
    • 云数据库 PostgreSQL 全面支持 PostgreSQL 17.0。[44]
    • 云数据库 PostgreSQL 开启8core 64GiB规格售卖。[45]

    参考链接

  • 关闭 InnoDB 的 redo log

    ·

    在 MySQL 实例恢复时(尤其是逻辑备份的恢复),为了获得更快的恢复速度,通常会关闭二进制日志(Binary Log),并且将 InnoDB 的日志持久化级别调整到最低。从 MySQL 8.0.21起[1],更进一步的,可以彻底的关闭 InnoDB redo 从而获得更好导入速度。后续的 8.4 / 9.0 / 9.1 可以使用该特性。

    在本文的测试中,可以看到关闭 InnoDB redo log 导入速度可以提升约 26%

    使用场景

    最为常见的就是在进行大量数据导入时,希望能够加速数据导入的过程。

    管理命令

    可以使用如下的命令关闭/或打开 InnoDB redo log:

     ALTER INSTANCE {ENABLE|DISABLE} INNODB REDO_LOG

    关闭 InnoDB redo log

    mysql>  ALTER INSTANCE DISABLE INNODB REDO_LOG;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> SHOW STATUS LIKE '%Innodb_redo_log_enabled%';
    +-------------------------+-------+
    | Variable_name           | Value |
    +-------------------------+-------+
    | Innodb_redo_log_enabled | OFF   |
    +-------------------------+-------+
    1 row in set (0.02 sec)
    
    

    打开 InnoDB redo log

    mysql> ALTER INSTANCE ENABLE INNODB REDO_LOG;
    Query OK, 0 rows affected (1.02 sec)
    
    mysql> SHOW STATUS LIKE '%Innodb_redo_log_enabled%';
    +-------------------------+-------+
    | Variable_name           | Value |
    +-------------------------+-------+
    | Innodb_redo_log_enabled | ON    |
    +-------------------------+-------+
    1 row in set (0.00 sec)

    执行该命令的权限

    因为该命令对数据库影响巨大,所以也引入独立的权限 INNODB_REDO_LOG_ENABLE来管理该命令的执行权限。具体参考:

    mysql> GRANT INNODB_REDO_LOG_ENABLE ON *.* to 'data_load_admin';

    性能对比

    这里做应该简单的性能对比,看看关闭 InnoDB Redo Log 导入速度会提升多少。

    # mysql -uroot test -e "show status like 'Innodb_redo_log_enabled'"
    +-------------------------+-------+
    | Variable_name           | Value |
    +-------------------------+-------+
    | Innodb_redo_log_enabled | ON    |
    +-------------------------+-------+
    #  mysql -uroot test -e "truncate table passenger"
    # time mysql -uroot test < passenger.1000.sql > /dev/null
    
    real	0m3.109s
    user	0m0.017s
    sys	0m0.013s
    # mysql -uroot test -e "truncate table passenger"
    # mysql -uroot test -e "ALTER INSTANCE DISABLE INNODB REDO_LOG"
    # time mysql -uroot test < passenger.1000.sql > /dev/null
    
    real	0m2.286s
    user	0m0.022s
    sys	0m0.009s

    在这个初步测试中,可以观察到,在关闭 InnoDB Redo 之后,到如时间从 3.109s 降低到了 2.286s,在该导入中,节省时间约 26%的时间。

    参考文档

  • This content is password protected. To view it please enter your password below:

  • 标题:Neo4j宣布ARR超$2亿 ;IDC报告2024H1中国RDBMS规模为$19.3亿;AWS Aurora/RDS 支持 Graviton4 实例

    重要更新

    下周,AWS 将举行 re:Invent 年度开发者大会,Aurora/RDS 都将发布支持新一代的 ARM 架构自研芯片 Graviton 4 [20] [21]

    Neo4j 宣布其年度经常性收入(ARR)已超过2亿美元,并在过去三年中实现了ARR翻番。Neo4j 在快速扩张的图技术市场中的领导地位,以及最近几年图技术对于提升生成式AI(GenAI)结果的准确性、透明性和可解释性至关重要作用,是增长的核心因素。[1]

    IDC 发布《2024年上半年中国关系型数据库软件市场跟踪报告》,报告称,2024上半年中国关系型数据库软件市场规模为19.3亿美元,同比增长10.7%。其中,公有云关系型数据库规模12.9亿美元,同比增长14.1%;本地部署关系型数据库规模6.4亿美元,同比增长4.2% 。 [2] 

    更新详情
    腾讯云
    • TDSQL-C MySQL 版支持透明数据加密(Transparent Data Encryption,TDE)功能,可满足静态数据加密的合规性要求。[44]
    • TDSQL-C MySQL Serverless 集群的算力配置上下限支持设置为相同算力。[45]
    • TDSQL-C MySQL 版“只读分析引擎”发布了新版本,支持了 bit、enum、set 数据类型数据加载为列存以及在分析引擎中进行查询。[46]
    火山云(字节)
    • 云数据库 SQL Server 白名单开放 0.0.0.0/0 IP 网段,允许所有地址访问;支持通过关联 ECSIP 和关联入方向 IP两种方式绑定 ECS 安全组,更贴近实际使用场景。[15]
    • 云数据库 SQL Server 版正式支持售卖创建 3il 规格的实例 [16]
    AWS(亚马逊云)
    • Aurora 支持 db.r8g 实例类 [20]
    • RDS 支持 db.m8g 和 db.r8g 实例类 [21]
    • RDS 支持 MySQL 8.4 [22]
    • RDS for PostgreSQL、MySQL 和 MariaDB 现支持 M8g 和 R8g 数据库实例 [28]
    • Amazon Q Developer 现可将嵌入式 SQL 从 Oracle 转换为 PostgreSQL [35]
    Azure(微软云)
    • 基于 vCore 的 Azure Cosmos DB for MongoDB 中支持次要用户(secondary users)的读取和读/写权限(预览) [7]
    • 发布适用于 Azure Cosmos DB 的 Visual Studio Code 扩展 (预览)[8]
    GCP(谷歌云)
    • 支持使用 Cloud SQL for PostgreSQL 备份的创建新的 AlloyDB 集群 [10] 
    Oracle云
    • 托管 PostgreSQL 数据库支持只读访问端点 [12]
    • 托管 PostgreSQL 支持对扩展插件的管理 [13]

    参考链接

  • 更进一步,本文将从零构建一个浅层的前馈神经网络,实现手写数字0和1的识别。示例手写数字如下图所示。整体实现包括数据预处理、参数初始化、forward/backword propagation、训练与预测。

    问题描述

    使用 Python ,但不使用任何深度学习框架,编写一个浅层的神经网络实现,识别 MNIST 数据集中的手写数字 0 和 1。


    前置知识

    • 熟悉Python 、NumPy的使用
    • 了解神经网络的表示与forward propagation
    • 了解 backward propagation
    • 了解 MNIST 数据集

    数据说明

    这里的手写数字使用了数据集 MNIST,该数据集有着机器学习界的“果蝇”之称。里面包含了大量经过预处理的手写数字,这里选择其中标记为0或1的图片进行训练。并使用对应的测试集,验证训练的神经网络的效果。

    这里取一张 MNIST 中的图片,以及对应的像素数据,以帮助直观理解其中的数据。MNIST 中的图片数据可以理解为一个如下的 28*28 的数组,其所代表的图片也如下图所示:

    更为具体的,数组中的每一个数值代表了图片中的某个像素的灰度值,0代表黑色,255代表白色,这之间的值则代表介于之间的灰色。

    MNIST 原始数据可以在:“THE MNIST DATABASE of handwritten digits”页面获取。也可以直接使用 Python 中模块keras.datasets获取。本程序使用后者方式获取。

    实现概述

    这里的使用了两层神经网络,即一个隐藏层,一个输出层,因为这是二分类问题,所以输出层使用logistic函数,而隐藏层则使用了 ReLU 作为激活函数。隐藏层的神经元是一个动态参数(超参数),在测试时,可以调整为10~300之间不等。输入层则是,MNIST 的图片,也就是一个28*28个数组。

    这里使用了最为基础的Gradient Descent算法,有时候也被称为Batch Gradient Descent。

    数据预处理

    在 Python 中可以使用 keras.datasets模块便捷的获取 MNIST 的数据集。这里编写了一个简单的函数,将该数据集中0和1相关的图片筛选出来,然后将数组的维度,转化为需要的维度。例如,期望的输入数组的维度为\( (n^{[0]},m) \),其中 \( n^{[0]} \) 表示输入的特性(feature)数量,这里也就是 784(即28*28),\( m \)表示样本个数。

    具体代码如下:

    # return only data lable 0 or 1 from MNIST for the binary classification
    def filter_mnist_data(data_X, data_y):
        data_filter = np.where((data_y == 0) | (data_y == 1))
        filtered_data_X, filtered_data_y = data_X[data_filter], data_y[data_filter]
        r_data_X = filtered_data_X.reshape(filtered_data_X.shape[0],filtered_data_X.shape[1]*filtered_data_X.shape[2])
        return (r_data_X, filtered_data_y)
    
    (train_all_X, train_all_y), (test_all_X, test_all_y) = mnist.load_data()
    (train_X,train_y) = filter_mnist_data(train_all_X, train_all_y)
    (test_X ,test_y ) = filter_mnist_data(test_all_X, test_all_y)
    
    X  = train_X.T
    Y  = train_y.reshape(1,train_y.shape[0])

    超参数的配置

    该程序涉及的超参数(hyper-parameter)包括:隐藏层神经元个数 \( n_1 = 10 \)、学习率 \( \alpha = 0.5 \)、迭代次数等。

    # hyper-parameter; read the comments above for structure of the NN
    n0 = X.shape[0]   # number of input features
    n1 = 10           # nerons of the hidden layer
    n2 = 1            # nerons of the output layer
    iteration_count = 500
    learning_rate   = 0.5

    feature scaling和参数初始化

    为了增加训练的速度,这里对输入进行了“标准化”,将所有的数据,归一化为均值为0、方差为1的数据集。具体的步骤如下:

    # feature scaling / Normalization
    mean = np.mean(X,axis = 1,keepdims = True)
    std  = np.std( X,axis = 1,keepdims = True)+0.000000001
    X  = (X-mean)/std

    注意:实现过程中需要注意的是,如果对输入进行了标准化,那么在预测时也需要对预测的输入进行相同的归一化。所以这里的均值和方差,需要记录下来,以备后续使用。这里给方差额外加了一个非常小的数字,一般是没有必要的,这里是为了防止输入数据全部都相同,即方差为0。

    此外,为了增加训练的速度,也参考业界通用做法,对输入也进行了随机初始化,具体如下:

    # initial parameters: W1 W2 b1 b2 size
    np.random.seed(561)
    W1 = np.random.randn(n1,n0)*0.01
    W2 = np.random.randn(n2,n1)*0.01
    b1 = np.zeros([n1,1])
    b2 = np.zeros([n2,1])

    Forward Propagation / Backward Propagation

    Forward Propagation 总是简单的。Backward Propagation 本身的计算与推导是非常复杂的,这里不打算详述(后续再单独介绍)。这里把 Backward Propagation 的计算结果列举如下。对于输出层:

    $$
    \begin{align}
    dW^{[l]} & = \frac{\partial J}{\partial W^{[l]}} \\
    & = \frac{\partial J}{\partial Z^{[l]}} @ (A^{[l-1]})^T
    \\
    dZ^{[l]} & = \frac{\partial J}{\partial Z^{[l]}} \\
    & = \frac{1}{m}(A^{[l]} – Y)
    \end{align}
    $$

    这里的 @ 表示矩阵乘法符号。 上标 T表示矩阵转置。这里一共两层,故这里的 \( l = 2 \) 。

    对于隐藏层:

    $$
    \begin{align*}
    dW^{[k-1]} & = \frac{\partial J}{\partial W^{[k-1]}} \\
    & = \frac{\partial J}{\partial Z^{[k-1]}} @ (A^{[l-2]})^T &
    \\
    dZ^{[k-1]} & = \frac{\partial J}{\partial Z^{[k-1]}} \\
    & = (W^{[k]})^T @ \partial Z^{[k]} \cdot g\prime(Z^{[k-1]}) &
    \end{align*}
    $$

    因为这里只有一个隐藏层和一个输出层,故这里的 \( k = 2 \);相同的,这里的 @ 表示矩阵乘法符号;这里的 \( \cdot \) 表示对应元素相乘(element-wise);函数 \( g \) 表示 ReLU函数。

    具体的代码实现:

        # forward propagation
        A0 = X
    
        Z1 = W1@X + b1  # W1 (n1,n0)  X: (n0,m)
        A1 = np.maximum(Z1,0) # relu
        Z2 = W2@A1 + b2
        A2 = logistic_function(Z2)
    
        dZ2 = (A2-Y)/m
        dW2 = dZ2@A1.T
        db2 = np.sum(dZ2,axis=1,keepdims = True)
    
        dZ1 = W2.T@dZ2*(np.where(Z1 > 0, 1, 0)) # np.where(Z1 > 0, 1, 0) is derivative of relu function
        dW1 = dZ1@A0.T
        db1 = np.sum(dZ1,axis=1,keepdims = True)

    训练迭代

    每次训练迭代都需要根据样本数据,进行一次上述的 Forward Propagation / Backward Propagation ,然后更新新的参数值,以用于下一次迭代,具体的实现如下:

    cost_last = np.inf # very large data,maybe better , what about np.inf
    for i in range(iteration_count):
        ...
        Forward Propagation / Backward Propagation
        ...
        W1 = W1 - learning_rate*dW1
        W2 = W2 - learning_rate*dW2
        b1 = b1 - learning_rate*db1
        b2 = b2 - learning_rate*db2

    使用测试数据集验证训练效果

    在完成训练后,就可以对测试集中的数据进行验证了。需要注意的是,在前面做了“feature scaling”,这里需要对应将输入数据做对应的处理。将预测结果,与标注数据进行对比,就可以确定该模型的效果了。

    # Normalization for test dataset
    X  = (test_X.T - mean)/std
    Y  = test_y.reshape(1,test_y.shape[0])
    
    Y_predict = (logistic_function(W2@np.maximum((W1@X+b1),0)+b2) > 0.5).astype(int)
    
    for index in (np.where(Y != Y_predict)[1]):
        print(f"failed to recognize: {index}")
        # np.set_printoptions(threshold=np.inf)
        # np.set_printoptions(linewidth=np.inf)
        # print(test_X[index].reshape(28,28))
    
    print("total test set:" + str(Y.shape[1]) + ",and err rate:"+str((np.sum(np.square(Y-Y_predict)))/Y.shape[1]))

    完整的代码

    完整的代码可以参考 GitHub 仓库中的 ssnn_bear.py 脚本:参考。这里张贴当前版本如下:

    """
    super simple neural networks (bear version)
      * input x is matrix 784x1 (where 784 = 28*28 which is MNIST image data)
      * 30 neurons for the only hidden layer, as n^{[1]} = 30
      * output layer: one neuron for classification(logistic)
      * using relu for activation function in hidden layer
    
    input layer:
        x: (shape 784x1)
            X: m samples where m = 12665 , X: 784 x 12665
            as : a^{[0]}: 784 x 12665  n^{[0]} = 784
    hidden layer:
        n^{[1]}:  30
        W^{[1]}: (30,784)   as (n^{[1]},n^{[0]})
        Z^{[1]}: (30,12665) as (n^{[1]},m)
        A^{[1]}: (30,12665) as (n^{[1]},m)
    output layer:
        n^{[2]}: 1
        W^{[2]}: (1,30)     as (n^{[2]},n^{[1]})
        Z^{[2]}: (1,12665)  as (n^{[2]},m)
        A^{[2]}: (1,12665)  as (n^{[2]},m)
    
    output:
        y \in [0,1] or  p \in {0,1}
            Y: (1 x m) ndarray
    structure for only one sample:
          x_1   ->   W*X + B   ->  relu  ->
          x_2   ->   W*X + B   ->  relu  ->  \
          ...   ->     ...     ->     .. ->  -> w*x+b -> logistic
          x_784 ->   W*X + B   ->  relu  ->  /
         ------     --------------------       ------------------
           |                |                          |
           V                V                          V
         input         30 neurons                 one neuron
        feature      relu activation             output layer
    
      By numpy with m samples:
        np.logistic(W2@g(W1@X+b1)+b2) as \hat{Y}: (1 x m) ndarray
    
        dimension analysis:
            W2        : (n2,n1)
            g(W1@X+b1): (n1,m)
                W1 : (n1,n0)
                X  : (n0,m)
                b1 : (n1,1)  with broadcasting to (n1,m)
            b2: (n2,1) with broadcasting to (n2,m)
    
    grad and notaion:
        forward propagation : A1 A2 Z1 Z2
        backward propagation: dW1 dW2 db1 db2
    
        more details:
            Z1 = W1@X  + b1
            Z2 = W2@A1 + b2
            A1 = g(Z1)      -- g     for relu
            A2 = \sigma(Z2) -- sigma for logistic
    
            dW2 = ((1/m)*(A2-Y))@A1.T
                dW2 = dZ2@A1.T  where dZ2 = (1/m)*(A2-Y)
                A2.shape:(1,m) Y.shape:(1,m) A1.T.shape:(n1,m)
                so: dW2.shape: (1,n1)
    
            dW1 = (W2.T@((1/m)*(A2-Y))*g_prime(Z1))@A0.T
                dW1 = dZ1@A1.T
                    where
                        dZ1 = W2.T@dZ2 * g_prime(Z1)
                        g_prime is derivative of relu
                    dW2.shape: (n1,n0)
            note: @ for matrix multiply;   * for dot product/element-wise
    
    Challenges
        1. Understanding the MNIST dataset and labels
        2. Understanding gradient caculate and the gradient descent
        3. Understanding logistic regression loss function and the caculation
        3. Knowing feature normalization
    
    about it:
        it's a simple project for human learning how machine learning
        version ant : scalar input/one neuron/one layer/binary classification
        version bear: vector input/30+1 neurons /two layer/binary classification
        by orczhou.com
    """
    
    from keras.datasets import mnist
    import numpy as np
    
    # return only data lable 0 or 1 from MNIST for the binary classification
    def filter_mnist_data(data_X, data_y):
        data_filter = np.where((data_y == 0) | (data_y == 1))
        filtered_data_X, filtered_data_y = data_X[data_filter], data_y[data_filter]
        r_data_X = filtered_data_X.reshape(filtered_data_X.shape[0],filtered_data_X.shape[1]*filtered_data_X.shape[2])
        return (r_data_X, filtered_data_y)
    
    (train_all_X, train_all_y), (test_all_X, test_all_y) = mnist.load_data()
    (train_X,train_y) = filter_mnist_data(train_all_X, train_all_y)
    (test_X ,test_y ) = filter_mnist_data(test_all_X, test_all_y)
    
    X  = train_X.T
    Y  = train_y.reshape(1,train_y.shape[0])
    
    m = X.shape[1]    # number of samples
    
    # hyper-parameter; read the comments above for structure of the NN
    n0 = X.shape[0]   # number of input features
    n1 = 10           # nerons of the hidden layer
    n2 = 1            # nerons of the output layer
    iteration_count = 500
    learning_rate   = 0.5
    
    # feature scaling / Normalization
    mean = np.mean(X,axis = 1,keepdims = True)
    std  = np.std( X,axis = 1,keepdims = True)+0.000000001
    X  = (X-mean)/std
    
    # initial parameters: W1 W2 b1 b2 size
    np.random.seed(561)
    W1 = np.random.randn(n1,n0)*0.01
    W2 = np.random.randn(n2,n1)*0.01
    b1 = np.zeros([n1,1])
    b2 = np.zeros([n2,1])
    
    # logistic function
    def logistic_function(x):
        return 1/(1+np.exp(-x))
    
    about_the_train = '''\
    try to train the model with:
      learning rate: {:f}
      iteration    : {:d}
      neurons in hidden layer: {:d}
    \
    '''
    print(about_the_train.format(learning_rate,iteration_count,n1))
    
    # forward/backward propagation (read the comment above:"grad and notaion")
    cost_last = np.inf # very large data,maybe better , what about np.inf
    for i in range(iteration_count):
        # forward propagation
        A0 = X
    
        Z1 = W1@X + b1  # W1 (n1,n0)  X: (n0,m)
        A1 = np.maximum(Z1,0) # relu
        Z2 = W2@A1 + b2
        A2 = logistic_function(Z2)
    
        dZ2 = (A2-Y)/m
        dW2 = dZ2@A1.T
        db2 = np.sum(dZ2,axis=1,keepdims = True)
    
        dZ1 = W2.T@dZ2*(np.where(Z1 > 0, 1, 0)) # np.where(Z1 > 0, 1, 0) is derivative of relu function
        dW1 = dZ1@A0.T
        db1 = np.sum(dZ1,axis=1,keepdims = True)
    
        cost_current = np.sum(-(Y*(np.log(A2))) - ((1-Y)*(np.log(1-A2))))/m
        if (i+1)%(iteration_count/20) == 0:
            print("iteration: {:5d},cost_current:{:f},cost_last:{:f},cost reduce:{:f}".format( i+1,cost_current,cost_last,cost_last-cost_current))
    
        cost_last = cost_current
        W1 = W1 - learning_rate*dW1
        W2 = W2 - learning_rate*dW2
        b1 = b1 - learning_rate*db1
        b2 = b2 - learning_rate*db2
    
    print("Label:")
    print(np.round( Y[0][:20]+0.,0))
    print("Predict:")
    print(np.round(A2[0][:20],0))
    
    # Normalization for test dataset
    X  = (test_X.T - mean)/std
    Y  = test_y.reshape(1,test_y.shape[0])
    
    Y_predict = (logistic_function(W2@np.maximum((W1@X+b1),0)+b2) > 0.5).astype(int)
    
    for index in (np.where(Y != Y_predict)[1]):
        print(f"failed to recognize: {index}")
        # np.set_printoptions(threshold=np.inf)
        # np.set_printoptions(linewidth=np.inf)
        # print(test_X[index].reshape(28,28))
    
    print("total test set:" + str(Y.shape[1]) + ",and err rate:"+str((np.sum(np.square(Y-Y_predict)))/Y.shape[1]))

    简单统计,代码总计180行,其中注释约90行。

    运行结果说明

    运行该程序会有如下输出:

    try to train the model with:
      learning rate: 0.500000
      iteration    : 500
      neurons in hidden layer: 10
    iteration:    25,cost_current:0.009138,cost_last:0.009497,cost reduce:0.000358
    ...
    iteration:   500,cost_current:0.000849,cost_last:0.000850,cost reduce:0.000001
    failed to recognize: 1749
    failed to recognize: 2031
    total test set:2115,and err rate:0.0009456264775413711

    在本次训练后,对于测试集中的 2115 中图片,识别的错误率为 0.094%,即有两张图片未能够正确识别。这两张图片的编号分别是1749和2031,对应的图像如下:

    最后

    该神经网络在训练后,对于2115张测试集中的图片,仅有2张识别失败。通过该程序,可以看到,神经网络的强大与神奇。

    因为神经网络的 Backward Propagation 的推导非常复杂,本文中直接给出了相关公式,后续将独立介绍该部分内容。如果有任何问题可以留言讨论或公众号后台给作者留言。

    补充记录

    在这个该程序实现中,还遇到了一些其他的问题,记录如下。

    一些报错

    “RuntimeWarning: overflow encountered in exp: return 1/(1+np.exp(-x))”

    当训练或者预测计算过程中,如果出现部分-x值比较大,那么就会出现np.exp(-x)溢出的问题。不过,还好该问题在该场景下并不会影响计算结果,因为这种情况下,1/(1+np.exp(-x))依旧会返回0,而这也是预期中的。

    难以分析

    在这个识别程序中,最后发现有部分图片是难以识别的,而且无论如何调整参数,部分图片还是难以识别。最好的处理方法当然是增加训练数据集,但现实中可能并不总是能够做到。

    而关于模型应该如何优化,这似乎是难以分析,无从入手。