Chuyển đến nội dung chính

Triển khai Autoencoder trong Keras: Hướng dẫn

Trong hướng dẫn này, bạn sẽ tìm hiểu thêm về autoencoder và cách xây dựng convolutional và denoising autoencoder với bộ dữ liệu notMNIST trong Keras.
· 15 phút đọc

Khám phá cùng AI

Nhìn chung, bạn có thể xem autoencoder như một kỹ thuật học không giám sát, vì bạn không cần nhãn rõ ràng để huấn luyện mô hình. Tất cả những gì bạn cần để huấn luyện một autoencoder là dữ liệu đầu vào thô.

Trong hướng dẫn này, bạn sẽ tìm hiểu về autoencoder trong học sâu và sẽ triển khai một convolutional autoencoder và một denoising autoencoder bằng Python với Keras. Bạn sẽ làm việc với bộ dữ liệu bảng chữ cái NotMNIST làm ví dụ.

Tóm gọn lại, hôm nay bạn sẽ đề cập các chủ đề sau:

  • Trước tiên bạn sẽ tìm hiểu thêm về autoencoder: chúng là gì, so sánh với các kỹ thuật giảm chiều như thế nào, và những loại thuật toán này mà bạn có thể bắt gặp;
  • Tiếp theo, bạn sẽ tập trung vào convolutional autoencoder: bạn sẽ xem loại autoencoder này làm gì và cách bạn có thể xây dựng nó. Sau đó, bạn sẽ tự triển khai một mô hình! Bạn sẽ học cách tải dữ liệu khi ở định dạng ubyte gzip, rồi khám phá dữ liệu, tiền xử lý dữ liệu, đưa dữ liệu vào mô hình, trực quan hóa biểu đồ loss huấn luyện và xác thực, và cuối cùng dự đoán trên tập kiểm tra.
  • Tiếp theo, bạn sẽ được giới thiệu về denoising autoencoder và xem cách triển khai: bạn sẽ học cách thêm nhiễu vào ảnh. Sau đó, bạn đưa các ảnh này vào mô hình học sâu và huấn luyện! Cuối cùng, bạn sẽ dự đoán trên các ảnh kiểm tra có nhiễu.

Autoencoder

Như bạn đã đọc trong phần mở đầu, autoencoder là một thuật toán học máy không giám sát nhận một ảnh làm đầu vào và cố gắng tái tạo ảnh đó bằng việc sử dụng ít bit hơn tại nút thắt (bottleneck), còn gọi là không gian tiềm ẩn (latent space). Ảnh được nén mạnh tại nút thắt. Việc nén trong autoencoder đạt được bằng cách huấn luyện mạng trong một khoảng thời gian; khi học, mạng tìm cách biểu diễn tốt nhất ảnh đầu vào tại nút thắt. Các thuật toán nén ảnh thông thường như JPEG và JPEG lossless nén ảnh mà không cần bất kỳ quá trình huấn luyện nào và làm khá tốt việc nén ảnh.

Autoencoder tương tự các kỹ thuật giảm chiều như Phân tích Thành phần Chính (PCA). Chúng chiếu dữ liệu từ không gian chiều cao xuống chiều thấp bằng biến đổi tuyến tính và cố gắng giữ lại các đặc trưng quan trọng của dữ liệu trong khi loại bỏ các phần không thiết yếu.

Tuy nhiên, khác biệt lớn giữa autoencoder và PCA nằm ở phần biến đổi: như bạn đã đọc, PCA sử dụng biến đổi tuyến tính trong khi autoencoder sử dụng các biến đổi phi tuyến.

Bây giờ bạn đã có chút hiểu biết về autoencoder, hãy cùng “mổ xẻ” thuật ngữ này và xây dựng trực giác về nó!

autoencoder keras

Hình trên là một autoencoder “vanilla” hai lớp với một lớp ẩn. Trong thuật ngữ học sâu, bạn sẽ thường thấy rằng lớp đầu vào không được tính khi đếm tổng số lớp trong một kiến trúc. Tổng số lớp trong kiến trúc chỉ bao gồm số lớp ẩn và lớp đầu ra.

Như hình minh họa, lớp đầu vào và đầu ra có cùng số lượng neuron.

Lấy một ví dụ. Bạn đưa vào autoencoder một ảnh chỉ với năm giá trị điểm ảnh, được bộ mã hóa (encoder) nén thành ba giá trị điểm ảnh tại nút thắt (lớp giữa) hay không gian tiềm ẩn. Dựa trên ba giá trị này, bộ giải mã (decoder) cố gắng tái tạo lại năm giá trị điểm ảnh hay chính ảnh đầu vào mà bạn đã nạp vào mạng.

Trong thực tế, có nhiều lớp ẩn hơn giữa đầu vào và đầu ra.

autoencoder python keras

Autoencoder có thể được chia thành ba phần

  • Encoder: phần này của mạng nén hoặc lấy mẫu thưa đầu vào thành ít bit hơn. Không gian được biểu diễn bởi số bit ít hơn này thường được gọi là latent-space hoặc bottleneck. Bottleneck cũng được gọi là “điểm nén tối đa” vì tại điểm này đầu vào được nén nhiều nhất. Các bit nén đại diện cho đầu vào gốc được gọi chung là “mã hóa” (encoding) của đầu vào.
  • Decoder: phần này của mạng cố gắng tái tạo đầu vào chỉ sử dụng phần mã hóa của đầu vào. Khi decoder có thể tái tạo đầu vào chính xác như những gì đã đưa vào encoder, bạn có thể nói encoder đã tạo ra các mã hóa tốt nhất để decoder tái tạo hiệu quả!

