# 分类器

本示例通过ResNet50模型说明如何使用MIGraphX Python API进行分类模型的推理，包括预处理、推理并获取推理结果。



## 模型简介

本示例使用了经典的ResNet50模型，onnx文件在Resource/Models/文件夹下，模型结构可以通过netron (https://netron.app/) 查看，该模型的输入shape为[1,3,224,224] ，数据排布为NCHW。

​																														

## 预处理

在将数据输入到模型之前，需要对图像做如下预处理操作：

- 图像格式转换，将BGR转换为RGB
- 调整图像大小，使短边为 256，长边等比例缩放
- 裁剪图像，在中心窗口位置裁剪出224x224大小的图像
- normalize操作，对图像减均值再除方差
- 调整输入数据的尺寸为（1, 3, 224, 224）

本示例代码采用了OpenCV实现了预处理操作：

```python
def Preprocessing(pathOfImage):
    
    image = cv2.imread(pathOfImage, cv2.IMREAD_COLOR)    
    
    # 转换为RGB格式
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    
    # 调整大小，保持长宽比
    ratio = float(256) / min(image.shape[0], image.shape[1])
    if image.shape[0] > image.shape[1]:
        new_size = [int(round(ratio * image.shape[0])), 256]
    else:
        new_size = [256, int(round(ratio * image.shape[1]))]
    image = np.array(cv2.resize(image, (new_size[1],new_size[0])))    # w h 格式
    
    # 裁剪中心窗口
    h, w, c = image.shape
    start_x = w//2 - 224//2
    start_y = h//2 - 224//2
    image = image[start_y:start_y+224, start_x:start_x+224, :]
    
    # transpose
    image = image.transpose(2, 0, 1)
    
    # 将输入数据转换为float32
    img_data = image.astype('float32')
    
    # normalize
    mean_vec = np.array([123.675, 116.28, 103.53])
    stddev_vec = np.array([58.395, 57.12, 57.375])
    norm_img_data = np.zeros(img_data.shape).astype('float32')
    for i in range(img_data.shape[0]):
        norm_img_data[i,:,:] = (img_data[i,:,:] - mean_vec[i]) / stddev_vec[i]
    
    # 调整输入数据的尺寸
    norm_img_data = norm_img_data.reshape(1, 3, 224, 224).astype('float32')
    return norm_img_data
```

## 量化

该示例工程提供了FP16和INT8两种量化方法，可以根据选项设置是否需要量化：

```python
use_int8 = False      // 设置为True时，开启INT8量化
use_fp16 = False      // 设置为True时，开启FP16量化
```

### FP16量化

使用FP16模式只需要在编译前调用migraphx.quantize_fp16() 即可。

```python
if __name__ == '__main__':
    ...
	if use_fp16:
        migraphx.quantize_fp16(model)
	...
```

### INT8量化

使用INT8模式需要提供量化校准数据，MIGraphX采用线性量化算法，通过校准数据计算量化参数并生成量化模型。为了保证量化精度，一般使用测试集或验证集中的数据作为校准数据。

```python
if __name__ == '__main__':
    ...
	if use_int8:
    	dic = dict()                                  # 创建字典
		testofImage = "../Resource/Images/ImageNet_test.jpg"    # 提供的量化校准数据
		testimage = Preprocessing(testofImage)
		dic[inputName] = migraphx.argument(testimage) # 创建量化数据
		calibration = [dic]

		# INT8量化
		migraphx.quantize_int8(model, migraphx.get_target("gpu"), calibration)
	...
```

## 推理

完成预处理后，就可以执行推理了：

```python
if __name__ == '__main__':
    ...
    # 编译
    model.compile(t=migraphx.get_target("gpu"),device_id=0) # device_id: 设置GPU设备，默认为0号设备
    
    # 预处理
    pathOfImage ="../Resource/Images/ImageNet_01.jpg"
    image = Preprocessing(pathOfImage)

    # 推理
    results = model.run({inputName: image}) # 推理结果，list类型

    # 获取输出节点属性
    result = results[0]                      # 获取第一个输出节点的数据,migraphx.argument类型
    outputShape = result.get_shape()         # 输出节点的shape,migraphx.shape类型
    outputSize = outputShape.lens()          # 每一维大小，维度顺序为(N,C,H,W),list类型
    numberOfOutput = outputShape.elements()  # 输出节点元素的个数

    # 获取分类结果
    print(np.array(result))
```

- Preprocessing()函数返回输入数据（numpy类型），然后通过{inputName: migraphx.argument(image)}构造一个字典输入模型执行推理，如果模型有多个输入，则在字典中需要添加多个输入数据。
- model.run()返回模型的推理结果，返回结果是一个list类型，results[0]表示第一个输出节点的输出，是一个migraphx.argument类型，由于示例模型只有一个输出节点，所以results[0]对应resnetv24_dense0_fwd节点。最后，通过np.array(result)获取分类结果。

在Classifier_OffloadFalse.py脚本中，主要在推理阶段有所不同，需要进行内存拷贝。

```python
if __name__ == '__main__':
    ...
    
    # 编译
    model.compile(t=migraphx.get_target("gpu"),offload_copy=False,device_id=0)  # device_id: 设置GPU设备，默认为0号设备

    # 为输出节点分配device内存，用于保存输出数据
    modelData=AllocateOutputMemory(model)

    # 预处理并转换为NCHW
    pathOfImage ="../Resource/Images/ImageNet_01.jpg"
    image = Preprocessing(pathOfImage)
    modelData[inputName] = migraphx.to_gpu(migraphx.argument(image))

    # 推理
    results = model.run(modelData)

    # 获取输出节点属性
    result = migraphx.from_gpu(results[0]) # 获取第一个输出节点的数据,migraphx.argument类型
    outputShape = result.get_shape() # 输出节点的shape,migraphx.shape类型
    outputSize = outputShape.lens() # 每一维大小，维度顺序为(N,C,H,W),list类型
    numberOfOutput = outputShape.elements() # 输出节点元素的个数

    # 获取分类结果
    print(np.array(result))
```

- AllocateOutputMemory()函数主要用于为输出节点分配device内存，用来保存输出数据。
- Preprocessing()函数返回输入数据后，通过migraphx.to_gpu(migraphx.argument(image))方法，将输入数据转换为device数据。