本文主要总结了GAN(Generative Adversarial Networks) 生成对抗网络的基本原理并通过mnist数据集展示GAN网络的应用。

GAN网络是由两个目标相对立的网络构成的,在所有GAN框架中都至少包含了两个部分,生成模型部分和判别模型部分。生成模型的目标是制造出一些与真实数据十分相似的伪造数据而判别模型的目标则恰恰相反,是找到如何分辨这些真实的数据以及伪造数据。

下图可以用来比较简明地理解GAN的工作原理 :

生成模型的输入是随机的噪声编码zzz,通过这个噪声生成的数据 G(z)G(z)G(z) 就是我们伪造出的数据了。判别模型的输入是一组混合了真实数据xxx以及伪造的数据G(z)G(z)G(z)的混合数据并输出D(G(z))D(G(z))D(G(z)) 以及D(x)D(x)D(x),代表了对真实数据和伪造数据的判定。如果我们把伪造数据的标签定为0,真实数据的标签定为1,那么判别模型的训练目标就是使D(G(z))D(G(z))D(G(z))无限接近0,使D(x)D(x)D(x)无限接近1,以此来达到分辨真实数据和伪造数据的目的。相反的,生成模型的训练目标则是要使得D(G(z))D(G(z))D(G(z))接近1,即达到欺骗判别模型,以假乱真的目的。我们不难发现,实际生成模型的训练离不开判别模型的判定,而判别模型的训练也需要生成模型生成的伪造数据,二者相辅相成。这一点在下面基于mnist数据集的训练代码中也会有所体现。

首先是import所需库并导入mnist数据,我们通过全部除以255的方法正则化用于训练和测试的图像。

import tensorflow as tf
from tensorflow import keras
from matplotlib import pyplot as plt
from keras.layers import Dense, Conv2DTranspose, BatchNormalization, Reshape, LeakyReLU, Conv2D
import numpy as np# load data from database mnist
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(path="mnist.npz")# normaliser à [0,1]
x_train = x_train/255.0
x_test = x_test/255.0

训练集x_train中包含了60000张 28*28的单通道灰度图片,每张图片对应标签为0-9的十个数字,下面展示其中一张代表了数字5的图片。

1. 生成模型部分

首先是我们的生成模型,如上所说,生成模型的输入为随机噪声,输出为伪造的数据。在这个例子中,我们最终要输出一张与真实图片大小一致的灰度图。生成模型由两类主要的层构成,其中之一就是全连接层dense,这个层实际上类似于CNN卷积神经网络中代表特征 feature 的一层,我们可以理解为它由数个低解析度的图像组成。之后需要的就是将解析度提升至的操作,这里用了Conv2DTranspose层,可以理解为是一个反向的pooling池化层(用于还原参数和数据)和一个2D卷积层的结合。Conv2DTranspose层中的参数stride设置为(2, 2) 即保证了每经过一次该层,输出的宽度和高度都扩大一倍。如下例所示,由7 * 7 经过两次Conv2DTranspose层使得最终输出的灰度图宽度和高度为 28 * 28 。生成模型的输出层是一个简单的2D卷积层,使用activation激励函数为sigmoid,这是由于sigmoid函数可以使得输出值属于[0, 1]的区间,也对应了我们在一开始在数据预处理的时候,将数据正则化至[0, 1]的操作。

# creation of a generator
def creation_generateur(dim_latent=10):generator = keras.models.Sequential()generator.add(Dense(128*7*7, input_dim=dim_latent))generator.add(Reshape((7,7,128)))# upsampling generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))generator.add(LeakyReLU(alpha=0.2))# upsampling generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))generator.add(LeakyReLU(alpha=0.2))generator.add(Conv2D(1, kernel_size=(7, 7), activation='sigmoid', padding="same"))return generator

2. 判别模型部分

接着我们创建GAN中的判别模型,相比生成模型而言,判别模型就更加简明,其实质就是一个classifier二元分类器。他由多个卷积层构成,其中添加了drop out用于防止过拟合。输出层是一个仅有一个神经元的全连接层,使用sigmoid作为激励函数。正如我们前文所提到的,判别模型会对输入进行分类,判别输入究竟是真实图像还是由生成模型伪造的图像。

