모형 배치의 dll 패키지

9537 단어 ncnn
본고는 주로 모형을 훈련한 후에 어떻게 dll 인터페이스로 봉하여 다른 언어로 호출할 수 있는지 설명한다.신경 네트워크 프레임워크는ncnn을 예로 들면 다른 프레임워크는 대체적으로 사상이 다르지 않기 때문에 본고의 사상을 참고하거나 모델을 ncnn으로 바꾸어 본고의 교과서를 직접 사용해도 된다.
포장하기 전에 먼저 포장의 목표를 알아야 한다. 다음과 같다.
       1. 포장된 서류.우리의 목표는 dll 파일을 생성하는 것이다. 만약에 코드를 dll로만 포장하면 모델 파일은 독립되어 포장된 dll에 코드만 포함하고 배치할 때 모델 파일을 함께 발표해야 한다. 즉, dll+모델의 조합 발표는 매우 불편하고 간략미에 부합되지 않는다.본고는 다른 방식으로 모델 파일을 메모리에 읽고 코드와 함께 dll에 통합시켜 발표할 때 dll 파일만 있으면 된다.여기서는 ncnn 모델의 로드 방식에 대한 질문으로 이전 블로그를 참조하십시오.https://blog.csdn.net/Enchanted_ZhouH/article/details/106063552, 이 글은 모바일에너트v2는 포장의 과정을 예시로 설명하고 모바일에넷v2에 대응하는ncnn모델의 획득은 이 블로그의resnet18을mobilenet 로 바꾸기만 하면 됩니다v2면 됩니다.
       2. 포장 방식.정적 패키지 or 동적 패키지?정적 패키지는 모든 의존 라이브러리를 dll에 함께 패키지하는 것을 말한다. 이렇게 dll은 어느 환경에서든 실행할 수 있다.동적 패키지란 dll이 실행될 때 자동으로 의존 관계에 따라 현재 장치에서 의존 라이브러리를 찾는 것을 말한다. 만약에 두 장치(패키지 장치/배치 장치) 환경이 일치하지 않으면 dll를 호출할 때 오류가 발생하고 xxx를 찾을 수 없음을 알린다.dll.비교적 관건적인 것은vcruntime 라이브러리입니다. 서로 다른 장치에 이 라이브러리가 설치되어 있거나 버전이 다른 것은 아닙니다.더욱 좋은 이식성을 얻기 위해 본고는 정적 포장을 채택할 것이다. 주의해야 할 점은 정적 포장을 할 때 모든 의존 라이브러리는 정적 번역이 필요하다는 것이다.
       3. 포장된 자리수.운영체제는 32비트와 64비트로 나뉘는데 32비트의 dll은 32비트 환경에서 실행되고 64비트의 dll은 64비트 환경에서 실행된다.실제 테스트에서 32비트의 dll은 32비트의python에서만 실행되며 64비트는 동일합니다.본고는 자주 사용하는 64비트로 포장하고python으로 dll을 호출하여 테스트합니다.
종합적으로 본고는 64비트 환경에서 정적 패키지 방식으로 코드와 모델을 dll에 통합시켰다.
전체 텍스트의 코드, 메모리를 읽는 모델과 포장된 dll 등이github에 놓여 있습니다. 주소는 다음과 같습니다.https://github.com/PigTS/model-package-dll
1. ncnn의 컴파일
ncnn의 컴파일 참조 홈페이지:https://github.com/Tencent/ncnn/wiki/how-to-build#build- for-windows-x64-using-visual-studio-community-2017, 정적 컴파일 옵션을 켜는 것이 주요 변경 사항입니다.
VS 컴파일 도구를 열면 본고는 VS2015를 예로 들면 다른 버전의 조작 절차는 기본적으로 다음과 같다. 시작-> 프로젝트-> Visual Studio 2015-> VS2015 x64 본체 도구 명령 알림부호.
만약 32비트의 dll를 포장해야 한다면, 이 도구는 x86을 선택하면 됩니다. 후속 모든 라이브러리는 x86에서 컴파일되고, 본문의 후속은 x64에서 컴파일됩니다.
먼저 protobuf 라이브러리를 다음과 같이 컴파일합니다.
download protobuf-3.4.0 from https://github.com/google/protobuf/archive/v3.4.0.zip
> cd 
> mkdir build-static
> cd build-static
> cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=ON ../cmake
> nmake
> nmake install

