華為云GPU服務(wù)器使用PaddleClas和PaddleServing訓(xùn)練图焰、部署車輛類型分類模型服務(wù)

0 前言

以下針對最近使用PaddleClas和PaddleServing在華為云GPU服務(wù)器上訓(xùn)練和部署一個車輛類型識別模型過程進(jìn)行記錄夏志,以供日后自己參考和其他有需要的朋友一些幫助鬼癣,接觸這方面東西時間較短擂送,如有問題歡迎批評指正回溺。

如何在華為云服務(wù)器上搭建GPU版本的PaddlePaddle環(huán)境請參考以下文章: https://blog.csdn.net/loutengyuan/article/details/126527326

1 環(huán)境準(zhǔn)備

需要準(zhǔn)備PaddleClas的運行環(huán)境和Paddle Serving的運行環(huán)境春贸。

  • 準(zhǔn)備PaddleClas的運行環(huán)境鏈接
# 克隆代碼
git clone https://github.com/PaddlePaddle/PaddleClas
  • 安裝PaddleServing的運行環(huán)境混萝,步驟如下
# 安裝serving,用于啟動服務(wù)
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_server_gpu-0.8.3.post102-py3-none-any.whl
pip3 install paddle_serving_server_gpu-0.8.3.post102-py3-none-any.whl

# 安裝client萍恕,用于向服務(wù)發(fā)送請求
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_client-0.8.3-cp38-none-any.whl
pip3 install paddle_serving_client-0.8.3-cp38-none-any.whl

# 安裝serving-app
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_app-0.8.3-py3-none-any.whl
pip3 install paddle_serving_app-0.8.3-py3-none-any.whl

2 數(shù)據(jù)集及其處理

將分類整理好的數(shù)據(jù)按照不同分類分別放在不同文件夾下逸嘀,然后將數(shù)據(jù)上傳至華為云服務(wù)器,目錄結(jié)構(gòu)如下:

# tree ./TruckType
.
├── test_01.jpg
├── TruckType
│   ├── 0-qyc
│   │   ├── 10765.jpg
│   │   ├── 19994.jpg
│   │   ├── 1029.jpg
│   │   ├── 106710.jpg
│   │   ├── 9610.jpg
│   │   ├── 98388.jpg
│   │   └── 9938.jpg
│   ├── 1-zhc
│   │   ├── 10154.jpg
│   │   ├── 1055.jpg
│   │   ├── 10801.jpg
│   │   ├── 9969.jpg
│   │   ├── 9970.jpg
│   │   ├── 9513.jpg
│   │   └── 9515.jpg
│   ├── 2-zxc
│   │   ├── 5274.jpg
│   │   ├── 69648.jpg
│   │   ├── 6649.jpg
│   │   ├── 5651.jpg
│   │   ├── 3055.jpg
│   │   ├── 7630.jpg
│   │   ├── 58.jpg
│   │   └── 9082.jpg
│   ├── 3-gc
│   │   ├── 9587.jpg
│   │   ├── 855.jpg
│   │   ├── 663.jpg
│   │   ├── 5611.jpg
│   │   ├── 9085.jpg
│   │   └── 2284.jpg
│   ├── 4-jbc
│   │   ├── 874.jpg
│   │   ├── 56456.jpg
│   │   ├── 36576.jpg
│   │   └── 25244.jpg
│   ├── all_list.txt
│   ├── label_list.txt
│   ├── test_list.txt
│   ├── train_list.txt
│   └── val_list.txt
└── write_label_truck_type.py

test_01.jpg 用于測試訓(xùn)練模型
0-qyc 允粤、1-zhc 崭倘、2-zxc 、3-gc 类垫、4-jbc 分別是不同類型的車輛類型圖片(注意:圖片文件名最好不要有中文司光、括號或者空格之類的特殊字符,容易訓(xùn)練報錯)
all_list.txt悉患、label_list.txt残家、test_list.txt、train_list.txt售躁、val_list.txt 分別是處理后生成的標(biāo)簽文件
write_label_truck_type.py 是處理數(shù)據(jù)的腳步文件坞淮,用于自動生成以上標(biāo)簽文件

