用手写数字集实现AnoGAN的代码(自用学习笔记)

一、mnist_csv.py文件:原格式转csv格式
在这里插入图片描述

def convert(imgf, labelf, outf, n):
    f = open(imgf, "rb")
    o = open(outf, "w")
    l = open(labelf, "rb")

    f.read(16)
    l.read(8)
    images = []

    for i in range(n):
        image = [ord(l.read(1))]
        for j in range(28 * 28):
            image.append(ord(f.read(1)))
        images.append(image)

    for image in images:
        o.write(",".join(str(pix) for pix in image) + "\n")
    f.close()
    o.close()
    l.close()

convert("F:\\dyt\Mnist\\train-images-idx3-ubyte", "F:\\dyt\Mnist\\train-labels-idx1-ubyte",
        "F:\\dyt\Mnist\mnist_train.csv", 60000)

convert("F:\\dyt\Mnist\\t10k-images-idx3-ubyte", "F:\\dyt\Mnist\\t10k-labels-idx1-ubyte",
        "F:\\dyt\Mnist\mnist_test.csv", 10000)

print("Convert Finished!")

运行后得到:
在这里插入图片描述在这里插入图片描述
然后,复制为副本,如:
在这里插入图片描述
手动给副本数据集添加首行"label",只改label,后面空着,不然报错(具体原因不清楚,因为添加"1×1、1×2…1×784"后一直报错)
在这里插入图片描述
二、mnist_data.py文件:分别提取对应的训练集、测试集数据

import pandas as pd
import numpy as np
import csv

"""
第一个数值为标签label,表示其表示哪个手写数字,后784个数值为对应数字每个像素的值,手写数字图片大小为28×28,故一共有784个像素值
说明:
取训练集中的前400个标签为7或8的数据作为AnoGAN的训练集,即7、8都为正常数据。
取测试集前600个标签为2、7、8作为测试数据,即测试集中有正常数据(7、8)和异常数据(2)
"""

## 读取训练集数据  (6000, 785)表示训练集中共有60000个数据,即60000张手写数字的图片,每个数据都有785个值
train = pd.read_csv(r"F:/dyt\Mnist\mnist_train - 副本.csv",dtype = np.float32) # (60000, 785)
# print(train.shape)

# 查询训练数据中标签为7、8的数据,并取前400个
train = train.query("label in [7.0, 8.0]").head(400)
# print(train.shape)


## 读取测试集数据  (10000,785)
test = pd.read_csv(r"F:/dyt\Mnist\mnist_test - 副本.csv",dtype = np.float32) # (10000, 785)
# print(test.shape)

# 查询训练数据中标签为2 7、8的数据,并取前600个
test = test.query("label in [2.0,7.0, 8.0]").head(600)
# print(test.shape)

# 去除标签
"""在AnoGAN中,我们是无监督的学习,因此是不需要标签的,通过以下代码去除train和test中的标签label第一列"""
# 取除标签后的784列数据

train = train.iloc[:, 1:].values.astype('float32') # (400, 784)
test = test.iloc[:, 1:].values.astype('float32') # (600, 784)

# 将 train和 test reshape成图片的格式,即 28×28
# train:(400,784)-->(400,28,28)
# test:(600,784)-->(600,28,28)

train = train.reshape(train.shape[0], 28, 28) # (400, 28, 28) # 通过reshape重新建立维度,第一个维度就是X.shape[0],这就是正常的reshape操作;第二个维度是-1
# print(train.shape)

test = test.reshape(test.shape[0], 28, 28) # (600, 28, 28)
# print(test.shape)

三、mnist_data_class.py文件:自定义类数据集

import torch
import torchvision # pytorch 的一个图形库,服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型
from torchvision import transforms
from torch.utils.data import Dataset

import pandas as pd
import numpy as np
import csv

class image_data_set(Dataset):
    def __init__(self, data):
        self.images = data[:, :, :, None]
        self.transform = transforms.Compose([
            transforms.ToTensor(),
            # 采用插值算法将原来 28×28 大小的图片上采样成了6464大小
            transforms.Resize(64, interpolation = transforms.InterpolationMode.BICUBIC),
            transforms.Normalize((0.1307,), (0.3081,))
        ])

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



    def __getitem__(self, idx):
        return self.transform(self.images[idx])


