diff --git a/docs/apis/datasets/dataset_convert.md b/docs/apis/datasets/dataset_convert.md new file mode 100644 index 0000000000000000000000000000000000000000..2a04c8013bb5ea32b761fa28dad7fa9a6e09db99 --- /dev/null +++ b/docs/apis/datasets/dataset_convert.md @@ -0,0 +1,42 @@ +# 数据集转换 +## labelme2voc +```python +pdx.tools.labelme2voc(image_dir, json_dir, dataset_save_dir) +``` +将LabelMe标注的数据集转换为VOC数据集。 + +> **参数** +> > * **image_dir** (str): 图像文件存放的路径。 +> > * **json_dir** (str): 与每张图像对应的json文件的存放路径。 +> > * **dataset_save_dir** (str): 转换后数据集存放路径。 + +## 其它数据集转换 +### easydata2imagenet +```python +pdx.tools.easydata2imagenet(image_dir, json_dir, dataset_save_dir) +``` +### easydata2voc +```python +pdx.tools.easydata2voc(image_dir, json_dir, dataset_save_dir) +``` +### easydata2coco +```python +pdx.tools.easydata2coco(image_dir, json_dir, dataset_save_dir) +``` +### easydata2seg +```python +pdx.tools.easydata2seg(image_dir, json_dir, dataset_save_dir) +``` +### labelme2coco +```python +pdx.tools.labelme2coco(image_dir, json_dir, dataset_save_dir) +``` +### labelme2seg +```python +pdx.tools.labelme2seg(image_dir, json_dir, dataset_save_dir) +``` +### jingling2seg +```python +pdx.tools.jingling2seg(image_dir, json_dir, dataset_save_dir) +``` + diff --git a/docs/apis/datasets/index.rst b/docs/apis/datasets/index.rst index e04aa4582c6ab7e8ac83aefb7ad1f7c4c3186889..06326e441d1573521d1030eaaeaca8364899b2c6 100755 --- a/docs/apis/datasets/index.rst +++ b/docs/apis/datasets/index.rst @@ -29,3 +29,4 @@ PaddleX目前支持主流的CV数据集格式和 `EasyData **参数** -* **model_dir**: 训练过程中保存的模型路径 -* **use_gpu**: 是否使用GPU进行预测 +> > * **model_dir**: 训练过程中保存的模型路径, 注意需要使用导出的inference模型 +> > * **use_gpu**: 是否使用GPU进行预测 +> > * **gpu_id**: 使用的GPU序列号 +> > * **use_mkl**: 是否使用mkldnn加速库 +> > * **use_trt**: 是否使用TensorRT预测引擎 +> > * **use_glog**: 是否打印中间日志 +> > * **memory_optimize**: 是否优化内存使用 -#### Returns +> > ### 示例 +> > +> > ``` +> > import paddlex +> > model = paddlex.deploy.Predictor(model_dir, use_gpu=True) +> > result = model.predict(image_file) +> > ``` -* **Predictor**: paddlex.deploy.predictor.Predictor +### predict 接口 +> ``` +> predict(image, topk=1) +> ``` -### 示例 +> **参数 -``` -import paddlex -# 下 -Predictor = paddlex.deploy.create_predictor(model_dir, use_gpu=True) -``` - -## ClassifyPredictor -继承至paddlex.deploy.predictor.Predictor,当model_dir/model.yml里面 - -``` -paddlex.deploy.create_predictor(model_dir, use_gpu=False) -``` - -#### Args - -* **model_dir**: 训练过程中保存的模型路径 -* **use_gpu**: 是否使用GPU进行预测 - -#### Returns - -* **Predictor**: paddlex.deploy.predictor.Predictor - -### 示例 - -``` -import paddlex -# 下 -Predictor = paddlex.deploy.create_predictor(model_dir, use_gpu=True) -``` +* **image(str|np.ndarray)**: 待预测的图片路径或np.ndarray,若为后者需注意为BGR格式 +* **topk(int)**: 图像分类时使用的参数,表示预测前topk个可能的分类 diff --git a/docs/apis/index.rst b/docs/apis/index.rst index 900e165170997ec97ef8ad93cbda7f6c47e24f2e..d58e90d93fd0e5ae78476f9ce0841ca190675f11 100755 --- a/docs/apis/index.rst +++ b/docs/apis/index.rst @@ -10,3 +10,4 @@ PaddleX API说明文档 slim.md load_model.md visualize.md + deploy.md diff --git a/docs/apis/visualize.md b/docs/apis/visualize.md index 1138115ea89ed32b59af623517c64676c62184dd..8a253fc04310f43b8e424729e7d75f2712e8dc19 100755 --- a/docs/apis/visualize.md +++ b/docs/apis/visualize.md @@ -5,7 +5,7 @@ PaddleX提供了一系列模型预测和结果分析的可视化函数。 ``` paddlex.det.visualize(image, result, threshold=0.5, save_dir='./') ``` -将目标检测/实例分割模型预测得到的Box框和Mask在原图上进行可视化 +将目标检测/实例分割模型预测得到的Box框和Mask在原图上进行可视化。 ### 参数 > * **image** (str): 原图文件路径。 @@ -77,7 +77,7 @@ pdx.det.draw_pr_curve(gt=gt, pred_bbox=bbox, save_dir='./insect') ``` paddlex.seg.visualize(image, result, weight=0.6, save_dir='./') ``` -将语义分割模型预测得到的Mask在原图上进行可视化 +将语义分割模型预测得到的Mask在原图上进行可视化。 ### 参数 > * **image** (str): 原图文件路径。 @@ -99,11 +99,11 @@ pdx.det.visualize('city.png', result, save_dir='./') ``` paddlex.slim.visualize(model, sensitivities_file) ``` -利用此接口,可以分析在不同的`eval_metric_loss`参数下,模型被裁剪的比例情况。可视化结果纵轴为eval_metric_loss参数值,横轴为对应的模型被裁剪的比例 +利用此接口,可以分析在不同的`eval_metric_loss`参数下,模型被裁剪的比例情况。可视化结果纵轴为eval_metric_loss参数值,横轴为对应的模型被裁剪的比例。 ### 参数 ->* **model**: 使用PaddleX加载的模型 ->* **sensitivities_file**: 模型各参数在验证集上计算得到的参数敏感度信息文件 +>* **model** (paddlex.cv.models): 使用PaddleX加载的模型。 +>* **sensitivities_file** (str): 模型各参数在验证集上计算得到的参数敏感度信息文件。 ### 使用示例 > 点击下载示例中的[模型](https://bj.bcebos.com/paddlex/models/vegetables_mobilenet.tar.gz)和[sensitivities_file](https://bj.bcebos.com/paddlex/slim_prune/mobilenetv2.sensitivities) @@ -113,3 +113,28 @@ model = pdx.load_model('vegetables_mobilenet') pdx.slim.visualize(model, 'mobilenetv2.sensitivities', save_dir='./') # 可视化结果保存在./sensitivities.png ``` + +## 可解释性结果可视化 +``` +paddlex.interpret.visualize(img_file, + model, + dataset=None, + algo='lime', + num_samples=3000, + batch_size=50, + save_dir='./') +``` +将模型预测结果的可解释性可视化,目前只支持分类模型。 + +### 参数 +>* **img_file** (str): 预测图像路径。 +>* **model** (paddlex.cv.models): paddlex中的模型。 +>* **dataset** (paddlex.datasets): 数据集读取器,默认为None。 +>* **algo** (str): 可解释性方式,当前可选'lime'和'normlime'。 +>* **num_samples** (int): LIME用于学习线性模型的采样数,默认为3000。 +>* **batch_size** (int): 预测数据batch大小,默认为50。 +>* **save_dir** (str): 可解释性可视化结果(保存为png格式文件)和中间文件存储路径。 + + +### 使用示例 +> 对预测可解释性结果可视化的过程可参见[代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/interpret/interpret.py)。 diff --git a/docs/appendix/datasets.md b/docs/appendix/datasets.md new file mode 100644 index 0000000000000000000000000000000000000000..e966205c1c39eb8e68d9366db324c984a8a42134 --- /dev/null +++ b/docs/appendix/datasets.md @@ -0,0 +1,367 @@ +# 数据集格式说明 + +--- +## 图像分类ImageNet + +图像分类ImageNet数据集包含对应多个标签的图像文件夹、标签文件及图像列表文件。 +参考数据文件结构如下: +``` +./dataset/ # 数据集根目录 +|--labelA # 标签为labelA的图像目录 +| |--a1.jpg +| |--... +| └--... +| +|--... +| +|--labelZ # 标签为labelZ的图像目录 +| |--z1.jpg +| |--... +| └--... +| +|--train_list.txt # 训练文件列表文件 +| +|--val_list.txt # 验证文件列表文件 +| +└--labels.txt # 标签列表文件 + +``` +其中,相应的文件名可根据需要自行定义。 + +`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为图像文件对应的标签id(从0开始)。如下所示: +``` +labelA/a1.jpg 0 +labelZ/z1.jpg 25 +... +``` + +`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示: +``` +labelA +labelB +... +``` +[点击这里](https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz),下载蔬菜分类分类数据集。 +在PaddleX中,使用`paddlex.cv.datasets.ImageNet`([API说明](../apis/datasets/classification.html#imagenet))加载分类数据集。 + +## 目标检测VOC +目标检测VOC数据集包含图像文件夹、标注信息文件夹、标签文件及图像列表文件。 +参考数据文件结构如下: +``` +./dataset/ # 数据集根目录 +|--JPEGImages # 图像目录 +| |--xxx1.jpg +| |--... +| └--... +| +|--Annotations # 标注信息目录 +| |--xxx1.xml +| |--... +| └--... +| +|--train_list.txt # 训练文件列表文件 +| +|--val_list.txt # 验证文件列表文件 +| +└--labels.txt # 标签列表文件 + +``` +其中,相应的文件名可根据需要自行定义。 + +`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为标注文件相对于dataset的相对路径。如下所示: +``` +JPEGImages/xxx1.jpg Annotations/xxx1.xml +JPEGImages/xxx2.jpg Annotations/xxx2.xml +... +``` + +`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示: +``` +labelA +labelB +... +``` +[点击这里](https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz),下载昆虫检测数据集。 +在PaddleX中,使用`paddlex.cv.datasets.VOCDetection`([API说明](../apis/datasets/detection.html#vocdetection))加载目标检测VOC数据集。 + +## 目标检测和实例分割COCO +目标检测和实例分割COCO数据集包含图像文件夹及图像标注信息文件。 +参考数据文件结构如下: +``` +./dataset/ # 数据集根目录 +|--JPEGImages # 图像目录 +| |--xxx1.jpg +| |--... +| └--... +| +|--train.json # 训练相关信息文件 +| +└--val.json # 验证相关信息文件 + +``` +其中,相应的文件名可根据需要自行定义。 + +`train.json`和`val.json`存储与标注信息、图像文件相关的信息。如下所示: + +``` +{ + "annotations": [ + { + "iscrowd": 0, + "category_id": 1, + "id": 1, + "area": 33672.0, + "image_id": 1, + "bbox": [232, 32, 138, 244], + "segmentation": [[32, 168, 365, 117, ...]] + }, + ... + ], + "images": [ + { + "file_name": "xxx1.jpg", + "height": 512, + "id": 267, + "width": 612 + }, + ... + ] + "categories": [ + { + "name": "labelA", + "id": 1, + "supercategory": "component" + } + ] +} +``` +其中,每个字段的含义如下所示: + +| 域名 | 字段名 | 含义 | 数据类型 | 备注 | +|:-----|:--------|:------------|------|:-----| +| annotations | id | 标注信息id | int | 从1开始 | +| annotations | iscrowd | 标注框是否为一组对象 | int | 只有0、1两种取值 | +| annotations | category_id | 标注框类别id | int | | +| annotations | area | 标注框的面积 | float | | +| annotations | image_id | 当前标注信息所在图像的id | int | | +| annotations | bbox | 标注框坐标 | list | 长度为4,分别代表x,y,w,h | +| annotations | segmentation | 标注区域坐标 | list | list中有至少1个list,每个list由每个小区域坐标点的横纵坐标(x,y)组成 | +| images | id | 图像id | int | 从1开始 | +| images | file_name | 图像文件名 | str | | +| images | height | 图像高度 | int | | +| images | width | 图像宽度 | int | | +| categories | id | 类别id | int | 从1开始 | +| categories | name | 类别标签名 | str | | +| categories | supercategory | 类别父类的标签名 | str | | + + +[点击这里](https://bj.bcebos.com/paddlex/datasets/garbage_ins_det.tar.gz),下载垃圾实例分割数据集。 +在PaddleX中,使用`paddlex.cv.datasets.COCODetection`([API说明](../apis/datasets/detection.html#cocodetection))加载COCO格式数据集。 + +## 语义分割数据 +语义分割数据集包含原图、标注图及相应的文件列表文件。 +参考数据文件结构如下: +``` +./dataset/ # 数据集根目录 +|--images # 原图目录 +| |--xxx1.png +| |--... +| └--... +| +|--annotations # 标注图目录 +| |--xxx1.png +| |--... +| └--... +| +|--train_list.txt # 训练文件列表文件 +| +|--val_list.txt # 验证文件列表文件 +| +└--labels.txt # 标签列表 + +``` +其中,相应的文件名可根据需要自行定义。 + +`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为标注图像文件相对于dataset的相对路径。如下所示: +``` +images/xxx1.png annotations/xxx1.png +images/xxx2.png annotations/xxx2.png +... +``` + +`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示: +``` +background +labelA +labelB +... +``` + +标注图像为单通道图像,像素值即为对应的类别,像素标注类别需要从0开始递增(一般第一个类别为`background`), +例如0,1,2,3表示有4种类别,标注类别最多为256类。其中可以指定特定的像素值用于表示该值的像素不参与训练和评估(默认为255)。 + +[点击这里](https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz),下载视盘语义分割数据集。 +在PaddleX中,使用`paddlex.cv.datasets.SegReader`([API说明](../apis/datasets/semantic_segmentation.html#segdataset))加载语义分割数据集。 + + +## 图像分类EasyDataCls + +图像分类EasyDataCls数据集包含存放图像和json文件的文件夹、标签文件及图像列表文件。 +参考数据文件结构如下: +``` +./dataset/ # 数据集根目录 +|--easydata # 存放图像和json文件的文件夹 +| |--0001.jpg +| |--0001.json +| |--0002.jpg +| |--0002.json +| └--... +| +|--train_list.txt # 训练文件列表文件 +| +|--val_list.txt # 验证文件列表文件 +| +└--labels.txt # 标签列表文件 + +``` +其中,图像文件名应与json文件名一一对应。 + +每个json文件存储于`labels`相关的信息。如下所示: +``` +{"labels": [{"name": "labelA"}]} +``` +其中,`name`字段代表对应图像的类别。 + +`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为json文件相对于dataset的相对路径。如下所示: +``` +easydata/0001.jpg easydata/0001.json +easydata/0002.jpg easydata/0002.json +... +``` + +`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示: +``` +labelA +labelB +... +``` +[点击这里](https://ai.baidu.com/easydata/),可以标注图像分类EasyDataCls数据集。 +在PaddleX中,使用`paddlex.cv.datasets.EasyDataCls`([API说明](../apis/datasets/classification.html#easydatacls))加载分类数据集。 + + +## 目标检测和实例分割EasyDataDet + +目标检测和实例分割EasyDataDet数据集包含存放图像和json文件的文件夹、标签文件及图像列表文件。 +参考数据文件结构如下: +``` +./dataset/ # 数据集根目录ß +|--easydata # 存放图像和json文件的文件夹 +| |--0001.jpg +| |--0001.json +| |--0002.jpg +| |--0002.json +| └--... +| +|--train_list.txt # 训练文件列表文件 +| +|--val_list.txt # 验证文件列表文件 +| +└--labels.txt # 标签列表文件 + +``` +其中,图像文件名应与json文件名一一对应。 + +每个json文件存储于`labels`相关的信息。如下所示: +``` +"labels": [{"y1": 18, "x2": 883, "x1": 371, "y2": 404, "name": "labelA", + "mask": "kVfc0`0Zg0=1.7.0" + "Model Quantization is not available, try to upgrade your paddlepaddle>=1.8.0" ) is_use_cache_file = True if cache_dir is None: @@ -544,4 +545,4 @@ class BaseAPI: best_accuracy)) if eval_dataset is not None and early_stop: if earlystop(current_accuracy): - break + break \ No newline at end of file diff --git a/paddlex/cv/models/slim/post_quantization.py b/paddlex/cv/models/slim/post_quantization.py index bd173330111da51e0c20a7c622cb32dea98514a4..29ded34d20e3af85d43759b518169df9545b66e8 100644 --- a/paddlex/cv/models/slim/post_quantization.py +++ b/paddlex/cv/models/slim/post_quantization.py @@ -14,7 +14,7 @@ from paddle.fluid.contrib.slim.quantization.quantization_pass import QuantizationTransformPass from paddle.fluid.contrib.slim.quantization.quantization_pass import AddQuantDequantPass -from paddle.fluid.contrib.slim.quantization.quantization_pass import _op_real_in_out_name +from paddle.fluid.contrib.slim.quantization.quantization_pass import _out_scale_op_list from paddle.fluid.contrib.slim.quantization import PostTrainingQuantization import paddlex.utils.logging as logging import paddle.fluid as fluid @@ -44,7 +44,6 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): fp32 model. It uses calibrate data to calculate the scale factor of quantized variables, and inserts fake quant/dequant op to obtain the quantized model. - Args: executor(fluid.Executor): The executor to load, run and save the quantized model. @@ -78,6 +77,21 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): Returns: None ''' + self._support_activation_quantize_type = [ + 'range_abs_max', 'moving_average_abs_max', 'abs_max' + ] + self._support_weight_quantize_type = ['abs_max', 'channel_wise_abs_max'] + self._support_algo_type = ['KL', 'abs_max', 'min_max'] + self._support_quantize_op_type = \ + list(set(QuantizationTransformPass._supported_quantizable_op_type + + AddQuantDequantPass._supported_quantizable_op_type)) + + # Check inputs + assert executor is not None, "The executor cannot be None." + assert batch_size > 0, "The batch_size should be greater than 0." + assert algo in self._support_algo_type, \ + "The algo should be KL, abs_max or min_max." + self._executor = executor self._dataset = dataset self._batch_size = batch_size @@ -86,18 +100,19 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): self._algo = algo self._is_use_cache_file = is_use_cache_file self._cache_dir = cache_dir + self._activation_bits = 8 + self._weight_bits = 8 + self._activation_quantize_type = 'range_abs_max' + self._weight_quantize_type = 'channel_wise_abs_max' if self._is_use_cache_file and not os.path.exists(self._cache_dir): os.mkdir(self._cache_dir) - supported_quantizable_op_type = \ - QuantizationTransformPass._supported_quantizable_op_type + \ - AddQuantDequantPass._supported_quantizable_op_type if is_full_quantize: - self._quantizable_op_type = supported_quantizable_op_type + self._quantizable_op_type = self._support_quantize_op_type else: self._quantizable_op_type = quantizable_op_type for op_type in self._quantizable_op_type: - assert op_type in supported_quantizable_op_type + \ + assert op_type in self._support_quantize_op_type + \ AddQuantDequantPass._activation_type, \ op_type + " is not supported for quantization." @@ -107,25 +122,29 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): self._fetch_list = list(outputs.values()) self._data_loader = None - self._op_real_in_out_name = _op_real_in_out_name + self._out_scale_op_list = _out_scale_op_list self._bit_length = 8 self._quantized_weight_var_name = set() self._quantized_act_var_name = set() self._sampling_data = {} - self._quantized_var_scale_factor = {} + self._quantized_var_kl_threshold = {} + self._quantized_var_min = {} + self._quantized_var_max = {} + self._quantized_var_abs_max = {} def quantize(self): ''' Quantize the fp32 model. Use calibrate data to calculate the scale factor of quantized variables, and inserts fake quant/dequant op to obtain the quantized model. - Args: None Returns: the program of quantized model. ''' - self._preprocess() + self._load_model_data() + self._collect_target_varnames() + self._set_activation_persistable() batch_ct = 0 for data in self._data_loader(): batch_ct += 1 @@ -140,7 +159,10 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): feed=data, fetch_list=self._fetch_list, return_numpy=False) - self._sample_data(batch_id) + if self._algo == "KL": + self._sample_data(batch_id) + else: + self._sample_threshold() end = time.time() logging.debug('[Run batch data] Batch={}/{}, time_each_batch={} s.'.format( str(batch_id + 1), @@ -150,19 +172,23 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): if self._batch_nums and batch_id >= self._batch_nums: break logging.info("All run batch: ".format(batch_id)) + self._reset_activation_persistable() logging.info("Calculate scale factor ...") - self._calculate_scale_factor() + if self._algo == "KL": + self._calculate_kl_threshold() logging.info("Update the program ...") - self._update_program() + if self._algo in ["KL", "abs_max"]: + self._update_program() + else: + self._save_input_threhold() logging.info("Save ...") - self._save_output_scale() + self._save_output_threshold() logging.info("Finish quant!") return self._program def save_quantized_model(self, save_model_path): ''' Save the quantized model to the disk. - Args: save_model_path(str): The path to save the quantized model Returns: @@ -176,88 +202,47 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): executor=self._executor, params_filename='__params__', main_program=self._program) - - def _preprocess(self): + + def _load_model_data(self): ''' - Load model and set data loader, collect the variable names for sampling, - and set activation variables to be persistable. + Set data loader. ''' feed_vars = [fluid.framework._get_var(var.name, self._program) \ for var in self._feed_list] - self._data_loader = fluid.io.DataLoader.from_generator( feed_list=feed_vars, capacity=3 * self._batch_size, iterable=True) self._data_loader.set_sample_list_generator( self._dataset.generator(self._batch_size, drop_last=True), places=self._place) - # collect the variable names for sampling - persistable_var_names = [] - for var in self._program.list_vars(): - if var.persistable: - persistable_var_names.append(var.name) - - for op in self._program.global_block().ops: - op_type = op.type - if op_type in self._quantizable_op_type: - if op_type in ("conv2d", "depthwise_conv2d"): - self._quantized_act_var_name.add(op.input("Input")[0]) - self._quantized_weight_var_name.add(op.input("Filter")[0]) - self._quantized_act_var_name.add(op.output("Output")[0]) - elif op_type == "mul": - if self._is_input_all_not_persistable( - op, persistable_var_names): - op._set_attr("skip_quant", True) - logging.warning( - "Skip quant a mul op for two input variables are not persistable" - ) - else: - self._quantized_act_var_name.add(op.input("X")[0]) - self._quantized_weight_var_name.add(op.input("Y")[0]) - self._quantized_act_var_name.add(op.output("Out")[0]) - else: - # process other quantizable op type, the input must all not persistable - if self._is_input_all_not_persistable( - op, persistable_var_names): - input_output_name_list = self._op_real_in_out_name[ - op_type] - for input_name in input_output_name_list[0]: - for var_name in op.input(input_name): - self._quantized_act_var_name.add(var_name) - for output_name in input_output_name_list[1]: - for var_name in op.output(output_name): - self._quantized_act_var_name.add(var_name) - - # set activation variables to be persistable, so can obtain - # the tensor data in sample_data - for var in self._program.list_vars(): - if var.name in self._quantized_act_var_name: - var.persistable = True - - def _calculate_scale_factor(self): + def _calculate_kl_threshold(self): ''' - Calculate the scale factor of quantized variables. + Calculate the KL threshold of quantized variables. ''' - # apply channel_wise_abs_max quantization for weights + assert self._algo == "KL", "The algo should be KL to calculate kl threshold." ct = 1 + # Abs_max threshold for weights for var_name in self._quantized_weight_var_name: start = time.time() - data = self._sampling_data[var_name] - scale_factor_per_channel = [] - for i in range(data.shape[0]): - abs_max_value = np.max(np.abs(data[i])) - scale_factor_per_channel.append(abs_max_value) - self._quantized_var_scale_factor[ - var_name] = scale_factor_per_channel + weight_data = self._sampling_data[var_name] + weight_threshold = None + if self._weight_quantize_type == "abs_max": + weight_threshold = np.max(np.abs(weight_data)) + elif self._weight_quantize_type == "channel_wise_abs_max": + weight_threshold = [] + for i in range(weight_data.shape[0]): + abs_max_value = np.max(np.abs(weight_data[i])) + weight_threshold.append(abs_max_value) + self._quantized_var_kl_threshold[var_name] = weight_threshold end = time.time() logging.debug('[Calculate weight] Weight_id={}/{}, time_each_weight={} s.'.format( str(ct), str(len(self._quantized_weight_var_name)), str(end-start))) ct += 1 - + ct = 1 - # apply kl quantization for activation + # KL threshold for activations if self._is_use_cache_file: for var_name in self._quantized_act_var_name: start = time.time() @@ -269,13 +254,8 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): sampling_data.append(np.load(file_path)) os.remove(file_path) sampling_data = np.concatenate(sampling_data) - - if self._algo == "KL": - self._quantized_var_scale_factor[var_name] = \ - self._get_kl_scaling_factor(np.abs(sampling_data)) - else: - self._quantized_var_scale_factor[var_name] = \ - np.max(np.abs(sampling_data)) + self._quantized_var_kl_threshold[var_name] = \ + self._get_kl_scaling_factor(np.abs(sampling_data)) end = time.time() logging.debug('[Calculate activation] Activation_id={}/{}, time_each_activation={} s.'.format( str(ct), @@ -287,15 +267,13 @@ class PaddleXPostTrainingQuantization(PostTrainingQuantization): start = time.time() self._sampling_data[var_name] = np.concatenate( self._sampling_data[var_name]) - if self._algo == "KL": - self._quantized_var_scale_factor[var_name] = \ - self._get_kl_scaling_factor(np.abs(self._sampling_data[var_name])) - else: - self._quantized_var_scale_factor[var_name] = \ - np.max(np.abs(self._sampling_data[var_name])) + self._quantized_var_kl_threshold[var_name] = \ + self._get_kl_scaling_factor(np.abs(self._sampling_data[var_name])) end = time.time() logging.debug('[Calculate activation] Activation_id={}/{}, time_each_activation={} s.'.format( str(ct), str(len(self._quantized_act_var_name)), str(end-start))) - ct += 1 \ No newline at end of file + ct += 1 + + \ No newline at end of file diff --git a/paddlex/interpret/core/_session_preparation.py b/paddlex/interpret/core/_session_preparation.py index b66742ea95877b21a66ed83bb94d2369e65d4a28..3935338b540127efe0cfdd0349706ba19b1a6a0f 100644 --- a/paddlex/interpret/core/_session_preparation.py +++ b/paddlex/interpret/core/_session_preparation.py @@ -13,15 +13,30 @@ #limitations under the License. import os +import os.path as osp import paddle.fluid as fluid +import paddlex as pdx import numpy as np from paddle.fluid.param_attr import ParamAttr -from ..as_data_reader.readers import preprocess_image +from paddlex.interpret.as_data_reader.readers import preprocess_image -root_path = os.environ['HOME'] -root_path = os.path.join(root_path, '.paddlex') -h_pre_models = os.path.join(root_path, "pre_models") -h_pre_models_kmeans = os.path.join(h_pre_models, "kmeans_model.pkl") +def gen_user_home(): + if "HOME" in os.environ: + home_path = os.environ["HOME"] + if os.path.exists(home_path) and os.path.isdir(home_path): + return home_path + return os.path.expanduser('~') + + +root_path = gen_user_home() +root_path = osp.join(root_path, '.paddlex') +h_pre_models = osp.join(root_path, "pre_models") +if not osp.exists(h_pre_models): + if not osp.exists(root_path): + os.makedirs(root_path) + url = "https://bj.bcebos.com/paddlex/interpret/pre_models.tar.gz" + pdx.utils.download_and_decompress(url, path=root_path) +h_pre_models_kmeans = osp.join(h_pre_models, "kmeans_model.pkl") def paddle_get_fc_weights(var_name="fc_0.w_0"): @@ -110,4 +125,4 @@ def compute_features_for_kmeans(data_content): images = preprocess_image(data_content) # transpose to [N, 3, H, W], scaled to [0.0, 1.0] result = exe.run(prog, fetch_list=[resized_features], feed={'image': images}) - return result[0][0] \ No newline at end of file + return result[0][0] diff --git a/paddlex/interpret/core/interpretation_algorithms.py b/paddlex/interpret/core/interpretation_algorithms.py index 51f03c25ada990ce9703edd782aaef9d7fcc8540..eafb4850f5141dc86507fa12f0a345da8d713993 100644 --- a/paddlex/interpret/core/interpretation_algorithms.py +++ b/paddlex/interpret/core/interpretation_algorithms.py @@ -17,9 +17,10 @@ import numpy as np import time from . import lime_base -from ..as_data_reader.readers import read_image from ._session_preparation import paddle_get_fc_weights, compute_features_for_kmeans, h_pre_models_kmeans from .normlime_base import combine_normlime_and_lime, get_feature_for_kmeans, load_kmeans_model +from paddlex.interpret.as_data_reader.readers import read_image + import cv2 @@ -442,3 +443,6 @@ def save_fig(data_, save_outdir, algorithm_name, num_samples=3000): save_outdir, f_out ) ) + print('The image of intrepretation result save in {}'.format(os.path.join( + save_outdir, f_out + ))) diff --git a/paddlex/interpret/core/lime_base.py b/paddlex/interpret/core/lime_base.py index d0b2a79a6a1a413b58f18784a0b35b5ad6c5ab7c..57844baa1737bb9f656eaf43a4c1f267c421c262 100644 --- a/paddlex/interpret/core/lime_base.py +++ b/paddlex/interpret/core/lime_base.py @@ -36,6 +36,7 @@ from skimage.color import gray2rgb from sklearn.linear_model import Ridge, lars_path from sklearn.utils import check_random_state +import tqdm import copy from functools import partial from skimage.segmentation import quickshift @@ -509,7 +510,7 @@ class LimeImageInterpreter(object): labels = [] data[0, :] = 1 imgs = [] - for row in data: + for row in tqdm.tqdm(data): temp = copy.deepcopy(image) zeros = np.where(row == 0)[0] mask = np.zeros(segments.shape).astype(bool) diff --git a/paddlex/interpret/core/normlime_base.py b/paddlex/interpret/core/normlime_base.py index 75f1e920c479cc060e8963ffb5710bf4c8ca57fa..c1ae902f33a39a3488bf6e9fd5035c295e4ac45f 100644 --- a/paddlex/interpret/core/normlime_base.py +++ b/paddlex/interpret/core/normlime_base.py @@ -16,7 +16,7 @@ import os import numpy as np import glob -from ..as_data_reader.readers import read_image +from paddlex.interpret.as_data_reader.readers import read_image from . import lime_base from ._session_preparation import compute_features_for_kmeans, h_pre_models_kmeans diff --git a/paddlex/interpret/visualize.py b/paddlex/interpret/visualize.py index 819f0decb0c4a419a5d8050aad2e363b13653b78..1b2e12110efef909abe18a18463bc8e2417d83df 100644 --- a/paddlex/interpret/visualize.py +++ b/paddlex/interpret/visualize.py @@ -21,7 +21,7 @@ import paddlex as pdx from .interpretation_predict import interpretation_predict from .core.interpretation import Interpretation from .core.normlime_base import precompute_normlime_weights - +from .core._session_preparation import gen_user_home def visualize(img_file, model, @@ -44,6 +44,8 @@ def visualize(img_file, 'Now the interpretation visualize only be supported in classifier!' if model.status != 'Normal': raise Exception('The interpretation only can deal with the Normal model') + if not osp.exists(save_dir): + os.makedirs(save_dir) model.arrange_transforms( transforms=model.test_transforms, mode='test') tmp_transforms = copy.deepcopy(model.test_transforms) @@ -107,13 +109,14 @@ def get_normlime_interpreter(img, model, dataset, num_samples=3000, batch_size=5 labels_name = None if dataset is not None: labels_name = dataset.labels - root_path = os.environ['HOME'] + root_path = gen_user_home() root_path = osp.join(root_path, '.paddlex') pre_models_path = osp.join(root_path, "pre_models") if not osp.exists(pre_models_path): - os.makedirs(pre_models_path) + if not osp.exists(root_path): + os.makedirs(root_path) url = "https://bj.bcebos.com/paddlex/interpret/pre_models.tar.gz" - pdx.utils.download_and_decompress(url, path=pre_models_path) + pdx.utils.download_and_decompress(url, path=root_path) npy_dir = precompute_for_normlime(precompute_predict_func, dataset, num_samples=num_samples, diff --git a/requirements.txt b/requirements.txt index 95d94e211e422c7723595c504ea321559a638d35..5a50e8b80c712ea98b488ed7a00ff01f906748a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,8 @@ -pyyaml tqdm colorama sklearn cython pycocotools -visualdl=1.3.0 -paddleslim=1.0.1 +visualdl >= 2.0.0b +paddleslim == 1.0.1 shapely diff --git a/tutorials/interpret/interpret.py b/tutorials/interpret/interpret.py index a052ad8bc4983cb93bcc5f761428ead0aa4f5261..f52d1053f5dcb1b2f1a585f50e9e0f2b1cb13ef2 100644 --- a/tutorials/interpret/interpret.py +++ b/tutorials/interpret/interpret.py @@ -24,18 +24,15 @@ test_dataset = pdx.datasets.ImageNet( transforms=model.test_transforms) # 可解释性可视化 -# LIME算法 pdx.interpret.visualize( - 'mini_imagenet_veg/mushroom/n07734744_1106.JPEG', - model, - test_dataset, - algo='lime', - save_dir='./') - -# NormLIME算法 + 'mini_imagenet_veg/mushroom/n07734744_1106.JPEG', + model, + test_dataset, + algo='lime', + save_dir='./') pdx.interpret.visualize( - 'mini_imagenet_veg/mushroom/n07734744_1106.JPEG', - model, - test_dataset, - algo='normlime', - save_dir='./') + 'mini_imagenet_veg/mushroom/n07734744_1106.JPEG', + model, + test_dataset, + algo='normlime', + save_dir='./')