玩转Ascend310模型推理(一)模型训练与离线转换

举报
wangkai86 发表于 2020/12/10 21:08:44 2020/12/10
【摘要】 本文通过使用tensorflow,以手写数字识别为例,通过tensorflow常用的3种建模方式分别演示和介绍模型训练、模型保存、以及离线模型转换三个步骤,在下一篇(二)模型加载与运行推理中介绍转换后的模型如何部署与运行推理。   运行环境: Ubuntu 18.04 x86_64  

Ascend310系列推理芯片,广泛用于人脸识别、图像分类、智能摄像领域,为智慧城市、智慧交通、智慧金融等场景提供超强AI推理能力,主要产品包括Atlas 300I 推理卡、Atlas 200 AI加速模块、Atlas 200 DK 开发者套件等,参考官方介绍[Atlas 300I](https://e.huawei.com/cn/products/cloud-computing-dc/atlas/atlas-300-ai)。  

本文通过使用tensorflow,以手写数字识别为例,通过tensorflow常用的3种建模方式分别演示和介绍模型训练、模型保存、以及离线模型转换三个步骤,在下一篇(二)模型加载与运行推理中介绍转换后的模型如何部署与运行推理。  
运行环境: Ubuntu 18.04 x86_64  

训练和模型保存,代码参考。  
项目目录结构:

├── data
│   ├── data.py
│   ├── mnist_images.png
│   └── mnist_labels_uint8
├── raw
│   └── model.py
├── keras
│   └── model.py
├── keras_seq
│   └── model.py
└── train.py

数据准备  

手写字数据集由2个文件组成,一个图片文件,一个标签文件,图像文件为png格式,按照28*28(784)单通道黑白连续存储65000张图片,即65000*784个数据,合并为一个png文件方式存储,标签文件中每个手写数字由一个10字节数组表示,数字所在元素值为1,其他为0,一共65000*10个数据。  

数据获取部分代码参考data/data.py。  

    def get_train(self, size):
        if size > self.size:
            size = self.size
        img = self.image[: size * IMG_W * IMG_H].reshape((size, IMG_W, IMG_H, 1))
        lab = self.label[: size * LAB_W].reshape((size, LAB_W))
        return img, lab

模型建立与训练  

RAW模型  

RAW模型为使用tensorflow基础构图模型建立,使用底层API实现,代码参考raw/model.py。 

初始化变量:  

        self.X = tf.compat.v1.placeholder(dtype=tf.float32, shape=[None, 28, 28, 1], name='x_img')
        self.Y = tf.compat.v1.placeholder(dtype=tf.float32, shape=[None, 10], name='y_num')
        ...
        self.CW1 = tf.Variable(tf.random.normal([5, 5, 1, 32]), name='conv_1_weight')
        self.CB1 = tf.Variable(tf.random.normal([32]), name='conv_1_bias')
        ...

定义卷积、池化等操作:  

def conv2d(x, kernel, bias, strides=1, name='conv2d'):
    ishape = x.get_shape()
    convo = tf.nn.conv2d(x, kernel, strides, padding='VALID')
    biaso = tf.nn.bias_add(convo, bias)
    oshape = biaso.get_shape()
    print('op %s shape %s ==> %s' % (name, ishape, oshape))
    return biaso

构图:  

        c1 = conv2d(self.X, self.CW1, self.CB1, name='conv2d_1')
        p1 = maxpool2d(c1, name='max_pool2d_1')
        ...
        self.prediction = tf.nn.softmax(out, name='prediction')
        self.out_node_name = 'prediction'

构造损失函数:  

        self.loss_op = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=out, labels=self.Y))
        optimizer = tf.compat.v1.train.AdamOptimizer(learning_rate=0.009)
        self.train_op = optimizer.minimize(self.loss_op)

        correct_pred = tf.equal(tf.argmax(self.prediction, 1), tf.argmax(self.Y, 1))
        self.accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32)

训练:  

            for step in range(5):
                for batch in range(int(size / batch_size)):
                    sess.run(self.train_op, feed_dict={self.X: img[batch * batch_size: batch * batch_size + batch_size],
                        self.Y: lab[batch * batch_size: batch * batch_size + batch_size],
                        self.D: 0.2})

执行训练:  

python3 train.py

keras模型

keras模型为高度抽象模型,不需要像RAW模型那样定义底层变量,在构图中手动计算参数变量与tensor的关系,一切通过抽象好的模型叠加即可,参数在keras内部自动定义和计算,基本由构图和训练两部即可完成。  

