通过对抗学习生成CAPTCHA
1. 引言
在当今的网络世界中,CAPTCHA(全自动区分计算机和人类的公开图灵测试)是一种常见的安全机制,用于防止机器人自动化操作。本文将介绍如何使用生成对抗网络(GAN)来生成类似于街景房屋号码数据集(SVHN数据集)的图像,并将这些图像用作CAPTCHA。
2. SVHN数据集
SVHN是一个在机器学习和深度学习领域广泛使用的真实世界数据集,主要用于目标识别算法。该数据集包含从谷歌街景图像中获取的房屋号码的真实图像,可以从以下链接下载: http://ufldl.stanford.edu/housenumbers/ 。我们将使用调整大小后的房屋号码数据集,其中图像的尺寸已调整为(32, 32),具体使用的数据集文件是 train_32x32.mat 。
3. 生成对抗网络(GAN)原理
GAN由生成器(G)和判别器(D)组成,它们在损失函数的基础上进行零和极小极大博弈。随着时间的推移,生成器和判别器都会不断改进,直到达到一个稳定点,即鞍点。在这个鞍点上,两者都无法进一步改进。
3.1 生成器(G)
生成器G的作用是将来自给定分布P(z)的噪声z转换为房屋号码图像x,即x = G(z)。生成器的目标是生成能够欺骗判别器的图像,让判别器认为这些图像是真实的。
3.2 判别器(D)
判别器D的任务是区分生成器生成的假图像和SVHN数据集中的真实图像。如果将真实图像标记为1,生成器生成的假图像标记为0,那么判别器将尝试最小化二元交叉熵损失,作为一个二分类网络。判别器的损失函数可以表示为:
(此处原文档未给出具体公式,可根据上下文推测为相关的二元交叉熵损失公式)
3.3 效用函数与鞍点
我们可以将优化的损失看作是生成器和判别器都在针对各自参数进行优化的效用函数U。从博弈论的角度来看,生成器G和判别器D在效用函数U上进行零和极小极大博弈,优化问题可以表示为:
(此处原文档未给出具体公式,可根据上下文推测为相关的极小极大优化公式)
在参数空间中,如果一个函数在某些参数上是局部最大值,而在其他参数上是局部最小值,那么这个点就是鞍点。对于效用函数U,鞍点是极小极大零和博弈的纳什均衡,此时生成器和判别器的参数是最优的。
4. 优化GAN损失
为了优化GAN的损失,我们通常使用梯度上升来最大化目标函数,使用梯度下降来最小化成本函数。优化问题可以分解为两个部分:生成器和判别器轮流通过梯度上升和梯度下降来优化效用函数。
4.1 判别器优化
在优化的任何步骤t中,判别器会尝试通过最小化效用来移动到一个新的状态,具体表示为:
(此处原文档未给出具体公式,可根据上下文推测为相关的判别器优化公式)
4.2 生成器优化
生成器则会尝试最大化相同的效用。由于判别器D没有生成器的参数,效用函数的第二项不会影响生成器的优化,具体表示为:
(此处原文档未给出具体公式,可根据上下文推测为相关的生成器优化公式)
最终,我们将生成器和判别器的优化目标都转换为最小化问题,并使用梯度下降进行优化,直到达到目标函数的鞍点。
5. 生成器网络
生成器网络的输入是一个100维的随机噪声向量,每个维度都是一个服从均值为0、标准差为1的标准正态分布的随机变量。网络的结构如下:
def generator(input_dim,alpha=0.2):
model = Sequential()
model.add(Dense(input_dim=input_dim, output_dim=4*4*512))
model.add(Reshape(target_shape=(4,4,512)))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha))
model.add(Conv2DTranspose(256, kernel_size=5, strides=2,
padding='same'))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha))
model.add(Conv2DTranspose(128, kernel_size=5, strides=2,
padding='same'))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha))
model.add(Conv2DTranspose(3, kernel_size=5, strides=2,
padding='same'))
model.add(Activation('tanh'))
return model
生成器网络的初始密集层有8192个单元,然后将其重塑为形状为4 x 4 x 512的三维张量。为了增加张量的空间维度,我们使用了一系列步长为2、核滤波器尺寸为5 x 5的转置二维卷积。转置卷积通常伴随着批量归一化,以提高收敛性。网络除了激活层外,使用LeakyReLU作为激活函数。最后一层使用tanh激活函数,将图像像素值归一化到[-1, 1]的范围内。最终输出的是尺寸为32 x 32 x 3的图像。
6. 判别器网络
判别器是一个经典的二元分类卷积神经网络,用于区分生成器生成的假图像和SVHN数据集中的真实图像。判别器网络的代码如下:
def discriminator(img_dim,alpha=0.2):
model = Sequential()
model.add(
Conv2D(64, kernel_size=5,strides=2,
padding='same',
input_shape=img_dim)
)
model.add(LeakyReLU(alpha))
model.add(Conv2D(128,kernel_size=5,strides=2,padding='same'))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha))
model.add(Conv2D(256,kernel_size=5,strides=2,padding='same'))
model.add(BatchNormalization())
model.add(LeakyReLU(alpha))
model.add(Flatten())
model.add(Dense(1))
model.add(Activation('sigmoid'))
return model
判别器网络将假图像和真实图像作为输入,经过3组二维卷积后,通过一个全连接层和sigmoid激活函数输出一个概率值,表示输入图像为真实图像的概率。卷积层后面没有使用池化层,而是使用批量归一化和LeakyReLU激活函数。
7. 训练GAN
训练GAN的流程并不简单,需要考虑很多技术细节。我们定义了三个网络用于训练:
- 生成器网络g,带有参数(此处原文档未明确给出参数表示,推测为相关权重参数)
- 判别器网络d,带有参数(此处原文档未明确给出参数表示,推测为相关权重参数)
- 组合生成器判别器网络g_d,带有权重(此处原文档未明确给出权重表示,推测为相关权重参数)
7.1 训练流程
训练过程中,生成器g生成假图像,判别器d对这些图像进行评估并尝试标记为假。在g_d网络中,生成器g生成假图像并试图欺骗判别器d,让其认为这些图像是真实的。判别器网络使用二元交叉熵损失进行编译,并针对判别器参数进行优化;而g_d网络则针对生成器g的参数进行编译,以欺骗判别器。
训练代码如下:
def train(dest_train,outdir,
gen_input_dim,gen_lr,gen_beta1,
dis_input_dim,dis_lr,dis_beta1,
epochs,batch_size,alpha=0.2,smooth_coef=0.1):
#X_train,X_test = read_data(dest_train),read_data(dest_test)
train_data = loadmat(dest_train + 'train_32x32.mat')
X_train, y_train = train_data['X'], train_data['y']
X_train = np.rollaxis(X_train, 3)
print(X_train.shape)
#Image pixels are normalized between -1 to +1 so that one can use the tanh activation function
#_train = (X_train.astype(np.float32) - 127.5)/127.5
X_train = (X_train/255)*2-1
g = generator(gen_input_dim,alpha)
plot_model(g,show_shapes=True, to_file='generator_model.png')
d = discriminator(dis_input_dim,alpha)
d_optim = Adam(lr=dis_lr,beta_1=dis_beta1)
d.compile(loss='binary_crossentropy',optimizer=d_optim)
plot_model(d,show_shapes=True, to_file='discriminator_model.png')
g_d = generator_discriminator(g, d)
g_optim = Adam(lr=gen_lr,beta_1=gen_beta1)
g_d.compile(loss='binary_crossentropy', optimizer=g_optim)
plot_model(g_d,show_shapes=True, to_file=
'generator_discriminator_model.png')
for epoch in range(epochs):
print("Epoch is", epoch)
print("Number of batches", int(X_train.shape[0]/batch_size))
for index in range(int(X_train.shape[0]/batch_size)):
noise =
np.random.normal(loc=0, scale=1, size=(batch_size,gen_input_dim))
image_batch = X_train[index*batch_size:(index+1)*batch_size,:]
generated_images = g.predict(noise, verbose=0)
if index % 20 == 0:
combine_images(generated_images,outdir,epoch,index)
# Images converted back to be within 0 to 255
print(image_batch.shape,generated_images.shape)
X = np.concatenate((image_batch, generated_images))
d1 = d.train_on_batch(image_batch,[1 - smooth_coef]*batch_size)
d2 = d.train_on_batch(generated_images,[0]*batch_size)
y = [1] * batch_size + [0] * batch_size
# Train the Discriminator on both real and fake images
make_trainable(d,True)
#_loss = d.train_on_batch(X, y)
d_loss = d1 + d2
print("batch %d d_loss : %f" % (index, d_loss))
noise =
np.random.normal(loc=0, scale=1, size=(batch_size,gen_input_dim))
make_trainable(d,False)
#d.trainable = False
# Train the generator on fake images from Noise
g_loss = g_d.train_on_batch(noise, [1] * batch_size)
print("batch %d g_loss : %f" % (index, g_loss))
if index % 10 == 9:
g.save_weights('generator', True)
d.save_weights('discriminator', True)
7.2 训练参数
GAN的训练需要调整一些重要的参数,以确保模型能够正常工作。以下是一些重要参数及其说明:
| 参数 | 值 | 说明 |
| ---- | ---- | ---- |
| batch_size | 100 | 小批量随机梯度下降的批量大小 |
| gen_input_dim | 100 | 输入随机噪声向量的维度 |
| gen_lr | 0.0001 | 生成器的学习率 |
| gen_beta1 | 0.5 | 生成器Adam优化器的beta_1参数 |
| dis_input_dim | (32, 32, 3) | 判别器输入的真实和假房屋号码图像的形状 |
| dis_lr | 0.001 | 判别器网络的学习率 |
| dis_beta1 | 0.5 | 判别器Adam优化器的beta_1参数 |
| alpha | 0.2 | LeakyReLU激活函数的泄漏因子,用于解决死亡ReLU问题 |
| epochs | 100 | 训练的轮数 |
| smooth_coef | 0.1 | 平滑系数,用于减少判别器对真实样本损失的权重,帮助GAN更好地收敛 |
使用GeForce GTX 1070 GPU,使用这些参数训练GAN大约需要3.12小时。建议使用GPU进行更快的训练。
7.3 训练过程中的参数控制
在训练过程中,我们需要控制判别器和生成器的训练状态。可以使用以下函数来禁用或启用网络参数的学习:
def make_trainable(model, trainable):
for layer in model.layers:
layer.trainable = trainable
通过将 trainable 设置为False,可以禁用参数的学习;设置为True,则可以启用训练。
8. 噪声分布
输入到GAN的噪声需要遵循特定的概率分布。通常使用均匀分布U[-1, 1]或标准正态分布(均值为0,标准差为1)来采样噪声向量的每个维度。经验表明,从标准正态分布采样噪声似乎比从均匀分布采样效果更好。在本实现中,我们使用标准正态分布来采样随机噪声。
9. 数据预处理
我们使用的SVHN数据集图像尺寸为32 x 32 x 3。为了实现更快和稳定的收敛,需要将图像的原始像素归一化到[-1, 1]的范围内。因此,生成器的最终激活函数使用tanh,以确保生成的图像像素值在[-1, 1]之间。
数据预处理的代码如下:
def load_img(path,dim=(32,32)):
img = cv2.imread(path)
img = cv2.resize(img,dim)
img = img.reshape((dim[1],dim[0],3))
return img
def read_data(dest,dir_flag=False):
if dir_flag == True:
files = os.listdir(dest)
X = []
for f in files:
img = load_img(dest + f)
X.append(img)
return X
else:
train_data = loadmat(path)
X,y = train_data['X'], train_data['y']
X = np.rollaxis(X,3)
X = (X/255)*2-1
return X
read_data 函数可以根据 dir_flag 的值来判断输入是原始处理的数据矩阵文件还是图像目录。如果是目录,则使用 load_img 函数读取图像并进行处理;如果是数据矩阵文件,则使用 loadmat 函数读取数据并进行归一化处理。
9.1 运行训练脚本
可以使用以下命令运行训练脚本:
python captcha_gan.py train --dest_train '/home/santanu/Downloads/train_32x32.mat' --outdir '/home/santanu/ML_DS_Catalog-/captcha/SVHN/' --dir_flag False --batch_size 100 --gen_input_dim 100 --gen_beta1 0.5 --gen_lr 0.0001 --dis_input_dim '(32,32,3)' --dis_lr 0.001 --dis_beta1 0.5 --alpha 0.2 --epochs 100 --smooth_coef 0.1
该脚本使用Python的 fire 包来调用用户指定的 train 函数,用户可以通过命令行参数提供函数的输入。
10. 训练过程中CAPTCHA的质量
在训练过程的不同轮次中,生成的CAPTCHA图像质量会有所不同。通过观察在第5轮、第51轮和第100轮训练后生成的CAPTCHA图像,可以发现随着训练的进行,CAPTCHA图像的质量逐渐提高。
10.1 第5轮训练后的CAPTCHA图像
(此处应插入Figure 10.7a的图像,但原文档未提供具体图像内容)
10.2 第51轮训练后的CAPTCHA图像
(此处应插入Figure 10.7b的图像,但原文档未提供具体图像内容)
10.3 第100轮训练后的CAPTCHA图像
(此处应插入Figure 10.7c的图像,但原文档未提供具体图像内容)
通过以上步骤,我们可以使用GAN生成高质量的CAPTCHA图像,为网络安全提供更有效的保护。同时,通过调整训练参数和网络结构,可以进一步提高生成图像的质量和多样性。
11. 训练流程总结
为了更清晰地理解整个训练过程,我们可以将其总结为以下流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(加载数据集):::process
B --> C(数据预处理):::process
C --> D(定义生成器网络g):::process
D --> E(定义判别器网络d):::process
E --> F(定义组合网络g_d):::process
F --> G(初始化优化器):::process
G --> H(设置训练参数):::process
H --> I{是否完成所有轮次}:::decision
I -- 否 --> J(生成随机噪声):::process
J --> K(生成器生成假图像):::process
K --> L(判别器评估真假图像):::process
L --> M(更新判别器参数):::process
M --> N(生成新的随机噪声):::process
N --> O(冻结判别器参数):::process
O --> P(生成器尝试欺骗判别器):::process
P --> Q(更新生成器参数):::process
Q --> I{是否完成所有轮次}:::decision
I -- 是 --> R([结束]):::startend
这个流程图展示了训练GAN的主要步骤,从数据加载到最终完成训练。在每一轮训练中,生成器和判别器会交替更新参数,直到达到指定的轮数。
12. 关键参数对训练的影响
在训练GAN时,一些关键参数会对训练结果产生重要影响。以下是对这些参数的详细分析:
12.1 学习率(Learning Rate)
- 生成器学习率(gen_lr) :控制生成器参数更新的步长。如果学习率过大,生成器可能会跳过最优解,导致训练不稳定;如果学习率过小,训练速度会变得非常缓慢。在本实现中,生成器学习率设置为0.0001。
- 判别器学习率(dis_lr) :判别器学习率的作用与生成器类似。通常,判别器的学习率可以设置得比生成器稍大一些,因为判别器需要更快地适应生成器的变化。本实现中,判别器学习率设置为0.001。
12.2 批量大小(Batch Size)
批量大小决定了每次更新参数时使用的样本数量。较大的批量大小可以使训练更加稳定,但会增加内存需求;较小的批量大小可以增加训练的随机性,但可能会导致训练不稳定。在本实现中,批量大小设置为100。
12.3 平滑系数(Smooth Coef)
平滑系数用于减少判别器对真实样本损失的权重。通过降低真实样本的损失权重,可以帮助GAN更好地收敛。在本实现中,平滑系数设置为0.1。
12.4 激活函数参数(Alpha)
LeakyReLU激活函数的泄漏因子(alpha)用于解决死亡ReLU问题。当输入为负数时,LeakyReLU会提供一个小的梯度,确保神经元不会因为梯度消失而死亡。在本实现中,alpha设置为0.2。
13. 常见问题及解决方法
在训练GAN的过程中,可能会遇到一些常见问题。以下是这些问题的分析及解决方法:
13.1 模式崩溃(Mode Collapse)
- 问题描述 :生成器只生成有限的几种图像,无法生成多样化的图像。
- 解决方法 :调整学习率、增加批量大小、使用更复杂的网络结构等。
13.2 训练不稳定
- 问题描述 :判别器和生成器的损失波动较大,训练结果不稳定。
- 解决方法 :调整学习率、使用梯度裁剪、增加平滑系数等。
13.3 判别器过强
- 问题描述 :判别器能够轻松区分真假图像,生成器难以欺骗判别器。
- 解决方法 :降低判别器的学习率、增加生成器的复杂度、使用标签平滑等。
13.4 生成器过强
- 问题描述 :生成器生成的图像能够轻易欺骗判别器,但图像质量不高。
- 解决方法 :增加判别器的复杂度、提高判别器的学习率等。
14. 总结与展望
通过使用生成对抗网络(GAN),我们可以生成类似于SVHN数据集的房屋号码图像,并将其用作CAPTCHA。在训练过程中,我们需要仔细调整参数,以确保生成器和判别器能够达到平衡,生成高质量的图像。
未来的研究方向可以包括:
- 改进网络结构 :尝试使用更复杂的网络结构,如ResNet、Inception等,以提高生成图像的质量。
- 多模态生成 :结合其他模态的数据,如图像、文本等,生成更丰富多样的CAPTCHA。
- 自适应训练 :根据训练过程中的反馈,自动调整参数,提高训练效率和生成图像的质量。
总之,使用GAN生成CAPTCHA是一个有前途的研究方向,可以为网络安全提供更有效的保护。通过不断的研究和改进,我们可以生成更加复杂和难以破解的CAPTCHA,从而提高网络的安全性。
超级会员免费看
1523

被折叠的 条评论
为什么被折叠?



