百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

机器学习——生成模型

toyiye 2024-06-27 01:08 21 浏览 0 评论

前面无监督学习主要针对的是一种“降维”的学习任务,将数据降维到另一个能够表达数据含义的某种空间中,本节主要是无监督学习中的另一个任务——生成进行介绍。


生成模型

0.生成模型介绍

通常生成模型是指学习样本数据的分布,可以生成一些新的数据,是相对于判别模型而言的,并不特指有监督学习和无监督学习,比如朴素贝叶斯模型就是一种生成模型。

在这里生成模型主要指的是无监督学习中的生成模型,在无监督学习中的主要任务是让机器学习给定的样本,然后生成一些新的东西出来。比如:

给机器看一些图片,能够生成一些新的图片出来,给机器读一些诗,然后能够自己写诗出来。

在前面所学习的无监督学习主要是针对降维的,成为化繁为简,那么在这里的生成模型则称之为无中生有。

有三种常见的生成模型:

1、Component-by-Component

2、AutoEncoder

3、Generative Adversarial NetWork(GAN)

下面就对这三种方法进行简单的介绍,这里还是主要介绍其大致概念,后面深度学习会具体展开讨论。

1.Component-by-Component

这种方法类似于前面说的Predicted-based的方法,即根据前面的来预测后面的。比如一张3*3大小的图片:

我们希望有一个网络,输入相邻的两个像素,然后输出下一个像素。通过大量的图片来训练网络,然后给定一个初始的像素,就可以生成一张新的图片出来。

又或者通过阅读大量的文章,然后输出一张新的文章出来。

这个任务也称作Seq2Seq的学习任务,其网络主要用的就是RNN。后面到深度学习部分会对RNN再进行了解。这里先举个简单的例子:让机器自己创造一些宝可梦出来。

通过大量的宝可梦的图片训练一个网络,然后让这个网络生成一些图片出来,如图:

在测试时,首先拿一些真实的宝可梦的图片,然后盖住一部分,比如盖住50%,让机器生成这50%的图片,可以得到如右图所示的结果(并非对应关系)。

2.AutoEncoder

2.1 Review AutoEncoder

在前面说过AutoEncoder的基本概念,即通过encoder对数据的降维,而在生成模型中,decoder则可以用来生成新的数据出来。

当把AutoEncoder的网络层数增加时,就变成了deep AutoEncoder。通过给定一个code,然后输入到decoder中,就会产生新的image出来。

比如在手写识别中,数据降到2维后,给定一个二维code,则可以生成一张手写数字出来。

然而在实际中,通常对于未知的code,我们并不能保证所给的code与产生的图片属于同一个分布,这也就可能会导致当给一个code时,所生成的图片是一个“四不像”,与预期不符。

比如对于月球图片的学习,将图片降到1维(中间红色的线),然后再decode回去,如图:

假设两边的状态一个是满月一个是新月,我们想要中间找个点,得到弦月的图片,然而当我们在中间的位置任意找一个code输入到decoder进去时,并不一定能保证得到的是弦月。

也就是说我们无法真正的构造出code,我们并不知道code来自于哪个分布,因此为解决这一问题,需要变分自编码器(VAE)

2.2 VAE简介

VAE在进行图片还原时,要保证code与decoder的输出服从一定的分布,所以V的意思代表Variational。换句话说,就是在生成code的时候我们限制这些code服从一定的分布

VAE的直观理解是,在生成的code上加上一些“噪音”,如图:

在生成的code上面加上噪音,例如满月的图片生成code之后,在其周围加上噪音之后,那么在噪音范围内所生成的图片都是满月的图片,同理新月也是。

当我们在满月和新月的code的中间取一点时(红色的箭头),此时相当于对新月和满月进行一个加权,从而生成了弦月。

上面就是VAE的直观的解释,那么通常这个code的“噪声”是如何加呢?又为什么这么加,下面就是VAE的网络结构和原理:

可以看到code的部分变成了ci的样子,其中m是原来的code,σ是噪音分布的方差,是自己学出来的,取exp是为了保证学习的时候是一个正值,e是一个正态分布,从而得到新的code。