Có nhiều loại autoencoder, như convolutional autoencoder, denoising autoencoder, variational autoencoder và sparse autoencoder. Tuy nhiên, như đã đề cập ở phần mở đầu, trong hướng dẫn này bạn sẽ chỉ tập trung vào convolutional và denoising.Convolutional Autoencoders bằng Python với Keras

Vì dữ liệu đầu vào của bạn là ảnh, sẽ là một ý tưởng tốt khi dùng một convolutional autoencoder. Đây không phải là một biến thể autoencoder mới, mà là một autoencoder truyền thống được xếp chồng với các lớp tích chập: về cơ bản, bạn thay thế các lớp kết nối đầy đủ bằng các lớp tích chập. Các lớp tích chập cùng với các lớp max-pooling chuyển đổi đầu vào từ rộng (ảnh 28 x 28) và mỏng (một kênh hoặc thang xám) thành nhỏ (ảnh 7 x 7 tại không gian tiềm ẩn) và dày (128 kênh).

Đừng lo nếu bạn chưa hiểu rõ ý tưởng trên! Phần hai của hướng dẫn, nơi bạn tập trung vào triển khai, sẽ giúp bạn làm rõ mọi thắc mắc (nếu có!).

Mẹo: nếu bạn muốn biết thêm về mạng nơ-ron tích chập, hãy xem hướng dẫn này.

Điều này giúp mạng trích xuất đặc trưng thị giác từ ảnh và do đó thu được biểu diễn không gian tiềm ẩn chính xác hơn. Quá trình tái tạo sử dụng upsampling và tích chập, được gọi là decoder. Quá trình downsampling là khi ảnh được nén vào không gian thấp, còn gọi là encoder.

Lưu ý rằng encoder chủ yếu nén ảnh đầu vào, ví dụ: nếu ảnh đầu vào có kích thước 176 x 176 x 1 (~30976), thì điểm nén tối đa có thể có kích thước 22 x 22 x 512 (~247808). Vậy, trong trường hợp này, bạn bắt đầu với ảnh thang xám kích thước 176 x 176 và bằng cách đưa qua một vài lớp tích chập và chính xác là ba lớp max-pooling, ảnh của bạn cuối cùng được lấy mẫu thưa xuống kích thước 22 x 22 nhưng số kênh tăng từ 1 lên 512. Như đã nêu ở trên, đầu vào của bạn từ rộng (176 x 176) và mỏng (1) trở thành nhỏ (22x22) và dày (512).

Tải dữ liệu

Bộ dữ liệu notMNIST là bộ dữ liệu nhận dạng ký tự phông chữ cho các chữ cái từ A đến J. Nó khá giống với bộ dữ liệu kinh điển MNIST, chứa ảnh chữ số viết tay 0 đến 9: trong trường hợp này, bạn sẽ thấy bộ NotMNIST gồm các ảnh thang xám 28x28 của 70.000 chữ cái từ A - J, tổng cộng 10 hạng mục, và 6.000 ảnh mỗi hạng mục.

Mẹo: nếu bạn muốn học cách triển khai một Multi-Layer Perceptron (MLP) cho bài toán phân loại với bộ MNIST, hãy xem hướng dẫn này.

Bộ dữ liệu NotMNIST không được định nghĩa sẵn trong Keras hay TensorFlow, vì vậy bạn sẽ phải tải dữ liệu từ nguồn này. Dữ liệu sẽ được tải xuống ở định dạng ubyte.gzip, nhưng đừng lo! Bạn sẽ sớm học cách đọc các định dạng bytestream và chuyển đổi chúng thành mảng NumPy. Bắt đầu thôi!

Mạng sẽ được huấn luyện trên Nvidia Tesla K40, nên nếu bạn huấn luyện trên GPU và dùng Jupyter Notebook, bạn sẽ cần thêm ba dòng mã để chỉ định thứ tự thiết bị CUDA và CUDA visible devices bằng mô-đun os.

Trong đoạn mã dưới đây, bạn thiết lập biến môi trường trong notebook bằng os.environ. Nên làm như sau trước khi khởi tạo Keras để giới hạn backend TensorFlow của Keras dùng GPU đầu tiên. Nếu máy bạn huấn luyện có GPU ở 0, hãy chắc chắn dùng 0 thay vì 1. Bạn có thể kiểm tra bằng một lệnh đơn giản trên terminal: ví dụ, nvidia-smi

import os
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="1" #model will be trained on GPU 1

Tiếp theo, bạn nhập tất cả các mô-đun cần thiết như numpy, matplotlib và quan trọng nhất là keras, vì bạn sẽ dùng framework này trong hướng dẫn hôm nay!

import keras
from matplotlib import pyplot as plt
import numpy as np
import gzip
%matplotlib inline
from keras.layers import Input,Conv2D,MaxPooling2D,UpSampling2D
from keras.models import Model
from keras.optimizers import RMSprop
Using TensorFlow backend.

Tại đây, bạn định nghĩa một hàm mở tệp gzip, đọc tệp bằng bytestream.read(). Bạn truyền kích thước ảnh và tổng số ảnh vào hàm này. Sau đó, dùng np.frombuffer(), bạn chuyển chuỗi lưu trong biến buf thành mảng NumPy kiểu float32.

Tiếp theo, bạn reshape mảng thành mảng ba chiều hoặc tensor, trong đó chiều thứ nhất là số ảnh, chiều thứ hai và ba là kích thước ảnh. Cuối cùng, bạn trả về mảng NumPy data.

