每日算法 — 使用java实现GAN生成对抗网络:对抗训练与图像生成

引言

生成对抗网络(Generative Adversarial Network,GAN)是Ian Goodfellow于2014年提出的革命性生成模型框架。GAN通过让两个神经网络——生成器(Generator)和判别器(Discriminator)——相互对抗、共同进化,实现了从未标注数据中学习数据分布并生成逼真样本的能力。GAN开启了深度学习生成模型的新纪元,衍生出DCGAN、StyleGAN、CycleGAN等众多变体,在图像生成、风格迁移、数据增强等领域展现了惊人效果。本文将用Java从零实现一个简化版GAN,训练其在二维平面上学习数据分布并生成符合目标分布的新样本。

算法原理

对抗博弈框架

GAN的核心是一个 minimax 博弈:

$$\min_G \max_D V(D, G) = \mathbb{E}{x \sim p{data}(x)}[\log D(x)] + \mathbb{E}_{z \sim p_z(z)}[\log(1 – D(G(z)))]$$

生成器G:接收随机噪声 $z$,输出生成样本 $G(z)$,目标是欺骗判别器使其无法区分真假
判别器D:接收真实样本或生成样本,输出其为真实样本的概率,目标是准确区分真假

训练流程

  1. 训练判别器:最大化识别真假样本的能力
  2. 训练生成器:最小化判别器正确识别假样本的概率(即最大化 $D(G(z))$)
  3. 交替迭代直到纳什均衡

Java实现

1. 神经网络基础层

import java.util.Random;

/**
 * 全连接层:实现线性变换 y = xW + b
 */
public class LinearLayer {
    private final double[][] weights;
    private final double[] bias;
    private final int inputSize, outputSize;

    // 缓存前向传播输入用于反向传播
    private double[] lastInput;

    public LinearLayer(int inputSize, int outputSize, long seed) {
        this.inputSize = inputSize;
        this.outputSize = outputSize;
        Random rand = new Random(seed);

        // Xavier初始化
        this.weights = new double[inputSize][outputSize];
        double scale = Math.sqrt(2.0 / (inputSize + outputSize));
        for (int i = 0; i < inputSize; i++) {
            for (int j = 0; j < outputSize; j++) {
                weights[i][j] = rand.nextGaussian() * scale;
            }
        }

        this.bias = new double[outputSize];
    }

    /** 前向传播 */
    public double[] forward(double[] input) {
        this.lastInput = input.clone();
        double[] output = new double[outputSize];
        for (int j = 0; j < outputSize; j++) {
            double sum = bias[j];
            for (int i = 0; i < inputSize; i++) {
                sum += input[i] * weights[i][j];
            }
            output[j] = sum;
        }
        return output;
    }

    /** 反向传播并更新参数(简化版梯度下降) */
    public double[] backward(double[] gradOutput, double learningRate) {
        double[] gradInput = new double[inputSize];

        // 计算权重梯度和输入梯度
        for (int j = 0; j < outputSize; j++) {
            for (int i = 0; i < inputSize; i++) {
                // 权重梯度
                double gradW = lastInput[i] * gradOutput[j];
                weights[i][j] -= learningRate * gradW;
                // 输入梯度
                gradInput[i] += weights[i][j] * gradOutput[j];
            }
            // 偏置梯度
            bias[j] -= learningRate * gradOutput[j];
        }

        return gradInput;
    }
}

/**
 * 激活函数层
 */
public class ActivationLayer {
    public enum Type { RELU, LEAKY_RELU, SIGMOID, TANH }
    private final Type type;
    private double[] lastInput;

    public ActivationLayer(Type type) { this.type = type; }

    public double[] forward(double[] input) {
        this.lastInput = input.clone();
        double[] output = new double[input.length];
        for (int i = 0; i < input.length; i++) {
            output[i] = activate(input[i]);
        }
        return output;
    }

