天天看點

深度學習7. 卷積的概念

深度學習7. 卷積的概念

一、卷積的概念

卷積來源于英文的Convolution,其中Con是積分,vol是轉、卷。

卷積是一種數學運算,常用于信号處理和圖像處理等領域,它用簡單的數學形式,描述了一個動态的過程。

卷積的定義如下(這個複雜的公式,在卷積神經網絡中可能是用不到):

設 f 和 g 是兩個定義在實數域上的函數,它們的卷積 f∗g 定義為:

深度學習7. 卷積的概念

其中 t 為實數,τ 是積分變量。

在離散形式下,如果 f 和 g 是兩個長度為 n 的向量,它們的卷積 f∗g 定義為:

深度學習7. 卷積的概念

其中 k 是整數,[i] 表示向量 f 的第 i 個元素。

二、神經網絡中的卷積

1. 神經網絡卷積概念

在卷積神經網絡中,卷積操作是一種特殊的線性變換,卷積核(也稱為濾波器)在輸入資料上進行滑動,每次計算與卷積核重疊部分的點乘和。

這樣的操作可以提取輸入資料的局部特征,實作特征的共享和抽象,進而使得網絡對輸入資料的變化更加魯棒和準确。

2. 卷積核

卷積核是一種可學習的濾波器,用于對輸入圖像進行特征提取。卷積核通常是一個小的二維矩陣,其大小通常為 k×k,其中 k 是一個正整數,稱為卷積核大小。卷積核的值通常是由神經網絡自動學習得到的。

卷積核的作用是提取輸入資料的局部特征。在卷積操作中,卷積核可以識别輸入圖像中的不同特征,例如邊緣、紋理、角落等,進而提取更加進階的特征表示。通過使用多個卷積核,可以提取不同類型的特征,形成更加複雜的特征表示,進而提高模型的性能。

不同的卷積核(即采用不同的二維矩陣)可以實作不同的效果,常見的卷積核有:

  • Sobel卷積核:邊緣檢測;
  • Scharr卷積核:也是邊緣檢測卷積核,比Sobel更加平滑;
  • Laplacian 卷積核:用于檢測圖像中的邊緣和角點,具有旋轉不變性和尺度不變性;
  • 高斯卷積核:用于圖像平滑,減少圖像中的噪聲和細節資訊;
  • 梯度卷積核:用于檢測圖像中的梯度資訊,如水準和垂直方向的梯度;
  • Prewitt 卷積核:用于檢測圖像中的邊緣資訊,與 Sobel 卷積核類似,但效果略差 ;
  • Roberts 卷積核:用于檢測圖像中的邊緣資訊,與 Sobel 卷積核類似,但計算速度更快,精度稍低;
  • LoG 卷積核:Laplacian of Gaussian 卷積核,是 Laplacian 卷積核和高斯卷積核的組合,用于檢測圖像中的邊緣和斑點。

3. 卷積核大小

卷積核的大小是卷積神經網絡中的一個超參數,通常與輸入資料的尺寸以及需要提取的特征的大小有關。在卷積神經網絡中,卷積核的大小通常比較小,例如常見的卷積核大小為 3 或 5,因為較小的卷積核可以更好地保留輸入圖像中的局部特征。

同時,卷積核的大小也需要根據卷積操作的步幅和填充等超參數進行選擇。在後面例子中,卷積核大小為 3,步幅為 1,填充為 1,即每次卷積操作會對輸入圖像中的 3 × 3 3\times33×3 的區域進行處理,并生成一個相同大小的卷積特征。填充的目的是為了保留輸入圖像的邊緣資訊,以避免在卷積操作中丢失像素。

需要注意的是,卷積核大小的選擇需要根據具體問題進行調整,通常需要通過實驗來确定最佳的超參數。

三、實作一個簡單的卷積功能

1. 卷積函數

import numpy as np
from PIL import Image

def convolve(image, kernel):
    # 擷取圖像和卷積核的大小
    image_rows, image_cols = image.shape
    kernel_rows, kernel_cols = kernel.shape

    # 計算輸出圖像的大小
    output_rows = image_rows - kernel_rows + 1
    output_cols = image_cols - kernel_cols + 1

    # 初始化輸出圖像矩陣,全零的矩陣
    output = np.zeros((output_rows, output_cols))

    # 執行卷積操作
    for row in range(output_rows):
        for col in range(output_cols):
            output[row, col] = np.sum(image[row:row + kernel_rows, col:col + kernel_cols] * kernel)

    return output
           

自定義的卷積函數接收兩個參數:

  • image: 輸入圖像
  • kernel: 卷積核

卷積使用 valid 卷積的方式,在進行卷積操作時,輸出圖像的尺寸會變小,計算公式是:

(image_rows - kernel_rows + 1, image_cols - kernel_cols + 1)