生成標(biāo)簽文件腳步 write_label_truck_type.py 代碼如下:

# -*- coding: utf-8 -*-
import os
import sys
from sklearn.utils import shuffle

# 拿到總的訓(xùn)練數(shù)據(jù)txt
# -*- coding: utf-8 -*-
# 根據(jù)官方paddleclas的提示,我們需要把圖像變?yōu)閮蓚€txt文件
# train_list.txt(訓(xùn)練集)
# val_list.txt(驗證集)
# 先把路徑搞定 比如:foods/beef_carpaccio/855780.jpg ,讀取到并寫入txt
# 根據(jù)左側(cè)生成的文件夾名字來寫根目錄
# 先得到總的txt后續(xù)再進(jìn)行劃分陪捷,因為要劃分出驗證集回窘,所以要先打亂,因為原本是有序的
def get_all_txt(image_root, dir_name):
    all_list = []
    i = 0 # 標(biāo)記總文件數(shù)量
    j = -1 # 標(biāo)記文件類別
    is_image_root = True
    for root, dirs, files in os.walk(image_root+dir_name): # 分別代表根目錄市袖、文件夾毫玖、文件
        if is_image_root:
            out_dirs = dirs
            is_image_root = False
        for file in files:
            i = i + 1
            # 文件中每行格式: 圖像相對路徑      圖像的label_id(數(shù)字類別)(注意:中間有空格)。
            imgpath = os.path.join(root,file).replace(image_root, "")
            all_list.append(imgpath+" "+str(j)+"\n")
        j = j + 1
    return all_list, i, out_dirs


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("請傳入預(yù)處理圖像根目錄和文件夾: 傳入?yún)?shù)長度錯誤凌盯!")
    else:
        # for arg in sys.argv:
        #     print(arg)
        image_root = sys.argv[1]
        dir_name = sys.argv[2]
        print("image_root = {}  dir_name = {}".format(image_root, dir_name))
        # 拿到總的訓(xùn)練數(shù)據(jù)txt
        all_list, all_len, dirs = get_all_txt(image_root, dir_name)
        print(all_len)
        print(dirs)

        # 寫入標(biāo)簽文件
        label_list = []
        dir_idx = 0
        for dir in dirs:
            label_list.append("{} {}\n".format(dir_idx, dir))
            dir_idx = dir_idx + 1
        label_str = ''.join(label_list)
        f = open(image_root+dir_name+'/label_list.txt', 'w', encoding='utf-8')
        f.write(label_str)
        print("寫入標(biāo)簽文件完成")

        # 把數(shù)據(jù)打亂
        all_list = shuffle(all_list)
        allstr = ''.join(all_list)
        f = open(image_root+dir_name+'/all_list.txt', 'w', encoding='utf-8')
        f.write(allstr)
        print("打亂成功,并寫入文本")

        # 按照比例劃分?jǐn)?shù)據(jù)集 食品的數(shù)據(jù)有5000張圖片烹玉,不算大數(shù)據(jù)驰怎,一般9:1即可
        train_size = int(all_len * 0.8)
        train_list = all_list[:train_size]
        temp_list = all_list[train_size:]
        val_size = int(len(temp_list) * 0.8)
        val_list = temp_list[:val_size]
        test_list = temp_list[val_size:]

        print(len(train_list))
        print(len(val_list))
        print(len(test_list))

        # 生成訓(xùn)練集txt
        train_txt = ''.join(train_list)
        f_train = open(image_root+dir_name+'/train_list.txt', 'w', encoding='utf-8')
        f_train.write(train_txt)
        f_train.close()
        print("train_list.txt 生成成功!")

        # 生成驗證集txt
        val_txt = ''.join(val_list)
        f_val = open(image_root+dir_name+'/val_list.txt', 'w', encoding='utf-8')
        f_val.write(val_txt)
        f_val.close()
        print("val_list.txt 生成成功二打!")

        # 生成驗證集txt
        test_txt = ''.join(test_list)
        f_test = open(image_root+dir_name+'/test_list.txt', 'w', encoding='utf-8')
        f_test.write(test_txt)
        f_test.close()
        print("test_list.txt 生成成功县忌!")

