Last Updated on 2021-07-11 by Clay
AutoEncoder 到底是什麼?
AutoEncoder,中文直譯的話就是『自動編碼器』,也經常被簡稱為 AE,是無監督式學習 (Unsupervised Learning) 的神經網路,基本上不需要有『標記』的資料。
那麼 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 產生出來的圖片有些模型,不過還算是很好地抓住了輸入的特徵。
使用 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
- https://medium.com/pytorch/implementing-an-autoencoder-in-pytorch-19baa22647d1
- https://www.kaggle.com/jagadeeshkotra/autoencoders-with-pytorch
- https://www.kaggle.com/ljlbarbosa/convolution-autoencoder-pytorch
- https://discuss.pytorch.org/t/autoencoders-in-pytorch/844