十餘行程式碼完成遷移學習,PaddleHub實戰篇

飛槳PaddlePaddle發表於2019-05-07

移學習 (Transfer Learning) 是屬於深度學習的一個子研究領域,該研究領域的目標在於利用資料、任務、或模型之間的相似性,將在舊領域學習過的知識,遷移應用於新領域中。遷移學習吸引了很多研究者投身其中,因為它能夠很好的解決深度學習中的以下幾個問題:

  • 一些研究領域只有少量標註資料,且資料標註成本較高,不足以訓練一個足夠魯棒的神經網路

  • 大規模神經網路的訓練依賴於大量的計算資源,這對於一般使用者而言難以實現

  • 應對於普適化需求的模型,在特定應用上表現不盡如人意

為了讓開發者更便捷地應用遷移學習,百度 PaddlePaddle 開源了預訓練模型管理工具 PaddleHub。開發者用使用僅僅十餘行的程式碼,就能完成遷移學習。本文將為讀者全面介紹 PaddleHub 並其應用方法。

專案地址:https://github.com/PaddlePaddle/PaddleHub


PaddleHub 介紹

PaddleHub 是基於 PaddlePaddle 開發的預訓練模型管理工具,可以藉助預訓練模型更便捷地開展遷移學習工作,旨在讓 PaddlePaddle 生態下的開發者更便捷體驗到大規模預訓練模型的價值。

PaddleHub 目前的預訓練模型覆蓋了影像分類、目標檢測、詞法分析、Transformer、情感分析五大類別。未來會持續開放更多型別的深度學習模型,如語言模型、視訊分類、影像生成等預訓練模型。PaddleHub 的功能全景如圖 1 所示。

十餘行程式碼完成遷移學習,PaddleHub實戰篇

圖 1 PaddleHub 功能全景

PaddleHub 主要包括兩個功能:命令列工具和 Fine-tune API。

命令列工具

PaddleHub 借鑑了 Anaconda 和 PIP 等軟體包管理的理念,開發了命令列工具,可以方便快捷的完成模型的搜尋、下載、安裝、預測等功能,對應的關鍵的命令分別是 search,download,install,run 等。我們以 run 命令為例,介紹如何通過命令列工具進行預測。


Run 命令用於執行 Module 的預測,這裡分別舉一個 NLP 和 CV 的例子。

對於 NLP 任務:輸入資料通過--input_text 指定。以百度 LAC 模型(中文詞法分析)為例,可以通過以下命令實現單行文字分析。

# 單文字預測
$ hub run lac --input_text "今天是個好日子"

對於 CV 任務:輸入資料通過--input_path 指定。以 SSD 模型(單階段目標檢測)為例子,可以通過以下命令實現單張圖片的預測

# 使用SSD檢測模型對圖片進行目標檢測,第一條命令是下載圖片,第二條命令是執行預測,使用者也可以自
# 己準備圖片
$ wget --no-check-certificate https://paddlehub.bj.bcebos.com/resources/test_img_bird.jpg
$ hub run ssd_mobilenet_v1_pascal --input_path test_img_bird.jpg

更多的命令用法,請讀者參考文首的 Github 專案連結。

Fine-tune API

PaddleHub 提供了基於 PaddlePaddle 實現的 Fine-tune API, 重點針對大規模預訓練模型的 Fine-tune 任務做了高階的抽象,讓預訓練模型能更好服務於使用者特定場景的應用。通過大規模預訓練模型結合 Fine-tune,可以在更短的時間完成模型的收斂,同時具備更好的泛化能力。PaddleHub API 的全景如圖 2 所示。

十餘行程式碼完成遷移學習,PaddleHub實戰篇

