日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

TensorRT(3)-C++ API使用:mnist手写体识别

發布時間:2024/9/27 c/c++ 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TensorRT(3)-C++ API使用:mnist手写体识别 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本節將介紹如何使用tensorRT C++ API 進行網絡模型創建。

1 使用C++ API 進行 tensorRT 模型創建

還是通過 tensorRT官方給的一個例程來學習。

還是mnist手寫體識別的例子。上一節主要是用 tensorRT提供的NvCaffeParser來將 Caffe中的model 轉換成tensorRT中特有的模型結構。NvCaffeParser是tensorRT封裝好的一個用以解析Caffe模型的工具 (較頂層的API),同樣的還有 NvUffPaser是用于解析TensorFlow的工具。

除了以上兩個封裝好的工具之外,還可以使用tensorRT提供的C++ API(底層的API)來直接在tensorRT中創建模型。這時 tensorRT 相當于是一個獨立的深度學習框架了,這個框架和其他框架(Caffe, TensorFlow,MXNet等)一樣都具備搭建網絡模型的能力(只有前向計算沒有反向傳播)。

不同之處在于:

  • 這個框架不能用于訓練,模型的權值參數要人為給定;
  • 可以針對設定網絡模型(自己使用API創建網絡模型)或給定模型(使用NvCaffeParser或NvUffPaser導入其他深度學習框架訓練好的模型)做一系列優化,以加快推理速度(inference)

使用C++ API函數部署網絡主要分為四個步驟:

  • 創建網絡;
  • 為網絡添加輸入;
  • 添加各種各樣的層;
  • 設定網絡輸出;

以上,第1,2,4步驟在使用 NvCaffeParser 時也是有的。只有第3步是本節所講的方法中特有的,其實對于NvCaffeParser 工具來說,他只是把 第 3步封裝起來了而已。

如下,對比一下 NvCaffeParser 的使用方法,下面的代碼中只列出了關鍵部分的代碼。完整代碼請看上一節。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//build phase

INetworkDefinition* network = builder->createNetwork(); //1. 創建網絡

CaffeParser* parser = createCaffeParser();

std::unordered_map<std::string, infer1::Tensor> blobNameToTensor;

const IBlobNameToTensor* blobNameToTensor = //3. 添加各種各樣的層

parser->parse(locateFile(deployFile).c_str(), //NvCaffeParser 工具

locateFile(modelFile).c_str(), //把添加層的內容封裝起來了

*network,

DataType::kFLOAT);

for (auto& s : outputs)

network->markOutput(*blobNameToTensor->find(s.c_str())); // 4. 設定網絡輸出

ICudaEngine* engine = builder->buildCudaEngine(*network); //創建engine

//省略一些內容………………

//execution phase

IExecutionContext *context = engine->createExecutionContext(); //創建 context

int inputIndex = engine->getBindingIndex(INPUT_BLOB_NAME),

outputIndex = engine->getBindingIndex(OUTPUT_BLOB_NAME); //2.為網絡添加輸入

//省略一些內容………………

context.enqueue(batchSize, buffers, stream, nullptr); //調用cuda核計算

cudaStreamSynchronize(stream); //同步cuda 流

上述四個步驟對應部分已在注釋標出。可見 NvCaffeParser 工具中最主要的是 parse 函數,這個函數接受網絡模型文件(deploy.prototxt)、權值文件(net.caffemodel)為參數,這兩個文件是caffe的模型定義文件和訓練參數文件。parse 函數會解析這兩個文件并對應生成 tensorRT的模型結構。

對于NvCaffeParser 工具來說,是需要三個文件的,分別是:

  • 網絡模型文件(比如,caffe的deploy.prototxt)
  • 訓練好的權值文件(比如,caffe的net.caffemodel)
  • 標簽文件(這個主要是將模型產生的數字標號分類,與真實的名稱對應起來)

以下分步驟說明四個步驟:

1.1 創建網絡

先創建一個tensorRT的network,這個network 現在只是個空架子,比較簡單:

1

INetworkDefinition* network = builder->createNetwork();

1.2 為網絡添加輸入

所有的網絡都需要明確輸入是哪個blob,因為這是數據傳送的入口。

1

2

// Create input of shape { 1, 1, 28, 28 } with name referenced by INPUT_BLOB_NAME auto

