浙大人工智慧演算法與系統課程作業指南系列(一)口罩識別的資料處理部分

JacobDale發表於2020-12-14

浙大人工智慧演算法與系統課程作業指南系列(一)口罩識別的資料處理部分

寫在前面:

我原來本科的時候並不是計算機專業的,屬於跨考到計算機這個專業的。在本科期間也就接觸了點C的基礎,然後因為是傳統工科,所以Matlab和Fortran也寫過一些程式碼,趁著考完研的暑假繼續學習了一下C++,順便拿B站上能搜到的吳恩達的網課補了補機器學習的網課,然後拿C++簡單寫了個全連線網路。多多少少有一點基礎以後開學了,然後就攤上這一門尷尬的課······你敢信這門課讓你在兩個月左右的時間學Pytorch並且完成CV、NLP還有RL的全家桶作業麼······

我感覺我已經算是有一點點基礎了,但是做作業的時候還是感覺噁心的要死,那更別提其他根本不怎麼寫程式碼的專業了(今年這門課就好幾個專業一塊上,不管以後用不用AI都要做這個AI作業┓( ´∀` )┏),所以在這裡打算把做作業過程裡面的一些不清楚的東西記錄在這裡,以供廣大跨專業的浙大研一計算機 倒黴蛋 新生們參考。

這篇部落格主要是從程式碼功能層面上幫各位萌新們做個簡單指南,並不會(或者很少)涉及到原理上的內容(比如說我肯定不會分析Adam的優化演算法是咋回事),所以如果是想看原理上的內容,還是去其他大佬們的部落格裡轉轉吧······

由於我這也是第一次寫部落格,肯定會在格式還有內容分配上有很多的問題,還請多多擔待。如果裡面有什麼知識性的錯誤,還望評論區大佬們不吝賜教,我在看到以後會盡快修改。

啊,還有還有,這篇部落格如果有需要的話可以轉載,但是儘量別轉CSDN吧,好了就這些

  1. 前提要求:如果是純粹的小白,就是那種只會C,從沒用過python;或者是python會用但是機器學習或深度學習的基礎一點都沒有的,那麼看這個可能會有一點點費勁,所以在看這一篇部落格之前,最好先做到以下三點:

    • 要了解Python的基礎,高大上的操作不會,但是最起碼Python的一些基本的資料結構(list,dict,tuple)是要知道並且大致會用的,並且怎麼匯入一些模組、包還有庫什麼的也是要會的呢

    • 要有一些機器學習和神經網路的基礎,不要求會手推梯度下降啥的,至少對於一個CNN(卷積神經網路)的基本結構要有一定的瞭解

    • 該課程在網站平臺上提供的notebook檔案要從頭到尾看過,並且都實際執行過,至少要知道自己要實現什麼功能,還要知道自己哪一部分沒有看懂,然後好在這裡找找該咋辦

  2. 推薦材料:這門課的所有的程式碼推薦是使用Pytorch框架,當然了平臺也允許你使用其他的比如Keras和TensorFlow,還有一個華為的AI平臺。因為現在好多的新的論文裡面程式碼都用Pytorch寫嘛,所以我這裡的東西都是基於Pytorch寫的,如果是想用tf的,那對不起啦我幫不了忙呢。這裡推薦一些Pytorch入門的資料吧~

    • Pytorch入門的書籍:首推《Deep Learning with Pytorch》這本書,雖然是全英文的,但是很好讀,這本書會教你快速入門Pytorch,瞭解Pytorch裡面的一些基本語法以及資料結構,並且會教你做一些簡單的識別專案。如果只是想先簡單入下門的話,就讀前八章就好咯~(資源在知乎以及直接在瀏覽器上搜尋都是找得到的喲)

    • Pytorch的入門視訊:比較推薦Pytorch官方提供的60分鐘的Pytorch教程,總之先看一下具體要怎麼建立一個向量(Tensor)以及怎麼載入資料什麼的

    • Pytorch的官方文件:這個就去官網看就好了,在這篇裡面可能查文件還少一點,等到後面的那個NLP的作業,可能官方文件就要經常查了。啊說句題外話,雖然這個課的作業很坑,但是做完作業以後,別的不說,查文件的能力會提高不少23333

好了下面開始我們的正式介紹吧。啊還有還有,因為寫這個文章的時候,課程網站的作業介面已經進不去了,所以我只能憑著印象按順序介紹了,如果與實際的notebook上的順序不一樣的話,就麻煩大家慢慢找了www

  1. 資料處理部分

在資料處理部分,我們先拋去前面所有的和什麼圖片大小修改、圖片顯示、圖片翻轉等亂七八糟的東西不談,我們直接找到下面的這個函式:

def processing_data(data_path, height = 224, width = 224, batch_size = 32, test_split = 0.1):
 
     transforms = T.Compose([
         T.Resize((height, width)), #圖片尺寸的重整
         T.RandomHorizontalFlip(0.1),  # 進行隨機水平翻轉
         T.RandomVerticalFlip(0.1),  # 進行隨機豎直翻轉
         T.ToTensor(), #將圖片的資料轉換為一個tensor
         T.Normalize([0], [1]) #將圖片的數值進行標準化處理
     ])

     dataset = ImageFolder(data_path, transform = transforms)

     train_size = int((1-test_split) * len(dataset))
     test_size = len(dataset) - train_size

     train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])
     train_data_loader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)
     test_data_loader = DataLoader(test_dataset, batch_size = batch_size, shuffle = True)

     return train_data_loader, test_data_loader

為什麼要直接找這一段程式碼,主要是因為只有這一段程式碼有用,其他的圖片處理的那些程式碼都是讓你知道資料集裡面的資料大致上是長什麼樣子,對於後面你去做作業訓練模型是基本沒什麼用的。像我一樣的各位從沒接觸過Pytorch的小白們一看到這個肯定是一臉懵逼,天啊這裡面都是啥鬼玩意,別急別急,我們們一點點來說。

首先是這個函式的第一個部分:

 transforms = T.Compose([
 	T.Resize((height, width)),
 	T.RandomHorizontalFlip(0.1),
 	T.RandomVertivcalFlip(0.1),
 	T.ToTensor(),
 	T.Normalize([0], [1])
 ])

這段程式碼主要是定義了圖片處理的方式,就是說,如果你的程式讀入了一張圖片,那麼程式應該怎麼處理這張圖片呢?你可以設定重新設定大小(Resize)、進行圖片的隨機翻轉(Random巴拉巴拉)、圖片轉化為向量(ToTensor)還有圖片的標準化處理(Normalize)。

重新設定大小的函式其實很容易看懂啊,就是根據你輸入的那個想要轉化的圖片的尺寸,把圖片重新進行處理嘛,比如說你的模型訓練的時候,想要圖片的尺寸都是160x160,但是如果資料集的所有圖片都是224x224的,那你有兩種解決辦法:一個是你自己手動在畫圖裡面圖片一個一個轉化了(怕不是個憨憨),另一個就是直接調這個Resize方法。

然後是圖片的隨機翻轉,就是兩個Random巴拉巴拉的函式,這兩個函式······我覺得看函式名字就知道是要幹啥吧······功能就是函式名,然後後面的引數是概率,做隨機的翻轉主要是為了增強我們的資料,就是萬一我們的模型在實際使用的時候讀到一些讓人頭皮發麻的倒置資料的時候能夠識別出來(雖然我覺得沒啥必要,┓( ´∀` )┏)

