引言
生成对抗网络(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:接收真实样本或生成样本,输出其为真实样本的概率,目标是准确区分真假
训练流程
- 训练判别器:最大化识别真假样本的能力
- 训练生成器:最小化判别器正确识别假样本的概率(即最大化 $D(G(z))$)
- 交替迭代直到纳什均衡
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模型)至关重要。