一、引言:重新思考CNN架构

在2014年之前,深度卷积神经网络的发展主要遵循一个简单直接的路径:增加网络深度和宽度。AlexNet的成功证明了深度网络的优势,随后出现的ZFNet、VGG等模型不断加深网络层数,扩大滤波器数量。但这种粗暴的规模扩展带来了两个严峻问题:

  1. 计算复杂度爆炸式增长:更多参数意味着更多计算量,特别是链式卷积层中,滤波器数量的增加会使计算量呈平方级增长
  2. 过拟合风险显著增加:大规模参数需要大量标注数据,但高质量标注数据(如ImageNet)的获取成本极高

正是在这样的背景下,Google Research团队提出了Inception V1架构(代号GoogLeNet),在2014年ImageNet挑战赛中以top-5错误率6.67%的优异成绩夺冠,同时参数量仅有AlexNet的1/12,实现了精度与效率的双重突破

二、Inception V1

视觉信息天然具有多尺度特性——图像中包含从细微纹理到宏观结构的各种尺度特征。目标在图像中的大小占比总是存在着很大的变化

在这里插入图片描述

传统单尺度卷积核存在明显局限:

  1. ​小卷积核​(如1×1):只能捕获局部特征,缺乏上下文信息 ​
  2. 大卷积核​(如5×5):计算成本高,容易过度平滑细节
  3. 固定尺度​:无法自适应处理不同大小的目标物体

多尺度卷积核通过并行处理机制完美解决了这一困境。

2.1 初始设计(Naive Version)

最初的想法很简单:让网络在每个层级"自主选择"合适的感受野大小。原始Inception模块并行使用多种尺寸卷积核:

  • 1×1卷积(捕获局部特征)
  • 3×3卷积(捕获中等区域特征)
  • 5×5卷积(捕获更大区域特征)
  • 3×3最大池化(提供空间不变性)

所有输出被拼接形成最终输出,让下一层决定如何使用这些多尺度特征。

2.2 优化设计(加入降维)

原始设计存在严重计算瓶颈:5×5卷积在滤波器数量较大时计算成本极高,且池化层会保留所有通道数。解决方案是引入1×1卷积bottleneck层

  • 在3×3、5×5卷积前增加1×1卷积进行降维(减少输入通道数)
  • 在池化层后增加1×1卷积调整通道数
  • 1×1卷积同时充当线性整流激活函数,增加非线性能力

这种设计使计算量降至原来的1/10,同时保留了多尺度特征提取能力。1×1卷积在这里扮演了维度缩减器特征变换器的双重角色。
在这里插入图片描述

2.3 代码实现

import torch
import torch.nn as nn


class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(
            in_channels,
            out_channels,
            kernel_size,
            bias=False,
            **kwargs
        )
        self.bn = nn.BatchNorm2d(out_channels, eps=0.001)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x


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

        # 分支1: 1x1卷积(无降维)
        self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)

        # 分支2: 1x1降维 -> 3x3卷积
        self.branch2 = nn.Sequential(
            BasicConv2d(in_channels, ch3x3red, kernel_size=1),  # 降维层
            BasicConv2d(ch3x3red, ch3x3, kernel_size=3, padding=1)
        )

        # 分支3: 1x1降维 -> 5x5卷积
        self.branch3 = nn.Sequential(
            BasicConv2d(in_channels, ch5x5red, kernel_size=1),  # 降维层
            BasicConv2d(ch5x5red, ch5x5, kernel_size=5, padding=2)
        )

        # 分支4: 3x3池化 -> 1x1降维
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            BasicConv2d(in_channels, pool_proj, kernel_size=1)  # 降维层
        )

    def forward(self, x):
        print(f"OptimizedInceptionModule input shape: {x.shape}\n")  # 打印输入形状

        # 分支1
        branch1 = self.branch1(x)
        print(f"Branch 1 input shape: {x.shape}")  # 输入形状
        print(f"Branch 1 output shape: {branch1.shape}\n")  # 输出形状

        # 分支2
        branch2_reduced = self.branch2[0](x)  # 降维后的输出
        branch2 = self.branch2[1](branch2_reduced)  # 3x3卷积输出
        print(f"Branch 2 input shape: {x.shape}")  # 输入形状
        print(f"Branch 2 reduced shape: {branch2_reduced.shape}")  # 降维后的输出形状
        print(f"Branch 2 output shape: {branch2.shape}\n")  # 输出形状

        # 分支3
        branch3_reduced = self.branch3[0](x)  # 降维后的输出
        branch3 = self.branch3[1](branch3_reduced)  # 5x5卷积输出
        print(f"Branch 3 input shape: {x.shape}")  # 输入形状
        print(f"Branch 3 reduced shape: {branch3_reduced.shape}")  # 降维后的输出形状
        print(f"Branch 3 output shape: {branch3.shape}\n")  # 输出形状

        # 分支4
        branch4 = self.branch4(x)
        print(f"Branch 4 input shape: {x.shape}")  # 输入形状
        print(f"Branch 4 output shape: {branch4.shape}\n")  # 输出形状

        output = torch.cat([branch1, branch2, branch3, branch4], 1)
        print(f"OptimizedInceptionModule output shape: {output.shape}\n")  # 打印输出形状
        return output