執(zhí)行腳本:

cd 數(shù)據(jù)目錄
python write_label_truck_type.py ./ TruckType

all_list.txt、test_list.txt继效、train_list.txt症杏、val_list.txt 內(nèi)容格式類似如下:

TruckType/1-zhc/495218.jp 1
TruckType/3-gc/543432.jpg 3
TruckType/2-zxc/3453.jpg 2
TruckType/2-zxc/343453.jpg 2
TruckType/3-gc/34545.jpg 3
TruckType/1-zhc/637371.jpg 1
TruckType/0-qyc/32354.jpg 0
TruckType/0-qyc/650456.jpg 0

label_list.txt 格式如下:

0 0-qyc
1 1-zhc
2 2-zxc
3 3-gc
4 4-jbc

3 模型訓(xùn)練

進(jìn)入之前下載的PaddleClas代碼目錄

# cd PaddleClas
# ll
total 148
drwxr-xr-x  2 root root  4096 Aug 25 14:52 benchmark
drwxr-xr-x  2 root root  4096 Aug 25 14:52 dataset
drwxr-xr-x 22 root root  4096 Sep  2 11:10 deploy
drwxr-xr-x  6 root root  4096 Aug 25 14:52 docs
-rw-r--r--  1 root root 28095 Aug 25 14:52 hubconf.py
drwxr-xr-x  4 root root  4096 Sep  3 09:32 inference
-rw-r--r--  1 root root   705 Aug 25 14:52 __init__.py
-rw-r--r--  1 root root 11357 Aug 25 14:52 LICENSE
-rw-r--r--  1 root root   259 Aug 25 14:52 MANIFEST.in
drwxr-xr-x  6 root root  4096 Sep  3 08:55 output
-rw-r--r--  1 root root 24463 Aug 25 14:52 paddleclas.py
drwxr-xr-x 12 root root  4096 Aug 31 16:34 ppcls
-rw-r--r--  1 root root  9819 Aug 25 14:52 README_ch.md
-rw-r--r--  1 root root  9149 Aug 25 14:52 README_en.md
-rw-r--r--  1 root root    12 Aug 25 14:52 README.md
-rw-r--r--  1 root root   148 Aug 25 14:52 requirements.txt
-rw-r--r--  1 root root  2343 Aug 25 14:52 setup.py
drwxr-xr-x  3 root root  4096 Aug 25 14:52 tests
drwxr-xr-x  5 root root  4096 Aug 25 14:52 test_tipc
drwxr-xr-x  2 root root  4096 Aug 25 14:52 tools

3.1 修改配置文件

主要是以下幾點:分類數(shù)、訓(xùn)練和驗證的路徑瑞信、圖像尺寸厉颤、數(shù)據(jù)預(yù)處理、訓(xùn)練和預(yù)測的num_workers: 0
(需要將num_workers改為0凡简,因為是單卡的)
下面以新手快速入門的ShuffleNetV2_x0_25為例子演示逼友,實際上PaddleClas/ppcls/configs/ImageNet/下面的文件夾全都是模型文件精肃,可以自行選用。
路徑如下:

PaddleClas/ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25.yaml

將其拷貝一份出來命名為ShuffleNetV2_x0_25_truck_type.yaml 路徑如下:

PaddleClas/ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25_truck_type.yaml

修改配置文件 ShuffleNetV2_x0_25_truck_type.yaml 如下:

