Home » Генерация лиц с помощью DCGAN
Генерация лиц с помощью DCGAN

Генерация лиц с помощью DCGAN

Вас приветствует статья о практической генерации лиц с использованием Deep Convolutional Generative Adversarial Networks (DCGAN). Если вы задумывались о том, как настроить собственный AI-генератор лиц на своем сервере, автоматизировать создание контента для проектов или просто хотите поэкспериментировать с машинным обучением — этот гайд именно для вас. Мы пройдем весь путь от теории до практической реализации, настроим окружение, разберем архитектуру и запустим генерацию. Бонусом — интеграция с различными пайплайнами и автоматизация через скрипты.

Как работает DCGAN и зачем это нужно

DCGAN — это архитектура нейросетей, состоящая из двух соревнующихся моделей: генератора и дискриминатора. Генератор создает изображения из случайного шума, а дискриминатор пытается отличить настоящие изображения от сгенерированных. В процессе обучения они “играют” друг против друга, что приводит к улучшению качества генерируемых изображений.

Основные преимущества DCGAN для генерации лиц:

  • Стабильность обучения по сравнению с обычными GAN
  • Возможность интерполяции в латентном пространстве
  • Относительно простая архитектура для понимания
  • Хорошие результаты на лицах при правильной настройке

Подготовка сервера и окружения

Для комфортной работы с DCGAN вам понадобится сервер с GPU. Рекомендую взять VPS с GPU или выделенный сервер с современной видеокартой.

Установим необходимые зависимости:


# Обновляем систему
sudo apt update && sudo apt upgrade -y

# Устанавливаем Python и pip
sudo apt install python3 python3-pip python3-venv git -y

# Создаем виртуальное окружение
python3 -m venv dcgan_env
source dcgan_env/bin/activate

# Устанавливаем PyTorch с поддержкой CUDA
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# Дополнительные библиотеки
pip install numpy matplotlib pillow tqdm opencv-python

Проверим, что CUDA доступна:


python3 -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}')"

Архитектура и реализация DCGAN

Создадим основную структуру проекта:


mkdir dcgan_faces && cd dcgan_faces
mkdir data models outputs logs

Основной код генератора и дискриминатора:


import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt

# Генератор
class Generator(nn.Module):
    def __init__(self, nz=100, ngf=64, nc=3):
        super(Generator, self).__init__()
        self.main = nn.Sequential(
            # Input: nz x 1 x 1
            nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(True),
            # State: (ngf*8) x 4 x 4
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(True),
            # State: (ngf*4) x 8 x 8
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(True),
            # State: (ngf*2) x 16 x 16
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(True),
            # State: (ngf) x 32 x 32
            nn.ConvTranspose2d(ngf, nc, 4, 2, 1, bias=False),
            nn.Tanh()
            # Output: nc x 64 x 64
        )

    def forward(self, input):
        return self.main(input)

# Дискриминатор
class Discriminator(nn.Module):
    def __init__(self, nc=3, ndf=64):
        super(Discriminator, self).__init__()
        self.main = nn.Sequential(
            # Input: nc x 64 x 64
            nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            # State: ndf x 32 x 32
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # State: (ndf*2) x 16 x 16
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # State: (ndf*4) x 8 x 8
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),
            # State: (ndf*8) x 4 x 4
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, input):
        return self.main(input).view(-1, 1).squeeze(1)

Подготовка данных и обучение

Скачаем датасет CelebA или используем собственный:


# Скрипт для загрузки и подготовки данных
import os
import urllib.request
import zipfile

def download_celeba():
    url = "https://drive.google.com/uc?id=0B7EVK8r0v71pZjFTYXZWM3FlRnM"
    filename = "img_align_celeba.zip"
    
    if not os.path.exists(filename):
        print("Downloading CelebA dataset...")
        urllib.request.urlretrieve(url, filename)
    
    if not os.path.exists("data/celeba"):
        print("Extracting dataset...")
        with zipfile.ZipFile(filename, 'r') as zip_ref:
            zip_ref.extractall("data/celeba")

# Трансформации для данных
transform = transforms.Compose([
    transforms.Resize(64),
    transforms.CenterCrop(64),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

# Загрузчик данных
dataset = datasets.ImageFolder("data/celeba", transform=transform)
dataloader = DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4)

Основной цикл обучения:


# Инициализация моделей
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
netG = Generator().to(device)
netD = Discriminator().to(device)

# Оптимизаторы
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))

# Функция потерь
criterion = nn.BCELoss()

# Обучение
num_epochs = 50
fixed_noise = torch.randn(64, 100, 1, 1, device=device)