    public double[] backward(double[] gradOutput, double learningRate) {
        double[] gradInput = new double[gradOutput.length];
        for (int i = 0; i < gradOutput.length; i++) {
            gradInput[i] = gradOutput[i] * derivative(lastInput[i]);
        }
        return gradInput;
    }

    private double activate(double x) {
        switch (type) {
            case RELU: return Math.max(0, x);
            case LEAKY_RELU: return x > 0 ? x : 0.01 * x;
            case SIGMOID: return 1.0 / (1.0 + Math.exp(-x));
            case TANH: return Math.tanh(x);
            default: return x;
        }
    }

    private double derivative(double x) {
        switch (type) {
            case RELU: return x > 0 ? 1 : 0;
            case LEAKY_RELU: return x > 0 ? 1 : 0.01;
            case SIGMOID:
                double s = activate(x);
                return s * (1 - s);
            case TANH:
                double t = activate(x);
                return 1 - t * t;
            default: return 1;
        }
    }
}

2. 生成器网络

/**
 * 生成器:将随机噪声映射为目标数据分布
 */
public class Generator {
    private final LinearLayer fc1, fc2, fc3;
    private final ActivationLayer act1, act2, act3;
    private final int noiseDim, outputDim;

    public Generator(int noiseDim, int hiddenDim, int outputDim, long seed) {
        this.noiseDim = noiseDim;
        this.outputDim = outputDim;

        // 三层全连接网络:noiseDim -> hiddenDim -> hiddenDim -> outputDim
        fc1 = new LinearLayer(noiseDim, hiddenDim, seed);
        act1 = new ActivationLayer(ActivationLayer.Type.LEAKY_RELU);
        fc2 = new LinearLayer(hiddenDim, hiddenDim, seed + 1);
        act2 = new ActivationLayer(ActivationLayer.Type.LEAKY_RELU);
        fc3 = new LinearLayer(hiddenDim, outputDim, seed + 2);
        act3 = new ActivationLayer(ActivationLayer.Type.TANH);
    }

    /** 生成样本 */
    public double[] generate(double[] noise) {
        double[] h1 = act1.forward(fc1.forward(noise));
        double[] h2 = act2.forward(fc2.forward(h1));
        return act3.forward(fc3.forward(h2));
    }

    /** 训练生成器:目标是欺骗判别器 */
    public void train(double[] noise, Discriminator discriminator, double lr) {
        // 前向传播
        double[] h1 = act1.forward(fc1.forward(noise));
        double[] h2 = act2.forward(fc2.forward(h1));
        double[] fakeSample = act3.forward(fc3.forward(h2));

        // 判别器对生成样本的输出
        double discOutput = discriminator.evaluate(fakeSample);

        // 生成器的损失:-log(D(G(z))),希望D(G(z))接近1
        double loss = -Math.log(discOutput + 1e-8);

        // 反向传播梯度(简化版)
        double[] grad = new double[outputDim];
        for (int i = 0; i < outputDim; i++) {
            grad[i] = -1.0 / (discOutput + 1e-8) * discriminator.getInputGradient(fakeSample)[i];
        }

        // 通过各层反向传播
        grad = act3.backward(grad, lr);
        grad = fc3.backward(grad, lr);
        grad = act2.backward(grad, lr);
        grad = fc2.backward(grad, lr);
        grad = act1.backward(grad, lr);
        fc1.backward(grad, lr);
    }

    public int getNoiseDim() { return noiseDim; }
}

3. 判别器网络

/**
 * 判别器:区分真实样本和生成样本
 */
public class Discriminator {
    private final LinearLayer fc1, fc2, fc3;
    private final ActivationLayer act1, act2;
    private final int inputDim;
    private double[] lastInput;
    private double lastOutput;