圖 2 PaddleHub Fine-tune API 全景

  • Fine-tune :對一個 Task 進行 Fine-tune,並且定期進行驗證集評估。在 Fine-tune 的過程中,介面會定期的儲存 checkpoint(模型和執行資料),當執行被中斷時,通過 RunConfig 指定上一次執行的 checkpoint 目錄,可以直接從上一次執行的最後一次評估中恢復狀態繼續執行。

  • 遷移任務 Task:在 PaddleHub 中,Task 代表了一個 Fine-tune 的任務。任務中包含了執行該任務相關的 program 以及和任務相關的一些度量指標(如分類準確率 accuracy、precision、 recall、 F1-score 等)、模型損失等。

  • 執行配置 RunConfig:在 PaddleHub 中,RunConfig 代表了在對 Task 進行 Fine-tune 時的執行配置。包括執行的 epoch 次數、batch 的大小、是否使用 GPU 訓練等。

  • 優化策略 Strategy:在 PaddleHub 中,Strategy 類封裝了一系列適用於遷移學習的 Fine-tune 策略。Strategy 包含了對預訓練引數使用什麼學習率變化策略,使用哪種型別的優化器,使用什麼型別的正則化等。

  • 預訓練模型 Module :Module 代表了一個可執行的模型。這裡的可執行指的是,Module 可以直接通過命令列 hub run ${MODULE_NAME} 執行預測,或者通過 context 介面獲取上下文後進行 Fine-tune。在生成一個 Module 時,支援通過名稱、url 或者路徑建立 Module。

  • 資料預處理 Reader :PaddleHub 的資料預處理模組 Reader 對常見的 NLP 和 CV 任務進行了抽象。

  • 資料集 Dataset:PaddleHub 提供多種 NLP 任務和 CV 任務的資料集,可供使用者載,使用者也可以在自定義資料集上完成 Fine-tune。


基於以上介紹的 PaddleHub 兩大功能,使用者可以實現:

  • 無需編寫程式碼,一鍵使用預訓練模型進行預測;

  • 通過 hub download 命令,快速地獲取 PaddlePaddle 生態下的所有預訓練模型;

  • 藉助 PaddleHub Fine-tune API,使用少量程式碼完成遷移學習。

以下將從實戰角度,教你如何使用 PaddleHub 進行影像分類遷移。

PaddleHub 實戰

1. 安裝

PaddleHub 是基於 PaddlePaddle 的預訓練模型管理框架,使用 PaddleHub 前需要先安裝 PaddlePaddle,如果你本地已經安裝了 CPU 或者 GPU 版本的 PaddlePaddle,那麼可以跳過以下安裝步驟。

$ pip install paddlepaddle #CPU 安裝命令
或者
$ pip install paddlepaddle-gpu # GPU 安裝

推薦使用大於 1.4.0 版本的 PaddlePaddle。

通過以下命令來安裝 PaddleHub


$ pip install paddlehub

2. 選擇合適的模型

首先匯入必要的 python 包

# -*- coding: utf8 -*-
import paddlehub as hub
import paddle.fluid as fluid

接下來我們要在 PaddleHub 中選擇合適的預訓練模型來 Fine-tune,由於貓狗分類是一個影像分類任務,因此我們使用經典的 ResNet-50 作為預訓練模型。PaddleHub 提供了豐富的影像分類預訓練模型,包括了最新的神經網路架構搜尋類的 PNASNet,我們推薦你嘗試不同的預訓練模型來獲得更好的效能。

module_map = {
    "resnet50": "resnet_v2_50_imagenet",
    "resnet101": "resnet_v2_101_imagenet",
    "resnet152": "resnet_v2_152_imagenet",
    "mobilenet": "mobilenet_v2_imagenet",
    "nasnet": "nasnet_imagenet",
    "pnasnet": "pnasnet_imagenet"
}

module_name = module_map["resnet50"]
module = hub.Module(name = module_name)

3. 資料準備

接著需要載入圖片資料集。為了快速體驗,我們直接載入 PaddleHub 提供的貓狗分類資料集,如果想要使用自定義的資料進行體驗,請檢視自定義資料。

# 直接用PaddleHub提供的資料集
dataset = hub.dataset.DogCat()

4. 自定義資料

本節說明如何組裝自定義的資料,如果想使用貓狗資料集進行體驗,可以直接跳過本節。


使用自定義資料時,我們需要自己切分資料集,將資料集且分為訓練集、驗證集和測試集。

同時使用三個文字檔案來記錄對應的圖片路徑和標籤,此外還需要一個標籤檔案用於記錄標籤的名稱。

