GAN网络无监督对抗网络

1、在MINST数据集中,选出一个样本,输入数字标签,输出图像,并让输出的图像与样本图像尽可能相似,总误差最小化; 2、同上,只不过并不直接比较输出和样本相似性,而是让一个已训练好的手写数字识别网络来判断这个伪造的图像是几; 3、DCGAN,同时训练一个生成器一个判别器。每个时刻随机采样一个向量输入给生成器,它输出一张图像,同时读取一个数据样本,判别器判断样本图像 和生成图像的真假。

# 导入所需要的包,请保证torchvision已经在你的环境中安装好.
# 在Windows需要单独安装torchvision包,在命令行运行pip install torchvision即可
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.optim as optim
import torch.nn.functional as F

import torchvision.datasets as dsets
import torchvision.transforms as transforms
import torchvision.utils as vutil


import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline
# 定义超参数
image_size = 28 #图像尺寸大小
input_dim = 100 #输入给生成器的向量维度,维度越大可以增加生成器输出样本的多样性
num_channels = 1# 图像的通道数
num_features = 64 #生成器中间的卷积核数量
batch_size = 64 #批次大小

# 如果系统中存在着GPU,z我们将用GPU来完成张量的计算
use_cuda = torch.cuda.is_available() #定义一个布尔型变量,标志当前的GPU是否可用

# 如果当前GPU可用,则将优先在GPU上进行张量计算
dtype = torch.cuda.FloatTensor if use_cuda else torch.FloatTensor
itype = torch.cuda.LongTensor if use_cuda else torch.LongTensor

# 加载MINIST数据,如果没有下载过,就会在当前路径下新建/data子目录,并把文件存放其中
# MNIST数据是属于torchvision包自带的数据,所以可以直接调用。
# 在调用自己的数据的时候,我们可以用torchvision.datasets.ImageFolder或者torch.utils.data.TensorDataset来加载
train_dataset = dsets.MNIST(root='./data',  #文件存放路径
                            train=True,   #提取训练集
                            transform=transforms.ToTensor(),  #将图像转化为Tensor,在加载数据的时候,就可以对图像做预处理
                            download=True) #当找不到文件的时候,自动下载

# 加载测试数据集
test_dataset = dsets.MNIST(root='./data', 
                           train=False, 
                           transform=transforms.ToTensor())

# 训练数据集的加载器,自动将数据分割成batch,顺序随机打乱
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, 
                                           batch_size=batch_size, 
                                           shuffle=True)

'''我们希望将测试数据分成两部分,一部分作为校验数据,一部分作为测试数据。
校验数据用于检测模型是否过拟合,并调整参数,测试数据检验整个模型的工作'''


# 首先,我们定义下标数组indices,它相当于对所有test_dataset中数据的编码
# 然后定义下标indices_val来表示校验集数据的那些下标,indices_test表示测试集的下标
indices = range(len(test_dataset))
indices_val = indices[:5000]
print(indices_val)
indices_test = indices[5000:]

# 根据这些下标,构造两个数据集的SubsetRandomSampler采样器,它会对下标进行采样
sampler_val = torch.utils.data.sampler.SubsetRandomSampler(indices_val)
print(sampler_val)
sampler_test = torch.utils.data.sampler.SubsetRandomSampler(indices_test)

# 根据两个采样器来定义加载器,注意将sampler_val和sampler_test分别赋值给了validation_loader和test_loader
validation_loader = torch.utils.data.DataLoader(dataset =test_dataset,
                                                batch_size = batch_size,
                                                sampler = sampler_val
                                               )
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, 
                                          batch_size=batch_size, 
                                          sampler = sampler_test
                                         )

#生成器模型定义


class ModelG(nn.Module):
def __init__(self):
super(ModelG,self).__init__()
self.model=nn.Sequential() #model为一个内嵌的序列化的神经网络模型