總之這個塊塊裡面的前三個都不用太在意,但是接下來的兩個是重頭戲,千萬不能嫌麻煩不看了,因為這個對於之後自己獨立去做一些圖片識別的專案是特別重要的吖!

在介紹後面兩個函式之前,我們不可避免地要介紹一下,什麼樣子的資料才能被Pytorch裡面建立的神經網路模型正確地讀出來,並且在模型裡面正常地進行運算。

如果各位萌新同伴們接觸過opencv的話,那就應該知道我們在讀入一張圖片的時候,不考慮檔案的檔案頭等東西的話,我們得到的資料應該是矩陣,矩陣的每一個元素記錄了圖片在某個位置的畫素值。對於一個我們常見的.jpg格式的圖片,它是一個三通道的RGB圖片,也就是說我們用opencv讀出來的話每個圖片的矩陣維度資訊應該是這個樣子的:

(height, width, channels)

其中呢,height和width不用我多說吧,就是尺寸嘛,然後這個channels就是對應的R、G、B的顏色通道上的畫素值(雖然opencv實際上讀進來是BGR的鬼順序,┓( ´∀` )┏)

但是!!!Pytorch表示我不吃你這一套(好任性),首先你opencv還有PIL讀到的圖片都是numpy陣列,而Pytorch它只處理tensor;而且事實上為了能夠儘可能的加快矩陣運算的速度,Pytorch的模型在讀取圖片的時候,要求你讀的圖片都要是(channels, height, width)的尺寸,你要是對圖片不處理就直接放到模型裡面,雖然倒是能跑,但是那個結果emmmmm,算了算了。除此之外,神經網路在進行訓練的時候,受到啟用函式等一系列的限制,如果能把輸入資料的範圍調整在[-1, 1]或者[0, 1]之間那是墜吼的,然而,圖片裡面的畫素值的範圍都是[0, 255]······為了能夠讓你的模型能夠正常訓練然後跑個還算可以的結果,你必須要把圖片的畫素值都除以255,強制轉換為[0, 1]之間。

萌新們看到這裡可能會覺得,哇我才剛入門,咋寫這一大堆的處理函式啊Or2(眾所周知,Or2比Orz的屁股更翹www),別急別急,這個函式ToTensor把上面的要做的幾項處理全都給你搞定了喲!所以你可以認為,當經過ToTensor()這個函式之後,圖片就已經變成(channels, height, width)的數值在[0, 1]之間的tensor了,是不是很神奇?當然了,具體的原理還是建議大家看一下原始碼,篇幅有限我就不貼了。

接下來我們看一下這一塊還剩下來的最後一個函式,Normalize([0], [1])。如果你看過吳恩達的那個在Coursera上的那個很簡單的機器學習的課程的話,你應該會了解,當我們的輸入資料的尺度差的很大的時候(比如一個在1000量級,一個才0.1量級),模型的訓練會很不穩定,因此我們需要儘可能把資料的範圍縮放到同一個尺度上。萌新可能就會問啦,這個圖片不是已經縮放到[0, 1]之間了嘛,那隻能說你太天真,萬一除以255之前,資料畫素值都是0和1呢?縮放後基本都是0,然後來了一張全都255的圖,縮放完全是1,看你咋搞,┓( ´∀` )┏。所以我們需要使用資料的均值和方差來進行縮放。實際上Normalize這個函式的引數寫全了應該是這樣(只考慮我們要用的部分哈):

Normalize(
	mean = [xxx, yyy, zzz],
	std = [aaa, bbb, ccc],
)

mean就是均值,std就是標準差,在呼叫這個函式的時候,實際上做了一個x = (x - mean) / std的處理,就把圖片的畫素值的尺度搞到一個差不多的範圍內了。

然後呢我們接著看processing_data函式的剩下的部分(寫了這麼久才剛到這裡,我都快要哭了)。接下來的一行程式碼裡面一個很關鍵的函式是ImageFolder(data_path, transform = transforms)。第二個引數就是我們剛剛定義的那個資料轉換方式啦,但是前面這個引數還有這個函式的整體功能是啥吖?好吧這又是一個大坑。我估計這篇文章要寫完可能要將近5000字,┓( ´∀` )┏

先看一下這個函式的功能吧。在看完課程提供的notebook的程式碼之後(所以還沒看完課程提供的notebook的趕緊去看,哼╭(╯^╰)╮),可能各位萌新小夥伴們都會感到疑惑:欸?資料在訓練時候用的標籤都是什麼時候加進去的呀?我咋沒看著呀?emmmmm,實際上標籤就是在這個函式裡面加進去的。這個函式的功能很別緻,他會假設你已經把資料進行了分類,並且分別放在了不同的資料夾裡面,這樣這個函式就會根據資料圖片所在的圖片的資料夾位置,自動為每一個圖片新增標籤。可能這裡光說還是不太清楚,那我就給各位萌新夥伴們舉個例子吧~

我們就按照notebook提供的資料集為例吧,在我們的notebook裡的datasets的資料夾裡面一直往裡找,我們會找到image這個資料夾,開啟以後裡面有mask和nomask兩個資料夾,分別存著有口罩和沒口罩的圖片,然後呢,這個ImageFolder函式,就會按照image這個資料夾裡面的資料夾的順序,為資料夾裡的檔案設定好標籤。也就是說,ImageFolder函式會將所有mask資料夾裡面的圖片標籤設定為0,所有nomask資料夾裡的圖片標籤設定為1。驗證的話在notebook下面是有示例的,雖然當時看的時候沒看明白。這也解釋了為啥notebook的說明上面羅裡吧嗦地說了一大堆,它就是想讓你在傳入data_path的時候,就寫到image這一層就行了,別再往下寫了。(所以,data_path在notebook裡面有過多次改動,唯一要記住的是那個末尾是/image的那個,別問我為啥不直接粘到這裡,因為資料集裡有坑,之後再說啦)

接下來我們來看一下這個程式碼段:

train_size = int((1-test_split) * len(dataset))
test_size = len(dataset) - train_size

train_dataset, test_dataset = torch.utils.data.random_split(dataset, [train_size, test_size])

這個很簡單,就是把之前我們得到的那個加好標籤了的整個資料集,按照我們給出的各個集合中的元素個數,隨機分配成訓練集和驗證集。

然後又是一個勸退萌新的大坑,┓( ´∀` )┏。就是最後的一段程式碼:

train_data_loader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)

