본문 바로가기
programming/기타

tensorflow-lite signiture 분석

by lightlee 2019. 10. 15.

tensorflow-lite 에서 제공하는 api 는

  • tensorflow-lite converter
  • tensorflow-lite interpreter

이렇게 두가지로 구성됩니다.

converter 는 모바일에 올리기 위해 모델을 경량화 시키는 과정에 해당하므로 본격적인 추론이 일어나는 interpreter api를 사용하는 부분을 위주로 보겠습니다. 즉, tensorflow lite converter 에 의해 경량화된 모델(.tflite) 가 있다는 가정 하에 진행합니다.

1. tflite 모델로 interpreter 객체를 만드는 단계
2. input data 의 format이 모델과 맞도록 resizing 하는 단계
3. 추론을 진행하는 단계
4. output tensor를 의미있게 해석하는 단계 

위와 같이 크게 4단계로 나눌 수 있고, 간단한 설명과 함께 obect detection 라는 예제에서는 각 단계들에 실제로 어떤 코드가 사용되었는지 같이 첨부하겠습니다.

 

1. tflite 모델로 interpreter 객체를 만드는 단계

.tflite 모델을 메모리에 올리는 작업으로, 매개변수의 종류에 따라 두가지로 나눌 수 있습니다.

public Interpreter(@NotNull File modelFile); 
//.tflite 로 interpreter 초기화

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);
//mappedBytebuffer로 초기화

.tflite 확장자 파일을 직접 가리키도록 만들 수도 있고 MappedByteBuffer 클래스를 이용할 수도 있습니다.

 

[object dection - TFLiteObjectDetectionAPIModel class]

private Interpreter tfLite;

object dection

TFLiteObjectDetectionAPIModel 클래스의 멤버변수로 tfLite라는 Interpreter 객체가 있습니다.

private static MappedByteBuffer loadModelFile(AssetManager assets, String modelFilename)    throws IOException {  
    AssetFileDescriptor fileDescriptor = assets.openFd(modelFilename);  
    FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());  
    FileChannel fileChannel = inputStream.getChannel();  
    long startOffset = fileDescriptor.getStartOffset();  
    long declaredLength = fileDescriptor.getDeclaredLength();  
    return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength);
}

loadModelFile이라는 메소드는 assets(안드로이드 앱의 전용 메모리에 올려놓고 사용할 데이터의 모음) 에 있는 tflite 모델을 MappedByteBuffer 타입으로 반환하는 역할을 하고 있습니다.

try {  
    d.tfLite = new Interpreter(loadModelFile(assetManager, modelFilename));
} catch (Exception e) {  
    throw new RuntimeException(e);
}

new Interpreter 로 객체를 생성하며 tfLite라는 멤버변수가 해당 모델 객체를 가리키도록 합니다.

이제 .tflite 모델을 메모리에 올렸고 그 객체를 가리키는 변수가 생겼습니다.

 

2. input data를 resizing 하는 단계

모델에 맞는 input이 들어올 수 있도록 데이터에 전처리를 가하는 단계입니다. 필요에 따라 진행하면 되는 부분입니다. 바로 object detection 예제에서 살펴보겠습니다.

 

[object dection - TFLiteObjectDetectionAPIModel class]

private ByteBuffer imgData;

이미지 데이터가 ByteBuffer 클래스의 형태로 멤버변수에 정의되어 있습니다.

tensorflow-lite의 api를 사용하는 부분이 없으므로 아래 전처리 과정의 자세한 설명은 생략합니다.

 d.imgData =
     ByteBuffer.allocateDirect(1 * d.inputSize * d.inputSize * 3 * numBytesPerChannel);
    d.imgData.order(ByteOrder.nativeOrder());