然而仅仅在code上加noise是不够的,在训练时,我们希望recontruction error越小越好,那么在训练时,会偏向于将σ学成0,因为当σ=0时,损失就越小。

因此在训练时要加上一项:

exp(σ)为蓝色的线,(1+σ)为红色的线,二者相减则为绿色的线,最小化这一项,则使得σi在0附近,再取exp,那么varaice则趋向于1,最后一项m的平方则可以看做是L2正则化。

因此VAE在训练时是重构误差加上上边那一项

上边是VAE的做法和直观的理解,对于VAE的原理和推导稍微有点复杂,这里简单总结一下:

首先在高斯混合模型中,样本x的分布可以用有限个高斯分布组合而成的:

假设样本由M个混合高斯模型所组成的,x从其中一个高斯分布m而来,那么产生x的概率p(x) =p(m)*p(x|m),总体的分布则为:

那么现在我们在训练时限制住x降维后所产生的code服从一定的标准正态分布z~N(0,I),那么:

这里z就是降维后的code,其每一维代表一个属性,不同的是这里的高斯混合模型相当于有无限个高斯模型,因此是积分的形式。

随机出来一个z,得到z的均值和方差,就可以得到一个x,我们希望有这样一个function,输入z,输出为z的均值和方差:

这也就是decoder,同样,需要借助一个分布q(z|x),其含义是给一个x,其在z这个空间中的分布,也就是给一个x,在z空间中的均值和方差,从而sample出一个z。其做的事刚好是跟上面的相反的。

这就是encoder。

根据所给定的样本x,根据极大似然估计:

我们需要最大化上面的式子L,接下来就是一系列的推导:

然后就变成了最大化Lower bound,进一步:

至此训练变成了最大化这两项,其中前一项可以表示为P(z)和q(z|x)的散度的负数,最大化这一项即是最小化二者的KL散度

这一项的含义就是保证z(也就是降维的code)的分布要尽量与x在z空间中的分布保持一致这也就限制了在降维后要保持一定的分布,那么在sample时我们可以从该分布中来生成一些样本。这也是VAE的精神所在

而后一项就是使得z生成的x要与原来的x越接近越好,这与原来的autoencoder是一致的

2.3 VAE的问题

??VAE虽然能够比较容易地产生一些数据,但其实VAE并没有学会如何真正的去生成一些新的事物,而是一直在模仿,希望尽可能地接近已知样本。比如:

??对于生成的两张图片“7”,第一张显然对于我们来说是可以接受的,而第二种是不可接受的,然而对于VAE来说,二者具有相同的损失(重构误差)。同时通过实验可以看出,VAE生成的图片一般比较“糊”,这是因为autoencoder在生成图片时,每一个pixel是独立的,它并没有考虑相互之间的关系(大局观)。

??这时就需要另一个生成模型登场了——GAN。

2.4 VAE的实现

在介绍GAN之前,先来做个VAE的demo,前面简单对autoencoder进行了简单的实现,这里顺便就做一下VAE,代码没有封装,只是VAE的实现过程,便于理解本部分内容,以手写数字识别为例。

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data


# 读取数据
mnist = input_data.read_data_sets('/MNIST_data', one_hot=True)

# 输入占位符,因为是无监督学习,所以只需要x
x = tf.placeholder(tf.float32, [None, 784])


# 定义变量从输入层到隐藏层的w,b,隐藏层假设有100个节点
encode_w = tf.Variable(tf.truncated_normal([784, 100], stddev=0.01))
encode_b = tf.Variable(tf.zeros([100]))

# 定义隐藏层到输出m那一层的权重w,假设降维到128维
encode_mean_w = tf.Variable(tf.truncated_normal([100, 128], stddev=0.01))
# 定义隐藏层到输出的variance的权重
encode_var_w = tf.Variable(tf.truncated_normal([100, 128], stddev=0.01))

# code到输出层的w和b
decode_w = tf.Variable(tf.truncated_normal([128, 784], stddev=0.01))
decode_b = tf.Variable(tf.zeros([784]))