# 利用add_module增加一个反卷积层,输入为input_dim维,输出为2*num_features维,窗口大小为5,padding是0
# 输入图像大小为1,输出图像大小为W'=(W-1)S-2P+K+P'=(1-1)*2-2*0+5+0=3, 5*5
self.model.add_module('deconv1',nn.ConvTranspose2d(input_dim, num_features*2, 5, 2, 0, bias=False))
# 增加一个batchnorm层
self.model.add_module('bnorm1',nn.BatchNorm2d(num_features*2))
# 增加非线性层
self.model.add_module('relu1',nn.ReLU(True))
# 增加第二层反卷积层,输入2*num_features维,输出num_features维,窗口5,padding=0
# 输入图像大小为5,输出图像大小为W'=(W-1)S-2P+K+P'=(5-1)*2-2*0+5+0=13, 13*13
self.model.add_module('deconv2',nn.ConvTranspose2d(num_features*2, num_features, 5, 2, 0, bias=False))
# 增加一个batchnorm层
self.model.add_module('bnorm2',nn.BatchNorm2d(num_features))
# 增加非线性层
self.model.add_module('relu2',nn.ReLU(True))


# 增加第二层反卷积层,输入2*num_features维,输出num_features维,窗口4,padding=0
# 输入图像大小为13,输出图像大小为W'=(W-1)S-2P+K+P'=(13-1)*2-2*0+4+0=28, 28*28
self.model.add_module('deconv3',nn.ConvTranspose2d(num_features, num_channels, 4, 2, 0,bias=False))
#self.model.add_module('tanh',nn.Tanh())
self.model.add_module('sigmoid',nn.Sigmoid())
def forward(self,input):
output = input

#遍历网络的所有层,一层层输出信息
for name, module in self.model.named_children():
output = module(output)
#输出一张28*28的图像
return(output)



def weight_init(m):
#模型参数初始化.
#默认的初始化参数卷积核的权重是均值大概为0,方差在10^{-2}. BatchNorm层的权重均值是大约0.5,方差在0.2左右
#使用如下初始化方式可以,可以让方差更小,使得收敛更快
class_name=m.__class__.__name__
if class_name.find('conv')!=-1:
m.weight.data.normal_(0,0.02)
if class_name.find('norm')!=-1:
m.weight.data.normal_(1.0,0.02)
def make_show(img):
# 将张量变成可以显示的图像
img = img.data.expand(batch_size, 3, image_size, image_size)
return img
def imshow(inp, title=None, ax=None):
# 在屏幕上绘制图像
"""Imshow for Tensor."""
if inp.size()[0] > 1:
inp = inp.numpy().transpose((1, 2, 0))
else:
inp = inp[0].numpy()
mvalue = np.amin(inp)
maxvalue = np.amax(inp)
if maxvalue > mvalue:
inp = (inp - mvalue)/(maxvalue - mvalue)
ax.imshow(inp)
if title is not None:
ax.set_title(title)
ModelG()

 

 


#训练模型

print('Initialized!')

#定义生成器模型
net = ModelG()
net = net.cuda() if use_cuda else net

#目标函数采用最小均方误差
criterion = nn.MSELoss()
#定义优化器
optimizer = optim.SGD(net.parameters(), lr=0.0001, momentum=0.9)

# 随机选择生成0-9的数字,用于每个周期打印查看结果用
samples = np.random.choice(10, batch_size)
samples = torch.from_numpy(samples).type(dtype).requires_grad_(False)

#开始训练
step = 0 #计数经历了多少时间步
num_epochs = 100 #总的训练周期
record = []
for epoch in range(num_epochs):
train_loss = []

# 加载数据批次
for batch_idx, (data, target) in enumerate(train_loader):
# 注意数据中的data转化为了要预测的target,数据中的target则转化成了输入给网络的标签
target, data = data.clone().detach().requires_grad_(True), target.clone().detach() #data为一批图像,target为一批标签
# 将数据加载到GPU中
if use_cuda:
target, data = target.cuda(), data.cuda()
#将输入的数字标签转化为生成器net能够接受的(batch_size, input_dim, 1, 1)维张量
data = data.type(dtype)
data = data.resize(data.size()[0], 1, 1, 1)
data = data.expand(data.size()[0], input_dim, 1, 1)

net.train() # 给网络模型做标记,标志说模型正在训练集上训练,
#这种区分主要是为了打开关闭net的training标志
output = net(data) #神经网络完成一次前馈的计算过程,得到预测输出output
loss = criterion(output, target) #将output与标签target比较,计算误差
optimizer.zero_grad() #清空梯度
loss.backward() #反向传播
optimizer.step() #一步随机梯度下降算法
step += 1
# 记载损失函数值
if use_cuda:
loss = loss.cpu()
train_loss.append(loss.data.numpy())


if step % 100 == 0: #每间隔100个batch执行一次打印等操作
net.eval() # 给网络模型做标记,标志说模型在校验集上运行
val_loss = [] #记录校验数据集准确率的容器