class FirstInceptionModule(nn.Module):
    def __init__(self, in_channels):
        super(FirstInceptionModule, self).__init__()
        self.module = OptimizedInceptionModule(
            in_channels,
            ch1x1=64,
            ch3x3red=96,
            ch3x3=128,
            ch5x5red=16,
            ch5x5=32,
            pool_proj=32 
        )

    def forward(self, x):
        return self.module(x)


if __name__ == "__main__":
    in_channels = 192
    input_tensor = torch.randn(1, in_channels, 28, 28)
    first_module = FirstInceptionModule(in_channels)
    first_output = first_module(input_tensor)
    print(f"输入形状: {input_tensor.shape}")
    print(f"第一个Inception模块输出形状: {first_output.shape}")

OptimizedInceptionModule input shape: torch.Size([1, 192, 28, 28])

Branch 1 input shape: torch.Size([1, 192, 28, 28])
Branch 1 output shape: torch.Size([1, 64, 28, 28])

Branch 2 input shape: torch.Size([1, 192, 28, 28])
Branch 2 reduced shape: torch.Size([1, 96, 28, 28])
Branch 2 output shape: torch.Size([1, 128, 28, 28])

Branch 3 input shape: torch.Size([1, 192, 28, 28])
Branch 3 reduced shape: torch.Size([1, 16, 28, 28])
Branch 3 output shape: torch.Size([1, 32, 28, 28])

Branch 4 input shape: torch.Size([1, 192, 28, 28])
Branch 4 output shape: torch.Size([1, 32, 28, 28])

OptimizedInceptionModule output shape: torch.Size([1, 256, 28, 28])

输入形状: torch.Size([1, 192, 28, 28])
第一个Inception模块输出形状: torch.Size([1, 256, 28, 28])

三、Inception V2

Inception V2(也称为BN-Inception)是Google研究团队对原始Inception架构(GoogLeNet)的重要改进,旨在提高计算效率、减少参数数量并增强泛化能力。

3.1 卷积网络架构通用设计原则

  1. 避免表征瓶颈,尤其是在网络早期。 前馈网络可以表示为一个从输入层到分类器或回归器的有向无环图。这为信息流定义了一个明确的方向。对于任何将输入与输出分开的切割,都可以访问通过该切割的信息量。应避免极端压缩的瓶颈。通常,表征大小应从输入到输出平缓减小,然后才达到用于当前任务的最终表征。从理论上讲,信息内容不能仅通过表征的维度来评估,因为它丢弃了重要的因素(如相关结构);维度仅提供了信息内容的粗略估计。
  2. 高维表征在网络内更容易进行局部处理。 增加卷积网络中每个局部区域(tile)的激活量可以实现更多解耦的特征。由此产生的网络训练速度更快。
  3. 空间聚合可以在较低维度的嵌入上完成,而不会造成表征能力的太多或任何损失。 例如,在执行更扩散的(例如3×3)卷积之前,可以在空间聚合之前减少输入表征的维度,而不会预期产生严重的负面影响。我们假设其原因是,相邻单元之间的强相关性导致在维度缩减期间信息损失更少,如果输出是在空间聚合的上下文中使用。鉴于这些信号应该易于压缩,维度缩减甚至能促进更快的学习。
  4. 平衡网络的宽度和深度。 网络的最佳性能可以通过平衡每个阶段的滤波器数量和网络的深度来实现。同时增加网络的宽度和深度有助于构建更高质量的网络。然而,如果并行增加两者,可以在恒定计算量下达到最佳的改进效果。因此,计算预算应该在网络的深度和宽度之间以平衡的方式分配。

3.2 Inception V2 技术改进

大卷积核分解为小卷积核​:将大卷积核分解为多个小卷积核(如用两个3×3卷积替代一个5×5卷积)。
在这里插入图片描述

空间分解为非对称卷积​

在这里插入图片描述
扩展滤波器组输出:在原有的4个分支基础上,​增加了额外的并行路径​。
在这里插入图片描述
批归一化(Batch Normalization)的引入

  • 在每个卷积层后添加批归一化层
  • 标准化每层的输入分布,加速收敛
  • 允许使用更高的学习率,提升训练稳定性
class BNConv2d(nn.Module):
    """带批归一化的卷积块"""
    def __init__(self, in_channels, out_channels, kernel_size, **kwargs):
        super(BNConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, **kwargs)
        self.bn = nn.BatchNorm2d(out_channels, eps=0.001)
        self.relu = nn.ReLU(inplace=True)
    
    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

3.3 Inception V2架构

在这里插入图片描述

四、Inception V3

4.1 Inception V2 技术改进

V3 相比 V2 网络结构几乎一致,增加了下述改进:

使用 RMSProp 参数更新器:RMSProp 的核心是通过指数加权移动平均来调整每个参数的学习率
E[g2]t=ρ∗E[g2]t−1+(1−ρ)∗gt2E[g²]_t = ρ * E[g²]_{t-1} + (1 - ρ) * g_t²E[g2]t=ρE[g2]t1+(1ρ)gt2
θt+1=θt−(η/√(E[g2]t+ε))∗gtθ_{t+1} = θ_t - (η / √(E[g²]_t + ε)) * g_tθt+1=θt(η/√(E[g2]t+ε))gt

其中:

  • E[g2]tE[g²]_tE[g2]t:梯度平方的指数移动平均值
  • ρρρ:衰减率(通常 0.9),控制历史信息的保留程度
  • ηηη:全局学习率
  • εεε:小常数(通常 1e-8),防止除零错误
  • gtg_tgt:当前时间步的梯度

标签平滑正则化(Label Smoothing):通过在训练期间估计标签丢弃(label-dropout)的边际效应来正则化分类器层。其核心目的是防止模型对训练数据过度自信,从而改善泛化能力。传统分类任务中,标签通常采用one-hot编码,这会导致模型学习过程中过度拟合训练样本,对正确类别的预测概率接近1,而对错误类别的预测概率接近0。LSR通过软化标签分布,引入不确定性,使模型训练更加稳健。

对于真实标签 yyy,传统one-hot编码的分布为:
q(k∣x)=δk,y={1if k=y0if k≠yq(k|x) = \delta_{k,y} = \begin{cases} 1 & \text{if } k = y \\ 0 & \text{if } k \neq y \end{cases}q(kx)=δk,y={10if k=yif k=y
其中δk,y\delta_{k,y}δk,yKronecker delta函数。

LSR将原始分布替换为混合分布:
q′(k∣x)=(1−ε)⋅δk,y+ε⋅u(k)q'(k|x) = (1 - \varepsilon) \cdot \delta_{k,y} + \varepsilon \cdot u(k)q(kx)=(1ε)δk,y+εu(k)
其中:

  • ε\varepsilonε:平滑参数(通常设为0.1),控制平滑程度
  • u(k)u(k)u(k):先验分布,通常使用均匀分布u(k)=1Ku(k) = \frac{1}{K}u(k)=K1KKK为类别数)

交叉熵损失随之变为:
H(q′,p)=−∑k=1Klog⁡p(k)⋅q′(k)=(1−ε)H(q,p)+εH(u,p)H(q', p) = -\sum_{k=1}^{K} \log p(k) \cdot q'(k) = (1-\varepsilon) H(q, p) + \varepsilon H(u, p)H(q,p)=k=1Klogp(k)q(k)=(1ε)H(q,p)+εH(u,p)
这等价于同时优化两个目标:

  1. 传统交叉熵损失H(q,p)H(q, p)H(q,p)
  2. 预测分布与先验分布间的交叉熵H(u,p)H(u, p)H(u,p)

梯度形式变为:
∂ℓ∂zk=p(k)−q′(k)\frac{\partial \ell}{\partial z_k} = p(k) - q'(k)zk=p(k)q(k)
由于q′(k)q'(k)q(k)不再极端(所有类别都有非零概率),梯度更新更加温和,防止logits差异过大。

4.2 Inception V3架构

在 PyTorch 中,Inception V3 模型可以通过 torchvision 库轻松访问。torchvision 提供了预训练的 Inception V3 模型,可以直接使用它进行图像分类任务。

import torch
import torchvision.transforms as transforms
from torchvision import models
from torchvision.models import Inception_V3_Weights
from PIL import Image

# 加载预训练的 Inception V3 模型(使用最新权重)
model = models.inception_v3(weights=Inception_V3_Weights.DEFAULT)
model.eval()  # 设置模型为评估模式

# 正确的图像预处理(Inception V3 需要 299x299 输入)
preprocess = transforms.Compose([
    transforms.Resize(299),  # 调整到 299px
    transforms.CenterCrop(299),  # 中心裁剪到 299x299
    transforms.ToTensor(),  # 转换为张量
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),  # ImageNet 标准化
])

# 加载并预处理图像
img_path = 'images.jpeg'  # 替换为您的图像路径
image = Image.open(img_path).convert('RGB')  # 确保 RGB 格式
input_tensor = preprocess(image)
input_batch = input_tensor.unsqueeze(0)  # 增加批次维度

# 使用 GPU 加速(如果可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
input_batch = input_batch.to(device)

# 进行推理
with torch.no_grad():
    output = model(input_batch)

# 获取预测结果
probabilities = torch.nn.functional.softmax(output[0], dim=0)  # 转换为概率
top5_prob, top5_indices = torch.topk(probabilities, 5)

# 打印 top-5 预测
print("Top-5 predictions:")
for i in range(5):
    print(f"{top5_indices[i].item()}: {top5_prob[i].item() * 100:.2f}%")

Top-5 predictions:
167: 77.05%
162: 10.93%
166: 5.33%
163: 0.60%
188: 0.24%

在这里插入图片描述

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