卷积GAN生成人脸


卷积GAN生成人脸

数据集

  • CelebA数据集:CelebA是CelebFaces Attribute的缩写,意即名人人脸属性数据集,其包含10,177个名人身份的202,599张人脸图片,每张图片都做好了特征标记,包含人脸bbox标注框、5个人脸特征点坐标以及40个属性标记,CelebA由香港中文大学开放提供,广泛用于人脸相关的计算机视觉训练任务,可用于人脸属性标识训练、人脸检测训练以及landmark标记等。
  • 数据集处理

    • 图片预处理:将178*218的图片裁剪为128*128的图片

      def crop_centre(img, new_width, new_height):
          height, width, _ = img.shape
          startx = width // 2 - new_width // 2
          starty = height // 2 - new_height // 2
          return img[starty:starty + new_height, startx:startx + new_width, :]
    • 数据集处理:

      class FaceDataset(Dataset):
          def __init__(self, file_dir):
              self.image_dir = file_dir
              # self.img_list = random.sample(os.listdir(file_dir), 10000)
              # 先读取文件名称,需要时再读取。
              self.img_list = os.listdir(file_dir)
      
          def __len__(self):
              return len(self.img_list)
      
          def __getitem__(self, index):
              img = numpy.array(Image.open(os.path.join(self.image_dir, self.img_list[index])))
              # 裁剪图片
              img = crop_centre(img, 128, 128)  
              # permute(2,0,1)将numpy数组重新排序为(3,高度,宽度)。view(1,3,128,128)为批量大小增加了一个额外的维度,设置为1。
              img = torch.cuda.FloatTensor(img).permute(2, 0, 1).view(1, 3, 128, 128)
              return img / 255.0
      
          def plot_image(self, index):
              img = numpy.array(Image.open(os.path.join(self.image_dir, self.img_list[index])))
              img = crop_centre(img, 128, 128)
              plt.imshow(img)
              plt.show()

鉴别器

  • 鉴别器设计:鉴别器使用3个卷积层和1个最后的全连接层;

    • 第一个卷积层取3通道彩色图像,应用256个卷积核,输出256个特征图。使用的卷积核大小为8×8,步长为2,所以特征图的大小为61像素× 61像素。
    • 下一个卷积层也一样,有256个大小为8×8的卷积核,步长为2。从这一层中,我们仍然得到256个特征图,不过特征图大小缩小到27×27。当接近网络的末端时,我们需要考虑减少数据。
    • 下一个卷积层只有3个卷积核,但同样是8×8的大小和步长2,这就给了我们3个大小为10×10的特征图。我们需要将这300个值缩减到一个鉴别器的输出值。我们使用一个简单的全连接线性层来将它们映射到输出。
  • 代码实现:

    class View(nn.Module):
      def __init__(self, shape):
          super(View, self).__init__()
          self.shape = shape,
    
      def forward(self, x):
          return x.view(*self.shape)
    
    class Discriminator(nn.Module):
      def __init__(self):
          super(Discriminator, self).__init__()
          self.model = nn.Sequential(
              nn.Conv2d(3, 256, kernel_size=8, stride=2),  # (128 - 8) / 2 + 1
              nn.BatchNorm2d(256), # 标准化
              nn.LeakyReLU(0.2),
    
              nn.Conv2d(256, 256, kernel_size=8, stride=2),  # ((61 - 8 ) / 2 + 1
              nn.BatchNorm2d(256), # 标准化
              nn.LeakyReLU(0.2),
    
              nn.Conv2d(256, 3, kernel_size=8, stride=2),  # (27 - 8) / 2 + 1
              nn.LeakyReLU(0.2),
    
              View(3 * 10 * 10),
              nn.Linear(3 * 10 * 10, 1),
              nn.Sigmoid()
          )
    
          self.loss_func = nn.BCELoss()
    
          self.optimiser = torch.optim.Adam(self.parameters(), lr=0.0001)
          # self.optimiser.param_groups[0]['capturable'] = True  # pytroch-1.12.0 的bug
    
          # 记录训练进展的计数器和列表
          self.counter = 0
          self.progress = []
    
      def forward(self, inputs):
          return self.model(inputs)
    
      def train(self, inputs, targets):
          outputs = self.forward(inputs)
          loss = self.loss_func(outputs, targets)
          # 梯度归零,反向传播,并更新权重
          self.optimiser.zero_grad()
          loss.backward()
          self.optimiser.step()
          self.counter += 1
    
          if self.counter % 10 == 0:
              self.progress.append(loss.item())
    
      def plot_progress(self):
          plt.scatter([i for i in range(len(self.progress))], self.progress, s=1)
          plt.show()
    