# global configs
Global:
  checkpoints: null
  pretrained_model: null
  output_dir: ./output/truck_type/
  # 使用GPU訓(xùn)練
  device: gpu
  # 每幾個輪次保存一次
  save_interval: 1 
  eval_during_train: True
  # 每幾個輪次驗證一次
  eval_interval: 1 
  # 訓(xùn)練輪次
  epochs: 100
  print_batch_step: 1
  use_visualdl: True #開啟可視化(目前平臺不可用)
  # used for static mode and model export
  # 圖像大小
  image_shape: [3, 224, 224] 
  save_inference_dir: ./inference/clas_truck_type_infer
  # training model under @to_static
  to_static: False

# model architecture
Arch:
  # 采用的網(wǎng)絡(luò)
  name: ShuffleNetV2_x0_25
  class_num: 5
 
# loss function config for traing/eval process
Loss:
  Train:

    - CELoss: 
        weight: 1.0
  Eval:
    - CELoss:
        weight: 1.0


Optimizer:
  name: Momentum
  momentum: 0.9
  lr:
    name: Piecewise
    learning_rate: 0.015
    decay_epochs: [30, 60, 90]
    values: [0.1, 0.01, 0.001, 0.0001]
  regularizer:
    name: 'L2'
    coeff: 0.0005


# data loader for train and eval
DataLoader:
  Train:
    dataset:
      name: ImageNetDataset
      # 根路徑
      image_root: /yxdata/truck_type/
      # 前面自己生產(chǎn)得到的訓(xùn)練集文本路徑
      cls_label_path: /yxdata/truck_type/TruckType/train_list.txt
      # 數(shù)據(jù)預(yù)處理
      transform_ops:
        - DecodeImage:
            to_rgb: True
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - RandFlipImage:
            flip_code: 1
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''

    sampler:
      name: DistributedBatchSampler
      batch_size: 128
      drop_last: False
      shuffle: True
    loader:
      num_workers: 0
      use_shared_memory: True

  Eval:
    dataset: 
      name: ImageNetDataset
      # 根路徑
      image_root: /yxdata/truck_type/
      # 前面自己生產(chǎn)得到的驗證集文本路徑
      cls_label_path: /yxdata/truck_type/TruckType/val_list.txt
      # 數(shù)據(jù)預(yù)處理
      transform_ops:
        - DecodeImage:
            to_rgb: True
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
    sampler:
      name: DistributedBatchSampler
      batch_size: 128
      drop_last: False
      shuffle: True
    loader:
      num_workers: 0
      use_shared_memory: True

Infer:
  infer_imgs: /yxdata/truck_type/test_01.jpg
  batch_size: 10
  transforms:
    - DecodeImage:
        to_rgb: True
        channel_first: False
    - ResizeImage:
        resize_short: 256
    - CropImage:
        size: 224
    - NormalizeImage:
        scale: 1.0/255.0
        mean: [0.485, 0.456, 0.406]
        std: [0.229, 0.224, 0.225]
        order: ''
    - ToCHWImage:
  PostProcess:
    name: Topk
    # 輸出的可能性最高的前topk個
    topk: 3
    # 標(biāo)簽文件 需要自己新建文件
    class_id_map_file: /yxdata/truck_type/TruckType/label_list.txt

Metric:
  Train:
    - TopkAcc:
        topk: [1, 3]
  Eval:
    - TopkAcc:
        topk: [1, 3]

3.2 開始訓(xùn)練

python3 tools/train.py \
    -c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25_truck_type.yaml \
    -o Global.device=gpu

訓(xùn)練后會在 PaddleClas/output/truck_type/ 目錄下生成模型文件