def extract_data(filename, num_images):
    with gzip.open(filename) as bytestream:
        bytestream.read(16)
        buf = bytestream.read(28 * 28 * num_images)
        data = np.frombuffer(buf, dtype=np.uint8).astype(np.float32)
        data = data.reshape(num_images, 28,28)
        return data

Bây giờ bạn sẽ gọi hàm extract_data() bằng cách truyền các tệp huấn luyện và kiểm tra cùng với số lượng ảnh tương ứng:

train_data = extract_data('train-images-idx3-ubyte.gz', 60000)
test_data = extract_data('t10k-images-idx3-ubyte.gz', 10000)

Tương tự, bạn định nghĩa một hàm trích xuất nhãn mở tệp gzip, đọc tệp bằng bytestream.read(), trong đó bạn truyền kích thước nhãn (1) và tổng số ảnh. Sau đó, dùng np.frombuffer(), bạn chuyển chuỗi lưu trong biến buf thành mảng NumPy kiểu int64.

Lần này, bạn không cần reshape mảng vì biến labels sẽ trả về một vector cột kích thước 60.000 x 1. Cuối cùng, bạn trả về mảng NumPy labels.

def extract_labels(filename, num_images):
    with gzip.open(filename) as bytestream:
        bytestream.read(8)
        buf = bytestream.read(1 * num_images)
        labels = np.frombuffer(buf, dtype=np.uint8).astype(np.int64)
        return labels

Bây giờ bạn sẽ gọi hàm trích xuất nhãn bằng cách truyền các tệp nhãn huấn luyện và kiểm tra cùng với số lượng ảnh tương ứng:

train_labels = extract_labels('train-labels-idx1-ubyte.gz',60000)
test_labels = extract_labels('t10k-labels-idx1-ubyte.gz',10000)

Khi đã tải xong dữ liệu huấn luyện và kiểm tra, bạn sẵn sàng phân tích dữ liệu để có trực giác về bộ dữ liệu mà bạn sẽ làm việc trong hướng dẫn hôm nay!

Khám phá dữ liệu

Giờ hãy phân tích cách các ảnh trong bộ dữ liệu trông như thế nào và xem kích thước ảnh với thuộc tính mảng NumPy .shape:

# Shapes of training set
print("Training set (images) shape: {shape}".format(shape=train_data.shape))

# Shapes of test set
print("Test set (images) shape: {shape}".format(shape=test_data.shape))
Training set (images) shape: (60000, 28, 28)
Test set (images) shape: (10000, 28, 28)

Từ kết quả trên, bạn có thể thấy dữ liệu huấn luyện có kích thước 60000 x 28 x 28 vì có 60.000 mẫu huấn luyện, mỗi mẫu là ma trận kích thước 28 x 28. Tương tự, dữ liệu kiểm tra có kích thước 10000 x 28 x 28 vì có 10.000 mẫu kiểm tra.

Lưu ý rằng trong tác vụ này, bạn sẽ không dùng nhãn huấn luyện và kiểm tra. Nhiệm vụ hiện tại chỉ xử lý ảnh huấn luyện và kiểm tra. Tuy nhiên, để mục đích khám phá, giúp bạn có trực giác tốt hơn về dữ liệu, bạn sẽ sử dụng nhãn.

Hãy tạo một từ điển chứa tên lớp với nhãn phân loại tương ứng:

# Create dictionary of target classes
label_dict = {
 0: 'A',
 1: 'B',
 2: 'C',
 3: 'D',
 4: 'E',
 5: 'F',
 6: 'G',
 7: 'H',
 8: 'I',
 9: 'J',
}

Bây giờ, hãy xem một vài ảnh trong bộ dữ liệu của bạn:

plt.figure(figsize=[5,5])

# Display the first image in training data
plt.subplot(121)
curr_img = np.reshape(train_data[0], (28,28))
curr_lbl = train_labels[0]
plt.imshow(curr_img, cmap='gray')
plt.title("(Label: " + str(label_dict[curr_lbl]) + ")")

# Display the first image in testing data
plt.subplot(122)
curr_img = np.reshape(test_data[0], (28,28))
curr_lbl = test_labels[0]
plt.imshow(curr_img, cmap='gray')
plt.title("(Label: " + str(label_dict[curr_lbl]) + ")")
<matplotlib.text.Text at 0x7f3ec73db240>

mẫu ảnh từ dữ liệu huấn luyện và kiểm tra

Kết quả của hai biểu đồ trên là một trong số các ảnh mẫu từ cả dữ liệu huấn luyện và kiểm tra, và các ảnh này được gán nhãn lớp 5 hay F ở một bên, và 3 hay D ở bên còn lại. Tương tự, các chữ cái khác sẽ có nhãn khác nhau, nhưng các chữ cái giống nhau sẽ có cùng nhãn. Điều này có nghĩa là tất cả 6.000 ảnh lớp F sẽ có nhãn lớp 5.

Tiền xử lý dữ liệu

Các ảnh trong bộ dữ liệu thực sự là ảnh thang xám với giá trị điểm ảnh từ 0 đến 255 và kích thước 28 x 28, nên trước khi đưa dữ liệu vào mô hình, việc tiền xử lý rất quan trọng. Bạn sẽ chuyển từng ảnh 28 x 28 của tập train và test thành ma trận kích thước 28 x 28 x 1, để có thể đưa vào mạng:

train_data = train_data.reshape(-1, 28,28, 1)
test_data = test_data.reshape(-1, 28,28, 1)
train_data.shape, test_data.shape
((60000, 28, 28, 1), (10000, 28, 28, 1))