# 隐藏层输出
encode_output = tf.nn.relu(tf.matmul(x, encode_w) + encode_b)
# 求解mean和variance
encode_mean = tf.matmul(encode_output, encode_mean_w)
encode_var = tf.matmul(encode_output, encode_var_w)
# 加上一个random normal
E = tf.random_normal([1, 128])
# 降维后的数据 m + exp(var) * E
code = tf.add(tf.exp(encode_var)*E, encode_mean)

# decoder,把code解回原数据784维
decode_output = tf.nn.relu(tf.matmul(code, decode_w) + decode_b)


# loss,原来的重构误差
decode_loss = tf.reduce_mean((decode_output - x) ** 2)
# 加上另一项误差
encode_loss = tf.reduce_mean(tf.exp(encode_var) - (1 + encode_var) + encode_mean ** 2)

loss = tf.add(decode_loss, encode_loss)

optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)


# tf.reset_default_graph()

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for epoch in range(10000):
        # total_num = int(mnist.train.num_examples/100)
        # for i in range(total_num):
        xs, ys = mnist.train.next_batch(100)
        _, loss_ = sess.run([optimizer, loss], feed_dict={x: xs})


        if epoch % 100 == 0:
            print('epoch:', epoch, 'loss:', loss_)

            # test
            I_test = tf.truncated_normal(shape=[1, 128], stddev=0.00001)

            decode_output_test = tf.nn.relu(tf.matmul(I_test, decode_w) + decode_b)

            decode_output_test_data = sess.run([decode_output_test])

            test_img = np.reshape(decode_output_test_data, [28, 28])

            plt.imshow(test_img, cmap='gray')

            plt.pause(0.1)折叠 


可以看到随着训练的次数增加,生成的图片也越来越“清晰”,隐约可以看到“9”的形状,一方面是因为没有对模型中的参数进行调节,加上模型较为简单,特征提取不完整。另一方面也是前面说的VAE本身的问题。

接下来结合CNN,经过卷积之后再对图片进行降维,利用VAE进行降维和还原:


# 这里首先定义一个max_pool函数,返回的经过max_pool之后的图片和所对应的索引
def max_pool_with_argmax(net, stride):
    _, mask = tf.nn.max_pool_with_argmax(net, ksize=[1, stride, stride, 1], strides=[1, stride, stride, 1], padding='SAME')
    mask = tf.stop_gradient(mask)
    net = tf.nn.max_pool(net, ksize=[1, stride, stride, 1], strides=[1, stride, stride, 1], padding='SAME')
    return net, mask

# 根据max_pool的索引进行反池化的操作,原理在前面CNN部分已经说过
def unpool(net, mask, stride):
    ksize = [1, stride, stride, 1]
    input_shape = net.get_shape().as_list()

    output_shape = (input_shape[0], input_shape[1] * ksize[1], input_shape[2] * ksize[2], input_shape[3])

    one_like_mask = tf.ones_like(mask)
    batch_range = tf.reshape(tf.range(output_shape[0], dtype=tf.int64), shape=[input_shape[0], 1, 1, 1])
    b = one_like_mask * batch_range
    y = mask // (output_shape[2] * output_shape[3])
    x = mask % (output_shape[2] * output_shape[3]) // output_shape[3]
    feature_range = tf.range(output_shape[3], dtype=tf.int64)
    f = one_like_mask * feature_range

    updates_size = tf.size(net)
    indices = tf.transpose(tf.reshape(tf.stack([b, y, x, f]), [4, updates_size]))
    values = tf.reshape(net, [updates_size])
    ret = tf.scatter_nd(indices, values, output_shape)
    return ret


x = tf.placeholder(tf.float32, [100, 28, 28, 1])

w_conv1 = tf.Variable(tf.truncated_normal([3, 3, 1, 64], stddev=0.01))
b_conv1 = tf.constant(0.1, shape=[64])

conv1 = tf.nn.relu(tf.nn.conv2d(x, w_conv1, strides=[1, 1, 1, 1], padding='SAME') + b_conv1)
pool1, mask1 = max_pool_with_argmax(conv1, 2)


w_conv2 = tf.Variable(tf.truncated_normal([3, 3, 64, 10], stddev=0.01))
b_conv2 = tf.constant(0.1, shape=[10])