중요한 변화는 - DprotobufMSVC_STATIC_RUNTIME=ON, 즉 정적 컴파일을 엽니다. 공식 컴파일 옵션은 기본적으로 닫힙니다. 프로토버프의 CMakeLists.txt 파일에서 이 옵션의 내용은 다음과 같습니다(124~132 줄).
if (MSVC AND protobuf_MSVC_STATIC_RUNTIME)
    foreach(flag_var
        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
      if(${flag_var} MATCHES "/MD")
        string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
      endif(${flag_var} MATCHES "/MD")
    endforeach(flag_var)
  endif (MSVC AND protobuf_MSVC_STATIC_RUNTIME)

주로/MD를 모두/MT로 바꾸는데 그 중에서/MD는 동적 컴파일이고/MT는 정적 컴파일이며 모두release 아래에 있다. 만약에 접미사에 d, 예를 들어/MDd와/MTd를 더하면 대응하는 debug 버전이다.
다음은 ncnn, 공식 ncnn의 CMakeLists를 컴파일합니다.txt 파일에 이 옵션을 정적 컴파일하지 않았습니다. protobuf의 CMakeLists에 따라.txt 파일을 더하면 됩니다. NCNN 을 정의합니다.MSVC_STATIC_RUNTIME 컴파일 옵션, 업데이트된 CMakeLists.txt 파일은 위에서 제시한github 주소에 있고, 파일은ncnn 디렉터리에 있으며, 컴파일 명령은 다음과 같습니다.
cd 
> mkdir build-static
> cd build-static
> cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=%cd%/install -DProtobuf_INCLUDE_DIR=/build-vs2017/install/include -DProtobuf_LIBRARIES=/build-vs2017/install/lib/libprotobuf.lib -DProtobuf_PROTOC_EXECUTABLE=/build-vs2017/install/bin/protoc.exe -DNCNN_MSVC_STATIC_RUNTIME=ON -DNCNN_VULKAN=OFF ..
> nmake
> nmake install

주의 더하기 - DNCNNMSVC_STATIC_RUNTIME=ON, ncnn의 정적 컴파일을 엽니다.
이로써 ncnn 컴파일링이 완료되었습니다.build-static/install 안의 헤더 파일과 라이브러리 파일만 있으면 됩니다.
2. dll의 포장
dll는 다른 언어로 호출하거나 다른 기계에 이식할 수 있는 인터페이스입니다.dll를 포장하려면 먼저 드러날 인터페이스가 무엇인지 알아야 한다. 본고가 디자인한 인터페이스 헤드 파일은 src/interface.h, 기본 인터페이스는 다음과 같습니다.
//init model
void initModel();
//identify
const char* identify(const char* img_base64);
//free model
void freeModel();

주로 다음과 같은 기능을 실현한다.모형 초기화하기;2. 이미지로 입력된base64 인코딩 흐름을 식별하여 전송하기 편리하고 유형+확률의char지침으로 되돌려줍니다.3. 모델 메모리를 방출한다.
코드의 실현 부분에 관해서는 src/interface를 직접 보십시오.cpp면 됩니다.그림% 1개의 캡션을 편집했습니다.https://github.com/nothings/stb) - 이 라이브러리를 사용하면 이미지만 읽을 수 있고 작고 가볍고 포장이 용이합니다.
다음은 dll를 포장할 준비를 하고 절차는 다음과 같다.VS에 빈 항목을 만들고 ncnn의 헤더 파일과 라이브러리를 가져옵니다.2.github에서 src의 코드(.h/.cpp)를 대응하는 헤더 파일과 원본 파일로 가져옵니다.3. 프로필을 추가한다. 즉, 원본 파일에 src/interface를 추가한다.def.
       interface.def 파일은 다음과 같이 출력하는 방법, 즉 dll의 인터페이스를 정의하는 템플릿 정의 파일입니다.
LIBRARY ImageRecognitionEngine

EXPORTS
initModel
identify
freeModel

마지막으로 프로젝트의 속성 페이지에서 라이브러리에서 다중 루틴(/MT)을 선택하여 앞과 일치합니다.오른쪽 단추로 항목 -> 속성 -> C/C++ -> 코드 생성 -> 라이브러리 -> 다중 루틴 선택 (/MT).
전체 프로젝트를 컴파일하면 dll 파일을 생성할 수 있습니다. 생성된 dll 파일은github의 dll 디렉터리를 보십시오. dll/static 디렉터리는 정적 컴파일된 dll 파일이고 dll/dynamic 디렉터리는 동적 컴파일된 dll 파일입니다.
3. python 호출 dll 테스트
python 호출 dll의 예는 다음과 같습니다(파일:python/dll test.py).
import ctypes
import base64
import time

#test img
img_path = "../img/test.jpg"
with open(img_path, 'rb') as f:
    img_base64 = base64.b64encode(f.read())
#load dll
IREngine = ctypes.CDLL("../dll/static/ImageRecognitionEngine.dll")
#IREngine = ctypes.CDLL("../dll/dynamic/ImageRecognitionEngine.dll")
#config interface argtypes and restypes
IREngine.initModel.argtypes = []
IREngine.initModel.restype = ctypes.c_void_p
IREngine.identify.argtypes = [ctypes.c_char_p]
IREngine.identify.restype = ctypes.c_char_p
IREngine.freeModel.argtypes = []
IREngine.freeModel.restypes = ctypes.c_void_p
#init model
IREngine.initModel()
#indentify
count = 0
while count < 100:
    res = IREngine.identify(img_base64).decode()
    cls, value = res.split()
    print("[dll]--->predicted class: %s, predicted value: %s" % (cls, value))
    count += 1
    time.sleep(150/1000) #sleep 150ms
#free model
IREngine.freeModel()

dll 예측 결과는 다음과 같습니다.
[dll]--->predicted class: 920, predicted value: 19.291550
...

pytorch가 실행된 결과와 비교하면 pytorch 테스트의 코드는 다음과 같다(python/pytorch test.py).
import torch
import torchvision
import numpy as np
import cv2

#test image
img_path = "../img/test.jpg"
img = cv2.imread(img_path)
img = cv2.resize(img, (224, 224))
img = np.transpose(img, (2, 0, 1)).astype(np.float32)
img = torch.from_numpy(img)
img = img.unsqueeze(0)

#pytorch test
model = torchvision.models.mobilenet_v2(pretrained=True)
model.eval()
output = model.forward(img)
val, cls = torch.max(output.data, 1)
print("[pytorch]--->predicted class: %d, predicted value: %.6f" % (cls.item(), val.item()))

pytorch 예측 결과는 다음과 같습니다.
[pytorch]--->predicted class: 920, predicted value: 19.230936

이를 통해 알 수 있듯이 dll과pytorch 예측 유형은 일치하고 계산 라이브러리에 따라 예측 값이 약간의 편차가 있다.
4. 총결산
글 끝에 작은 매듭을 짓고 다음과 같다.
       1. 모형을 포장할 때 모형과 코드를 함께 포장하려면 모형을 메모리에 읽어 넣을 수 있다. 이렇게 포장하면 dll 파일이 하나밖에 없어서 배치하기 편리함과 동시에 모형을 암호화할 수 있다.
       2. 정적 컴파일링은 vcruntime 등 라이브러리를 dll에 함께 포장하여 dll의 이식성이 더욱 좋고 동적 컴파일링은 배치 기계와 포장 기계 환경이 일치해야 dll를 실행할 수 있다.정적 컴파일된 dll은 동적 컴파일된 dll보다 부피가 약간 크고 구체적으로 dll 디렉터리를 볼 수 있습니다.
VS 자체 Dumpbin 도구를 사용하여 dll 의존 관계를 분석합니다. 정적 컴파일된 dll 의존 관계는 다음과 같습니다.
dumpbin /dependents static/ImageRecognitionEngine.dll

    :

Dump of file ImageRecognitionEngine.dll

File Type: DLL

  Image has the following dependencies:

    VCOMP140.DLL
    KERNEL32.dll

정적 컴파일된 dll은 VCOMP140에만 의존한다는 것을 알 수 있습니다.DLL 및 KERNEL32.Dll 라이브러리 2개, KERNEL32.dll은 win 시스템이 자체로 가지고 있는 VCOMP140입니다.DLL은 보통 win에도 있습니다. VCOMP140을 보십시오.DLL의 종속성 결과는 다음과 같습니다.
dumpbin /dependents VCOMP140.DLL

    :

Dump of file VCOMP140.DLL

File Type: DLL

  Image has the following dependencies:

    KERNEL32.dll
    USER32.dll

보입니다. VCOMP140.DLL이 의존하는 라이브러리는 모두 시스템 자체 라이브러리입니다. 만약에 배치 기계에 VCOMP140이 없다면.DLL, 기계의 VCOMP140을 포장합니다.DLL은 ImageRecognitionEngine과 같습니다.dll와 함께 배치기기에 복사하면 됩니다.
그런 다음 동적 컴파일의 dll 종속성을 다음과 같이 분석합니다.
dumpbin /dependents dynamic/ImageRecognitionEngine.dll

    :

Dump of file ImageRecognitionEngine.dll

File Type: DLL

  Image has the following dependencies:

    MSVCP140.dll
    VCOMP140.DLL
    VCRUNTIME140.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-convert-l1-1-0.dll
    api-ms-win-crt-math-l1-1-0.dll
    KERNEL32.dll


이를 통해 알 수 있듯이 동적 컴파일된 dll은 의존하는 라이브러리가 많은데 주로 VCRUNTIME140이 있다.dll, 그리고 MSVCP140.dll와 일련의api-ms-win-xxx 등 라이브러리, 이런 라이브러리는 일부 다른 라이브러리에 동적으로 의존하여 배치하기 매우 번거롭다. 만약에 배치 기계에vcruntime 등 일련의 라이브러리가 설치되지 않으면 동적 컴파일된 dll를 호출하면 오류를 보고하기 쉽고 xxx를 찾을 수 없다는 것을 알린다.dll.
따라서 정적 컴파일링 방식으로 dll을 배치하는 것을 강력히 추천합니다.
       3. 패키지 비트는 자신의 요구에 따라 32비트 or64비트를 선택합니다.
       4. 일반적인 이미지는 귀일화 등 예처리를 한 다음에 네트워크에 보내서 식별을 하는데 데이터의 예처리는python과 C++에서 통일되어야 한다.
이로써 dll 패키지의 핵심 내용은 소개가 끝났습니다. dll은 주로 윈도 기기에 배치됩니다. 만약에 모바일 기기, 예를 들어 안드로이드에 배치하려면 so인터페이스로 포장해야 합니다. 다음 블로그는 본고의 코드를 사용하여 so인터페이스를 어떻게 포장하는지 소개할 것입니다. 안드로이드 기기로 so인터페이스를 호출하여 이미지 식별을 할 수 있도록 합니다. 관심 있는 파트너들은 유의할 수 있습니다.

좋은 웹페이지 즐겨찾기