# tree ./truck_type/
├── ShuffleNetV2_x0_25
│   ├── best_model.pdopt
│   ├── best_model.pdparams
│   ├── best_model.pdstates
│   ├── epoch_100.pdopt
│   ├── epoch_100.pdparams
│   ├── epoch_100.pdstates
│   ├── epoch_10.pdopt
│   ├── epoch_10.pdparams
│   ├── epoch_10.pdstates
│   ├── epoch_11.pdopt
│   ├── epoch_11.pdparams
│   ├── epoch_11.pdstates
│   ├── epoch_1.pdopt
│   ├── epoch_1.pdparams
│   ├── epoch_1.pdstates
│   ├── export.log
│   ├── infer.log
│   ├── latest.pdopt
│   ├── latest.pdparams
│   ├── latest.pdstates
│   └── train.log
└── vdl
    └── vdlrecords.1662166534.log

3.3 預(yù)測一張

python3 tools/infer.py \
    -c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25_truck_type.yaml \
    -o Infer.infer_imgs=/yxdata/truck_type/test_01.jpg \
    -o Global.pretrained_model=output/truck_type/ShuffleNetV2_x0_25/best_model

預(yù)測結(jié)果如下:

[{'class_ids': [4, 0, 1], 'scores': [0.9976, 0.00225, 0.0001], 'file_name': '/yxdata/truck_type/test_01.jpg', 'label_names': ['1-zhc', '3-gc', '2-zxc']}]

3.4 批量預(yù)測

python3 tools/infer.py \
    -c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25_truck_type.yaml \
    -o Infer.infer_imgs=/yxdata/truck_type/ \
    -o Global.pretrained_model=output/truck_type/ShuffleNetV2_x0_25/best_model

預(yù)測結(jié)果如下:

[{'class_ids': [4, 0, 1], 'scores': [0.9976, 0.00225, 0.0001], 'file_name': '/yxdata/truck_type/test_01.jpg', 'label_names': ['1-zhc', '3-gc', '2-zxc']}]

3.5 導(dǎo)出預(yù)測模型

python3 tools/export_model.py \
    -c ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25_truck_type.yaml \
    -o Global.pretrained_model=output/truck_type/ShuffleNetV2_x0_25/best_model

導(dǎo)出成功后將在 PaddleClas/inference/clas_truck_type_infer/ 目錄下生成模型文件帜乞,結(jié)構(gòu)如下:

# tree ./clas_truck_type_infer/
├── inference.pdiparams
├── inference.pdiparams.info
└── inference.pdmodel

4 模型服務(wù)化部署

4.1 模型轉(zhuǎn)換

進(jìn)入工作目錄:

cd PaddleClas/deploy/

創(chuàng)建并進(jìn)入models文件夾:

# 創(chuàng)建并進(jìn)入models文件夾
mkdir models
cd models

將上一步模型訓(xùn)練的最后導(dǎo)出的練好的 inference 模型放到該文件夾下司抱,結(jié)構(gòu)如下:

└── clas_truck_type_infer
    ├── inference.pdiparams
    ├── inference.pdiparams.info
    └── inference.pdmodel

轉(zhuǎn)換車輛類型分類 inference 模型為 Serving 模型:

# 轉(zhuǎn)換車輛類型分類模型
python3.8 -m paddle_serving_client.convert \
--dirname ./clas_truck_type_infer/ \
--model_filename inference.pdmodel  \
--params_filename inference.pdiparams \
--serving_server ./clas_truck_type_serving/ \
--serving_client ./clas_truck_type_client/

車輛類型分類 inference 模型轉(zhuǎn)換完成后,會在當(dāng)前文件夾多出 clas_truck_type_serving/和 clas_truck_type_client/ 的文件夾黎烈,具備如下結(jié)構(gòu):

    ├── clas_truck_type_serving/
    │   ├── inference.pdiparams
    │   ├── inference.pdmodel
    │   ├── serving_server_conf.prototxt
    │   └── serving_server_conf.stream.prototxt
    └── clas_truck_type_client/
          ├── serving_client_conf.prototxt
          └── serving_client_conf.stream.prototxt

