fastai lesson2 – Data cleaning and production; SGD from scratch part1

課程的一開始,教授介紹了非常多大家從lesson1成功套用在自己專業上的一些案例,這部分有興趣的在麻煩大家自己去看,這些片段基本上在整支影片的前12:50都可以看到。
fastai課程:https://course.fast.ai/videos/?lesson=2
在正式進入到這次的主題之前,教授用了這張簡報再重覆為大家說明本次課程的教學宗旨,這邊簡單跟大家介紹一下,這邊引用哈佛研究者 – Perkins所使用的一段故事:如果你要教一個小朋友踢球,你第一件教他的事情是什麼?計算球與地面的摩擦力嗎?還是如何算出球在空中運動時的拋物線函數?還是就是請他踢就對了?相信大部分的人應該都會選擇先讓小朋友踢就對了吧,先能夠踢球之後,再不斷不斷地去學習,怎樣能夠把球踢到我想踢到的方,怎麼樣能夠把球踢到我想要的高度等等;這整件事情套用在學習Deep Learning也是一樣,我們需要先學一大堆理論,之後再開始真正建造自己的model嗎?不,我們要直接開始寫code,不懂也沒關係,我們再一步一步把我們不熟或是不了解的地方慢慢地去填補起來,如果你無法完整了解第一堂課所教的內容,也沒關係,先接下去繼續做,等你做到一定的地方時,你可以回過頭再去看看lesson1,你會發現第二次看比第一次看又多懂了不少東西,這也是為什麼教授說這堂課是設計給”practitioner”的課程,從做中學,才是這堂課與其他學習Deep Learning不一樣的地方。
 
回到正題,在這堂課程,我們要學習的是如何建立我們自己的 image classifier,這跟我們上週所建立的pet detector很類似,但這次的image classifier可以偵測到所有你想偵測的東西,不只是上週提到的寵物。那這此課程中所會使用到以及示範的主題是,我們希望可以建立一個teddy bears的dectector,並且能夠區別出黑色以及灰色的熊。
 

第一步-蒐集每種不同圖片的URLs

這些圖片哪裡來呢?我們這邊要透過Google Image去抓取teddy bears的圖片,搜尋得到結果後,往下滑,滑到你覺得圖片數量夠了的地方停下來,如果你是Windows / Linux使用者,可以透過Ctrl + Shift + J叫出視窗;如果你是Mac使用者,可以透過Cmd + Opt + J叫出視窗,會是下面這個樣子

接下來,在下面視窗的地方貼上以下這段code
urls = Array.from(document.querySelectorAll(‘.rg_di .rg_meta’)).map(el=>JSON.parse(el.textContent).ou);
window.open(‘data:text/csv;charset=utf-8,’ + escape(urls.join(‘\n’)));
這個視窗其是一個Javascript console;再來只要按下Enter就可以下載了,我們這邊會把他存取成”teddies.txt”,這個檔案就會是這些teddy bears的URLs了,接下來我們就可以對黑熊及灰熊重覆執行上面的動作。
 

第二步-下載圖片

這一步我們要把剛剛這些得到的檔案下載到我們的伺服器,說到這裡,我們可以再複習一下lesson1的內容,因為我們使用的這個Jupyter Notebook並不是用我們自己電腦開啟的,而是透過第三方的伺服器來做開啟(不然一般人的電腦應該是沒辦法用來train這些model),像我個人是使用Salamander的伺服器,那我這邊就必須把剛剛的檔案下載到我使用的伺服器上面

開始進入到我們的Jupyter Notebook
首先,先import待會會用到的幾個套件

from fastai import *
from fastai.vision import *
接下來,我們要將圖片的URLs下載到伺服器上
folder = ‘black’
file = ‘urls_black.txt’
folder = ‘teddys’
file = ‘urls_teddys.txt’
folder = ‘grizzly’
file = ‘urls_grizzly.txt’
再來,我們要執行下面的code來建立資料夾
path = Path(‘data/bears’)
dest = path/folder
dest.mkdir(parents=True, exist_ok=True)
如果你有用過command line的話,對這邊的mkdir應該不陌生,如果你沒用過的話,mkdir就是”make directory”的縮寫,在電腦裡面就是創建資料夾的意思。

