iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
AI & Data

ML From Scratch系列 第 23

[Day 23] Autoencoder — 解決真實問題

  • 分享至 

  • xImage
  •  

第 23 天了 !!!

我們要透過 Autoencoder 解決臉部辨識的問題。

Dataset

資料集的部分來自 Kaggle 中 Labelled Faces in the Wild (LFW) Dataset

從網絡上檢測並居中了 13233 張 5749 個人的圖像,這些圖像是由 Viola Jones 人臉檢測器檢測並居中收集的。

在數據集中,有 1680 人的圖像具有兩個或更多不同的照片。

此數據集中有11個文件:

  • lfw-deepfunneled.zip是包含圖像的文件。
  • 其他10個文件是相關的 meta data,可能有助於您形成用於您的模型的訓練和測試集。

https://ithelp.ithome.com.tw/upload/images/20230923/20152821XGJa6f4V43.png

Implementation

Autoencoder

class Autoencoder(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Linear(45*45*3,1500),
            nn.BatchNorm1d(1500),
            nn.ReLU(),
            nn.Linear(1500,1000),
            nn.BatchNorm1d(1000),
            nn.ReLU(),
            nn.Linear(1000, dim_z),
            nn.BatchNorm1d(dim_z),
            nn.ReLU()
        )
        self.decoder = nn.Sequential(
            nn.Linear(dim_z,1000),
            nn.BatchNorm1d(1000),
            nn.ReLU(),
            nn.Linear(1000,1500),
            nn.BatchNorm1d(1500),
            nn.ReLU(),
            nn.Linear(1500,45*45*3)
        )
      
    def encode(self,x):
        return self.encoder(x)
    
    def decode(self,z):
        return self.decoder(z)
        
    def forward(self, x):
        encoded = self.encode(x) 
        decoded = self.decode(encoded)     

        
        return encoded, decoded

模型使用全連接層來進行特徵壓縮和重建。

第一個部分,編碼器(encoder)接受一個大小為3x45x45的輸入,通過多個全連接層進行特徵壓縮,最終輸出維度為dim_z的特徵向量。

第二個部分,解碼器(decoder)接受這個特徵向量,通過多個全連接層進行重建,最終輸出一個大小為3x45x45的圖像。

Network

class Autoencoder_cnn(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(in_channels=16, out_channels=8, kernel_size=3),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
        )
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(in_channels=8, out_channels=16, kernel_size=5, stride=2),
            nn.ReLU(),
            nn.ConvTranspose2d(in_channels=16, out_channels=3, kernel_size=5, stride=2),
        )
        
    def decode(self,z):
        return self.decoder(z)
        
    def forward(self, x):
        x = x.permute(0,3,1,2)
        encoded = self.encoder(x)  
        decoded = self.decode(encoded)     

        
        return encoded, decoded

卷積自編碼器(Autoencoder_cnn):

模型使用卷積層來進行特徵壓縮和重建,適用於圖像數據。

編碼器接受一個大小為3xHxW的圖像輸入,通過多個卷積層和池化層進行特徵壓縮,最終輸出一個特徵張量。

解碼器則接受這個特徵張量,通過轉置卷積層進行重建,最終輸出一個大小為3xHxW的重建圖像。

這些模型可以用於特徵提取、降維、壓縮和圖像重建等任務,具體取決於您的應用。

我們可以使用encode函式將輸入數據編碼為特徵向量,並使用decode函式將特徵向量解碼為重建數據。

forward函式中,模型將輸入數據通過編碼器和解碼器,然後返回編碼特徵和重建數據。

Train the model

def get_batch(data, batch_size=64):
    total_len = data.shape[0]
    for i in range(0, total_len, batch_size):
        yield data[i:min(i+batch_size,total_len)]

def plot_gallery(images, h, w, n_row=3, n_col=6, with_title=False, titles=[]):
    plt.figure(figsize=(1.5 * n_col, 1.7 * n_row))
    plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35)
    for i in range(n_row * n_col):
        plt.subplot(n_row, n_col, i + 1)
        try:
            plt.imshow(images[i].reshape((h, w, 3)), cmap=plt.cm.gray, vmin=-1, vmax=1, interpolation='nearest')
            if with_title:
                plt.title(titles[i])
            plt.xticks(())
            plt.yticks(())
        except:
            pass
        
def fit_epoch(model, train_x, criterion, optimizer, batch_size, is_cnn=False):
    running_loss = 0.0
    processed_data = 0
    
    for inputs in get_batch(train_x,batch_size):
        
        if not is_cnn:
            inputs = inputs.view(-1, 45*45*3)
        inputs = inputs.to(DEVICE)
        
        optimizer.zero_grad()
        
        encoder, decoder = model(inputs)
        
        if not is_cnn:
            outputs = decoder.view(-1, 45*45*3)
        else:
            outputs = decoder.permute(0,2,3,1)
        
        loss = criterion(outputs,inputs)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item() * inputs.shape[0]
        processed_data += inputs.shape[0]
    
    train_loss = running_loss / processed_data    
    return train_loss

