快速學會一個算法,UNet
今天給大家分享一個強大的算法模型,U-Net
U-Net 是一種廣泛應用于圖像分割任務的卷積神經網絡(CNN)架構,最初由 Olaf Ronneberger 等人在 2015 年為生物醫學圖像分割而提出。
由于其出色的性能和靈活性,U-Net 現已廣泛應用于各種圖像分割領域,如醫學影像分析、遙感圖像處理等。
圖片
U-Net 的架構
U-Net 的核心思想是通過對稱的編碼器-解碼器結構,實現對輸入圖像的高效特征提取和精確的像素級分割。
UNet的架構由兩部分組成:
- 收縮路徑(編碼器)類似于傳統的卷積神經網絡,用于捕捉上下文信息。通過一系列卷積層和池化層逐步降低空間分辨率,同時增加特征通道數。
- 擴展路徑(解碼器)逐步恢復空間分辨率,結合編碼器中的高分辨率特征,通過上采樣和卷積操作生成精細的分割圖。
兩部分通過跳躍連接(Skip Connections)連接,允許高分辨率特征與解碼器中的對應層進行融合,從而提高分割的精確度。
編碼器
編碼器的主要作用是通過一系列的卷積層和池化層逐步提取圖像的特征,同時減少空間維度(下采樣)。
圖片
具體結構如下:
- 卷積層:通常采用多個 3×3 的卷積核,步幅為 1,無填充(padding)。
- 激活函數:通常使用 ReLU 激活函數。
- 池化層:采用 2×2 的最大池化(Max Pooling),步幅為 2,用于下采樣。
每經過一個下采樣步驟,特征圖的空間尺寸減半,通道數增加一倍,以捕捉更高級別的特征。
解碼器
解碼器的主要作用是通過上采樣逐步恢復圖像的空間分辨率,同時結合編碼器的特征進行精細的分割。
圖片
具體結構如下:
- 上采樣:通常采用轉置卷積將特征圖的空間尺寸放大一倍。
- 卷積層:與編碼器類似,使用 3×3 的卷積核。
- 激活函數:使用 ReLU 激活函數。
跳躍連接
跳躍連接是 U-Net 的關鍵設計,通過將編碼器中每個下采樣步驟的特征圖與解碼器中相應上采樣步驟的特征圖進行拼接,保留了高分辨率的信息,幫助解碼器更準確地定位和分割目標區域。
圖片
U-Net 的優勢
- 高效的特征利用通過跳躍連接,U-Net 能夠充分利用不同層次的特征信息,既包含了高層的語義信息,又保留了低層的空間信息,提高了分割的準確性。
- 對少量數據的有效利用U-Net 在設計上適合處理數據量較少的任務,尤其在生物醫學圖像處理中表現出色。
- 端到端訓練整個 U-Net 可以通過端到端的方式進行訓練,簡化了模型設計和優化過程。
案例分享
下面是一個使用 UNet 架構進行圖像分割的示例代碼。
該示例使用 TensorFlow 和 Keras 構建 UNet 模型,并在合成數據上進行訓練。
首先,我們來構建一個 Unet 模型。
import tensorflow as tf
from tensorflow.keras import layers, models
def unet_model(input_size=(128, 128, 3)):
inputs = layers.Input(input_size)
# 編碼器
c1 = layers.Conv2D(64, (3, 3), activatinotallow='relu', padding='same')(inputs)
c1 = layers.Conv2D(64, (3, 3), activatinotallow='relu', padding='same')(c1)
p1 = layers.MaxPooling2D((2, 2))(c1)
c2 = layers.Conv2D(128, (3, 3), activatinotallow='relu', padding='same')(p1)
c2 = layers.Conv2D(128, (3, 3), activatinotallow='relu', padding='same')(c2)
p2 = layers.MaxPooling2D((2, 2))(c2)
c3 = layers.Conv2D(256, (3, 3), activatinotallow='relu', padding='same')(p2)
c3 = layers.Conv2D(256, (3, 3), activatinotallow='relu', padding='same')(c3)
p3 = layers.MaxPooling2D((2, 2))(c3)
c4 = layers.Conv2D(512, (3, 3), activatinotallow='relu', padding='same')(p3)
c4 = layers.Conv2D(512, (3, 3), activatinotallow='relu', padding='same')(c4)
p4 = layers.MaxPooling2D(pool_size=(2, 2))(c4)
# 底部
c5 = layers.Conv2D(1024, (3, 3), activatinotallow='relu', padding='same')(p4)
c5 = layers.Conv2D(1024, (3, 3), activatinotallow='relu', padding='same')(c5)
# 解碼器
u6 = layers.Conv2DTranspose(512, (2, 2), strides=(2, 2), padding='same')(c5)
u6 = layers.concatenate([u6, c4])
c6 = layers.Conv2D(512, (3, 3), activatinotallow='relu', padding='same')(u6)
c6 = layers.Conv2D(512, (3, 3), activatinotallow='relu', padding='same')(c6)
u7 = layers.Conv2DTranspose(256, (2, 2), strides=(2, 2), padding='same')(c6)
u7 = layers.concatenate([u7, c3])
c7 = layers.Conv2D(256, (3, 3), activatinotallow='relu', padding='same')(u7)
c7 = layers.Conv2D(256, (3, 3), activatinotallow='relu', padding='same')(c7)
u8 = layers.Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(c7)
u8 = layers.concatenate([u8, c2])
c8 = layers.Conv2D(128, (3, 3), activatinotallow='relu', padding='same')(u8)
c8 = layers.Conv2D(128, (3, 3), activatinotallow='relu', padding='same')(c8)
u9 = layers.Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(c8)
u9 = layers.concatenate([u9, c1], axis=3)
c9 = layers.Conv2D(64, (3, 3), activatinotallow='relu', padding='same')(u9)
c9 = layers.Conv2D(64, (3, 3), activatinotallow='relu', padding='same')(c9)
outputs = layers.Conv2D(1, (1, 1), activatinotallow='sigmoid')(c9)
model = models.Model(inputs=[inputs], outputs=[outputs])
return model
# 模型實例化
model = unet_model()
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model.summary()
為了演示,我們將生成一些合成圖像和對應的掩碼。實際應用中,你應該使用真實的圖像和標注數據。
import numpy as np
import matplotlib.pyplot as plt
def generate_synthetic_data(num_samples, img_size):
X = np.zeros((num_samples, img_size, img_size, 3), dtype=np.float32)
y = np.zeros((num_samples, img_size, img_size, 1), dtype=np.float32)
for i in range(num_samples):
# 隨機生成圓形
radius = np.random.randint(10, img_size//4)
center_x = np.random.randint(radius, img_size - radius)
center_y = np.random.randint(radius, img_size - radius)
Y, X_grid = np.ogrid[:img_size, :img_size]
dist_from_center = np.sqrt((X_grid - center_x)**2 + (Y - center_y)**2)
mask = dist_from_center <= radius
y[i, mask, 0] = 1.0
# 圖像為掩碼的隨機顏色
X[i] = np.random.rand(img_size, img_size, 3) * mask[..., np.newaxis]
return X, y
# 生成訓練和驗證數據
train_X, train_y = generate_synthetic_data(1000, 128)
val_X, val_y = generate_synthetic_data(200, 128)
# 可視化樣本
def display_sample(X, y, index):
plt.figure(figsize=(6,3))
plt.subplot(1,2,1)
plt.imshow(X[index])
plt.title('Image')
plt.subplot(1,2,2)
plt.imshow(y[index].squeeze(), cmap='gray')
plt.title('Mask')
plt.show()
display_sample(train_X, train_y, 0)
圖片
接下來,我們來訓練模型。
# 使用回調函數保存最佳模型
checkpoint = tf.keras.callbacks.ModelCheckpoint('unet_best_model.h5',
mnotallow='val_loss',
verbose=1,
save_best_notallow=True,
mode='min')
# 訓練模型
history = model.fit(train_X, train_y,
validation_data=(val_X, val_y),
epochs=20,
batch_size=16,
callbacks=[checkpoint])
評估和預測
# 加載最佳模型
model.load_weights('unet_best_model.h5')
# 在驗證集上評估
loss, accuracy = model.evaluate(val_X, val_y)
print(f'Validation Loss: {loss}')
print(f'Validation Accuracy: {accuracy}')
# 進行預測并可視化
def predict_and_display(model, X, y, index):
pred = model.predict(X[index:index+1])[0]
pred_mask = pred > 0.5
plt.figure(figsize=(12,4))
plt.subplot(1,3,1)
plt.imshow(X[index])
plt.title('Image')
plt.subplot(1,3,2)
plt.imshow(y[index].squeeze(), cmap='gray')
plt.title('True Mask')
plt.subplot(1,3,3)
plt.imshow(pred_mask.squeeze(), cmap='gray')
plt.title('Predicted Mask')
plt.show()
predict_and_display(model, val_X, val_y, 0)