# creation of a discriminator
def creation_discriminateur():discriminator = keras.models.Sequential()discriminator.add(Conv2D(filters=64, kernel_size=(5,5),strides=(2,2), input_shape=(28,28,1), padding="same")) discriminator.add(LeakyReLU(alpha=0.2))discriminator.add(keras.layers.Dropout(0.4))discriminator.add(Conv2D(filters=64, kernel_size=(3,3),strides=(2,2), padding="same"))discriminator.add(LeakyReLU(alpha=0.2))discriminator.add(keras.layers.Dropout(0.4))discriminator.add(keras.layers.Flatten())discriminator.add(keras.layers.Dense(1,activation='sigmoid'))return discriminator

3. 叠加模型(用于训练生成模型)

有了上述两部分代码,接下来我们可以构建基于生成模型部分以及判别模型部分的GAN神经网络。这里我们只是将两部分叠加起来,而并非构建第三个神经网络。这里构建GAN的方式与之后训练GAN是有关系的。如下代码所示,我们将生成模型与判别模型叠加起来,并让判别模型中的参数在该模型中不可训练。其实质是因为这个叠加模型GAN是用于训练生成模型的。整体的过程如下 : 输入是一组随机噪声,经过生成模型后变成了一组伪造的数字灰度图,再经过判别模型,输出一个0-1之间的值。这是梯度的正向传播过程。接着我们利用反向梯度传播来更新我们生成模型的各个权重,以此来达到使该叠加模型输出趋向于1。 这里其实就是GAN模型训练的重中之重,即生成模型的目标与判别模型相反,其目标为生成的伪造数据能更大概率被识别为是真实数据,即标签1。这一点在下一个部分GAN模型的训练中会更详细解释。

def creation_reseau_GAN(model_generateur, model_discriminateur):GAN = keras.models.Sequential()GAN.add(model_generateur)# 判别模型中的参数设置为不可训练discriminateur.trainable = FalseGAN.add(model_discriminateur)optimizer_GAN = keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)GAN.compile(loss='binary_crossentropy', optimizer=optimizer_GAN)return GAN

4. GAN模型的训练过程

根据上述三个部分,我们创建完毕了所要用到的神经网络。在这个部分中我们就进入GAN模型的训练过程。

首先我们要给予生成模型一组随机噪声,以便其根据这个噪声创造不同的伪造数据,该噪声的意义在于避免所有的生成数据都是一样的。可以用高斯噪声或正态分布生成噪声,不同的噪声类型选取对模型训练的影响不大。

# 随机选取潜在编码(噪声)
def code_latent_aleatoire(dim_latent, nb_exemples):# distribution normal standard normal distribution# code_latent = np.random.normal(loc=0, scale=1, size=(nb_exemples, dim_latent))# code_latent = code_latent.reshape(nb_exemples, dim_latent)code_latent = np.random.randn(nb_exemples, dim_latent)return code_latent

为了判别模型的训练,我们同时需要真实的数据和伪造的数据,因此我们定义函数用于根据现有的生成模型和随机噪声来生成伪造图片,伪造图片的标签为0。同样的,定义另一个函数,用于在mnist数据中随机选取真实图片,真实图片的标签为1。

def construction_image_generateur(generateur_model, dim_latent, nb_exemples):# 生成随机噪声X = code_latent_aleatoire(dim_latent,nb_exemples)image_fraud = generateur_model.predict(X)# 生成的图片的真实标签为0Label_genere = np.zeros((nb_exemples,1))return image_fraud, Label_generedef tirage_reelle_aleatoire(base_de_donnees, nb_exemples):mat_images_reel = np.zeros((nb_exemples, 28, 28))for i in range(nb_exemples):index = np.random.randint(0, base_de_donnees.shape[0])mat_images_reel[i,:,:] = base_de_donnees[index,:,:]mat_images_reel = mat_images_reel.reshape(nb_exemples, 28, 28, 1)# 从mnist数据集选取的图片的标签均为1labels_reel = np.ones((nb_exemples, 1))return mat_images_reel, labels_reel