test_data_loader = DataLoader(test_dataset, batch_size = batch_size, shuffle = True)

在介紹這段程式碼之前,我們還是要回到之前介紹Pytorch的輸入模型的資料的結構上面>_<

我們之前說過,在Pytorch中的圖片要調整成(channels, height, width)的尺寸,可能你覺得這就完事了,但是事實上並沒有。Pytorch為了進一步提高你電腦硬體的使用效率,它從不滿足於一次讀入一個圖片,它會要求你傳入的資料要能夠一次傳入多個圖片。萌新夥伴們可能就又要懵逼了,這咋傳呀(╬◣д◢)。其實很簡單,想一想我們的矩陣,如果一個二維矩陣是一個圖片,那麼我好幾個圖片疊在一起,不就是好幾個圖了嘛,好幾張圖疊在一起就看起來就像一個立方體,也就是說你要在矩陣裡再多加一維嘛。同理,對於一個三通道的tensor來說,想要好幾張圖放一起,在最開頭那裡加一維就好啦。

在深度學習裡面,這一坨的圖片的專門術語叫批(batch)。所以實際上,讀入到一個模型中的圖片尺寸維度應該是這樣的:(batch_size, channels, height, width)
batch_size是你一批裡面有幾張圖片(可以是1喲,傳一張其實也是沒事情的,但是這一維必須要有)
剩下的引數就還和之前介紹的每一張圖片的維度是一樣的,就不多BB了。

然後呢,DataLoader這個函式就是替你完成 “將好幾張圖放在一起組成一個batch” 這件事的,說清楚了圖片怎麼組,那這個函式的引數就不用我再多說了吧,已經很清楚了吖~

好了好了,圖片處理部分終於結束了,我的手都要廢了┓( ´∀` )┏。別看這個函式程式碼很少,但是對於之前根本沒有接觸過Pytorch的萌新小夥伴們(比如我),裡面坑太多了,真的查資料就查的要吐血了。接下來的一部分,看情況為大家介紹一下神經網路模型和訓練模型部分的一些坑,然後如果內容太多,可能還要再多分一節出來,和大家聊聊這個作業裡面關於資料集還有程式碼裡面的一些坑,那這篇就到這裡,我們下一篇再見咯~

參考內容:

  1. Pytorch基礎:《Deep Learning with Pytorch》
  2. ToTensor相關:https://www.cnblogs.com/ocean1100/p/9494640.html
  3. ImageFolder相關:https://www.cnblogs.com/wanghui-garcia/p/10649364.html

相關文章