Tiếp theo, bạn cần kiểm tra kiểu dữ liệu của các mảng NumPy huấn luyện và kiểm tra, chúng nên ở định dạng float32; nếu không, bạn sẽ cần chuyển đổi, nhưng vì bạn đã chuyển đổi khi đọc dữ liệu nên không cần làm lại. Bạn cũng phải chuẩn hóa giá trị điểm ảnh về khoảng 0 - 1. Hãy làm điều đó!

Đừng quên xác minh kiểu dữ liệu của dữ liệu huấn luyện và kiểm tra:

train_data.dtype, test_data.dtype
(dtype('float32'), dtype('float32'))

Tiếp theo, chuẩn hóa dữ liệu huấn luyện và kiểm tra theo giá trị điểm ảnh tối đa của chúng:

np.max(train_data), np.max(test_data)
(255.0, 255.0)
train_data = train_data / np.max(train_data)
test_data = test_data / np.max(test_data)

Hãy xác minh giá trị tối đa của dữ liệu huấn luyện và kiểm tra, sau khi chuẩn hóa, phải là 1.0!

np.max(train_data), np.max(test_data)
(1.0, 1.0)

Sau tất cả, việc phân chia dữ liệu là quan trọng. Để mô hình của bạn tổng quát tốt, bạn chia dữ liệu huấn luyện thành hai phần: tập huấn luyện và tập xác thực. Bạn sẽ huấn luyện mô hình trên 80% dữ liệu và xác thực trên 20% dữ liệu huấn luyện còn lại.

Điều này cũng giúp giảm khả năng quá khớp, vì bạn sẽ xác thực mô hình trên dữ liệu mà nó không nhìn thấy trong giai đoạn huấn luyện.

Bạn có thể dùng mô-đun train_test_split của scikit-learn để chia dữ liệu đúng cách:

from sklearn.model_selection import train_test_split
train_X,valid_X,train_ground,valid_ground = train_test_split(train_data,
                                                             train_data, 
                                                             test_size=0.2, 
                                                             random_state=13)

Lưu ý rằng với tác vụ này, bạn không cần nhãn huấn luyện và kiểm tra. Đó là lý do bạn truyền ảnh huấn luyện hai lần. Ảnh huấn luyện vừa đóng vai trò đầu vào vừa là ground truth tương tự như nhãn trong bài toán phân loại.

Bây giờ bạn sẵn sàng định nghĩa mạng và đưa dữ liệu vào. Không chần chừ nữa, sang bước tiếp theo!

Convolutional Autoencoder

Các ảnh có kích thước 28 x 28 x 1 hay vector 784 chiều. Bạn chuyển ma trận ảnh thành mảng, chuẩn hóa về khoảng 0 đến 1, reshape về kích thước 28 x 28 x 1, và đưa làm đầu vào cho mạng.

Ngoài ra, bạn sẽ dùng batch size 128; dùng batch size lớn hơn như 256 hoặc 512 cũng được, tùy vào hệ thống bạn huấn luyện mô hình. Tham số này ảnh hưởng lớn đến các tham số học và độ chính xác dự đoán. Bạn sẽ huấn luyện mạng trong 50 epoch.

batch_size = 128
epochs = 50
inChannel = 1
x, y = 28, 28
input_img = Input(shape = (x, y, inChannel))

Như đã thảo luận, autoencoder được chia thành hai phần: encoder và decoder.

Encoder

  • Lớp đầu tiên có 32 bộ lọc kích thước 3 x 3, theo sau là một lớp lấy mẫu thưa (max-pooling),
  • Lớp thứ hai có 64 bộ lọc kích thước 3 x 3, theo sau là một lớp lấy mẫu thưa khác,
  • Lớp cuối của encoder có 128 bộ lọc kích thước 3 x 3.



Decoder

  • Lớp đầu tiên có 128 bộ lọc kích thước 3 x 3 theo sau là một lớp upsampling,/li>
  • Lớp thứ hai có 64 bộ lọc kích thước 3 x 3 theo sau là một lớp upsampling khác,
  • Lớp cuối của encoder có 1 bộ lọc kích thước 3 x 3.

Lớp max-pooling sẽ giảm lấy mẫu đầu vào xuống hai lần mỗi khi bạn dùng, trong khi lớp upsampling sẽ tăng lấy mẫu đầu vào lên hai lần mỗi khi sử dụng.

Lưu ý: Số lượng bộ lọc, kích thước bộ lọc, số lớp, số epoch bạn huấn luyện mô hình đều là siêu tham số và nên được quyết định dựa trên trực giác của bạn; bạn được tự do thử nghiệm bằng cách tinh chỉnh các siêu tham số này và đo lường hiệu năng mô hình. Và đó là cách bạn dần học được “nghệ thuật” của học sâu!

def autoencoder(input_img):
    #encoder
    #input = 28 x 28 x 1 (wide and thin)
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img) #28 x 28 x 32
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1) #14 x 14 x 32
    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1) #14 x 14 x 64
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2) #7 x 7 x 64
    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2) #7 x 7 x 128 (small and thick)

    #decoder
    conv4 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3) #7 x 7 x 128
    up1 = UpSampling2D((2,2))(conv4) # 14 x 14 x 128
    conv5 = Conv2D(64, (3, 3), activation='relu', padding='same')(up1) # 14 x 14 x 64
    up2 = UpSampling2D((2,2))(conv5) # 28 x 28 x 64
    decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2) # 28 x 28 x 1
    return decoded

Sau khi tạo mô hình, bạn phải biên dịch nó với bộ tối ưu hóa RMSProp.