    public Discriminator(int inputDim, int hiddenDim, long seed) {
        this.inputDim = inputDim;
        fc1 = new LinearLayer(inputDim, hiddenDim, seed + 100);
        act1 = new ActivationLayer(ActivationLayer.Type.LEAKY_RELU);
        fc2 = new LinearLayer(hiddenDim, hiddenDim, seed + 101);
        act2 = new ActivationLayer(ActivationLayer.Type.LEAKY_RELU);
        fc3 = new LinearLayer(hiddenDim, 1, seed + 102);
    }

    /** 评估样本:输出为真实样本的概率 */
    public double evaluate(double[] sample) {
        this.lastInput = sample.clone();
        double[] h1 = act1.forward(fc1.forward(sample));
        double[] h2 = act2.forward(fc2.forward(h1));
        double logit = fc3.forward(h2)[0];
        this.lastOutput = sigmoid(logit);
        return lastOutput;
    }

    private double sigmoid(double x) {
        return 1.0 / (1.0 + Math.exp(-x));
    }

    /** 获取输入梯度(供生成器使用) */
    public double[] getInputGradient(double[] sample) {
        // 简化的梯度:sigmoid的导数
        double[] grad = new double[inputDim];
        for (int i = 0; i < inputDim; i++) {
            grad[i] = lastOutput * (1 - lastOutput);
        }
        return grad;
    }

    /**
     * 训练判别器
     * @param realSample 真实样本
     * @param fakeSample 生成样本
     * @param lr 学习率
     * @return 损失值
     */
    public double train(double[] realSample, double[] fakeSample, double lr) {
        // 对真实样本的预测
        double realPred = evaluate(realSample);
        // 对生成样本的预测
        double fakePred = evaluate(fakeSample);

        // 二元交叉熵损失
        double realLoss = -Math.log(realPred + 1e-8);
        double fakeLoss = -Math.log(1 - fakePred + 1e-8);
        double totalLoss = realLoss + fakeLoss;

        // 简化的参数更新(直接调整最后一层权重模拟梯度下降效果)
        updateFromPrediction(realPred, 1.0, lr); // 真实样本标签为1
        updateFromPrediction(fakePred, 0.0, lr); // 生成样本标签为0

        return totalLoss;
    }

    /** 根据预测结果和标签更新网络(简化版) */
    private void updateFromPrediction(double pred, double label, double lr) {
        double error = pred - label;
        // 模拟反向传播对最后一层的影响
        double[] grad = new double[1];
        grad[0] = error;
        fc3.backward(grad, lr);
    }
}

4. GAN训练引擎

import java.util.Random;

/**
 * GAN训练引擎:协调生成器和判别器的对抗训练
 */
public class GANTrainer {
    private final Generator generator;
    private final Discriminator discriminator;
    private final Random random;
    private final int noiseDim;

    public GANTrainer(int noiseDim, int hiddenDim, int dataDim, long seed) {
        this.noiseDim = noiseDim;
        this.generator = new Generator(noiseDim, hiddenDim, dataDim, seed);
        this.discriminator = new Discriminator(dataDim, hiddenDim, seed);
        this.random = new Random(seed);
    }

    /** 生成随机噪声向量 */
    public double[] generateNoise() {
        double[] noise = new double[noiseDim];
        for (int i = 0; i < noiseDim; i++) {
            noise[i] = random.nextGaussian();
        }
        return noise;
    }

    /**
     * 训练一个epoch
     * @param realData 真实数据集
     * @param batchSize 批次大小
     * @param gLR 生成器学习率
     * @param dLR 判别器学习率
     * @param dSteps 每轮判别器训练次数
     */
    public void trainEpoch(double[][] realData, int batchSize, 
                           double gLR, double dLR, int dSteps) {
        // 1. 训练判别器dSteps次
        for (int step = 0; step < dSteps; step++) {
            for (int b = 0; b < batchSize; b++) {
                double[] realSample = realData[random.nextInt(realData.length)];
                double[] noise = generateNoise();
                double[] fakeSample = generator.generate(noise);
                discriminator.train(realSample, fakeSample, dLR);
            }
        }

        // 2. 训练生成器
        for (int b = 0; b < batchSize; b++) {
            double[] noise = generateNoise();
            generator.train(noise, discriminator, gLR);
        }
    }

