Generative Adversarial Networks 生成对抗网络 Matlab实现与讲解

GAN是一种特殊类型的多层前馈神经网络。整体上看,它就是一个多层前馈神经网络;分开来看,其包含生成器(Generator)与判别器(Discriminator)两个网络(多层前馈神经网络)。GAN属于生成模型,它的主要作用就是生成与训练数据相似的数据。

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


对别人的代码改动了一部分,然后加了许多注释。

猜你喜欢

转载自blog.csdn.net/jinhualun911/article/details/123894062