Mẹo: xem hướng dẫn này của Andrew Ng về RMSProp nếu bạn muốn biết thêm.

Lưu ý rằng bạn cũng phải chỉ định loại loss qua tham số loss. Ở đây là mean squared error, vì loss sau mỗi batch sẽ được tính giữa batch đầu ra dự đoán và ground truth bằng sai số bình phương trung bình theo từng pixel:

autoencoder = Model(input_img, autoencoder(input_img))
autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())

Hãy trực quan hóa các lớp bạn đã tạo ở bước trên bằng hàm summary, hàm này sẽ hiển thị số tham số (trọng số và bias) ở mỗi lớp và tổng tham số trong mô hình.

autoencoder.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_6 (InputLayer)         (None, 28, 28, 1)         0         
_________________________________________________________________
conv2d_25 (Conv2D)           (None, 28, 28, 32)        320       
_________________________________________________________________
max_pooling2d_9 (MaxPooling2 (None, 14, 14, 32)        0         
_________________________________________________________________
conv2d_26 (Conv2D)           (None, 14, 14, 64)        18496     
_________________________________________________________________
max_pooling2d_10 (MaxPooling (None, 7, 7, 64)          0         
_________________________________________________________________
conv2d_27 (Conv2D)           (None, 7, 7, 128)         73856     
_________________________________________________________________
conv2d_28 (Conv2D)           (None, 7, 7, 128)         147584    
_________________________________________________________________
up_sampling2d_9 (UpSampling2 (None, 14, 14, 128)       0         
_________________________________________________________________
conv2d_29 (Conv2D)           (None, 14, 14, 64)        73792     
_________________________________________________________________
up_sampling2d_10 (UpSampling (None, 28, 28, 64)        0         
_________________________________________________________________
conv2d_30 (Conv2D)           (None, 28, 28, 1)         577       
=================================================================
Total params: 314,625
Trainable params: 314,625
Non-trainable params: 0
_________________________________________________________________

Đã đến lúc huấn luyện mô hình với hàm fit() của Keras! Mô hình huấn luyện trong 50 epoch. Hàm fit() sẽ trả về một đối tượng lịch sử; Bằng cách lưu kết quả này vào fashion_train, bạn có thể dùng sau để vẽ biểu đồ loss giữa huấn luyện và xác thực giúp bạn phân tích hiệu năng mô hình bằng trực quan.

Huấn luyện mô hình

autoencoder_train = autoencoder.fit(train_X, train_ground, batch_size=batch_size,epochs=epochs,verbose=1,validation_data=(valid_X, valid_ground))
Train on 48000 samples, validate on 12000 samples
Epoch 1/50
48000/48000 [==============================] - 16s - loss: 0.0368 - val_loss: 0.0132
Epoch 2/50
48000/48000 [==============================] - 15s - loss: 0.0101 - val_loss: 0.0085
Epoch 3/50
48000/48000 [==============================] - 15s - loss: 0.0071 - val_loss: 0.0081
Epoch 4/50
48000/48000 [==============================] - 15s - loss: 0.0057 - val_loss: 0.0056
Epoch 5/50
48000/48000 [==============================] - 15s - loss: 0.0048 - val_loss: 0.0051
Epoch 6/50
48000/48000 [==============================] - 15s - loss: 0.0043 - val_loss: 0.0039
Epoch 7/50
48000/48000 [==============================] - 15s - loss: 0.0038 - val_loss: 0.0039
Epoch 8/50
48000/48000 [==============================] - 15s - loss: 0.0035 - val_loss: 0.0039
Epoch 9/50
48000/48000 [==============================] - 15s - loss: 0.0032 - val_loss: 0.0030
Epoch 10/50
48000/48000 [==============================] - 15s - loss: 0.0030 - val_loss: 0.0029
Epoch 11/50
48000/48000 [==============================] - 15s - loss: 0.0029 - val_loss: 0.0026
Epoch 12/50
48000/48000 [==============================] - 15s - loss: 0.0027 - val_loss: 0.0025
Epoch 13/50
48000/48000 [==============================] - 15s - loss: 0.0026 - val_loss: 0.0028
Epoch 14/50
48000/48000 [==============================] - 15s - loss: 0.0025 - val_loss: 0.0022
Epoch 15/50
48000/48000 [==============================] - 15s - loss: 0.0024 - val_loss: 0.0024
Epoch 16/50
48000/48000 [==============================] - 16s - loss: 0.0023 - val_loss: 0.0027
Epoch 17/50
48000/48000 [==============================] - 15s - loss: 0.0023 - val_loss: 0.0022
Epoch 18/50
48000/48000 [==============================] - 16s - loss: 0.0022 - val_loss: 0.0025
Epoch 19/50
48000/48000 [==============================] - 16s - loss: 0.0022 - val_loss: 0.0022
Epoch 20/50
48000/48000 [==============================] - 15s - loss: 0.0021 - val_loss: 0.0022
Epoch 21/50
48000/48000 [==============================] - 16s - loss: 0.0021 - val_loss: 0.0020
Epoch 22/50
48000/48000 [==============================] - 16s - loss: 0.0020 - val_loss: 0.0019
Epoch 23/50
48000/48000 [==============================] - 16s - loss: 0.0020 - val_loss: 0.0021
Epoch 24/50
48000/48000 [==============================] - 16s - loss: 0.0020 - val_loss: 0.0018
Epoch 25/50
48000/48000 [==============================] - 16s - loss: 0.0019 - val_loss: 0.0020
Epoch 26/50
48000/48000 [==============================] - 16s - loss: 0.0019 - val_loss: 0.0020
Epoch 27/50
48000/48000 [==============================] - 16s - loss: 0.0019 - val_loss: 0.0017
Epoch 28/50
48000/48000 [==============================] - 16s - loss: 0.0018 - val_loss: 0.0018
Epoch 29/50
48000/48000 [==============================] - 16s - loss: 0.0018 - val_loss: 0.0019
Epoch 30/50
48000/48000 [==============================] - 16s - loss: 0.0018 - val_loss: 0.0017
Epoch 31/50
48000/48000 [==============================] - 16s - loss: 0.0018 - val_loss: 0.0019
Epoch 32/50
48000/48000 [==============================] - 16s - loss: 0.0017 - val_loss: 0.0018
Epoch 33/50
48000/48000 [==============================] - 16s - loss: 0.0017 - val_loss: 0.0017
Epoch 34/50
48000/48000 [==============================] - 15s - loss: 0.0017 - val_loss: 0.0018
Epoch 35/50
48000/48000 [==============================] - 15s - loss: 0.0017 - val_loss: 0.0019
Epoch 36/50
48000/48000 [==============================] - 15s - loss: 0.0017 - val_loss: 0.0016
Epoch 37/50
48000/48000 [==============================] - 15s - loss: 0.0017 - val_loss: 0.0017
Epoch 38/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0019
Epoch 39/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0015
Epoch 40/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0017
Epoch 41/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0017
Epoch 42/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0014
Epoch 43/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0018
Epoch 44/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0015
Epoch 45/50
48000/48000 [==============================] - 15s - loss: 0.0016 - val_loss: 0.0014
Epoch 46/50
48000/48000 [==============================] - 16s - loss: 0.0015 - val_loss: 0.0016
Epoch 47/50
48000/48000 [==============================] - 16s - loss: 0.0015 - val_loss: 0.0017
Epoch 48/50
48000/48000 [==============================] - 16s - loss: 0.0015 - val_loss: 0.0015
Epoch 49/50
48000/48000 [==============================] - 16s - loss: 0.0015 - val_loss: 0.0016
Epoch 50/50
48000/48000 [==============================] - 16s - loss: 0.0015 - val_loss: 0.0020

Cuối cùng! Bạn đã huấn luyện mô hình trên Not-MNIST trong 50 epoch. Bây giờ, hãy vẽ biểu đồ loss giữa dữ liệu huấn luyện và xác thực để trực quan hóa hiệu năng mô hình.

Biểu đồ Training vs Validation Loss

loss = autoencoder_train.history['loss']
val_loss = autoencoder_train.history['val_loss']
epochs = range(epochs)
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

biểu đồ training và validation loss

Cuối cùng, bạn có thể thấy rằng validation loss và training loss đồng bộ với nhau. Điều này cho thấy mô hình của bạn không bị quá khớp: validation loss đang giảm chứ không tăng, và hiếm khi có khoảng cách lớn giữa training và validation loss.

Vì vậy, có thể nói khả năng tổng quát hóa của mô hình là tốt.

Cuối cùng, đã đến lúc tái tạo ảnh kiểm tra bằng hàm predict() của Keras và xem mô hình tái tạo trên dữ liệu kiểm tra tốt đến đâu.

Dự đoán trên dữ liệu kiểm tra

Bạn sẽ dùng mô hình đã huấn luyện để dự đoán trên toàn bộ 10.000 ảnh kiểm tra và vẽ một vài ảnh đã tái tạo để trực quan hóa mức độ tái tạo của mô hình.

pred = autoencoder.predict(test_data)
pred.shape
(10000, 28, 28, 1)
plt.figure(figsize=(20, 4))
print("Test Images")
for i in range(10):
    plt.subplot(2, 10, i+1)
    plt.imshow(test_data[i, ..., 0], cmap='gray')
    curr_lbl = test_labels[i]
    plt.title("(Label: " + str(label_dict[curr_lbl]) + ")")
plt.show()    
plt.figure(figsize=(20, 4))
print("Reconstruction of Test Images")
for i in range(10):
    plt.subplot(2, 10, i+1)
    plt.imshow(pred[i, ..., 0], cmap='gray')  
plt.show()
Test Images

ảnh kiểm tra

Reconstruction of Test Images

tái tạo ảnh kiểm tra

Từ các hình trên, bạn có thể quan sát mô hình đã làm rất tốt việc tái tạo các ảnh kiểm tra được dự đoán bằng mô hình. Ít nhất là về mặt trực quan, ảnh kiểm tra và ảnh tái tạo trông gần như giống hệt nhau.

Denoising Autoencoder

Một denoising autoencoder cố gắng học một biểu diễn (không gian tiềm ẩn hay nút thắt) bền vững với nhiễu. Bạn thêm nhiễu vào ảnh rồi đưa ảnh nhiễu làm đầu vào cho phần encoder của mạng. Phần encoder của autoencoder biến đổi ảnh sang một không gian khác, cố gắng giữ lại chữ cái nhưng loại bỏ nhiễu.

Nhưng nó loại bỏ nhiễu chính xác như thế nào?

Trong quá trình huấn luyện, bạn định nghĩa một hàm loss, tương tự như root mean squared error mà bạn đã dùng trước đó trong convolutional autoencoder. Ở mỗi vòng lặp huấn luyện, mạng sẽ tính loss giữa ảnh nhiễu được decoder xuất ra và ground truth (ảnh đã khử nhiễu) và cũng sẽ cố gắng tối thiểu hóa loss hay chênh lệch giữa ảnh tái tạo và ảnh gốc không nhiễu. Nói cách khác, mạng sẽ học một không gian 7 x 7 x 128 với các mã hóa không nhiễu cho dữ liệu mà bạn huấn luyện!

Triển khai Denoising Autoencoder

Bây giờ, để xem cách hoạt động trong Python, bạn sẽ dùng cùng bộ dữ liệu NotMNIST như ở phần đầu hướng dẫn. Điều đó có nghĩa là bạn không cần tiền xử lý dữ liệu nữa vì đã làm từ trước! Tuy nhiên, một bước tiền xử lý quan trọng trong phần này là thêm nhiễu vào các ảnh huấn luyện, xác thực và kiểm tra. Vậy hãy làm nhanh bước đó trước!

Thêm nhiễu vào ảnh

Đầu tiên hãy định nghĩa một hệ số nhiễu, đây là một siêu tham số. Hệ số nhiễu được nhân với một ma trận ngẫu nhiên có trung bình 0,0 và độ lệch chuẩn 1,0. Ma trận này sẽ lấy mẫu từ phân phối chuẩn (Gaussian). Hình dạng mảng normal ngẫu nhiên sẽ tương tự như hình dạng dữ liệu mà bạn sẽ thêm nhiễu.

Để đơn giản, hiểu bằng ví dụ: biến train_X có kích thước 48000 x 28 x 28 x 1. Do đó, mảng normal ngẫu nhiên cũng sẽ có kích thước tương tự train_X; chỉ khi đó bạn mới có thể cộng hai mảng vì chúng có cùng kích thước.

noise_factor = 0.5
x_train_noisy = train_X + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=train_X.shape)
x_valid_noisy = valid_X + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=valid_X.shape)
x_test_noisy = test_data + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=test_data.shape)
x_train_noisy = np.clip(x_train_noisy, 0., 1.)
x_valid_noisy = np.clip(x_valid_noisy, 0., 1.)
x_test_noisy = np.clip(x_test_noisy, 0., 1.)

np.clip() sẽ ngưỡng hóa tất cả giá trị âm thành 0 và tất cả giá trị lớn hơn 1 thành 1. Vì bạn muốn giá trị điểm ảnh nằm trong khoảng 0 đến 1. Và có khả năng sau khi đưa nhiễu vào dữ liệu, dải giá trị điểm ảnh có thể thay đổi đôi chút, nên để an toàn, thực hành tốt là đưa các giá trị điểm ảnh về ngưỡng.

Trực quan hóa ảnh nhiễu

plt.figure(figsize=[5,5])

# Display the first image in training data
plt.subplot(121)
curr_img = np.reshape(x_train_noisy[1], (28,28))
plt.imshow(curr_img, cmap='gray')

# Display the first image in testing data
plt.subplot(122)
curr_img = np.reshape(x_test_noisy[1], (28,28))
plt.imshow(curr_img, cmap='gray')
<matplotlib.image.AxesImage at 0x7f3ec6b20e48>

ảnh có nhiễu

Bây giờ bạn đã có dữ liệu nhiễu, bạn có thể đưa vào mạng và xem nhiễu được loại bỏ “thần kỳ” khỏi ảnh như thế nào!

Mạng Denoising Autoencoder

Như đã thảo luận, autoencoder được chia làm hai phần: encoder và decoder. Kiến trúc bạn sẽ xây dựng sẽ trông như sau:

Encoder

  • Lớp đầu có 32 bộ lọc 3 x 3 theo sau là một lớp lấy mẫu thưa (max-pooling),
  • Lớp thứ hai có 64 bộ lọc 3 x 3 theo sau là một lớp lấy mẫu thưa khác,
  • Lớp cuối của encoder có 128 bộ lọc 3 x 3.

Decoder

  • Lớp đầu có 128 bộ lọc 3 x 3 theo sau là một lớp upsampling,
  • Lớp thứ hai có 64 bộ lọc 3 x 3 theo sau là một lớp upsampling khác,
  • Lớp cuối của encoder có 1 bộ lọc 3 x 3.
batch_size = 128
epochs = 20
inChannel = 1
x, y = 28, 28
input_img = Input(shape = (x, y, inChannel))
def autoencoder(input_img):
    #encoder
    conv1 = Conv2D(32, (3, 3), activation='relu', padding='same')(input_img)
    pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
    conv2 = Conv2D(64, (3, 3), activation='relu', padding='same')(pool1)
    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
    conv3 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool2)

    #decoder
    conv4 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv3)
    up1 = UpSampling2D((2,2))(conv4)
    conv5 = Conv2D(64, (3, 3), activation='relu', padding='same')(up1)
    up2 = UpSampling2D((2,2))(conv5)
    decoded = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2)
    return decoded
