VGG塊的組成規(guī)律是:連續(xù)使?數(shù)個(gè)相同的填充為1、窗口形狀為3 ×3的卷積層后接上?個(gè)步幅為2、窗口形狀為2 ×2的最?池化層。卷積層保持輸?的?和寬不變,而池化層則對(duì)其減半。

從李沐大神的《動(dòng)手學(xué)深度學(xué)習(xí)》中有提到,VGG塊的實(shí)現(xiàn)為:

import d2lzh as d2l
from mxnet import gluon, init, nd
from mxnet.gluon import nn

def vgg_block(num_convs, num_channels):
blk = nn.Sequential()
for _ in range(num_convs):
blk.add(nn.Conv2D(num_channels, kernel_size=3, padding=1, activation='relu'))
blk.add(nn.MaxPool2D(pool_size=2, strides=2))
return blk

而我們經(jīng)常用torch的話,可以這樣實(shí)現(xiàn):

import torch
import torch.nn as nn

def vgg_block(in_channels, out_channels, num_convs, kernel_size=3, stride=1, padding=1):

layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding))
layers.append(nn.ReLU(inplace=True))
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
return nn.Sequential(*layers)

# 創(chuàng)建一個(gè)包含2個(gè)卷積層的VGG塊
vgg_block_example = vgg_block(in_channels=64, out_channels=128, num_convs=2)

# 打印VGG塊的結(jié)構(gòu)
print(vgg_block_example)

而VGG網(wǎng)絡(luò)則是通過(guò)多個(gè) VGG 塊堆疊而成,常見的結(jié)構(gòu)是 VGG-16 和 VGG-19,分別表示包含 16 和 19 層可訓(xùn)練參數(shù)的網(wǎng)絡(luò)。

VGG16的網(wǎng)絡(luò)結(jié)構(gòu)如圖所示:

網(wǎng)絡(luò)的具體設(shè)計(jì)如下:

VGGNet-16由13個(gè)卷積層和3個(gè)全連接層組成。下面我會(huì)列出每一層的具體信息,可能有點(diǎn)長(zhǎng),這個(gè)其實(shí)作為了解就夠了:

第一層卷積的輸入圖像大小為224×224×3,使用64個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為224×224×64,接著應(yīng)用ReLU激活函數(shù)。

第二層卷積的輸入為224×224×64,使用64個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為224×224×64,再應(yīng)用ReLU激活函數(shù),隨后進(jìn)行最大池化,使用2×2大小的池化核、步長(zhǎng)為2、填充為0,最終輸出112×112×64。

第三層卷積的輸入為112×112×64,使用128個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為112×112×128,隨后應(yīng)用ReLU激活函數(shù)。

第四層卷積的輸入為112×112×128,使用128個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為112×112×128,應(yīng)用ReLU后進(jìn)行最大池化,池化核大小為2×2、步長(zhǎng)為2、填充為0,最終輸出56×56×128。

第五層卷積的輸入為56×56×128,使用256個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為56×56×256,隨后應(yīng)用ReLU激活函數(shù)。

第六層卷積的輸入為56×56×256,使用256個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為56×56×256,再應(yīng)用ReLU激活函數(shù)。

第七層卷積的輸入為56×56×256,使用256個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為56×56×256,經(jīng)過(guò)ReLU激活后進(jìn)行最大池化,池化核大小為2×2、步長(zhǎng)為2、填充為0,最終輸出28×28×256。

第八層卷積的輸入為28×28×256,使用512個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為28×28×512,隨后應(yīng)用ReLU激活函數(shù)。

第九層卷積的輸入為28×28×512,使用512個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為28×28×512,接著應(yīng)用ReLU激活函數(shù)。

第十層卷積的輸入為28×28×512,使用512個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為28×28×512,應(yīng)用ReLU后進(jìn)行最大池化,池化核大小為2×2、步長(zhǎng)為2、填充為0,最終輸出14×14×512。