构图,注意要指定keras.Input否则导出的模型没有Placeholder:  

        X = keras.Input(shape=(28, 28, 1), name='x_img')
        c1 = layers.Conv2D(32, 5)(X)
        p1 = layers.MaxPool2D(2, 2)(c1)
        ...
        Y = layers.Dense(10, activation="softmax")(f2)

训练:  

        self.model.compile(optimizer=keras.optimizers.Adam(),
                loss=keras.losses.CategoricalCrossentropy(),
                metrics=[keras.metrics.CategoricalAccuracy()])
        ...
        self.model.fit(img, lab, batch_size=batch_size, epochs=2, validation_data=(timg, tlab))

执行训练:  

python3 train.py --model keras

keras sequence模型

sequence模型是更加抽象的针对串行执行模型定制的一种模式,其语法更加简洁。  

构图,一样,要注意要指定keras.Input否则导出的模型没有Placeholder:

        self.model = keras.Sequential()
        X = keras.Input(shape=(28, 28, 1), name='x_img')
        self.model.add(X)
        self.model.add(layers.Conv2D(32, 5))
        self.model.add(layers.MaxPool2D(2, 2))
        ...
        Y = layers.Dense(10, activation="softmax")
        self.model.add(Y)

训练:  

        self.model.compile(optimizer=keras.optimizers.Adam(),
                loss=keras.losses.CategoricalCrossentropy(),
                metrics=[keras.metrics.CategoricalAccuracy()])
        ...
        self.model.fit(img, lab, batch_size=batch_size, epochs=2, validation_data=(timg, tlab))

执行训练:  

python3 train.py --model keras_seq

模型保存

模型保存是一个需要特别注意的地方,目前tensorflow至少存在4种或者更多的模型或者训练结果的保存方式,与Ascend310兼容的模型格式是将参数与图以常量方式保存为二进制的proto文件方式,这样可以在离线推理中使用,这一点要特别注意,以下重点介绍。  

执行完训练后,在代码路径下会生产如下目录模型保存的目录结构:  

── saved_model
   ├── checkpoint    #------------------> checkpoint模型结果
   │   ├── checkpoint
   │   ├── saved_ckpt.data-00000-of-00001
   │   ├── saved_ckpt.index
   │   └── saved_ckpt.meta
   ├── graph         #------------------> 只有graph节点,没有weight
   │   └── saved_graph.pbtxt
   ├── trainsaver    #------------------> saved_model格式,checkpoint的另一种形式
   │   ├── saved_model.pb
   │   └── variables
   │       ├── variables.data-00000-of-00001
   │       └── variables.index
   └── whole_graph   #------------------> Ascend310兼容模型
       ├── saved_model.pb
       └── saved_model.pbtxt


Ascend310兼容方式

通过给定训练完成的session与prediction的图节点,将prediction的子图和相关的weight参数转换为一张常量的图,然后序列化为二进制的protobuf文件。  

    def saved_graph_and_variable(self, sess, saved_dir):
        ...
        out_graph = tf.compat.v1.graph_util.convert_variables_to_constants(sess, sess.graph_def, [self.out_node_name])
        with open(os.path.join(saved_dir, 'saved_model.pb'), 'wb+') as f:
            f.write(out_graph.SerializeToString())
            f.close()
        tf.io.write_graph(out_graph, saved_dir, 'saved_model.pbtxt')

**注意,所有的模型必须有Placeholder,否则无法做离线转换,RAW方式是必选,keras要加keras.Input,不加虽然能训练成功,但是做离线模型转换时会失败**。  

**keras的session获取**:  

tf.compat.v1.keras.backend.get_session()


**keras的prediction节点名字获取**:  

        Y = layers.Dense(10, activation="softmax")(f2)
        self.out_node_name = Y.op.name

**keras_seq中的prediction节点名字获取**:  

        Y = layers.Dense(10, activation="softmax")
        self.model.add(Y)
        self.out_node_name = Y.output.op.name


其中saved_model.pb为二进制的protobuf文件,为可用Ascend310兼容模型文件,saved_model.pbtxt为文本格式,作为校验,可以查看其中数据,可以看到内部有图节点由weight值。  

其他几种方式介绍

checkpoint模型文件,可以在训练过程中保存不同checkpoint,防止最终训练失败或者崩溃后,中间过程的训练结果丢失。    

    def saved_checkpoint(self, sess, saved_dir):
        ...
        saver = tf.compat.v1.train.Saver()
        saver.save(sess, os.path.join(saved_dir, 'saved_ckpt'))