"""下半部分是草稿,可不用,懒得删了"""
# 实例化
from mnist_data import * # 提取的训练集\测试集数据
from torch.utils.data import Dataset, DataLoader
# 加载训练/测试数据
train_set = image_data_set(train)
train_loader = DataLoader(train_set, batch_size=128)

# 加载测试数据
test_set = image_data_set(test)
test_loader = DataLoader(test_set, batch_size=5, shuffle=False)# **kwargs表示将传入多个带名称的参数(包括0个参数)变为字典
# print(next(iter(test_loader)))

for images in train_loader:
    ## 训练判别器 Discriminator
    # 定义真标签(全1)和假标签(全0)   维度:(batch_size)
    label_real = torch.full((images.size(0),), 1.0)  # images.size(0)指有多少行,单列

    label_fake = torch.full((images.size(0),), 0.0)

四、mnist_network.py文件:搭建生成网络、判别网络

import random
import torch
import torch.nn as nn # 所有神经网络的基类,模型应该继承这个类
import torch.optim as optim

# Set random seed for reproducibility
manualSeed = 999
#manualSeed = random.randint(1, 10000) # use if you want new results
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)

"""
        nc -输入图像中的颜色通道数。 对于彩色图像,这是 3, nc=3
        nz -潜矢量的长度,是z输入向量的长度 nz=100
        ngf -与通过生成器传送的特征图的深度有关,ngf=64
        ndf -设置通过鉴别器传播的特征图的深度
"""
# 生成模型搭建
############################################# 定义生成器网络结构 #################################
"""
生成器由卷积转曾层,批处理规范化层和RELU激活组成.输入是从标准正态中提取的潜矢量,输出是3×64×64RGB图像
"""
class Generator(nn.Module):
    def __init__(self):  # 生成网络结构的基本信息
        super(Generator, self).__init__()
        # class torch.nn.Sequential(* args) 是一个时序容器,Modules 会以他们传入的顺序被添加到容器中。也可以传入一个OrderedDict

        self.generator_network = nn.Sequential(
                    # imput is z, going into a convolution
                    # 需要注意的是,在第一次转置卷积时,使用的参数是k=4,s=1,p=0,后面的参数都是k=4,s=2,p=1
                    nn.ConvTranspose2d(20, 64 * 8, kernel_size=4, stride=1, padding=0,bias=False), #
                    nn.BatchNorm2d(64 * 8, affine=True), # 对加速收敛及提高卷积神经网络性能中非常有效的方法
                    nn.ReLU(True),

                    # state size. (ngf*8)×4×4
                    nn.ConvTranspose2d(64 * 8, 64 * 4, kernel_size=4, stride=2, padding=1,bias=False),
                    nn.BatchNorm2d(64 * 4, affine=True),
                    nn.LeakyReLU(0.1, inplace=True),

                    # state size. (ngf*4)×8×8
                    nn.ConvTranspose2d(64 * 4, 64 * 2, kernel_size=4, stride=2, padding=1,bias=False),
                    nn.BatchNorm2d(64 * 2, affine=True),
                    nn.ReLU(True),# inplace为True,将会改变输入的数据 ,否则不会改变原输入,只会产生新的输出。

                    # state size. (ngf*2)×16×16
                    nn.ConvTranspose2d(64 * 2, 64 * 1,kernel_size=4, stride=2, padding=1,bias=False),
                    nn.BatchNorm2d(64 * 1, affine=True),# BatchNorm2d的输入通道数与前一层Conv2d的输出通道数要一致
                    nn.ReLU(inplace=True),

                    # state size. (ngf)×32×32
                    nn.ConvTranspose2d(64 ,1, kernel_size=4, stride=2, padding=1,bias=False),
                    nn.Tanh()
                    # state size. (nc)×64×64
        )

    def forward(self, input):
        out = self.generator_network(input)
        return out

# 判别器模型搭建
############################################# 定义判别器网络结构 #################################
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator,self).__init__()
        self.feature_network = nn.Sequential(
                    # input is (nc)×64×64,第一层不加入BN
                    nn.Conv2d(1, 64 , kernel_size=4, stride=2, padding=1, bias=False),
                    nn.LeakyReLU(0.1, inplace=True),  # 对加速收敛及提高卷积神经网络性能中非常有效的方法

                    # state size. (ndf) x 32 x 32
                    nn.Conv2d(64, 64 * 2, 4, 2, 1, bias=False),
                    nn.BatchNorm2d(64 * 2),
                    nn.LeakyReLU(0.1, inplace=True),

                    # state size. (ndf*2) x 16 x 16
                    nn.Conv2d(64 * 2, 64 * 4, 4, 2, 1, bias=False),
                    nn.BatchNorm2d(64 * 4),
                    nn.LeakyReLU(0.1, inplace=True),

                    # state size. (ndf*4) x 8 x 8
                    nn.Conv2d(64 * 4, 64 * 8, 4, 2, 1, bias=False),
                    nn.BatchNorm2d(64 * 8),
                    nn.LeakyReLU(0.1, inplace=True),
                )

        self.critic_network = nn.Sequential(
                    # state size. (ndf*8) x 4 x 4
                    nn.Conv2d(64 * 8, 1, 4, 1, 0, bias=False),
                    nn.Sigmoid()
        )

    # 判别网络有两个输出,一个是最终的输出,还有一个是第四个CBA BLOCK提取到的特征,这个在理论部分介绍损失函数时有提及。
    def forward(self,input):
        out = self.feature_network(input) # 在第3个卷积块处有一个输出

        feature = out
        feature = feature.view(feature.size(0),-1) # 展平

        out = self.critic_network(out) # critic_network 续接 feature_network() ,也做一个输出

        return out, feature

# model_1 = Generator()
# print(model_1)
#
# model_2 = Discriminator()
# print(model_2)

五、mnist_train.py文件:模型训练

"""5.1导包"""
import torchvision
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from torchvision.transforms import InterpolationMode
from mnist_network import * # 搭建的生成器判别器网络
from mnist_data import * # 提取的训练集\测试集数据
from mnist_data_class import * # 自定义的类数据集
import argparse
import warnings
warnings.filterwarnings("ignore")

# argparse 模块是可以让人轻松编写用户友好的命令行接口
def main():
    parser = argparse.ArgumentParser(description='Anomaly Detection based on AnoGAN') # 解析器,创建对象
    parser.add_argument('--epochs', type=int, default=300, help='maximum training epochs')  # 训练过程中全部样本数据将的总轮次数
    parser.add_argument('--batch_size', type=int, default=128)  # 批量大小,指一次喂进网络的样本数为 128
    parser.add_argument('--lr', type=float, default=0.0001, help='learning rate of others in SGD')
    parser.add_argument('--seed', type=int, default=668, help='manual seed')  # 为CPU设置种子用于生成随机数,以使得结果是确定的

    args = parser.parse_args()  # 4、解析参数对象获得解析对象

    """ 5.2 指定设备\ 加载数据 \ 加载模型 """
    # 指定设备
    use_cuda = torch.cuda.is_available()
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    if args.seed is None:
        args.seed = random.randint(1, 10000)
        random.seed(args.seed)
        torch.manual_seed(args.seed) # 为CPU设置种子用于生成随机数,以使得结果是确定的
    if use_cuda:
        torch.cuda.manual_seed_all(args.seed) # 如果使用多个GPU,应该使用torch.cuda.manual_seed_all()为所有的GPU设置种子。

    # 加载训练/测试数据
    train_set = image_data_set(train)
    train_loader = DataLoader(train_set, batch_size=args.batch_size, shuffle=False)

    # 加载测试数据
    test_set = image_data_set(test)
    test_loader = DataLoader(test_set, batch_size=5, shuffle=False)# **kwargs表示将传入多个带名称的参数(包括0个参数)变为字典
    input_images = next(iter(test_loader)).to(device)

    # 加载模型
    G = Generator().to(device)
    D = Discriminator().to(device)

    # 训练模式
    G.train()
    D.train()

    # 设置优化器
    optimizerG = torch.optim.Adam(G.parameters(), lr=0.0001, betas=(0.0, 0.9))
    optimizerD = torch.optim.Adam(D.parameters(), lr=0.0004, betas=(0.0, 0.9))

    # 定义损失函数
    criterion = nn.BCEWithLogitsLoss(reduction='mean')
    print('Loading Datasets')

    """ 5.3 开始训练GAN网络"""
    for epoch in range(args.epochs):
        # 定义初始损失
        log_g_loss, log_d_loss = 0.0, 0.0
        for images in train_loader:
            images = images.to(device)

            """ 先训练判别器 Discriminator """
            # 定义真标签(全1)和假标签(全0)   维度:(batch_size)
            label_real = torch.full((images.size(0),), 1.0).to(device) # images.size(0)指有多少行
            label_fake = torch.full((images.size(0),), 0.0).to(device)

            # 定义随机潜在变量z    维度:(batch_size,20,1,1) = torch.Size([128, 20, 1, 1])
            z = torch.randn(images.size(0), 20).to(device).view(images.size(0), 20, 1, 1).to(device)# 维度怎么确定是 20 的?
            # print(z.shape)
            # 潜在变量喂入生成网络--->fake_images:(batch_size,1,64,64)
            fake_images = G(z)

            # 真图像和假图像送入判别网络,得到d_out_real、d_out_fake   维度:都为(batch_size,1,1,1)
            d_out_real, _ = D(images)
            d_out_fake, _ = D(fake_images)
            # print(d_out_real)

            # 损失计算
            d_loss_real = criterion(d_out_real.view(-1), label_real)
            d_loss_fake = criterion(d_out_fake.view(-1), label_fake)
            d_loss = d_loss_real + d_loss_fake

            # 误差反向传播,更新损失
            optimizerD.zero_grad()
            d_loss.backward()
            optimizerD.step()

            """ 训练生成器 Generator """
            # 定义潜在变量z    维度:(batch_size,20,1,1)
            z = torch.randn(images.size(0), 20).to(device).view(images.size(0), 20, 1, 1).to(device)
            fake_images = G(z)

            # 假图像喂入判别器,得到 d_out_fake   维度:(batch_size,1,1,1)
            d_out_fake, _ = D(fake_images)

            # 损失计算
            g_loss = criterion(d_out_fake.view(-1), label_real)

            # 误差反向传播,更新损失
            optimizerG.zero_grad()
            g_loss.backward()
            optimizerG.step()

            ## 累计一个epoch的损失,判别器损失和生成器损失分别存放到log_d_loss、log_g_loss中
            log_d_loss += d_loss.item()
            log_g_loss += g_loss.item()
        ## 打印损失
        print(f'epoch {epoch}, D_Loss:{log_d_loss / 128:.4f}, G_Loss:{log_g_loss / 128:.4f}')

    ## 展示生成器存储的图片,存放在result文件夹下的G_out.jpg
    z = torch.randn(8, 20).to(device).view(8, 20, 1, 1).to(device)
    fake_images = G(z)
    torchvision.utils.save_image(fake_images, f"G_out.jpg") # 直接将tensor保存为图片

    """
    5.4 异常检测AnoGAN部分
    """
    def anomaly_score(input_image, fake_image, D):
        # Residual loss 计算
        residual_loss = torch.sum(torch.abs(input_image - fake_image), (1, 2, 3))

        # Discrimination loss 计算
        _, real_feature = D(input_image)
        _, fake_feature = D(fake_image)
        discrimination_loss = torch.sum(torch.abs(real_feature - fake_feature), (1))

        # 结合Residual loss和Discrimination loss计算每张图像的损失
        total_loss_by_image = 0.9 * residual_loss + 0.1 * discrimination_loss
        # 计算总损失,即将一个batch的损失相加
        total_loss = total_loss_by_image.sum()

        return total_loss, total_loss_by_image, residual_loss

    # 加载测试数据
    test_set = image_data_set(test)
    test_loader = DataLoader(test_set, batch_size=5, shuffle=False)
    input_images = next(iter(test_loader)).to(device)

    # 定义潜在变量z  维度:(5,20,1,1)
    z = torch.randn(5, 20).to(device).view(5, 20, 1, 1)
    # z的requires_grad参数设置成Ture,让z可以更新
    z.requires_grad = True

    # 定义优化器
    z_optimizer = torch.optim.Adam([z], lr=1e-3)

    # 搜索z
    for epoch in range(5000):
        fake_images = G(z)
        loss, _, _ = anomaly_score(input_images, fake_images, D) # 传递到 异常检测函数中

        z_optimizer.zero_grad()
        loss.backward()
        z_optimizer.step()

        if epoch % 1000 == 0:
            print(f'epoch: {epoch}, loss: {loss:.0f}')

    fake_images = G(z)
    _, total_loss_by_image, _ = anomaly_score(input_images, fake_images, D)
    print(total_loss_by_image.cpu().detach().numpy()) # 对应图像的损失
    torchvision.utils.save_image(input_images,f"Nomal.jpg") # 输入图像
    torchvision.utils.save_image(fake_images, f"ANomal.jpg") # 生成图像

if __name__ == '__main__':
    main()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值