乐趣区

技术干货丨卷积神经网络之LeNet5迁移实践案例

摘要:LeNet- 5 是 Yann LeCun 在 1998 年设计的用于手写数字识别的卷积神经网络,当年美国大多数银行就是用它来识别支票上面的手写数字的,它是早期卷积神经网络中最有代表性的实验系统之一。可以说,LeNet- 5 就相当于编程语言入门中的“Hello world!”。

华为的昇腾训练芯片一直是大家所期待的,目前已经开始提供公测,如何在昇腾训练芯片上运行一个训练任务,这是目前很多人都在采坑过程中,所以我写了一篇指导文章,附带上所有相关源代码。注意,本文并没有包含环境的安装,请查看另外相关文档。

环境约束:昇腾 910 目前仅配套 TensorFlow 1.15 版本。

基础镜像上传之后,我们需要启动镜像命令,以下命令挂载了 8 块卡(单机所有卡):

docker run -it –net=host –device=/dev/davinci0 –device=/dev/davinci1 –device=/dev/davinci2 –device=/dev/davinci3 –device=/dev/davinci4 –device=/dev/davinci5 –device=/dev/davinci6 –device=/dev/davinci7 –device=/dev/davinci_manager –device=/dev/devmm_svm –device=/dev/hisi_hdc -v /var/log/npu/slog/container/docker:/var/log/npu/slog -v /var/log/npu/conf/slog/slog.conf:/var/log/npu/conf/slog/slog.conf -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ -v /usr/local/Ascend/driver/tools/:/usr/local/Ascend/driver/tools/ -v /data/:/data/ -v /home/code:/home/local/code -v ~/context:/cache ubuntu_18.04-docker.arm64v8:v2 /bin/bash

设置环境变量并启动手写字训练网络:

#!/bin/bash
export LD_LIBRARY_PATH=/usr/local/lib/:/usr/local/HiAI/runtime/lib64
export PATH=/usr/local/HiAI/runtime/ccec_compiler/bin:$PATH
export CUSTOM_OP_LIB_PATH=/usr/local/HiAI/runtime/ops/framework/built-in/tensorflow
export DDK_VERSION_PATH=/usr/local/HiAI/runtime/ddk_info
export WHICH_OP=GEOP
export NEW_GE_FE_ID=1
export GE_AICPU_FLAG=1
export OPTION_EXEC_EXTERN_PLUGIN_PATH=/usr/local/HiAI/runtime/lib64/plugin/opskernel/libfe.so:/usr/local/HiAI/runtime/lib64/plugin/opskernel/libaicpu_plugin.so:/usr/local/HiAI/runtime/lib64/plugin/opskernel/libge_local_engine.so:/usr/local/H
iAI/runtime/lib64/plugin/opskernel/librts_engine.so:/usr/local/HiAI/runtime/lib64/libhccl.so
 
export OP_PROTOLIB_PATH=/usr/local/HiAI/runtime/ops/built-in/
 
export DEVICE_ID=2
export PRINT_MODEL=1
#export DUMP_GE_GRAPH=2
 
#export DISABLE_REUSE_MEMORY=1
#export DUMP_OP=1
#export SLOG_PRINT_TO_STDOUT=1
 
export RANK_ID=0
export RANK_SIZE=1
export JOB_ID=10087
export OPTION_PROTO_LIB_PATH=/usr/local/HiAI/runtime/ops/op_proto/built-in/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/fwkacllib/lib64/:/usr/local/Ascend/driver/lib64/common/:/usr/local/Ascend/driver/lib64/driver/:/usr/local/Ascend/add-ons/
export PYTHONPATH=$PYTHONPATH:/usr/local/Ascend/opp/op_impl/built-in/ai_core/tbe
export PATH=$PATH:/usr/local/Ascend/fwkacllib/ccec_compiler/bin
export ASCEND_HOME=/usr/local/Ascend
export ASCEND_OPP_PATH=/usr/local/Ascend/opp
export SOC_VERSION=Ascend910
 