imgData.rewind();
    for (int i = 0; i < inputSize; ++i) {
      for (int j = 0; j < inputSize; ++j) {
        int pixelValue = intValues[i * inputSize + j];
        if (isModelQuantized) {
          // Quantized model
          imgData.put((byte) ((pixelValue >> 16) & 0xFF));
          imgData.put((byte) ((pixelValue >> 8) & 0xFF));
          imgData.put((byte) (pixelValue & 0xFF));
        } else { // Float model
          imgData.putFloat((((pixelValue >> 16) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
          imgData.putFloat((((pixelValue >> 8) & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
          imgData.putFloat(((pixelValue & 0xFF) - IMAGE_MEAN) / IMAGE_STD);
        }
      }
    }

위의 과정을 거쳐 최종 input 데이터의 형태는 아래와 같이 Object의 배열로 정리되었습니다.

Object[] inputArray = {imgData};

 

3. 추론을 진행하는 단계

interpreter api 를 가장 많이 쓰는 부분이라고 할 수 있습니다. input, output 이 각각 여러개냐 1개냐에 따라 단순히 run 메소드를 쓸 수도 있고, runForMultipleInputsOutputs라는 메소드를 쓸 수도 있습니다.

interpreter.run(input, output);
//1 input, 1 output

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);
//multiple input and output

 

[object dection - TFLiteObjectDetectionAPIModel class]

tfLite.runForMultipleInputsOutputs(inputArray, outputMap);

앞에서 inputArray의 형태로 input data를 가공했던 이유는 이 runForMultipleInputsOutputs의 인수로 넣기 위함입니다.

이때, outputMap 은 map 형태라는 것을 볼 수 있는데,

Map<Integer, Object> outputMap = new HashMap<>();
outputMap.put(0, outputLocations);
outputMap.put(1, outputClasses);
outputMap.put(2, outputScores);
outputMap.put(3, numDetections);

이런 식으로 정보들을 항목별로 묶어서 (위치, 분류, 정확도 등) map으로 만든것을 output으로 지정합니다. 추론을 진행하고 나면 이 outputMap 에 정보들이 들어가 있게 됩니다.

input과 output 형태는 사용자가 정하기 나름이지만 "Be aware that the order of tensors in input must match the order given to the TensorFlow Lite Converter."

converter에 줬던 tensor와 순서가 동일해야한다는 주의사항이 있습니다.

 

4. output tensor를 의미있게 해석하는 단계

3단계 에서 outputMap을 정할 때 이미 어느 정도 진행된 단계라고 볼 수 있습니다.

추론 결과 나온 output이 이 예제에서 의미있게 해석된다는 것은, 인식한 사물 주변에 네모난 박스가 생기고 정확도가 표시되며 그 사물을 무엇으로 분류할 수 있는지에 대한 정보들이 보여야 한다는 것을 말합니다. 즉,

이런 화면으로 변환까지 되어야 의미있게 해석했다고 볼 수 있습니다.

 

[object dection - TFLiteObjectDetectionAPIModel class]

@Overridepublic List<Recognition> recognizeImage(final Bitmap bitmap) { 
    
    //inputArray, outputMap 만드는 과정 생략 ....
    
 	 tfLite.runForMultipleInputsOutputs(inputArray, outputMap);  
     final ArrayList<Recognition> recognitions = new ArrayList<>(NUM_DETECTIONS); 
     for (int i = 0; i < NUM_DETECTIONS; ++i) {    
         final RectF detection = new RectF(            
             outputLocations[0][i][1] * inputSize,            
             outputLocations[0][i][0] * inputSize,            
             outputLocations[0][i][3] * inputSize,            
             outputLocations[0][i][2] * inputSize);    
         int labelOffset = 1;    
         recognitions.add(        
             new Recognition(            
                 "" + i,            
                 labels.get((int) outputClasses[0][i] + labelOffset),            						 outputScores[0][i],            
                 detection));  }
    return recognitions;}

outputLocations[0][i][1] ~ outputLocations[0][i][3]에 사각형의 4 꼭지점을 표시할 수 있는 위치정보가 들어가 있고 outputClasses[0][i]에는 분류 클래스(사과, 바나나 등.. )가, outputScores[0][i]에는 정확도가 들어가 있습니다.

이 정보들이 Recognition 이라는 객체를 초기화시키고, 한번에 여러 사물이 인식될 수 있기 때문에 저 Recognition 객체들의 모음인 recognitions들이 최종 결과물이 됩니다.

 

[object dection - DetectorActivity class]

try {  
    detector = TFLiteObjectDetectionAPIModel.create(          
        getAssets(),          
        TF_OD_API_MODEL_FILE,          
        TF_OD_API_LABELS_FILE,          
        TF_OD_API_INPUT_SIZE,          
        TF_OD_API_IS_QUANTIZED);  
    cropSize = TF_OD_API_INPUT_SIZE;
} catch (final IOException e) {  
    e.printStackTrace();  
}

TFLiteObjectDetectionAPIModel이라는 클래스가 DetectorActivity 에서 하나의 멤버변수(=detector)가 되어 위에서 설명한 inferencer의 모든 기능을 수행합니다.

croppedBitmap 은 카메라로부터 받아온 비트맵을 crop한 것을 나타내는 변수입니다.

final List<Classifier.Recognition> results = detector.recognizeImage(croppedBitmap);

추론 결과들이 results 라는 변수에 모여

for (final Classifier.Recognition result : results) {  
    final RectF location = result.getLocation();  
    if (location != null && result.getConfidence() >= minimumConfidence){    					canvas.drawRect(location, paint);                                                         cropToFrameTransform.mapRect(location);                                                   result.setLocation(location);                                                             mappedRecognitions.add(result);  
   	}
}

위와 같이 각 결과마다 해당 위치에 박스를 표시하고 분류 결과를 표시합니다.

 

< TFLiteObjectDetectionAPIModel class>

수행 역할 input output(타입)
모델 불러오기 .tflite 파일 자체 or MappedByteBuffer Interpreter
추론 input Array, output Map output Map
결과 해석 bitmap ArrayList

< DetectorActivity class>

수행 역할 input output(타입)
TFLiteObjectDetectionAPIModel 객체만들기 getAssets(), TF_OD_API_MODEL_FILE, TF_OD_API_LABELS_FILE, TF_OD_API_INPUT_SIZE,TF_OD_API_IS_QUANTIZED TFLiteObjectDetectionAPIModel