Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tensorflow 1.3 tips - 2 #60

Open
GH1995 opened this issue May 9, 2020 · 39 comments
Open

tensorflow 1.3 tips - 2 #60

GH1995 opened this issue May 9, 2020 · 39 comments

Comments

@GH1995
Copy link
Owner

GH1995 commented May 9, 2020

正则化

weights = tf.constant([[1.0, -2.0], [-3.0, 4.0]])

with tf.Session() as sess:
    # 输出为(|1| + |-2| + |-3| + |4|) * 0.5 = 5,其中0.5为正则化项的权重
    print(sess.run(tf.contrib.layers.l1_regularizer(.5)(weights)))
    # 输出为(1^2 + (-2)^2 + (-3)^2 + 4^2/2) * 0.5 = 7.5,TF会将L2的正则化损失值除以2使得求导更简洁
    print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights)))
@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# b. 损失函数
# 计算交叉熵及其平均值
# 目标类别只有一个正确答案时可使用tf.nn.sparse_softmax_cross_entropy_with_logits来加速交叉熵计算
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, 
    labels=tf.argmax(y_, axis=1))
# 计算当前batch中所有样例的交叉熵平均值
cross_entropy_mean = tf.reduce_mean(cross_entropy)
# 计算L2正则化损失,一般只计算权重部分,不使用偏置项
regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
regularaztion = regularizer(weights1) + regularizer(weights2)
# 总的损失函数
loss = cross_entropy_mean + regularaztion

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

global_step = tf.Variable(0, trainable=False)
# 给定滑动平均衰减率、训练轮数,初始化滑动平均类。给定训练轮数可以加快训练早期变量的更新速度
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
# 在所有代表神经网络参数的变量上使用滑动平均:即Graphkeys.TRAINABLE_VARIABLES中的元素,也即所有没有指定trainable=False的参数
variables_averages_op = variable_averages.apply(tf.trainable_variables())
# 滑动平均不会改变变量本身取值,只是维护一个影子变量来记录其滑动平均值,因此需要使用这个滑动平均值时需要明确调用average函数
average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# c. 反向传播
# 设置指数衰减的学习率。
learning_rate = tf.train.exponential_decay(
    LEARNING_RATE_BASE,
    global_step,
    mnist.train.num_examples / BATCH_SIZE,
    LEARNING_RATE_DECAY,
    staircase=True)

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 优化损失函数(更新神经网络参数)
train_step = tf.train.GradientDescentOptimizer(learning_rate)
    .minimize(loss, global_step=global_step)

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 每一遍训练,反向传播既需要更新参数,也需要更新每一个参数的滑动平均值
# train_op = tf.group(train_step, variables_average_op)等价下面两行
with tf.control_dependencies([train_step, variables_averages_op]):
    train_op = tf.no_op(name='train')

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 另;检验使用了滑动平均模型的神经网络前向传播是否正确
# tf.equal判断两个张量的每一维是否相等,返回True/False
correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))  
# tf.cast转换数据类型,这里将布尔型转换成实数型
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))  

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

