MobileNetV2 的出現,為行動裝置的深度學習應用帶來新的可能性。其倒置殘差瓶頸設計,巧妙地利用深度可分離卷積和線性瓶頸,在有限的計算資源下達到高效的效能。相較於傳統的殘差結構,倒置殘差瓶頸先擴張再壓縮通道,保留更多特徵資訊,並減少計算量。而後續的 EfficientNet 則更進一步,透過神經架構搜尋技術,找出最佳的模型縮放比例,同時調整深度、寬度和解析度,在效能和效率之間取得更佳的平衡,成為輕量級模型發展的重要里程碑。

MobileNetV2:輕量級神經網路架構的革新

MobileNetV2 是由 Mark Sandler 等人於 2018 年提出的輕量級深度學習模型,主要針對行動裝置的運算資源限制進行最佳化。該架構的核心創新在於引入了「倒置殘差瓶頸(Inverted Residual Bottleneck)」結構,有效降低了模型引數量和運算延遲。

倒置殘差瓶頸結構的設計理念

與 ResNet 和 Xception 的殘差結構相比,MobileNetV2 的倒置殘差瓶頸結構具有以下特點:

  1. 深度可分離卷積(Depthwise Separable Convolution):將標準卷積層分解為深度卷積(dw-cnv)和逐點卷積(conv 1x1),大幅減少引數量。
  2. 擴張與壓縮通道設計:先使用 1x1 卷積擴張通道,再進行深度卷積,最後用 1x1 卷積壓縮通道。
  3. 線性瓶頸(Linear Bottleneck):在最後的 1x1 卷積後不使用非線性啟用函式,以保留低維空間中的資訊。
# 倒置殘差瓶頸範例程式碼
def inverted_residual_block(inputs, expansion_factor, num_filters, stride):
    expanded = tf.keras.layers.Conv2D(
        filters=inputs.shape[-1] * expansion_factor,
        kernel_size=1,
        activation='relu6'
    )(inputs)
    
    depthwise = tf.keras.layers.DepthwiseConv2D(
        kernel_size=3,
        strides=stride,
        activation='relu6',
        padding='same'
    )(expanded)
    
    projected = tf.keras.layers.Conv2D(
        filters=num_filters,
        kernel_size=1,
        activation=None  # 線性啟用
    )(depthwise)
    
    if stride == 1 and inputs.shape[-1] == num_filters:
        return tf.keras.layers.Add()([inputs, projected])
    return projected

內容解密:

  1. 擴張層:使用 1x1 卷積將輸入特徵圖擴張到更高維度,以捕捉更豐富的特徵。
  2. 深度卷積:對擴張後的特徵圖進行深度可分離卷積,減少計算量。
  3. 投影層:使用線性 1x1 卷積將特徵圖壓縮回低維空間,避免資訊損失。
  4. 殘差連線:當步長為 1 且輸入輸出通道數相同時,使用殘差連線以促進梯度傳播。

MobileNetV2 整體架構

MobileNetV2 由多個倒置殘差瓶頸塊堆積疊而成,其架構如圖 3-44 所示。主要特點包括:

  • 使用 ReLU6 作為啟用函式(後續版本改回標準 ReLU)。
  • 所有卷積層後接批次標準化(Batch Normalization)。
  • 最後一層卷積不使用啟用函式。

MobileNetV2 的優勢與侷限

| 模型 | 引數量 | ImageNet 精確度 | Flowers 104 F1 分數 | |


|


-|




-|





-| | MobileNetV2 | 230 萬 | 71% | 92% | | NASNetLarge | 8500 萬 | 82% | 89% | | DenseNet201 | 1800 萬 | 77% | 95% |

MobileNetV2 在保持較低引數量的同時犧牲了一定的精確度,但其簡單的結構非常適合行動裝置佈署。

從 MobileNetV2 到 EfficientNet

研究團隊進一步利用神經架構搜尋(Neural Architecture Search, NAS)最佳化 MobileNetV2,衍生出 MnasNet 和 EfficientNet 系列。EfficientNet 的主要創新在於:

  1. 多維度縮放:同時調整網路深度、寬度和輸入解析度,以實作最佳效能。
  2. 自動化搜尋:使用 NAS 自動最佳化網路結構,重新引入 5x5 卷積核提升效能。

此圖示展示了從 MobileNetV2 到 EfficientNet 的演進過程。

圖示解說:

  • MobileNetV2 是基礎架構。
  • MnasNet 是使用 NAS 對 MobileNetV2 的最佳化結果。
  • EfficientNet 系列進一步最佳化並擴充套件為不同大小的模型,以適應不同的計算資源需求。

EfficientNet 架構深度解析

EfficientNet 是一種根據倒置殘差瓶頸(inverted residual bottlenecks)序列的深度學習架構,主要用於影像分類別任務。其設計理念是透過複合縮放(compound scaling)方法,同時調整網路的深度、寬度和輸入影像的解析度,以達到最佳的效能。

架構特點

  • 倒置殘差瓶頸序列:EfficientNet 的核心是多個倒置殘差瓶頸塊的序列,每個序列由多個相同的塊組成。
  • 複合縮放:EfficientNet 透過同時縮放網路的深度、寬度和輸入影像的解析度,來提高模型的效能。
  • Squeeze-and-Excitation 最佳化:每個倒置殘差瓶頸塊都經過 Squeeze-and-Excitation 最佳化,以增強通道間的互動。
  • SiLU 啟用函式:EfficientNet 使用 SiLU(Swish-1)作為啟用函式,以提高模型的非線性表達能力。

引陣列態

EfficientNet 家族(B0-B7)具有相同的整體結構,但引陣列態不同。下表展示了不同 EfficientNet 模型的引陣列態:

模型名稱輸入影像大小深度寬度Dropout 率
EfficientNetB02241.01.00.2
EfficientNetB12401.11.00.2
EfficientNetB22601.21.10.3
EfficientNetB33001.41.20.3
EfficientNetB43801.81.40.4
EfficientNetB54562.21.60.4
EfficientNetB65282.61.80.5
EfficientNetB76003.12.00.5

程式碼範例:

import tensorflow as tf
from tensorflow.keras.applications import EfficientNetB0

# 載入 EfficientNetB0 模型
model = EfficientNetB0(weights='imagenet', include_top=False, input_shape=(224, 224, 3))

# 列印模型摘要
model.summary()

內容解密:

  • EfficientNetB0 是 EfficientNet 家族中最基礎的模型,輸入影像大小為 224x224。
  • weights='imagenet' 表示使用在 ImageNet 資料集上預訓練的權重。
  • include_top=False 表示不包含頂層的全連線層,以便於自定義輸出層。
  • input_shape=(224, 224, 3) 定義了輸入影像的形狀。

績效評估

EfficientNet 在 ImageNet 分類別任務上取得了最佳效能,最高準確率達到 84%。然而,在較小的資料集(如 Flowers-104)上,其效能與其他模型(如 Xception、DenseNet201 和 InceptionV3)相近,均達到約 95% 的精確率和召回率。

圖表說明

下圖展示了 EfficientNet 家族中不同模型的引陣列態和效能比較:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 輕量級深度學習模型 MobileNetV2 與 EfficientNet 演進

package "輕量級模型演進" {
    package "MobileNetV2" {
        component [倒置殘差瓶頸] as inverted
        component [深度可分離卷積] as depthwise
        component [線性瓶頸] as linear
    }

    package "EfficientNet" {
        component [神經架構搜尋] as nas
        component [多維度縮放] as scale
        component [複合係數] as compound
    }

    package "設計特點" {
        component [通道擴張壓縮] as channel
        component [ReLU6 啟用] as relu6
        component [殘差連線] as residual
    }
}

inverted --> depthwise : 減少計算
nas --> scale : 最佳比例
channel --> linear : 資訊保留

note bottom of inverted
  行動裝置專用
  低延遲設計
end note

collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型

note right of feature
  特徵工程包含:
  - 特徵選擇
  - 特徵轉換
  - 降維處理
end note

note right of eval
  評估指標:
  - 準確率/召回率
  - F1 Score
  - AUC-ROC
end note

@enduml

此圖示展示了 EfficientNet 家族中不同模型之間的縮放關係,從 B0 到 B7,模型的複雜度和效能逐漸提高。

視覺化Transformer架構於影像處理的應用

在影像處理領域中,Transformer架構的應用逐漸受到重視。不同於傳統的卷積神經網路(CNN),Transformer架構能夠有效地處理序列資料,因此在影像分類別任務中展現出潛力。

影像分塊與編碼

首先,將輸入的影像分割成多個小塊(patches),如圖3-49所示。這種方法將影像視為一個序列輸入,送入Transformer模型中進行處理。

patches = tf.image.extract_patches(
    images=images,
    sizes=[1, self.patch_size, self.patch_size, 1],
    strides=[1, self.patch_size, self.patch_size, 1],
    rates=[1, 1, 1, 1],
    padding="VALID",
)

內容解密:

  • tf.image.extract_patches函式用於從輸入影像中提取小塊。
  • sizes引數定義了每個小塊的大小。
  • strides引數控制了小塊之間的間隔。
  • 將影像分割成小塊後,每個小塊被視為序列中的一個元素。

小塊表示與位置編碼

每個小塊透過連線其畫素值和在影像中的位置來表示:

encoded = (tf.keras.layers.Dense(...)(patch) + 
           tf.keras.layers.Embedding(...)(position))

內容解密:

  • tf.keras.layers.Dense層用於對小塊畫素值進行線性變換。
  • tf.keras.layers.Embedding層用於對小塊的位置進行編碼,將位置視為類別變數。
  • 這種表示方法能夠捕捉小塊之間的相關性。

Transformer區塊處理

小塊表示被送入多個Transformer區塊,每個區塊包含一個注意力頭(attention head):

x1 = tf.keras.layers.LayerNormalization()(encoded)
attention_output = tf.keras.layers.MultiHeadAttention(
    num_heads=num_heads, key_dim=projection_dim, dropout=0.1
)(x1, x1)

內容解密:

  • tf.keras.layers.LayerNormalization層用於對輸入進行標準化。
  • tf.keras.layers.MultiHeadAttention層實作了多頭注意力機制,用於學習輸入的不同部分之間的相關性。

注意力輸出與殘差連線

注意力輸出被用於增強小塊表示:

x2 = tf.keras.layers.Add()([attention_output, encoded])
x3 = tf.keras.layers.LayerNormalization()(x2)

內容解密:

  • tf.keras.layers.Add層實作了殘差連線,將注意力輸出與原始輸入相加。
  • 這種設計能夠保留原始輸入的資訊。

多層感知機(MLP)處理

接著,將輸出送入多層感知機(MLP)進行進一步處理:

x3 = mlp(x3, hidden_units=transformer_units, dropout_rate=0.1)
encoded = tf.keras.layers.Add()([x3, x2])

內容解密:

  • mlp函式實作了多層感知機,用於對輸入進行非線性變換。
  • dropout_rate引數控制了丟棄率,用於防止過擬合。

模型選擇與效能比較

在選擇模型架構時,首先應使用無需編寫程式碼的服務來訓練機器學習模型,以確定在特定問題上可實作的準確率。Google Cloud AutoML、Microsoft Azure Custom Vision AI、DataRobot和H2O.ai等服務提供了神經架構搜尋(NAS)和遷移學習等技術,能夠快速實作影像分類別任務。

效能比較

微調模型效能比較

模型引數數量(不含分類別頭)ImageNet準確率104 flowers F1得分(微調)
EfficientNetB640M84%95.5%
EfficientNetB764M84%95.5%
DenseNet20118M77%95.4%
Xception21M79%94.6%
InceptionV322M78%94.6%
ResNet5023M75%94.1%
MobileNetV22.3M71%92%
NASNetLarge85M82%89%
VGG1920M71%88%
Ensemble79M(DenseNet201 + Xception + EfficientNetB6)-96.2%

從零開始訓練的模型效能比較

模型引數數量(不含分類別頭)ImageNet準確率104 flowers F1得分(從零開始訓練)
Xception21M79%82.6%
SqueezeNet(24層)2.7M-76.2%
DenseNet1217M75%76.1%
ResNet5023M75%73%
EfficientNetB418M83%69%
AlexNet3.7M60%39%

物件偵測與影像分割技術

在前面的章節中,我們已經探討了多種機器學習架構,但僅用於解決對整個影像進行分類別或迴歸的問題。本章將討論三種新的視覺問題:物件偵測(Object Detection)、例項分割(Instance Segmentation)和整體場景語義分割(Whole-Scene Semantic Segmentation)(如圖4-1所示)。其他更進階的視覺問題,如影像生成、計數、姿態估計和生成模型,將在第11和12章中討論。

物件偵測

物件偵測是一種在影像中識別和定位多個物件的技術。它不僅需要分類別影像中的物件,還需要確定物件的位置和邊界框。物件偵測在許多應用中非常重要,例如自動駕駛、監視系統和機器人視覺。

例項分割

例項分割是物件偵測的進一步擴充套件,它不僅需要識別和定位物件,還需要對每個物件例項進行畫素級別的分割。這意味著對於影像中的每個物件,例項分割需要輸出一個二值掩碼(Binary Mask),指示該物件的精確邊界。

整體場景語義分割

整體場景語義分割是一種將影像中的每個畫素分配給特定類別或標籤的技術。它與例項分割不同,因為它不需要區分同一類別的不同例項。語義分割對於理解影像的整體內容和結構非常重要,例如在自動駕駛中理解道路場景。

物件偵測技術

物件偵測技術可以大致分為兩類別:一階段(One-Stage)方法和兩階段(Two-Stage)方法。

一階段方法

一階段方法直接從影像中預測物件的類別和位置。典型的例子包括YOLO(You Only Look Once)和SSD(Single Shot Detector)。這些方法通常速度較快,但可能犧牲一些準確度。

YOLO範例程式碼

import cv2

# 載入YOLO模型
net = cv2.dnn.readNet("yolov3.weights", "yolov3.cfg")
classes = []
with open("coco.names", "r") as f:
    classes = [line.strip() for line in f.readlines()]

# 影像處理
img = cv2.imread("image.jpg")
height, width, _ = img.shape
blob = cv2.dnn.blobFromImage(img, 1/255, (416, 416), swapRB=True, crop=False)
net.setInput(blob)
outputs = net.forward(net.getUnconnectedOutLayersNames())

# 偵測結果處理
for output in outputs:
    for detection in output:
        scores = detection[5:]
        class_id = np.argmax(scores)
        confidence = scores[class_id]
        if confidence > 0.5:
            # 物件偵測結果
            center_x = int(detection[0] * width)
            center_y = int(detection[1] * height)
            w = int(detection[2] * width)
            h = int(detection[3] * height)
            x = int(center_x - w / 2)
            y = int(center_y - h / 2)
            cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
            cv2.putText(img, f"{classes[class_id]} {confidence:.2f}", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)

#### 程式碼解密:
1. 載入YOLO模型權重和組態檔案用於後續的物件偵測
2. 影像預處理將影像轉換為模型所需的輸入格式
3. 將預處理後的影像輸入模型取得偵測結果
4. 對偵測結果進行過濾選取信心度大於0.5的偵測框
5. 在原始影像上繪製偵測框和類別標籤

### 兩階段方法

兩階段方法首先生成候選區域Region Proposals),然後對這些區域進行分類別和邊界框迴歸典型的例子包括Faster R-CNN這些方法通常更準確但計算成本較高