saved_model也是一种checkpoint,是目前比较常用的方式,提供了save和restore:  

    def saved_model_save(self, sess, saved_dir):
        ...
        builder = tf.compat.v1.saved_model.Builder(saved_dir)
        builder.add_meta_graph_and_variables(sess, ["saved_model"])
        builder.save()


graph,只保存了graph节点,没有weight,不能被使用:  

    def saved_graph(self, sess, saved_dir):
        ...
        tf.io.write_graph(sess.graph_def, saved_dir, 'saved_graph.pbtxt')

目前saved_model和checkpoint的结果还不能作为Ascend310离线推理使用,但可以在其他Ascend做在线推理用。  


离线模型转换

前面训练号模型后,使用Ascend310提供的ATC模型转换工具,可以将tensorflow模型转换为Ascend310格式的模型文件,然后使用Ascend310提供的推理库加载并运行。  

运行环境: Ubuntu x86_64  

安装开发环境

Ascend社区已经提供了完整的开发环境安装手册,请参考[链接](https://support.huaweicloud.com/instg-cli-cann/atlascli_03_0023.html), 软件安装> 软件安装指南 (开发&运行场景, 通过命令行方式) > 安装开发环境(推理)> 安装前准备> Ubuntu x86_64系统。  

以下操作建议在root用户下执行。  

  • 创建HwHiAiUser用户  
# useradd -m HwHiAiUser


  • 按照要求按照必备软件与Python

注意Python版本一定是3.7.5版本,一定要完整按照手册说明逐步完成。  

  • 准备软件包  

参考手册中软件包准备章节,链接,下载`Ascend-cann-toolkit_20.1.rc1_linux-x86_64.run`这个文件。  

  • 安装软件包  
# chmod +x Ascend-cann-toolkit_20.1.rc1_linux-x86_64.run
# ./Ascend-cann-toolkit_20.1.rc1_linux-x86_64.run --install


记录一下这个路径,后面会用到: `/usr/local/Ascend/ascend-toolkit/latest`。  

运行ATC模型转换

详细手册参考: 链接,推理场景> 开发辅助工具指南> ATC工具使用指南> 使用入门> 准备动作。  

  • 设置环境变量

install_path即为上面安软件包安装的路径,`/usr/local/Ascend/ascend-toolkit/latest`。  

export install_path=/usr/local/Ascend/ascend-toolkit/latest
export PATH=/usr/local/python3.7.5/bin:${install_path}/atc/ccec_compiler/bin:${install_path}/atc/bin:$PATH
export PYTHONPATH=${install_path}/atc/python/site-packages:${install_path}/atc/python/site-packages/auto_tune.egg/auto_tune:${install_path}/atc/python/site-packages/schedule_search.egg:$PYTHONPATH
export LD_LIBRARY_PATH=${install_path}/atc/lib64:$LD_LIBRARY_PATH
export ASCEND_OPP_PATH=${install_path}/opp


  • 执行模型转换
# atc --model=./saved_model/whole_graph/saved_model.pb --framework=3 --output=./output/hand_write --soc_version=Ascend310


手册的模板是这样写的,但是你会发现会报如下的错误:  

ATC start working now, please wait for a moment.
ATC run failed, Please check the detail log, Try 'atc --help' for more information
E19000: Path[/usr/local/Ascend/ascend-toolkit/20.1.rc1/x86_64-linux/opp/op_impl/custom/ai_core/tbe/config/ascend310]'s realpath is empty, errmsg[The file path does not exist.]
E10001: Invalid value for x_img[-1], maybe you should set input_shape to specify its shape.


莫慌,这个是因为tensorflow在训练的时候,我们指定的输入tensor x_img的shape是[None, 28, 28, 1],第一维是batch,这个是可变的,在转换的时候要手动指定一下:  

# atc --model=./saved_model/whole_graph/saved_model.pb --framework=3 --input_shape x_img:1,28,28,1 --output=./output/hand_write --soc_version=Ascend310
ATC start working now, please wait for a moment.
ATC run success, welcome to the next use.


OK,转换成功,--input_shape指定的x_img即输入的tensor名字,第一维可以根据自身需要指定。  


总结

确保成功的几个注意点:  

  • 模型保存的方式非常重要,一定要使用Ascend310兼容的方式保存。
  • 模型一定要设置Placeholder,keras对应为keras.Input。
  • 开发环境安装一定严格按照手册指导完成。
  • 转要手动指定input的shape。

好了,关于离线推理模型训练与转换就介绍到这里,下一篇继续介绍离线模型加载与运行,敬请期待。  


著作权归作者所有,禁止转载  

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。