'''开始在校验数据集上做循环,计算校验集上面的准确度'''
idx = 0
for (data, target) in validation_loader:
target, data = data.clone().detach().requires_grad_(True), target.clone().detach()
idx += 1
if use_cuda:
target, data = target.cuda(), data.cuda()
data = data.type(dtype)
data = data.resize(data.size()[0], 1, 1, 1)
data = data.expand(data.size()[0], input_dim, 1, 1)
output = net(data) #完成一次前馈计算过程,得到目前训练得到的模型net在校验数据集上的表现
loss = criterion(output, target) #将output与标签target比较,计算误差
if use_cuda:
loss = loss.cpu()
val_loss.append(loss.data.numpy())
#打印误差等数值,其中正确率为本训练周期Epoch开始后到目前撮的正确率的平均值
print('训练周期: {} [{}/{} ({:.0f}%)]\t训练数据Loss: {:.6f}\t校验数据Loss: {:.6f}'.format(
epoch, batch_idx * batch_size, len(train_loader.dataset),
100. * batch_idx / len(train_loader), np.mean(train_loss), np.mean(val_loss)))
record.append([np.mean(train_loss), np.mean(val_loss)])


# 产生一组图像保存到temp1文件夹下(需要事先建立好该文件夹),检测生成器当前的效果
# 改变输入数字的尺寸,适应于生成器网络
samples.data.resize_(batch_size,1,1,1)
samples = samples.data.expand(batch_size, input_dim, 1, 1)
samples = samples.cuda() if use_cuda else samples #加载到GPU
fake_u=net(samples) #用原始网络作为输入,得到伪造的图像数据
fake_u = fake_u.cpu() if use_cuda else fake_u
img = make_show(fake_u) #将张量转化成可绘制的图像
vutil.save_image(img,'temp1/fake%s.png'% (epoch)) #保存生成的图像

 

 

 

plt.figure(figsize = (10, 7))
plt.plot([i[0] for i in record], label='Training')
plt.plot([i[1] for i in record], label='Validation')
plt.xlabel('Batchs')
plt.ylabel('Loss')
plt.legend()

# 绘制一批图像样本
fake_u = fake_u.cpu() if use_cuda else fake_u
samples = samples.cpu() if use_cuda else samples
img = fake_u.data #将张量转化成可绘制的图像
fig = plt.figure(figsize = (15, 15))
# Two subplots, the axes array is 1-d

f, axarr = plt.subplots(8,8, sharex=True, figsize=(15,15))
for i in range(batch_size):
    
    axarr[i // 8, i % 8].axis('off')
    imshow(img[i], 
           samples.data.numpy()[i][0,0,0].astype(int),
           axarr[i // 8, i % 8])

 

# 定义待迁移的网络框架,所有的神经网络模块包括:Conv2d、MaxPool2d,Linear等模块都不需要重新定义,会自动加载
# 但是网络的forward功能没有办法自动实现,需要重写。
# 一般的,加载网络只加载网络的属性,不加载方法
depth = [4, 8]
class ConvNet(nn.Module):
    def __init__(self):
        super(ConvNet, self).__init__()
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool(x)
        x = F.relu(self.conv2(x))
        x = self.pool(x)
        # 将立体的Tensor全部转换成一维的Tensor。两次pooling操作,所以图像维度减少了1/4
        x = x.view(-1, image_size // 4 * image_size // 4 * depth[1])
        x = F.relu(self.fc1(x)) #全链接,激活函数
        x = F.dropout(x, training=self.training) #以默认为0.5的概率对这一层进行dropout操作
        x = self.fc2(x) #全链接,激活函数
        x = F.log_softmax(x, dim = 1) #log_softmax可以理解为概率对数值
        return x
    def retrieve_features(self, x):
        #该函数专门用于提取卷积神经网络的特征图的功能,返回feature_map1, feature_map2为前两层卷积层的特征图
        feature_map1 = F.relu(self.conv1(x)) #完成第一层卷积
        x = self.pool(feature_map1)  # 完成第一层pooling
        feature_map2 = F.relu(self.conv2(x)) #第二层卷积,两层特征图都存储到了feature_map1, feature_map2中
        return (feature_map1, feature_map2)
def rightness(predictions, labels):
    """计算预测错误率的函数,其中predictions是模型给出的一组预测结果,batch_size行num_classes列的矩阵,labels是数据之中的正确答案"""
    pred = torch.max(predictions.data, 1)[1] # 对于任意一行(一个样本)的输出值的第1个维度,求最大,得到每一行的最大元素的下标
    rights = pred.eq(labels.data.view_as(pred)).sum() #将下标与labels中包含的类别进行比较,并累计得到比较正确的数量
    return rights, len(labels) #返回正确的数量和这一次一共比较了多少元素

我们加入了一个识别器,它通过固定值的方式迁移自一个手写体识别器 然后让生成器生成图像,并让识别器进行识别,将识别的误差作为目标函数,调整生成器,从而能给出正确的分类标签

# 定义待迁移的网络框架,所有的神经网络模块包括:Conv2d、MaxPool2d,Linear等模块都不需要重新定义,会自动加载
# 但是网络的forward功能没有办法自动实现,需要重写。
# 一般的,加载网络只加载网络的属性,不加载方法
depth = [4, 8]
class ConvNet(nn.Module):
    def __init__(self):
        super(ConvNet, self).__init__()
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool(x)
        x = F.relu(self.conv2(x))
        x = self.pool(x)
        # 将立体的Tensor全部转换成一维的Tensor。两次pooling操作,所以图像维度减少了1/4
        x = x.view(-1, image_size // 4 * image_size // 4 * depth[1])
        x = F.relu(self.fc1(x)) #全链接,激活函数
        x = F.dropout(x, training=self.training) #以默认为0.5的概率对这一层进行dropout操作
        x = self.fc2(x) #全链接,激活函数
        x = F.log_softmax(x, dim = 1) #log_softmax可以理解为概率对数值
        return x
    def retrieve_features(self, x):
        #该函数专门用于提取卷积神经网络的特征图的功能,返回feature_map1, feature_map2为前两层卷积层的特征图
        feature_map1 = F.relu(self.conv1(x)) #完成第一层卷积
        x = self.pool(feature_map1)  # 完成第一层pooling
        feature_map2 = F.relu(self.conv2(x)) #第二层卷积,两层特征图都存储到了feature_map1, feature_map2中
        return (feature_map1, feature_map2)
def rightness(predictions, labels):
    """计算预测错误率的函数,其中predictions是模型给出的一组预测结果,batch_size行num_classes列的矩阵,labels是数据之中的正确答案"""
    pred = torch.max(predictions.data, 1)[1] # 对于任意一行(一个样本)的输出值的第1个维度,求最大,得到每一行的最大元素的下标
    rights = pred.eq(labels.data.view_as(pred)).sum() #将下标与labels中包含的类别进行比较,并累计得到比较正确的数量
    return rights, len(labels) #返回正确的数量和这一次一共比较了多少元素

netR = torch.load('minst_conv_checkpoint') #读取硬盘上的minst_conv_checkpoint文件
netR = netR.cuda() if use_cuda else netR #加载到GPU中
for para in netR.parameters():
para.requires_grad = False #将识别器的权重设置为固定值
netR

 

 

 

#开始训练

print('Initialized!')

netG = ModelG() #新建一个生成器
netG = netG.cuda() if use_cuda else netG #加载到GPU上
netG.apply(weight_init) #初始化参数

criterion = nn.CrossEntropyLoss() #用交叉熵作为损失函数
optimizer = optim.SGD(netG.parameters(), lr=0.0001, momentum=0.9) #定义优化器

#随机选择batch_size个数字,用他们来生成数字图像
samples = np.random.choice(10, batch_size)
samples = torch.from_numpy(samples).type(dtype).requires_grad_(False)

num_epochs = 100 #总训练周期
statistics = [] #数据记载器
for epoch in range(num_epochs):
    train_loss = []
    train_rights = []
    
    # 加载数据
    for batch_idx, (data, target) in enumerate(train_loader):
        # 注意图像和标签互换了
        target, data = data.clone().detach().requires_grad_(True), target.clone().detach() #data为一批标签,target为一批图像
        if use_cuda:
            target, data = target.cuda(), data.cuda()
        # 复制标签变量放到了label中
        label = data.clone()
        data = data.type(dtype)
        # 改变张量形状以适用于生成器网络
        data = data.resize(data.size()[0], 1, 1, 1)
        data = data.expand(data.size()[0], input_dim, 1, 1)

        netG.train() # 给网络模型做标记,标志说模型正在训练集上训练,
        netR.train() #这种区分主要是为了打开关闭net的training标志,从而决定是否运行dropout
        output1 = netG(data) #神经网络完成一次前馈的计算过程,得到预测输出output
        output = netR(output1) #用识别器网络来做分类
        loss = criterion(output, label) #将output与标签target比较,计算误差
        optimizer.zero_grad() #清空梯度
        loss.backward() #反向传播
        optimizer.step() #一步随机梯度下降算法
        step += 1
        if use_cuda:
            loss = loss.cpu()
        train_loss.append(loss.data.numpy())
        right = rightness(output, label) #计算准确率所需数值,返回数值为(正确样例数,总样本数)
        train_rights.append(right) #将计算结果装到列表容器train_rights中
        
        if step % 100 == 0: #每间隔100个batch执行一次打印等操作
            
            netG.eval() # 给网络模型做标记,标志说模型正在校验集上运行,
            netR.eval() #这种区分主要是为了打开关闭net的training标志,从而决定是否运行dropout
            val_loss = [] #记录校验数据集准确率的容器
            val_rights = []
            
            '''开始在校验数据集上做循环,计算校验集上面的准确度'''
            for (data, target) in validation_loader:
                # 注意target是图像,data是标签
                target, data = data.clone().detach().requires_grad_(True), target.clone().detach()
                if use_cuda:
                    target, data = target.cuda(), data.cuda()
                label = data.clone()
                data = data.type(dtype)
                #改变Tensor大小以适应生成网络
                data = data.resize(data.size()[0], 1, 1, 1)
                data = data.expand(data.size()[0], input_dim, 1, 1)
                
                output1 = netG(data) #神经网络完成一次前馈的计算过程,得到预测输出output
                output = netR(output1) #利用识别器来识别
                loss = criterion(output, label) #将output与标签target比较,计算误差
                if use_cuda:
                    loss = loss.cpu()
                val_loss.append(loss.data.numpy())
                right = rightness(output, label) #计算准确率所需数值,返回正确的数值为(正确样例数,总样本数)
                val_rights.append(right)
            # 分别计算在目前已经计算过的测试数据集,以及全部校验集上模型的表现:分类准确率
            #train_r为一个二元组,分别记录目前已经经历过的所有训练集中分类正确的数量和该集合中总的样本数,
            #train_r[0]/train_r[1]就是训练集的分类准确度,同样,val_r[0]/val_r[1]就是校验集上的分类准确度
            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
            #val_r为一个二元组,分别记录校验集中分类正确的数量和该集合中总的样本数
            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))
            print(('训练周期: {} [{}/{} ({:.0f}%)]\t训练数据Loss: {:.6f},正确率: {:.2f}%\t校验数据Loss:' +
                  '{:.6f},正确率:{:.2f}%').format(epoch, batch_idx * batch_size, len(train_loader.dataset),
                100. * batch_idx / len(train_loader), np.mean(train_loss), 
                                               100. * train_r[0].numpy() / train_r[1], 
                                               np.mean(val_loss), 
                                               100. * val_r[0].numpy() / val_r[1]))
            #记录中间的数据
            statistics.append({'loss':np.mean(train_loss),'train': 100. * train_r[0] / train_r[1],
                               'valid':100. * val_r[0] / val_r[1]})
            
    # 产生一组图像保存到temp1文件夹下(需要事先建立好该文件夹),检测生成器当前的效果
    samples.data.resize_(batch_size,1,1,1)
    samples = samples.data.expand(batch_size, input_dim, 1, 1)
    samples = samples.cuda() if use_cuda else samples
    fake_u=netG(samples)
    fake_u = fake_u.cpu() if use_cuda else fake_u
    img = make_show(fake_u)
    vutil.save_image(img,'temp1/fake%s.png'% (epoch))
# 训练曲线
result1 = [100 - i['train'] for i in statistics]
result2 = [100 - i['valid'] for i in statistics]
plt.figure(figsize = (10, 7))
plt.plot(result1, label = 'Training')
plt.plot(result2, label = 'Validation')
plt.xlabel('Step')
plt.ylabel('Error Rate')
plt.legend()

 

 

#绘制一批样本
samples = torch.Tensor([0,1,2,3,4,5,6,7,8,9])
samples = samples.type(dtype).clone()

sample_size = 10
samples.data.resize_(sample_size,1,1,1)
samples = samples.data.expand(sample_size, input_dim, 1, 1)
samples = samples.cuda() if use_cuda else samples
fake_u = netG(samples)
fake_u = fake_u.cpu() if use_cuda else fake_u
samples = samples.cpu() if use_cuda else samples
img = fake_u #.expand(sample_size, 3, image_size, image_size) #将张量转化成可绘制的图像
#fig = plt.figure(figsize = (15, 6))
f, axarr = plt.subplots(2,5, sharex=True, figsize=(15,6))

for i in range(sample_size):
    axarr[i // 5, i % 5].axis('off')
    imshow(img[i].data, samples.data.numpy()[i][0,0,0].astype(int), axarr[i // 5, i % 5])
    

 

DCGAN

同样的生成器,根据输入的一个随机噪声向量生成手写数字的图像,然后再同时训练一个辨别器,它的任务就是负责辨别一张输入的图像是来源于生成器 造假还是来源于原始的数据文件。我们将两个网络一起训练。值得注意的是,辨别器和生成器的目标函数是反的。

具体训练步骤:

1、读取一个batch的原始数据,将图像喂给辨别器,辨别器应该输出为真,计算误差:D_x,

2、用随机噪声输入生成器,生成器创造一个batch的假图片,将这些图片输入给辨别器,辨别器应该输出为假,计算误差D_x2

3、将两个误差和起来,反向传播训练辨别器

4、通过生成图像计算误差,对生成器进行反向传播更新梯度

# 构造判别器
class ModelD(nn.Module):
    def __init__(self):
        super(ModelD,self).__init__()
        self.model=nn.Sequential() #序列化模块构造的神经网络
        self.model.add_module('conv1',nn.Conv2d(num_channels, num_features, 5, 2, 0, bias=False)) #卷积层
        self.model.add_module('relu1',nn.ReLU()) #激活函数使用了ReLu
        #self.model.add_module('relu1',nn.LeakyReLU(0.2, inplace = True)) #激活函数使用了leakyReLu,可以防止dead ReLu的问题
        
        #第二层卷积
        self.model.add_module('conv2',nn.Conv2d(num_features, num_features * 2, 5, 2, 0, bias=False))
        self.model.add_module('bnorm2',nn.BatchNorm2d(num_features * 2)) 
        self.model.add_module('linear1', nn.Linear(num_features * 2 * 4 * 4,   #全链接网络层
                                                   num_features))
        self.model.add_module('linear2', nn.Linear(num_features, 1)) #全链接网络层
        self.model.add_module('sigmoid',nn.Sigmoid())
    def forward(self,input):
        output = input
        # 对网络中的所有神经模块进行循环,并挑选出特定的模块linear1,将feature map展平
        for name, module in self.model.named_children():
            if name == 'linear1':
                output = output.view(-1, num_features * 2 * 4 * 4)
            output = module(output)
        return output
# 构建一个生成器模型,并加载到GPU上
netG = ModelG().cuda() if use_cuda else ModelG()
# 初始化网络的权重
netG.apply(weight_init)
print(netG)

# 构建一个辨别器网络,并加载的GPU上
netD=ModelD().cuda() if use_cuda else ModelD()
# 初始化权重
netD.apply(weight_init)

# 要优化两个网络,所以需要有两个优化器
# 使用Adam优化器,可以自动调节收敛速度
#optimizerD=optim.SGD(netD.parameters(),lr=0.0002)
#optimizerG=optim.SGD(netG.parameters(),lr=0.0002)
optimizerD=optim.Adam(netD.parameters(),lr=0.0002,betas=(0.5,0.999))
optimizerG=optim.Adam(netG.parameters(),lr=0.0002,betas=(0.5,0.999))


# 模型的输入输出
# 生成一个随机噪声输入给生成器
noise=torch.tensor((batch_size, input_dim, 1, 1), dtype = torch.float, requires_grad = True)
#固定噪声是用于评估生成器结果的,它在训练过程中始终不变
fixed_noise=torch.FloatTensor(batch_size, input_dim, 1, 1).normal_(0,1).requires_grad_(True)
if use_cuda:
    noise = noise.cuda()
    fixed_noise = fixed_noise.cuda()


#BCE损失函数
criterion=nn.BCELoss()
error_G = None #总误差
num_epochs = 100 #训练周期
results = []
for epoch in range(num_epochs):
    
    for batch_idx, (data, target) in enumerate(train_loader):
        
        #训练辨别器网络
        #清空梯度
        optimizerD.zero_grad()
        #1、输入真实图片
        data, target = data.clone().detach().requires_grad_(True), target.clone().detach()
        # 用于鉴别赝品还是真品的标签
        label = torch.ones(data.size()[0])  #正确的标签是1(真实)
        label = label.cuda() if use_cuda else label
        
        if use_cuda:
            data, target, label = data.cuda(), target.cuda(), label.cuda()
        netD.train()
        output=netD(data) #放到辨别网络里辨别

        
        #计算损失函数
        label.data.fill_(1)
        error_real=criterion(output, label)
        error_real.backward() #辨别器的反向误差传播
        D_x=output.data.mean()
        
        #2、用噪声生成一张假图片
        noise.data.resize_(data.size()[0], input_dim, 1, 1).normal_(0, 1) #噪声是一个input_dim维度的向量
        #喂给生成器生成图像
        fake_pic=netG(noise).detach() #这里的detach是为了让生成器不参与梯度更新
        output2=netD(fake_pic) #用辨别器识别假图像
        label.data.fill_(0) #正确的标签应该是0(伪造)
        error_fake=criterion(output2,label) #计算损失函数
        error_fake.backward() #反向传播误差
        error_D=error_real + error_fake #计算真实图像和机器生成图像的总误差
        optimizerD.step() #开始优化
        # 单独训练生成器网络
        if error_G is None or np.random.rand() < 0.5:
            optimizerG.zero_grad() #清空生成器梯度

            '''注意生成器的目标函数是与辨别器的相反的,故而当辨别器无法辨别的时候为正确'''
            label.data.fill_(1) #分类标签全部标为1,即真实图像
            noise.data.normal_(0,1) #重新随机生成一个噪声向量
            netG.train()
            fake_pic=netG(noise) #生成器生成一张伪造图像
            output=netD(fake_pic) #辨别器进行分辨
            error_G=criterion(output,label) #辨别器的损失函数
            error_G.backward() #反向传播
            optimizerG.step() #优化网络
        if use_cuda:
            error_D = error_D.cpu()
            error_G = error_G.cpu()
        # 记录数据
        results.append([float(error_D.data.numpy()), float(error_G.data.numpy())])
        
        # 打印分类器损失等指标
        if batch_idx % 100 == 0:
            print ('第{}周期,第{}/{}撮, 分类器Loss:{:.2f}, 生成器Loss:{:.2f}'.format(
                epoch,batch_idx,len(train_loader),
                error_D.data.item(), 
                error_G.data.item()))
    #生成一些随机图片,但因输出到文件
    netG.eval()
    fake_u=netG(fixed_noise)
    fake_u = fake_u.cpu() if use_cuda else fake_u
    img = make_show(fake_u)

    #挑选一些真实数据中的图像图像保存
    data, _ = next(iter(train_loader))
    vutil.save_image(img,'temp/fake%s.png'% (epoch))
    # 保存网络状态到硬盘文件
    torch.save(netG.state_dict(), '%s/netG_epoch_%d.pth' % ('net', epoch))
    torch.save(netD.state_dict(), '%s/netD_epoch_%d.pth' % ('net', epoch))
    if epoch == 0:
        img = make_show(data.clone().detach().requires_grad_(True))
        vutil.save_image(img,'temp/real%s.png' % (epoch))

 

 

# 预测曲线
plt.figure(figsize = (10, 7))
plt.plot([i[1] for i in results], '.', label = 'Generator', alpha = 0.5)
plt.plot([i[0] for i in results], '.', label = 'Discreminator', alpha = 0.5)
plt.xlabel('Step')
plt.ylabel('Loss')
plt.legend()

 

 

# 绘制一些样本
noise = torch.FloatTensor(batch_size, input_dim, 1, 1)
noise.data.normal_(0,1)
noise = noise.cuda() if use_cuda else noise
sample_size = batch_size
netG.eval()
fake_u = netG(noise)
fake_u = fake_u.cpu() if use_cuda else fake_u
noise = noise.cpu() if use_cuda else samples
img = fake_u #.expand(sample_size, 3, image_size, image_size) #将张量转化成可绘制的图像
#print(img.size())
f, axarr = plt.subplots(8,8, sharex=True, figsize=(15,15))
for i in range(batch_size):
    axarr[i // 8, i % 8].axis('off')
    imshow(img[i].data, None,axarr[i // 8, i % 8])

 

posted @ 2020-05-21 11:49  Knight66666  阅读(1326)  评论(0编辑  收藏  举报