autoencoder = Model(input_img, autoencoder(input_img))
autoencoder.compile(loss='mean_squared_error', optimizer = RMSprop())

Huấn luyện

Nếu bạn còn nhớ, khi huấn luyện convolutional autoencoder, bạn đã đưa ảnh huấn luyện vào hai lần vì đầu vào và ground truth giống nhau. Tuy nhiên, trong denoising autoencoder, bạn đưa ảnh có nhiễu làm đầu vào trong khi ground truth là các ảnh không nhiễu mà bạn đã thêm nhiễu. Chỉ như vậy mạng mới có thể tính loss giữa ảnh nhiễu và ảnh thật không nhiễu.

autoencoder_train = autoencoder.fit(x_train_noisy, train_X, batch_size=batch_size,epochs=epochs,verbose=1,validation_data=(x_valid_noisy, valid_X))
Train on 48000 samples, validate on 12000 samples
Epoch 1/20
48000/48000 [==============================] - 15s - loss: 0.0531 - val_loss: 0.0268
Epoch 2/20
48000/48000 [==============================] - 14s - loss: 0.0243 - val_loss: 0.0217
Epoch 3/20
48000/48000 [==============================] - 14s - loss: 0.0207 - val_loss: 0.0200
Epoch 4/20
48000/48000 [==============================] - 14s - loss: 0.0190 - val_loss: 0.0184
Epoch 5/20
48000/48000 [==============================] - 14s - loss: 0.0179 - val_loss: 0.0183
Epoch 6/20
48000/48000 [==============================] - 14s - loss: 0.0171 - val_loss: 0.0178
Epoch 7/20
48000/48000 [==============================] - 14s - loss: 0.0165 - val_loss: 0.0161
Epoch 8/20
48000/48000 [==============================] - 14s - loss: 0.0160 - val_loss: 0.0166
Epoch 9/20
48000/48000 [==============================] - 14s - loss: 0.0157 - val_loss: 0.0157
Epoch 10/20
48000/48000 [==============================] - 14s - loss: 0.0153 - val_loss: 0.0159
Epoch 11/20
48000/48000 [==============================] - 14s - loss: 0.0151 - val_loss: 0.0153
Epoch 12/20
48000/48000 [==============================] - 14s - loss: 0.0148 - val_loss: 0.0154
Epoch 13/20
48000/48000 [==============================] - 14s - loss: 0.0146 - val_loss: 0.0151
Epoch 14/20
48000/48000 [==============================] - 14s - loss: 0.0145 - val_loss: 0.0152
Epoch 15/20
48000/48000 [==============================] - 14s - loss: 0.0143 - val_loss: 0.0163
Epoch 16/20
48000/48000 [==============================] - 14s - loss: 0.0141 - val_loss: 0.0152
Epoch 17/20
48000/48000 [==============================] - 14s - loss: 0.0140 - val_loss: 0.0149
Epoch 18/20
48000/48000 [==============================] - 14s - loss: 0.0139 - val_loss: 0.0153
Epoch 19/20
48000/48000 [==============================] - 14s - loss: 0.0138 - val_loss: 0.0152
Epoch 20/20
48000/48000 [==============================] - 14s - loss: 0.0137 - val_loss: 0.0150