程式使用兩個嵌套的循環周遊輸出圖像的每個像素,并計算該像素對應的卷積結果。

np.sum函數中的參數 image 對輸入圖像進行切片,矩陣會進行逐元素相乘(Hadamard乘積或元素級乘積)。image[row:row + kernel_rows, col:col + kernel_cols] 和kernel的大小都是 kernel_rows x kernel_cols, 相乘結果傳回一個相同形狀的矩陣。

2. 邊緣檢測卷積核調用示例

# 加載圖像
img = np.array(Image.open('lena_gray.jpg').convert('L'))

# 定義卷積核
kernel = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]])

# 執行卷積操作
output = convolve(img, kernel)

# 儲存輸出圖像
output_img = Image.fromarray(np.uint8(output))
output_img.save('lena_gray_convolved.jpg')

           

示例的卷積核是一個簡單的邊緣檢測器,用于檢測圖像中的邊緣。

這裡加載一張灰階圖:

深度學習7. 卷積的概念

程式輸出結果如下 :

深度學習7. 卷積的概念

3. 高斯卷積核示例

# 加載圖像
img = np.array(Image.open('lena_gray.jpg').convert('L'))

# 定義卷積核
def gaussian_kernel(size, sigma):
    x, y = np.mgrid[-size:size+1, -size:size+1]
    g = np.exp(-((x**2 + y**2)/(2.0*sigma**2)))
    return g / g.sum()

kernel = gaussian_kernel(3, 1.5)

# 執行卷積操作
output = convolve(img, kernel)

# 儲存輸出圖像
output_img = Image.fromarray(np.uint8(output))
output_img.save('lena_gray_convolved.jpg')
           

輸出結果:

深度學習7. 卷積的概念

四、PyTorch計算卷積

1. 生成單通道圖像調用卷積

(1)生成單通道圖像torch.randn(1, 1, 28, 28)

下面用torch.randn(1, 1, 28, 28) 來生成随機數的 PyTorch 函數,它傳回一個大小為 (1, 1, 28, 28) 的張量。其中每個參數的具體含義如下:

  1. 第一個參數 1 表示生成的張量的 batch size(批大小)為 1。
  2. 第二個參數 1 表示生成的張量的通道數為 1(單通道圖像)。
  3. 第三個參數 28 表示生成的張量的高度為 28。
  4. 第四個參數 28 表示生成的張量的寬度為 28。

    torch.randn(1, 1, 28, 28) 傳回的張量可以看作是大小為 1x28x28 的單通道圖像,每個像素的值是從标準正态分布(均值為 0,方差為 1)中随機采樣得到的。

(2)卷積層

nn.Conv2d 是 PyTorch 中用于定義卷積層的類。

代碼nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, padding=1) 表示建立一個卷積層對象 conv_layer,參數的含義如下:

  1. in_channels=1 表示輸入通道數為 1,即輸入的是單通道的圖像。
  2. out_channels=16 表示輸出通道數為 16,即卷積核的數量為 16;卷積核的數量是一個經驗值,需要根據實際情況進行調整,并且會對模型的運作速度和記憶體占用等方面産生影響。過多的卷積核會導緻模型更加複雜,需要更多的計算和存儲資源,而過少的卷積核可能無法充分提取輸入資料的特征。。
  3. kernel_size=3 表示卷積核大小為 3×3。
  4. padding=1 表示在輸入的每個邊緣填充 1 個零。這樣做的目的是為了保持輸入輸出大小相同,即輸出特征圖的大小與輸入特征圖的大小相同。如果不進行填充操作,則卷積核會“越過”圖像的邊緣像素,進而導緻輸出特征圖的大小減小。

最終,可以通過調用 conv_layer(input_data) 來實作卷積操作,其中 input_data 是輸入的資料,卷積操作的結果将作為函數傳回值。

import torch
import torch.nn as nn

# 建立一個大小為 28*28 的單通道圖像
input_data = torch.randn(1, 1, 28, 28)  # 一個大小為28x28的單通道圖像

# 建立卷積層,輸入通道數為 1
# 輸出通道數16
# 卷積核大小3*3
# 1個0填充
conv_layer = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, padding=1)

# 對輸入資料進行卷積操作
output_data = conv_layer(input_data)

# 輸出結果
print(output_data.shape)  # (1, 16, 28, 28)

           

卷積後得到了 1個批次、16 個大小為 28 × 28 28\times2828×28 的特征圖。

2. 加載灰階圖像進行卷積操作

下面示例中,卷積結果 [batch_size, channel,height,width] 會進行降維操作,以便于可視化顯示。

最後會使用 Image.fromarray ,将數組轉為圖檔顯示出來。

import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

# 讀入示例圖檔
img = Image.open('lena_gray.jpg').convert('L')  # 将示例圖檔轉換為灰階圖
plt.imshow(img, cmap='gray')
plt.show()