接下來我們要做的是,根據上面不同的URL去下載各自類別的圖片,並將檔案下載至我們上面所指定的路徑
 
classes = [“teddys”, “grizzly”, “black”]
download_images(path/file, dest, max_pics = 200)
看到這邊你應該可以發現,咦?不太對的感覺,前面我們的folder以及file的變數如果都同時執行的話,那最後存取的結果會是”grizzly”這個folder以及他的file,這樣我們在download_image()這邊,不就只會下載到灰熊的圖片嗎?沒錯,所以這邊在執行前面的code的時候,我們必須按照順序執行,首先我們會先執行folder = “black”這個cell,執行完後我們再跳來 path = Path(“data/bears”)這個cell來做執行,最後我們再回到download_images()去執行,執行完畢之後我們再跳回folder = “teddys”這個cell去執行,重覆這樣的動作;當然這些重覆的動作我們也可以寫一個loop去執行他,不過由於這邊只有三組資料的關係,再加上我們是practitioner,只要先能夠執行就好,所以我們這邊就先不拘泥那麼多細節。
 
老師在影片的這個部分有講解一下,他的Jupyter Notebook為什麼長那個樣子,這邊簡單跟大家說明一下,他的Juputer Notebook之所以不是所有東西都是從上到下有順序地執行,是因為他覺得他有時候需要回去再測試一些東西能不能work,他的核心概念是,只要東西能work,就沒有必要去管這些其他東西。
(在運行上面這段code的時候,有的人可能會發生錯誤的情況,如果有的話請改用下面這段code)
download_image(path_file, dest, mac_pics = 20, max_workers = 0)
 

第三步-創建ImageDataBunch

接下來我們要做的事情,是去移除那些其實不是image的檔案,總會有一些檔案在batch當中損壞,譬如有些在Google image上面他是有URL的,也看得到預覽圖片,但其實在他們的網站,他們已經把圖片撤下來了,所以圖片其實不存在。所以我們這邊會使用到verify_images(),這個方法能夠幫我們確認所有我們給他路徑的圖片檔是不是有問題,如果我們在參數設置delete =True,他就會在驗證過程中直接幫我們刪除掉不是圖片檔的檔案

for c in classes:
    print(c)
    verify_images(path/c, delete = True, max_workers = 8)
接著,在Jupyter Notebook的視窗的output欄位就會出現如下圖的進度條,這也是Jupyter Notebook最大的特色之一,就是在你code執行之後,他能夠根據code的要求去給你適應的interactive interface
這個動作執行完畢之後,可以發現我們有一個叫做bears的資料夾,這個bears的資料夾裡面有其他三個資料夾,叫做teddys, grizzly, black,也就是說我們已經有創建ImageDataBunch的基本架構了。

如果你是從Kaggle上面下載資料下來的話,你會發現他有資料夾會叫做train, valid, test,這幾種資料夾,不過因為我們的資料是從Google Image上面下載下來的,所以我們並沒有獨立出來的validation set(試驗集),但是我們仍然需要一個validation set,否則我們就沒辦法知道我們的model是否能夠運作以及正確。

當我們建立好我們的data bunch時,如果我們沒有獨立的validation set,我們可以直接告訴function training set已經在當前的資料夾裡面了,但我們額外還需要做一件事情,就是把現今資料當中的20%給分離出來,作為這裡的validation set,我們只要在function當中加入一個參數 – valid_pct,並且將其設置為20%,他就會自動隨機幫我們抽取資料來當作validation set。從下面的code可以發現,教授每次在隨機創建validation set的之前,都會設置他的random seed為一個固定的數字,也就是說每次只要執行這段code,我們都可以得到同樣的validation set,當然對有些人來說可能會喜歡讓他的machine learning實驗是可以複製的,好確保每次得到的結果都是一樣的,不過對教授而言,隨機性對於尋找一個穩定的solution以及確保這個model每次都能夠work是非常重要的,但最重要的是還是要保持有相同的validation set,因為當你得到結果時,你可能會想要找其他辦法去優化他,如果你讓你的validation set變得不一樣時,你在這組實驗就多了一個變因,你將無法知道這個model準確度的提升是因為你的演算法的改進,還是因為你的validation set變簡單了,這也是為什麼我們要讓我們的validation set保持一致。
 