for epoch in range(num_epochs):
    for i, data in enumerate(dataloader):
        # Обновляем дискриминатор
        netD.zero_grad()
        real_data = data[0].to(device)
        batch_size = real_data.size(0)
        
        # Настоящие данные
        label = torch.full((batch_size,), 1, dtype=torch.float, device=device)
        output = netD(real_data)
        errD_real = criterion(output, label)
        errD_real.backward()
        
        # Поддельные данные
        noise = torch.randn(batch_size, 100, 1, 1, device=device)
        fake = netG(noise)
        label.fill_(0)
        output = netD(fake.detach())
        errD_fake = criterion(output, label)
        errD_fake.backward()
        optimizerD.step()
        
        # Обновляем генератор
        netG.zero_grad()
        label.fill_(1)
        output = netD(fake)
        errG = criterion(output, label)
        errG.backward()
        optimizerG.step()
        
        if i % 100 == 0:
            print(f'[{epoch}/{num_epochs}][{i}/{len(dataloader)}] '
                  f'Loss_D: {errD_real.item() + errD_fake.item():.4f} '
                  f'Loss_G: {errG.item():.4f}')
    
    # Сохраняем результаты каждую эпоху
    if epoch % 5 == 0:
        with torch.no_grad():
            fake = netG(fixed_noise)
            save_image(fake, f'outputs/fake_samples_epoch_{epoch}.png', normalize=True)
        
        torch.save(netG.state_dict(), f'models/generator_epoch_{epoch}.pth')
        torch.save(netD.state_dict(), f'models/discriminator_epoch_{epoch}.pth')

Практические кейсы и примеры использования

Создадим скрипт для генерации лиц по требованию:


#!/usr/bin/env python3
# generate_faces.py

import torch
import argparse
from torchvision.utils import save_image
import os

def generate_faces(model_path, num_faces=16, output_dir="generated"):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    # Загружаем модель
    netG = Generator().to(device)
    netG.load_state_dict(torch.load(model_path, map_location=device))
    netG.eval()
    
    # Создаем директорию для вывода
    os.makedirs(output_dir, exist_ok=True)
    
    with torch.no_grad():
        noise = torch.randn(num_faces, 100, 1, 1, device=device)
        fake_faces = netG(noise)
        
        # Сохраняем как сетку
        save_image(fake_faces, f"{output_dir}/generated_faces.png", 
                  normalize=True, nrow=4)
        
        # Сохраняем отдельные файлы
        for i in range(num_faces):
            save_image(fake_faces[i], f"{output_dir}/face_{i:03d}.png", 
                      normalize=True)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--model", required=True, help="Path to generator model")
    parser.add_argument("--num", type=int, default=16, help="Number of faces to generate")
    parser.add_argument("--output", default="generated", help="Output directory")
    
    args = parser.parse_args()
    generate_faces(args.model, args.num, args.output)

Скрипт для интерполяции между лицами:


# interpolate_faces.py
def interpolate_faces(model_path, steps=10):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    netG = Generator().to(device)
    netG.load_state_dict(torch.load(model_path, map_location=device))
    netG.eval()
    
    # Два случайных вектора
    z1 = torch.randn(1, 100, 1, 1, device=device)
    z2 = torch.randn(1, 100, 1, 1, device=device)
    
    with torch.no_grad():
        interpolated_faces = []
        for i in range(steps):
            alpha = i / (steps - 1)
            z_interp = (1 - alpha) * z1 + alpha * z2
            face = netG(z_interp)
            interpolated_faces.append(face)
        
        # Сохраняем последовательность
        all_faces = torch.cat(interpolated_faces, dim=0)
        save_image(all_faces, "interpolation.png", normalize=True, nrow=steps)

Сравнение с другими решениями

Метод Качество Скорость обучения Требования к ресурсам Стабильность
DCGAN Среднее Быстрая Средние Хорошая
StyleGAN Очень высокое Медленная Высокие Отличная
Progressive GAN Высокое Очень медленная Высокие Хорошая
VAE Низкое Быстрая Низкие Отличная

Оптимизация и улучшения

Для повышения качества генерации можно применить следующие техники:

  • Spectral Normalization для стабилизации обучения
  • Progressive Growing для увеличения разрешения
  • Self-Attention для лучшей детализации
  • Различные функции потерь (WGAN, LSGAN)

Модифицированный дискриминатор с Spectral Normalization:


from torch.nn.utils import spectral_norm

class ImprovedDiscriminator(nn.Module):
    def __init__(self, nc=3, ndf=64):
        super(ImprovedDiscriminator, self).__init__()
        self.main = nn.Sequential(
            spectral_norm(nn.Conv2d(nc, ndf, 4, 2, 1, bias=False)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False)),
            nn.Sigmoid()
        )

Автоматизация и интеграция

Создадим REST API для генерации лиц:


# api_server.py
from flask import Flask, request, send_file
import torch
import io
import base64
from PIL import Image
import torchvision.transforms as transforms

app = Flask(__name__)

# Глобальная модель
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
netG = Generator().to(device)
netG.load_state_dict(torch.load("models/generator_final.pth", map_location=device))
netG.eval()

@app.route('/generate', methods=['POST'])
def generate_face():
    try:
        data = request.json
        num_faces = data.get('num_faces', 1)
        seed = data.get('seed', None)
        
        if seed:
            torch.manual_seed(seed)
        
        with torch.no_grad():
            noise = torch.randn(num_faces, 100, 1, 1, device=device)
            fake_faces = netG(noise)
            
            # Конвертируем в PIL Image
            transform = transforms.ToPILImage()
            faces = []
            
            for i in range(num_faces):
                face_tensor = fake_faces[i].cpu()
                face_tensor = (face_tensor + 1) / 2  # Normalize to [0, 1]
                face_image = transform(face_tensor)
                
                # Конвертируем в base64
                buffered = io.BytesIO()
                face_image.save(buffered, format="PNG")
                img_str = base64.b64encode(buffered.getvalue()).decode()
                faces.append(img_str)
            
            return {'faces': faces, 'status': 'success'}
    
    except Exception as e:
        return {'error': str(e), 'status': 'error'}, 500

@app.route('/health')
def health_check():
    return {'status': 'healthy', 'model_loaded': True}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Systemd сервис для автозапуска:


# /etc/systemd/system/dcgan-api.service
[Unit]
Description=DCGAN Face Generation API
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/dcgan_faces
Environment=PATH=/home/ubuntu/dcgan_faces/dcgan_env/bin
ExecStart=/home/ubuntu/dcgan_faces/dcgan_env/bin/python api_server.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Мониторинг и логирование

Добавим систему мониторинга процесса обучения:


# monitoring.py
import torch
import matplotlib.pyplot as plt
import json
import time
from datetime import datetime

class TrainingMonitor:
    def __init__(self, log_file="training_log.json"):
        self.log_file = log_file
        self.losses = {"generator": [], "discriminator": []}
        self.timestamps = []
    
    def log_losses(self, g_loss, d_loss, epoch, batch):
        entry = {
            "timestamp": datetime.now().isoformat(),
            "epoch": epoch,
            "batch": batch,
            "generator_loss": float(g_loss),
            "discriminator_loss": float(d_loss)
        }
        
        with open(self.log_file, "a") as f:
            f.write(json.dumps(entry) + "\n")
        
        self.losses["generator"].append(g_loss)
        self.losses["discriminator"].append(d_loss)
        self.timestamps.append(time.time())
    
    def plot_losses(self, save_path="loss_plot.png"):
        plt.figure(figsize=(12, 6))
        
        plt.subplot(1, 2, 1)
        plt.plot(self.losses["generator"], label="Generator")
        plt.plot(self.losses["discriminator"], label="Discriminator")
        plt.xlabel("Iteration")
        plt.ylabel("Loss")
        plt.legend()
        plt.title("Training Losses")
        
        plt.subplot(1, 2, 2)
        if len(self.timestamps) > 1:
            intervals = [self.timestamps[i+1] - self.timestamps[i] 
                        for i in range(len(self.timestamps)-1)]
            plt.plot(intervals)
            plt.xlabel("Iteration")
            plt.ylabel("Time per iteration (s)")
            plt.title("Training Speed")
        
        plt.tight_layout()
        plt.savefig(save_path)
        plt.close()

Интересные факты и нестандартные применения

DCGAN может использоваться не только для генерации лиц, но и для:

  • Создания синтетических датасетов для обучения других моделей
  • Аугментации данных в условиях их нехватки
  • Анонимизации реальных лиц с сохранением статистических свойств
  • Создания аватаров для игр и приложений
  • Генерации тестовых данных для систем распознавания лиц

Интересный факт: DCGAN может “запомнить” некоторые лица из обучающего датасета, что называется mode collapse. Для детекции этого можно использовать метрики как FID (Fréchet Inception Distance) и IS (Inception Score).

Полезные ссылки и ресурсы

Официальные репозитории и документация:

Заключение и рекомендации

DCGAN — отличная отправная точка для изучения генеративных моделей. Она относительно проста в реализации, стабильна в обучении и дает хорошие результаты на лицах. Для продакшена рекомендую использовать более современные архитектуры вроде StyleGAN, но для экспериментов и обучения DCGAN подходит идеально.

Основные рекомендации по использованию:

  • Используйте качественный датасет с большим количеством разнообразных лиц
  • Мониторьте процесс обучения и вовремя останавливайте при переобучении
  • Экспериментируйте с гиперпараметрами, особенно learning rate
  • Добавляйте регуляризацию для стабилизации обучения
  • Используйте мощные GPU для ускорения процесса

Для серьезных проектов рассмотрите аренду мощного VPS с GPU или выделенного сервера — это существенно ускорит эксперименты и позволит работать с большими датасетами.

Удачи в генерации лиц! Помните, что в области GAN развитие идет очень быстро, поэтому следите за новыми архитектурами и техниками.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked