关于t-SNE降维方法

2019-12-14  本文已影响0人  Jabes

关于t-SNE降维方法

Differential Dynamics of the Maternal Immune System in Healthy Pregnancy and Preeclampsia 这篇论文中用到了t-SNE的降维方法进行可视化。

论文原图是这样的:

image.png

1. 什么是t-SNE:

全名是t-distributed Stochastic Neighbor Embedding(t-SNE),翻译过来应该可以叫学生t分布的随机邻点嵌入法。

t-SNE将数据点之间的相似度转换为概率。原始空间中的相似度由高斯联合概率表示,嵌入空间的相似度由“学生t分布”表示。t-SNE在一些降维方法中表现得比较好。因为t-SNE主要是关注数据的局部结构

通过原始空间和嵌入空间的联合概率的Kullback-Leibler(KL)散度来评估可视化效果的好坏,也就是说用有关KL散度的函数作为loss函数,然后通过梯度下降最小化loss函数,最终获得收敛结果

正式点来描述就是:

给定一组 N 个点 x_1, \cdots, x_N \in \mathbb{R}^d, t-SNE 首先计算x_ix_j之间的相似度p_{ij}。这个相似度公式定义为:

p_{ij} = (p_{i \mid j} + p_{j \mid i})/(2N)

对于每个i都有p_{j \mid i} \propto \exp(\|x_i-x_j\|^2/\sigma_i^2) ,这里就是用的高斯核了,只涉及到一个参数\sigma_i.