np.random.seed(42)
data = ImageDataBunch.from_folder(path, train = “.”, valid_pct = 0.2, ds_tfms = get_transforms(), size = 224, num_workers = 4).normalize(imagenet_stats)
 
在執行上面這段code之後,我們得到了一個data bunch,接下來我們可以輸入下面這段code去看我們的data bunch裡面有那些class,也就是我們這邊標記這些data的label
 
data.classes
 
>>>
[“black”, “grizzly”, “teddys”]
接下來,我們可以使用show_batch這個function去看一下我們每個data被label的狀況,仔細看可以發現有些圖片有點奇怪,譬如teddy旁邊有一個人,或是一隻灰熊站在黑熊上面等等
data.show_batch(row = 3, figsize = (7,8))

再來,我們可以透過下面這段code去檢查我們的label, labe的數量, train set以及validation set各自的資料數量
data.classes, data.c, len(data.train_ds), len(data.valid_ds)
 
>>>
([“black”, “grizzly”, “teddys”], 3, 473, 140)

 

第四步-訓練model

接下來,我們要藉由剛剛得到的資料來建立我們的convolutional neural network(卷積神經網路),那我們這邊一樣使用前面提到的resnet34這個model
learn = creat_cnn(data, models.resnet34, metrics = error_rate)
再來,我們要執行fit_one_cycle()這個function 4次,來看一下這個model的表現。
learn.fit_one_cycle(4)
 
>>>
Total time: 00:54
epoch  train_loss  valid_loss  error_rate
1      0.710584    0.087024    0.021277    (00:14)
2      0.414239    0.045413    0.014184    (00:13)
3      0.306174    0.035602    0.014184    (00:13)
4      0.239355    0.035230    0.021277    (00:13)
從結果來看,我們得到了2%的錯誤率,也就是說有將近98%的正確率,已經算是非常不錯的表現。
 
在取得不錯的結果之後,教授通常會把這個結果存下來,省得我們又要再花54秒來得到這個結果
learn.save(“stage-1”)
按照往例,我們會unfreeze剩下的model,至於這是幹嘛用的,教授在之後的課程會再多加解釋。
learn.unfreeze()
再來,我們要執行learning rate finder,並且將他以圖表展示出來
learn.lr_find()
 
>>>
LR Finder complete, type {learner_name}.recorder.plot() to see the graph.

接下來我們會了解更多有關learning rate的資訊,但目前你知道這些就夠了,在learning rate finder中,你要找的是斜率向下且最斜的的區間,不過這邊可能會需要經驗以及有點主觀的判斷,這個部分在請大家自己去慢慢感受,當然我們也可以多試幾個learning rate,看哪個表現比較好來得知(根據教授過去的經驗,通常好的learning rate會落在 10^-5及10^-3之間)

從上圖的圈圈可以發現,我們這邊選擇3e-5來當作我們最低的learning rate,至於最高的learning rate,教授通常會選擇1e-4或3e-4,這邊你需要知道的一件事情是,這些參數其實並沒有影響那麼大,所以只要你不要選得太偏離,基本上都可以運作得不錯。

learn.fit_one_cycle(2, max_lrs = slice(3e-5, 3e-4))
 
>>>
Total time: 00:28
epoch  train_loss  valid_loss  error_rate
1      0.107059    0.056375    0.028369    (00:14)
2      0.070725    0.041957    0.014184    (00:13)
從上面結果可以看到,在經過learning rate的調整過後,我們獲得了1.4%的錯誤率,比前面原本的model表現得還好,接下來我們在把這次的結果存起來
learn.save(“stage-2”)

 

Interpretation

learn.load(“stage-2”)
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()

從上圖可以發現,這個model只有發生1次錯誤,也就是他把黑熊誤認為灰熊,雖然這樣看下來,這個model已經做得非常好,但我們還可以做得更好,也許有可能是因為Google Image沒有辦法每次都給我們正確的圖片,那我們該怎麼修正這個問題呢?我們必須要去清理這些錯誤的資料,我們不必一定要拘泥在透過電腦才能解決,有時候人眼或是人的常識對錯誤資料的清理也有很大的幫助,但非常少人在做這件事情。我們接下來要用的是top losses,上次我們有把top losses用圖表展現出來,接下來我們要利用這項工具來去找出那些錯誤的資料,仔細想想,如果有被標錯label的資料,那根本不太可能以非常高的confidence去準確預測這個data,所以我們需要把注意力放在那些model告訴我們it’s not confident或是it was confident and it was wrong,這些資料比較有可能被mislabel

