第 23 天了 !!!
我們要透過 Autoencoder 解決臉部辨識的問題。
資料集的部分來自 Kaggle 中 Labelled Faces in the Wild (LFW) Dataset
從網絡上檢測並居中了 13233 張 5749 個人的圖像,這些圖像是由 Viola Jones 人臉檢測器檢測並居中收集的。
在數據集中,有 1680 人的圖像具有兩個或更多不同的照片。
此數據集中有11個文件:
lfw-deepfunneled.zip
是包含圖像的文件。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的圖像。
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
函式中,模型將輸入數據通過編碼器和解碼器,然後返回編碼特徵和重建數據。
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_row
和 n_col
指定子圖的行數和列數,以及可選參數 with_title
和 titles
用於顯示子圖標題。
並使用此圖像和參數用於繪制一組圖像的子圖,通常用於可視化模型輸出。
最後直接在圖形窗口中顯示圖像。
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)
:使用指定的數據和模型進行訓練,返回訓練過程中的損失歷史。
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 ~