博客 / 詳情

返回

吳恩達深度學習課程四:計算機視覺 第一週:卷積基礎知識 課後習題和代碼代碼實踐

此分類用於記錄吳恩達深度學習課程的學習筆記。
課程相關信息鏈接如下:

  1. 原課程視頻鏈接:[雙語字幕]吳恩達深度學習deeplearning.ai
  2. github課程資料,含課件與筆記:吳恩達深度學習教學資料
  3. 課程配套練習(中英)與答案:吳恩達深度學習課後習題與答案

本篇為第四課第一週的課後習題和代碼實踐部分。


1. 理論習題

【中英】【吳恩達課後測驗】Course 4 -卷積神經網絡 - 第一週測驗
本週的題多是一些尺寸和參數量的計算,只要對公式和層級結構足夠熟練,就沒什麼大問題。
來看看這道可能容易混淆的題:

把下面這個過濾器應用到灰度圖像會怎麼樣?

\[\begin{bmatrix} 0 & 1 & -1 & 0\\ 1 & 3 & -3 & -1\\ 1 & 3 & -3 & -1\\ 0 & 1 & -1 & 0 \end{bmatrix} \]

答案:檢測豎直邊緣。

這道題乍一看可能會有些迷惑,但是觀察就會發現,左右數字對稱,符號相反
也就是説,如果應用這個過濾器,當對應區域左右像素接近時,結果就幾乎為0。但當左右像素出現較大差別時,結果的絕對值就會較大。
這就是豎直邊緣的邏輯,如果把整個矩陣旋轉 90 度,檢測的就是水平邊緣,只是二者的效果可能都沒有我們常用的邊緣檢測過濾器好。

2. 代碼實踐

吳恩達卷積神經網絡實戰
同樣,這位博主還是手工構建了卷積網絡中的各個組件,有興趣可以鏈接前往。

我們還是用 PyTorch 來進行演示,終於正式引入了卷積網絡,還是用貓狗二分類來看看卷積網絡在圖學習中的效果。
首先來看看 PyTorch 中如何定義卷積層

self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=0, stride=1)
# Conv2d 是指二維卷積,雖然圖片可以有多個通道,但它實際上還是二維的”紙片人“。
# Conv3d 便適用於視頻和 3D 圖片這樣的三維數據。
# 3,16 是指輸入和輸入的通道數,必須顯式指定。
# kernel_size=3 是卷積核尺寸,必須顯式指定。
# padding=0 ,0 就是padding 的默認值。
# stride 就是步長,1 就是步長的默認值。

再看看池化層

self.max_pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
# MaxPool2d 即為最大池化。
# 在池化層中,stride 默認和 kernel_size 相同。
# 池化層會自適應輸入通道數,因此不用顯示指定。
self.avg_pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
# 同理,AvgPool2d 就是平均池化。

現在,我們就來看看卷積網絡的使用效果。

2.1 卷積網絡 1.0

我們現在設計卷積網絡如下:

class SimpleCNN(nn.Module):  
    def __init__(self):  
        super().__init__()  
        # 卷積層  
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)  
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)  
        self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)  
        self.pool = nn.MaxPool2d(2, 2)  
        # 全連接層  
        self.fc1 = nn.Linear(64 * 16 * 16, 128)  
        self.fc2 = nn.Linear(128, 1)  
   
  
    def forward(self, x):  
        x = self.conv1(x)  
        x = F.relu(x)  # 另一種調用激活函數的方式
        x = self.pool(x)  
        x = self.conv2(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.conv3(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = torch.flatten(x, 1) # 進入全連接層前要先展平 
        x = self.fc1(x)  
        x = self.fc2(x)  
        x = torch.sigmoid(x)  
  
        return x

來看看運行結果如何:
image.png
可以看到,僅僅經過 20 輪訓練,訓練準確率就幾乎達到 100% ,但是驗證準確率卻仍在70%左右徘徊。
這是典型的過擬合現象:模型的學習能力很強,但是泛化能力不好。
經過前面的內容,我們已經瞭解了很多可以緩解過擬合現象的方法。現在,我們就開始一步步調試,緩解過擬合現象,增強模型的泛化能力。

2.2 卷積網絡 2.0:加入 Dropout

我們在正則化部分了解了可以通過應用 dropout 來緩解過擬合,現在就來看看效果。
如果你有些忘了什麼是dropout,它的第一次出現在這裏:dropout正則化
應用 dropout 後,我們更新網絡結構如下:

class SimpleCNN(nn.Module):  
    def __init__(self):  
        super().__init__()  
        # 卷積層  
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)  
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)  
        self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)  
        self.pool = nn.MaxPool2d(2, 2)  
        # 全連接層  
        self.fc1 = nn.Linear(64 * 16 * 16, 128)  
        self.fc2 = nn.Linear(128, 1)  
        # dropout
        self.dropout = nn.Dropout(p=0.3)  
  
    def forward(self, x):  
        x = self.conv1(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.dropout(x) # dropout  
        x = self.conv2(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.dropout(x) # dropout
        x = self.conv3(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.dropout(x) # dropout 
        x = torch.flatten(x, 1)  
        x = self.fc1(x)  
        x = self.dropout(x) # dropout 
        x = self.fc2(x)  
        x = torch.sigmoid(x)  
  
        return x

現在再來看看結果:
image.png

你會發現,dropout 確實有作用,增加訓練輪次為 30 輪,訓練集上的準確率上升的沒有那麼快了,確實和驗證準確率的差距更小了。
但問題是驗證準確率也沒上去啊! 原本的過擬合問題,現在變成了欠擬合問題:模型對數據的擬合能力不足,而且通過趨勢,會發現如果繼續訓練,仍存在過擬合風險。
那該怎麼辦呢?
我們知道,無論是過擬合還是欠擬合,我們希望提高模型性能,最直接的方法就是增加數據量。
我們先不急着上網找圖片,不如就先試試我們之前經常提到的數據增強,看看效果如何。

2.3 卷積網絡 3.0:進行數據增強

現在,我們要進行數據增強,那麼要修改的代碼內容就換到了預處理部分。
同樣,數據增強第一次出現在這裏:其他緩解過擬合的方法
現在,我們仍然保留上一步的 dropout 內容,修改預處理代碼如下:

transform = transforms.Compose([  
    transforms.Resize((128, 128)),  
    transforms.RandomHorizontalFlip(),# 圖片有 50% 可能水平翻轉    
    transforms.RandomRotation(10),  # 在 角度 -10° 到 10° 之間隨機旋轉圖像。 
    transforms.ToTensor(),  
    transforms.Normalize((0.5,), (0.5,))  
])

再再來看看效果如何:
image.png
不是,好像變化不大啊!
彆着急,之前我們一直在關注準確率,但是你會發現,在相同的訓練輪次下,損失仍在平穩下降,但是下降的更慢了,並沒有達到最開始過擬合的損失水平,比剛加入 dropout 時的損失還要高。
這説明:我們還沒有達到模型的上限。
現在,繼續維持其他內容不變,只增加訓練輪次,我們再來運行看看。
image

現在,把訓練輪次增加到100輪,就可以發現:驗證準確率有了一定的提升,但好像還是有過擬合風險,如果要繼續修改,可以選擇提高 dropout 的比率,也可以嘗試其他正則化。
簡單瞭解卷積層的性能後,我們就不在繼續調試了。
實際上,通過繼續修改網絡結構或者繼續增強數據,還可以讓模型有更多的提升,但就不在這裏演示了。
下一週的內容就是對現有的一些經典網絡結構的介紹,到時候,我們再來看看出色的網絡結構是什麼效果。

3.附錄

3.1 卷積網絡 3.0 pytorch代碼

import torch  
import torch.nn as nn  
import torch.nn.functional as F  
import torch.optim as optim  
from torchvision import datasets, transforms  
from torch.utils.data import DataLoader, random_split  
import matplotlib.pyplot as plt  
  
transform = transforms.Compose([  
    transforms.Resize((128, 128)),  
    transforms.RandomHorizontalFlip(),  
    transforms.RandomRotation(10),  
    transforms.ToTensor(),  
    transforms.Normalize((0.5,), (0.5,))  
])  
  
dataset = datasets.ImageFolder(root='./cat_dog', transform=transform)  
  
train_size = int(0.8 * len(dataset))  
val_size = int(0.1 * len(dataset))  
test_size = len(dataset) - train_size - val_size  
train_dataset, val_dataset, test_dataset = random_split(dataset, [train_size, val_size, test_size])  
  
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)  
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)  
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)  
  

class SimpleCNN(nn.Module):  
    def __init__(self):  
        super().__init__()  
        # 卷積層  
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)  
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)  
        self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)  
        self.pool = nn.MaxPool2d(2, 2)  
        # 全連接層  
        self.fc1 = nn.Linear(64 * 16 * 16, 128)  
        self.fc2 = nn.Linear(128, 1)  
        self.dropout = nn.Dropout(p=0.3)  
  
    def forward(self, x):  
        x = self.conv1(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.dropout(x)  
        x = self.conv2(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.dropout(x)  
        x = self.conv3(x)  
        x = F.relu(x)  
        x = self.pool(x)  
        x = self.dropout(x)  
        x = torch.flatten(x, 1)  
        x = self.fc1(x)  
        x = self.dropout(x)  
        x = self.fc2(x)  
        x = torch.sigmoid(x)  
  
        return x  
   
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  
model = SimpleCNN().to(device)  
criterion = nn.BCELoss()  
optimizer = optim.Adam(model.parameters(), lr=1e-3)  
  
epochs = 100  
train_losses = []  
train_accuracies = []   
val_accuracies = []  
  
for epoch in range(epochs):  
    model.train()  
    epoch_loss = 0  
    correct_train = 0  
    total_train = 0  
  
    for images, labels in train_loader:  
        images, labels = images.to(device), labels.to(device).float().unsqueeze(1)  
  
        outputs = model(images)  
        loss = criterion(outputs, labels)  
  
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step()  
  
        epoch_loss += loss.item()  
        preds = (outputs > 0.5).int()  
        correct_train += (preds == labels.int()).sum().item()  
        total_train += labels.size(0)  
  
    avg_loss = epoch_loss / len(train_loader)  
    train_acc = correct_train / total_train  
    train_losses.append(avg_loss)  
    train_accuracies.append(train_acc)   
  
    model.eval()  
    correct_val = 0  
    total_val = 0  
    with torch.no_grad():  
        for images, labels in val_loader:  
            images, labels = images.to(device), labels.to(device).float().unsqueeze(1)  
            outputs = model(images)  
            preds = (outputs > 0.5).int()  
            correct_val += (preds == labels.int()).sum().item()  
            total_val += labels.size(0)  
  
    val_acc = correct_val / total_val  
    val_accuracies.append(val_acc)  
  
    print(f"輪次: [{epoch+1}/{epochs}], 訓練損失: {avg_loss:.4f}, 訓練準確率: {train_acc:.4f}, 驗證準確率: {val_acc:.4f}")  
  
 
# 可視化  
plt.rcParams['font.sans-serif'] = ['SimHei']  
plt.rcParams['axes.unicode_minus'] = False  
  
plt.plot(train_losses, label='訓練損失')  
plt.plot(train_accuracies, label='訓練準確率')  
plt.plot(val_accuracies, label='驗證準確率')  
  
plt.title("訓練損失、訓練準確率、驗證準確率變化曲線")  
plt.xlabel("訓練輪次(Epoch)")  
plt.ylabel("數值")  
plt.legend()  
plt.grid(True)  
plt.show()  
  
# 最終測試,可忽略 
model.eval()  
correct_test = 0  
total_test = 0  
with torch.no_grad():  
    for images, labels in test_loader:  
        images, labels = images.to(device), labels.to(device).float().unsqueeze(1)  
        outputs = model(images)  
        preds = (outputs > 0.5).int()  
        correct_test += (preds == labels.int()).sum().item()  
        total_test += labels.size(0)  
  
test_acc = correct_test / total_test  
print(f"測試準確率: {test_acc:.4f}")

3.2 卷積網絡 3.0 TF版代碼

import tensorflow as tf  
from tensorflow.keras import layers, models  
import matplotlib.pyplot as plt  
  
img_size = (128, 128)  
batch_size = 32  
  
train_ds = tf.keras.preprocessing.image_dataset_from_directory(  
    './cat_dog',  
    validation_split=0.2,  
    subset="training",  
    seed=42,  
    image_size=img_size,  
    batch_size=batch_size  
)  
  
val_test_ds = tf.keras.preprocessing.image_dataset_from_directory(  
    './cat_dog',  
    validation_split=0.2,  
    subset="validation",  
    seed=42,  
    image_size=img_size,  
    batch_size=batch_size  
)  
  
val_size = int(0.5 * len(val_test_ds))  
val_ds = val_test_ds.take(val_size)  
test_ds = val_test_ds.skip(val_size)  
  
data_augmentation = tf.keras.Sequential([  
    layers.RandomFlip("horizontal"),  
    layers.RandomRotation(0.1)  
])  
  
normalization = layers.Rescaling(1/0.5, offset=-1)  
  
AUTOTUNE = tf.data.AUTOTUNE  
train_ds = train_ds.map(lambda x, y: (normalization(data_augmentation(x)), y)).prefetch(AUTOTUNE)  
val_ds = val_ds.map(lambda x, y: (normalization(x), y)).prefetch(AUTOTUNE)  
test_ds = test_ds.map(lambda x, y: (normalization(x), y)).prefetch(AUTOTUNE)  
train_ds = train_ds.cache().shuffle(1000).prefetch(tf.data.AUTOTUNE)  
val_ds = val_ds.cache().prefetch(tf.data.AUTOTUNE)  
  
model = models.Sequential([  
    layers.Conv2D(16, (3, 3), padding='same', activation='relu', input_shape=img_size + (3,)),  
    layers.MaxPooling2D(2, 2),  
    layers.Dropout(0.3),  
  
    layers.Conv2D(32, (3, 3), padding='same', activation='relu'),  
    layers.MaxPooling2D(2, 2),  
    layers.Dropout(0.3),  
  
    layers.Conv2D(64, (3, 3), padding='same', activation='relu'),  
    layers.MaxPooling2D(2, 2),  
    layers.Dropout(0.3),  
  
    layers.Flatten(),  
    layers.Dense(128, activation='relu'),  
    layers.Dense(1, activation='sigmoid')  
])  
  
model.summary()  
  
  
model.compile(  
    optimizer=tf.keras.optimizers.Adam(1e-3),  
    loss='binary_crossentropy',  
    metrics=['accuracy']  
)  
epochs = 100  
history = model.fit(  
    train_ds,  
    validation_data=val_ds,  
    epochs=epochs  
)  
  
plt.rcParams['font.sans-serif'] = ['SimHei']  
plt.rcParams['axes.unicode_minus'] = False  
  
plt.plot(history.history['loss'], label='訓練損失')  
plt.plot(history.history['accuracy'], label='訓練準確率')  
plt.plot(history.history['val_accuracy'], label='驗證準確率')  
  
plt.title("訓練損失、訓練準確率、驗證準確率變化曲線")  
plt.xlabel("訓練輪次(Epoch)")  
plt.ylabel("數值")  
plt.legend()  
plt.grid(True)  
plt.show()  
  
  
test_loss, test_acc = model.evaluate(test_ds)  
print(f"測試準確率: {test_acc:.4f}")
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.