def eval_epoch(model, x_val, criterion, is_cnn=False):
    running_loss = 0.0
    processed_data = 0
    model.eval()
    
    for inputs in get_batch(x_val):
        if not is_cnn:
            inputs = inputs.view(-1, 45*45*3)
        inputs = inputs.to(DEVICE)
        
        with torch.set_grad_enabled(False):
            encoder, decoder = model(inputs)
            
            if not is_cnn:
                outputs = decoder.view(-1, 45*45*3)
            else:
                outputs = decoder.permute(0,2,3,1)
                
            loss = criterion(outputs,inputs)
            running_loss += loss.item() * inputs.shape[0]
            processed_data += inputs.shape[0]
    
    val_loss = running_loss / processed_data
    
    #draw
    with torch.set_grad_enabled(False):
        pic = x_val[3]
        
        if not is_cnn:            
            pic_input = pic.view(-1, 45*45*3)
        else:
            pic_input = torch.FloatTensor(pic.unsqueeze(0))
            
        pic_input = pic_input.to(DEVICE)        
        encoder, decoder = model(pic_input)
        
        if not is_cnn:
            pic_output = decoder.view(-1, 45*45*3).squeeze()
        else:
            pic_output = decoder.permute(0,2,3,1)
            
        pic_output = pic_output.to("cpu")        
        pic_input = pic_input.to("cpu")
        plot_gallery([pic_input, pic_output],45,45,1,2)
    
    return val_loss

def train(train_x, val_x, model, epochs=10, batch_size=32, is_cnn=False):     
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)        
    history = []
    log_template = "\nEpoch {ep:03d} train_loss: {t_loss:0.4f} val_loss: {val_loss:0.4f}"
    
    with tqdm(desc="epoch", total=epochs) as pbar_outer:
        for epoch in range(epochs):            
            train_loss = fit_epoch(model,train_x,criterion,optimizer,batch_size,is_cnn)
            val_loss = eval_epoch(model,val_x,criterion, is_cnn)
            print("loss: ", train_loss)

            history.append((train_loss,val_loss))

            pbar_outer.update(1)
            tqdm.write(log_template.format(ep=epoch+1, t_loss=train_loss, val_loss=val_loss))            
        
    return history

這裡我們訓練模型進行圖像重建

get_batch(data, batch_size)

透過一個數據集 data 和一個可選的批處理大小 batch_size

並使用生成器函數,用於將數據集分成小批次。

最後返回生成器對象,每次迭代產生一個批次數據。

plot_gallery(images, h, w, n_row, n_col, with_title, titles)

輸出是一組圖像 images,圖像的高度 h 和寬度 w,可選參數 n_rown_col 指定子圖的行數和列數,以及可選參數 with_titletitles 用於顯示子圖標題。

並使用此圖像和參數用於繪制一組圖像的子圖,通常用於可視化模型輸出。

最後直接在圖形窗口中顯示圖像。

fit_epoch(model, train_x, criterion, optimizer, batch_size, is_cnn)

執行一個訓練周期,包括前向傳播、反向傳播和參數更新。

並回傳訓練周期的平均損失值。

eval_epoch(model, x_val, criterion, is_cnn)

這裡我們評估模型在驗證數據上的性能,計算平均損失,並可視化一張圖像的輸入和輸出。

並返回驗證周期的平均損失值。

train(train_x, val_x, model, epochs, batch_size, is_cnn)

使用指定的數據和模型進行訓練,返回訓練過程中的損失歷史。

VAE

class VAE(nn.Module):
    def __init__(self):
        super(VAE, self).__init__()
        self.fc1 = nn.Linear(45*45*3, 1500)
        self.fc21 = nn.Linear(1500, dim_z)
        self.fc22 = nn.Linear(1500, dim_z)
        self.fc3 = nn.Linear(dim_z, 1500)
        self.fc4 = nn.Linear(1500, 45*45*3)        
        self.relu = nn.LeakyReLU()

    def encode(self, x):
        x = self.relu(self.fc1(x))
        return self.fc21(x), self.fc22(x)

    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5 *logvar)
        eps = torch.randn_like(std)
        return eps.mul(std).add_(mu)
    
    def decode(self, z):
        z = self.relu(self.fc3(z)) #1500
        return torch.sigmoid(self.fc4(z))

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        z = self.decode(z)
        return z, mu, logvar
    
def loss_vae_fn(x, recon_x, mu, logvar):    
    BCE = F.binary_cross_entropy(recon_x, x, reduction='sum')
    KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
    return BCE + KLD

編碼器將輸入數據點映射到潛在空間的均值和對數方差,然後使用重參數化技巧採樣潛在變量。

解碼器將潛在變量映射回數據空間生成重構的數據點。

模型的訓練損失由重構誤差和KL散度項組成。


完整的 code 可以去 Kaggle Notebook 實際執行喔~

之後進入 Neural Network 系列文中的最後一部分 Recurrent Neural Network ~

/images/emoticon/emoticon01.gif

Reference


上一篇
[Day 22] Autoencoder — 主題實作
下一篇
[Day 24] Recurrent Neural Network — 背後理論
系列文
ML From Scratch31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言