接下来就是重头戏,GAN网络的训练过程了。要训练一个gan模型,我们常用的方法是利用train_on_batch函数来训练。在每个batch中,首先训练判别模型,我们先构建一组由真实数据和伪造数据组成的数据集,训练判别模型使其能更准确分辨哪些数据是真实的而哪些数据是伪造的。紧接着在同一个batch中,训练叠加模型,通过随机噪声进入生成模型去生成伪造的图片,并给予他们一个假的标签,即所有的伪造图片我们都标为1,以此来使得生成模型向着使判别模型判定伪造图片为真的方向进行。

# GAN 模型的训练
def entrainement_GAN_model(model_generateur, model_discriminateur, base_de_donnees, dim_latent, GAN_model, epochs=20, batch_size=128):vector_loss_discriminateur = []vector_loss_gan = []# 通过数据集的大小以及batchsize计算每个epoch对应的batch数量total_batch = np.floor(base_de_donnees.shape[0]/batch_size).astype(np.int)# 对于每个epochfor nb_epoch in range(epochs):# 对于每个batchfor index_batch in range(total_batch):# 每个batch中有一半的图像为伪造图片x_fraud, y_fraud = construction_image_generateur(model_generateur, dim_latent=dim_latent, nb_exemples=int(batch_size/2))# 剩下的图像为真实的从mnist数据集中随机提取的图片x_reel, y_reel = tirage_reelle_aleatoire(base_de_donnees=base_de_donnees, nb_exemples=int(batch_size/2))# 叠加所有的数据 id_exemple, 28, 28x_chaque_batch = np.vstack((x_reel, x_fraud))# 叠加所有的标签 : id_exemple, labely_chaque_batch = np.vstack((y_reel, y_fraud))# 使用构建的一半真实一半伪造的数据训练判别模型,更新权重。loss_discriminateur = model_discriminateur.train_on_batch(x_chaque_batch, y_chaque_batch)# 接下来是每个Batch训练叠加模型的部分,其实质是用于训练生成模型。x_generateur = code_latent_aleatoire(dim_latent=dim_latent, nb_exemples=batch_size)# 这里我们要使得叠加模型的输出趋向于1,即使判别模型认为生成的图像也是真实的。y_gan = np.ones((batch_size,1))# 使用判别模型(叠加模型)的输出来训练生成模型gan_loss = GAN_model.train_on_batch(x_generateur, y_gan)# 显示每个batch对应的两个部分的lossprint("%d epochs,%d batches, loss_discriminateur : %f, loss_gan : %f"%(nb_epoch+1, index_batch+1, loss_discriminateur, gan_loss))# 保存模型以及图片部分if (nb_epoch + 1)%3 == 0:model_generateur.save("generateur_models/generateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")model_discriminateur.save("discriminateur_models/discriminateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(loss_discriminateur) + ".h5") GAN_model.save("gan_models/gan_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")generer_sauvegarde_images(model_generateur=model_generateur, after_epoch=nb_epoch)vector_loss_discriminateur.append(loss_discriminateur)vector_loss_gan.append(gan_loss)return vector_loss_discriminateur, vector_loss_gan

5. 附录(完整代码)及相关讨论

该部分中我将放出完整的代码以及结果。