第十一層卷積的輸入為14×14×512,使用512個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為14×14×512,隨后應(yīng)用ReLU激活函數(shù)。

第十二層卷積的輸入為14×14×512,使用512個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為14×14×512,再應(yīng)用ReLU激活函數(shù)。

第十三層卷積的輸入為14×14×512,使用512個(gè)大小為3×3、步長(zhǎng)為1、填充為1的卷積核,輸出特征圖大小為14×14×512,應(yīng)用ReLU后進(jìn)行最大池化,池化核大小為2×2、步長(zhǎng)為2、填充為0,最終輸出7×7×512。

VGG16中的13個(gè)卷積層均采用大小為3×3、步長(zhǎng)為1、填充為1的卷積核,而5次最大池化操作均使用大小為2×2、步長(zhǎng)為2、填充為0的池化核。

VGG 的顯著特點(diǎn)是結(jié)構(gòu)簡(jiǎn)單,所有卷積層的參數(shù)大小都相同。這種一致性讓它易于理解和實(shí)現(xiàn)。

2 VGG網(wǎng)絡(luò)的實(shí)現(xiàn)

接下來(lái)我基于torch來(lái)實(shí)現(xiàn)VGG網(wǎng)絡(luò):

import torch
import torch.nn as nn

# 定義VGG16和VGG19的配置,數(shù)字代表輸出通道,M代表池化層
cfgs = {
'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

# 添加模型層
def make_layers(cfg, batch_norm=False):
layers = []
in_channels = 3
for v in cfg:
if v == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
else:
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
if batch_norm:
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers += [conv2d, nn.ReLU(inplace=True)]
in_channels = v
return nn.Sequential(*layers)

# 定義VGG模型
class VGG(nn.Module):
def __init__(self, features, num_classes=1000):
super(VGG, self).__init__()
self.features = features
self.classifier = nn.Sequential(
nn.Linear(512 * 7 * 7, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
nn.Dropout(),
nn.Linear(4096, num_classes),
)

def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x

# 創(chuàng)建VGG16和VGG19模型實(shí)例
def vgg_model(model_name='VGG16', num_classes=1000, batch_norm=False):
cfg = cfgs[model_name]
model = VGG(make_layers(cfg, batch_norm=batch_norm), num_classes=num_classes)
return model

# 創(chuàng)建VGG16模型實(shí)例
model_vgg16 = vgg_model('VGG16')
print(model_vgg16)

# 創(chuàng)建VGG19模型實(shí)例
model_vgg19 = vgg_model('VGG19')
print(model_vgg19)

在訓(xùn)練 VGG 網(wǎng)絡(luò)時(shí),有幾個(gè)常用的處理方式:

  1. 1.?數(shù)據(jù)預(yù)處理
transform = transforms.Compose([
transforms.RandomResizedCrop(224), # 隨機(jī)裁剪到224x224
transforms.RandomHorizontalFlip(), # 隨機(jī)水平翻轉(zhuǎn)
transforms.ToTensor(), # 將圖像轉(zhuǎn)換為張量
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), # 歸一化處理
])
  1. 1. 訓(xùn)練技巧:
  2. 2. 硬件需求:

訓(xùn)練代碼可以參考我下面的這部分代碼:

# 加載數(shù)據(jù)集
train_dataset = ImageFolder(root='./data/train', transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4)

val_dataset = ImageFolder(root='./data/val', transform=transform)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=4)

# 初始化模型、損失函數(shù)和優(yōu)化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = vgg_model(model_name='VGG16', num_classes=1000).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

# 定義訓(xùn)練函數(shù)
def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs=25, device='cuda'):
since = time.time()
best_acc = 0.0
writer = SummaryWriter()

for epoch in range(num_epochs):
print(f'Epoch {epoch}/{num_epochs - 1}')
print('-' * 10)

# 訓(xùn)練階段:計(jì)算損失、反向傳播、更新權(quán)重。
model.train()
running_loss = 0.0
running_corrects = 0