def inference(input_tensor, reuse=False):
    # 定义第一层神经网络的变量和前向传播过程
    with tf.variable_scope('layer1', reuse=reuse):
        # 根据传进来的reuse判断创建新变量还是使用已创建好的。第一次构造网络时需要创建新的,以后每次调用都使用reuse=True就不需要每次传递变量进来
        weights = get_weight_variable([INPUT_NODE, LAYER1_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1))
        biases = tf.get_variable("biases", [LAYER1_NODE], initializer=tf.constant_initializer(0.0))
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

    # 定义第二层神经网络的变量和前向传播过程
    with tf.variable_scope('layer2', reuse=reuse):
        weights = get_weight_variable([LAYER1_NODE, OUTPUT_NODE], initializer=tf.truncated_normal_initializer(stddev=0.1))
        biases = tf.get_variable("biases", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
        layer2 = tf.matmul(layer1, weights) + biases

    # 返回最后的前向传播结果
    return layer2

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 声明tf.train.Saver类用于保存模型
saver = tf.train.Saver()

with tf.Session() as sess:
    sess.run(init_op)
    saver.save(sess, "Saved_model/model.ckpt")

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 该类可以读取checkpoint文件中保存的所有变量,注意后面的.data和.index可以省去
reader = tf.train.NewCheckpointReader('Saved_model/model.ckpt')

# 获取所有变量列表,这个是一个从变量名到变量维度的字典
global_varibales = reader.get_variable_to_shape_map()
for variable_name in global_varibales:
    # variable_name为变量名称,global_variables[variable_name]为变量的维度
    print(variable_name, global_varibales[variable_name])
    
# 获取名为v1的变量的取值
print("Value for variable v1 is ", reader.get_tensor("v1"))

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

saver = tf.train.Saver()

with tf.Session() as sess:
    # 加载已经保存的模型,并通过已保存的模型中变量来计算加法
    saver.restore(sess, "Saved_model/model.ckpt")
    print(sess.run(result))

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

saver = tf.train.import_meta_graph("Saved_model/model.ckpt.meta")

with tf.Session() as sess:
    saver.restore(sess, "Saved_model/model.ckpt")
    # 通过张量名称来获取张量
    print(sess.run(tf.get_default_graph().get_tensor_by_name("add:0")))

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

保存或加载部分变量

saver = tf.train.Saver([v1])

with tf.Session() as sess:
    # 加载已经保存的模型,并通过已保存的模型中变量来计算加法
    saver.restore(sess, "Saved_model/model.ckpt")
    print(sess.run(result))

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

变量重命名

# 注意定义时的name不一样!!!
v1 = tf.Variable(tf.constant(1.0, shape=[1]), name="other-v1")
v2 = tf.Variable(tf.constant(1.0, shape=[1]), name="other-v2")


# 将原来名称为v1的变量加载到现在变量v1中(名称为other-v1)
# 将原来名称为v2的变量加载到现在变量v1中(名称为other-v2)
saver = tf.train.Saver({"v1": v1, "v2": v2})

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

滑动平均类的保存和加载

# 通过变量重命名将原来变量v的滑动平均值直接赋值给v。
saver = tf.train.Saver({"v/ExponentialMovingAverage": v})
v = tf.Variable(0, dtype=tf.float32, name="v")

ema = tf.train.ExponentialMovingAverage(0.99)

# 通过variables_to_restore函数可以直接生成上面代码中提供的字典:{"v/ExponentialMovingAverage": v}
print(ema.variables_to_restore())     # 输出:{'v/ExponentialMovingAverage': <tf.Variable 'v:0' shape=() dtype=float32_ref>}

saver = tf.train.Saver(ema.variables_to_restore())

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

常量方式保存(pb格式)

# 导出当前计算图的GraphDef部分,只需要这一部分就可以完成从输入层到输出层的计算过程graph_def = tf.get_default_graph().as_graph_def()
# 将圈中的变量及其取值转化为常量,同时将图中不必要的节点去掉。在5.4.2 节中将会看
# 到一些系统运算也会被转化为计算图中的节点(比如变量初始化操作)。如果只关心程序中定
# 义的某些计算时,和这些计算无关的节点就没有必要导出并保存了。在下面一行代码中,最
# 后一个参数['add']给出了需要保存的节点名称。add 节点是上面定义的两个变量相加的
# 操作。注意这里给出的是计算节点的名称,所以没有后面的:0 。
output_graph_def = graph_util.convert_variables_to_constants(sess, graph_def, ['add'])

with tf.gfile.GFile("Saved_model/combined_model.pb", "wb") as f:
       f.write(output_graph_def.SerializeToString())

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 读取保存的模型文件,并将文件解析成对应的GraphDef Protocol Buffer
with gfile.FastGFile(model_filename, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())

# 将graph_def中保存的图加载到当前的图中。return_elements=["add:0"]给出了返回的张量
# 的名称。在保存的时候给出的是计算节点的名称,所以为"add",在加载的时候给出
# 的是张量的名称,所以为"add:0"。
result = tf.import_graph_def(graph_def, return_elements=["add:0"])
print(sess.run(result))

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

tf.Coordinator主要用于协同多个线程一起停止,并提供了should_stoprequest_ stopjoin三个函数。在启动线程之前,需要先声明一个tf.Coordinator类,并将这个类传入每一个创建的线程中。 启动的线程需要一直查询tf.Coordinator类中提供的should_stop函数,当这个函数的返回值为True时,则当前线程也需要退出。每一个启动的线程都可以通过调用request_stop函数来通知其他线程退出。 当某一个线程调用request_stop函数之后,should_stop函数的返回值将被设置为True这样其他的线程就可以同时终止了。

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

tf.QueueRunner主要用于启动多个线程来操作同一个队列,启动的这些线程可以通过上面介绍的tf.Coordinator类来统一管理。

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 先声明一个先进先出的队列,队列中最多100个元素,类型为实数型
queue = tf.FIFOQueue(100, "float")
# 定义队列的入队操作
enqueue_op = queue.enqueue([tf.random_normal([1])])

# 使用tf.train.QueueRunner来创建多个线程运行队列的入队操作
#   第一个参数给出了被操作的队列;
#   第二个参数表示需要启动5个线程,每个线程中运行的是enqueue_op操作
qr = tf.train.QueueRunner(queue, [enqueue_op] * 5)

# 将定义过的QueueRunner加入TensorFlow计算图上指定的集合,默认tf.GraphKeys,QUEUE_RUNNERS
tf.train.add_queue_runner(qr)
# 定义出队操作
out_tensor = queue.dequeue()

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

with tf.Session() as sess:
    # 使用tf.train.Coordinator来协同启动的线程
    coord = tf.train.Coordinator()
    
    # 使用tf.train.QueueRunner时,需要明确调用tf.train.start_queue_runners来启动所有线程,
    # 否则因为没有线程运行入队操作,当调用出队操作时,程序会一直等待入队操作被运行。
    # tf.train.start_queue_runners函数会默认启动tf.GraphKeys.QUEUE_RUNNERS集合中所有的QueueRunner,
    # 因为这个函数只支持启动指定集合中的QueueRnner,所以一般来说
    # tf.train.add_queue_runner函数和tf.train.start_queue_runners函数会指定同一个集合。
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    # 获取队列中的值
    for _ in range(3):
        print(sess.run(out_tensor)[0])
    
    # 使用tf.train.Coordinator来停止所有线程
    coord.request_stop()
    coord.join(threads)
    
# 以上程序将启动5个线程来执行队列入队的操作,其中每一个线程都是将随机数写入队列。
# 于是在每次运行出队操作时,可以得到一个随机数

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

利用数据集读取数据有三个基本步骤:

  1. 定义数据集的构造方法,这个例子使用tf.data.Dataset.from_tensor_slices(),表明数据集是从一个张量中构建的,如果数据集是文件中构建的,则需要相应调整不同的构造方法。
  2. 定义遍历器,这个例子使用了简单的one_shot_iterator来遍历数据集,稍后将介绍更加灵活的initializable_iterator
  3. 使用get_next方法从遍历器中读取数据张量,作为计算图其他部分的输入。

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

读取文本文件里的数据

# 从文本文件创建数据集。假定每行文字是一个训练例子。这里可以提供多个文件。
input_files = ["./test1.txt", "./test2.txt"]
dataset = tf.data.TextLineDataset(input_files)

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 定义迭代器。
iterator = dataset.make_one_shot_iterator()

# 这里get_next()返回一个字符串类型的张量,代表文件中的一行。
x = iterator.get_next()  
with tf.Session() as sess:
    for i in range(4):
        print(sess.run(x))

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

解析TFRecord文件里的数据

在图像相关任务中,输入数据通常以TFRecord形式存储,这时可以用TFRecordDataset来读取数据。与文本文件不同,每一个TFRecord都有自己不同的feature格式,因此在读取TFRecord时,需要提供一个parser函数来解析所读取的TFRecord的数据格式。

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

使用initializable_iterator来动态初始化数据集

# 从TFRecord文件创建数据集,具体文件路径是一个placeholder,稍后再提供具体路径。
input_files = tf.placeholder(tf.string)
dataset = tf.data.TFRecordDataset(input_files)
dataset = dataset.map(parser)

# 定义遍历dataset的initializable_iterator。
iterator = dataset.make_initializable_iterator()
image, label = iterator.get_next()

with tf.Session() as sess:
    # 首先初始化iterator,并给出input_files的值。
    sess.run(iterator.initializer,
             feed_dict={input_files: ["output.tfrecords"]})
    # 遍历所有数据一个epoch。当遍历结束时,程序会抛出OutOfRangeError。
    while True:
        try:
            x, y = sess.run([image, label])
        except tf.errors.OutOfRangeError:
            break 

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

在上面的例子中,文件路径使用placeholder和feed_dict的方式传给数据集。使用这种方法,在实际项目中就不需要总是将参数写入计算图的定义,而可以使用程序参数的方式动态指定参数。

另外注意到,上面例子中的循环体不是指定循环运行10次sess.run,而是使用while(True) - try - except的形式来将所有数据遍历一遍(即一个epoch)。这是因为在动态指定输入数据时,不同数据来源的数据量大小难以预知,而这个方法使我们不必提前知道数据量的精确大小。

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

数据集的高层操作

1. dataset.map

dataset = dataset.map(parser)

dataset = dataset.map(lambda x:preprocess_for_train(x, image_size, image_size, None))

2. dataset.batch & dataset.shuffle

  • dataset = dataset.batch(batch_size)将数据组合成batch,参数batch_size代表要输出的每个batch由多少条数据组成。如果数据集中包含多个张量,那么batch操作将对每一个张量分开进行。举例而言,如果数据集中的每一个数据(即iterator.get_next()的返回值)是image、label两个张量,其中image的维度是[300, 300],label的维度是[],batch_size是128,那么经过batch 操作后的数据集的每一个输出将包含两个维度分别是[128, 300, 300]和[128]的张量。

  • dataset = dataset.shuffle(buffer_size),随机打乱顺序,参数buffer_size等效于tf.train.shuffle_batch中的参数min_after_dequeue。shuffle算法在内部使用一个缓冲区中保存buffer_size条数据,每读入一条新数据时,从这个缓冲区中随机选择一条数据进行输出。缓冲区的大小越大,随机的性能越好,但占用的内存也越多。

3. dataset.repeat

repeat是另一个常用的操作方法,它将数据集中的数据复制多份,其中每一份被称为一个epoch。

dataset = dataset.repeat(N)

上面代码将数据重复N份。需要指出的是,**如果数据集在repeat前己经进行了shuffle操作,输出的每个epoch中随机shuffle的结果并不会相同。**例如,如果输入数据是[1, 2, 3],shuffle后输出的第一个epoch是[2, 1, 3],而第二个epoch则有可能是[3, 2, 1]。repeat和map、shuffle、batch等操作一样,都只是计算图中的一个计算节点。repeat只代表重复相同的处理过程,并不会记录前一个epoch的处理结果。

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

4. 其他数据集操作

除这些方法以外,数据集还提供了其他多种操作。例如:

  • concatenate()将两个数据集顺序连接起来;
  • take(N)从数据集中读取前N项数据;
  • skip(N)在数据集中跳过前N项数据;
  • flap_map()从多个数据集中轮流读取数据;

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 1. 列举输入文件。训练和测试使用不同的数据
train_files = tf.train.match_filenames_once("output.tfrecords")
test_files = tf.train.match_filenames_once("output_test.tfrecords")

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 2. 解析一个TFRecord的方法。
def parser(record):
    features = tf.parse_single_example(
        record,
        features={
            'image_raw':tf.FixedLenFeature([],tf.string),
            'pixels':tf.FixedLenFeature([],tf.int64),
            'label':tf.FixedLenFeature([],tf.int64)
        })
    decoded_images = tf.decode_raw(features['image_raw'],tf.uint8)
    retyped_images = tf.cast(decoded_images, tf.float32)
    images = tf.reshape(retyped_images, [784])
    labels = tf.cast(features['label'],tf.int32)
    #pixels = tf.cast(features['pixels'],tf.int32)
    return images, labels

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 3. 定义训练数据集
image_size = 299          # 定义神经网络输入层图片的大小。
batch_size = 100          # 定义组合数据batch的大小。
shuffle_buffer = 10000   # 定义随机打乱数据时buffer的大小。

# 定义读取训练数据的数据集。
dataset = tf.data.TFRecordDataset(train_files)
dataset = dataset.map(parser)

# 对数据集进行预处理,这里略去
# dataset = dataset.map(lambda image, label:(
#                          preprocess_for_train(image, image_size, image_size, None), label)

# 对数据进行shuffle和batching操作。这里省略了对图像做随机调整的预处理步骤。
dataset = dataset.shuffle(shuffle_buffer).batch(batch_size)

# 重复NUM_EPOCHS个epoch。间接指定训练的轮数
NUM_EPOCHS = 10
dataset = dataset.repeat(NUM_EPOCHS)

# 定义数据集迭代器。虽然定义数据集时没有直接使用placeholder来提供文件地址,但是
# tf.train.match_filenames_once方法得到的结果和与placeholder的机制类似,也需要
# 初始化,所以这里使用的是initializable_iterator。
iterator = dataset.make_initializable_iterator()
image_batch, label_batch = iterator.get_next()

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 4. 定义神经网络的结构个优化过程。这里与7.3.4小节相同。
def inference(input_tensor, weights1, biases1, weights2, biases2):
    layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
    return tf.matmul(layer1, weights2) + biases2

INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500
REGULARAZTION_RATE = 0.0001   
TRAINING_STEPS = 5000        

weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))

weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))

y = inference(image_batch, weights1, biases1, weights2, biases2)
    
# 计算交叉熵及其平均值
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=label_batch)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
    
# 损失函数的计算
regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
regularaztion = regularizer(weights1) + regularizer(weights2)
loss = cross_entropy_mean + regularaztion

# 优化损失函数
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 5. 定义测试用的数据集及相关
# 与训练时不同,测试数据的Dataset不需要经过随机翻转等预处理操作,
# 也不需要打乱顺序和重复多个epoch,这里使用与训练数据相同的parser
# 进行解析,调整分辨率到网络输入层大小,然后直接进行batching操作。
# 定义测试用的Dataset。
test_dataset = tf.data.TFRecordDataset(test_files)
test_dataset = test_dataset.map(parser)
test_dataset = test_dataset.batch(batch_size)

# 定义测试数据上的迭代器。
test_iterator = test_dataset.make_initializable_iterator()
test_image_batch, test_label_batch = test_iterator.get_next()

# 定义测试数据上的预测结果为logits值最大的分类。
test_logit = inference(test_image_batch, weights1, biases1, weights2, biases2)
predictions = tf.argmax(test_logit, axis=-1, output_type=tf.int32)

@GH1995
Copy link
Owner Author

GH1995 commented May 9, 2020