import tensorflow as tf
from tensorflow import keras
from matplotlib import pyplot as plt
from keras.layers import Dense, Conv2DTranspose, BatchNormalization, Reshape, LeakyReLU, Conv2D
import numpy as np# creation of a generator
def creation_generateur(dim_latent=10):generator = keras.models.Sequential()generator.add(Dense(128*7*7, input_dim=dim_latent))generator.add(Reshape((7,7,128)))# upsampling generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))generator.add(LeakyReLU(alpha=0.2))# upsampling generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))generator.add(LeakyReLU(alpha=0.2))generator.add(Conv2D(1, kernel_size=(7, 7), activation='sigmoid', padding="same"))return generator# creation of a discriminator
def creation_discriminateur():discriminator = keras.models.Sequential()discriminator.add(Conv2D(filters=64, kernel_size=(5,5),strides=(2,2), input_shape=(28,28,1), padding="same")) discriminator.add(LeakyReLU(alpha=0.2))discriminator.add(keras.layers.Dropout(0.4))discriminator.add(Conv2D(filters=64, kernel_size=(3,3),strides=(2,2), padding="same"))discriminator.add(LeakyReLU(alpha=0.2))discriminator.add(keras.layers.Dropout(0.4))discriminator.add(keras.layers.Flatten())discriminator.add(keras.layers.Dense(1,activation='sigmoid'))return discriminatordef creation_reseau_GAN(model_generateur, model_discriminateur):GAN = keras.models.Sequential()GAN.add(model_generateur)# 判别模型中的参数设置为不可训练discriminateur.trainable = FalseGAN.add(model_discriminateur)optimizer_GAN = keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)GAN.compile(loss='binary_crossentropy', optimizer=optimizer_GAN)return GAN# 随机选取潜在编码(噪声)
def code_latent_aleatoire(dim_latent, nb_exemples):# distribution normal standard normal distribution# code_latent = np.random.normal(loc=0, scale=1, size=(nb_exemples, dim_latent))# code_latent = code_latent.reshape(nb_exemples, dim_latent)code_latent = np.random.randn(nb_exemples, dim_latent)return code_latentdef construction_image_generateur(generateur_model, dim_latent, nb_exemples):# 生成随机噪声X = code_latent_aleatoire(dim_latent,nb_exemples)image_fraud = generateur_model.predict(X)# 生成的图片的真实标签为0Label_genere = np.zeros((nb_exemples,1))return image_fraud, Label_generedef tirage_reelle_aleatoire(base_de_donnees, nb_exemples):mat_images_reel = np.zeros((nb_exemples, 28, 28))for i in range(nb_exemples):index = np.random.randint(0, base_de_donnees.shape[0])mat_images_reel[i,:,:] = base_de_donnees[index,:,:]mat_images_reel = mat_images_reel.reshape(nb_exemples, 28, 28, 1)# 从mnist数据集选取的图片的标签均为1labels_reel = np.ones((nb_exemples, 1))return mat_images_reel, labels_reel# 用于保存和显示图片的函数
def generer_sauvegarde_images(model_generateur,after_epoch,nb_images_sqrt=5,dim_latent=100):nb_images = nb_images_sqrt * nb_images_sqrtimages_frauds, _ = construction_image_generateur(model_generateur,dim_latent=dim_latent,nb_exemples=nb_images)for i in range(nb_images):plt.subplot(nb_images_sqrt, nb_images_sqrt, i+1)plt.imshow(images_frauds[i].reshape(28,28))filepath = "images_fraudes_genere_%d.png" %(after_epoch+1)plt.savefig(filepath)plt.close()# GAN 模型的训练
def entrainement_GAN_model(model_generateur, model_discriminateur, base_de_donnees, dim_latent, GAN_model, epochs=20, batch_size=128):vector_loss_discriminateur = []vector_loss_gan = []# 通过数据集的大小以及batchsize计算每个epoch对应的batch数量total_batch = np.floor(base_de_donnees.shape[0]/batch_size).astype(np.int)# 对于每个epochfor nb_epoch in range(epochs):# 对于每个batchfor index_batch in range(total_batch):# 每个batch中有一半的图像为伪造图片x_fraud, y_fraud = construction_image_generateur(model_generateur, dim_latent=dim_latent, nb_exemples=int(batch_size/2))# 剩下的图像为真实的从mnist数据集中随机提取的图片x_reel, y_reel = tirage_reelle_aleatoire(base_de_donnees=base_de_donnees, nb_exemples=int(batch_size/2))# 叠加所有的数据 id_exemple, 28, 28x_chaque_batch = np.vstack((x_reel, x_fraud))# 叠加所有的标签 : id_exemple, labely_chaque_batch = np.vstack((y_reel, y_fraud))# 使用构建的一半真实一半伪造的数据训练判别模型,更新权重。loss_discriminateur = model_discriminateur.train_on_batch(x_chaque_batch, y_chaque_batch)# 接下来是每个Batch训练叠加模型的部分,其实质是用于训练生成模型。x_generateur = code_latent_aleatoire(dim_latent=dim_latent, nb_exemples=batch_size)# 这里我们要使得叠加模型的输出趋向于1,即使判别模型认为生成的图像也是真实的。y_gan = np.ones((batch_size,1))# 使用判别模型(叠加模型)的输出来训练生成模型gan_loss = GAN_model.train_on_batch(x_generateur, y_gan)# 显示每个batch对应的两个部分的lossprint("%d epochs,%d batches, loss_discriminateur : %f, loss_gan : %f"%(nb_epoch+1, index_batch+1, loss_discriminateur, gan_loss))# 保存模型以及图片部分if (nb_epoch + 1)%3 == 0:model_generateur.save("generateur_models/generateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")model_discriminateur.save("discriminateur_models/discriminateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(loss_discriminateur) + ".h5") GAN_model.save("gan_models/gan_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")generer_sauvegarde_images(model_generateur=model_generateur, after_epoch=nb_epoch)vector_loss_discriminateur.append(loss_discriminateur)vector_loss_gan.append(gan_loss)return vector_loss_discriminateur, vector_loss_gan# load data from database mnist
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(path="mnist.npz")
# normaliser à [0,1]
x_train = x_train/255.0
x_test = x_test/255.0
dim_code_latent = 100
generateur = creation_generateur(dim_latent=dim_code_latent)
discriminateur = creation_discriminateur()
opt_discriminateur = keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)
discriminateur.compile(loss=keras.losses.binary_crossentropy, optimizer=opt_discriminateur)
GAN = creation_reseau_GAN(generateur, discriminateur)
loss_discriminateur, loss_gan = entrainement_GAN_model(model_generateur=generateur, model_discriminateur=discriminateur,base_de_donnees=x_train, GAN_model=GAN, epochs=21, batch_size=256, dim_latent=dim_code_latent)

