HW8-Autoencoder

HW8-Autoencoder

Task

本次作业是利用Autoencoder来实现检测异常图片的模型。训练过程为:输入图片X,Autoencoder对输入的图片进行向量编码,其实是一个压缩过程,然后通过Decoder将生成的向量输出为图片Y(解压),loss即为图片Y与图片X的差异。在测试时,将测试图片A输入训练好的模型得到图片B,然后设置一个阈值a,两个图片的差距大于a的判定为异常,表明图片A与训练集的图片不属于统一类别。
由于训练集没有标签,故该训练过程为Self-supervise无监督训练。

只运行助教提供的Sample Code可以通过Sample、将Autoencoder改为fcn可以通过Medium,就无法提升了。找到一个通过Strong、Boss的blog链接,他是将CNNVAE增加了层数以及加入了normalization & linear层。
https://nbviewer.org/ 这个网址可以预览网页上资源的.ipynb文件。

Sample Code

相关问题会体现在注释中。

Package installation & Downloading data

# Training progress bar
!pip install -q qqdm
!pip install kaggle
from google.colab import files
uploaded = files.upload()
!mkdir ~/.kaggle
!mv kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
!kaggle competitions download -c ml2022spring-hw8
!unzip ml2022spring-hw8.zip 

助教挂在GitHub上的Data已经失效,直接运行Sample Code会出现找不到Data的问题,而Kaggle上的Data可以使用,故需要按照上面的指令,使Kaggle的Data加载到Colab。参考blog

Import packages

import random
import numpy as np
import torch
from torch import nn
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler, TensorDataset
import torchvision.transforms as transforms
import torch.nn.functional as F
from torch.autograd import Variable
import torchvision.models as models
from torch.optim import Adam, AdamW
from qqdm import qqdm, format_str
import pandas as pd

Loading data

train = np.load('data/trainingset.npy', allow_pickle=True)
test = np.load('data/testingset.npy', allow_pickle=True)

print(train.shape) # 100k张图片,每张图片维度为 64*64*3
print(test.shape)

Random seed