    /** 生成一批样本 */
    public double[][] generateSamples(int count) {
        double[][] samples = new double[count][];
        for (int i = 0; i < count; i++) {
            samples[i] = generator.generate(generateNoise());
        }
        return samples;
    }

    public Generator getGenerator() { return generator; }
    public Discriminator getDiscriminator() { return discriminator; }
}

5. 主程序:二维数据分布学习

/**
 * GAN二维分布生成演示
 * 目标:让生成器学习生成符合环形分布的二维点
 */
public class GANDemo {

    public static void main(String[] args) {
        // 实验参数
        int dataDim = 2;       // 二维数据
        int noiseDim = 10;     // 噪声维度
        int hiddenDim = 64;    // 隐藏层维度
        int dataSize = 2000;   // 真实数据集大小
        int epochs = 3000;     // 训练轮数
        int batchSize = 32;    // 批次大小
        double gLR = 0.001;    // 生成器学习率
        double dLR = 0.001;    // 判别器学习率

        System.out.println("=== GAN生成对抗网络演示 ===");
        System.out.println("目标:学习二维环形数据分布并生成新样本");
        System.out.println("网络结构:");
        System.out.println("  生成器: " + noiseDim + " -> " + hiddenDim + " -> " + hiddenDim + " -> " + dataDim);
        System.out.println("  判别器: " + dataDim + " -> " + hiddenDim + " -> " + hiddenDim + " -> 1");
        System.out.println("训练配置:");
        System.out.println("  真实样本数: " + dataSize);
        System.out.println("  训练轮数: " + epochs);
        System.out.println("  批次大小: " + batchSize);
        System.out.println();

        // 生成真实数据:环形分布
        double[][] realData = generateRingData(dataSize, 42);
        System.out.println("真实数据统计:");
        printDataStats(realData);

        // 创建GAN训练器
        GANTrainer trainer = new GANTrainer(noiseDim, hiddenDim, dataDim, 123);

        // 训练过程
        System.out.println("\n开始训练...");
        for (int epoch = 0; epoch < epochs; epoch++) {
            trainer.trainEpoch(realData, batchSize, gLR, dLR, 1);

            if (epoch % 500 == 0) {
                double[][] fakeSamples = trainer.generateSamples(100);
                double mse = computeDistributionMSE(realData, fakeSamples);
                System.out.printf("Epoch %d, 分布MSE: %.6f%n", epoch, mse);
            }
        }

        // 最终生成效果评估
        System.out.println("\n=== 生成效果评估 ===");
        double[][] generated = trainer.generateSamples(500);

        System.out.println("生成样本统计:");
        printDataStats(generated);

        // 计算生成样本到真实分布的距离
        double avgDistance = computeAverageDistance(realData, generated);
        System.out.printf("\n生成样本到真实分布的平均距离: %.4f%n", avgDistance);

        // 展示部分生成样本
        System.out.println("\n生成样本示例(前10个):");
        System.out.printf("%-10s %-10s %-10s%n", "序号", "X", "Y");
        for (int i = 0; i < 10; i++) {
            System.out.printf("%-10d %-10.4f %-10.4f%n", i + 1, generated[i][0], generated[i][1]);
        }

        System.out.println("\nGAN通过生成器和判别器的对抗博弈,");
        System.out.println("使生成样本逐渐逼近真实数据分布,无需显式建模数据概率密度。");
    }

    /** 生成环形分布数据 */
    private static double[][] generateRingData(int size, long seed) {
        Random rand = new Random(seed);
        double[][] data = new double[size][2];
        for (int i = 0; i < size; i++) {
            double angle = rand.nextDouble() * 2 * Math.PI;
            double radius = 3.0 + rand.nextGaussian() * 0.3; // 环形半径约3
            data[i][0] = radius * Math.cos(angle);
            data[i][1] = radius * Math.sin(angle);
        }
        return data;
    }