模型參數(shù)修改
Serving 為了兼容不同模型的部署习柠,提供了輸入輸出重命名的功能。讓不同的模型在推理部署時照棋,只需要修改配置文件的 alias_name 即可资溃,無需修改代碼即可完成推理部署。因此在轉(zhuǎn)換完畢后需要分別修改 clas_truck_type_serving下的文件 serving_server_conf.prototxt 和 clas_truck_type_client 下的文件 serving_client_conf.prototxt必怜,將 fetch_var 中 alias_name: 后的字段改為 prediction肉拓,修改后的 serving_server_conf.prototxt 和 serving_client_conf.prototxt 如下所示:

feed_var {
  name: "x"
  alias_name: "x"
  is_lod_tensor: false
  feed_type: 1
  shape: 3
  shape: 224
  shape: 224
}
fetch_var {
  name: "softmax_1.tmp_0"
  alias_name: "prediction"
  is_lod_tensor: false
  fetch_type: 1
  shape: 5
}

上述命令中參數(shù)具體含義如下表所示:

參數(shù) 類型 默認(rèn)值 描述
dirname str - 需要轉(zhuǎn)換的模型文件存儲路徑,Program結(jié)構(gòu)文件和參數(shù)文件均保存在此目錄梳庆。
model_filename str None 存儲需要轉(zhuǎn)換的模型Inference Program結(jié)構(gòu)的文件名稱暖途。如果設(shè)置為None,則使用 __model__ 作為默認(rèn)的文件名
params_filename str None 存儲需要轉(zhuǎn)換的模型所有參數(shù)的文件名稱膏执。當(dāng)且僅當(dāng)所有模型參數(shù)被保>存在一個單獨的二進(jìn)制文件中驻售,它才需要被指定。如果模型參數(shù)是存儲在各自分離的文件中更米,設(shè)置它的值為None
serving_server str "serving_server" 轉(zhuǎn)換后的模型文件和配置文件的存儲路徑欺栗。默認(rèn)值為serving_server
serving_client str "serving_client" 轉(zhuǎn)換后的客戶端配置文件存儲路徑。默認(rèn)值為serving_client

4.2 服務(wù)部署

進(jìn)入到工作目錄

  cd ./deploy/paddleserving/

paddleserving 目錄包含啟動 Python Pipeline 服務(wù)征峦、C++ Serving 服務(wù)和發(fā)送預(yù)測請求的代碼迟几,包括:

__init__.py
classification_web_service.py # 啟動pipeline服務(wù)端的腳本
config.yml                    # 啟動pipeline服務(wù)的配置文件
pipeline_http_client.py       # http方式發(fā)送pipeline預(yù)測請求的腳本
pipeline_rpc_client.py        # rpc方式發(fā)送pipeline預(yù)測請求的腳本
readme.md                     # 分類模型服務(wù)化部署文檔
run_cpp_serving.sh            # 啟動C++ Serving部署的腳本
test_cpp_serving_client.py    # rpc方式發(fā)送C++ serving預(yù)測請求的腳本

修改config.yml文件如下:

#worker_num, 最大并發(fā)數(shù)。當(dāng)build_dag_each_worker=True時, 框架會創(chuàng)建worker_num個進(jìn)程栏笆,每個進(jìn)程內(nèi)構(gòu)建grpcSever和DAG
##當(dāng)build_dag_each_worker=False時类腮,框架會設(shè)置主線程grpc線程池的max_workers=worker_num
worker_num: 1

#http端口, rpc_port和http_port不允許同時為空。當(dāng)rpc_port可用且http_port為空時蛉加,不自動生成http_port
http_port: 8877
#rpc_port: 9993

dag:
    #op資源類型, True, 為線程模型蚜枢;False,為進(jìn)程模型
    is_thread_op: False
op:
    clas_truck_type:
        #并發(fā)數(shù)针饥,is_thread_op=True時厂抽,為線程并發(fā);否則為進(jìn)程并發(fā)
        concurrency: 1

        #當(dāng)op配置沒有server_endpoints時丁眼,從local_service_conf讀取本地服務(wù)配置
        local_service_conf:

            #uci模型路徑
            model_config: ../models/clas_truck_type_serving
