一、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()