Fastai Lesson 1:Image Classification (上篇)
介紹如何使用 fastai 輕鬆完成圖片分類
這一系列文章是在學習 Fastai 時所記錄的筆記,內容有參考教學資源中其他同學所做的筆記,建議同時搭配影片和裡面的檔案學習,單看文章內容效果有限。
Lesson1 大綱:
- 簡介
- 前期準備
- 資料 Data
- 訓練 Training
- 結果
- 製作標籤
簡介
Fastai 是由一群在 Kaggle 競賽得到第一的大神們所建立的 library,他們透過使用這個 library 來開設 deep learning 的教學課程,要求只有一年的寫程式經驗和高中數學,網址如下:
- Fastai course:https://course.fast.ai/
- Fastai docs:https://docs.fast.ai/
- Github repo:https://github.com/fastai
- Fastai forum:https://forums.fast.ai/
在進行課程之前,需要先進行一些基本的安裝,像是 Jupyter Notebook, fastai library 還有 GPU 的相關安裝設定等等,光這個搞完大概一天的時間就沒了,安裝這些東西一向也是曠日費時,要有耐心一些。
以下就直接進入 Lesson 1 的課程內容,文章大部分只會紀錄關於 deep learning 的教學內容,課程還有提到一些講者的背景介紹,幫大家打一些強心針,跟從 fastai 學成後的人們,有哪些成果等等。
第一個階段總共分 7 堂課,課程是計畫學生一週能花 10 個小時在這上面,當全部完成時,預計我們能夠學會如何:
- 建立一個由我們自己所挑選圖像的分類模型
- 分類任何我們有興趣的文字
- 預測像是銷售等等的商業應用
- 搭建一個像是 Netflix 的推薦系統
前期準備
課程跟其他比較學院派的課程不同,這同時也是講者不斷強調的事情,deep learing 是一個實務走得比理論還快的領域,所以他是透過先實作知道程式碼如何運作,他們認為要做好這件事的關鍵就是實踐 (get your hand dirty),之後再了解為什麼(即理論的部分)。
在 lesson1 的 notebook 中,會首先看到三行程式碼:
%reload_ext autoreload
%autoreload 2
%matplotlib
「%」符號是 Jupyter Notebook 的特別指令,並不是 Python 程式碼,他們被稱作 ‘magics’,上面的程式碼作用是:
- 如果有人變動了底層的源碼,那我跑這個時候,幫我自動 reload
- 將圖印在 Jupyter Notebook 中
接著:
from fastai import *
from fastai.vision import *
在課程中將會大量用 fastai 的 library,還有 Pytorch,一個開源的機器學習庫,fastai 就是建立在他上面的。
另外在上面的程式碼中,有學過 Python 的人可能會有個疑問,就是我們不是該避免使用 import *
嗎?在一般的狀況中適用,但在這裡,因為需要大量且快速地測試,我們常常隨時又要拉個新指令進來,若是每次都還要再回頭在 notebook 上面 import 東西會很麻煩,等我們很熟悉 fastai 庫中的指令時,再來用 import 特定 function,因此為了避免 import *
可能會帶來的問題,fastai 庫也有特別設計過。
資料
資料來源主要有二:
- 學術資料
- Kaggle 競賽上的資料
今天要用的是學術資料,來自於這篇論文,在這個資料中我們要分類 37 種不同品種的貓狗,這事實上還蠻難的,連大部分的人類都很難分辨 37 個不同品種的貓狗,一般人也就只能辨認 10 來個吧?這種高相似度類別的分類,在學術中我們稱之為 fine grained classification。
接著來看看程式碼:
untar_data
path = untar_data(URLs,PETS); path
首先要下載和取出我們要的資料,我們使用一個叫 untar_data
的 function 來自動下載他並解壓縮。
help
在這裏我們一定會有一個疑問就是,我是會知道 untar_data
在幹嘛喔?我們可以使用 help
來知道他是從哪個 module 來的(不過我們用了 import * 所以無法),他如何運作和要傳入什麼參數,我們來看看 untar_data
要傳入哪些參數:
help(untar_data)
out:
Help on function untar_data in module fastai.datasets:untar_data(url:str, fname:Union[pathlib.Path, str]=None, dest:Union[pathlib.Path, str]=None)
Download `url` if doesn't exist to `fname` and un-tgz to folder `dest`
在 untar_data
中,url 是字串 (str),fname 可以是 path 或 str,default 則是 None (Union 就是 either 的意思),dest 則和 fname 一樣
path = untar_data(URLs,PETS); path
所以在這段程式碼中,untar_data 會自動判定只需要從連結中下載資料並解壓縮,最後回傳內容。
PosixPath('/data1/chenyu/git/course-v3/nbs/dl1/data/oxford-iiit-pet')
我們可以看看在 path object 中有什麼:
path.ls()
out:
['annotations', 'images']
包括註釋(他的品種)和圖片
path_anno = path/'annotations'
path_img = path/'images'
上面的語法來自 pathlib,我們透過「/」加我們要的存取的內容,有點像是增加一個子路徑,path_img 就是圖像的路徑。
get_image_files
get_image_files 會抓取圖片
fnames = get_image_files(path_img)
fnames[:5]
out:
[PosixPath('/data1/chenyu/git/course-v3/nbs/dl1/data/oxford-iiit-pet/images/american_bulldog_146.jpg'),
PosixPath('/data1/chenyu/git/course-v3/nbs/dl1/data/oxford-iiit-pet/images/german_shorthaired_137.jpg'),
PosixPath('/data1/chenyu/git/course-v3/nbs/dl1/data/oxford-iiit-pet/images/japanese_chin_139.jpg'),
PosixPath('/data1/chenyu/git/course-v3/nbs/dl1/data/oxford-iiit-pet/images/great_pyrenees_121.jpg'),
PosixPath('/data1/chenyu/git/course-v3/nbs/dl1/data/oxford-iiit-pet/images/Bombay_151.jpg')]
有了照片後我們需要每個照片所對應的 label,在 fastai 中有個叫 ImageDataBunch 的 object,一個 ImageDataBunch 囊括了所有我們訓練模型時的資料 — 測試集 (training set)、驗證集 (validation set) 還有他們的 label。在範例中,我們要從他們的名字中取出標籤,在這裡會使用 from_name_re,re 就是 regular expressions 的意思,以下是我們用來取出 label 的 regular expression:
np.random.seed(2)
pat = r'/([^/]+)_\d_.jpg$'data = ImageDataBunch.from_name_re(path_img, fnames, pat, ds_tfms=get_transforms(), size=224) data.normalize(imagenet_stats)
以上這些方法讓我們拿到以下資料:
- path_img:一個含有圖片的路徑
- fnames:一個含有檔案名的 list
- pat:用來從檔名中取出 label 的 regular expression
- ds_tfm:之後會講到 transform
- size:我們要用多大的照片來訓練模型
在目前訓練圖像辨識的模型中,我們需要將每個照片的大小設為一致的,在 part 1 的課程中會先用正方形的圖像,在 part 2 時則會學習如何用矩形的圖像來訓練模型。224 x 224 也是主流訓練方法中會用的圖像大小,所以我們把它設成 224,大部分都能夠有不錯的表現。
ImageDataBunch.from_name_re 會回傳一個 DataBunch object。在 fastai 中模型所需的資料都會以 DataBunch 的類型存放,其中通常會有 2 到 3 個資料集,training data, validation data, 還有 optionally test data,在每個資料集中,也都會含有各自的 label。
最後我們會將資料做 normalization,比較直接的理解方式就是這些圖像的來源、組成方式都不同,我們利用 normalization 來把這些資料變得很近似,以利於我們做模型的訓練。
data.show_batch
接下來可以印出照片來看一下
data.show_batch(rows=3, figsize=(7,6))
看資料很重要
做資料相關的工作很重要的一件事就是要能夠看我們手上的資料,除了上面的 show_batch 來看照片,我們還要看一下 labels,在這裏我們稱它為 classes
print(data.classes)
len(data.classes), data.c
out:
['american_bulldog', 'german_shorthaired', 'japanese_chin', 'great_pyrenees', 'Bombay', 'Bengal', 'keeshond', 'shiba_inu', 'Sphynx', 'boxer', 'english_cocker_spaniel', 'american_pit_bull_terrier', 'Birman', 'basset_hound', 'British_Shorthair', 'leonberger', 'Abyssinian', 'wheaten_terrier', 'scottish_terrier', 'Maine_Coon', 'saint_bernard', 'newfoundland', 'yorkshire_terrier', 'Persian', 'havanese', 'pug', 'miniature_pinscher', 'Russian_Blue', 'staffordshire_bull_terrier', 'beagle', 'Siamese', 'samoyed', 'chihuahua', 'Egyptian_Mau', 'Ragdoll', 'pomeranian', 'english_setter'](37, 37)
上面就是我們透過 regular expression 從檔名拿到的 label,可以看到我們一共有 37 個品種。DataBunch 會有一個屬性 c,之後會再來解釋,現在先理解為 class 的數量就好。
訓練 Training
接下來就要直接進入 training 的部分了,在 fastai 中用一個叫做 ‘learner’ 的東西來訓練模型
- DataBunch:fastai 中資料的概念,其中又有因應個別問題的子類別,這次是做圖像分類的 ImageDataBunch
- Learner:能夠讓資料學習去擬合 (fit) 模型的概念,同樣也有子類別,例如 convnet learner 就是指卷積神經網路 (Convolutional neural network, CNN)。
learn = create_cnn(data, model.resnet34, metrics=error_rate)
上面這段程式碼就會建立一個卷積神經網路,我們只要告訴他兩件事,資料 (data ) 和架構 (arch)。
現在我們只要先知道有一個叫做 ResNet 的特別模型,在大多數時候他的效能都很好,所以我們要做得選擇可能就是選擇 ResNet 的大小,有 ResNet34 和 ResNet 50 可以選擇,ResNet34 比較輕量,可以訓練得比較快,一開始我們可以先使用訓練快的來試試。
最後是 metrics,我們先把它當作一個衡量的指標,這次先使用錯誤率 error_rate。
Training resnet34
接著就是來訓練 ResNet34 了,現在我們先知道這是一個能夠輸入圖像並輸出預測機率的模型就好,我們會訓練他 5 個 epochs,即對全部的資料做五個循環。
在一個新的一次訓練時,它會先下載 ResNet34 的預訓練 (pre-trained) 權重 (weights),預訓練代表他已先被訓練過了,而這個訓練是透過 150 萬張圖片、幾千項類別訓練而成的,這個資料集叫做 ImageNet,所以我們不從一個什麼都不知道的模型開始,而是從一個訓練好的模型開始,雖然他未必做過 37 種貓狗品種的辨識,但他一定已經看過很多阿貓阿狗的照片,並得到一份權重,可以讓我們馬上來在新的模型訓練上使用。
Transfer learning
遷移學習 (Tranfer learning) 是這次課程的重點,上面的例子就叫做遷移學習,也就是運用一個已經對圖片有不錯的了解的模型來做我們想做的訓練,這樣我們可以省下非常多的訓練時間,而且效果還非常好!
Overfitting
在訓練模型時,可能因為過度學習,讓他很難去預測訓練資料以外的資料,變得像是一個專門為預測訓練資料所存在的模型,但是這樣的模型沒有辦法在實際的場合中做預測。像是在訓練辨認貓的模型中,我們給的資料若是清一色都是橘貓,這樣模型就會過度學習出一個「橘色是貓的一種特徵」的問題。
為了避免這個問題,於是有了 validation set,我們的模型在每一次的 epoch 訓練中,會對 validation set 做一次預測,透過 metrics 來給我們一個觀察指標,來看看我們的模型是不是 overfitting 了,fastai 的 data bunch object 也會自動幫我們分好 validation set,並在訓練過程中確定模型不會 overfit。
擬合模型 (Fitting model)
接下來就要 fit 我們的模型拉,簡單來說就是開始訓練,在這裡我們不用 fit function,而是用一個叫做 fit_one_cycle 的方法,這個方法來自於這篇論文,他比其他的訓練方式都還要更快更準確,一樣,先別管那麼多,只要記得要用這個 one cycle 的方法來 fit 就對了。
learn.fit_one_cycle(4)
上面的 4,代表我們走過幾次完整的資料來學習,每次的學習都能夠讓它更進步一些,但同時也意味著模型可能會 overfit,之後會再來學習如何微調這個數字,現在我們先用 4。
Total time: 01:46
epoch train_loss valid_loss error_rate
1 1.409939 0.357608 0.102165
2 0.539408 0.242496 0.073072
3 0.340212 0.221338 0.066306
4 0.261859 0.216619 0.071042
在上表中可以看出我們的模型在分類這 37 個貓狗品種時有 93 % 的準確率,為了感受一下,我們可以回頭看一下當初論文中的數據,在 2012 年時,牛津大學頂尖的學者做出來的數據是 59 %,用得可能還是一堆超複雜的程式碼,但是在現在我們只要簡單三行程式碼就能夠達到 93% 的準確率,由此可以感受深度學習的快速發展。
再回到講者一開始所說的,先暫時別對理論上的名詞鑽牛角尖,之後都會學到,於此更重要的是 run the code! 我們要知道輸入什麼東西,和輸出什麼,遇到不知道的函數就來查一下 fastai documentation。
接著作者分享了一些 fastai 現況的發展,和上完課程的人們如何利用 fastai 來做自己的案子或創業,在這邊就不一一贅述,有興趣的可以從這裡開始看。
結果
訓練模型其實就是讓模型學習出一組權重 (weights) 來辨認目標,如果知道線性回歸的話,這就是其中的係數,我們可以將訓練所得到係數和參數存起來,以便我們之後來取用:
learn.save('stage-1')
為了能看看從訓練中得到什麼,我們有個方法能夠解釋 (interpret) 這些類別,所以我們要傳入一個 learn object,learn object 需要兩個東西:
- 我們的資料
- 我們的模型,現在已經是個訓練好的模型
有了這兩者就可以來 interpret 這個模型了:
interp = ClassificationInterpretation.from_learner(learn)
有個相當有用的東西是 plot_top_losses,其中運用了一個叫做 loss function 的概念,用來判斷模型判斷的優劣,例如模型很有信心的判斷一個貓的品種,但事實上他是另一個,那麼這個例子的 loss 就會非常高,所以把 top_loss 的案例列出來,我們就可以看一下可能是出了哪些問題。
interp.plot_top_losses(9, figsize=(15,11))
從上圖可以看出他會顯示四個資訊,我們可以從 doc 裡面來看看,除了 help 外,我們可以用 doc 來看更完整的 function 內容。
doc(interp.plot_top_losses)
跳出下面的資訊視窗後,還可以點最底部的 show in docs,就會連結到完整的 documentation,在裡面我們可以找到這段話:
The title of each image shows: prediction, actual, loss, probability of actual class.
所以第一個是模型的預測,第二是實際的品種,三是 loss,四是預測實際品種的機率。
除了看 function 如何實際運用外,我們還可以看他的 source code,直接看他內部有哪些程式碼,在 doc 頁的右上角,有一個 source,點下去就可以查看 source code。
下篇我們會介紹如何解釋訓練模型後的結果還有如何讀取和製作標籤。