#            model_config: ../models/ResNet50_vd_serving

            #計算硬件類型: 空缺時由devices決定(CPU/GPU)筷凤,0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpu
            device_type: 1

            #計算硬件ID,當(dāng)devices為""或不寫時為CPU預(yù)測户盯;當(dāng)devices為"0", "0,1,2"時為GPU預(yù)測,表示使用的GPU卡
            devices: "0" # "0,1"

            #client類型,包括brpc, grpc和local_predictor.local_predictor不啟動Serving服務(wù)雇逞,進(jìn)程內(nèi)預(yù)測
            client_type: local_predictor

            #Fetch結(jié)果列表,以client_config中fetch_var的alias_name為準(zhǔn)
            fetch_list: ["prediction"]

修改 classification_web_service.py 文件如下:

# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import sys
from paddle_serving_app.reader import Sequential, URL2Image, Resize, CenterCrop, RGB2BGR, Transpose, Div, Normalize, Base64ToImage
try:
    from paddle_serving_server_gpu.web_service import WebService, Op
except ImportError:
    from paddle_serving_server.web_service import WebService, Op
import logging
import numpy as np
import base64, cv2


class TruckTypeClasOp(Op):
    def init_op(self):
        print("------------------------ TruckTypeClasOp init_op ---------------------------")
        self.seq = Sequential([
            Resize(256), CenterCrop(224), RGB2BGR(), Transpose((2, 0, 1)),
            Div(255), Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225],
                                True)
        ])
        self.label_dict = {}
        label_idx = 0
        with open("truck_type_list.label") as fin:
            for line in fin:
                self.label_dict[label_idx] = line.strip()
                label_idx += 1
        print("label_dict --> {}".format(self.label_dict))

    def preprocess(self, input_dicts, data_id, log_id):
        print("{} TruckTypeClasOp preprocess\tbegin\t--> data_id: {}".format(datetime.datetime.now(), data_id))
        (_, input_dict), = input_dicts.items()
        batch_size = len(input_dict.keys())
        imgs = []
        for key in input_dict.keys():
            data = base64.b64decode(input_dict[key].encode('utf8'))
            data = np.fromstring(data, np.uint8)
            im = cv2.imdecode(data, cv2.IMREAD_COLOR)
            img = self.seq(im)
            imgs.append(img[np.newaxis, :].copy())
        input_imgs = np.concatenate(imgs, axis=0)
        print("{} TruckTypeClasOp preprocess\tfinish\t--> data_id: {}".format(datetime.datetime.now(), data_id))
        # return {"inputs": input_imgs}, False, None, ""
        return {"x": input_imgs}, False, None, ""

    def postprocess(self, input_dicts, fetch_dict, data_id, log_id):
        print("{} TruckTypeClasOp postprocess\tbegin\t--> data_id: {}".format(datetime.datetime.now(), data_id))
        score_list = fetch_dict["prediction"]
        print("{} data_id: {}  -->  score_list: {}".format(datetime.datetime.now(), data_id, score_list))
        result = []
        for score in score_list:
            item = {}
            score = score.tolist()
            max_score = max(score)
            idx = score.index(max_score)
            print("{} data_id: {}  -->  max_score = {}  -->  idx = {}".format(datetime.datetime.now(), data_id, max_score, idx))
            if self.label_dict is not None:
                if idx < len(self.label_dict):
                    label = self.label_dict[score.index(max_score)].strip().replace(",", "")
                else:
                    label = 'ErrorType'
            else:
                label = str(idx)
            item["label"] = label
            item["prob"] = max_score
            result.append(item)
        print("{} TruckTypeClasOp postprocess\tfinish\t--> data_id: {} --> result:{}".format(datetime.datetime.now(), data_id, result))
        return {"result": str({"truck_type": result})}, None, ""