# 6. 声明会话并运行神经网络的优化过程。
with tf.Session() as sess:  
    # 初始化变量。
    sess.run((tf.global_variables_initializer(),
              tf.local_variables_initializer()))
    
    # 初始化训练数据的迭代器。
    sess.run(iterator.initializer)
    
    # 循环进行训练,直到数据集完成输入、抛出OutOfRangeError错误。
    while True:
        try:
            sess.run(train_step)
        except tf.errors.OutOfRangeError:
            break

    # 初始化测试数据的迭代器。
    sess.run(test_iterator.initializer)
    # 获取预测结果。
    test_results = []
    test_labels = []
    while True:
        try:
            pred, label = sess.run([predictions, test_label_batch])
            test_results.extend(pred)
            test_labels.extend(label)
        except tf.errors.OutOfRangeError:
            break

# 计算准确率
correct = [float(y == y_) for (y, y_) in zip (test_results, test_labels)]
accuracy = sum(correct) / len(correct)
print("Test accuracy is:", accuracy) 

@GH1995
Copy link
Owner Author

GH1995 commented May 10, 2020

if i % 1000 == 0:
    # 配置运行时需要记录的信息
    run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
    # 运行时记录运行信息的proto
    run_metadata = tf.RunMetadata()
    # 将配置信息和记录运行的proto传入运行的过程,从而记录运行时每一个节点的时间、内存信息
    _, loss_value, step = sess.run([train_op, loss, global_step],
                                    feed_dict={x: xs, y_:ys},
                                    options=run_options,
                                    run_metadata=run_metadata)
    # 将节点在运行时的信息写入日志文件
    train_writer.add_run_metadata(run_metadata, 'step%03d'%i)
    print("After %d training step(s), loss on training batch is %g" % (step, loss_value))

