赞
踩
自然界中人类的特性可以概括两大特殊能力,分别是认识和创造。那么在深度学习-神经网络中,我们之前所学习的全连接神经网络、卷积神经网络等,它们都有一个共同的特点就是只实现了认识的功能,或者说是分类。那么如何让网络能够具有创造力,能根据我们的需求去自主地创造呢?换句话说,我们想让一直当评委/裁判的神经网络,现在能够自己上台去表演。这就是生成对抗网络的由来。
生成式对抗网络(GAN, Generative Adversarial Networks )是一种深度学习模型,它在2014年由Ian Goodfellow首次提出,该模型通过框架中(至少)两个模块:生成模块(Generative Model)和判别模块(Discriminative Model)的互相博弈学习产生相当好的输出。随后几年里,GAN飞速发展,产生了广泛的应用。并衍生出了很多流行的模型变种,比如DCGAN、C-GAN、WGAN、pix2pix等等。
GAN主要由生成器模型G和判别器模型D两大模块组成,原始 GAN 理论中,并不要求 G 和 D 都是神经网络,只需要是能拟合相应生成和判别的函数即可。但实用中一般均使用不同结构的深度神经网络作为 G 和 D 。其中:
- 生成器模型G:生成器是用来创造样本的。其输入一些随机噪声,通过生成网络输出我们需要的样本数据(二维图像数据等)
- 判别器模型D:判别器是用来识别真假的。其输入生成器生成的样本和真实样本,通过判别网络输出对样本数据的真假分类判别(二分类)。
GAN受博弈论中的零和博弈启发,将生成问题视作判别器和生成器这两个网络的对抗和博弈:生成器从给定噪声中(一般是指均匀分布或者正态分布)产生合成数据,判别器分辨生成器的的输出和真实数据。前者试图产生更接近真实的数据,相应地,后者试图更完美地分辨真实数据与生成数据。由此,两个网络在对抗中进步,在进步后继续对抗,由生成式网络得的数据也就越来越完美,逼近真实数据,从而可以生成想要得到的数据(图片、序列、视频等),二者在不断的对抗中逐步到达一种纳什均衡状态。
通俗点来说:假如你(G)是一个初级绘画师,你想提高你的绘画能力达到世界大师的水平。但是只靠你自己是无法成功的,所以你叫来了你的朋友(D)帮助你。你们训练的方式就是:你不断创作绘画作品,然后将你的绘画作品和世界大师的作品一起交给你的朋友鉴赏,然后你的朋友来分辨哪一个是你画的,哪一个是大师的。
- 对于你来说:每次朋友鉴赏完成后,告诉你他的分辨结果。然后你根据结果不断改进自己不足的地方。目的就是不断提高自己的水平,让你朋友分辨不出来哪一个是你画的哪一个是大师的。
- 对于你朋友来说:每次你创作完成之后,要对你的进步负责。他都要尽可能正确的将大师作品和你画的作品区分开来,提高自己的鉴赏水平。
在你们两个不断地博弈对抗的过程中,你的绘画水平不断提高,甚至能达到以假乱真的效果。而你的朋友的鉴赏水平不断提高,甚至真画假画一眼便知。你们两个在对抗中一起成长,达到平衡。
- 初始化生成器G和判别器D两个网络的初始参数。
- 固定生成器G的网络参数,从训练集抽取一个batch,生成器输入定义的随机噪声分布生成n个输出样本。
- 将真实训练样本数据与生成器输出样本数据拼接为判别器输入,并给以label真(1)和假(0),训练辨别器D,使其尽可能区分输入样本的真假。
- 这样循环训练更新k次判别器D之后,固定判别器参数。从训练集抽取一个batch,生成器输入定义的随机噪声分布生成n个输出样本。
- 将真实训练样本数据与生成器输出样本数据拼接为判别器输入,并给以label全真(1),训练辨别器D,使生成器输出的数据尽可能的真实,辨别器尽可能区分不了真假。
- 多轮这样的更新迭代后,理想状态下,最终辨别器D无法区分图片到底是来自真实的训练样本集合,还是来自生成器G生成的样本即可,此时辨别的概率为0.5,完成训练。或者达到相应的训练轮数阈值。
黑白图像的彩色化问题一直以来都是研究的热点,该问题旨在输入黑白图像,输出着色彩色化后的彩色图像。对于此问题可以看作是一个色彩生成问题,我们可以借助GAN网络来进行解决。
自然界中人们对于颜色的感受可以量化为色调、饱和度和亮度,其中色调表示颜色纯色的属性,比如红橙黄绿青蓝紫。饱和度表示色彩的鲜艳程度,纯色光越多饱和度越高,比如颜色的浓淡深浅。亮度描述颜色的明暗程度,可划分为黑灰白三个层次。颜色常用的量化定义分为三种类型,分别是RGB空间、YUV空间、Lab空间,三种颜色空间的说明如下:
- 图片数据集导入(使用DUTS数据集)
- 图片数据处理:将导入的彩色rgb图像转换为Lab格式图像,按照训练集:测试集划分为5:1的比例,并将图像的L通道分量复制作为黑白图像噪声用于生成器神经网络输入训练
- 设计实现生成网络模型:使用改进的U-Net模型作为生成网络,输入为L通道分量的单通道黑白图像,输出为预测的a、b通道,叠加L通道分量后形成Lab格式的预测彩色图片
- 设计实现判别网络模型:输入真实图片和生成器预测图片拼接的数据集,输出预测标签(fake or true)
- 网络训练:每一轮先训练k次判别网络,固定生成网络;再训练一次生成网络,固定判别网络。如此反复多轮直到达到一定的阈值。
- 模型测试:使用训练好的生成模型,输入L通道的黑白图像,输出预测彩色图片
- import numpy as np
- import torch
- from skimage import color
- from PIL import Image
- import torchvision.transforms as transforms
- from torchvision import utils
-
- #从(h,w,c)格式的Lab中拿到 标准化的Tensor L通道、ab通道
- def splitFromLab(image_lab):
- image_lab = image_lab.transpose((2, 0, 1)) #(c,h,w)
- image_l = image_lab[0]/100 #(h,w) L通道范围 0~100 -> 归一化到 0~1
- image_ab = image_lab[1:,:,:]/110 #(2,h,w) ab通道 -> 归一化到 -1~1
- image_l = torch.from_numpy(image_l).unsqueeze(0) #(1,h,w) Tensor
- image_ab = torch.from_numpy(image_ab) #(2,h,w) Tensor
- #返回标准化 L (1,h,w) + 真实图像lab(3,h,w)
- return image_l,torch.cat([image_l,image_ab],dim=0)
-
- def TransfertToRGB(image):
- #image (1,3,h,w) -> (3,h,w)
- image = image.squeeze(0)
- image[0,:,:]*=100
- image[1:,:,:]*=110
- image_ndarray = np.array(image)
- #image (3,h,w) -> (h,w,3)
- image_lab = image_ndarray.transpose((1,2,0))
- #image rgb (h,w,3)
- image_rgb = color.lab2rgb(image_lab)
- #image rgb (1,3,h,w)
- image_rgb = torch.from_numpy(image_rgb.transpose((2, 0, 1))).unsqueeze(0)
- return image_rgb
-
- def TransferBacktoWhite(path):
- transTensor = transforms.ToTensor() # 将PIL Image自动转化并归一化为tensor(c,h,w)
- img_path = path
- image = Image.open(img_path).convert("RGB") # 图片为 "RGB"格式 真彩图像 三通道 (h,w,c) 范围[0,255] -> 二值黑白图像.convert("1")
- image_tensor = transTensor(image)
- image_tensor[image_tensor>0.5] = 1
- image_tensor[image_tensor<=0.5] = 0
- save_path = r"D:\日常材料\图片照片\签名结果.png"
- # 注意,save_image将图像保存为RGB三通道,如果是二值图像则三个通道数值都相同,即伪灰度图像
- utils.save_image(image_tensor, save_path)
-
- if __name__ == "__main__":
- path = r"D:\日常材料\图片照片\导师签名2.png"
- TransferBacktoWhite(path)
-
- import os
- import numpy as np
- from PIL import Image
- from torch.utils.data import Dataset
- from util import *
- from skimage import color
- os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
-
- #构造自定义数据集
- class ModelDataset(Dataset):
- def __init__(self,image_dir):
- super(ModelDataset, self).__init__()
- self.image_dir = image_dir
- self.images = os.listdir(self.image_dir) #os.listdir()函数用于返回指定的文件夹包含的文件或文件夹的[名字]的列表。
-
- def __len__(self):
- return len(self.images)
-
- def __getitem__(self, index):
- #加载图片
- img_path = os.path.join(self.image_dir,self.images[index]) #os.path.join函数用于将字符串按照系统盘符拼接为路径
- image = Image.open(img_path).convert("RGB").resize((256,256)) #图片为 "RGB"格式 真彩图像 三通道 (h,w,c) 范围[0,255]
- # 图片转化为rgb Numpy (h,w,c)
- image_rgb = np.array(image)
- image_grey = np.array(image.convert("L"))/255
- # 将rgb空间 -> lab空间
- image_lab = color.rgb2lab(image_rgb)
- # 获取L、lab Tensor格式(c,h,w)归一化数据
- image_l,image_lab = splitFromLab(image_lab)
- return image_l,image_lab,torch.from_numpy(image_grey).unsqueeze(0)
-
-
- '''
- 图片处理:
- 1.PIL
- (1)PIL读取数据:Image.open() 返回Image对象,尺寸为(width,height)
- (2)PIL显示图像:Image.show():调用本地的图片浏览器显示
- (3)PIL Image转换到Numpy ndarray:np.array(Image),尺寸为(height,width,channel)
- (4)matplotlib显示ndarry图像:plt.imshow(img)+plt.show() ,要求img尺寸为(H, W, C)
- 2.skimage
- (1)skimage读取数据:io.imread(img_path) ,返回ndarray格式,尺寸为(height,width,channel)
- (2)skimage显示图像:直接使用plt.show()显示ndarray
- (3)skimage颜色空间转换
- - rgb -> lab: color.rgb2lab(rgb, illuminant='D65', observer='2', *, channel_axis=- 1) 默认将(h,w,c)的rgb ndarray图像转化为(h,w,c)的lab图像
- - lab -> rgb: color.lab2rgb(lab, illuminant='D65', observer='2', *, channel_axis=- 1) 默认将(h,w,c)的lab ndarray图像转化为(h,w,c)的rgb图像
- (4)注意:
- - 对于rgb to lab来说:
- a.如果输入rgb为[0,255]的int类型,则在转换时,函数会先将输入/255转换到[0,1]之间的float64(这叫gamma矫正),再计算lab通道。
- b.如果输入rgb为[0,1]的/255之后的标准化float64数据,则函数不会进行处理,直接拿来计算lab
- c.函数最终返回float64 的 lab空间图像ndarray
- d.该函数就是严格按照rgb2lab公式来的:先->xyz->lab,要算gamma(r/255)矫正->lab等等,所以求出来的lab取值范围就是 L[0,100],a[-110,110],b[-110,110]
- - 对于lab to rgb来说:输入lab空间矩阵,返回[0,1]之间标准化的rgb颜色矩阵
-
- 3.Pytorch
- (1)神经网络中训练要求数据格式必须为 (channel,height,width)
- (2)格式转换1:torchvision.transforms.ToTensor 把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloatTensor(除以255,自动进行归一化)
- (3)格式转换2:交换维度np.transpose( xxx, (2, 0, 1)) 将xxx (H x W x C)的ndarray 转化为 (C x H x W)的ndarray
- '''
- import torch
- import torch.nn as nn
-
- #1.生成器-卷积模块
- class ConvBlock(nn.Module):
- def __init__(self,in_channel,out_channel):
- super(ConvBlock, self).__init__()
- #构建 卷积块(进行两次卷积操作)
- self.layer = nn.Sequential(
- #第一次卷积操作
- nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=1,padding=1),#卷积操作 (batch,in_ch,h,w) -> (batch,out_ch,h,w) 不改变大小
- nn.BatchNorm2d(out_channel),#批标准化 将数据标准化到正态分布
- nn.ReLU(inplace=True),#激活函数 inplace=True表示覆盖输入数据(避免了临时变量频繁释放,提高效率)
- #第二次卷积操作
- nn.Conv2d(out_channel, out_channel, kernel_size=3, stride=1, padding=1),
- # 卷积操作 (batch,in_ch,h,w) -> (batch,out_ch,h,w) 不改变大小
- nn.BatchNorm2d(out_channel), # 批标准化 将数据标准化到正态分布
- nn.ReLU(inplace=True), # 激活函数 inplace=True表示覆盖输入数据(避免了临时变量频繁释放,提高效率)
- )
-
- def forward(self,x):
- return self.layer(x)
-
- #2.生成器-上采样模块:反卷积+拼接
- class DeConvBlock(nn.Module):
- def __init__(self,in_channel,out_channel):
- super(DeConvBlock, self).__init__()
- #上采样:反卷积 (batch,in_ch,h,w) -> (batch,out_ch,2h,2w)
- self.up = nn.ConvTranspose2d(in_channel,out_channel,kernel_size=2,stride=2)
-
- def forward(self,input_1,input_2):
- output_2 = self.up(input_2) #先上采样
- merge = torch.cat([input_1,output_2],dim=1) #跳跃连接,合并输入
- return merge #返回合并后结果
-
-
- #3.生成器网络
- class Generator(nn.Module):
- def __init__(self,in_channel,out_channel):
- super(Generator, self).__init__()
- filter_maps = [64,128,256,512,1024]
- self.pool = nn.MaxPool2d(2)
- # 编码器
- self.encoderConv1 = ConvBlock(in_channel, filter_maps[0])
- self.encoderConv2 = ConvBlock(filter_maps[0], filter_maps[1])
- self.encoderConv3 = ConvBlock(filter_maps[1], filter_maps[2])
- self.encoderConv4 = ConvBlock(filter_maps[2], filter_maps[3])
- self.encoderConv5 = ConvBlock(filter_maps[3], filter_maps[4])
- # 解码器
- self.upSimple1 = DeConvBlock(filter_maps[4], filter_maps[3])
- self.decoderConv1 = ConvBlock(filter_maps[4], filter_maps[3])
- self.upSimple2 = DeConvBlock(filter_maps[3], filter_maps[2])
- self.decoderConv2 = ConvBlock(filter_maps[3], filter_maps[2])
- self.upSimple3 = DeConvBlock(filter_maps[2], filter_maps[1])
- self.decoderConv3 = ConvBlock(filter_maps[2], filter_maps[1])
- self.upSimple4 = DeConvBlock(filter_maps[1], filter_maps[0])
- self.decoderConv4 = ConvBlock(filter_maps[1], filter_maps[0])
- # 输出
- self.final = nn.Conv2d(filter_maps[0], out_channel, kernel_size=1)
- self.out = nn.Tanh()
-
- def forward(self, x):
- # 编码,下采样过程
- en_x1 = self.encoderConv1(x) # 输出 (batch,64,256,256)
- down_x1 = self.pool(en_x1) # 输出 (batch,64,128,128)
- en_x2 = self.encoderConv2(down_x1) # 输出 (batch,128,128,128)
- down_x2 = self.pool(en_x2) # 输出 (batch,128,64,64)
- en_x3 = self.encoderConv3(down_x2) # 输出 (batch,256,64,64)
- down_x3 = self.pool(en_x3) # 输出(batch,256,32,32)
- en_x4 = self.encoderConv4(down_x3) # 输出(batch,512,32,32)
- down_x4 = self.pool(en_x4) # 输出(batch,512,16,16)
- en_x5 = self.encoderConv5(down_x4) # 输出(batch,1024,16,16)
- # 解码,上采样过程
- up_x1 = self.upSimple1(en_x4, en_x5) # 输出 (batch,1024,32,32)
- de_x1 = self.decoderConv1(up_x1) # 输出 (batch,512,32,32)
- up_x2 = self.upSimple2(en_x3, de_x1) # 输出 (batch,512,64,64)
- de_x2 = self.decoderConv2(up_x2) # 输出 (batch,256,64,64)
- up_x3 = self.upSimple3(en_x2, de_x2) # 输出 (batch,256,128,128)
- de_x3 = self.decoderConv3(up_x3) # 输出 (batch,128,128,128)
- up_x4 = self.upSimple4(en_x1, de_x3) # 输出 (batch,128,256,256)
- de_x4 = self.decoderConv4(up_x4) # 输出 (batch,64,256,256)
- # 输出
- return self.out(self.final(de_x4)) # 输出(batch,2,256,256) 图像ab通道 并标准化到(-1,1)
-
- #4.判别器-卷积模块
- class DiscriminatorBlock(nn.Module):
- def __init__(self,in_channel,out_channel):
- super(DiscriminatorBlock, self).__init__()
- #论文:使用stride=2来代替pool进行下采样,pool会损失信息!
- self.block = nn.Sequential(
- nn.Conv2d(in_channel, out_channel, kernel_size=4, stride=2, padding=1),
- nn.BatchNorm2d(out_channel),
- nn.LeakyReLU(0.2,inplace=True)
- )
- def forward(self,x):
- return self.block(x)
-
- #5.判别器网络
- class Discriminator(nn.Module):
- def __init__(self,in_channel):
- super(Discriminator, self).__init__()
- filter_maps = [32,64, 128, 256, 512]
- self.Conv1 = DiscriminatorBlock(in_channel,filter_maps[0])
- self.Conv2 = DiscriminatorBlock(filter_maps[0],filter_maps[1])
- self.Conv3 = DiscriminatorBlock(filter_maps[1],filter_maps[2])
- self.Conv4 = DiscriminatorBlock(filter_maps[2],filter_maps[3])
- self.Conv5 = DiscriminatorBlock(filter_maps[3],filter_maps[4])
- self.Conv6 = DiscriminatorBlock(filter_maps[4],filter_maps[4])
- self.out = nn.Conv2d(filter_maps[4],1,kernel_size=4,stride=1)
- self.cls = nn.Sigmoid()
-
- def forward(self,x):
- x = self.Conv1(x) #(b,32,128,128)
- x = self.Conv2(x) #(b,64,64,64)
- x = self.Conv3(x) #(b,128,32,32)
- x = self.Conv4(x) #(b,256,16,16)
- x = self.Conv5(x) #(b,512,8,8)
- x = self.Conv6(x) #(b,512,4,4)
- return self.cls(self.out(x)).view(-1) #(b,1,1,1) -> 一维(b)
- import os
- import torch
- from torch.utils.data import DataLoader
- from data import *
- from net import *
-
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')#调整设备,优先使用gpu
- img_dir = r"D:\日常材料\作业报告\机器学习\DUTS数据集\DUTS-TE\DUTS-TE-Image"
- weightD_path = "params/Dnet.pth"
- weightG_path = "params/Gnet.pth"
- batch_size = 4
- epoch = 10
- d_every = 1
- g_every = 2
-
- if __name__ == "__main__":
- #1.加载自己训练数据集的数据加载器
- data_loader = DataLoader(ModelDataset(img_dir),batch_size=batch_size,shuffle=True)
- #2.将模型加载到设备上
- Dnet,Gnet = Discriminator(3).to(device),Generator(1,2).to(device)
- #2.1加载预训练权重(如果有的话)
- if os.path.exists(weightD_path):
- Dnet.load_state_dict(torch.load(weightD_path))
- if os.path.exists(weightG_path):
- Gnet.load_state_dict(torch.load(weightG_path))
- #3.设置优化器和损失
- optim_D = torch.optim.Adam(Dnet.parameters(),lr=0.0001,betas=(0.5,0.999))
- optim_G = torch.optim.Adam(Gnet.parameters(),lr=0.0001,betas=(0.5,0.999))
- criterion = torch.nn.BCELoss()
- #4.设置真假标签(真为1,假为0)
- true_label = torch.ones(batch_size).to(device)
- fake_label = torch.zeros(batch_size).to(device)
- #4.开始训练
- for i in range(epoch):
- lossSum_D = 0.0
- lossSum_G = 0.0
-
- for index,(img_l,img_real,_) in enumerate(data_loader):
- #img_l,img_real = img_l.to(device),img_real.to(device) #将数据放到设备上
- img_l,img_real = img_l.type(torch.cuda.FloatTensor).to(device),img_real.type(torch.cuda.FloatTensor).to(device)
- if index % d_every==0:
- #训练判别器,固定生成器
- #1.训练真实图片,尽可能将真图片判别为正确
- output_real = Dnet(img_real)
- loss_real = criterion(output_real,true_label)
- #累计梯度
- optim_D.zero_grad()
- loss_real.backward()
- #2.训练假图片,尽可能将假图片判别为假
- output_ab = Gnet(img_l).detach()#使用detach截断计算图,防止判别器更新生成器
- img_fake = torch.cat([img_l,output_ab],dim=1).to(device)
- output_fake = Dnet(img_fake)
- loss_fake = criterion(output_fake,fake_label)
- #累计梯度
- loss_fake.backward()
- #3.更新判别网络
- optim_D.step()
- lossSum_D = lossSum_D + loss_real.item() + loss_fake.item()
- if index % g_every==0:
- # 训练生成器,固定判别器
- # 1.生成假图片
- output_ab = Gnet(img_l)
- img_fake = torch.cat([img_l, output_ab], dim=1).to(device)
- output_fake = Dnet(img_fake)
- #2.让假图片尽可能以假乱真
- loss_fakeTrue = criterion(output_fake,true_label)
- #更新参数
- optim_G.zero_grad()
- loss_fakeTrue.backward()
- optim_G.step()
- lossSum_G = lossSum_G + loss_fakeTrue.item()
-
- torch.save(Dnet.state_dict(),weightD_path)#每一轮都保存训练参数weight
- torch.save(Gnet.state_dict(),weightG_path)
- print("[epoch %d]: Dloss is %.3f and Gloss is %.3f" % (i+1,lossSum_D,lossSum_G))
- import os
- import torch
- from net import *
- from data import *
- from torch.utils.data import DataLoader
- from torchvision import utils
- from util import *
- import matplotlib.pyplot as plt
-
- image_dir = r"D:\日常材料\作业报告\机器学习\DUTS数据集\train_data"
- save_dir = r"D:\日常材料\作业报告\机器学习\DUTS数据集\color_result"
- weightG_path = "params/Gnet.pth"
-
- Gnet = Generator(1,2)
- if os.path.exists(weightG_path):
- Gnet.load_state_dict(torch.load(weightG_path))
- print("weights load successful")
- else:
- print("weights is not exist")
-
- Gnet.eval() #切换训练模式
- test_loader = DataLoader(ModelDataset(image_dir),batch_size=1,shuffle=False) #(1,c,h,w) 网络必须四维输入
-
- #不进行计算图构建
- with torch.no_grad():
- for index,(test_img_l,test_img_real,test_img_grey) in enumerate(test_loader):
- test_img_l,test_img_real = test_img_l.type(torch.FloatTensor),test_img_real.type(torch.FloatTensor)
- out_ab = Gnet(test_img_l)
- out_image = torch.cat([test_img_l,out_ab],dim=1)
- image_grey = torch.cat([test_img_grey,test_img_grey,test_img_grey],dim=1)
- image_real = TransfertToRGB(test_img_real)
- image_color = TransfertToRGB(out_image)
- # 将真实图像和预测图象拼接(拼接到batch上,构造雪碧网格图),也可以降维输出单个图像
- img = torch.cat([image_grey,image_real,image_color],dim=0)
- #保存图像
- save_path = os.path.join(save_dir,str(index)+".png")
- #注意,save_image将图像保存为RGB三通道,如果是二值图像则三个通道数值都相同,即伪灰度图像
- utils.save_image(img,save_path)
从左到右依次是:黑白图像、原彩色图像、预测输出彩色图像。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。