class ClassificationService(WebService):
    def get_pipeline_response(self, read_op):
        truck_type_op = TruckTypeClasOp(name="clas_truck_type", input_ops=[read_op])
        return truck_type_op


uci_service = ClassificationService(name="classification")
uci_service.prepare_pipeline_config("config.yml")
uci_service.run_service()

添加文件 truck_type_list.label 吃靠,內(nèi)容如下:

牽引車
載貨車
自卸車
掛車
攪拌車

啟動服務(wù):

# 啟動服務(wù),運行日志保存在 paddleclas_recognition_log.txt
nohup python3.8 -u classification_web_service.py &>./paddleclas_recognition_log.txt &

查看進(jìn)程

ps -ef|grep python

關(guān)閉進(jìn)程

# 通過上一步查看進(jìn)程號足淆,殺死指定進(jìn)程
kill -9 19913
# 或者通過以下命令
python3.8 -m paddle_serving_server.serve stop

查看日志

tail -f 1000 ./paddleclas_recognition_log.txt

如何查看端口占用

$: netstat -anp | grep 8888
tcp        0      0 127.0.0.1:8888          0.0.0.0:*               LISTEN      13404/python3       
tcp        0      1 172.17.0.10:34036       115.42.35.84:8888       SYN_SENT    14586/python3 

強制殺掉進(jìn)程:通過pid

$: kill -9 13404
$: kill -9 14586
$: netstat -anp | grep 8888
$:

4.3 服務(wù)測試

修改pipeline_http_client.py文件如下:

import requests
import json
import base64
import os


def cv2_to_base64(image):
    return base64.b64encode(image).decode('utf8')


if __name__ == "__main__":
    url = "http://127.0.0.1:8877/classification/prediction"
    with open(os.path.join(".", "圖片路徑.jpg"), 'rb') as file:
        image_data1 = file.read()
    image = cv2_to_base64(image_data1)

    data = {"key": ["image"], "value": [image]}
    for i in range(1):
        r = requests.post(url=url, data=json.dumps(data))
        print(r.json())

發(fā)送請求:

python3.8 pipeline_http_client.py

成功運行后巢块,模型預(yù)測的結(jié)果會打印在客戶端中,如下所示:

{'err_no': 0, 'err_msg': '', 'key': ['result'], 'value': ["{'truck_type': [{'label': '載貨車', 'prob': 0.98669669032096863}]}"], 'tensors': []}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末巧号,一起剝皮案震驚了整個濱河市族奢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丹鸿,老刑警劉巖越走,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異靠欢,居然都是意外死亡廊敌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門门怪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骡澈,“玉大人,你說我怎么就攤上這事掷空±吲梗” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵坦弟,是天一觀的道長护锤。 經(jīng)常有香客問我,道長酿傍,這世上最難降的妖魔是什么蔽豺? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮拧粪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沧侥。我一直安慰自己可霎,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布宴杀。 她就那樣靜靜地躺著癣朗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旺罢。 梳的紋絲不亂的頭發(fā)上旷余,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天绢记,我揣著相機與錄音,去河邊找鬼正卧。 笑死蠢熄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炉旷。 我是一名探鬼主播签孔,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窘行!你這毒婦竟也來了饥追?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤罐盔,失蹤者是張志新(化名)和其女友劉穎但绕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惶看,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡捏顺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碳竟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片草丧。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莹桅,靈堂內(nèi)的尸體忽然破棺而出昌执,到底是詐尸還是另有隱情,我是刑警寧澤诈泼,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布懂拾,位于F島的核電站,受9級特大地震影響铐达,放射性物質(zhì)發(fā)生泄漏岖赋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一瓮孙、第九天 我趴在偏房一處隱蔽的房頂上張望唐断。 院中可真熱鬧,春花似錦杭抠、人聲如沸脸甘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丹诀。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铆遭,已是汗流浹背硝桩。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留枚荣,地道東北人碗脊。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像棍弄,于是被迫代替她去往敵國和親望薄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容