感謝San Francisco fastai study group,他們建立了一個新的widget叫做FileDeleter,好讓我們能從top losses裡面去取得我們想取得的data,接下來這個widget的top_losses會return兩個東西,第一個是the losses of the things that were the worst;第二個是the indexes into the dataset of the things that were the worst,如果你不傳任何變數去接住這些return,他會直接把整個dataset return給你。每個在fastai的dataset都有x以及y,x包含了那些被使用的東西,就這個案例來說,就是圖片,也就是圖片的檔名;y則是labels,所以如果我們把indexes取出並且傳入dataset當中的x,他會回傳給我們依照highest loss依序排下來的dataset的檔名(這邊的highest loss指得就是我們前面提到it’s not confident or it was confident and it was wrong)。

from fastai.widgets import *
 
losses, idxs = interp.top_losses()
top_loss_paths = data.valid_ds.x[idxs]
fd = FileDeleter(file_paths = top_loss_paths)

這邊要澄清一下,top_loss_paths包含了所有在dataset的檔案名稱,當教授說”out dataset”時,特別指的是validation set。所以我們上面做的事情就是把validation set當中被mislabel的資料給移除掉,當然,其他set也需要做清理,所我們會需要再回去把上面的”valid_ds”替換成”train_ds”,來去清理我們的training set,我們後面會提到更多有關test sets的事情,當然如果你這邊已經有test set的話,也必須要重覆以上的動作,才能把資料一併清理乾淨

在我們執行FileDeleter,並且傳入整理好的lists of path,就會出現如上圖這樣的互動式介面,這些圖片都是我們前面提到wrong or least confident about的資料,所以我們可以看到上面從左邊數過來第二張圖,根本沒有熊,只有一個人拿著吉他,很明顯是錯誤的資料,接下來我們只要按下上方出現的Delete按鈕,接下來按下Confirm他就會幫我們執行資料刪除。

教授通常在這時候會再繼續去做資料上的確認,直到他確認整個螢幕出現的資料都看起來非常okay,執行完之後,我們就可以再回到我們的training set去重新訓練我們的model,因為我們已經把不乾淨的資料給清除了。教授在這個部分有提及有關 GUI的內容,因為這跟主題並沒有直接關係,但對於你的programming技能或是之後的實際應用上會有幫助,有興趣的可以自己搜尋”ipywidgets”

Putting our model in production

 
大部分的時候,這些models非常善於處理錯誤資料數量適中的data,通常問題主要會發生在當你的錯誤資料並不是隨機地散布在你的dataset當中而是整組偏掉(biased),也就是說,如果當你清理資料過後再重新跑一次你的model,然後你只得到譬如0.001%的改善,這很正常沒關係,但你最好還是可以再回頭去檢查dataset當中是不是還有很多錯誤的資料去避免這整個dataset是biased的。
 
好的,讓我們回到這個題目,對於大多數我們目前正在做的事情,我們會希望這些東西透過CPU執行而不是GPU,為什麼?因為GPU擅長的是在同一時間處理非常多不同的事情,除非你有一個流量很大的網站,否則你不太可能有這種情形:同時有64張圖片要去分類,然後你必須要把他合成一個batch丟進GPU處理。如果我們確實用GPU來處理的話,我們就必須要將所有user丟進來的東西包起來一起執行,而你所有的user就必須要等這個batch打包完成並且執行,這可能會引起不少的爭論及麻煩,譬如你是第一個將東西丟進來的使用者,你卻因為這個網站使用GPU,就要等其他使用者把東西丟進來,包在一起,然後再一起執行,然後在同時得到結果,這會莫名其妙浪費掉你非常多時間;如果你使用CPU的話,整件事情就會變得非常簡單,一次處理一個東西,處理完就丟回來,雖然這會花上大概10~20倍長的時間,這樣看可能很嚇人,但通常處理時間大概要花0.01秒,你用CPU大概要花0.2秒,所以慢這10~20倍的時間其實根本沒關係。也就是說,當我們得到我們訓練好的model的時候,我們就未必要再使用GPU去執行他,意思就是我們不用為了執行這個model而花大錢去買這些virtual GPU了!在這邊我們又熟悉另一個名詞叫做”inference”,當我們不是在訓練model,而是我們已經有一個訓練好的model且我們要拿他來預測事情時,我們叫他”inference”
這句是他們常說的話

