專欄:神經(jīng)網(wǎng)絡(luò)復(fù)現(xiàn)目錄
本章介紹的是現(xiàn)代神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)和復(fù)現(xiàn),包括深度卷積神經(jīng)網(wǎng)絡(luò)(AlexNet),VGG,NiN,GoogleNet,殘差網(wǎng)絡(luò)(ResNet),稠密連接網(wǎng)絡(luò)(DenseNet)。
文章部分文字和代碼來(lái)自《動(dòng)手學(xué)深度學(xué)習(xí)》
殘差網(wǎng)絡(luò)(ResNet)
殘差網(wǎng)絡(luò)(Residual Network,簡(jiǎn)稱 ResNet)是由微軟研究院于 2015 年提出的一種深度卷積神經(jīng)網(wǎng)絡(luò)。它的主要特點(diǎn)是在網(wǎng)絡(luò)中添加了“殘差塊”(Residual Block),有效地解決了深層網(wǎng)絡(luò)的梯度消失和梯度爆炸問題,從而使得更深的網(wǎng)絡(luò)結(jié)構(gòu)可以訓(xùn)練得更好。
ResNet 的核心思想是學(xué)習(xí)殘差,即在訓(xùn)練過程中讓神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)一個(gè)殘差映射,該映射將輸入直接映射到輸出,即 F ( x ) = H ( x ) ? x F(x) = H(x) - x F(x)=H(x)?x。這里 x x x 表示殘差塊的輸入, H ( x ) H(x) H(x) 表示殘差塊的輸出。在傳統(tǒng)的網(wǎng)絡(luò)結(jié)構(gòu)中,網(wǎng)絡(luò)的每一層都需要學(xué)習(xí)一個(gè)映射函數(shù),即 H ( x ) H(x) H(x),但這種方法會(huì)出現(xiàn)梯度消失和梯度爆炸問題。而使用殘差塊可以使網(wǎng)絡(luò)只學(xué)習(xí)殘差部分,即 F ( x ) F(x) F(x),這使得訓(xùn)練更加容易,并且可以構(gòu)建更深的網(wǎng)絡(luò)結(jié)構(gòu)。
ResNet 的基本結(jié)構(gòu)是殘差塊,其中包含了跨層連接(skip connection)的機(jī)制??鐚舆B接可以將輸入直接傳遞到輸出端,從而避免了梯度消失問題,同時(shí)也減輕了梯度爆炸問題。除了跨層連接之外,ResNet 還采用了批量歸一化(Batch Normalization)和池化操作等技巧來(lái)提高網(wǎng)絡(luò)的訓(xùn)練效率和泛化能力。
總體上來(lái)說(shuō),ResNet 是一種十分有效的深度神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),其在許多計(jì)算機(jī)視覺任務(wù)上都取得了優(yōu)異的表現(xiàn),例如圖像分類、物體檢測(cè)和語(yǔ)義分割等
恒等變換
恒等變換(Identity Transformation)指的是一種變換,使得輸入和輸出完全相同。在數(shù)學(xué)上,恒等變換可以用一個(gè)函數(shù)f(x) = x來(lái)表示。
在深度學(xué)習(xí)中,恒等變換通常用于殘差塊(Residual Block)中。在殘差塊中,恒等變換被用作跳躍連接(Shortcut Connection),將輸入直接傳遞給輸出,這樣可以加速梯度的傳播和網(wǎng)絡(luò)的訓(xùn)練。
舉個(gè)例子,假設(shè)有一個(gè)殘差塊的輸入x和輸出y,它們的維度都為d。那么該殘差塊可以表示為:
y = f(x) + x
其中,f(x)是殘差塊的變換,它會(huì)對(duì)輸入進(jìn)行處理。而x則是恒等變換,它使得輸入可以直接傳遞給輸出。通過這樣的設(shè)計(jì),殘差塊可以保留輸入中的有用信息,同時(shí)仍然能夠?qū)斎脒M(jìn)行一定程度的處理,從而提高網(wǎng)絡(luò)的性能。
跳躍連接
跳躍連接(Skip Connection),也稱為殘差連接(Residual Connection),是深度神經(jīng)網(wǎng)絡(luò)中的一種連接方式,用于解決網(wǎng)絡(luò)訓(xùn)練過程中梯度消失問題。
在跳躍連接中,網(wǎng)絡(luò)的某一層的輸出不僅會(huì)傳遞給下一層進(jìn)行計(jì)算,還會(huì)直接傳遞到距離當(dāng)前層較遠(yuǎn)的層。這樣可以使得網(wǎng)絡(luò)中的信息能夠更快地傳遞和共享,同時(shí)也可以減輕梯度消失的問題,使得訓(xùn)練過程更加穩(wěn)定。
在ResNet網(wǎng)絡(luò)中,跳躍連接被用于將網(wǎng)絡(luò)的輸入直接連接到卷積層的輸出上,形成一個(gè)殘差塊。這樣,網(wǎng)絡(luò)的前向傳播就變成了從輸入到輸出的“路徑”加上一個(gè)殘差塊的“跳躍”,即“shortcut”。跳躍連接的使用大大改善了ResNet的性能,使得它在ImageNet圖像分類等任務(wù)中取得了非常優(yōu)秀的表現(xiàn)。
殘差塊
殘差塊(Residual Block)是深度學(xué)習(xí)中常用的一種模塊,可以用來(lái)構(gòu)建深度神經(jīng)網(wǎng)絡(luò)。殘差塊的主要作用是使得神經(jīng)網(wǎng)絡(luò)的訓(xùn)練更加容易,并且能夠加速神經(jīng)網(wǎng)絡(luò)的收斂。
讓我們聚焦于神經(jīng)網(wǎng)絡(luò)局部:如圖所示,假設(shè)我們的原始輸入為x,而希望學(xué)出的理想映射為f(x)作為上方激活函數(shù)的輸入,左圖虛線框中的部分需要直接擬合出該映射f(x),而右圖虛線框中的部分則需要擬合出殘差映射f(x)-x。殘差映射在現(xiàn)實(shí)中往往更容易優(yōu)化。 以本節(jié)開頭提到的恒等映射作為我們希望學(xué)出的理想映射f(x),我們只需將圖中右圖虛線框內(nèi)上方的加權(quán)運(yùn)算(如仿射)的權(quán)重和偏置參數(shù)設(shè)成0,那么f(x)即為恒等映射,實(shí)際中,當(dāng)理想映射f(x)極接近于恒等映射時(shí),殘差映射也易于捕捉恒等映射的細(xì)微波動(dòng)。
在深度神經(jīng)網(wǎng)絡(luò)中,很容易出現(xiàn)梯度消失或梯度爆炸的問題,這會(huì)導(dǎo)致深度神經(jīng)網(wǎng)絡(luò)的訓(xùn)練非常困難。殘差塊的設(shè)計(jì)可以緩解這個(gè)問題。殘差塊的主要思想是在網(wǎng)絡(luò)中增加一條跳躍連接(Shortcut Connection),這條連接可以讓輸入直接跳過一些層,從而更加容易地傳遞梯度。具體來(lái)說(shuō),殘差塊可以分為如下幾個(gè)步驟:
- 輸入x經(jīng)過一個(gè)卷積層,得到特征圖y1。
- 將y1經(jīng)過一個(gè)Batch Normalization層和ReLU激活函數(shù)。
- 將y1再次經(jīng)過一個(gè)卷積層,得到特征圖y2。
- 將y2經(jīng)過一個(gè)Batch Normalization層。
- 將輸入x和y2相加,得到殘差特征圖y3。
- 將y3再經(jīng)過一個(gè)ReLU激活函數(shù)。
在這個(gè)過程中,輸入x可以看做是一種殘差,因?yàn)樗鼤?huì)直接和特征圖y2相加。這個(gè)殘差塊的設(shè)計(jì)可以讓網(wǎng)絡(luò)更容易地學(xué)習(xí)殘差,從而更好地?cái)M合訓(xùn)練數(shù)據(jù)。此外,殘差塊也可以增加網(wǎng)絡(luò)的深度,從而提升網(wǎng)絡(luò)的效果。
ResNet模型
結(jié)構(gòu)
ResNet原文中給出了幾種基本的網(wǎng)絡(luò)結(jié)構(gòu)配置,本文以ResNet50為例。
ResNet50結(jié)構(gòu)詳解:
-
輸入層(Input Layer):輸入圖像的大小為224 x 224 x 3。
-
卷積層(Convolution Layer):7x7的卷積核,步長(zhǎng)為2,輸出通道為64,padding為3。
-
標(biāo)準(zhǔn)化層(Batch Normalization Layer):對(duì)每個(gè)通道的輸出做標(biāo)準(zhǔn)化處理,包括均值和方差。
-
激活函數(shù)(Activation Layer):使用ReLU激活函數(shù)。
-
最大池化層(Max Pooling Layer):3x3的池化核,步長(zhǎng)為2,padding為1,對(duì)每個(gè)通道的輸出取最大值。
-
殘差塊(Residual Block)1:包含3個(gè)卷積層和標(biāo)準(zhǔn)化層。第一個(gè)卷積層的卷積核為1x1,輸出通道為64;第二個(gè)卷積層的卷積核為3x3,輸出通道為64;第三個(gè)卷積層的卷積核為1x1,輸出通道為256(因?yàn)闅埐顗K的輸入和輸出通道數(shù)不同,需要用1x1的卷積核進(jìn)行通道變換)。
-
殘差塊(Residual Block)2:包含4個(gè)卷積層和標(biāo)準(zhǔn)化層。第一個(gè)卷積層的卷積核為1x1,輸出通道為128;第二個(gè)卷積層的卷積核為3x3,輸出通道為128;第三個(gè)卷積層的卷積核為1x1,輸出通道為512。
-
殘差塊(Residual Block)3:包含6個(gè)卷積層和標(biāo)準(zhǔn)化層。第一個(gè)卷積層的卷積核為1x1,輸出通道為256;第二個(gè)卷積層的卷積核為3x3,輸出通道為256;第三個(gè)卷積層的卷積核為1x1,輸出通道為1024。
-
殘差塊(Residual Block)4:包含3個(gè)卷積層和標(biāo)準(zhǔn)化層。第一個(gè)卷積層的卷積核為1x1,輸出通道為512;第二個(gè)卷積層的卷積核為3x3,輸出通道為512;第三個(gè)卷積層的卷積核為1x1,輸出通道為2048。
-
平均池化層(Average Pooling Layer):使用全局平均池化,將輸出的特征圖轉(zhuǎn)化為向量。
-
全連接層(Fully Connected Layer):將向量連接到最終的輸出層,該層包含1000個(gè)神經(jīng)元,每個(gè)神經(jīng)元對(duì)應(yīng)于一個(gè)類別,表示圖像屬于該類別的概率。
-
Softmax層(Softmax Layer):使用softmax函數(shù)將全連接層的輸出轉(zhuǎn)化為概率分布,每個(gè)類別的概率為0到1之間的實(shí)數(shù),概率之和為1。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-465776.html
總結(jié):ResNet50網(wǎng)絡(luò)結(jié)構(gòu)由多個(gè)殘差塊組成,每個(gè)殘差塊內(nèi)部包含多個(gè)卷積層和標(biāo)準(zhǔn)化層。通過使用殘差學(xué)習(xí)的方法,ResNet50網(wǎng)絡(luò)能夠在訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)時(shí)解決梯度消失和梯度爆炸的問題,同時(shí)在圖像分類等任務(wù)中表現(xiàn)出色。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-465776.html
實(shí)現(xiàn)
殘差塊
import torch.nn as nn
import torch.onnx
class Residual(nn.Module):
def __init__(self, in_channels, channels, stride, downsample=None):
super(Residual, self).__init__()
# 1x1的卷積降維操作
self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=channels, kernel_size=(1, 1),
bias=False)
self.bn1 = nn.BatchNorm2d(channels)
# 3x3的卷積提取特征操作
self.conv2 = nn.Conv2d(in_channels=channels, out_channels=channels, kernel_size=(3, 3),
stride=stride,
padding=1,
bias=False)
self.bn2 = nn.BatchNorm2d(channels)
# 1x1的卷積升維操作
self.conv3 = nn.Conv2d(in_channels=channels, out_channels=channels * 4, kernel_size=(1, 1),
bias=False)
self.bn3 = nn.BatchNorm2d(channels * 4)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
# 4組卷積層的頭一層網(wǎng)絡(luò)會(huì)做一次降采樣,目的是使out和identity維度一致可以做加法
if self.downsample is not None:
self.dconv = nn.Conv2d(in_channels, channels * 4, stride=stride, kernel_size=(1, 1), bias=False)
self.dbn = nn.BatchNorm2d(channels * 4)
def forward(self, x):
identity = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
identity = self.dconv(identity)
identity = self.dbn(identity)
out += identity
out = self.relu(out)
return out
ResNet
class Resnet50(nn.Module):
def __init__(self, num_classes):
super(Resnet50, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=(7, 7), stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu1 = nn.ReLU(inplace=True)
self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# 對(duì)應(yīng)第1組網(wǎng)絡(luò)層,3*Resnet的基本結(jié)構(gòu)
self.conv64_1 = Residual(64, 64, stride=1, downsample=True)
self.conv64_2 = Residual(256, 64, stride=1)
self.conv64_3 = Residual(256, 64, stride=1)
# 對(duì)應(yīng)第2組網(wǎng)絡(luò)層,4*Resnet的基本結(jié)構(gòu)
self.conv128_1 = Residual(256, 128, stride=2, downsample=True)
self.conv128_2 = Residual(128 * 4, 128, stride=1)
self.conv128_3 = Residual(128 * 4, 128, stride=1)
self.conv128_4 = Residual(128 * 4, 128, stride=1)
# 對(duì)應(yīng)第3組網(wǎng)絡(luò)層,6*Resnet的基本結(jié)構(gòu)
self.conv256_1 = Residual(512, 256, stride=2, downsample=True)
self.conv256_2 = Residual(256 * 4, 256, stride=1)
self.conv256_3 = Residual(256 * 4, 256, stride=1)
self.conv256_4 = Residual(256 * 4, 256, stride=1)
self.conv256_5 = Residual(256 * 4, 256, stride=1)
self.conv256_6 = Residual(256 * 4, 256, stride=1)
# 對(duì)應(yīng)第4組網(wǎng)絡(luò)層,3*Resnet的基本結(jié)構(gòu)
self.conv512_1 = Residual(1024, 512, stride=2, downsample=True)
self.conv512_2 = Residual(512 * 4, 512, stride=1)
self.conv512_3 = Residual(512 * 4, 512, stride=1)
self.avgpool = nn.AdaptiveAvgPool2d((1,1))
self.fc = nn.Linear(2048, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu1(x)
x = self.maxpool1(x)
x = self.conv64_1(x)
x = self.conv64_2(x)
x = self.conv64_3(x)
x = self.conv128_1(x)
x = self.conv128_2(x)
x = self.conv128_3(x)
x = self.conv128_4(x)
x = self.conv256_1(x)
x = self.conv256_2(x)
x = self.conv256_3(x)
x = self.conv256_4(x)
x = self.conv256_5(x)
x = self.conv256_6(x)
x = self.conv512_1(x)
x = self.conv512_2(x)
x = self.conv512_3(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
利用ResNet50進(jìn)行CIFAR10分類
數(shù)據(jù)集
# 導(dǎo)入數(shù)據(jù)集
from torchvision import datasets
import torch
import torchvision.transforms as transforms
transform = transforms.Compose([
transforms.Resize((224,224)),
transforms.RandomHorizontalFlip(p=0.5),
transforms.ToTensor(),
transforms.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))
])
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'trunk')
cifar_train = datasets.CIFAR10(root="/data",train=True, download=True, transform=transform)
cifar_test = datasets.CIFAR10(root="/data",train=False, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(cifar_train, batch_size=16, shuffle=True)
test_loader = torch.utils.data.DataLoader(cifar_test, batch_size=16, shuffle=False)
損失函數(shù)優(yōu)化器
# 定義損失函數(shù)和優(yōu)化器
net=Resnet50(10);
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
epoch = 5
net = net.to(device)
total_step = len(train_loader)
train_all_loss = []
val_all_loss = []
訓(xùn)練
import numpy as np
for i in range(epoch):
net.train()
train_total_loss = 0
train_total_num = 0
train_total_correct = 0
for iter, (images,labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
outputs = net(images)
loss = criterion(outputs,labels)
train_total_correct += (outputs.argmax(1) == labels).sum().item()
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_total_num += labels.shape[0]
train_total_loss += loss.item()
print("Epoch [{}/{}], Iter [{}/{}], train_loss:{:4f}".format(i+1,epoch,iter+1,total_step,loss.item()/labels.shape[0]))
net.eval()
test_total_loss = 0
test_total_correct = 0
test_total_num = 0
for iter,(images,labels) in enumerate(test_loader):
images = images.to(device)
labels = labels.to(device)
outputs = net(images)
loss = criterion(outputs,labels)
test_total_correct += (outputs.argmax(1) == labels).sum().item()
test_total_loss += loss.item()
test_total_num += labels.shape[0]
print("Epoch [{}/{}], train_loss:{:.4f}, train_acc:{:.4f}%, test_loss:{:.4f}, test_acc:{:.4f}%".format(
i+1, epoch, train_total_loss / train_total_num, train_total_correct / train_total_num * 100, test_total_loss / test_total_num, test_total_correct / test_total_num * 100
))
train_all_loss.append(np.round(train_total_loss / train_total_num,4))
val_all_loss.append(np.round(test_total_loss / test_total_num,4))
可視化
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
plt.figure()
plt.title("Train Loss and Test Loss Curve")
plt.xlabel('plot_epoch')
plt.ylabel('loss')
plt.plot(train_all_loss)
plt.plot(val_all_loss)
plt.legend(['train loss', 'test loss'])
到了這里,關(guān)于現(xiàn)代卷積神經(jīng)網(wǎng)絡(luò)(ResNet)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!