for some parameter \sigma_i. Intuitively, the value p_{ij} measures the `similarity' between points x_i and x_j. t-SNE then aims to learn the lower dimensional points y_1, \cdots, y_N \in \mathbb{R}^2 such that if q_{ij} \propto (1+\|y_i-y_j \|_2^2)^{-1}, then q_{ij} minimizes the Kullback–Leibler divergence of the distribution \{q_{ij}\} from the distribution \{p_{ij}\}. For a more detailed explanation of the t-SNE algorithm, see \cite{orig_tsne}.

直观地讲,该值p_{ij}衡量点与点之间的相似性。 然后t-SNE学习较低的维度,以便在低维空间中将分布的KL散度最小化。

这里简单列一下t-SNE的算法:

image.png

关于t-SNE的更多内容参考另外一篇论文Visualizing Data using t-SNE .

2. t-SNE的问题

3. t-SNE的参数

这里列一下在TensorFlow中t-SNE相关的参数,其实参数很多,但是TensorFlow做了很多自动化处理,所以只考虑下面这几个:

4. 实验工具:TensorBoard

TensorFlow是一个开源软件库,在机器学习,深度学习以及强化学习这些领域应用最广泛的最基础的工具,他好处之一是自动计算微分,然后很容易搭建神经网络并进行训练。

TensorBoard 是用于可视化 TensorFlow 模型的训练过程的工具(the flow of tensors,在你安装 TensorFlow 的时候就已经安装了 TensorBoard。

他的组成比较复杂,功能比较多,参考这个结构:

image.png

我们主要只是使用EMBEDDINGS功能。

5. TensorFlow安装

6. 实验设定

这次实验我们只做做基础的使用MNIST观察下t-SNE的结果,只用了1000张手写数字图片。

MNIST是一个简单的计算机视觉数据集。它包含如下所示的手写数字的图片集:

image.png

MNIST数据保存在了Yann LeCun的网站,

下载的数据包含两部分,6万个训练数据(mnist.train)和10万各测试数据(mnist.test)。这个区分是非常重要的:在机器学习领域,我们分离出一部分数据,这部分数据我们不用来作训练,以此来保证我们学到的是通用的规则。

就像前面提到的那样,每个MNIST数据有都有两个部分:一个手写数字的图片,以及一个相应的标签。我们用"xs"表示图片,用"ys"表示标签。训练数据和测试数据都有xs和ys,以测试数据举例来说,训练图片是mnist.train.images,训练标签是mnist.train.labels。

每个图片都是28X28像素,我们可以将它理解为一个大数组。

image.png

这个数组可以flatten为一个包含28X28=784个数字的向量,如何flatten这个数组是没有关系的,因为我们在所有图片中都是相同的。从这个角度看,MNIST图片只是一堆784维空间中的一个点

我们将mnist看作是一个[60000,784]的张量(an n-dimensional array) 。第一个维度是图片的索引,第二个维度图片上像素的索引。这个张量的每个实体都是某个图片,某个像素的,像素亮度用0到1之间的数字表示

MNIST对应的标签是0~9的数字,描述了给定图片是哪个数字。基于本教程的目的,我们想要我们的标签作为"one-hot vectors",one-hot vector是一个大多数维度都是0,只有一个维度为1的向量。在我们这个例子里,第n个数字会表示为一个第n位为1的向量,比如0会表示为[1,0,0,0,0,01,0,0,0,0,0,0,0,0,0,0],相应的,mnist.train.labels是一个[60000,10]的float数组。

7. 实验的代码

我们实验的代码如下

这里我们主要使用的网络架构是这样的,输入图片进入第一层卷积神经网络,使用32个[5,5]形状的滤波器,并使用补偿为2的最大池化操作。然后进入第二层卷积神经网络,使用64个[5,5]形状的滤波器,并使用补偿为2的最大池化操作。最后把得到的张量铺平成一个一维度的向量,通过一个512个cell的全连接网络,最后再进入soft输出结果是数字1-10的概率。

使用cross-entropy作为损失函数,并使用优化算法

#导入相关的库
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import os
import time
from tensorflow.contrib.tensorboard.plugins import projector
import matplotlib.pyplot as plt
import numpy as np
#这里用slim这个API来进行卷积网络构建
slim = tf.contrib.slim

#定义卷积神经网络模型
#网络架构是卷积网络--最大池化--卷积网络--最大池化---flatten---MLP-softmax的全连接MLP
def model(inputs, is_training, dropout_rate, num_classes, scope='Net'):
    inputs = tf.reshape(inputs, [-1, 28, 28, 1])
    with tf.variable_scope(scope):
        with slim.arg_scope([slim.conv2d, slim.fully_connected],
                            normalizer_fn=slim.batch_norm):
            net = slim.conv2d(inputs, 32, [5, 5], padding='SAME', scope='conv1')
            net = slim.max_pool2d(net, 2, stride=2, scope='maxpool1')
            tf.summary.histogram("conv1", net)

            net = slim.conv2d(net, 64, [5, 5], padding='SAME', scope='conv2')
            net = slim.max_pool2d(net, 2, stride=2, scope='maxpool2')
            tf.summary.histogram("conv2", net)

            net = slim.flatten(net, scope='flatten')
            fc1 = slim.fully_connected(net, 1024, scope='fc1')
            tf.summary.histogram("fc1", fc1)

            net = slim.dropout(fc1, dropout_rate, is_training=is_training, scope='fc1-dropout')
            net = slim.fully_connected(net, num_classes, scope='fc2')

            return net, fc1


def create_sprite_image(images):
    """更改图片的shape"""
    if isinstance(images, list):
        images = np.array(images)
    img_h = images.shape[1]
    img_w = images.shape[2]
    n_plots = int(np.ceil(np.sqrt(images.shape[0])))

    sprite_image = np.ones((img_h * n_plots, img_w * n_plots))

    for i in range(n_plots):
        for j in range(n_plots):
            this_filter = i * n_plots + j
            if this_filter < images.shape[0]:
                this_img = images[this_filter]
                sprite_image[i * img_h:(i + 1) * img_h,
                j * img_w:(j + 1) * img_w] = this_img

    return sprite_image


def vector_to_matrix_mnist(mnist_digits):
    """把正常的mnist数字图片(batch,28*28)这个格式,转换为新的张量形状(batch,28,28)"""
    return np.reshape(mnist_digits, (-1, 28, 28))


def invert_grayscale(mnist_digits):
    """处理下图片颜色,黑色变白,白色边黑"""
    return 1 - mnist_digits


if __name__ == "__main__":
    # 定义参数
    #学习率
    learning_rate = 1e-4
    #定义迭代参数
    total_epoch = 500
    #定义批量
    batch_size = 200
    #程序运行中打印频率
    display_step = 20
    #程序运行中保存结果的频率
    save_step = 100
    load_checkpoint = False
    checkpoint_dir = "checkpoint"
    checkpoint_name = 'model.ckpt'
    #结果存放的路径
    logs_path = "logs"
    #定义我们使用多少个图片
    test_size = 1000
    #定义第二层路径
    projector_path = 'projector'

    # 网络参数
    n_input = 28 * 28   # 每个图片是28*28个像素,也就是784个特征
    n_classes = 10  # MNIST数据集有0-9是个类别的结果
    dropout_rate = 0.5  # Dropout的比率

    mnist = input_data.read_data_sets('MNIST-data', one_hot=True)

    # 定义计算图
    x = tf.placeholder(tf.float32, [None, n_input], name='InputData')
    y = tf.placeholder(tf.float32, [None, n_classes], name='LabelData')
    is_training = tf.placeholder(tf.bool, name='IsTraining')
    keep_prob = dropout_rate

    logits, fc1 = model(x, is_training, keep_prob, n_classes)

    with tf.name_scope('Loss'):
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))
    tf.summary.scalar("loss", loss)

    with tf.name_scope('Accuracy'):
        correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax(y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
    tf.summary.scalar("accuracy", accuracy)

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

    projector_dir = os.path.join(logs_path, projector_path)
    path_metadata = os.path.join(projector_dir,'metadata.tsv')
    path_sprites = os.path.join(projector_dir, 'mnistdigits.png')
    # 检查结果目录的状态
    if not os.path.exists(projector_dir):
        os.makedirs(projector_dir)

    # 这里进行嵌入
    mnist_test = input_data.read_data_sets('MNIST-data', one_hot=False)
    batch_x_test = mnist_test.test.images[:test_size]
    batch_y_test = mnist_test.test.labels[:test_size]

    embedding_var = tf.Variable(tf.zeros([test_size, 1024]), name='embedding')
    assignment = embedding_var.assign(fc1)

    config = projector.ProjectorConfig()
    embedding = config.embeddings.add()
    embedding.tensor_name = embedding_var.name
    embedding.metadata_path = os.path.join(projector_path,'metadata.tsv')
    embedding.sprite.image_path = os.path.join(projector_path, 'mnistdigits.png')
    embedding.sprite.single_image_dim.extend([28,28])

    # 初始化变量
    init = tf.global_variables_initializer()

    # 'Saver' op to save and restore all the variables
    saver = tf.train.Saver()
    merged_summary_op = tf.summary.merge_all()

    # 运行计算图
    with tf.Session() as sess:
        sess.run(init)
        # Restore model weights from previously saved model
        prev_model = tf.train.get_checkpoint_state(checkpoint_dir)
        if load_checkpoint:
            if prev_model:
                saver.restore(sess, prev_model.model_checkpoint_path)
                print('Checkpoint found, {}'.format(prev_model))
            else:
                print('No checkpoint found')

        summary_writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph())
        projector.visualize_embeddings(summary_writer, config)
        start_time = time.time()
        # 开始训练
        for epoch in range(total_epoch):
            batch_x, batch_y = mnist.train.next_batch(batch_size)
            # reshapeX = np.reshape(batch_x, [-1, 28, 28, 1])
            # 开始反向传播算法
            sess.run(optimizer, feed_dict={x: batch_x, y: batch_y,
                                           is_training: True})
            if epoch % display_step == 0:
                # 计算损失和精度
                cost, acc, summary = sess.run([loss, accuracy, merged_summary_op],
                                              feed_dict={x: batch_x,
                                                         y: batch_y,
                                                         is_training: False})
                elapsed_time = time.time() - start_time
                start_time = time.time()
                print('epoch {}, training accuracy: {:.4f}, loss: {:.5f}, time: {}'
                      .format(epoch, acc, cost, elapsed_time))
                summary_writer.add_summary(summary, epoch)
            if epoch % save_step == 0:
                # 保存训练的结果
                sess.run(assignment, feed_dict={x: mnist.test.images[:test_size],
                                                y: mnist.test.labels[:test_size], is_training: False})
                checkpoint_path = os.path.join(checkpoint_dir, checkpoint_name)
                save_path = saver.save(sess, checkpoint_path)
                print("Model saved in file: {}".format(save_path))

        # 保存结果
        saver.save(sess, os.path.join(logs_path, "model.ckpt"), 1)
        # 创建可视化的图片
        to_visualise = batch_x_test
        to_visualise = vector_to_matrix_mnist(to_visualise)
        to_visualise = invert_grayscale(to_visualise)
        sprite_image = create_sprite_image(to_visualise)
        # 保存可视化的图片
        plt.imsave(path_sprites, sprite_image, cmap='gray')
        # 写文件
        with open(path_metadata, 'w') as f:
            f.write("Index\tLabel\n")
            for index, label in enumerate(batch_y_test):
                f.write("%d\t%d\n" % (index, label))

        print("训练完成")

8. 实验结果

在上面的代码训练完成之后,我们把结果保存在了代码中定义的目录logs中。我们直接进入logs目录的父目录然后在CMD中启动tensorboard即可:

​ tensorboard --logdir logs

启动过程如下所示:

image.png

然后我们通过chrome浏览器访问tensorboard,

http://localhost:6006

我们在PROJECTOR可以看到t-SNE的结果:

image.png

这个记过看起来这1000个图片在三维空间上确实是把不同的图片分开了。Perplexity,learning rate与supervise都随意调的,可以仔细调整看看。然后这里只跑了184次迭代,因为发现这里效果看起来不错,训练的迭代次数往后越多,这些堆分开得越远。效果很好,但是不好看。

我们看看在二维空间的可视化结果:

image.png

同样的参数下也只训练了182个迭代,效果不错的。

此外最后我们再看看PCA降维的效果,感觉和T-SNE差别比较大,没有深入研究,可能TSNE是在动态训练,PCA这里只是静态的。注意PCA在三维上是需要手动选择特征的。

image.png
上一篇下一篇

猜你喜欢

热点阅读