对图像进行卷积的过程可以看作是特征提取的过程。在图像迁移学习中可以将一组目标问题的图片通过训练好的卷积层得到瓶颈层,这些瓶颈层向量就是多个高维向量。如果在目标问题图像数据集上同一种类的图片在经过卷积层之后得到的瓶颈层向量在空间上比较接近,那么这样迁移学习得到的结果就可能会更好。
TensorBoard提供了PROJECTOR界面来可视化高维向量之间的关系。PROJECTOR要求用户准备一个sprite图像(所谓sprite图像就是将一组图片组合成一整张大图片)和一个tsv文件给处每张图片对应的标签信息。
create_sprite_image.py
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import os
from tensorflow.examples.tutorials.mnist import input_data
# PROJECTOR需要的日志文件名和地址相关参数
LOG_DIR='./log'
SPRITE_FILE='mnist_sprite.jpg'
META_FILE='mnist_meta.tsv'
# 使用给出的mnist图片列表生成sprite图像
# sprite图像可以理解成是所有小图片拼成的大正方形矩阵,大正方形矩阵中的每一个元素就是原来的小图片
# 如果小图片的数量是n则正方形的边长就是sqrt(n)
def create_sprite_image(images):
if isinstance(images, list):
images = np.array(images)
img_h = images.shape[1]
img_w = images.shape[2]
m = int(np.ceil(np.sqrt(images.shape[0]))) # sprite大图正方形边长,每个位置是个小图
# 使用全1来初始化最终的大图
sprite_image = np.ones((img_h*m, img_w*m))
for i in range(m):
for j in range(m):
cur = i * m + j # 当前图片的编号
if cur < images.shape[0]:
# 将当前小图片的内容复制到最终的sprite图像上
sprite_image[i*img_h:(i+1)*img_h, j*img_w:(j+1)*img_w] = images[cur]
return sprite_image
# 加载mnist数据,这里指定one_hot=False得到的labels就是一个数字,表示当前图像表示的数字
mnist = input_data.read_data_sets('/home/lg/Desktop/learn/MNIST_data',one_hot=False)
# 生成sprite图像
tobe_visualise = 1 - np.reshape(mnist.test.images,(-1,28,28)) # -1表示将颜色反转,仅为可视化,也可不反转
spirte_img = create_sprite_image(tobe_visualise)
# 将生成的sprite图像放到相应的日志目录下
path_sprite = os.path.join(LOG_DIR,SPRITE_FILE)
plt.imsave(path_sprite, spirte_img, cmap='gray')
plt.imshow(spirte_img, cmap='gray')
# 生成每张图片对应的标签文件并写到相应的日志目录下
path_metadata = os.path.join(LOG_DIR,META_FILE)
with open(path_metadata, 'w') as f:
f.write('Index\tLabel\n')
for index,label in enumerate(mnist.test.labels):
f.write('%d\t%d\n'%(index,label))
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.contrib.tensorboard.plugins import projector
import os
LOG_DIR='./log'
SPRITE_FILE='mnist_sprite.jpg'
META_FILE='mnist_meta.tsv'
TENSOR_NAME='FINAL_LOGITS'
INPUT_NODE = 28*28
NODE1 = 500
NODE2= 10
BATCH_SIZE = 100
BASE_LEARNING_RATE = 0.8
DECAY_RATE = 0.999
TRAIN_STEPS = 30000
# 生成变量监控信息并定义生成监控信息日志,其中var是需要记录的变量,name给出了在可视化结果中显示的图表名称,一般与变量名一致
def summary_variable(var, name):
# 将生成监控信息的操作放到同一个命名空间下
with tf.name_scope('summaries'):
# 通过tf.summary.histogram()函数会记录张量中元素的取值分布,对于给出的图表名称和张量,tf.summary.histogram函数会
# 生成一个Summary protocol buffer. 将Summary写入tensorboard日志文件后,在HISTOGRAMS栏和DISTRIBUTION栏下都会
# 出现对应名称的图表. 和其它操作类似,tf.summary.histogram函数不会立刻执行只有当sess.run函数明确调用该操作后tensorflow
# 才会真正生成并输出Summary protocol buffer
tf.summary.histogram(name,var)
# 计算变量的平均值,并定义生成平均值信息日志的操作
mean = tf.reduce_mean(var)
tf.summary.scalar('mean/' + name, mean)
# 计算变量的标准差,并定义生成平均值信息日志的操作
stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
tf.summary.scalar('stddev/' + name, stddev)
def get_weights(shape):
weight = tf.get_variable('weight',shape,tf.float32,initializer=tf.truncated_normal_initializer(mean=0.0, stddev=0.5))
tmp = tf.contrib.layers.l2_regularizer(0.001)(weight)
tf.add_to_collection(tf.GraphKeys.LOSSES,tmp)
return weight
def inference(input_tensor):
with tf.variable_scope('layer1'):
weight = get_weights([INPUT_NODE,NODE1])
bias = tf.get_variable('bias',[NODE1])
layer1 = tf.nn.relu(tf.matmul(input_tensor, weight) + bias)
summary_variable(weight, weight.name)
summary_variable(bias, bias.name)
tf.summary.histogram(layer1.name, layer1)
with tf.variable_scope('layer2'):
weight = get_weights([NODE1,NODE2])
bias = tf.get_variable('bias',[NODE2])
layer2 = tf.matmul(layer1,weight) + bias
summary_variable(weight, weight.name)
summary_variable(bias, bias.name)
tf.summary.histogram(layer2.name, layer2)
return layer2
# 在生成projctor时相比于正常的训练过程多了返回测试数据经过整个神经网络得到的输出矩阵,
# 因为有多张测试图片,每张图片对应了一个输出层向量,因此返回的结果是这些向量组成的矩阵
def train(mnist):
x = tf.placeholder(tf.float32,[None, INPUT_NODE],name='x-input')
y_gt = tf.placeholder(tf.float32,[None,NODE2], name='y-input')
# 将输入向量还原成图片的像素矩阵,并通过tf.summary.image()函数将当前图片信息写入日志
with tf.name_scope('input_reshape'):
image_x = tf.reshape(x,[-1,28,28,1])
tf.summary.image('image_x', image_x, 10)
y_pred = inference(x)
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(labels=tf.argmax(y_gt,1),logits=y_pred))
regular_loss = tf.add_n(tf.get_collection(tf.GraphKeys.LOSSES))
loss = loss + regular_loss
# 和TensorFlow计算图可视化结果不同的是,SCALARS,IMAGES,AUDIO,TEXT,HISTOGRAMS,DISTRIBUTIONS栏只会对最高层的命名空间进行整合
tf.summary.scalar('namespace/loss', loss)
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(y_pred,1), tf.argmax(y_gt,1)),tf.float32))
tf.summary.scalar('namespace/accu', accuracy)
merged_summaries = tf.summary.merge_all()
global_step = tf.Variable(0,trainable=False)
learning_rate = tf.train.exponential_decay(BASE_LEARNING_RATE,global_step,mnist.train.num_examples/BATCH_SIZE,DECAY_RATE)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step)
saver = tf.train.Saver()
with tf.Session() as sess:
summary_writer = tf.summary.FileWriter('./log', sess.graph)
sess.run(tf.global_variables_initializer())
# 配置运行时需要记录的信息
run_options = tf.RunOptions(trace_level=tf.RunOptions.SOFTWARE_TRACE)
# 运行时记录运行信息的proto
run_metadata = tf.RunMetadata()
for i in range(TRAIN_STEPS):
xs,ys = mnist.train.next_batch(BATCH_SIZE)
if i % 1000 == 0:
# 将配置信息和记录运行信息的proto传入运行的过程,从而记录运行时每一个节点的实时间、内存开销
step,_,acc,loss_, summary_ = sess.run([global_step, train_step, accuracy, loss, merged_summaries], \
feed_dict={x: xs, y_gt:ys},options=run_options, run_metadata=run_metadata)
summary_writer.add_summary(summary_,step)
# 将节点在运行时的信息写入日志文件
summary_writer.add_run_metadata(run_metadata,'step%03d'%step)
saver.save(sess,'./model/model')
print('step: %d, loss: %f, accuarcy: %f' % (step,loss_, acc))
else:
sess.run([train_step], feed_dict={x: xs, y_gt:ys})
summary_writer.close()
# 计算MNIST测试数据对应的输出层矩阵
final_result = sess.run(y_pred, feed_dict={x:mnist.test.images})
return final_result
# 生成可视化最终输出层向量所需要的日志文件
def visualization(final_result):
# 因为embedding是通过TensorFlow中变量完成的,所以PROJECTOR可视化的都是TensorFlow中的变量,因此需要定义一个变量来保存输出层向量的取值
y = tf.Variable(final_result,name=TENSOR_NAME)
summary_writer = tf.summary.FileWriter(LOG_DIR)
# 通过projector.ProjectorConfig类来帮助生成日志文件
config = projector.ProjectorConfig()
# 增加一个需要可视化的embedding结果
embedding = config.embeddings.add()
# 指定这个embedding结果对应的TensorFlow变量名称
embedding.tensor_name=y.name
# 指定embedding结果对应的原始数据信息,比如这里指定的就是每一张MNIST测试图片对应的真实类别,
# 该文件是可选的,如果没有指定那么向量就没有标签
embedding.metadata_path=META_FILE
# 指定sprite图像,也是可选的,如果没有提供sprite图像,那么可视化的结果每一个点就是一个小圆点,而不是具体图片
embedding.sprite.image_path=SPRITE_FILE
# 在提供sprite图像时通过single_image_dim可以指定单张图片的大小,这将用于从sprite图像中截取正确的原始图片
embedding.sprite.single_image_dim.extend([28,28])
# 将PROJECTOR所需要的内容写入日志文件
projector.visualize_embeddings(summary_writer,config)
# 生成会话,初始化新声明的变量并将需要的日志写入文件
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.save(sess, os.path.join(LOG_DIR,'model'), TRAIN_STEPS)
summary_writer.close()
def main():
mnist = input_data.read_data_sets('./MNIST_data',one_hot=True)
final_result = train(mnist)
visualization(final_result)
if __name__ == '__main__':
main()
图1 TRAIN_STEPS = 30000时分类效果
图2 TRAIN_STEPS改成100时分类效果
Reference
郑泽宇等.TensorFLow实战Google深度学习框架(第2版),电子工业出版社,2018.