Skip to content

[Machine Learning] AutoEncoder 基本介紹 (附 PyTorch 程式碼)

AutoEncoder 到底是什麼?

AutoEncoder,中文直譯的話就是『自動編碼器』,也經常被簡稱為 AE,是無監督式學習 (Unsupervised Learning) 的神經網路,基本上不需要有『標記』的資料。

那麼 AutoEncoder 究竟是在做什麼呢?

AutoEncoder 其實有著龐大的家族,有著相當多種的變體,適用於各式各樣的任務上。不過若是要簡單地描述 AutoEncoder 到底是在做什麼,我想可以繪製成以下這張圖片。

AutoEncoder 架構

AutoEncoder 架構分成兩大部份:Encoder (編碼器) 跟 Decoder (解碼器)。首先先將『輸入』放入 Encoder 中,由編碼器架構內的類神經網路將其壓縮成『低維度』的編碼,也就是圖片中的 “Code”,緊接著再將編碼輸入 Decoder 並解碼出最終的『輸出』。

然後重點來了 —— 我們要控制我們的 Loss function,來讓我們的 Inputs 和 Outputs 越像越好。

這樣做有什麼好處呢?最直觀的理解是,能夠『抗噪』及『降維』。

當我們的輸入經過 Encoder 編碼成低維度的 “Code” 後,若我們能藉由 “Code” 重新解碼,產生出與輸入十分相似的輸出時,我們也許就可以認為,我們中間編碼成的 “Code”,或許就代表著整個輸入在低維度上的特徵也不一定。這樣一來,我們或許就可以拿壓縮過的 Code 進行後續深度學習的處理,以降低計算成本 —— 畢竟若是高維度的資料特徵和低維度的資料特徵相仿,那一定是低維度的資料計算起來快速。

除此之外,低維度也更適合拿來做視覺化。

不僅如此,若是我們能控制中間編碼的 Code,甚至我們也可以使用 Code 產生假資料。

那麼『抗噪』又是什麼意思呢?

如果說我們將輸入編碼成低維度的 Code,然後我們又能從 Code 當中解碼恢復成跟輸入一樣維度的輸出,而且,『輸入和輸出越像越好』—— 那或許我們可以理解為,Code 真的學習到了輸入的某些重要特徵,並將不重要的特徵捨棄了。我想,這就是抗噪的原理,更詳細的部份也許可以參考 DAE。

那麼以下,我嘗試使用 PyTorch 搭建一個簡單的 AutoEncoder 模型,輸入資料為經典的 Mnist,目的為產生與輸入越像越好的圖片,並能藉由中間壓縮降維之後的 Code 進行視覺化。


AutoEncoder

以下,我一步步解釋我如何搭建 AutoEncoder。

首先,匯入會使用到的套件。

# coding: utf-8
import torch
import torch.nn as nn
import torch.utils.data as data
import torchvision


設定好要訓練的迭代次數 (epochs)、批次 (batch_size)、學習率 (lr),同時將 torchvision 中的 Mnist 資料集讀取進來。

# Settings
epochs = 10
batch_size = 128
lr = 0.008


# DataLoader
train_set = torchvision.datasets.MNIST(
    root='mnist',
    train=True,
    download=True,
    transform=torchvision.transforms.ToTensor(),
)
train_loader = data.DataLoader(train_set, batch_size=batch_size, shuffle=True)


定義好 AutoEncoder 的模型架構。正如前述,分成 Encoder 以及 Decoder 兩部份。

# Model structure
class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()

        # Encoder
        self.encoder = nn.Sequential(
            nn.Linear(784, 128),
            nn.Tanh(),
            nn.Linear(128, 64),
            nn.Tanh(),
            nn.Linear(64, 16),
            nn.Tanh(),
            nn.Linear(16, 2),
        )

        # Decoder
        self.decoder = nn.Sequential(
            nn.Linear(2, 16),
            nn.Tanh(),
            nn.Linear(16, 64),
            nn.Tanh(),
            nn.Linear(64, 128),
            nn.Tanh(),
            nn.Linear(128, 784),
            nn.Sigmoid()
        )

    def forward(self, inputs):
        codes = self.encoder(inputs)
        decoded = self.decoder(codes)

        return codes, decoded


建立模型,決定好優化器 (Optimizer) 以及損失函數 (Loss function)。

# Optimizer and loss function
model = AutoEncoder()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
loss_function = nn.MSELoss()


接著便是開始進行訓練了。

# Train
for epoch in range(epochs):
    for data, labels in train_loader:
        inputs = data.view(-1, 784)

        # Forward
        codes, decoded = model(inputs)

        # Backward
        optimizer.zero_grad()
        loss = loss_function(decoded, inputs)
        loss.backward()
        optimizer.step()

    # Show progress
    print('[{}/{}] Loss:'.format(epoch+1, epochs), loss.item())


# Save
torch.save(model, 'autoencoder.pth')



Output:

[1/10] Loss: 0.04639464616775513
[2/10] Loss: 0.04818795993924141
[3/10] Loss: 0.038940753787755966
[4/10] Loss: 0.039030447602272034
[5/10] Loss: 0.041724737733602524
[6/10] Loss: 0.03994645178318024
[7/10] Loss: 0.03632541000843048
[8/10] Loss: 0.041585564613342285
[9/10] Loss: 0.036579448729753494
[10/10] Loss: 0.04153323173522949

最後我們正式開始訓練,並將模型保存起來。

以下是完整的訓練程式碼:

# coding: utf-8
import torch
import torch.nn as nn
import torch.utils.data as data
import torchvision


# Settings
epochs = 10
batch_size = 128
lr = 0.008