“You probably want to use CPU for inference”

 
在inference時,我們已經有預先處理好的model,我們儲存了裡面的所有權重等等重要參數,那我們該怎麼真正地把它拿來做使用呢?
第一件我們需要知道的事情就是,我們到底訓練出哪些classes?我們不只是知道這些classes分別叫做什麼名字,還要知道他們的順序。所以接下來我們必須要將他們排列好,並且確認這些classes跟我們前面model訓練出來的一模一樣。
data.classes
 
>>>
[“black”, “grizzly”, “teddys”]
如果你的伺服器上沒有GPU,那他就會自動使用CPU來執行,但如果你有GPU但你想要用CPU來執行的話,你可以透過下面這段code去告訴fastai,並透過fastai回傳給PyTorch說你要使用CPU
fastai.defaults.device = torch.device(‘cpu’)
這邊舉個例子,我們有一個”teddy bear dector”,而教授的小孩正在決定是否要去抱這個長得像他手中圖片裡呈現的熊,於是他把手中這張圖片放進我們的model
我們這邊會設定一個變數叫做img,用來儲存圖片,而open_image則是我們用來在fastai裡面來打開這張圖片的function
img = open_image(path/”black”/”00000021.jpg”)
img

classes = [“black”, “grizzly”, “teddys”]
data2 = ImageDataBunch.single_from_classes(path, classes, tfms = get_transforms(), size = 224).normalize(imagenet_stats)
learn = creat_cnn(data2, models.resnet34)
learn.load(“stage-2”)
從上面可以看到,我們有一個之前原先儲存好的class的清單,跟往常一樣,我們創建了一個databunch,但這次,我們不打算從原本那個都是圖片的資料夾來建立,我們這邊建立一個可以一次只抓取一張圖片的特殊的databunch。所以我們先不傳入任何資料進去,可以從上面的code看到我們傳入的是path,為什麼我們要傳入path呢?是為了讓他知道哪裡能夠讀取我們的model,這個path就是我們model所在的位置。
 
但接下來我們要做的呢是我們需要傳入與我們原先訓練過的一樣的資料,一樣的transforms,一樣的size,一樣的normalization,為什麼呢?我們後面會再討論到,目前只要確保這些東西跟我們過去訓練model時一模一樣就好。
 
現在我們已經得到一個裡面什麼資料都沒有的data bunch,這是一個知道如何以原來我們訓練的方式transform一張新的圖片的東西,現在我們可以開始inference了
 
接下來,我們可以以前面的fake data bunch去creat_cnn,在這邊我們會使用原本訓練的model,並且讀取我們原本訓練得出的權重參數結果。也就是說,上面這串code是我們只會run一次的東西,當你的web app啟動時,他會花大概0.1秒的時間去run這段code,然後結束。
 
然後,我們就可以使用learn.predict(img)去預測我們傳進來的圖片
pred_class, pred_idx, outputs = learn.predict(img)
pred_class
 
>>>
“black”
預測結果為”black”這個class,預測正確!
 
所以,如果要把我們的code放到app或是web上運作,會長什麼樣?由於這邊比較複雜,也並不是這堂課的主題,我下面提供幾個關鍵字讓大家去自行搜尋!網路上都有許多免費的資源可以利用!
  • Starlette
  • Flask
  • PythonAnywhere
  • Zeit Now
以上都是一些可以非常快速地讓你將你的python package上傳,變成一個web app的工具,有興趣的大家可以自行去搜尋!
 
下一篇,會提到有關fastai課程當中約莫從46分開始,一直到90分鐘的部分,會提及一些可能發生的錯誤狀況,如何修正,以及進到更多數學的環節!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *