第三次作业:卷积神经网络基础
視頻學習
邱琦
講述了卷積神經網絡的應用:分類,檢索,檢測,分割,人臉識別,表情識別,圖像生成,自動駕駛。卷積神經網絡相對傳統神經網絡的優點:局部關聯,參數共享。卷積神經網絡存在卷積層,RELU激活層,池化層,全連接層。其中卷積是對兩個實變函數的一種數學操作,將圖片進行參數化。池化層保留了主要特征的同時減少參數和計算量,防止過擬合,提高模型泛化能力。全連接層兩層之間所有神經元都有權重鏈接,通常在卷積神經網絡尾部,參數量通常最大。卷積神經網絡典型結構包括AlexNet,ZFNet,VGG,GoogleNet,ResNet。這次視頻最后舉的例子,能夠幫助較好的理解,再加上自己的實驗,能夠對課程有更好的理解。
陳江棟
對于計算機視覺來說,每?個圖像是由?個個像素點構成,每個像素點有三個通道,分別
代表RGB三種顏?(不計算透明度),我們以?寫識別的數據集MNIST舉例,每個圖像的是 ?個?寬均為28,channel(通道數)為1的單?圖像,如果使?全連接的?絡結構, 即,?絡中的神經與相鄰層上的每個神經元均連接,那就意味著我們的?絡有
28×28=784個神經元(RGB3?的話還要乘3),hidden層如果使?了15個神經元,需要 的參數個數(w和b)就有:28×28×15×10+15+10=117625個,這個數量級到現在為?也是 ?個很恐怖的數量級,?次反向傳播計算量都是巨?的,這還只是?個單?的28像素? ?的圖?,如果我們使?更?的像素,計算量可想?知。 上?說到傳統的?絡需要?量的參數,但是這些參數是否重復了呢,例如,我們識別?個
?,只要看到他的眼睛,??,嘴,還有臉基本上就知道這個?是誰了,只是?這些局部 的特征就能做做判斷了,并不需要所有的特征。 另外?點就是我們上?說的可以有效提 取了輸?圖像的平移不變特征,就好像我們看到了這是個眼睛,這個眼睛在左邊還是在右 邊他都是眼睛,這就是平移不變性。并且我們也?般認為兩個靠近的物品他倆都有相似的 屬性。這也是圖像本?的特性,這?特性在把NLP中transformer模型遷移到CV領域?產 ?的swin-transformer這?篇論?中起到了很?的影響?。 我們通過卷積的計算操作來提 取圖像局部的特征,每?層都會計算出?些局部特征,這些局部特征再匯總到下?層,這 樣?層?層的傳遞下去,特征由初級變為?級,最后在通過這些局部的特征對圖?進?處 理,這樣??提?了計算效率,也提?了準確度。
李智杰
通過視頻,學習到了許多關于卷積神經網絡的知識,學習到了卷積網絡的基本結構,卷積、池化和一些經典的網絡與結構,AlexNet、VGG16、VGG19,GoogleNet的Inception和ResNet,通過后面的代碼練習也了解到了相比傳統的神經網絡,卷積網絡具有巨大的優勢。
宋子昂
卷積神經網絡的應用非常廣泛,包括分類、檢索、檢測、分割、人臉識別、圖像生成等,這些應用在我們現在看來都非常普遍;由此可以看出卷積神經網絡的重要性。而與傳統神經網絡相比,卷積神經網絡主要突出在局部關聯、參數共享,它不像傳統的網絡那樣需要大量的參數。這樣可以在訓練的時候大大節省時間。CNN在圖像處理問題上優勢明顯,它是一個可以自動提取特征,而且待訓練參數相對不是很多的神經網絡。卷積神經網絡是一個由卷積層、激活層、池化層、全連接層聚合而成的網絡結構。卷積運用了局部連接的思想,它對圖片的處理方式是一塊一塊的,并不是所有像素值一起處理,因此可以極大的降低參數值的總量。通過視頻的學習,我覺得發現卷積這一個點的人也是相當厲害了,通過矩陣相乘來實現了提取特征這一過程。但同時池化層的思想,我認為在網絡中的使用有待商榷,我認為應該分情況來決定是否使用池化層,這可以說是一種丟失了細節來提升模型不變性的方法,是存在一定弊端的,以后的研究可以提高池化層的準確率,避免在訓練時丟失掉重要的細節。而全連接層實際上就是一個分類器!在CNN有不少優勢的同時,我們也需正視其局限性:沒有考慮圖像中特征的依賴關系,且對特征的全局位置不敏感等在以后的研究中,希望可以有這些問題的解決。
韋境
通過本次對神經網絡的學習,我深刻了解到了這一學科的嚴謹性和思維發散性。最令我感觸的時googlenet模型中,對1×1卷積的應用。當某個卷積層輸入的特征數較多,對這個輸入進行卷積運算將產生巨大的計算量;如果對輸入先進行降維到1×1,減少特征數后再做卷積計算量就會顯著減少。它更高效的利用計算資源,在相同的計算量下能提取到更多的特征。
陳曉政
卷積神經網絡是一種深度學習模型或類似于人工神經網絡的多層感知器,卷積神經網絡依舊是層級網絡,只是層的功能和形式做了變化,是傳統神經網絡的一個改進。卷積神經網絡是仿照生物的視知覺來構建的,可以進行監督學習和非監督學習。
卷積神經網絡主要由 5 層組成:數據輸入層,卷積計算層,ReLU 激勵層 ,池化層 ,全連接層 。
優點:共享卷積核,對高維數據處理無壓力,無需手動選取特征,訓練好權重,即得特征分類效果好。
缺點:需要調參,需要大樣本量,訓練最好要 GPU,物理含義不明確,也就是說,我們并不知道沒個卷積層到底提取到的是什么特征,而且神經網絡本身就是一種難以解釋的“黑箱模型”。
代碼練習
MNIST 數據集分類
構建簡單的CNN對 mnist 數據集進行分類。同時,還會在實驗中學習池化與卷積操作的基本作用。
1. 引入庫文件
mport torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy# 一個函數,用來計算模型中有多少參數
def get_n_params(model):np=0for p in list(model.parameters()):np += p.nelement()return np# 使用GPU訓練,可以在菜單 "代碼執行工具" -> "更改運行時類型" 里進行設置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
2. 加載數據
PyTorch里包含了 MNIST, CIFAR10 等常用數據集,調用 torchvision.datasets 即可把這些數據由遠程下載到本地,下面給出MNIST的使用方法:
torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)
- root 為數據集下載到本地后的根目錄,包括 training.pt 和 test.pt 文件
- train,如果設置為True,從training.pt創建數據集就是為訓練集,否則從test.pt創建即測試集。
- download,如果設置為True, 從互聯網下載數據并放到root文件夾下
- transform, 一種函數或變換,輸入PIL圖片,返回變換之后的數據。
- target_transform 一種函數或變換,輸入目標,進行變換。
配合DataLoader進行使用
loader_data=DataLoader(test_set,batch_size=64,shuffle=True,drop_last=True)
- 首先傳入一個dataset數據類型的數據
- batch_size:可以理解為一副牌,每個人手里拿幾張牌
- shuffle:是否每次隨機抓取,就是是否重新洗牌
- drop_last:總共的dataset的數量除以batch_size的余數是否保留
- num_workers:加載數據的時候使用幾個子進程
input_size = 28*28 # MNIST上的圖像尺寸是 28x28
output_size = 10 # 類別為 0 到 9 的數字,因此為十類train_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=True, download=True,transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])),batch_size=64, shuffle=True)test_loader = torch.utils.data.DataLoader(datasets.MNIST('./data', train=False, transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])),batch_size=1000, shuffle=True)
2.1 查看數據
除了使用老師提供的plt,還可以使用tensorboard展示
#顯示數據集中的部分圖像
#除了老師使用的plt還可以使用SummaryWriter
from torch.utils.tensorboard import SummaryWriter
writer=SummaryWriter('logs')#日志文件的存放地址
step=0
for data in train_loader:img,target=datawriter.add_images('train_loader',img,step)step=step+1
%load_ext tensorboard
%tensorboard --logdir logs
writer.close()
這里的圖像呈現灰色,是因為前面做了標準化處理transforms.Normalize
3. 創建網絡
定義網絡時,需要繼承nn.Module,并實現它的forward方法,把網絡中具有可學習參數的層放在構造函數init中。
注:這兩個方法必須重寫!
只要在nn.Module的子類中定義了forward函數,backward函數就會自動被實現(利用autograd)。
class FC2Layer(nn.Module):def __init__(self, input_size, n_hidden, output_size):# nn.Module子類的函數必須在構造函數中執行父類的構造函數# 下式等價于nn.Module.__init__(self) super(FC2Layer, self).__init__()self.input_size = input_size# 這里直接用 Sequential 就定義了網絡,注意要和下面 CNN 的代碼區分開self.network = nn.Sequential(nn.Linear(input_size, n_hidden), nn.ReLU(), nn.Linear(n_hidden, n_hidden), nn.ReLU(), nn.Linear(n_hidden, output_size), nn.LogSoftmax(dim=1))def forward(self, x):# view一般出現在model類的forward函數中,用于改變輸入或輸出的形狀# x.view(-1, self.input_size) 的意思是多維的數據展成二維# 代碼指定二維數據的列數為 input_size=784,行數 -1 表示我們不想算,電腦會自己計算對應的數字# 在 DataLoader 部分,我們可以看到 batch_size 是64,所以得到 x 的行數是64# 大家可以加一行代碼:print(x.cpu().numpy().shape)# 訓練過程中,就會看到 (64, 784) 的輸出,和我們的預期是一致的# forward 函數的作用是,指定網絡的運行過程,這個全連接網絡可能看不啥意義,# 下面的CNN網絡可以看出 forward 的作用。x = x.view(-1, self.input_size)return self.network(x)
class CNN(nn.Module):def __init__(self, input_size, n_feature, output_size):# 執行父類的構造函數,所有的網絡都要這么寫super(CNN, self).__init__()# 下面是網絡里典型結構的一些定義,一般就是卷積和全連接# 池化、ReLU一類的不用在這里定義self.n_feature = n_featureself.conv1 = nn.Conv2d(in_channels=1, out_channels=n_feature, kernel_size=5)self.conv2 = nn.Conv2d(n_feature, n_feature, kernel_size=5)self.fc1 = nn.Linear(n_feature*4*4, 50)self.fc2 = nn.Linear(50, 10) # 下面的 forward 函數,定義了網絡的結構,按照一定順序,把上面構建的一些結構組織起來# 意思就是,conv1, conv2 等等的,可以多次重用def forward(self, x, verbose=False):x = self.conv1(x)x = F.relu(x)x = F.max_pool2d(x, kernel_size=2)x = self.conv2(x)x = F.relu(x)x = F.max_pool2d(x, kernel_size=2)x = x.view(-1, self.n_feature*4*4)x = self.fc1(x)x = F.relu(x)x = self.fc2(x)x = F.log_softmax(x, dim=1)return x
3.2 定義訓練和測試函數
# 訓練函數
def train(model):model.train()# 主里從train_loader里,64個樣本一個batch為單位提取樣本進行訓練for batch_idx, (data, target) in enumerate(train_loader):# 把數據送到GPU中data, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = F.nll_loss(output, target)loss.backward()optimizer.step()if batch_idx % 100 == 0:print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(batch_idx * len(data), len(train_loader.dataset),100. * batch_idx / len(train_loader), loss.item()))def test(model):model.eval()test_loss = 0correct = 0for data, target in test_loader:# 把數據送到GPU中data, target = data.to(device), target.to(device)# 把數據送入模型,得到預測結果output = model(data)# 計算本次batch的損失,并加到 test_loss 中test_loss += F.nll_loss(output, target, reduction='sum').item()# get the index of the max log-probability,最后一層輸出10個數,# 值最大的那個即對應著分類結果,然后把分類結果保存在 pred 里pred = output.data.max(1, keepdim=True)[1]# 將 pred 與 target 相比,得到正確預測結果的數量,并加到 correct 中# 這里需要注意一下 view_as ,意思是把 target 變成維度和 pred 一樣的意思 correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()test_loss /= len(test_loader.dataset)accuracy = 100. * correct / len(test_loader.dataset)print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(test_loss, correct, len(test_loader.dataset),accuracy))
4.1 在小型全連接網絡上訓練(Fully-connected network)
n_hidden = 8 # number of hidden unitsmodel_fnn = FC2Layer(input_size, n_hidden, output_size)
model_fnn.to(device)
optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_fnn)))train(model_fnn)
test(model_fnn)
運行結果
4.2 在卷積神經網絡上訓練
#@title 3.2 在卷積神經網絡上訓練
# Training settings
n_features = 6 # number of feature mapsmodel_cnn = CNN(input_size, n_features, output_size)
model_cnn.to(device)
optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_cnn)))train(model_cnn)
test(model_cnn)
運行結果
通過上面的測試結果,可以發現,含有相同參數的 CNN 效果要明顯優于 簡單的全連接網絡,是因為 CNN 能夠更好的挖掘圖像中的信息,主要通過兩個手段:
- 卷積:Locality and stationarity in images
- 池化:Builds in some translation invariance
5. 打亂像素順序再次在兩個網絡上訓練與測試
5.1 圖像的形態
考慮到CNN在卷積與池化上的優良特性,如果我們把圖像中的像素打亂順序,這樣 卷積 和 池化 就難以發揮作用了,為了驗證這個想法,我們把圖像中的像素打亂順序再試試。
首先下面代碼展示隨機打亂像素順序后,圖像的形態:
#@title 4. 打亂像素順序再次在兩個網絡上訓練與測試
# 這里解釋一下 torch.randperm 函數,給定參數n,返回一個從0到n-1的隨機整數排列
perm = torch.randperm(784)
plt.figure(figsize=(8, 4))
for i in range(10):image, _ = train_loader.dataset.__getitem__(i)# permute pixelsimage_perm = image.view(-1, 28*28).clone()image_perm = image_perm[:, perm]image_perm = image_perm.view(-1, 1, 28, 28)plt.subplot(4, 5, i + 1)plt.imshow(image.squeeze().numpy(), 'gray')plt.axis('off')plt.subplot(4, 5, i + 11)plt.imshow(image_perm.squeeze().numpy(), 'gray')plt.axis('off')
# 對每個 batch 里的數據,打亂像素順序的函數
def perm_pixel(data, perm):# 轉化為二維矩陣data_new = data.view(-1, 28*28)# 打亂像素順序data_new = data_new[:, perm]# 恢復為原來4維的 tensordata_new = data_new.view(-1, 1, 28, 28)return data_new# 訓練函數
def train_perm(model, perm):model.train()for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)# 像素打亂順序data = perm_pixel(data, perm)optimizer.zero_grad()output = model(data)loss = F.nll_loss(output, target)loss.backward()optimizer.step()if batch_idx % 100 == 0:print('Train: [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(batch_idx * len(data), len(train_loader.dataset),100. * batch_idx / len(train_loader), loss.item()))# 測試函數
def test_perm(model, perm):model.eval()test_loss = 0correct = 0for data, target in test_loader:data, target = data.to(device), target.to(device)# 像素打亂順序data = perm_pixel(data, perm)output = model(data)test_loss += F.nll_loss(output, target, reduction='sum').item()pred = output.data.max(1, keepdim=True)[1] correct += pred.eq(target.data.view_as(pred)).cpu().sum().item()test_loss /= len(test_loader.dataset)accuracy = 100. * correct / len(test_loader.dataset)print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(test_loss, correct, len(test_loader.dataset),accuracy))
重新定義訓練與測試函數,我們寫了兩個函數 train_perm 和 test_perm,分別對應著加入像素打亂順序的訓練函數與測試函數。
與之前的訓練與測試函數基本上完全相同,只是對 data 加入了打亂順序操作。
5.2 在全連接網絡上訓練與測試:
#@title 4.1 在全連接網絡上訓練與測試:
perm = torch.randperm(784)
n_hidden = 8 # number of hidden unitsmodel_fnn = FC2Layer(input_size, n_hidden, output_size)
model_fnn.to(device)
optimizer = optim.SGD(model_fnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_fnn)))train_perm(model_fnn, perm)
test_perm(model_fnn, perm)
5.3 在卷積神經網絡上訓練與測試:
#@title 4.2 在卷積神經網絡上訓練與測試:
perm = torch.randperm(784)
n_features = 6 # number of feature mapsmodel_cnn = CNN(input_size, n_features, output_size)
model_cnn.to(device)
optimizer = optim.SGD(model_cnn.parameters(), lr=0.01, momentum=0.5)
print('Number of parameters: {}'.format(get_n_params(model_cnn)))train_perm(model_cnn, perm)
test_perm(model_cnn, perm)
從打亂像素順序的實驗結果來看,全連接網絡的性能基本上沒有發生變化,但是 卷積神經網絡的性能明顯下降。
這是因為對于卷積神經網絡,會利用像素的局部關系,但是打亂順序以后,這些像素間的關系將無法得到利用。
關于實驗中的一些問題
- dataloader 里面 shuffle 取不同值有什么區別
當shuffle取True時就是每次取為亂序
取False就不會重新隨機排序
- transform 里,取了不同值,這個有什么區別?
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))])
首先理解transform是對數據進行一些預處理的操作
1. Compose為一個流水線操作,就是依次執行數組里的操作
2. ToTensor將數據轉為tensor數據類型
3. Normalize進行歸一化處理,這就是為什么查看圖片會是灰色的
- epoch 和 batch 的區別?
- Epoch:所有訓練樣本在神經網絡中都進行了一次正向傳播和一次反向傳播。也就是1個epoch等于使用訓練集中的全部樣本訓練一次。
- Batch:將整個訓練樣本分成若干個Batch。
- 1x1的卷積和 FC 有什么區別?主要起什么作用?
- 1x1的卷積是針對矩陣的卷積操作,1x1卷積不改變寬度和高度,只改變channel數,主要作用是升維、降維以及提升網絡的非線性;
- FC是針對神經元的操作。FC的作用主要是將局部特征進行整合,并得到分類標簽。
CIFAR10 數據集分類
使用 CNN 對 CIFAR10 數據集進行分類:
運用totchvision包來進行視覺數據的處理,數據集選用CIFAR10,包含十個類別:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的圖像尺寸為3x32x32,也就是RGB的3層顏色通道,每層通道內的尺寸為32*32。
首先,加載并歸一化 CIFAR10 使用 torchvision 。torchvision 數據集的輸出是范圍在[0,1]之間的 PILImage,我們將他們轉換成歸一化范圍為[-1,1]之間的張量 Tensors。
導入相關包,下載相關數據集并使用GPU訓練:
通過書寫imshow函數來展示CIFAR10里面的一些圖片:
定義網絡、損失函數和優化器:
訓練網絡,該過程耗時2分鐘:
從測試集中取出8張圖片:
把圖片輸入建立好的模型,查看CNN模型的識別效果,可以發現識別準確度不高,出現了一些錯誤:
再來查看網絡在整個數據集上的表現,我們發現準確率只能說是可以接受:
所以我們需要建立更好的網絡模型,總結CNN的使用效果,我們可以得到一些缺點:
- 當網絡層次太深時,采用BP傳播修改參數會使靠近輸入層的參數改動較慢;
- 采用梯度下降算法很容易使訓練結果收斂于局部最小值而非全局最小值;
- 池化層會丟失大量有價值信息,忽略局部與整體之間關聯性;
- 由于特征提取的封裝,為網絡性能的改進罩了一層黑盒
使用 VGG16 對 CIFAR10 分類
1. 重新定義 dataloader
2. VGG 網絡定義
原代碼中出現“cfg”無法識別問題,將參數layers(cfg)改為layers(self.cfg)后正常運行。
3. 網絡訓練
原代碼出現RuntimeError: mat1 and mat2 shapes cannot be multiplied (128x512 and 2048x10)報錯,因此對nn.Linear 參數進行修改,把2048改成了512。
4. 測試驗證準確率
使用一個簡化版的 VGG 網絡,就能將準確率提升到 83.18%
改為訓練15次后,準確率提升到87.64%。
問題回答
1、dataloader 里面 shuffle 取不同值有什么區別?
dataloader是一個加載數據到模型中訓練的方法,其中的參數shuffle參數有True/False兩種取值;官方文檔的解釋如下:
· 當shuffle=false時,每次的訓練不打亂數據的順序,然后以batch為單位取數據
· 當shuffle=true時,每次訓練前打亂所有的數據次序,然后以batch為單位取數據
2、transform 里,取了不同值,這個有什么區別?
transform里可以通過不同參數實現一些常用的數據預處理操作。
transforms.normalize()使得數據服從01分布。
加速了收斂,其使用的公式為x=(x-mean)/std
3、epoch 和 batch 的區別?
一個epoch中包含了多個batch,每一次對整個數據集的訓練都是一次epoch,而batch則表示單個epoch中一次批處理數據所包含的數據單位。
4、1x1的卷積和 FC 有什么區別?主要起什么作用?
1×1卷積核是對二維情況下的簡化,使得其僅僅反映在深度上的變化,起到了降維的作用。
相對于FC,1×1卷積核需要的參數量會比FC層所使用的參數量少,計算速度更快。
5、residual leanring 為什么能夠提升準確率?
因為它引入了殘差網絡結構的概念,殘差網絡借鑒了高速網絡的跨層鏈接思想,對其進行改進,打破了傳統的神經網絡n-1層的輸出只能給n層作為輸入的慣例,使某一層的輸出可以直接跨過幾層作為后面某一層的輸入,從而解決了深度增加后的“退化”問題,達到了提高準確率的目的。
6、代碼練習二里,網絡和1989年 Lecun 提出的 LeNet 有什么區別?
代碼練習二中使用的是最大池化和ReLU激活函數,而LeNet使用的是平均池化,激活函數是sigmoid;
7、代碼練習二里,卷積以后feature map 尺寸會變小,如何應用 Residual Learning?
通過線性變換將原圖像縮小為和feature map大小相同的圖像;當輸入輸出維度上升時有兩種處理方式:第一種是仍使用恒等映射,多出來的通道使用零矩陣填充,這樣做的好處是不會帶來額外的參數;第二種是添加變換方程,通常來說會使用 1*1 卷積來完成升維。
8、有什么方法可以進一步提升準確率?
調整網絡結構;增加數據集的大小,使數據更加豐富;增加深度與節點數;使用不同的激活函數、交叉熵函數;增加訓練輪數
總結
以上是生活随笔為你收集整理的第三次作业:卷积神经网络基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统第四章习题
- 下一篇: 并查集+基础知识点详解