課程名稱 | 零基礎入門深度學習授課講師 | 孫高峰 百度深度學習技術平臺部資深研發工程師授課時間 | 每周二、周四晚20:00-21:00
01 導讀
本課程是百度官方開設的零基礎入門深度學習課程,主要面向沒有深度學習技術基礎或者基礎薄弱的同學,幫助大家在深度學習領域實現從0到1+的跨越。從本課程中,你將學習到:
深度學習基礎知識numpy實現神經網絡構建和梯度下降算法計算機視覺領域主要方向的原理、實踐自然語言處理領域主要方向的原理、實踐個性化推薦算法的原理、實踐本周為開講第四周,百度深度學習技術平臺部資深研發工程師孫高峰,開始講解計算機視覺中圖像分類任務。
02 圖像分類概述
圖像分類是根據圖像的語義信息對不同類別圖像進行區分,是計算機視覺中重要的基礎問題,是物體檢測、圖像分割、物體跟蹤、行為分析、人臉識別等其他高層次視覺任務的基礎。圖像分類在許多領域都有著廣泛的應用,如:安防領域的人臉識別和智能視頻分析等,交通領域的交通場景識別,網際網路領域基於內容的圖像檢索和相冊自動歸類,醫學領域的圖像識別等。
上一節主要介紹了卷積神經網絡常用的一些基本模塊,本節將基於眼疾分類數據集iChallenge-PM,對圖像分類領域的經典卷積神經網絡進行剖析,介紹如何應用這些基礎模塊構建卷積神經網絡,解決圖像分類問題。涵蓋如下卷積神經網絡:
LeNet:Yan LeCun等人於1998年第一次將卷積神經網絡應用到圖像分類任務上[1],在手寫數字識別任務上取得了巨大成功。AlexNet:Alex Krizhevsky等人在2012年提出了AlexNet[2], 並應用在大尺寸圖片數據集ImageNet上,獲得了2012年ImageNet比賽冠軍(ImageNet Large Scale Visual Recognition Challenge,ILSVRC)。VGG:Simonyan和Zisserman於2014年提出了VGG網絡結構[3],是當前最流行的卷積神經網絡之一,由於其結構簡單、應用性極強而深受廣受研究者歡迎。GoogLeNet:Christian Szegedy等人在2014提出了GoogLeNet[4],並取得了2014年ImageNet比賽冠軍。ResNet:Kaiming He等人在2015年提出了ResNet[5],通過引入殘差模塊加深網絡層數,在ImagNet數據集上的識別錯誤率降低到3.6%,超越了人眼識別水平。ResNet的設計思想深刻的影響了後來的深度神經網絡的設計。03 LeNet
LeNet是最早的卷積神經網絡之一[1]。1998年,Yan LeCun第一次將LeNet卷積神經網絡應用到圖像分類上,在手寫數字識別任務中取得了巨大成功。LeNet通過連續使用卷積和池化層的組合提取圖像特徵,其架構如 圖1所示,這裡展示的是作者論文中的LeNet-5模型:
圖1:LeNet模型網絡結構示意圖
第一輪卷積和池化:卷積提取圖像中包含的特徵模式(激活函數使用sigmoid),圖像尺寸從32減小到28。經過池化層可以降低輸出特徵圖對空間位置的敏感性,圖像尺寸減到14。第二輪卷積和池化:卷積操作使圖像尺寸減小到10,經過池化後變成5。第三輪卷積:將經過第3次卷積提取到的特徵圖輸入到全連接層。第一個全連接層的輸出神經元的個數是64,第二個全連接層的輸出神經元個數是分類標籤的類別數,對於手寫數字識別其大小是10。然後使用Softmax激活函數即可計算出每個類別的預測概率。【提示】:
卷積層的輸出特徵圖如何當作全連接層的輸入使用呢?
卷積層的輸出數據格式是,在輸入全連接層的時候,會自動將數據拉平,
也就是對每個樣本,自動將其轉化為長度為的向量,
其中,一個mini-batch的數據維度變成了的二維向量。
03 LeNet在手寫數字識別上的應用
LeNet網絡的實現代碼如下:
# 導入需要的包
import paddle
import paddle.fluid as fluid
import numpy as np
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, FC
# 定義 LeNet 網絡結構
class LeNet(fluid.dygraph.Layer):
def __init__(self, name_scope, num_classes=1):
super(LeNet, self).__init__(name_scope)
name_scope = self.full_name()
# 創建卷積和池化層塊,每個卷積層使用Sigmoid激活函數,後面跟著一個2x2的池化
self.conv1 = Conv2D(name_scope, num_filters=6, filter_size=5, act='sigmoid')
self.pool1 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
self.conv2 = Conv2D(name_scope, num_filters=16, filter_size=5, act='sigmoid')
self.pool2 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
# 創建第3個卷積層
self.conv3 = Conv2D(name_scope, num_filters=120, filter_size=4, act='sigmoid')
# 創建全連接層,第一個全連接層的輸出神經元個數為64, 第二個全連接層輸出神經元個數為分裂標籤的類別數
self.fc1 = FC(name_scope, size=64, act='sigmoid')
self.fc2 = FC(name_scope, size=num_classes)
# 網絡的前向計算過程
def forward(self, x):
x = self.conv1(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.fc1(x)
x = self.fc2(x)
return x
程序使用隨機數作為輸入,查看經過LeNet-5的每一層作用之後,輸出數據的形狀
04 LeNet在眼疾識別數據集iChallenge-PM上的應用
iChallenge-PM是百度大腦和中山大學中山眼科中心聯合舉辦的iChallenge比賽中,提供的關於病理性近視(Pathologic Myopia,PM)的醫療類數據集,包含1200個受試者的眼底視網膜圖片,訓練、驗證和測試數據集各400張。下面我們詳細介紹LeNet在iChallenge-PM上的訓練過程。
說明:
如今近視已經成為困擾人們健康的一項全球性負擔,在近視人群中,有超過35%的人患有重度近視。近視將會導致眼睛的光軸被拉長,有可能引起視網膜或者絡網膜的病變。隨著近視度數的不斷加深,高度近視有可能引發病理性病變,這將會導致以下幾種症狀:視網膜或者絡網膜發生退化、視盤區域萎縮、漆裂樣紋損害、Fuchs斑等。因此,及早發現近視患者眼睛的病變並採取治療,顯得非常重要。
數據可以從AIStudio下載
示例圖片如下
數據集準備
/home/aistudio/data/data19065 目錄包括如下三個文件,解壓縮後存放在/home/aistudio/work/palm目錄下。
training.zip:包含訓練中的圖片和標籤validation.zip:包含驗證集的圖片valid_gt.zip:包含驗證集的標籤注意:
valid_gt.zip文件解壓縮之後,需要將/home/aistudio/work/palm/PALM-Validation-GT/目錄下的PM_Label_and_Fovea_Location.xlsx文件轉存成csv格式,本節代碼示例中已經提前轉成文件labels.csv。
# 初次運行時將注釋取消,以便解壓文件
# 如果已經解壓過了,則不需要運行此段代碼,否則文件已經存在解壓會報錯
#!unzip -d /home/aistudio/work/palm /home/aistudio/data/data19065/training.zip
#%cd /home/aistudio/work/palm/PALM-Training400/
#!unzip PALM-Training400.zip
#!unzip -d /home/aistudio/work/palm /home/aistudio/data/data19065/validation.zip
#!unzip -d /home/aistudio/work/palm /home/aistudio/data/data19065/valid_gt.zip
查看數據集圖片
iChallenge-PM中既有病理性近視患者的眼底圖片,也有非病理性近視患者的圖片,命名規則如下:
病理性近視(PM):文件名以P開頭非病理性近視(non-PM):高度近視(high myopia):文件名以H開頭正常眼睛(normal):文件名以N開頭我們將病理性患者的圖片作為正樣本,標籤為1;非病理性患者的圖片作為負樣本,標籤為0。從數據集中選取兩張圖片,通過LeNet提取特徵,構建分類器,對正負樣本進行分類,並將圖片顯示出來。代碼如下所示:
import os
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from PIL import Image
DATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
# 文件名以N開頭的是正常眼底圖片,以P開頭的是病變眼底圖片
file1 = 'N0012.jpg'
file2 = 'P0095.jpg'
# 讀取圖片
img1 = Image.open(os.path.join(DATADIR, file1))
img1 = np.array(img1)
img2 = Image.open(os.path.join(DATADIR, file2))
img2 = np.array(img2)
# 畫出讀取的圖片
plt.figure(figsize=(16, 8))
f = plt.subplot(121)
f.set_title('Normal', fontsize=20)
plt.imshow(img1)
f = plt.subplot(122)
f.set_title('PM', fontsize=20)
plt.imshow(img2)
plt.show()
# 查看圖片形狀
img1.shape, img2.shape
定義數據讀取器
使用OpenCV從磁碟讀入圖片,將每張圖縮放到大小,並且將像素值調整到之間,代碼如下所示:
import cv2
import random
import numpy as np
# 對讀入的圖像數據進行預處理
def transform_img(img):
# 將圖片尺寸縮放道 224x224
img = cv2.resize(img, (224, 224))
# 讀入的圖像數據格式是[H, W, C]
# 使用轉置操作將其變成[C, H, W]
img = np.transpose(img, (2,0,1))
img = img.astype('float32')
# 將數據範圍調整到[-1.0, 1.0]之間
img = img / 255.
img = img * 2.0 - 1.0
return img
# 定義訓練集數據讀取器
def data_loader(datadir, batch_size=10, mode = 'train'):
# 將datadir目錄下的文件列出來,每條文件都要讀入
filenames = os.listdir(datadir)
def reader():
if mode == 'train':
# 訓練時隨機打亂數據順序
random.shuffle(filenames)
batch_imgs = []
batch_labels = []
for name in filenames:
filepath = os.path.join(datadir, name)
img = cv2.imread(filepath)
img = transform_img(img)
if name[0] == 'H' or name[0] == 'N':
# H開頭的文件名表示高度近視,N開頭的文件名表示正常視力
# 高度近視和正常視力的樣本,都不是病理性的,屬於負樣本,標籤為0
label = 0
elif name[0] == 'P':
# P開頭的是病理性近視,屬於正樣本,標籤為1
label = 1
else:
raise('Not excepted file name')
# 每讀取一個樣本的數據,就將其放入數據列表中
batch_imgs.append(img)
batch_labels.append(label)
if len(batch_imgs) == batch_size:
# 當數據列表的長度等於batch_size的時候,
# 把這些數據當作一個mini-batch,並作為數據生成器的一個輸出
imgs_array = np.array(batch_imgs).astype('float32')
labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
yield imgs_array, labels_array
batch_imgs = []
batch_labels = []
if len(batch_imgs) > 0:
# 剩餘樣本數目不足一個batch_size的數據,一起打包成一個mini-batch
imgs_array = np.array(batch_imgs).astype('float32')
labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
yield imgs_array, labels_array
return reader
# 定義驗證集數據讀取器
def valid_data_loader(datadir, csvfile, batch_size=10, mode='valid'):
# 訓練集讀取時通過文件名來確定樣本標籤,驗證集則通過csvfile來讀取每個圖片對應的標籤
# 請查看解壓後的驗證集標籤數據,觀察csvfile文件裡面所包含的內容
# csvfile文件所包含的內容格式如下,每一行代表一個樣本,
# 其中第一列是圖片id,第二列是文件名,第三列是圖片標籤,
# 第四列和第五列是Fovea的坐標,與分類任務無關
# ID,imgName,Label,Fovea_X,Fovea_Y
# 1,V0001.jpg,0,1157.74,1019.87
# 2,V0002.jpg,1,1285.82,1080.47
# 打開包含驗證集標籤的csvfile,並讀入其中的內容
filelists = open(csvfile).readlines()
def reader():
batch_imgs = []
batch_labels = []
for line in filelists[1:]:
line = line.strip().split(',')
name = line[1]
label = int(line[2])
# 根據圖片文件名加載圖片,並對圖像數據作預處理
filepath = os.path.join(datadir, name)
img = cv2.imread(filepath)
img = transform_img(img)
# 每讀取一個樣本的數據,就將其放入數據列表中
batch_imgs.append(img)
batch_labels.append(label)
if len(batch_imgs) == batch_size:
# 當數據列表的長度等於batch_size的時候,
# 把這些數據當作一個mini-batch,並作為數據生成器的一個輸出
imgs_array = np.array(batch_imgs).astype('float32')
labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
yield imgs_array, labels_array
batch_imgs = []
batch_labels = []
if len(batch_imgs) > 0:
# 剩餘樣本數目不足一個batch_size的數據,一起打包成一個mini-batch
imgs_array = np.array(batch_imgs).astype('float32')
labels_array = np.array(batch_labels).astype('float32').reshape(-1, 1)
yield imgs_array, labels_array
return reader
# 查看數據形狀
DATADIR = '/home/aistudio/work/palm/PALM-Training400/PALM-Training400'
train_loader = data_loader(DATADIR,
batch_size=10, mode='train')
data_reader = train_loader()
data = next(data_reader)
data[0].shape, data[1].shap
通過運行結果可以看出,在眼疾篩查數據集iChallenge-PM上,LeNet的loss很難下降,模型沒有收斂。這是因為MNIST數據集的圖片尺寸比較小(),但是眼疾篩查數據集圖片尺寸比較大(原始圖片尺寸約為,經過縮放之後變成),LeNet模型很難進行有效分類。這說明在圖片尺寸比較大時,LeNet在圖像分類任務上存在局限性。
05 AlexNet
通過上面的實際訓練可以看到,雖然LeNet在手寫數字識別數據集上取得了很好的結果,但在更大的數據集上表現卻並不好。自從1998年LeNet問世以來,接下來十幾年的時間裡,神經網絡並沒有在計算機視覺領域取得很好的結果,反而一度被其它算法所超越,原因主要有兩方面,一是神經網絡的計算比較複雜,對當時計算機的算力來說,訓練神經網絡是件非常耗時的事情;另一方面,當時還沒有專門針對神經網絡做算法和訓練技巧的優化,神經網絡的收斂性是件非常困難的事情。
隨著技術的進步和發展,計算機的算力越來越強大,尤其是在GPU並行計算能力的推動下,複雜神經網絡的計算也變得更加容易實施。另一方面,網際網路上湧現出越來越多的數據,極大的豐富了資料庫。同時也有越來越多的研究人員開始專門針對神經網絡做算法和模型的優化,Alex Krizhevsky等人提出的AlexNet以很大優勢獲得了2012年ImageNet比賽的冠軍。這一成果極大的激發了業界對神經網絡的興趣,開創了使用深度神經網絡解決圖像問題的途徑,隨後也在這一領域湧現出越來越多的優秀工作。
AlexNet與LeNet相比,具有更深的網絡結構,包含5層卷積和3層全連接,同時使用了如下三種方法改進模型的訓練過程:
數據增多:深度學習中常用的一種處理方式,通過對訓練隨機加一些變化,比如平移、縮放、裁剪、旋轉、翻轉或者增減亮度等,產生一系列跟原始圖片相似但又不完全相同的樣本,從而擴大訓練數據集。通過這種方式,可以隨機改變訓練樣本,避免模型過度依賴於某些屬性,能從一定程度上抑制過擬合。使用Dropout抑制過擬合使用ReLU激活函數少梯度消失現象說明:
下一節詳細介紹數據增多的具體實現方式。
AlexNet的具體結構如 圖2所示:
圖2:AlexNet模型網絡結構示意圖
AlexNet在眼疾篩查數據集iChallenge-PM上具體實現的代碼如下所示:
# -*- coding:utf-8 -*-
# 導入需要的包
import paddle
import paddle.fluid as fluid
import numpy as np
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, FC
# 定義 AlexNet 網絡結構
class AlexNet(fluid.dygraph.Layer):
def __init__(self, name_scope, num_classes=1):
super(AlexNet, self).__init__(name_scope)
name_scope = self.full_name()
# AlexNet與LeNet一樣也會同時使用卷積和池化層提取圖像特徵
# 與LeNet不同的是激活函數換成了『relu』
self.conv1 = Conv2D(name_scope, num_filters=96, filter_size=11, stride=4, padding=5, act='relu')
self.pool1 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
self.conv2 = Conv2D(name_scope, num_filters=256, filter_size=5, stride=1, padding=2, act='relu')
self.pool2 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
self.conv3 = Conv2D(name_scope, num_filters=384, filter_size=3, stride=1, padding=1, act='relu')
self.conv4 = Conv2D(name_scope, num_filters=384, filter_size=3, stride=1, padding=1, act='relu')
self.conv5 = Conv2D(name_scope, num_filters=256, filter_size=3, stride=1, padding=1, act='relu')
self.pool5 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
self.fc1 = FC(name_scope, size=4096, act='relu')
self.drop_ratio1 = 0.5
self.fc2 = FC(name_scope, size=4096, act='relu')
self.drop_ratio2 = 0.5
self.fc3 = FC(name_scope, size=num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.pool5(x)
x = self.fc1(x)
# 在全連接之後使用dropout抑制過擬合
x= fluid.layers.dropout(x, self.drop_ratio1)
x = self.fc2(x)
# 在全連接之後使用dropout抑制過擬合
x = fluid.layers.dropout(x, self.drop_ratio2)
x = self.fc3(x)
return x
with fluid.dygraph.guard():
model = AlexNet("AlexNet")
train(model)
通過運行結果可以發現,在眼疾篩查數據集iChallenge-PM上使用AlexNet,loss能有效下降,經過5個epoch的訓練,在驗證集上的準確率可以達到94%左右。
06 總結
本周課程中孫老師主要為大家講解了計算機視覺中分類任務的主要內容,以眼疾識別任務為例,分別介紹了經典的LeNet和AlexNet神經網絡結構。在後期課程中,將繼續為大家帶來內容更豐富的課程,幫助學員快速掌握深度學習方法。
【如何學習】
如何觀看配套視頻?如何代碼實踐?視頻+代碼已經發布在AI Studio實踐平臺上,視頻支持PC端/手機端同步觀看,也鼓勵大家親手體驗運行代碼哦。打開以下連結:
https://aistudio.baidu.com/aistudio/course/introduce/888
學習過程中,有疑問怎麼辦?加入深度學習集訓營QQ群:726887660,班主任與飛槳研發會在群裡進行答疑與學習資料發放。
如何學習更多內容?百度飛槳將通過飛槳深度學習集訓營的形式,繼續更新《零基礎入門深度學習》課程,由百度深度學習高級研發工程師親自授課,每周二、每周四8:00-9:00不見不散,採用直播+錄播+實踐+答疑的形式,歡迎關注~
請搜索AI Studio,點擊課程-百度架構師手把手教深度學習,或者點擊https://aistudio.baidu.com/aistudio/course/introduce/888收看。