rm -f *.pbtxt
rm -f *.txt
rm -r /var/log/npu/slog/*.log
rm -rf train_url/*
 
 
python3 mnist_train.py

以下训练案例中我使用的 lecun 大师的 LeNet- 5 网络,先简单介绍 LeNet- 5 网络:

LeNet5 诞生于 1994 年,是最早的卷积神经网络之一,并且推动了深度学习领域的发展。自从 1988 年开始,在多年的研究和许多次成功的迭代后,这项由 Yann LeCun 完成的开拓性成果被命名为 LeNet5。

LeNet- 5 包含七层,不包括输入,每一层都包含可训练参数(权重),当时使用的输入数据是 32*32 像素的图像。下面逐层介绍 LeNet- 5 的结构,并且,卷积层将用 Cx 表示,子采样层则被标记为 Sx,完全连接层被标记为 Fx,其中 x 是层索引。

层 C1 是具有六个 5 * 5 的卷积核的卷积层(convolution),特征映射的大小为 28*28,这样可以防止输入图像的信息掉出卷积核边界。C1 包含 156 个可训练参数和 122304 个连接。

层 S2 是输出 6 个大小为 14*14 的特征图的子采样层(subsampling/pooling)。每个特征地图中的每个单元连接到 C1 中的对应特征地图中的 2 * 2 个邻域。S2 中单位的四个输入相加,然后乘以可训练系数(权重),然后加到可训练偏差(bias)。结果通过 S 形函数传递。由于 2 * 2 个感受域不重叠,因此 S2 中的特征图只有 C1 中的特征图的一半行数和列数。S2 层有 12 个可训练参数和 5880 个连接。

层 C3 是具有 16 个 5 - 5 的卷积核的卷积层。前六个 C3 特征图的输入是 S2 中的三个特征图的每个连续子集,接下来的六个特征图的输入则来自四个连续子集的输入,接下来的三个特征图的输入来自不连续的四个子集。最后,最后一个特征图的输入来自 S2 所有特征图。C3 层有 1516 个可训练参数和 156 000 个连接。

层 S4 是与 S2 类似,大小为 2 *2,输出为 16 个 5 * 5 的特征图。S4 层有 32 个可训练参数和 2000 个连接。

层 C5 是具有 120 个大小为 5 * 5 的卷积核的卷积层。每个单元连接到 S4 的所有 16 个特征图上的 5 * 5 邻域。这里,因为 S4 的特征图大小也是 5 *5,所以 C5 的输出大小是 1 *1。因此 S4 和 C5 之间是完全连接的。C5 被标记为卷积层,而不是完全连接的层,是因为如果 LeNet- 5 输入变得更大而其结构保持不变,则其输出大小会大于 1 *1,即不是完全连接的层了。C5 层有 48120 个可训练连接。

F6 层完全连接到 C5,输出 84 张特征图。它有 10164 个可训练参数。这里 84 与输出层的设计有关。

LeNet 的设计较为简单,因此其处理复杂数据的能力有限;此外,在近年来的研究中许多学者已经发现全连接层的计算代价过大,而使用全部由卷积层组成的神经网络。

LeNet- 5 网络训练脚本是 mnist_train.py,具体代码:

import os
import numpy as np
import tensorflow as tf
import time
from tensorflow.examples.tutorials.mnist import input_data
 
import mnist_inference
 
from npu_bridge.estimator import npu_ops #导入 NPU 算子库
from tensorflow.core.protobuf.rewriter_config_pb2 import RewriterConfig #重写 tensorFlow 里的配置,针对 NPU 的配置
 
 
batch_size = 100
learning_rate = 0.1
training_step = 10000
 
model_save_path = "./model/"
model_name = "model.ckpt"
 
def train(mnist):
    x = tf.placeholder(tf.float32, [batch_size, mnist_inference.image_size, mnist_inference.image_size, mnist_inference.num_channels], name = 'x-input')
    y_ = tf.placeholder(tf.float32, [batch_size, mnist_inference.num_labels], name = "y-input")
 
    regularizer = tf.contrib.layers.l2_regularizer(0.001)
    y = mnist_inference.inference(x, train = True, regularizer = regularizer) #推理过程
    global_step = tf.Variable(0, trainable=False)
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits = y, labels = tf.argmax(y_, 1)) #损失函数
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + tf.add_n(tf.get_collection("loss"))
 
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step = global_step) #优化器调用
 
    saver = tf.train.Saver() #启动训练
 
#以下代码是 NPU 所必须的代码,开始配置参数
    config = tf.ConfigProto(
        allow_soft_placement = True,
        log_device_placement = False)
    custom_op =  config.graph_options.rewrite_options.custom_optimizers.add()
    custom_op.name =  "NpuOptimizer"
    custom_op.parameter_map["use_off_line"].b = True
    #custom_op.parameter_map["profiling_mode"].b = True
    #custom_op.parameter_map["profiling_options"].s = tf.compat.as_bytes("task_trace:training_trace")
config.graph_options.rewrite_options.remapping = RewriterConfig.OFF
#配置参数结束
 
    writer = tf.summary.FileWriter("./log_dir", tf.get_default_graph())
    writer.close()
 
 
#参数初始化
    with tf.Session(config = config) as sess:
        tf.global_variables_initializer().run()
 
        start_time = time.time()
 
        for i in range(training_step):
xs, ys = mnist.train.next_batch(batch_size)
            reshaped_xs = np.reshape(xs, (batch_size, mnist_inference.image_size, mnist_inference.image_size, mnist_inference.num_channels))
            _, loss_value, step = sess.run([train_step, loss, global_step], feed_dict={x:reshaped_xs, y_:ys})
 
            #每训练 10 个 epoch 打印损失函数输出日志
            if i % 10 == 0:
                print("****************************++++++++++++++++++++++++++++++++*************************************\n" * 10)
                print("After %d training steps, loss on training batch is %g, total time in this 1000 steps is %s." % (step, loss_value, time.time() - start_time))
                #saver.save(sess, os.path.join(model_save_path, model_name), global_step = global_step)
                print("****************************++++++++++++++++++++++++++++++++*************************************\n" * 10)
                start_time = time.time()
def main():
    mnist = input_data.read_data_sets('MNIST_DATA/', one_hot= True)
    train(mnist)
 
if __name__ == "__main__":
    main()

本文主要讲述了经典卷积神经网络之 LeNet- 5 网络模型和迁移至昇腾 D910 的实现,希望大家快来动手操作一下试试看!

点击关注,第一时间了解华为云新鲜技术~

退出移动版