GAN是一种特殊类型的多层前馈神经网络。整体上看,它就是一个多层前馈神经网络;分开来看,其包含生成器(Generator)与判别器(Discriminator)两个网络(多层前馈神经网络)。GAN属于生成模型,它的主要作用就是生成与训练数据相似的数据。
GAN之所以能够生成与训练数据相似的数据,是因为有生成器,生成器就是负责生成样本的。而判别器是负责判定生成器生成的数据质量高低与否的,以此来提高生成器的质量。下面将从生成器与判别器两部分进行一个简单的原理介绍。
生成器:首先,生成器是一个多层前馈神经网络。它的输入是均匀分布的噪音(或者说就是一堆随机数),输出是与训练数据维度等相同的假数据(Fake data)。形式化点说:假设现在有一个数据集X,X={x1,x2,x3,...,xn},表示数据集X中一共有n个样本,xi表示其中的任意一个样本,而xi有d个特征(就是说每个样本都有d个特征)。那么生成器的最后一层神经元个数一定是d(要与数据集X中的相匹配),至于生成器的第一层第二层等等隐藏层,神经元个数设置为几个都无所谓。
判别器:判别器也是一个多层前馈神经网络。它的输入是我们手头有的训练数据集X(又称为real data)和生成器输出的样本(Fake data),输出是每个样本是真实数据(real data)的的概率值。
上面用公式大概推了一下GAN的数学原理,下面上代码:
clear;
clc;
%------------装载数据
train_x= load('Normalization_wbc.txt');
[m,n]= size(train_x);
%-----------定义模型
generator=nnsetup([30,15,30]);%这里的前几个数据无所谓,但最后一层神经元的个数要与训练数据的维度相等。
discriminator=nnsetup([30,15,1]);
%----------参数设置
batch_size= m; %将全部数据输入到网络中进行训练
iteration= 1000;%迭代多少次
images_num= m;
batch_num= floor(images_num / batch_size);
learning_rate= 0.00001;
for i = 1: iteration
kk = randperm(images_num);%生成m个无重复的随机数,将样本的序列全部打乱
%准备数据
images_real = train_x;
%这里要注意,可能要调整。最后的30应该是与生成器的第一层的神经元个数相同。
% noise = unifrnd(-1,1,m,30); %从下端点a和上端点的连续均匀分布生成随机数b,即从-1,1之间按照均匀分布生成随机数。生成的是m*100的随机矩阵。
noise = unifrnd(0,1,m,30);
%开始训练
%--------更新生成器,固定住辨别器
generator = nnff(generator, noise); %这里generator先走了一遍正向传播,用的是随机生成的噪音数据。
images_fake = generator.layers{generator.layers_count}.a; %images_fake就是生成器生成的fake数据,即生成器最后一层的输出,过了sigmoid.
discriminator = nnff(discriminator,images_fake);%辨别器开始走正向传播,辨别器的输入是生成器的输出。
logits_fake = discriminator.layers{discriminator.layers_count}.z; %logits_fake就是辨别器网络的输出,这里没有过sigmoid.不明白为什么?即判定是否是生成器生成的概率。
%这里的logits_fake又被称为score,是分类神经网络中最后一层的输出。
discriminator = nnbp_d(discriminator, logits_fake, ones(batch_size, 1));%logits_fake是判别器给出的结果,ones是全1的一个矩阵,即表示原始的真实数据的值全为1
generator = nnbp_g(generator, discriminator);
generator = nnapplygrade(generator, learning_rate);%这是利用adam优化器更新了生成器的权重。前面虽然计算了辨别器的反传,但是没有更新辨别器的权重
%------更新辨别器,固定住生成器
generator = nnff(generator, noise);%noise还是刚开始的noise,但是此时的生成器的权重已经更新过了
images_fake = generator.layers{generator.layers_count}.a;
images = [images_fake; images_real]; %images将生成的假数据与真实的训练数据放在了一起。按行拼接的。即把真实的数据放在了生成的数据的下面
discriminator = nnff(discriminator, images); %将生成数据与真实数据一并送入了辩别器网络进行正向传播。
logits = discriminator.layers{discriminator.layers_count}.z;
labels = [zeros(batch_size,1); ones(batch_size,1)];%预定义一个标签,前面的数据是0,后面的是1,也进行了拼接。
discriminator = nnbp_d(discriminator, logits, labels);%logits与真实的标签进行对比,注意与第29行代码进行对比。
discriminator = nnapplygrade(discriminator, learning_rate);%更新了辨别器网络的权重。
%----输出loss损失
c_loss(i,:) = sigmoid_cross_entropy(logits(1:batch_size), ones(batch_size,1));%这是生成器的损失
d_loss (i,:)= sigmoid_cross_entropy(logits, labels);%判别器的损失
% fprintf('c_loss:"%f", d_loss:"%f\n"', c_loss, d_loss);
% if corr2(images_fake,images_real)>0.6
% break;
% end
end
% sigmoid函数
function output = sigmoid(x)
output = 1 ./(1+exp(-x));
end
%relu函数
function output = relu(x)
output = max(x,0);
end
%relu对x的导数
function output = delta_relu(x)
output = max(x,0);
output(output>0) = 1;
end
%sigmoid交叉熵损失函数
function result = sigmoid_cross_entropy(logits,labels)
result = max(logits,0) - logits .* labels + log(1+exp(-abs(logits)));
result = mean(result);
end
%sigmoid_cross_entropy对logits的导数=
function result = delta_sigmoid_cross_entropy(logits,labels)
temp1 = max(logits, 0);
temp1(temp1>0) = 1;
temp2 = logits;
temp2(temp2>0) = -1;
temp2(temp2<0) = 1;
result = temp1- labels +exp(-abs(logits))./(1+exp(-abs(logits))) .*temp2;
end
%根据所给的结构构建神经网络
function nn = nnsetup(architecture)
nn.architecture = architecture;
nn.layers_count = numel(nn.architecture);%判断网络有几层,即architecture中[,,,,]有几个隐层神经元的设置
%adam优化器所用参数
nn.t=0;
nn.beta1 = 0.9;
nn.beta2 = 0.999;
nn.epsilon = 10^(-8);
%----------------------
for i=2 : nn.layers_count
nn.layers{i}.w = normrnd(0, 0.02, nn.architecture(i-1), nn.architecture(i));%normrnd是指生成正态分布的随机数,第一个数字0表示均值为0,第二个数字0.02表示sigma=0.02,第三个值表示生成的维度大小。例如第三与第四的值分别为30,15,则表示生成30*15的矩阵。
nn.layers{i}.b = normrnd(0, 0.02, 1, nn.architecture(i));%生成偏置
nn.layers{i}.w_m = 0;%好像是跟权重偏置有关的参数,但是都设置为了0,好像没啥意义。
nn.layers{i}.w_v = 0;
nn.layers{i}.b_m = 0;
nn.layers{i}.b_v = 0;
end
end
%前向传播
function nn = nnff(nn, x)
nn.layers{1}.a = x;
for i = 2 : nn.layers_count
input = nn.layers{i-1}.a;
w = nn.layers{i}.w;
b = nn.layers{i}.b;
nn.layers{i}.z = input *w + repmat(b, size(input,1), 1); %这里本质上是计算wx+b,而对b矩阵进行了变换,好像使得b加到了每一个wx上,后面调试的时候再看一下
if i ~= nn.layers_count
nn.layers{i}.a = relu(nn.layers{i}.z); %如果不是最后一层的话,就过relu激活函数
else
nn.layers{i}.a = sigmoid(nn.layers{i}.z);% 如果是最后一层的话,就过sigmoid激活函数
end
end
end
%discriminator辨别器的bp反传
function nn = nnbp_d(nn, y_h, y)
%d表示残差
n = nn.layers_count;
%最后一层的残差
nn.layers{n}.d = delta_sigmoid_cross_entropy(y_h, y); %sigmoid交叉熵损失函数的偏导数,就是最后一层的残差。
for i = n-1 : -1:2 %第n-1层到第n-2层;计算倒数第二层到倒数第三层的残差,依此类推
d = nn.layers{i+1}.d;
w = nn.layers{i+1}.w;
z = nn.layers{i}.z;
%每一层的残差是对每一层的未激活值z求偏导数,是上一层的残差乘 w 再乘上 激活值对未激活值的偏导数
nn.layers{i}.d = d * w' .* delta_relu(z);
end
%求出各层的残差之后,就可以依据残差计算出每一层的权重要改变多少,即deltaW
for i = 2: n
d = nn.layers{i}.d;
a = nn.layers{i-1}.a;
%dw
nn.layers{i}.dw = a'*d /size(d,1);%%%%%%这里的a是啥,还没写到这个功能。
nn.layers{i}.db = mean(d,1);
end
end
%generator生成器的bp反传
% function g_net = nnbp_g(g_net, d_net)
% n= g_net.layers_count;
% a= g_net.layers{n}.a; %%%%这里是第一次出现a
% %对g进行bp的时候,将g与d看成一个整体的神经网络,g最后一层的残差等于d第二层的残差乘上(a.*(a_o))
% g_net.layers{n}.d = d_net.layers{2}.d * d_net.layers{2}.w' .* (a .* (1-a));%%%%%这是生成器最后一层的残差,没看懂,后面最好手推一下
% for i = n-1 : n-2 %逐层计算残差
% d = g_net.layers{i+1}.d;
% w = g_net.layers{i+1}.w;
% z = g_net.layers{i}.z;
% %每一层的残差是对每一层的未激活值求偏导数,是后一层的残差呈上w,再乘上着一层激活值对未激活值的偏导数
% g_net.layers{i}.d = d*w' .* delta_relu(z);
% end
% %求出各层残差之后,计算w与b的改变量
% for i= 2:n
% d = g_net.layers{i}.d;
% a = g_net.layers{i-1}.a;
% g_net.layers{i}.dw = a'*d /size(d,1);
% g_net.layers{i}.db = mean(d,1);
% end
% end
% generator的bp
function g_net = nnbp_g(g_net, d_net)
n = g_net.layers_count;
a = g_net.layers{n}.a;
% generator的loss是由label_fake得到的,(images_fake过discriminator得到label_fake)
% 对g进行bp的时候,可以将g和d看成是一个整体
% g最后一层的残差等于d第2层的残差乘上(a .* (a_o))
g_net.layers{n}.d = d_net.layers{2}.d * d_net.layers{2}.w' .* (a .* (1-a));
for i = n-1:-1:2
d = g_net.layers{i+1}.d;
w = g_net.layers{i+1}.w;
z = g_net.layers{i}.z;
% 每一层的残差是对每一层的未激活值求偏导数,所以是后一层的残差乘上w,再乘上对激活值对未激活值的偏导数
g_net.layers{i}.d = d*w' .* delta_relu(z);
end
% 求出各层的残差之后,就可以根据残差求出最终loss对weights和biases的偏导数
for i = 2:n
d = g_net.layers{i}.d;
a = g_net.layers{i-1}.a;
% dw是对每层的weights进行偏导数的求解
g_net.layers{i}.dw = a'*d / size(d, 1);
g_net.layers{i}.db = mean(d, 1);
end
end
%Adam优化器
function nn = nnapplygrade(nn, learning_rate);
n = nn.layers_count;
nn.t = nn.t+1;
beta1 = nn.beta1;
beta2 = nn.beta2;
lr = learning_rate * sqrt(1-nn.beta2^nn.t) / (1-nn.beta1^nn.t);
for i = 2:n
dw = nn.layers{i}.dw;
db = nn.layers{i}.db;
%使用adam更新权重与偏置
nn.layers{i}.w_m = beta1 * nn.layers{i}.w_m + (1-beta1) * dw;
nn.layers{i}.w_v = beta2 * nn.layers{i}.w_v + (1-beta2) * (dw.*dw);
nn.layers{i}.w = nn.layers{i}.w -lr * nn.layers{i}.w_m ./ (sqrt(nn.layers{i}.w_v) + nn.epsilon);
nn.layers{i}.b_m = beta1 * nn.layers{i}.b_m + (1-beta1) * db;
nn.layers{i}.b_v = beta2 * nn.layers{i}.b_v + (1-beta2) * (db .* db);
nn.layers{i}.b = nn.layers{i}.b -lr * nn.layers{i}.b_m ./ (sqrt(nn.layers{i}.b_v) + nn.epsilon);
end
end
%%initialization初始化,生成随机高斯变量
function parameter=initializeGaussian(parameterSize,sigma)
if nargin < 2
sigma=0.05;
end
parameter=randn(parameterSize,'double') .* sigma;%sigma是标准差,.*sigma是为了让生成的正太分布的随机数满足标准差为sigma,标准差越大,离散程度越大,图形越平缓,反之,图形越陡,数据越集中,越靠近均值
end
对别人的代码改动了一部分,然后加了许多注释。