    /** 打印数据统计信息 */
    private static void printDataStats(double[][] data) {
        double sumX = 0, sumY = 0;
        double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
        double minY = Double.POSITIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY;

        for (double[] point : data) {
            sumX += point[0]; sumY += point[1];
            minX = Math.min(minX, point[0]); maxX = Math.max(maxX, point[0]);
            minY = Math.min(minY, point[1]); maxY = Math.max(maxY, point[1]);
        }

        System.out.printf("  X均值: %.4f, 范围: [%.4f, %.4f]%n", sumX / data.length, minX, maxX);
        System.out.printf("  Y均值: %.4f, 范围: [%.4f, %.4f]%n", sumY / data.length, minY, maxY);
    }

    /** 计算两个数据集分布的近似MSE(基于分箱直方图) */
    private static double computeDistributionMSE(double[][] real, double[][] fake) {
        // 简化为比较均值和方差
        double realMeanX = 0, realMeanY = 0, fakeMeanX = 0, fakeMeanY = 0;
        for (double[] p : real) { realMeanX += p[0]; realMeanY += p[1]; }
        for (double[] p : fake) { fakeMeanX += p[0]; fakeMeanY += p[1]; }
        realMeanX /= real.length; realMeanY /= real.length;
        fakeMeanX /= fake.length; fakeMeanY /= fake.length;

        double realVarX = 0, realVarY = 0, fakeVarX = 0, fakeVarY = 0;
        for (double[] p : real) {
            realVarX += (p[0] - realMeanX) * (p[0] - realMeanX);
            realVarY += (p[1] - realMeanY) * (p[1] - realMeanY);
        }
        for (double[] p : fake) {
            fakeVarX += (p[0] - fakeMeanX) * (p[0] - fakeMeanX);
            fakeVarY += (p[1] - fakeMeanY) * (p[1] - fakeMeanY);
        }

        return Math.abs(realMeanX - fakeMeanX) + Math.abs(realMeanY - fakeMeanY)
             + Math.abs(realVarX / real.length - fakeVarX / fake.length)
             + Math.abs(realVarY / real.length - fakeVarY / fake.length);
    }

    /** 计算生成样本到最近真实样本的平均距离 */
    private static double computeAverageDistance(double[][] real, double[][] generated) {
        double totalDist = 0;
        for (double[] g : generated) {
            double minDist = Double.POSITIVE_INFINITY;
            for (double[] r : real) {
                double dx = g[0] - r[0], dy = g[1] - r[1];
                double dist = Math.sqrt(dx * dx + dy * dy);
                minDist = Math.min(minDist, dist);
            }
            totalDist += minDist;
        }
        return totalDist / generated.length;
    }
}

复杂度分析

组件 时间复杂度(单次前向) 空间复杂度
生成器 O(hiddenDim × (noiseDim + hiddenDim + dataDim)) O(hiddenDim + dataDim)
判别器 O(hiddenDim × (dataDim + hiddenDim)) O(hiddenDim)
单次GAN迭代 O(batchSize × hiddenDim × max(noiseDim, dataDim, hiddenDim)) O(batchSize × hiddenDim)

GAN训练的主要挑战在于达到纳什均衡,而非计算复杂度本身。

总结

本文实现了一个简化版GAN,核心要点包括:

  • 生成器通过多层神经网络将低维随机噪声映射到高维数据空间,学习数据的生成分布
  • 判别器作为二分类器评估样本真实性,为生成器提供对抗信号
  • 对抗训练通过minimax博弈使两个网络相互促进,最终生成器能够产生以假乱真的样本
  • Java实现展示了神经网络的前向传播和简化版反向传播,完整保留了GAN的核心算法思想

在实际应用中,PyTorch和TensorFlow提供了自动微分和GPU加速,但理解GAN的底层原理对于设计更复杂的生成模型(如StyleGAN、Diffusion模型)至关重要。