├─data: 資料目錄
  ├─train_list.txt:訓練集資料列表
  ├─test_list.txt:測試集資料列表
  ├─validate_list.txt:驗證集資料列表
 ├─label_list.txt:標籤列表
  └─……


訓練/驗證/測試集的資料列表檔案的格式如下

圖片 1 路徑 圖片 1 標籤
圖片 2 路徑 圖片 2 標籤
...

標籤列表檔案的格式如下

分類 1 名稱
分類 2 名稱
...


使用如下的方式進行載入資料,生成資料集物件

注意事項:

  1. num_labels 要填寫實際的分類數量,如貓狗分類該欄位值為 2,food101 該欄位值為 101,下文以 2 為例子

  2. base_path 為資料集實際路徑,需要填寫全路徑,下文以/test/data 為例子

  3. 訓練/驗證/測試集的資料列表檔案中的圖片路徑需要相對於 base_path 的相對路徑,例如圖片的實際位置為/test/data/dog/dog1.jpg,base_path 為/test/data,則檔案中填寫的路徑應該為 dog/dog1.jpg


# 使用本地資料集
class MyDataSet(hub.dataset.base_cv_dataset.ImageClassificationDataset):
 def __init__(self):
 self.base_path = "/test/data"
 self.train_list_file = "train_list.txt"
 self.test_list_file = "test_list.txt"
 self.validate_list_file = "validate_list.txt"
 self.label_list_file = "label_list.txt"
 self.label_list = None
 self.num_labels = 2

5. 生成 Reader

接著生成一個影像分類的 reader,reader 負責將 dataset 的資料進行預處理,接著以特定格式組織並輸入給模型進行訓練。

當我們生成一個影像分類的 reader 時,需要指定輸入圖片的大小

data_reader = hub.reader.ImageClassificationReader(
    image_width=module.get_expected_image_width(),
    image_height=module.get_expected_image_height(),
    images_mean=module.get_pretrained_images_mean(),
    images_std=module.get_pretrained_images_std(),
    dataset=dataset)


6. 組建 Fine-tune Task

有了合適的預訓練模型和準備要遷移的資料集後,我們開始組建一個 Task。

由於貓狗分類是一個二分類的任務,而我們下載的 cv_classifer_module 是在 ImageNet 資料集上訓練的千分類模型,所以我們需要對模型進行簡單的微調,把模型改造為一個二分類模型:

  1. 獲取 cv_classifer_module 的上下文環境,包括輸入和輸出的變數,以及 Paddle Program;

  2. 從輸出變數中找到特徵圖提取層 feature_map;

  3. 在 feature_map 後面接入一個全連線層,生成 Task;

input_dict, output_dict, program = module.context(trainable=True)

img = input_dict["image"]
feature_map = output_dict["feature_map"]

task = hub.create_img_cls_task(
    feature=feature_map, num_classes=dataset.num_labels)

feed_list = [img.name, task.variable("label").name]

7. 選擇執行時配置

在進行 Fine-tune 前,我們可以設定一些執行時的配置,例如如下程式碼中的配置,表示:

  • use_cuda:設定為 False 表示使用 CPU 進行訓練。如果本機支援 GPU,且安裝的是 GPU 版本的 PaddlePaddle,我們建議你將這個選項設定為 True;

  • epoch:要求 Fine-tune 的任務只遍歷 1 次訓練集;

  • batch_size:每次訓練的時候,給模型輸入的每批資料大小為 32,模型訓練時能夠並行處理批資料,因此 batch_size 越大,訓練的效率越高,但是同時帶來了記憶體的負荷,過大的 batch_size 可能導致記憶體不足而無法訓練,因此選擇一個合適的 batch_size 是很重要的一步;

  • log_interval:每隔 10 step 列印一次訓練日誌;

  • eval_interval:每隔 50 step 在驗證集上進行一次效能評估;

  • checkpoint_dir:將訓練的引數和資料儲存到 cv_Fine-tune_turtorial_demo 目錄中;

  • strategy:使用 DefaultFine-tuneStrategy 策略進行 Fine-tune;

更多執行配置,請檢視文首的 Github 專案連結。

config = hub.RunConfig(
    use_cuda=False,
    num_epoch=1,
    checkpoint_dir="cv_finetune_turtorial_demo",
    batch_size=32,
    log_interval=10,
    eval_interval=50,
    strategy=hub.finetune.strategy.DefaultFinetuneStrategy())

8. 開始 Fine-tune

我們選擇 Fine-tune_and_eval 介面來進行模型訓練,這個介面在 Fine-tune 的過程中,會週期性的進行模型效果的評估,以便我們瞭解整個訓練過程的效能變化。

hub.finetune_and_eval(
    task, feed_list=feed_list, data_reader=data_reader, config=config)


9. 檢視訓練過程的效果

訓練過程中的效能資料會被記錄到本地,我們可以通過 visualdl 來視覺化這些資料。

我們在 shell 中輸入以下命令來啟動 visualdl,其中${HOST_IP} 為本機 IP,需要使用者自行指定

$ visualdl --logdir ./ cv_finetune_turtorial_demo/vdllog --host ${HOST_IP} --port 8989

啟動服務後,我們使用瀏覽器訪問${HOST_IP}:8989,可以看到訓練以及預測的 loss 曲線和 accuracy 曲線,如下圖所示。

十餘行程式碼完成遷移學習,PaddleHub實戰篇

10. 使用模型進行預測

當 Fine-tune 完成後,我們使用模型來進行預測,整個預測流程大致可以分為以下幾步:

  1. 構建網路

  2. 生成預測資料的 Reader

  3. 切換到預測的 Program

  4. 載入預訓練好的引數

  5. 執行 Program 進行預測

通過以下命令來獲取測試的圖片(適用於貓狗分類的資料集)

$ wget --no-check-certificate https://PaddleHub.bj.bcebos.com/resources/test_img_cat.jpg
$ wget --no-check-certificate https://PaddleHub.bj.bcebos.com/resources/test_img_dog.jpg


注意:其他資料集所用的測試圖片請自行準備。

完整預測程式碼如下:

import os
import numpy as np
import paddle.fluid as fluid
import paddlehub as hub

# Step 1: build Program
module_map = {
    "resnet50": "resnet_v2_50_imagenet",
    "resnet101": "resnet_v2_101_imagenet",
    "resnet152": "resnet_v2_152_imagenet",
    "mobilenet": "mobilenet_v2_imagenet",
    "nasnet": "nasnet_imagenet",
    "pnasnet": "pnasnet_imagenet"
}

module_name = module_map["resnet50"]
module = hub.Module(name = module_name)
input_dict, output_dict, program = module.context(trainable=False)
img = input_dict["image"]
feature_map = output_dict["feature_map"]

dataset = hub.dataset.DogCat()
task = hub.create_img_cls_task(
    feature=feature_map, num_classes=dataset.num_labels)
feed_list = [img.name]

# Step 2: create data reader
data = [
    "test_img_dog.jpg",
    "test_img_cat.jpg"
]

data_reader = hub.reader.ImageClassificationReader(
    image_width=module.get_expected_image_width(),
    image_height=module.get_expected_image_height(),
    images_mean=module.get_pretrained_images_mean(),
    images_std=module.get_pretrained_images_std(),
    dataset=None)

predict_reader = data_reader.data_generator(
    phase="predict", batch_size=1, data=data)

label_dict = dataset.label_dict()

# Step 3: switch to inference program
with fluid.program_guard(task.inference_program()):
    # Step 4: load pretrained parameters
    place = fluid.CPUPlace()
    exe = fluid.Executor(place)
    pretrained_model_dir = os.path.join("cv_finetune_turtorial_demo", "best_model")
    fluid.io.load_persistables(exe, pretrained_model_dir)
    feeder = fluid.DataFeeder(feed_list=feed_list, place=place)
    # Step 5: predict
    for index, batch in enumerate(predict_reader()):
        result, = exe.run(
            feed=feeder.feed(batch), fetch_list=[task.variable('probs')])
        predict_result = np.argsort(result[0])[::-1][0]
        print("input %i is %s, and the predict result is %s" %
              (index+1, data[index], label_dict[predict_result]))

相關文章