# 将圖檔轉換為張量并增加一個次元作為批次次元
img_tensor = transforms.ToTensor()(img).unsqueeze(0)

# 建立卷積層,輸入通道數為 1,輸出通道數1,卷積核大小3*3,1個0填充
conv_layer = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1)

# 對輸入資料進行卷積操作
output_tensor = conv_layer(img_tensor)
print(output_tensor.shape)
# 輸出 torch.Size([1, 1, 426, 397])

# 将卷積結果轉換為numpy數組并移除批次次元
output_np = output_tensor.squeeze(0).squeeze(0).detach().numpy()
print(output_np.shape)

# 輸出 (426, 397)
# 将卷積結果轉換為灰階圖像
output_img = Image.fromarray(np.uint8(output_np * 255), mode='L')

# 将卷積結果儲存為圖檔
output_img.save('output.jpg')

# 使用Matplotlib庫展示卷積結果
output_mat = plt.imread('output.jpg')
plt.imshow(output_mat, cmap='gray')
plt.show()


           

輸出:

深度學習7. 卷積的概念

3. 對彩色圖檔卷積,輸出1通道

對彩色圖檔進行卷積,要把輸入通道數改為3,加載時選擇RGB:

import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

# 讀入示例彩色圖檔
img = Image.open('lena_color.png').convert('RGB')
plt.imshow(img)
plt.show()

# 将圖檔轉換為張量并增加一個次元作為批次次元
img_tensor = transforms.ToTensor()(img).unsqueeze(0)

# 建立卷積層,輸入通道數為 3,輸出通道數1,卷積核大小3*3,1個0填充
conv_layer = nn.Conv2d(in_channels=3, out_channels=1, kernel_size=3, padding=1)

# 對輸入資料進行卷積操作
output_tensor = conv_layer(img_tensor)
print(output_tensor.shape)

# 将卷積結果轉換為numpy數組并移除批次次元
#output_np = output_tensor.squeeze(0).squeeze(0).detach().numpy()
#print(output_np.shape)

# 将卷積結果轉換為灰階圖像
#output_img = Image.fromarray(np.uint8(output_np * 255), mode='L')
output_np = output_tensor.squeeze(0).detach().numpy()  # 形狀為 (C, H, W)
output_np = np.repeat(output_np, 3, axis=0)  # 将通道數由 1 改為 3
output_np = np.expand_dims(output_np, axis=1)  # 添加一個新的次元
output_img = transforms.ToPILImage()(output_np)  # 轉換為 PIL.Image 對象

# 将卷積結果儲存為圖檔
output_img.save('output.jpg')

# 使用Matplotlib庫展示卷積結果
output_mat = plt.imread('output.jpg')
plt.imshow(output_mat, cmap='gray')
plt.show()

           

輸入:

深度學習7. 卷積的概念

卷積結果:

深度學習7. 卷積的概念

4. 輸出3通道的卷積操作

import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

# 讀入示例圖檔
img = Image.open('lena_color.png').convert('RGB')
plt.imshow(img)
plt.show()

# 将圖檔轉換為張量并增加一個次元作為批次次元
img_tensor = transforms.ToTensor()(img).unsqueeze(0)

# 建立卷積層,輸入通道數為3,輸出通道數為3,卷積核大小3*3,1個0填充
conv_layer = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3, padding=1)

# 對輸入資料進行卷積操作
output_tensor = conv_layer(img_tensor)
# 這時的形狀是 torch.Size([1, 3, 726, 724])

# # 将卷積結果轉換為圖像
output_np = output_tensor.squeeze(0).detach().numpy()  # 形狀為 (C, H, W)
# 這時的形狀 (3, 726, 724)
output_np = np.transpose(output_np, (1, 2, 0))  # 轉置使得顔色通道在最後一個次元
# 這時的形狀 (726, 724, 3)
# 為了轉為圖像,下面要對資料處理,傳入的 numpy 數組中的資料類型不是 uint8 類型。
# 由于 transforms.ToPILImage() 隻支援 uint8 類型的資料,要把 float32 類型的 numpy 數組縮放到 0-255 的範圍,并轉換為 uint8 類型。 np.clip 函數和 np.uint8 來實作此功能
# 如果輸出1通道,ToPILImage會轉成unit8資料,但輸出3通道時候是轉成float32,需要自己加轉換
output_np = np.clip(output_np * 255, 0, 255).astype(np.uint8)
# 這時形狀沒有發生變化 (726, 724, 3)
output_img = transforms.ToPILImage()(output_np)  # 轉換為 PIL.Image 對象


# 展示卷積結果
plt.imshow(output_img)
plt.show()

           

輸出:

深度學習7. 卷積的概念

繼續閱讀