data = network->addInput(INPUT_BLOB_NAME, dt, DimsCHW{ 1, INPUT_H, INPUT_W});

  • INPUT_BLOB_NAME 是為輸入 blob起的名字;

  • dt是指數據類型,有kFLOAT(float 32), kHALF(float 16), kINT8(int 8)等類型;

    1

    2

    3

    4

    5

    6

    7

    8

    //位于 NvInfer.h 文件

    enum class DataType : int

    {

    kFLOAT = 0, //!< FP32 format.

    kHALF = 1, //!< FP16 format.

    kINT8 = 2, //!< INT8 format.

    kINT32 = 3 //!< INT32 format. 這個是TensorRT新增的

    };

  • DimsCHW{ 1, INPUT_H, INPUT_W} 是指,batch為1(省略),channel 為1,輸入height 和width分別為 INPUT_H, INPUT_W的blob;

1.3 添加各種各樣的層

  • 以下示例是添加一個 scale layer

1

2

3

4

5

6

// Create a scale layer with default power/shift and specified scale parameter. float

scale_param = 0.0125f;

Weights power{DataType::kFLOAT, nullptr, 0};

Weights shift{DataType::kFLOAT, nullptr, 0};

Weights scale{DataType::kFLOAT, &scale_param, 1};

auto scale_1 = network->addScale(*data, ScaleMode::kUNIFORM, shift, scale, power);

主要就是 addScale 函數,后面接受的參數是這一層需要設置的參數。

scale 層的作用是對每個輸入數據進行冪運算

f(x)= (shift + scale * x) ^ power

層類型:Power

可選參數:

  power: 默認為1

  scale: 默認為1

  shift: 默認為0

就是一種激活層。

Weights 類的定義如下:

1

2

3

4

5

6

7

8

9

//NvInfer.h 文件

class Weights

{

public:

DataType type; //!< the type of the weights

const void* values; //!< the weight values, in a contiguous array

int64_t count; //!< the number of weights in the array

};

以上是不包含訓練參數的層,還有 Relu層,Pooling層等。

包含訓練參數的層,比如卷積層,全連接層,要先加載權值文件。

  • 以下示例是添加一個卷積層

1

2

3

4

5

6

7

8

9

// Add convolution layer with 20 outputs and a 5x5 filter.

// 加載權值文件,加載一次即可

std::map<std::string, Weights> weightMap = loadWeights(locateFile("mnistapi.wts"));

//添加卷積層

IConvolutionLayer* conv1 = network->addConvolution(*scale_1->getOutput(0), 20, DimsHW{5, 5}, weightMap["conv1filter"], weightMap["conv1bias"]);

//設置步長

conv1->setStride(DimsHW{1, 1});

第6行添加卷積層:

1

IConvolutionLayer* conv1 = network->addConvolution(*scale_1->getOutput(0), 20, DimsHW{5, 5}, weightMap["conv1filter"], weightMap["conv1bias"]);

*scale_1->getOutput(0) :獲取上一層 scale層的輸出20:卷積核個數,或者輸出feature map 層數DimsHW{5, 5}:卷積核大小weightMap["conv1filter"], weightMap["conv1bias"]:權值系數矩陣

上面的 mnistapi.wts 文件,是用于存放網絡中各個層間的權值系數的,該文件位于?/usr/src/tensorrt/data?文件夾中。

可以用notepad打開看一下,如下:

可見每一行都是一層的一些參數,比如 conv1bias 是指第一個卷積層的偏置系數,后面的0 指的是 kFLOAT 類型,也就是 float 32;后面的20是系數的個數,因為輸出是20,所以偏置是20個;下面一行是 卷積核的系數,因為是20個 5×5的卷積核,所以有 20×5×5=500個參數。其它層依次類推。

這個文件是例程中直接給的,感覺像是 用caffe等工具訓練后,將weights系數從caffemodel 中提取出來的。直接讀取caffemodel應該也是可以的,稍微改一下接口:解析caffemodel文件然后將層名和權值參數鍵值對存到一個map中,網上大概找了一下,比如?這個?,解析后的caffemodel如下所示:

conv1 最下面有一個 blobs結構,這個是weights系數;每一個包含參數的層(卷積,全連接等;激活層,池化層沒有參數)都有一個 blobs結構。只需將這些參數提取出來,保存到一個map中。

除此之外也可以添加很多其他的層,比如反卷積層,池化層,全連接層等,具體參考?英偉達官方API?。

添加層的過程就相當于 NvCaffeParser 工具中 parse 函數解析 deploy.prototxt 文件的過程。

1.4 設定網絡輸出

網絡必須知道哪一個blob是輸出的。

如下代碼,在網絡的最后添加了一個softmax層,并將這個層命名為 OUTPUT_BLOB_NAME,之后指定為輸出層。

1

2

3

4

// Add a softmax layer to determine the probability.

auto prob = network->addSoftMax(*ip2->getOutput(0));

prob->getOutput(0)->setName(OUTPUT_BLOB_NAME);

network->markOutput(*prob->getOutput(0));

那直接使用底層API有什么好處呢?看下表

FeatureC++PythonNvCaffeParserNvUffParser
CNNsyesyesyesyes
RNNsyesyesnono
INT8 CalibrationyesyesNANA
Asymmetric Paddingyesyesnono

上表列出了 tensorRT 的不同特點與 API 對應的情況。可以看到對于 RNN,int8校準(float 32 轉為 int8),不對稱 padding 來說,NvCaffeParser是不支持的,只有 C++ API 和 Python API,才是支持的。

所以說如果是針對很復雜的網絡結構使用tensorRT,還是直接使用底層的 C++ API,和Python API 較好。底層C++ API還可以解析像 darknet 這樣的網絡模型,因為它需要的就只是一個層名和權值參數對應的map文件。

2 官方例程

例程位于?/usr/src/tensorrt/samples/sampleMNISTAPI

2.1 build phase

1

2

3

4

5

//這個是main函數中的代碼片段

// create a model using the API directly and serialize it to a stream

IHostMemory *modelStream{nullptr};

//調用APIToModel函數,手動創建網絡模型

APIToModel(1, &modelStream);

APIToModel函數:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void APIToModel(unsigned int maxBatchSize, IHostMemory** modelStream)

{

// Create builder

IBuilder* builder = createInferBuilder(gLogger);

//下面這個createMNISTEngine函數才是真正手動創建網絡的過程

// Create model to populate the network, then set the outputs and create an engine

ICudaEngine* engine = createMNISTEngine(maxBatchSize, builder, DataType::kFLOAT);

assert(engine != nullptr);

// Serialize the engine

(*modelStream) = engine->serialize();

// Close everything down

engine->destroy();

builder->destroy();

}

createMNISTEngine函數如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

// Creat the engine using only the API and not any parser.

ICudaEngine* createMNISTEngine(unsigned int maxBatchSize, IBuilder* builder, DataType dt)

{

INetworkDefinition* network = builder->createNetwork();

// Create input tensor of shape { 1, 1, 28, 28 } with name INPUT_BLOB_NAME

ITensor* data = network->addInput(INPUT_BLOB_NAME, dt, Dims3{1, INPUT_H, INPUT_W});

assert(data);

// Create scale layer with default power/shift and specified scale parameter.

const float scaleParam = 0.0125f;

const Weights power{DataType::kFLOAT, nullptr, 0};

const Weights shift{DataType::kFLOAT, nullptr, 0};

const Weights scale{DataType::kFLOAT, &scaleParam, 1};

IScaleLayer* scale_1 = network->addScale(*data, ScaleMode::kUNIFORM, shift, scale, power);

assert(scale_1);

// Add convolution layer with 20 outputs and a 5x5 filter.

// 加載權值文件,加載一次即可

std::map<std::string, Weights> weightMap = loadWeights(locateFile("mnistapi.wts"));

// 添加卷積層

IConvolutionLayer* conv1 = network->addConvolution(*scale_1->getOutput(0), 20, DimsHW{5, 5}, weightMap["conv1filter"], weightMap["conv1bias"]);

assert(conv1);

//設置步長

conv1->setStride(DimsHW{1, 1});

// Add max pooling layer with stride of 2x2 and kernel size of 2x2.

IPoolingLayer* pool1 = network->addPooling(*conv1->getOutput(0), PoolingType::kMAX, DimsHW{2, 2});

assert(pool1);

pool1->setStride(DimsHW{2, 2});

// Add second convolution layer with 50 outputs and a 5x5 filter.

IConvolutionLayer* conv2 = network->addConvolution(*pool1->getOutput(0), 50, DimsHW{5, 5}, weightMap["conv2filter"], weightMap["conv2bias"]);

assert(conv2);

conv2->setStride(DimsHW{1, 1});

// Add second max pooling layer with stride of 2x2 and kernel size of 2x3>

IPoolingLayer* pool2 = network->addPooling(*conv2->getOutput(0), PoolingType::kMAX, DimsHW{2, 2});

assert(pool2);

pool2->setStride(DimsHW{2, 2});

// Add fully connected layer with 500 outputs.

IFullyConnectedLayer* ip1 = network->addFullyConnected(*pool2->getOutput(0), 500, weightMap["ip1filter"], weightMap["ip1bias"]);

assert(ip1);

// Add activation layer using the ReLU algorithm.

IActivationLayer* relu1 = network->addActivation(*ip1->getOutput(0), ActivationType::kRELU);

assert(relu1);

// Add second fully connected layer with 20 outputs.

IFullyConnectedLayer* ip2 = network->addFullyConnected(*relu1->getOutput(0), OUTPUT_SIZE, weightMap["ip2filter"], weightMap["ip2bias"]);

assert(ip2);

// Add softmax layer to determine the probability.

ISoftMaxLayer* prob = network->addSoftMax(*ip2->getOutput(0));

assert(prob);

prob->getOutput(0)->setName(OUTPUT_BLOB_NAME);

network->markOutput(*prob->getOutput(0));

// Build engine

builder->setMaxBatchSize(maxBatchSize);

builder->setMaxWorkspaceSize(1 << 20);

ICudaEngine* engine = builder->buildCudaEngine(*network);

// Don't need the network any more

network->destroy();

// Release host memory

for (auto& mem : weightMap)

{

free((void*) (mem.second.values));

}

return engine;

}

可見里面包含了很多 add* 函數,都是用于添加各種各樣的層的。可參考英偉達官方API?。

2.2 deploy phase

deploy階段基本與之前的無異。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

int main(int argc, char** argv)

{

………………

………………

// Deserialize engine we serialized earlier

// 創建運行時環境 IRuntime對象,傳入 gLogger 用于打印信息

IRuntime* runtime = createInferRuntime(gLogger);

assert(runtime != nullptr);

ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream->data(), trtModelStream->size(), nullptr);

assert(engine != nullptr);

trtModelStream->destroy();

//創建上下文環境,主要用于inference 函數中啟動cuda核

IExecutionContext* context = engine->createExecutionContext();

assert(context != nullptr);

//2.deploy 階段:調用 inference 函數,進行推理過程

// Run inference on input data

float prob[OUTPUT_SIZE];

doInference(*context, data, prob, 1);

………………

………………

}

doInference函數如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

void doInference(IExecutionContext& context, float* input, float* output, int batchSize)

{

const ICudaEngine& engine = context.getEngine();

// Pointers to input and output device buffers to pass to engine.

// Engine requires exactly IEngine::getNbBindings() number of buffers.

assert(engine.getNbBindings() == 2);

void* buffers[2];

// In order to bind the buffers, we need to know the names of the input and output tensors.

// Note that indices are guaranteed to be less than IEngine::getNbBindings()

const int inputIndex = engine.getBindingIndex(INPUT_BLOB_NAME);

const int outputIndex = engine.getBindingIndex(OUTPUT_BLOB_NAME);

// Create GPU buffers on device

CHECK(cudaMalloc(&buffers[inputIndex], batchSize * INPUT_H * INPUT_W * sizeof(float)));

CHECK(cudaMalloc(&buffers[outputIndex], batchSize * OUTPUT_SIZE * sizeof(float)));

// Create stream

cudaStream_t stream;

CHECK(cudaStreamCreate(&stream));

// DMA input batch data to device, infer on the batch asynchronously, and DMA output back to host

CHECK(cudaMemcpyAsync(buffers[inputIndex], input, batchSize * INPUT_H * INPUT_W * sizeof(float), cudaMemcpyHostToDevice, stream));

context.enqueue(batchSize, buffers, stream, nullptr);

CHECK(cudaMemcpyAsync(output, buffers[outputIndex], batchSize * OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost, stream));

cudaStreamSynchronize(stream);

// Release stream and buffers

cudaStreamDestroy(stream);

CHECK(cudaFree(buffers[inputIndex]));

CHECK(cudaFree(buffers[outputIndex]));

}

參考資料

  • caffe中的一些激活函數:Caffe學習系列(4):激活層(Activiation Layers)及參數 - denny402 - 博客園
  • caffemodel 解析:python讀取caffemodel文件 - ChrisZZ - 博客園
  • caffemodel 解析:http://www.cnblogs.com/zzq1989/p/4439429.html
  • tensorRT C++ API:TensorRT: TensorRT
  • tensorRT python API:TensorRT — NVIDIA TensorRT Standard Python API Documentation 8.4.0 documentation
  • tensorRT 開發者指南:Developer Guide :: NVIDIA Deep Learning TensorRT Documentation
  • NVIDIA Deep Learning SDK:NVIDIA Documentation Center | NVIDIA Developer
  • 總結

    以上是生活随笔為你收集整理的TensorRT(3)-C++ API使用:mnist手写体识别的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。