TensorRT&Sample&Python[end_to_end_tensorflow_mnist]

2022-11-16 235716 人工智能


本文是基于TensorRT 5.0.2基础上,关于其内部的end_to_end_tensorflow_mnist例子的分析和介绍。

1 引言

假设当前路径为:

TensorRT-5.0.2.6/samples

其对应当前例子文件目录树为:

# tree python

python
├── common.py
├── end_to_end_tensorflow_mnist
│   ├── model.py
│   ├── README.md
│   ├── requirements.txt
│   └── sample.py

2 基于tensorflow生成模型

其中只有2个文件:

  • model:该文件包含简单的训练模型代码
  • sample:该文件使用UFF mnist模型去创建一个TensorRT inference engine

首先介绍下model.py

# 该脚本包含一个简单的模型训练过程
import tensorflow as tf
import numpy as np


'''main中第一步:获取数据集 '''
def process_dataset():

    # 导入mnist数据集
    # 手动下载aria2c -x 16 https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
    # 将mnist.npz移动到~/.keras/datasets/
    #  tf.keras.datasets.mnist.load_data会去读取~/.keras/datasets/mnist.npz,而不从网络下载
    (x_train, y_train),(x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0

    # Reshape 
    NUM_TRAIN = 60000
    NUM_TEST = 10000
    x_train = np.reshape(x_train, (NUM_TRAIN, 28, 28, 1))
    x_test = np.reshape(x_test, (NUM_TEST, 28, 28, 1))
    return x_train, y_train, x_test, y_test


'''main中第二步:构建模型 '''
def create_model():

    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.InputLayer(input_shape=[28,28, 1]))
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(512, activation=tf.nn.relu))
    model.add(tf.keras.layers.Dense(10, activation=tf.nn.softmax))
    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    return model


'''main中第五步:模型存储 '''
def save(model, filename):

    output_names = model.output.op.name
    sess = tf.keras.backend.get_session()

    # freeze graph
    frozen_graph = tf.graph_util.convert_variables_to_constants(sess, sess.graph.as_graph_def(), [output_names])

    # 移除训练的节点
    frozen_graph = tf.graph_util.remove_training_nodes(frozen_graph)

    # 保存模型
    with open(filename, "wb") as ofile:
        ofile.write(frozen_graph.SerializeToString())


def main():

    ''' 1 - 获取数据'''
    x_train, y_train, x_test, y_test = process_dataset()

    ''' 2 - 构建模型'''
    model = create_model()

    ''' 3 - 模型训练'''
    model.fit(x_train, y_train, epochs = 5, verbose = 1)

    ''' 4 - 模型评估'''
    model.evaluate(x_test, y_test)

    ''' 5 - 模型存储'''
    save(model, filename="models/lenet5.pb")

if __name__ == '__main__':
    main()

在获得

models/lenet5.pb

之后,执行下述命令,将其转换成uff文件,输出结果如

'''该converter会显示关于input/output nodes的信息,这样你就可以用来在解析的时候进行注册;
本例子中,我们基于tensorflow.keras的命名规则,事先已知input/output nodes名称了 '''

[root@30d4bceec4c4 end_to_end_tensorflow_mnist]# convert-to-uff models/lenet5.pb
Loading models/lenet5.pb

3 基于tensorflow的pb文件生成UFF并处理

# 该例子使用UFF MNIST 模型去创建一个TensorRT Inference Engine
from random import randint
from PIL import Image
import numpy as np

import pycuda.driver as cuda
import pycuda.autoinit # 该import会让pycuda自动管理CUDA上下文的创建和清理工作

import tensorrt as trt

import sys, os
# sys.path.insert(1, os.path.join(sys.path[0], ".."))
# import common

# 这里将common中的GiB和find_sample_data,allocate_buffers,do_inference等函数移动到该py文件中,保证自包含。
def GiB(val):
    '''以GB为单位,计算所需要的存储值,向左位移10bit表示KB,20bit表示MB '''
    return val * 1 << 30

