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

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

寫在前面:

我原來本科的時候並不是電腦專業的,屬於跨考到電腦這個專業的。在本科期間也就接觸了點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)。

重新設置大小的函數其實很容易看懂啊,就是根據你輸入的那個想要轉化的圖片的尺寸,把圖片重新進行處理嘛,比如說你的模型訓練的時候,想要圖片的尺寸都是160×160,但是如果數據集的所有圖片都是224×224的,那你有兩種解決辦法:一個是你自己手動在畫圖裡面圖片一個一個轉化了(怕不是個憨憨),另一個就是直接調這個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相關://www.cnblogs.com/ocean1100/p/9494640.html
  3. ImageFolder相關://www.cnblogs.com/wanghui-garcia/p/10649364.html