训练过后的生成模型就可以用来生成伪造图片,下图为经过9个epoch的训练后,所生成的伪造图片,我们可以发现生成的图像中已经有可以辨认出的数字,例如5,7,9。有理由认为我们在经过更多的epoch训练后,生成模型的细节将进一步完善。

小tips : 在训练GAN模型时,要时刻关注判别模型的loss以及accuracy。如果判别模型的loss下降的太快,这就意味着生成模型正在给我们生成一些垃圾数据,这些数据轻易地就被判别模型给判定为伪造了。一般而言,维持判别模型的loss在一定范围内会对整个GAN模型的训练有帮助。更进一步的说,一个完美的判别模型,即可以完美分辨真伪图片的判别模型不能给予生成模型足够的信息来产生合理的伪造图片,因此在训练的过程中时刻保持判别模型的非完美性是有必要的,这也增加了在处理更复杂问题时,GAN模型在调参方面的困难程度。

GAN生成对抗网络基本概念及基于mnist数据集的代码实现相关推荐

  1. 深度学习(九) GAN 生成对抗网络 理论部分

    GAN 生成对抗网络 理论部分 前言 一.Pixel RNN 1.图片的生成模型 2.Pixel RNN 3.Pixel CNN 二.VAE(Variational Autoencoder) 1.VA ...

  2. 使用PyTorch构建GAN生成对抗网络源码(详细步骤讲解+注释版)01 手写字体识别

    文章目录 1 生成对抗网络基本概念 2 生成对抗网络建模 2.1 建立MnistDataset类 2.2 建立鉴别器 2.3 测试鉴别器 2.4 Mnist生成器制作 3 模型的训练 4 模型表现的判 ...

  3. 深度学习 GAN生成对抗网络-1010格式数据生成简单案例

    一.前言 本文不花费大量的篇幅来推导数学公式,而是使用一个非常简单的案例来帮助我们了解GAN生成对抗网络. 二.GAN概念 生成对抗网络(Generative Adversarial Networks ...

  4. 使用PyTorch构建GAN生成对抗网络源码(详细步骤讲解+注释版)02 人脸识别 上

    文章目录 1 数据集描述 2 GPU设置 3 设置Dataset类 4 设置辨别器类 5 辅助函数与辅助类 1 数据集描述 此项目使用的是著名的celebA(CelebFaces Attribute) ...

  5. 54_pytorch GAN(生成对抗网络)、Gan代码示例、WGAN代码示例

    1.54.GAN(生成对抗网络) 1.54.1.什么是GAN 2014 年,Ian Goodfellow 和他在蒙特利尔大学的同事发表了一篇震撼学界的论文.没错,我说的就是<Generative ...

  6. 使用PyTorch构建GAN生成对抗网络源码(详细步骤讲解+注释版)02 人脸识别 下

    文章目录 1 测试鉴别器 2 建立生成器 3 测试生成器 4 训练生成器 5 使用生成器 6 内存查看 上一节,我们已经建立好了模型所必需的鉴别器类与Dataset类. 使用PyTorch构建GAN生 ...

  7. GAN (生成对抗网络) 手写数字图片生成

    GAN (生成对抗网络) 手写数字图片生成 文章目录 GAN (生成对抗网络) 手写数字图片生成 Discriminator Network Generator Network 简单版本的生成对抗网络 ...

  8. GAN -- 生成对抗网络

    GAN -- 生成对抗网络 生成对抗网络(英语:Generative Adversarial Network,简称GAN)是非监督式学习的一种方法,通过让两个神经网络相互博弈的方式进行学习.该方法由伊 ...

  9. 【轩说AI】生成模型(2)—— GAN生成对抗网络 + WGAN + Conditional GAN + Cycle GAN

    文章目录 GAN生成对抗网络(Generative Adversarial Network) 神经网络的本质就是一个函数,一个用于拟合的函数 生成模型面临的前所未有的问题 GAN解决这一问题的思想 O ...

最新文章

  1. wordpress自动批量定时发布插件 DX-auto-publish
  2. android 跑分软件,跑分软件安兔兔公布了6月份Android手机性能榜TOP10
  3. 创建没有Document的MFC MDI应用程序
  4. 华为开发者大会上,鸿蒙问世、方舟编译器开源、还有 EMUI 10;壕置100万美元,苹果推出漏洞攻击报告赏金计划……...
  5. 如何改变炒股频繁交易的毛病?
  6. Python 装饰器的八种写法
  7. mybatis操作mysql的奇淫技巧总结(代码库)
  8. python大牛基础_十年Python大牛总结的python基础知识实例,快收藏!
  9. 3.1.3、控制结构
  10. ASP.NET WebAPI导入EXCEL数据
  11. 图解深度学习的笔记:1.MP模型,感知器,BP下的多层感知器
  12. c语言system.h头文件下载,c语言头文件大全
  13. Mybatis-plus代码生成器
  14. 这可能是 Python 里最强的绘制地图神器
  15. Android WebView 下载没反应
  16. 【操作系统】CPU是如何执行程序的?
  17. 北森{“message“:“un-authorized“}
  18. 基于VGG19的识别中国人、韩国人、日本人分类器
  19. “美亚杯”第二届中国电子数据取证大赛答案解析(个人赛)
  20. sysvinit源码分析 Linux-init-process-analyse

热门文章

  1. scala部分应用函数_Scala中的部分函数
  2. c# 整数类型转byte_C#中数据类型的整数类型
  3. stl vector 函数_vector :: pop_back()函数以及C ++ STL中的示例
  4. Java ListResourceBundle getContents()方法与示例
  5. stl vector 函数_vector :: push_back()函数,以及C ++ STL中的示例
  6. 三、“涤纶纤维和棉纤维两组分纤维在涤/棉混纺织物燃烧过程中有着明显的物理相互作用和化学相互作用”,解释这两种作用。
  7. python定时任务_Python定时任务(上)
  8. 每日一题:leetcode74.搜索二维矩阵
  9. Linux下C语言使用openssl库进行MD5校验
  10. diff and colordiff on Ubuntu