def find_sample_data(description="Runs a TensorRT Python sample", subfolder="", find_files=[]):
    '''该函数就是一个参数解析函数。
    Parses sample arguments.
    Args:
        description (str): Description of the sample.
        subfolder (str): The subfolder containing data relevant to this sample
        find_files (str): A list of filenames to find. Each filename will be replaced with an absolute path.
    Returns:
        str: Path of data directory.
    Raises:
        FileNotFoundError
    '''
    # 为了简洁,这里直接将路径硬编码到代码中。
    data_root = kDEFAULT_DATA_ROOT = os.path.abspath("/TensorRT-5.0.2.6/python/data/")

    subfolder_path = os.path.join(data_root, subfolder)
    if not os.path.exists(subfolder_path):
        print("WARNING: " + subfolder_path + " does not exist. Using " + data_root + " instead.")
    data_path = subfolder_path if os.path.exists(subfolder_path) else data_root

    if not (os.path.exists(data_path)):
        raise FileNotFoundError(data_path + " does not exist.")

    for index, f in enumerate(find_files):
        find_files[index] = os.path.abspath(os.path.join(data_path, f))
        if not os.path.exists(find_files[index]):
            raise FileNotFoundError(find_files[index] + " does not exist. ")

    if find_files:
        return data_path, find_files
    else:
        return data_path
#-----------------

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)

class ModelData(object):
    MODEL_FILE = os.path.join(os.path.dirname(__file__), "models/lenet5.uff")
    INPUT_NAME ="input_1"
    INPUT_SHAPE = (1, 28, 28)
    OUTPUT_NAME = "dense_1/Softmax"


'''main中第二步:构建engine'''
def build_engine(model_file):

    with trt.Builder(TRT_LOGGER) as builder, \
            builder.create_network() as network, \
            trt.UffParser() as parser:

        builder.max_workspace_size = GiB(1)

        # 解析 Uff 网络
        parser.register_input(ModelData.INPUT_NAME, ModelData.INPUT_SHAPE)
        parser.register_output(ModelData.OUTPUT_NAME)
        parser.parse(model_file, network)

        # 构建并返回一个engine
        return builder.build_cuda_engine(network)


'''main中第三步 '''
def allocate_buffers(engine):

    inputs = []
    outputs = []
    bindings = []
    stream = cuda.Stream()

    for binding in engine:

        size = trt.volume(engine.get_binding_shape(binding)) * engine.max_batch_size
        dtype = trt.nptype(engine.get_binding_dtype(binding))

        # 分配host和device端的buffer
        host_mem = cuda.pagelocked_empty(size, dtype)
        device_mem = cuda.mem_alloc(host_mem.nbytes)

        # 将device端的buffer追加到device的bindings.
        bindings.append(int(device_mem))

        # Append to the appropriate list.
        if engine.binding_is_input(binding):
            inputs.append(HostDeviceMem(host_mem, device_mem))
        else:
            outputs.append(HostDeviceMem(host_mem, device_mem))

    return inputs, outputs, bindings, stream


'''main中第四步 '''
# 从pagelocked_buffer.中读取测试样本
def load_normalized_test_case(data_path, pagelocked_buffer, case_num=randint(0, 9)):

    test_case_path = os.path.join(data_path, str(case_num) + ".pgm")

    # Flatten该图像成为一个1维数组,然后归一化,并copy到host端的 pagelocked内存中.
    img = np.array(Image.open(test_case_path)).ravel()
    np.copyto(pagelocked_buffer, 1.0 - img / 255.0)

    return case_num


'''main中第五步:执行inference '''
# 该函数可以适应多个输入/输出;输入和输出格式为HostDeviceMem对象组成的列表
def do_inference(context, bindings, inputs, outputs, stream, batch_size=1):

    # 将数据移动到GPU
    [cuda.memcpy_htod_async(inp.device, inp.host, stream) for inp in inputs]

    # 执行inference.
    context.execute_async(batch_size=batch_size, bindings=bindings, stream_handle=stream.handle)

    # 将结果从 GPU写回到host端
    [cuda.memcpy_dtoh_async(out.host, out.device, stream) for out in outputs]

    # 同步stream
    stream.synchronize()

    # 返回host端的输出结果
    return [out.host for out in outputs]


def main():

    ''' 1 - 寻找模型文件'''
    data_path = find_sample_data(
                              description="Runs an MNIST network using a UFF model file", 
                              subfolder="mnist")
    model_file = ModelData.MODEL_FILE

    ''' 2 - 基于build_engine函数构建engine'''
    with build_engine(model_file) as engine:

        ''' 3 - 分配buffer并创建一个流'''
        inputs, outputs, bindings, stream = allocate_buffers(engine)
       
        with engine.create_execution_context() as context:

            ''' 4 - 读取测试样本,并归一化'''
            case_num = load_normalized_test_case(data_path, pagelocked_buffer=inputs[0].host)

            ''' 5 - 执行inference,do_inference函数会返回一个list类型,此处只有一个元素'''
            [output] = do_inference(context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream)

            pred = np.argmax(output)
            print("Test Case: " + str(case_num))
            print("Prediction: " + str(pred))

if __name__ == '__main__':
    main()

结果如:

如何在Python中使用asyncio.shield防止关键任务被取消?

asyncio.shield必须用于关键清理操作(如日志落盘、事务提交、连接关闭),防止被取消中断导致数据不一致;应只包裹协程本身(shield(coro)),而非Task,且内部子await需单独shield。 asyncio.shield什么时候必须用? 当一个asyncio.Task正在执行关键清理、日志落盘、数据库事务提交或网络连接优雅关闭时,如果外部调用task.cancel()或超时触...

为什么Python单例模式在多线程环境下会失效?

__new__中判空非原子操作会导致多线程重复创建实例;DCL通过无锁初判+加锁后复判解决,需配合__init__幂等防护。 __new__中的if判断不是原子操作 线程A执行到ifcls._instanceisNone时为True,刚准备调用super().__new__(cls),此时被调度挂起;线程B同样判空成功,也进入创建逻辑——结果两个线程各自生成实例并赋值给_instance。GIL不...

如何修复Python aiohttp请求时出现的ClientPayloadError?

ClientPayloadError表明服务端提前关闭连接,非客户端代码错误;常见于服务端超时、拒绝大响应或代理截断,需通过抓包、检查响应头、服务端日志及添加读取超时和异常处理来定位解决。 ClientPayloadError通常意味着服务端提前关闭了连接 这个错误不是客户端代码写错了,而是aiohttp在读取响应体时发现服务端已经断开了连接。常见于服务端超时、主动拒绝大响应、或代理/网关(如Ng...

为什么在大型Python项目中推荐使用Monorepo环境管理?

Monorepo是应对跨子包频繁修改、版本对齐成本高、CI耗时爆炸等痛点的止损方案;需统一依赖管理、确保本地与CI环境一致,并严守子包独立测试边界。 Monorepo不是“更先进”,而是对特定规模和协作模式的项目更可控——当Python项目中出现跨子包频繁修改、版本对齐成本高、CI耗时爆炸或本地开发反复pipinstall-e失败时,Monorepo就不是可选项,而是止损点。 跨包依赖变更必须原子...

为什么Python爬虫在解析复杂表格时推荐使用Pandas库?

pandas.read_html能自动解析HTML表格并修复合并单元格、多级表头等结构,但需合理配置match、header、skiprows等参数,并处理JS渲染、数据类型、空值等问题,否则易导致列错位、类型错误等隐患。 因为pandas.read_html能直接把HTML表格转成DataFrame,省去手动遍历DOM、提取行列、对齐结构的麻烦——前提是表格语义清晰、嵌套不深。 为什么不用Bea...

为什么在Python项目中推荐使用Poetry管理包依赖?

poetryinstall与pipinstall-rrequirements.txt的本质差异在于:前者是“解析后装”,基于poetry.lock中已验证的完整依赖图确保版本兼容并自动激活专属虚拟环境;后者是“盲装”,仅按文本顺序安装、不解析依赖冲突、不隔离环境,易导致静默覆盖和全局污染。 因为pipinstall-rrequirements.txt无法保证依赖一致性,而poetryinstall...

怎样在Python Tkinter中使用线程池处理高并发的GUI数据更新?

不能直接用ThreadPoolExecutor更新Tkinter控件,因为Tkinter的Tcl解释器只允许主线程操作UI;子线程(包括线程池中的工作线程)调用widget方法会引发TclError或卡死;必须通过queue.Queue+root.after()将结果安全传回主线程更新。 为什么不能直接用concurrent.futures.ThreadPoolExecutor更新Tkinter控...

如何在Python中通过NumPy实现一维信号的平滑处理?

用np.convolve做移动平均需归一化kernel并截取中间部分;推荐镜像填充防边界畸变,小核用cumsum加速;savgol_filter参数需满足polyorder<window_length且window_length为奇数。 用np.convolve做简单移动平均平滑,但要注意边界处理 直接调用np.convolve是最常用的一维信号平滑方式,但它默认做的是“全卷积”,输出比原信...