conv2 = tf.nn.relu(tf.nn.conv2d(pool1, w_conv2, strides=[1, 1, 1, 1], padding='SAME') + b_conv2)
pool2, mask2 = max_pool_with_argmax(conv2, 2)
# pool2 = tf.nn.max_pool2d(conv2, ksize=[1, 3, 3, 1], strides=[1, 3, 3, 1], padding='SAME')

conv_out = tf.reshape(pool2, [-1, 490])

encode_w = tf.Variable(tf.truncated_normal([490, 100]))
encode_b = tf.Variable(tf.constant(0.1, shape=[100]))

encode_output = tf.add(tf.matmul(conv_out, encode_w), encode_b)

encode_mean_w = tf.Variable(tf.truncated_normal([100, 128], stddev=0.01))
encode_var_w = tf.Variable(tf.truncated_normal([100, 128], stddev=0.01))

encode_mean = tf.matmul(encode_output, encode_mean_w)
encode_var = tf.matmul(encode_output, encode_var_w)

E = tf.random_normal([1, 128])

code = tf.add(tf.exp(encode_var) * E, encode_mean)

# decoder

decode_w = tf.Variable(tf.truncated_normal([128, 490], stddev=0.01))
decode_b = tf.Variable(tf.constant(0.1, shape=[490]))

decode_output = tf.nn.relu(tf.add(tf.matmul(code, decode_w), decode_b))

decode_output = tf.reshape(decode_output, [-1, 7, 7, 10])

t_conv2 = unpool(decode_output, mask2, 2)
t_pool1 = tf.nn.conv2d_transpose(t_conv2 - b_conv2, w_conv2, pool1.shape, [1, 1, 1, 1])

t_conv1 = unpool(t_pool1, mask1, 2)
pre_output = tf.nn.conv2d_transpose(t_conv1-b_conv1, w_conv1, x.shape, [1, 1, 1, 1])


decode_loss = tf.reduce_mean((pre_output - x) ** 2)

encode_loss = tf.reduce_mean(tf.exp(encode_var) - (1 + encode_var) + encode_mean ** 2)

loss = tf.add(decode_loss, encode_loss)

optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(loss)


with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for epoch in range(10000):
        # total_num = int(mnist.train.num_examples/100)
        # for i in range(total_num):
        xs, ys = mnist.train.next_batch(100)
        xs = np.reshape(xs, [-1, 28, 28, 1])
        _, loss_ = sess.run([optimizer, loss], feed_dict={x: xs})


        if epoch % 100 == 0:
            print('epoch:', epoch, 'loss:', loss_)

            # test
            # 这里就有一个问题,在随机给定一个code时,在进行反池化操作时mask的选择不能用原来的训练的mask了。
            I_test = tf.truncated_normal(shape=[1, 128], stddev=0.01)

            decode_output_test = tf.nn.relu(tf.add(tf.matmul(I_test, decode_w), decode_b))

            decode_output_test = tf.reshape(decode_output_test, [-1, 7, 7, 10])

            mask2_test = tf.reshape(mask2[0], [-1, 7, 7, 10])

            t_conv2_test = unpool(decode_output_test, mask2_test, 2)
            t_pool1_test = tf.nn.conv2d_transpose(t_conv2_test - b_conv2, w_conv2, [1, 14, 14, 64], [1, 1, 1, 1])

            mask1_test = tf.reshape(mask1[0], [-1, 14, 14, 64])

            t_conv1_test = unpool(t_pool1_test, mask1_test, 2)

            pre_output_test = tf.nn.conv2d_transpose(t_conv1_test - b_conv1, w_conv1, [1, 28, 28, 1], [1, 1, 1, 1])

            decode_output_test_data = sess.run([pre_output_test], feed_dict={x: xs})

            test_img = np.reshape(decode_output_test_data, [28, 28])

            plt.imshow(test_img, cmap='gray')

            plt.pause(0.1)折叠 


可以看出上面生成的一些图片相比于之前的VAE更加清晰了,也比较像数字了,但其中有个问题就是代码中注释的那样,有的一般只做卷积,不做池化,关于池化后再test时如何生成,下去再思考。

