引言
生成模型是人工智能中最具创造性的方向之一,它让机器学会”想象”和”创造”全新的数据样本。生成对抗网络(Generative Adversarial Network, GAN)由Ian Goodfellow于2014年提出,通过一场生成器与判别器之间的”博弈”来学习数据分布。这种对抗训练机制灵感来源于博弈论中的纳什均衡,是深度学习领域最具革命性的创新之一。本文将用Java实现一个简化版的GAN,以二维数据生成为例,深入讲解对抗训练的核心思想。
算法原理
生成器与判别器的博弈
GAN由两个神经网络组成:
– 生成器(Generator, G):接收随机噪声z,尝试生成与真实数据分布相似的假样本 G(z)
– 判别器(Discriminator, D):接收真实样本x或生成样本G(z),判断其真伪
目标函数
GAN的优化是一个 minimax 博弈:
$$\min_G \max_D V(D, G) = \mathbb{E}{x \sim p{data}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1 – D(G(z)))]$$
- 判别器D试图最大化正确分类真实样本和生成样本的概率
- 生成器G试图最小化log(1-D(G(z))),即让判别器将生成样本误判为真实样本
训练过程
- 固定G,训练D区分真实样本和生成样本
- 固定D,训练G欺骗D
- 交替迭代直至收敛(理论上达到纳什均衡)
Java实现
import java.util.*;
/**
* GAN生成对抗网络简化实现
*
* 生成器学习生成符合目标分布的二维数据点
* 判别器学习区分真实数据和生成数据
*/
public class SimpleGAN {
// 数据维度
private int dataDim = 2;
private int noiseDim = 2;
// 网络结构参数
private int hiddenDim = 16;
// 生成器参数
private double[][] G_W1; // 输入层到隐藏层
private double[] G_b1;
private double[][] G_W2; // 隐藏层到输出层
private double[] G_b2;
// 判别器参数
private double[][] D_W1; // 输入层到隐藏层
private double[] D_b1;
private double[][] D_W2; // 隐藏层到输出层
private double[] D_b2;
// 学习率
private double gLearningRate = 0.01;
private double dLearningRate = 0.01;
private Random rand;
public SimpleGAN() {
rand = new Random(42);
initWeights();
}
/**
* 初始化网络权重
*/
private void initWeights() {
// 生成器权重
G_W1 = initMatrix(hiddenDim, noiseDim);
G_b1 = initVector(hiddenDim);
G_W2 = initMatrix(dataDim, hiddenDim);
G_b2 = initVector(dataDim);
// 判别器权重
D_W1 = initMatrix(hiddenDim, dataDim);
D_b1 = initVector(hiddenDim);
D_W2 = initMatrix(1, hiddenDim);
D_b2 = initVector(1);
}
private double[][] initMatrix(int rows, int cols) {
double[][] m = new double[rows][cols];
double scale = Math.sqrt(2.0 / (rows + cols));
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
m[i][j] = rand.nextGaussian() * scale;
}
}
return m;
}
private double[] initVector(int size) {
double[] v = new double[size];
for (int i = 0; i < size; i++) {
v[i] = rand.nextGaussian() * 0.01;
}
return v;
}
/**
* LeakyReLU激活函数
*/
private double leakyReLU(double x) {
return x > 0 ? x : 0.01 * x;
}
private double leakyReLUDerivative(double x) {
return x > 0 ? 1.0 : 0.01;
}
/**
* Sigmoid激活函数
*/
private double sigmoid(double x) {
if (x > 10) return 1.0;
if (x < -10) return 0.0;
return 1.0 / (1.0 + Math.exp(-x));
}
/**
* 矩阵向量乘法
*/
private double[] matVecMul(double[][] m, double[] v) {
int rows = m.length;
double[] result = new double[rows];
for (int i = 0; i < rows; i++) {
double sum = 0;
for (int j = 0; j < m[0].length; j++) {
sum += m[i][j] * v[j];
}
result[i] = sum;
}
return result;
}
/**
* 向量加法
*/
private double[] vecAdd(double[] a, double[] b) {
double[] r = new double[a.length];
for (int i = 0; i < a.length; i++) r[i] = a[i] + b[i];
return r;
}
/**
* 逐元素乘法
*/
private double[] vecMul(double[] a, double[] b) {
double[] r = new double[a.length];
for (int i = 0; i < a.length; i++) r[i] = a[i] * b[i];
return r;
}
/**
* 向量减法
*/
private double[] vecSub(double[] a, double[] b) {
double[] r = new double[a.length];
for (int i = 0; i < a.length; i++) r[i] = a[i] - b[i];
return r;
}
/**
* 标量乘法
*/
private double[] vecScale(double[] v, double s) {
double[] r = new double[v.length];
for (int i = 0; i < v.length; i++) r[i] = v[i] * s;
return r;
}
/**
* 对向量应用leakyReLU
*/
private double[] applyLeakyReLU(double[] v) {
double[] r = new double[v.length];
for (int i = 0; i < v.length; i++) r[i] = leakyReLU(v[i]);
return r;
}
/**
* 生成器前向传播
*/
public double[] generatorForward(double[] noise) {
double[] h1 = vecAdd(matVecMul(G_W1, noise), G_b1);
double[] a1 = applyLeakyReLU(h1);
double[] output = vecAdd(matVecMul(G_W2, a1), G_b2);
return output;
}
/**
* 判别器前向传播(返回概率)
*/
public double discriminatorForward(double[] data) {
double[] h1 = vecAdd(matVecMul(D_W1, data), D_b1);
double[] a1 = applyLeakyReLU(h1);
double[] output = vecAdd(matVecMul(D_W2, a1), D_b2);
return sigmoid(output[0]);
}
/**
* 生成随机噪声
*/
private double[] sampleNoise() {
double[] z = new double[noiseDim];
for (int i = 0; i < noiseDim; i++) {
z[i] = rand.nextGaussian();
}
return z;
}
/**
* 生成真实数据(以圆环分布为例)
*/
private double[] sampleRealData() {
double angle = rand.nextDouble() * 2 * Math.PI;
double radius = 2.0 + rand.nextGaussian() * 0.3;
return new double[] {
radius * Math.cos(angle),
radius * Math.sin(angle)
};
}
/**
* 训练判别器一个batch
*/
private double trainDiscriminator(int batchSize) {
double totalLoss = 0;
// 存储梯度
double[][] dW1Grad = new double[hiddenDim][dataDim];
double[] db1Grad = new double[hiddenDim];
double[][] dW2Grad = new double[1][hiddenDim];
double[] db2Grad = new double[1];
for (int i = 0; i < batchSize; i++) {
// 真实样本
double[] real = sampleRealData();
double realProb = discriminatorForward(real);
double realLoss = -Math.log(realProb + 1e-8);
totalLoss += realLoss;
// 反向传播(真实样本)
double dOut = -(1.0 / (realProb + 1e-8)) * realProb * (1 - realProb);
// 前向中间结果
double[] h1 = vecAdd(matVecMul(D_W1, real), D_b1);
double[] a1 = applyLeakyReLU(h1);
// 输出层梯度
for (int j = 0; j < hiddenDim; j++) {
dW2Grad[0][j] += dOut * a1[j];
}
db2Grad[0] += dOut;
// 隐藏层梯度
double[] da1 = new double[hiddenDim];
for (int j = 0; j < hiddenDim; j++) {
da1[j] = dOut * D_W2[0][j] * leakyReLUDerivative(h1[j]);
}
for (int j = 0; j < hiddenDim; j++) {
for (int k = 0; k < dataDim; k++) {
dW1Grad[j][k] += da1[j] * real[k];
}
db1Grad[j] += da1[j];
}
// 生成样本
double[] noise = sampleNoise();
double[] fake = generatorForward(noise);
double fakeProb = discriminatorForward(fake);
double fakeLoss = -Math.log(1 - fakeProb + 1e-8);
totalLoss += fakeLoss;
// 反向传播(生成样本,固定生成器)
dOut = -(1.0 / (1 - fakeProb + 1e-8)) * (-fakeProb * (1 - fakeProb));
h1 = vecAdd(matVecMul(D_W1, fake), D_b1);
a1 = applyLeakyReLU(h1);
for (int j = 0; j < hiddenDim; j++) {
dW2Grad[0][j] += dOut * a1[j];
}
db2Grad[0] += dOut;
da1 = new double[hiddenDim];
for (int j = 0; j < hiddenDim; j++) {
da1[j] = dOut * D_W2[0][j] * leakyReLUDerivative(h1[j]);
}
for (int j = 0; j < hiddenDim; j++) {
for (int k = 0; k < dataDim; k++) {
dW1Grad[j][k] += da1[j] * fake[k];
}
db1Grad[j] += da1[j];
}
}
// 更新判别器参数
for (int i = 0; i < hiddenDim; i++) {
for (int j = 0; j < dataDim; j++) {
D_W1[i][j] -= dLearningRate * dW1Grad[i][j] / batchSize;
}
D_b1[i] -= dLearningRate * db1Grad[i] / batchSize;
}
for (int j = 0; j < hiddenDim; j++) {
D_W2[0][j] -= dLearningRate * dW2Grad[0][j] / batchSize;
}
D_b2[0] -= dLearningRate * db2Grad[0] / batchSize;
return totalLoss / batchSize;
}
/**
* 训练生成器一个batch
*/
private double trainGenerator(int batchSize) {
double totalLoss = 0;
double[][] gW1Grad = new double[hiddenDim][noiseDim];
double[] gb1Grad = new double[hiddenDim];
double[][] gW2Grad = new double[dataDim][hiddenDim];
double[] gb2Grad = new double[dataDim];
for (int i = 0; i < batchSize; i++) {
double[] noise = sampleNoise();
// 生成器前向
double[] gH1 = vecAdd(matVecMul(G_W1, noise), G_b1);
double[] gA1 = applyLeakyReLU(gH1);
double[] fake = vecAdd(matVecMul(G_W2, gA1), G_b2);
// 判别器前向
double[] dH1 = vecAdd(matVecMul(D_W1, fake), D_b1);
double[] dA1 = applyLeakyReLU(dH1);
double[] dOut = vecAdd(matVecMul(D_W2, dA1), D_b2);
double prob = sigmoid(dOut[0]);
double loss = -Math.log(prob + 1e-8);
totalLoss += loss;
// 反向传播
double dLoss = -(1.0 / (prob + 1e-8)) * prob * (1 - prob);
// 判别器部分梯度
double[] ddA1 = new double[hiddenDim];
for (int j = 0; j < hiddenDim; j++) {
ddA1[j] = dLoss * D_W2[0][j] * leakyReLUDerivative(dH1[j]);
}
double[] dfake = new double[dataDim];
for (int j = 0; j < dataDim; j++) {
for (int k = 0; k < hiddenDim; k++) {
dfake[j] += ddA1[k] * D_W1[k][j];
}
}
// 生成器输出层梯度
for (int j = 0; j < dataDim; j++) {
for (int k = 0; k < hiddenDim; k++) {
gW2Grad[j][k] += dfake[j] * gA1[k];
}
gb2Grad[j] += dfake[j];
}
// 生成器隐藏层梯度
double[] dgA1 = new double[hiddenDim];
for (int j = 0; j < hiddenDim; j++) {
for (int k = 0; k < dataDim; k++) {
dgA1[j] += dfake[k] * G_W2[k][j];
}
dgA1[j] *= leakyReLUDerivative(gH1[j]);
}
for (int j = 0; j < hiddenDim; j++) {
for (int k = 0; k < noiseDim; k++) {
gW1Grad[j][k] += dgA1[j] * noise[k];
}
gb1Grad[j] += dgA1[j];
}
}
// 更新生成器参数
for (int i = 0; i < hiddenDim; i++) {
for (int j = 0; j < noiseDim; j++) {
G_W1[i][j] -= gLearningRate * gW1Grad[i][j] / batchSize;
}
G_b1[i] -= gLearningRate * gb1Grad[i] / batchSize;
}
for (int i = 0; i < dataDim; i++) {
for (int j = 0; j < hiddenDim; j++) {
G_W2[i][j] -= gLearningRate * gW2Grad[i][j] / batchSize;
}
G_b2[i] -= gLearningRate * gb2Grad[i] / batchSize;
}
return totalLoss / batchSize;
}
/**
* 训练GAN
*/
public void train(int epochs, int dSteps, int gSteps, int batchSize) {
System.out.println("开始训练GAN...");
for (int epoch = 0; epoch < epochs; epoch++) {
double dLoss = 0, gLoss = 0;
// 训练判别器
for (int i = 0; i < dSteps; i++) {
dLoss += trainDiscriminator(batchSize);
}
// 训练生成器
for (int i = 0; i < gSteps; i++) {
gLoss += trainGenerator(batchSize);
}
if ((epoch + 1) % 1000 == 0) {
System.out.printf("Epoch %d: D_loss=%.4f, G_loss=%.4f%n",
epoch + 1, dLoss / dSteps, gLoss / gSteps);
// 生成样本展示
double[] sample = generatorForward(sampleNoise());
System.out.printf(" 生成样本: (%.3f, %.3f)%n", sample[0], sample[1]);
}
}
}
/**
* 生成并打印多个样本
*/
public void generateSamples(int n) {
System.out.println("\n=== 生成样本 ===");
for (int i = 0; i < n; i++) {
double[] sample = generatorForward(sampleNoise());
System.out.printf("样本%d: (%.4f, %.4f)%n", i + 1, sample[0], sample[1]);
}
}
public static void main(String[] args) {
SimpleGAN gan = new SimpleGAN();
gan.train(20000, 2, 1, 32);
gan.generateSamples(10);
// 验证生成数据分布
System.out.println("\n=== 验证生成数据分布 ===");
double sumR = 0;
for (int i = 0; i < 1000; i++) {
double[] s = gan.generatorForward(gan.sampleNoise());
double r = Math.sqrt(s[0]*s[0] + s[1]*s[1]);
sumR += r;
}
System.out.printf("平均半径: %.4f (目标: ~2.0)%n", sumR / 1000);
}
}
代码解析
生成器架构
生成器是一个简单的全连接网络:噪声(2维)→ 隐藏层(16维,LeakyReLU)→ 输出层(2维)。它将随机噪声映射到数据空间,学习模拟真实数据的分布。
判别器架构
判别器同样是全连接网络:数据(2维)→ 隐藏层(16维,LeakyReLU)→ 输出层(1维,Sigmoid)。输出值表示输入为真实数据的概率。
对抗训练循环
代码中每轮先训练判别器2步,再训练生成器1步。这种不平衡的训练策略有助于稳定GAN的训练——判别器太强会导致生成器梯度消失,判别器太弱则无法提供有效指导。
损失函数
判别器损失是真实样本的交叉熵损失与生成样本的交叉熵损失之和。生成器损失则是 -log(D(G(z))),即让判别器对生成样本的输出概率尽可能接近1。
复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 单次生成 | O(H × (N + D)) | H为隐藏层大小,N为噪声维度,D为数据维度 |
| 单次判别 | O(H × (D + 1)) | 判别器前向传播 |
| 训练时间 | O(E × B × (H × D + H × N)) | E为轮数,B为batch大小 |
GAN的演进与挑战
- DCGAN:将全连接层替换为卷积层,生成更高质量的图像
- WGAN:使用Wasserstein距离替代JS散度,训练更稳定
- CGAN:引入条件信息,实现可控生成
- StyleGAN:分层风格控制,生成超逼真的人脸图像
- CycleGAN:无需配对数据即可实现图像域转换
训练难点
GAN训练 notoriously 不稳定,常见挑战包括:
– 模式崩溃(Mode Collapse):生成器只生成少数几种样本
– 梯度消失:判别器过强时生成器梯度趋近于零
– 不收敛:生成器和判别器难以达到纳什均衡
本文的简化实现以二维数据为例,避开了图像生成中的复杂挑战,但完整呈现了GAN对抗训练的核心机制。
总结
本文通过Java实现了一个简化版GAN,展示了生成器与判别器之间的对抗博弈过程。GAN的精妙之处在于将生成问题转化为一场双人博弈,两个网络在相互对抗中共同进化。虽然训练稳定性仍是研究热点,但GAN在图像生成、数据增强、风格迁移等领域的成功应用,证明了这一框架的强大潜力。