@GH1995
Copy link
Owner Author

GH1995 commented May 10, 2020

# 1. 生成变量监控信息并定义生成监控信息日志的操作。
# 其中var给出了需要记录的张量,name给出了在可视化结果中显示的图发名称,这个名称一般与变量名一致。
def variable_summaries(var, name):
    # 将生成监控信息的操作放到统一命名空间下
    with tf.name_scope('summaries'):
        # 通过tf.summary.histogram函数记录张量中元素的取值分布。对于给出的图表
        # 名称和张量,tf.summary.histogram函数会生成一个Summary protocol buffer。
        # 将Summary写入TensorBoard日志文件后,在HISTOGRAMS栏和DISTRIBUTION栏下
        # 都会出现对应名称的图表。和TensorFlow中其他操作类似,
        # tf.summary.histogram函数不会立刻被执行,只有当sess.run函数明确调用这
        # 个操作时,TensorFlow才会具正生成并输出Summary protocol buffer。
        # 下文将更加详细地介绍如何理解HISTOGRAMS栏和DISTRIBUTION栏下的信息。
        tf.summary.histogram(name, var)
        
        # 计算变量的平均值,并定义生成平均值信息日志的操作。记录变量平均值信息的日志标签名
        # 为'mean/'+name,其中mean为命名空间,/是命名空间的分隔符,从图11.14中可以看到,
        # 在相同命名空间中的监控指标会被整合到同一栏中;name则给出了当前监控指标属于哪一个变量。
        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)  

@GH1995
Copy link
Owner Author

GH1995 commented May 10, 2020

# 2. 生成一层全链接的神经网络。
def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
    # 将同一层神经网络放在一个统一的命名空间下
    with tf.name_scope(layer_name):
        # 声明神经网络边上的权重,并调用生成权重监控信息日志的函数。
        with tf.name_scope('weights'):
            weights = tf.Variable(tf.truncated_normal([input_dim, output_dim], stddev=0.1))
            variable_summaries(weights, layer_name + '/weights')
            
        # 声明神经网络的偏置项,并调用生成偏置项监控信息日志的函数。    
        with tf.name_scope('biases'):
            biases = tf.Variable(tf.constant(0.0, shape=[output_dim]))
            variable_summaries(biases, layer_name + '/biases')
            
        with tf.name_scope('Wx_plus_b'):
            preactivate = tf.matmul(input_tensor, weights) + biases
            # 记录神经网络输出节点在经过激活的数之前的分布。
            tf.summary.histogram(layer_name + '/pre_activations', preactivate)
        activations = act(preactivate, name='activation')        
        
        # 记录神经网络输出节点在经过激活函数之后的分布。在图11.17中,对于layer1,因
        # 为使用了ReLU函数作为激活函数,所以所有小于0的值部被设为了0。于是在激活后
        # 的layer1/activations图上所有的值都是大于0的。而对于layer2,因为没有使用
        # 激活函数,所以layer2/activations和layer2/pre_activations一样。
        tf.summary.histogram(layer_name + '/activations', activations)
        return activations

@GH1995
Copy link
Owner Author

GH1995 commented May 10, 2020

# 3. 主函数
def main():
    mnist = input_data.read_data_sets("../../datasets/MNIST_data", one_hot=True)
    # 定义输入
    with tf.name_scope('input'):
        x = tf.placeholder(tf.float32, [None, 784], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, 10], name='y-input')

    # 将输入向量还原成图片的像素矩阵,并通过tf.summary.image函数将当前的图片信息写入日志的操作
    with tf.name_scope('input_reshape'):
        image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
        tf.summary.image('input', image_shaped_input, 10)

    hidden1 = nn_layer(x, 784, 500, 'layer1')
    y = nn_layer(hidden1, 500, 10, 'layer2', act=tf.identity)
    
    # 计算交叉熵并定义生成交叉熵监控日志的操作
    with tf.name_scope('cross_entropy'):
        cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=y, labels=y_))
        tf.summary.scalar('cross_entropy', cross_entropy)

    with tf.name_scope('train'):
        train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)

    # 当前模型在当前给定数据上的正确率,并定义生成正确率监控日志的操作。如果在sess.run
    # 时给定的数据训练batch,那么得到的正确率就是在这个训练batch上的正确率;如果给定的
    # 数据为验证或者测试数据,那么得到的正确率就是在当前模型在验证或者测试数据上的正确率。
    with tf.name_scope('accuracy'):
        with tf.name_scope('correct_prediction'):
            correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        with tf.name_scope('accuracy'):
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        tf.summary.scalar('accuracy', accuracy)

    # 和TensorFlow其他操作类似,tf.summary.scalar、tf.summary.histogram和tf.summary.image
    # 函数都不会立即执行,需要通过sess.run来明确调用这些函数。因为程序中定义的写日志操作
    # 比较多,一一调用非常麻烦,所以TensorFlow提供了tf.summary.merge_all函数来整理所有的
    # 日志生成操作。在TensorFlow程序执行的过程中只要运行这个操作就可以将代码中定义的所有
    # 日志生成操作执行一次,从而将所有日志文件。
    merged = tf.summary.merge_all()

    with tf.Session() as sess:
        # 初始化写日志的writer,并将当前TensorFlow计算图写入日志。
        summary_writer = tf.summary.FileWriter(SUMMARY_DIR, sess.graph)
        tf.global_variables_initializer().run()

        for i in range(TRAIN_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            # 运行训练步骤以及所有的日志生成操作,得到这次运行的日志。
            summary, _ = sess.run([merged, train_step], feed_dict={x: xs, y_: ys})
            # 将得到的所有日志写入日志文件,这样TensorBoard程序就可以拿到这次运行所对应的
            # 运行信息。
            summary_writer.add_summary(summary, i)

    summary_writer.close()

@GH1995
Copy link
Owner Author

GH1995 commented May 10, 2020

# 生成可视化最终输出层向量所需要的日志文件。
def visualisation(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测试
    # 图片对应的真实类别。在单词向量中可以是单词ID对应的单词。这个文件是可选的,
    # 如果没有指定那么向量就没有标签。
    embedding.metadata_path = META_FIEL

    # 指定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)
    
    # 生成会话,初始化新声明的变量并将需要的日志信息写入文件。
    sess = tf.InteractiveSession()
    sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    saver.save(sess, os.path.join(LOG_DIR, "model"), TRAINING_STEPS)
    
    summary_writer.close()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant