TNN iOS非影像模型入門

woodWu發表於2021-08-28

注:本文同步釋出於微信公眾號:stringwu的網際網路雜談TNN iOS 非影像模型入門指南

1 背景

TNN是騰訊優圖實驗室開源的高效能、輕量級神經網路推理框架TNN,github上也有比較詳細的例子來說明如何在端上執行影像類的模型,但demo 更多是影像類相關的示例,而且裡面做了一層層的封裝,很難讓一個初學者直接上手一步步構建出可推理的結果,
本文主要從初學者的角度出發,按照TNNAPI文件一步步構建出非影像模型的入門文件。(本文不再詳述如何編譯和整合TNN工程,有需要的同學可直接參考Demo文件);

2 構建

TNN的推理流程主要包括模型的解析,網路構建,輸入設定,輸出獲取

2.1 模型解析

模型檔案包括兩個部分:

  • *.tnnmodel
  • *.tnnproto

模型解析的步驟包括:

  • 獲取模型檔案的路徑
  • 解析檔案內容
  • 初始化模型
	// 獲取模型檔案 (提前把對應的模型檔案整合到工程中)
    auto model_path = [[NSBundle mainBundle] pathForResource:@"model/recommend/recommend.tnnmodel"
                                                      ofType:nil];
    auto proto_path = [[NSBundle mainBundle] pathForResource:@"model/recommend/recommend.tnnproto" ofType:nil];

    //解析檔案內容
    string proto_content = [NSString stringWithContentsOfFile:proto_path encoding:NSUTF8StringEncoding error:nil].UTF8String;
    
    string model_content = [data_mode length] > 0 ? string((const char *)[data_mode bytes], [data_mode length]) : "";

    //模型的配置
    TNN_NS::ModelConfig model_config;
    model_config.model_type = TNN_NS::MODEL_TYPE_TNN;  // 指定模型的型別為TNN
    model_config.params.push_back(proto_content);
    model_config.params.push_back(model_content);

    auto tnn = std::make_shared<TNN_NS::TNN>(); //例項化TNN 的例項
    //初始化模型
    Status ret = tnn->Init(model_config);
    //結果為TNN_OK時才為模型初始化成功
    if (ret != TNN_OK) {
        return;
    }

2.2 網路構建

網路的構建需要配置TNN_NS::NetworkConfig,這個配置需要指定device_typelibrary_path,在iOS中的device_type正常是使用TNN_NS::DEVICE_ARMTNN_NS::DEVICE_METAL就可以了,但筆者在實際嘗試時,發現device_type指定這兩個型別都沒有辦法正常跑通,後與TNN相關同學諮詢請教後,使用了TNN_NS::DEVICE_NAIVE才正常跑通,具體的原因TNN的同學還在幫忙定位中。(如果發現資料正常時,流程沒有辦法跑通的話,可以多換幾個device_type看看)

  auto library_path = [[NSBundle mainBundle] pathForResource:@"tnn.metallib" ofType:nil];
  //shape資料
  auto shape_path = [[NSBundle mainBundle] pathForResource:@"model/recommend/tnn_input.json" ofType:nil];
  //將shape資料轉成NSDictionary
  NSData *data_shape = [NSData dataWithContentsOfFile:shape_path];
  NSDictionary * dictionary_shape = [NSJSONSerialization JSONObjectWithData:data_shape options:NSJSONReadingMutableLeaves error:nil];
   //構造出TNN可識別的shape資料
  InputShapesMap sdkInputShape = {};
   for (NSString *key in dictionary_shape) {
        NSDictionary*tmpValue = dictionary_shape[key];
        NSLog(@"InputShapesMap: println key %@****",key);
        NSInteger int1 = [tmpValue[@"dim1"]intValue];
        NSInteger int2 = [tmpValue[@"dim2"]intValue];
        NSLog(@"InputShapesMap222: println key %ld,%ld****",int1,int2);
        TNN_NS::DimsVector nc = {(int)int1,(int)int2};
        sdkInputShape.insert(std::pair<std::string, TNN_NS::DimsVector>(std::string(key.UTF8String),nc));
    }


   // 構造出net_config
    TNN_NS::NetworkConfig net_config;
    net_config.device_type = TNN_NS::DEVICE_NAIVE; // 指定device_type,如果跑不成功,可以多換幾個type試試
    net_config.library_path = {library_path.UTF8String};

   TNN_NS::Status error;
   //構造出TNN網路物件
   auto net_instace = tnn->CreateInst(net_config, error,sdkOptions->input_shapes);
     //結果為TNN_OK時才為網路構建成功
    if (error != TNN_OK) {
        return;
    }