for inputs, labels in train_loader:
inputs = inputs.to(device)
labels = labels.to(device)

optimizer.zero_grad()

with torch.set_grad_enabled(True):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)

loss.backward()
optimizer.step()

running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)

epoch_loss = running_loss / len(train_loader.dataset)
epoch_acc = running_corrects.double() / len(train_loader.dataset)

writer.add_scalar('Loss/train', epoch_loss, epoch)
writer.add_scalar('Accuracy/train', epoch_acc, epoch)

print(f'Train Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')

# 驗(yàn)證階段:計(jì)算驗(yàn)證集上的損失和準(zhǔn)確率。
model.eval()
val_running_loss = 0.0
val_running_corrects = 0

for inputs, labels in val_loader:
inputs = inputs.to(device)
labels = labels.to(device)

with torch.set_grad_enabled(False):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)

val_running_loss += loss.item() * inputs.size(0)
val_running_corrects += torch.sum(preds == labels.data)

val_epoch_loss = val_running_loss / len(val_loader.dataset)
val_epoch_acc = val_running_corrects.double() / len(val_loader.dataset)

writer.add_scalar('Loss/val', val_epoch_loss, epoch)
writer.add_scalar('Accuracy/val', val_epoch_acc, epoch)

print(f'Val Loss: {val_epoch_loss:.4f} Acc: {val_epoch_acc:.4f}')

# 保存最佳模型
if val_epoch_acc > best_acc:
best_acc = val_epoch_acc
torch.save(model.state_dict(), 'best_model.pth')

print()

# 開始訓(xùn)練
train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs=25, device=device)

盡管 VGG 的性能不錯(cuò),但它的計(jì)算成本和存儲(chǔ)需求較高,并且現(xiàn)在有了性能更好的其他模型作為替代,VGG現(xiàn)在還是作為學(xué)習(xí)多了解即可。

網(wǎng)絡(luò)中的網(wǎng)絡(luò)(NiN)

1 NiN塊

NiN全名叫做Network in Network,通過(guò)引入全局思維解決了傳統(tǒng) CNN 模型中局部性強(qiáng)的問(wèn)題。NiN 塊的核心是用 1×1 卷積層替代全連接層:

在花書中,nin塊的實(shí)現(xiàn)是這樣的:

import d2lzh as d2l
from mxnet import gluon, init, nd
from mxnet.gluon import nn

def nin_block(num_channels, kernel_size, strides, padding):
blk = nn.Sequential()
blk.add(nn.Conv2D(num_channels, kernel_size,strides, padding, activation='relu'),
nn.Conv2D(num_channels, kernel_size=1, activation='relu'),
nn.Conv2D(num_channels, kernel_size=1, activation='relu'))
return blk

NiN 塊一般由三個(gè)主要部分組成:

  1. 1. 常規(guī)卷積層(提取特征)。
  2. 2. 1×1 卷積層(非線性組合)。
  3. 3. ReLU 激活函數(shù)(引入非線性)。

2 NiN模型

NiN 模型是由多個(gè) NiN 塊堆疊而成,通常在塊之間插入最大池化層來(lái)壓縮特征:

  1. 1. 全局平均池化(Global Average Pooling):
  2. 2. 網(wǎng)絡(luò)結(jié)構(gòu):

通過(guò)這種設(shè)計(jì),NiN 不僅提升了計(jì)算效率,還減輕了過(guò)擬合風(fēng)險(xiǎn)。

因此我們可以得到NiN模型的簡(jiǎn)單實(shí)現(xiàn)如下:

net = nn.Sequential()
net.add(nin_block(96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2D(pool_size=3, strides=2),
nin_block(256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2D(pool_size=3, strides=2),
nin_block(384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2D(pool_size=3, strides=2), nn.Dropout(0.5),
# 標(biāo)簽類別數(shù)是10
nin_block(10, kernel_size=3, strides=1, padding=1),
# 全局平均池化層將窗?形狀?動(dòng)設(shè)置成輸?的?和寬
nn.GlobalAvgPool2D(),
# 將四維的輸出轉(zhuǎn)成?維的輸出,其形狀為(批量??, 10)
nn.Flatten())

3 訓(xùn)練模型

訓(xùn)練 NiN 模型時(shí)與 VGG 相似,但 NiN 由于參數(shù)更少,對(duì)硬件要求稍低:

  1. 1. 數(shù)據(jù)預(yù)處理:
  2. 2. 訓(xùn)練注意事項(xiàng):
  3. 3. 適用場(chǎng)景:

NiN 的創(chuàng)新點(diǎn)在于將全局信息和局部信息結(jié)合,但受限于設(shè)計(jì)思想,NiN 的表達(dá)能力與后續(xù)更復(fù)雜的模型相比還是有所不足。當(dāng)然,作為經(jīng)典的網(wǎng)絡(luò)模型架構(gòu),還是值得我們一學(xué),作為了解即可

含并行連接的網(wǎng)絡(luò)(GoogLeNet)

1 Inception塊

GoogLeNet 的核心是 Inception 塊,通過(guò)多分支的并行計(jì)算從多種尺度提取特征。每個(gè) Inception 塊包括:

  1. 1. 多種卷積核:
  2. 2. 降維處理:
  3. 3. 最大池化分支:

通過(guò)以上設(shè)計(jì),Inception 塊實(shí)現(xiàn)了高效的多尺度特征提取。下面是使用torch對(duì)Inception塊的實(shí)現(xiàn):

class Inception(nn.Module):
def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
super(Inception, self).__init__()

# 1x1卷積路徑
self.branch1 = nn.Sequential(
nn.Conv2d(in_channels, ch1x1, kernel_size=1),
nn.ReLU(inplace=True)
)

# 1x1卷積 + 3x3卷積路徑
self.branch2 = nn.Sequential(
nn.Conv2d(in_channels, ch3x3red, kernel_size=1),
nn.ReLU(inplace=True),
nn.Conv2d(ch3x3red, ch3x3, kernel_size=3, padding=1),
nn.ReLU(inplace=True)
)

# 1x1卷積 + 5x5卷積路徑
self.branch3 = nn.Sequential(
nn.Conv2d(in_channels, ch5x5red, kernel_size=1),
nn.ReLU(inplace=True),
nn.Conv2d(ch5x5red, ch5x5, kernel_size=5, padding=2),
nn.ReLU(inplace=True)
)

# 3x3最大池化 + 1x1卷積路徑
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
nn.Conv2d(in_channels, pool_proj, kernel_size=1),
nn.ReLU(inplace=True)
)

def forward(self, x):
branch1 = self.branch1(x)
branch2 = self.branch2(x)
branch3 = self.branch3(x)
branch4 = self.branch4(x)

# 將四個(gè)分支的輸出在通道維度上拼接
outputs = [branch1, branch2, branch3, branch4]
return torch.cat(outputs, 1)

2 GoogLeNet模型

GoogLeNet 是由多個(gè) Inception 塊堆疊而成的深度網(wǎng)絡(luò),并結(jié)合一些特殊設(shè)計(jì):

  1. 1. 深度更深:
  2. 2. 輔助分類器:
  3. 3. 減少參數(shù):

因此googlenet的實(shí)現(xiàn)也比剛才提到的兩個(gè)模型架構(gòu)要稍微復(fù)雜一丟丟,用torch的實(shí)現(xiàn)方式如下:

class GoogLeNet(nn.Module):
def __init__(self, num_classes=1000):
super(GoogLeNet, self).__init__()

# 初始卷積層
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=1)
)

# 第二卷積層
self.conv2 = nn.Sequential(
nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=1),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)

# Inception模塊
self.inception3a = Inception(192, 64, 96, 128, 16, 32, 32)
self.inception3b = Inception(256, 128, 128, 192, 32, 96, 64)
self.maxpool3 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