3. GAN简介

??Genrative Advesarial Networl(GAN)是机器学习中一个耳熟能详的算法,随着时间的发展,GAN也从原始的算法进化了更多的版本,这里就先对GAN进行简要的介绍,后面会单独开一节来介绍GAN及其变种算法。

??GAN全名叫做生成对抗网络,顾名思义,就是在不断地生成和对抗中进行成长学习。举一个例子:

??图中是枯叶蝶,其天敌是一种鸟类,在最开始时,枯叶蝶可能就是普通的蝴蝶,而这种鸟靠捕食蝴蝶为食,这种鸟认为蝴蝶不是棕色的,因此,蝴蝶进化成棕色的骗过第一代的鸟,而鸟类也在进化,进化成第二代,可以辨别蝴蝶是没有叶脉的,因此蝴蝶进一步进化成枯叶蝶。这其实就是一种对抗生成。

??那么在实际的机器学中,对抗生成网络有两个部分组成,一个是Generator,另一个是Discriminator,二者在不断进行生成与对抗,称为亦师亦友的关系。在图片生成中:

第一代的Generator所及生成一些图片,给到第一代的Discriminator识别,其认为都是假的图片,

然后Generator进化到第二代,此时第二代所产生的的图片能够骗过第一代的Discriminator,单后Discriminator进化到第二代,发现第二代Generator产生的也是假的,

如此反复不断进化和迭代,直到Discriminator无法分辨Generator所产生的图片是假的。

上面是GAN的基本概念,对于GAN的原理可以解释如下:

对于实际的样本图片的数据分布我们用Pdata(x)来表示,假设它的分布是下面这样的:

在蓝色的区域是实际的图片,区域以外则产生的图片不像是真的图片,那么我们想要通过训练得到Pdata(x)的分布,假设Generator所产生的分布为PG(x):

我们训练时希望G所产生的图片的分布于原来的图片的数据分布越接近越好。然而在实际中,我们其实并不知道PG(x)长什么样,因此这样也变得困难。

但是在GAN中,Discriminator则可以为解决这一问题提供方法,具体做法如下:

首先Generator产生一下图片数据,同时从样本中sample出一些数据

然后把这些数据Generator所产生的图片标记为0,从原数据中产生的图片标记为1,然后训练Discriminator:

那么Discriminator最终训练完成的loss则与PG(x)和Pdata(x)的JS divergence有关,也就说loss可以用来衡量两个分布有多相似

然后Generator则可以根据这个loss进行进化成为第二代Generator。那么参数是如何更新的呢?下面举个例子:

训练完成Discriminator后,随机sample一个数据,丢进Generator中,然后产生一张图片,图片经过Discriminator,假设此时Discriminator给的分数是0.13,那么此时Generator开始调整参数(注意此时Discriminator的参数是固定不变的),使得所产生的图片丢给Discriminator让其输出值为1,然后完成进化成为Generator V2

以上就是GAN的基本思想以及其算法的较为通俗的解释。下面给出文献中GAN的算法:

"""

Generator:G,Discriminator: D

  • Initialize G θg, D θd(初始化Generator和Discriminator的参数)for each training sample m examples{x1, x2, .....,xm} from datanse;(从样本集中sample出一些数据)sample m noise samples {z1, z2, .....,zm} from a distribution;(从一种分布中sample出一些数据)Obtaining generated data {x'1,x'2,......x'm},x'i=G(zi);(然后把z丢进G中产生一些图片)Fix G, update θd to maximize , (固定住Generator的参数,调整Discriminator的参数,这里并不一定使用这种方法更新,还有其他方法也就衍生了其他算法)
  • sample m noise samples{z1,z2,......,zm} from a distribution;(再另外从某种分布中sample出一些数据)Fix D, update to maximize;(固定住Discriminator,调整Generator的参数,使得sample出来的那些数据让Discriminator的分数越高越好)

"""

以上就是GAN的基本概念和算法,这里就暂时对这部分内容介绍到这里,后边会单独开一节GAN有关其他的衍生算法及其实现。

文章来自https://www.cnblogs.com/501731wyb/p/16455870.html

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码