2.3 輸入設定

輸入設定主要是通過TNN的方法 SetInputMat 來完成的;

//從檔案獲取模型的輸入
auto mock_input_path = [[NSBundle mainBundle] pathForResource:@"model/recommend/mock_input.json" ofType:nil];
//將資料轉換成NSDictionary
NSData *data_mock = [NSData dataWithContentsOfFile:mock_input_path];
NSDictionary * dictionary_mock = [NSJSONSerialization JSONObjectWithData:data_mock options:NSJSONReadingMutableLeaves error:nil];

//將資料轉換成TNN的輸入格式
std:map<std::string,std::vector<float>> inputDatas = {};
for (NSString *key in dictionary_mock) {
    NSArray *valueArray = dictionary_mock[key];
    __block std::vector<float> valueVector ={};
    valueVector.reserve([valueArray count]);
    [valueArray enumerateObjectsUsingBlock:^( id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
          valueVector.push_back([obj floatValue]);
    }];
    inputDatas[[key UTF8String]] = valueVector;
  }

BlobMap blob_map;
net_instace ->GetAllInputBlobs(blob_map);
//遍歷所有的輸入key
for (const auto&item : blob_map) {
    //獲取對應的key
    std::string name = item.first;
    //獲取key對應的輸入資料
    std::vector<float> tmpItem = inputDatas.at(name);
    //獲取key對應的shape資料
    DimsVector shape = sdkOptions->input_shapes[name];
    //構造出input_mat
    auto input_mat = std::make_shared<TNN_NS::Mat>(net_config.device_type,TNN_NS::NCHW_FLOAT,shape,tmpItem.data());

    MatConvertParam input_convert_params = TNN_NS::MatConvertParam();
    //把輸入資料通過SetInputMat方法給到TNN引擎
    auto status = net_instace->SetInputMat(input_mat,input_convert_params,name);
    //結果為TNN_OK時才為設定輸入成功
    if (status != TNN_OK) {
        NSLog(@"setInputmat error ");
        return;
     }

    }

//執行推理
auto forwardStatus = net_instace->Forward();
if (forwardStatus != TNN_OK) {
    NSLog(@"forwardStatus error ");
    return;
}

2.4 輸出獲取

獲取推理的結果,是通過TNNGetAllOutputBlobs介面來完成


BlobMap out_blob_map;
//獲取所有的輸出key
net_instace ->GetAllOutputBlobs(out_blob_map);
//所有的結果都輸出放在 mat_map裡面
std::map<std::string, std::shared_ptr<TNN_NS::Mat> > mat_map = {};
//遍歷所有輸出key
for (const auto&item: out_blob_map) {
    auto name = item.first;
    MatConvertParam output_convert_params = TNN_NS::MatConvertParam();
    std::shared_ptr<TNN_NS::Mat> output_mat = nullptr;
    auto status = net_instace->GetOutputMat(output_mat,output_convert_params,name,net_config.device_type);
    if (status != TNN_OK) {
        NSLog(@"outPutError error ");
        return;
    }
    mat_map[name] = output_mat;
    NSLog(@"outPutname %@",[NSString stringWithFormat:@"%s", name.c_str()]);
}

在得到輸出結果的map後mat_map後,就可以根據業務的情況進行結果的解析,本文使用的模型最終的結果是一個float值

//推薦模型的推理結果是存在在pred欄位裡面;
auto scores = mat_map["pred"];
auto dims = scores ->GetDims();
if (dims.size() <= 0) {
    NSLog(@"scores dims  error ");
    return;
}

//獲取當前的推理結果
float *score_data = static_cast<float*>(scores->GetData());
NSLog(@"scores is %f ",score_data[0]);

相關文章