self.inception4a = Inception(480, 192, 96, 208, 16, 48, 64)
self.inception4b = Inception(512, 160, 112, 224, 24, 64, 64)
self.inception4c = Inception(512, 128, 128, 256, 24, 64, 64)
self.inception4d = Inception(512, 112, 144, 288, 32, 64, 64)
self.inception4e = Inception(528, 256, 160, 320, 32, 128, 128)
self.maxpool4 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

self.inception5a = Inception(832, 256, 160, 320, 32, 128, 128)
self.inception5b = Inception(832, 384, 192, 384, 48, 128, 128)

# 輔助分類器
self.aux1 = nn.Sequential(
nn.AvgPool2d(kernel_size=5, stride=3),
nn.Conv2d(512, 128, kernel_size=1),
nn.ReLU(inplace=True),
nn.Flatten(),
nn.Linear(2048, 1024),
nn.ReLU(inplace=True),
nn.Dropout(0.7),
nn.Linear(1024, num_classes)
)

self.aux2 = nn.Sequential(
nn.AvgPool2d(kernel_size=5, stride=3),
nn.Conv2d(528, 128, kernel_size=1),
nn.ReLU(inplace=True),
nn.Flatten(),
nn.Linear(2048, 1024),
nn.ReLU(inplace=True),
nn.Dropout(0.7),
nn.Linear(1024, num_classes)
)

# 最終分類器
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.dropout = nn.Dropout(0.4)
self.fc = nn.Linear(1024, num_classes)

def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)

x = self.inception3a(x)
x = self.inception3b(x)
x = self.maxpool3(x)

x = self.inception4a(x)
if self.training:
aux1 = self.aux1(x)

x = self.inception4b(x)
x = self.inception4c(x)
x = self.inception4d(x)
if self.training:
aux2 = self.aux2(x)

x = self.inception4e(x)
x = self.maxpool4(x)

x = self.inception5a(x)
x = self.inception5b(x)

x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.dropout(x)
x = self.fc(x)

if self.training:
return x, aux1, aux2
else:
return x

model = GoogLeNet(num_classes=1000)
print(model)

看著很長(zhǎng),其實(shí)沒有那么復(fù)雜(你別忘了現(xiàn)在的模型可比這些復(fù)雜多了)。GoogLeNet 的訓(xùn)練過(guò)程更復(fù)雜,但效率較高:

  1. 1. 數(shù)據(jù)預(yù)處理:
  2. 2. 訓(xùn)練優(yōu)化:

盡管 GoogLeNet 的設(shè)計(jì)獨(dú)特,但其復(fù)雜性較高,后來(lái)被更現(xiàn)代的架構(gòu)(如 ResNet)所取代。

寫在最后

VGG、NiN 和 GoogLeNet 是深度學(xué)習(xí)發(fā)展過(guò)程中具有里程碑意義的模型。它們的設(shè)計(jì)理念各有側(cè)重:

這些網(wǎng)絡(luò)的誕生不僅提升了圖像分類的精度,還為后續(xù)的深度學(xué)習(xí)模型奠定了基礎(chǔ)。雖然現(xiàn)在有了更好的模型更好的架構(gòu),但是無(wú)論是學(xué)習(xí)經(jīng)典架構(gòu)還是設(shè)計(jì)新模型,理解這些網(wǎng)絡(luò)的設(shè)計(jì)思想都是至關(guān)重要的。

本文章轉(zhuǎn)載微信公眾號(hào)@Chal1ceAI

上一篇:

手把手教你申請(qǐng)Manus邀請(qǐng)碼

下一篇:

全網(wǎng)都在要Manus AI邀請(qǐng)碼,可能是 DeepSeek 后最大驚喜
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊(cè)

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場(chǎng)景實(shí)測(cè),選對(duì)API

#AI文本生成大模型API

對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

對(duì)比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個(gè)渠道
一鍵對(duì)比試用API 限時(shí)免費(fèi)