def same_seeds(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True

same_seeds(48763)

Autoencoder

Models & loss
class fcn_autoencoder(nn.Module):
    def __init__(self):
        super(fcn_autoencoder, self).__init__()
        self.encoder = nn.Sequential(
            nn.Linear(64 * 64 * 3, 128),
            nn.ReLU(),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Linear(64, 12),
            nn.ReLU(),
            nn.Linear(12, 3)
        )

        self.decoder = nn.Sequential(
            nn.Linear(3, 12),
            nn.ReLU(),
            nn.Linear(12, 64),
            nn.ReLU(),
            nn.Linear(64, 128),
            nn.ReLU(),
            nn.Linear(128, 64 * 64 * 3),
            nn.Tanh()
        )

    def forward(self, x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x


class conv_autoencoder(nn.Module):
    def __init__(self):
        super(conv_autoencoder, self).__init__()
        self.encoder = nn.Sequential(
            nn.Conv2d(3, 12, 4, stride=2, padding=1), # Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False)
            nn.ReLU(),
            nn.Conv2d(12, 24, 4, stride=2, padding=1),
            nn.ReLU(),
			      nn.Conv2d(24, 48, 4, stride=2, padding=1),
            nn.ReLU(),
        )
        self.decoder = nn.Sequential(
			      nn.ConvTranspose2d(48, 24, 4, stride=2, padding=1), # 反卷积.将图形逆向卷积"还原"为大图形.
            nn.ReLU(),
			      nn.ConvTranspose2d(24, 12, 4, stride=2, padding=1),
            nn.ReLU(),
            nn.ConvTranspose2d(12, 3, 4, stride=2, padding=1),
            nn.Tanh(),
        )

    def forward(self, x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x


class VAE(nn.Module):
    def __init__(self):
        super(VAE, self).__init__()
        self.encoder = nn.Sequential(
            nn.Conv2d(3, 12, 4, stride=2, padding=1),
            nn.ReLU(),
            nn.Conv2d(12, 24, 4, stride=2, padding=1),
            nn.ReLU(),
        )
        self.enc_out_1 = nn.Sequential(
            nn.Conv2d(24, 48, 4, stride=2, padding=1),
            nn.ReLU(),
        )
        self.enc_out_2 = nn.Sequential(
            nn.Conv2d(24, 48, 4, stride=2, padding=1),
            nn.ReLU(),
        )
        self.decoder = nn.Sequential(
			      nn.ConvTranspose2d(48, 24, 4, stride=2, padding=1),
            nn.ReLU(),
			      nn.ConvTranspose2d(24, 12, 4, stride=2, padding=1),
            nn.ReLU(),
            nn.ConvTranspose2d(12, 3, 4, stride=2, padding=1),
            nn.Tanh(),
        )

    def encode(self, x):
        h1 = self.encoder(x)
        return self.enc_out_1(h1), self.enc_out_2(h1)

    def reparametrize(self, mu, logvar):
        std = logvar.mul(0.5).exp_() # y.mul(x):y为张量,x为张量or数字.y中每个元素乘x/ y.exp_():将y中元素做以e为底数的指数,并存储在y中. https://www.cnblogs.com/picassooo/p/12583881.html
        if torch.cuda.is_available():
            eps = torch.cuda.FloatTensor(std.size()).normal_() # 在标准正态分布下生成std.size()大小的tensor矩阵
        else:
            eps = torch.FloatTensor(std.size()).normal_()
        eps = Variable(eps) # pytorch两个基本对象(张量Tensor, 变量Variable).后者可以变化进行反向传播,前者无法进行反向传播
        return eps.mul(std).add_(mu)

    def decode(self, z):
        return self.decoder(z)

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparametrize(mu, logvar)
        return self.decode(z), mu, logvar


def loss_vae(recon_x, x, mu, logvar, criterion):
    """
    recon_x: generating images
    x: origin images
    mu: latent mean
    logvar: latent log variance
    """
    mse = criterion(recon_x, x)
    KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar)
    KLD = torch.sum(KLD_element).mul_(-0.5)
    return mse + KLD

Dataset module

Module for obtaining and processing data. The transform function here normalizes image’s pixels from [0, 255] to [-1.0, 1.0].

class CustomTensorDataset(TensorDataset):
    """TensorDataset with support of transforms.
    """
    def __init__(self, tensors):
        self.tensors = tensors
        if tensors.shape[-1] == 3:
            self.tensors = tensors.permute(0, 3, 1, 2) # 维度转换 100000, 64, 64, 3 -> 100000, 3, 64, 64

        self.transform = transforms.Compose([
          transforms.Lambda(lambda x: x.to(torch.float32)), # self define function to transform
          transforms.Lambda(lambda x: 2. * x/255. - 1.),
        ])

    def __getitem__(self, index):
        x = self.tensors[index]

        if self.transform:
            # mapping images to [-1.0, 1.0]
            x = self.transform(x)

        return x

    def __len__(self):
        return len(self.tensors)

Training

Configuration
# Training hyperparameters
num_epochs = 50
batch_size = 2000
learning_rate = 1e-3

# Build training dataloader
x = torch.from_numpy(train)
train_dataset = CustomTensorDataset(x)

train_sampler = RandomSampler(train_dataset) # 数据随机采样,该命令效果为将训练集数据打乱.https://blog.csdn.net/qq_50001789/article/details/128974424
train_dataloader = DataLoader(train_dataset, sampler=train_sampler, batch_size=batch_size) # 传入自定义取样策略train_sampler,此时shuffle应为False

# Model
model_type = 'cnn'   # selecting a model type from {'cnn', 'fcn', 'vae', 'resnet'}
model_classes = {'fcn': fcn_autoencoder(), 'cnn': conv_autoencoder(), 'vae': VAE()}
model = model_classes[model_type].cuda()

# Loss and optimizer
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
Training loop

best_loss = np.inf
model.train()

qqdm_train = qqdm(range(num_epochs), desc=format_str('bold', 'Description'))
for epoch in qqdm_train:
    tot_loss = list()
    for data in train_dataloader:

        # ===================loading=====================
        img = data.float().cuda()
        if model_type in ['fcn']:
            img = img.view(img.shape[0], -1) # 将img变为两维矩阵,第一维固定,因个数不变故第二维进而确定。

        # ===================forward=====================
        output = model(img)
        if model_type in ['vae']:
            loss = loss_vae(output[0], img, output[1], output[2], criterion)
        else:
            loss = criterion(output, img)

        tot_loss.append(loss.item())
        # ===================backward====================
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    # ===================save_best====================
    mean_loss = np.mean(tot_loss)
    if mean_loss < best_loss:
        best_loss = mean_loss
        torch.save(model, 'best_model_{}.pt'.format(model_type))
    # ===================log========================
    qqdm_train.set_infos({
        'epoch': f'{epoch + 1:.0f}/{num_epochs:.0f}',
        'loss': f'{mean_loss:.4f}',
    })
    # ===================save_last========================
    torch.save(model, 'last_model_{}.pt'.format(model_type))

Inference

Initialize
  • dataloader
  • model
  • prediction file
eval_batch_size = 200

# build testing dataloader
data = torch.tensor(test, dtype=torch.float32)
test_dataset = CustomTensorDataset(data)
test_sampler = SequentialSampler(test_dataset)
test_dataloader = DataLoader(test_dataset, sampler=test_sampler, batch_size=eval_batch_size, num_workers=1)
eval_loss = nn.MSELoss(reduction='none')

# load trained model
checkpoint_path = f'last_model_{model_type}.pt'
model = torch.load(checkpoint_path)
model.eval()

# prediction file
out_file = 'prediction.csv'
anomality = list()
with torch.no_grad():
  for i, data in enumerate(test_dataloader):
    img = data.float().cuda()
    if model_type in ['fcn']:
      img = img.view(img.shape[0], -1)
    output = model(img)
    if model_type in ['vae']:
      output = output[0]
    if model_type in ['fcn']:
        loss = eval_loss(output, img).sum(-1)
    else:
        loss = eval_loss(output, img).sum([1, 2, 3])
    anomality.append(loss)
print(f'anomality shape: {len(anomality)}')
anomality = torch.cat(anomality, axis=0) # anomality=[loss1,loss2,...,lossn],将列表中的n个loss tensor按行拼接在一起
print(f'anomality shape: {anomality.shape}')
anomality = torch.sqrt(anomality).reshape(len(test), 1).cpu().numpy()
print(f'anomality shape: {anomality.shape}')
print(f'top-10 anomality:{anomality[0:10]}')
df = pd.DataFrame(anomality, columns=['score'])
print(df.iloc[0:8,:])
df.to_csv(out_file, index_label = 'ID')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值