生成器

  • 生成器设计:

    • 反卷积将较小的张量扩展成较大的张量。PyTorch将这种反向卷积称为转置卷积(transposed convolution),需要调用的模块是nn.ConvTranspose2d。
    • 设计一个神经网络,用于将张量扩展到合适大小,需要经过几个步骤。起始端有一个全连接层,它将100个种子值映射到3×11×11的张量。接着,它被转换成转置卷积层所需要的四维(1,3,11,11)张量。最后一步,转置卷积层需要一个额外的设置,即补全padding=1,作用是从中间网格中去掉外围的方格。如果没有补全,要想让输出的大小为(1,3,128,128),则需要增加网络本身的参数。
  • 生成器代码

    class Generator(nn.Module):
     def __init__(self):
         super(Generator, self).__init__()
         self.model = nn.Sequential(  # 输入是一个一维数组
             nn.Linear(100, 3 * 11 * 11),
             nn.LeakyReLU(0.2),
             # 转换成四维
             View((1, 3, 11, 11)),
             nn.ConvTranspose2d(3, 256, kernel_size=8, stride=2),  # (11-1)*2-2*0+1*(8-1)+1
             nn.BatchNorm2d(256),
             nn.LeakyReLU(0.2),
             nn.ConvTranspose2d(256, 256, kernel_size=8, stride=2),  # (28-1)*2-2*0+1*(8-1)+1
             nn.BatchNorm2d(256),
             nn.LeakyReLU(0.2),
             nn.ConvTranspose2d(256, 3, kernel_size=8, stride=2, padding=1),  # (60-1)*2+2*1+1*(8-1)+1
             nn.BatchNorm2d(3),
             nn.Sigmoid()
         )
         # self.loss_func = nn.BCELoss()
         self.optimiser = torch.optim.Adam(self.parameters(), lr=0.0001)
         # self.optimiser.param_groups[0]['capturable'] = True
    
         # 记录训练进展的计数器和列表
         self.counter = 0
         self.progress = []
    
     def forward(self, inputs):
         return self.model(inputs)
    
     def train(self, D: Discriminator, inputs, outputs):
         g_outputs = self.forward(inputs)
         d_outputs = D.forward(g_outputs)
         loss = D.loss_func(d_outputs, outputs)
         self.optimiser.zero_grad()
         loss.backward()
         self.optimiser.step()
    
         # 每隔10个训练样本增加一次计数器的值,并将损失值添加进列表的末尾
         self.counter += 1
    
         if self.counter % 10 == 0:
             self.progress.append(loss.item())
    
     def plot_progress(self):
         plt.scatter([i for i in range(len(self.progress))], self.progress, s=1)
         plt.show()
    
    

训练GAN

  • 我们向鉴别器展示一幅实际数据集中的图像,并让它对图像进行分类。输出应为1.0,我们再用损失来更新鉴别器。
  • 第2步同样是训练鉴别器,不过这一次我们向它展示的是生成器的图像。输出的结果应该是0.0。我们只用损失来更新鉴别器。在这一步中,我们必须注意不要更新生成器。因为我们不希望它因为被鉴别器识破而受到奖励。
  • 第3步是训练生成器。我们先用它生成一个图像,并将生成的图像输入给鉴别器进行分类。鉴别器的预期输出应该是1.0。我们希望生成器能成功骗过鉴别器,让它误以为图像是真实的,而不是生成的。我们只用结果的损失来更新生成器,而不更新鉴别器。

    
    def generate_random_image(size):
        random_data = torch.rand(size)
        return random_data
    
    
    def generate_random_seed(size):
        random_data = torch.randn(size)
        return random_data
    
    
    if torch.cuda.is_available():
        torch.set_default_tensor_type(torch.cuda.FloatTensor)
        print("using cuda:", torch.cuda.get_device_name(0))
    
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    print(device)
    
    face_dataset = FaceDataset(r'C:\Users\ronie\Desktop\program\PyTorchLearn\dataset\face_img\img_align_celeba')
    D = Discriminator()
    D.to(device)
    G = Generator()
    G.to(device)
    
    epochs = 1
    t1 = time.time()
    i = 0
    for epoch in range(epochs):
    print('epoch:', epoch + 1)
    for image_data_tensor in face_dataset:
        D.train(image_data_tensor, torch.cuda.FloatTensor([1.0]))
        D.train(G.forward(generate_random_seed(100)).detach(), torch.cuda.FloatTensor([0.0]))
        # 训练生成器
        G.train(D, generate_random_seed(100), torch.cuda.FloatTensor([1.0]))
        i += 1
        if i % 100 == 0:
            print(i)
        if i % 10000 == 0:
            torch.save(D, os.path.join(r'C:\Users\ronie\Desktop\program\PyTorchLearn\pth\gan_face', 'Dface_cnn_with_{}pictures.pth'.format(i + 400000)))
            torch.save(G, os.path.join(r'C:\Users\ronie\Desktop\program\PyTorchLearn\pth\gan_face', 'Gface_cnn_with_{}pictures.pth'.format(i + 400000)))
    print((time.time() - t1) / 60)
    G.plot_progress()
    D.plot_progress()
    

训练结果

  • 查看训练

    for i in range(1, 2):
      G = torch.load(
          r'C:\Users\ronie\Desktop\program\PyTorchLearn\pth\gan_face\Gface_cnn_with_{}0000pictures.pth'.format(45))
    
      # 在3列2行的网格中生成图像
      f, axarr = plt.subplots(2, 3, figsize=(16, 8))
      for i in range(2):
          for j in range(3):
              output = G.forward(generate_random_seed(100))
              imgs = output.detach().permute(0, 2, 3, 1).view(128, 128, 3).cpu().numpy()
              axarr[i, j].imshow(imgs, interpolation='none', cmap='Blues')
      plt.show()
  • 训练结果图片

    • 训练二十万张图片后
    • 训练三十万张图片后
    • 训练四十万张图片后
    • 全连接神经网络训练36000张图片后
  • 长的有鼻子有眼儿的

声明:Hello World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 卷积GAN生成人脸


我的朋友,理论是灰色的,而生活之树是常青的!