# DataLoader
train_set = torchvision.datasets.MNIST(
    root='mnist',
    train=True,
    download=True,
    transform=torchvision.transforms.ToTensor(),
)
train_loader = data.DataLoader(train_set, batch_size=batch_size, shuffle=True)


# Model structure
class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()

        # Encoder
        self.encoder = nn.Sequential(
            nn.Linear(784, 128),
            nn.Tanh(),
            nn.Linear(128, 64),
            nn.Tanh(),
            nn.Linear(64, 16),
            nn.Tanh(),
            nn.Linear(16, 2),
        )

        # Decoder
        self.decoder = nn.Sequential(
            nn.Linear(2, 16),
            nn.Tanh(),
            nn.Linear(16, 64),
            nn.Tanh(),
            nn.Linear(64, 128),
            nn.Tanh(),
            nn.Linear(128, 784),
            nn.Sigmoid()
        )

    def forward(self, inputs):
        codes = self.encoder(inputs)
        decoded = self.decoder(codes)

        return codes, decoded


# Optimizer and loss function
model = AutoEncoder()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
loss_function = nn.MSELoss()


# Train
for epoch in range(epochs):
    for data, labels in train_loader:
        inputs = data.view(-1, 784)

        # Forward
        codes, decoded = model(inputs)

        # Backward
        optimizer.zero_grad()
        loss = loss_function(decoded, inputs)
        loss.backward()
        optimizer.step()

    # Show progress
    print('[{}/{}] Loss:'.format(epoch+1, epochs), loss.item())


# Save
torch.save(model, 'autoencoder.pth')



測試模型效果

既然我們剛才已經訓練好 AutoEncoder 的模型了,那麼我們來看看我們從壓縮之後的 Code 中還原的圖片,跟原圖之間究竟像不像吧?

# coding: utf-8
import torch
import torch.nn as nn
import torch.utils.data as data
import torchvision
import numpy as np
import matplotlib.pyplot as plt


# Settings
plt.rcParams['figure.figsize'] = (10.0, 8.0)
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'


# Show images
def show_images(images):
    sqrtn = int(np.ceil(np.sqrt(images.shape[0])))

    for index, image in enumerate(images):
        plt.subplot(sqrtn, sqrtn, index+1)
        plt.imshow(image.reshape(28, 28))
        plt.axis('off')


# Model structure
class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()

        # Encoder
        self.encoder = nn.Sequential(
            nn.Linear(784, 128),
            nn.Tanh(),
            nn.Linear(128, 64),
            nn.Tanh(),
            nn.Linear(64, 16),
            nn.Tanh(),
            nn.Linear(16, 2),
        )

        # Decoder
        self.decoder = nn.Sequential(
            nn.Linear(2, 16),
            nn.Tanh(),
            nn.Linear(16, 64),
            nn.Tanh(),
            nn.Linear(64, 128),
            nn.Tanh(),
            nn.Linear(128, 784),
            nn.Sigmoid()
        )

    def forward(self, inputs):
        codes = self.encoder(inputs)
        decoded = self.decoder(codes)

        return codes, decoded


# Load model
model = torch.load('autoencoder.pth')
model.eval()
print(model)


# DataLoader
test_set = torchvision.datasets.MNIST(
    root='mnist',
    train=False,
    download=True,
    transform=torchvision.transforms.ToTensor(),
)
test_loader = data.DataLoader(test_set, batch_size=16, shuffle=False)


# Test
with torch.no_grad():
    for data in test_loader:
        inputs = data[0].view(-1, 28*28)
        show_images(inputs)
        plt.show()

        code, outputs = model(inputs)
        show_images(outputs)
        plt.show()
        exit()



Output:

原圖
AutoEncoder 產生的圖片

可以看到,由 AutoEncoder 產生出來的圖片有些模型,不過還算是很好地抓住了輸入的特徵。


使用 Code 視覺化

其中我們壓縮起來的 Code,可以很方便地用於視覺化:

# coding: utf-8
import torch
import torch.nn as nn
import torch.utils.data as data
import torchvision
import matplotlib.pyplot as plt


# Model structure
class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()

        # Encoder
        self.encoder = nn.Sequential(
            nn.Linear(784, 128),
            nn.Tanh(),
            nn.Linear(128, 64),
            nn.Tanh(),
            nn.Linear(64, 16),
            nn.Tanh(),
            nn.Linear(16, 2),
        )

        # Decoder
        self.decoder = nn.Sequential(
            nn.Linear(2, 16),
            nn.Tanh(),
            nn.Linear(16, 64),
            nn.Tanh(),
            nn.Linear(64, 128),
            nn.Tanh(),
            nn.Linear(128, 784),
            nn.Sigmoid()
        )

    def forward(self, inputs):
        codes = self.encoder(inputs)
        decoded = self.decoder(codes)

        return codes, decoded


# Load model
model = torch.load('autoencoder.pth')
model.eval()
print(model)


# DataLoader
test_set = torchvision.datasets.MNIST(
    root='mnist',
    train=False,
    download=True,
    transform=torchvision.transforms.ToTensor(),
)
test_loader = data.DataLoader(test_set, batch_size=16, shuffle=False)


axis_x = []
axis_y = []
answers = []
with torch.no_grad():
    for data in test_loader:
        inputs = data[0].view(-1, 28*28)
        answers += data[1].tolist()

        code, outputs = model(inputs)
        axis_x += code[:, 0].tolist()
        axis_y += code[:, 1].tolist()


plt.scatter(axis_x, axis_y, c=answers)
plt.colorbar()
plt.show()



Output:

不同顏色代表不同數字

可以看出,其實 AutoEncoder 壓縮成的 Code,已經能夠初步地抓到每種不同圖片之中存在著不同的特徵,這點,真的是滿有意思的。


References


Read More

Leave a Reply