Biểu đồ Train và Validation Loss

loss = autoencoder_train.history['loss']
val_loss = autoencoder_train.history['val_loss']
epochs = range(epochs)
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

biểu đồ training và validation loss 2

Từ biểu đồ trên, bạn có thể rút ra trực giác rằng mô hình bị quá khớp ở một vài epoch trong khi phần lớn thời gian thì đồng bộ. Bạn hoàn toàn có thể cố gắng cải thiện hiệu năng bằng cách tăng độ phức tạp để loss giảm hơn nữa, thử huấn luyện thêm epoch rồi quyết định.

Dự đoán trên tập kiểm tra

pred = autoencoder.predict(x_test_noisy)
plt.figure(figsize=(20, 4))
print("Test Images")
for i in range(10,20,1):
    plt.subplot(2, 10, i+1)
    plt.imshow(test_data[i, ..., 0], cmap='gray')
    curr_lbl = test_labels[i]
    plt.title("(Label: " + str(label_dict[curr_lbl]) + ")")
plt.show()    
plt.figure(figsize=(20, 4))
print("Test Images with Noise")
for i in range(10,20,1):
    plt.subplot(2, 10, i+1)
    plt.imshow(x_test_noisy[i, ..., 0], cmap='gray')
plt.show()    

plt.figure(figsize=(20, 4))
print("Reconstruction of Noisy Test Images")
for i in range(10,20,1):
    plt.subplot(2, 10, i+1)
    plt.imshow(pred[i, ..., 0], cmap='gray')  
plt.show()
Test Images

Ảnh kiểm tra 2

Test Images with Noise

Ảnh kiểm tra có nhiễu

Reconstruction of Noisy Test Images

Tái tạo ảnh kiểm tra có nhiễu

Có vẻ như chỉ với 20 epoch, một kiến trúc mạng khá đơn giản đã làm rất tốt việc loại bỏ nhiễu khỏi các ảnh kiểm tra, phải không?

Tiến xa hơn!

Hướng dẫn này là khởi đầu tốt để hiểu autoencoder cả lý thuyết lẫn thực hành. Nếu bạn theo kịp dễ dàng hoặc dù phải nỗ lực hơn chút, rất tuyệt! Hãy xem Keras Tutorial: Deep Learning in Python của DataCamp và khóa Introduction to Deep Learning in Python.

Trong hướng dẫn tiếp theo, bạn sẽ học cách đọc ảnh từ đầu, phân tích, tiền xử lý và đưa chúng vào mô hình sử dụng bộ dữ liệu dấu vân tay. Bạn cũng sẽ học cách đọc ảnh y khoa của chế độ T-1 và tái tạo chúng bằng autoencoder!

Vẫn còn nhiều điều để khám phá, vậy tại sao không tham gia khóa Deep Learning in Python của DataCamp? Nếu bạn chưa làm. Bạn sẽ học từ những kiến thức cơ bản và dần tiến tới làm chủ lĩnh vực học sâu; đây chắc chắn sẽ là nguồn tài nguyên không thể thiếu khi bạn học cách làm việc với mạng nơ-ron tích chập trong Python, cách phát hiện khuôn mặt, đối tượng, v.v.

Chủ đề

Liên quan