加速tensorflow图计算速度学习笔记

1.线程和队列

来源:如何让TensorFlow模型运行提速36.8%_lyc_yongcai的博客-CSDN博客

1.抛弃传统tensorflow中的同步方法,既是训练操作必须要等数据传入之后才能开始运行,取而代之的是tensorflow中的线程和队列。将数据的输出和取出用队列形式操作,将其放在不同的线程中,这样就可以取代传统的方法。

import time
import tensorflow as tf
from tensorflow.contrib.rnn import LSTMCell
 
time_length = 128   
batch_size = 400    
feature_size = 512
 
hidden_size = 128
 
## prepare data
x = tf.random_normal([time_length, batch_size, feature_size], mean=0, stddev=1)
 
q = tf.FIFOQueue(capacity=4, dtypes=tf.float32)     #创建tensorflow队列,最多4个元素,类型为float32
enqueue_op = q.enqueue(x)                           #将生成的样本入队
num_threads = 1             #创建列表内线程的数目
qr = tf.train.QueueRunner(q, [enqueue_op] * num_threads)
tf.train.add_queue_runner(qr) #将queue_runner添加到TensorFlow QUEUE_RUNNERS集合中去
inputs = q.dequeue()                #获取生成的x输入数据
inputs.set_shape(x.get_shape())     #定义输入的形状
y = tf.reduce_mean(tf.reduce_sum(inputs, axis=0), axis=1, keep_dims=True)   #通过生成的input降维,用于便签y
labels = tf.cast(tf.greater(y, 0), tf.int32)    #强制转换,如果y<0就置0,y>0就置1,生成标签y
 
## build model
sequence_length = tf.Variable([time_length]*batch_size, dtype=tf.int32)
cell_fw = LSTMCell(num_units=hidden_size)               #定义前后向的cell
cell_bw = LSTMCell(num_units=hidden_size)
outputs, state = tf.nn.bidirectional_dynamic_rnn(       #定义模型
      cell_fw=cell_fw,
      cell_bw=cell_bw,
      inputs=inputs,
      sequence_length=sequence_length,
      dtype=tf.float32,
      time_major=True)
 
outputs_fw, outputs_bw = outputs        
outputs = tf.concat([outputs_fw, outputs_bw], axis=2) #前后向的输出连接
outputs = tf.reduce_mean(outputs, axis=0)             #降维得到最终的outputs
#在BiLstm的顶端加入一个全连接层,将outputs连接上,最终输出为0,1标签
outputs = tf.contrib.layers.fully_connected(     
            inputs=outputs,
            num_outputs=1,
            activation_fn=None)
 
losses_op = tf.nn.sigmoid_cross_entropy_with_logits(None, tf.cast(labels, tf.float32), outputs)
losses_op = tf.reduce_mean(losses_op)   #定义损失函数
 
y_pred = tf.cast(tf.greater(outputs, 0), tf.int32)  #简单得到预测值
accuracy = tf.reduce_mean(tf.cast(tf.equal(y_pred, labels), tf.float32)) #计算准确率
# adam = tf.train.AdamOptimizer(0.001)
train_op = tf.train.AdamOptimizer(0.001).minimize(losses_op, name="train_op")   #定义学习率,优化算法
 
t1 = time.time()    
with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())            #初始化模型参数
  coord = tf.train.Coordinator()                         #Coordinator类用于控制线程
  threads = tf.train.start_queue_runners(coord=coord)          #启动所有线程
  for i in range(50):
    _, losses, acc = sess.run([train_op, losses_op, accuracy])
    print('epoch:%d, loss: %f' % (i, losses))
 
  coord.request_stop()      #线程停止
  coord.join(threads)       #要求等待线程结束
  print("Time taken: %f" % (time.time() - t1))

2.可以利用GPU进行计算加速

来源:https://www.jianshu.com/p/26ac409dfb38

在tensorflow中可以选定我们运行每一个操作的设备,这个设备可以是本地的cpu或者gpu,也可以是某一台远程的服务器。该文章中只关心本地的设备。在默认情况下,本地的cpu在其中称为/cpu:0,即使有多个,也不会区分他们,而对于gpu则会区分,命名方式一样,后面数字代表不同的gpu。

快捷方式查看每一个运算的设备,参数:log_device_placement

import tensorflow as tf

a = tf.constant([1.0, 2.0, 3.0], shape=[3], name='a')
b = tf.constant([1.0, 2.0, 3.0], shape=[3], name='b')
c = a + b
# 通过log_device_placement参数来输出运行每一个运算的设备。
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
print sess.run(c)

'''
在没有GPU的机器上运行以上代码可以得到类似以下的输出:
Device mapping: no known devices.

add: (Add): /job:localhost/replica:0/task:0/cpu:0
b: (Const): /job:localhost/replica:0/task:0/cpu:0
a: (Const): /job:localhost/replica:0/task:0/cpu:0
[ 2.  4.  6.]
'''

在配置好gpu的机器中,若没有指明运算用什么机器,则程序会默认使用gpu,例如上面放入有gpu的机器中运行会得到下面的结果


'''
Device mapping:
/job:localhost/replica:0/task:0/gpu:0 -> device: 0, name: GRID K520, pci bus id: 0000:00:03.0
/job:localhost/replica:0/task:0/gpu:1 -> device: 1, name: GRID K520, pci bus id: 0000:00:04.0
/job:localhost/replica:0/task:0/gpu:2 -> device: 2, name: GRID K520, pci bus id: 0000:00:05.0
/job:localhost/replica:0/task:0/gpu:3 -> device: 3, name: GRID K520, pci bus id: 0000:00:06.0

add: (Add): /job:localhost/replica:0/task:0/gpu:0
b: (Const): /job:localhost/replica:0/task:0/gpu:0
a: (Const): /job:localhost/replica:0/task:0/gpu:0
[ 2.  4.  6.]
'''

如果不指定不同的运算在不同的gpu上,那么即使机器中有着多个gpu那么程序还是只会放在一个gpu0上运行。

如果需要不同的运算放在不同的设备上运行,那么需要使用tf.device来手工指定。如下:

import tensorflow as tf

# 通过tf.device将运算指定到特定的设备上。
with tf.device('/cpu:0'):
   a = tf.constant([1.0, 2.0, 3.0], shape=[3], name='a')
   b = tf.constant([1.0, 2.0, 3.0], shape=[3], name='b')

with tf.device('/gpu:1'):
    c = a + b

sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
print sess.run(c)

'''
在AWS g2.8xlarge实例上运行上述代码可以得到以下结果:
Device mapping:
/job:localhost/replica:0/task:0/gpu:0 -> device: 0, name: GRID K520, pci bus id: 0000:00:03.0
/job:localhost/replica:0/task:0/gpu:1 -> device: 1, name: GRID K520, pci bus id: 0000:00:04.0
/job:localhost/replica:0/task:0/gpu:2 -> device: 2, name: GRID K520, pci bus id: 0000:00:05.0
/job:localhost/replica:0/task:0/gpu:3 -> device: 3, name: GRID K520, pci bus id: 0000:00:06.0

add: (Add): /job:localhost/replica:0/task:0/gpu:1
b: (Const): /job:localhost/replica:0/task:0/cpu:0
a: (Const): /job:localhost/replica:0/task:0/cpu:0
[ 2.  4.  6.]
'''

但是如果机器没有配置相应的设备但是代码中写有运行该设备,程序则会报错。

import tensorflow as tf

# 在CPU上运行tf.Variable
a_cpu = tf.Variable(0, name="a_cpu")

with tf.device('/gpu:0'):
    # 将tf.Variable强制放在GPU上。
    a_gpu = tf.Variable(0, name="a_gpu")

sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
sess.run(tf.initialize_all_variables())

'''
运行以上程序将会报出以下错误:
tensorflow.python.framework.errors.InvalidArgumentError: Cannot assign a device to node 'a_gpu': Could not satisfy explicit device specification '/device:GPU:0' because no supported kernel for GPU devices is available.
Colocation Debug Info:
Colocation group had the following types and devices: 
Identity: CPU 
Assign: CPU 
Variable: CPU 
[[Node: a_gpu = Variable[container="", dtype=DT_INT32, shape=[], shared_ name="", _device="/device:GPU:0"]()]]
'''

 注意:在gpu上,tf.Variable操作只支持实数型(float16、float32和double)的参数。

为避免这个问题,TensorFlow在生成会话时可以指定allow_soft_placement参数。当allow_soft_placement参数设置为True时,如果运算无法由GPU执行,那么TensorFlow会自动将它放到CPU上执行,下面例子。

import tensorflow as tf

a_cpu = tf.Variable(0, name="a_cpu")
with tf.device('/gpu:0'):
    a_gpu = tf.Variable(0, name="a_gpu")

# 通过allow_soft_placement参数自动将无法放在GPU上的操作放回CPU上。
sess = tf.Session(config=tf.ConfigProto(
    allow_soft_placement=True, log_device_ placement=True))
sess.run(tf.initialize_all_variables())

'''
运行上面这段程序可以得到以下结果:
Device mapping:
/job:localhost/replica:0/task:0/gpu:0 -> device: 0, name: GRID K520, pci bus id: 0000:00:03.0
/job:localhost/replica:0/task:0/gpu:1 -> device: 1, name: GRID K520, pci bus id: 0000:00:04.0
/job:localhost/replica:0/task:0/gpu:2 -> device: 2, name: GRID K520, pci bus id: 0000:00:05.0
/job:localhost/replica:0/task:0/gpu:3 -> device: 3, name: GRID K520, pci bus id: 0000:00:06.0
a_gpu: /job:localhost/replica:0/task:0/cpu:0
a_gpu/read: /job:localhost/replica:0/task:0/cpu:0
a_gpu/Assign: /job:localhost/replica:0/task:0/cpu:0
init/NoOp_1: /job:localhost/replica:0/task:0/gpu:0
a_cpu: /job:localhost/replica:0/task:0/cpu:0
a_cpu/read: /job:localhost/replica:0/task:0/cpu:0
a_cpu/Assign: /job:localhost/replica:0/task:0/cpu:0
init/NoOp: /job:localhost/replica:0/task:0/gpu:0
init: /job:localhost/replica:0/task:0/gpu:0
a_gpu/initial_value: /job:localhost/replica:0/task:0/gpu:0
a_cpu/initial_value: /job:localhost/replica:0/task:0/cpu:0

从输出的日志中可以看到在生成变量a_gpu时,无法放到GPU上的运算被自动调整到了CPU上(比如a_gpu和a_gpu/read),而可以被GPU执行的命令(比如a_gpu/initial_value)依旧由GPU执行。
'''

 虽然gpu可以加快tensorflow的运算但是一般来说不会把所有的运算操作全部放在gpu上运行,因为gpu在机器上算是比较独立的单元,数据从机器送到gpu需要消耗时间,结果送回去又需要时间。为了提高运算的速度,用户需要将一些相关的操作放在同一个设备上运行。

tensorflow会默认占用设备中所有gpu的所有显存,如果在程序中只需要运用到部分的gpu,那么可以用CUDA_VISIBLE_DEVICES环境变量来控制。以下例子。

# 只使用第二块GPU(GPU编号从0开始)。在demo_code.py中,机器上的第二块GPU的名称变成/gpu:0,在运行时所有/gpu:0的运算将被放在第二块GPU上。
CUDA_VISIBLE_DEVICES=1 python demo_code.py
# 只使用第一块和第二块GPU。
CUDA_VISIBLE_DEVICES=0,1 python demo_code.py

也可以在程序中指定:

import os

# 只使用第三块GPU。
os.environ["CUDA_VISIBLE_DEVICES"] = "2"

虽然TensorFlow默认会一次性占用一个GPU的所有显存,但是TensorFlow也支持动态分配GPU的显存,使得一块GPU上可以同时运行多个任务。下面给出了TensorFlow动态分配显存的方法。

config = tf.ConfigProto()

# 让TensorFlow按需分配显存。

config.gpu_options.allow_growth = True

# 或者直接按固定的比例分配。以下代码会占用所有可使用GPU的40%显存。
# config.gpu_options.per_process_gpu_memory_fraction = 0.4
session = tf.Session(config=config, ...)

3.分布式tensorflow

来源:TensorFlow10:TensorFlow计算加速 - 简书

通过多个gpu配合工作可以得到很好的加速效果,但是机器上安装的gpu数总是有限的,要想再进一步加快其计算速度,那就要通过将tensorflow分布式运行在不同的机器上。

 一个最简单的tensorflow集群

import tensorflow as tf
c = tf.constant("Hello, distributed Tensorflow!")
# 创建一个本地TensorFlow集群
server = tf.train.Server.create_local_server()
# 在集群上创建一个会话
sess = tf.Session(server.target)
print(sess.run(c))

TensorFlow集群中的任务也会被聚合成工作(jobs),每个工作可以包含一个或者多个任务。比如在训练深度学习模型时,一台运行反向传播的机器是一个任务,而所有运行反向传播机器的集合是一种工作。

上面的代码是只有一个任务的,但是在我们一般情况中都是有多个任务的,这就需要我们使用tf.train.ClusterSpec来指定运行每一个任务的机器,比如下面的代码,就是有两个任务的工作。

import tensorflow as tf
# 创建两个集群
c = tf.constant("Hello from server1!")
# 生成一个有两个任务的集群,一个任务跑在本地2222端口,另外一个跑在本地2223端口
cluster = tf.train.ClusterSpec({"local": ["localhost:2222", "localhost:2223"]})
# 通过上面生成的集群配置成server
# job_name和task_index:指定当前所启动的任务。
server = tf.train.Server(cluster, job_name="local", task_index=0)
# server.target:生成会话来使用TensorFlow集群中的资源。
# log_device_placement:可以看到执行每一个操作的任务
sess = tf.Session(server.target, config=tf.ConfigProto(log_device_placement=True)) 
print(sess.run(c))
server.join()

# 第二个任务的代码
import tensorflow as tf
c = tf.constant("Hello from server2!")
cluster = tf.train.ClusterSpec({"local": ["localhost:2222", "localhost:2223"]})
server = tf.train.Server(cluster, job_name="local", task_index=1)
sess = tf.Session(server.target, config=tf.ConfigProto(log_device_placement=True)) 
print(sess.run(c))
server.join()

启动第一个任务后,可以得到类似下面的输出:

从第一个任务的输出中可以看到,当只启动第一个任务时,程序会停下来等待第二个任务启动,而且持续输出:fail to connect to...
当第二个任务启动后,可以看到从第一个任务中会输出Hello from server1!的结果。第二个任务的输出如下:

可以看出来第二个任务中定义的计算也被放在了设备/job:/local/replica:0/task/cpu:0上,也就是这个计算任务也是由第一个任务来执行,所以由上面看出,通过tf.train.Server.target生成的会话可以统一管理整个TensorFlow集群中的资源。

和使用多GPU类似,TensorFlow支持通过tf.device来指定操作运行在哪个任务上。比如将第二个任务中定义计算的语句改为以下代码,就可以看到这个计算将被调度到/job:local/replica:0/task:1/cpu:0上面。

with tf.device("/job:local/task:1")
        c = tf.constant("Hello from server2!")

在上面的样例中只定义了一个工作“local”。但一般在训练深度学习模型时,会定义两个工作。一个工作专门负责存储、获取以及更新变量的取值,这个工作所包含的任务统称为参数服务器(parameter server,ps)。另外一个工作负责运行反向传播算法来获取参数梯度,这个工作所包含的任务统称为服务器(worker)。下面给出了一个比较常见的用于训练深度学习模型的TensorFlow集群配置方法:

可以看出其中的两个上面所提及的两个任务。

3.1 分布式tensorflow训练深度学习模型一般有两种方式。

3.1.1计算图内分布式

使用这种分布式训练方式时,所有的任务都会使用一个TensorFlow计算图中的变量(也就是深度学习模型中的参数),而只是将计算部分发布到不同的计算服务器上。

然而因为计算图内分布式需要有一个中心节点来生成这个计算图并分配计算任务,所以当数据量太大时,这个中心节点容易造成性能瓶颈。

3.1.2计算图内分布式

在每一个计算服务器上都会创建一个独立的TensorFlow计算图,但不同计算图中的相同参数需要以一种固定的方式放到同一个参数服务器上。

TensorFlow提供了tf.train.replica_device_setter函数来帮助完成这一个过程,在下部分将给出具体的样例。因为每个计算服务器的TensorFlow计算图是独立的,所以这种方式的并行度要更高。但在计算图之间分布式下进行参数的同步更新比较困难。为了解决这个问题,TensorFlow提供了tf.train.SyncReplicasOptimizer函数来帮助实现参数的同步更新


3.2 分布式TensorFlow模型训练

3.2.1 异步模式样例程序

# coding=utf-8
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

import mnist_inference

# 配置神经网络的参数。
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.01
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 1000
MOVING_AVERAGE_DECAY = 0.99

# 模型保存的路径和文件名。
MODEL_SAVE_PATH = "logs/log_async"
DATA_PATH = "../../datasets/MNIST_data"

FLAGS = tf.app.flags.FLAGS

# 指定当前程序是参数服务器还是计算服务器。
tf.app.flags.DEFINE_string('job_name', 'worker', ' "ps" or "worker" ')
# 指定集群中的参数服务器地址。
tf.app.flags.DEFINE_string(
    'ps_hosts', ' tf-ps0:2222,tf-ps1:1111',
    'Comma-separated list of hostname:port for the parameter server jobs. e.g. "tf-ps0:2222,tf-ps1:1111" ')
# 指定集群中的计算服务器地址。
tf.app.flags.DEFINE_string(
    'worker_hosts', ' tf-worker0:2222,tf-worker1:1111',
    'Comma-separated list of hostname:port for the worker jobs. e.g. "tf-worker0:2222,tf-worker1:1111" ')
# 指定当前程序的任务ID。
tf.app.flags.DEFINE_integer('task_id', 0, 'Task ID of the worker/replica running the training.')

# 定义TensorFlow的计算图,并返回每一轮迭代时需要运行的操作。
def build_model(x, y_, is_chief):
    regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
    # 通过后面给出的mnist_inference.py代码计算神经网络前向传播的结果。
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)

    # 计算损失函数并定义反向传播过程。
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE, global_step, 60000 / BATCH_SIZE, LEARNING_RATE_DECAY)
    train_op = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    # 定义每一轮迭代需要运行的操作。
    if is_chief:
        # 计算变量的滑动平均值。   
        variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
        variables_averages_op = variable_averages.apply(tf.trainable_variables())
        with tf.control_dependencies([variables_averages_op, train_op]):
            train_op = tf.no_op()
    return global_step, loss, train_op


def main(argv=None):
    # 解析flags并通过tf.train.ClusterSpec配置TensorFlow集群。
    ps_hosts = FLAGS.ps_hosts.split(',')
    worker_hosts = FLAGS.worker_hosts.split(',')
    cluster = tf.train.ClusterSpec({"ps": ps_hosts, "worker": worker_hosts})
    # 通过tf.train.ClusterSpec以及当前任务创建tf.train.Server。
    server = tf.train.Server(cluster, job_name = FLAGS.job_name, task_index=FLAGS.task_id)

    # 参数服务器只需要管理TensorFlow中的变量,不需要执行训练的过程。server.join()会一直停在这条语句上。
    if FLAGS.job_name == 'ps':
        with tf.device("/cpu:0"):
            server.join()

    # 定义计算服务器需要运行的操作。
    is_chief = (FLAGS.task_id == 0)
    mnist = input_data.read_data_sets(DATA_PATH, one_hot=True)
    #不同计算图中的相同参数需要以一种固定的方式放到同一个参数服务器上
    device_setter = tf.train.replica_device_setter(worker_device="/job:worker/task:%d" % FLAGS.task_id, cluster=cluster)
    with tf.device(device_setter):

        # 定义输入并得到每一轮迭代需要运行的操作。
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
        global_step, loss, train_op = build_model(x, y_, is_chief)

        # 定义用于保存模型的saver。
        saver = tf.train.Saver()
        # 定义日志输出操作。
        summary_op = tf.summary.merge_all()
        # 定义变量初始化操作。
        init_op = tf.global_variables_initializer()
        # 通过tf.train.Supervisor管理训练深度学习模型时的通用功能。
        sv = tf.train.Supervisor(
            is_chief=is_chief,
            logdir=MODEL_SAVE_PATH,
            init_op=init_op,
            summary_op=summary_op,
            saver=saver,
            global_step=global_step,
            save_model_secs=60,
            save_summaries_secs=60)

        sess_config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
        # 通过tf.train.Supervisor生成会话。
        sess = sv.prepare_or_wait_for_session(server.target, config=sess_config)

        step = 0
        start_time = time.time()

        # 执行迭代过程。
        while not sv.should_stop():
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, global_step_value = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
            if global_step_value >= TRAINING_STEPS: break

           # 每隔一段时间输出训练信息。
            if step > 0 and step % 100 == 0:
                duration = time.time() - start_time
                sec_per_batch = duration / global_step_value
                format_str = "After %d training steps (%d global steps), loss on training batch is %g.  (%.3f sec/batch)"
                print format_str % (step, global_step_value, loss_value, sec_per_batch)
            step += 1
    sv.stop()

if __name__ == "__main__":
    tf.app.run()

假设上面代码的文件名为dist_tf_mnist_async.py,那么要启动一个拥有一个参数服务器、两个计算服务器的集群,需要先在运行参数服务器的机器上启动以下命令:

然后在运行第一个计算服务器的机器上启动以下命令:

最后在运行第二个计算服务器的机器上启动以下命令:

在启动第一个计算服务器之后,这个计算服务器就会尝试连接其他的服务器(包括计算服务器和参数服务器)。如果其他服务器还没有启动,则被启动的计算服务器会报连接出错的问题。下面展示了一个出错信息:

不过这不会影响TensorFlow集群的启动。当TensorFlow集群中所有服务器都被启动之后,每一个计算服务器将不再报错。在TensorFlow集群完全启动之后,训练过程将被执行。

在异步模式下,即使有计算服务器没有正常工作,参数更新的过程仍可继续,而且全局的迭代轮数时所有计算服务器迭代轮数的和。

3.2.2 同步模式样例程序

# coding=utf-8
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

import mnist_inference

# 配置神经网络的参数。
BATCH_SIZE = 100 
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRAINING_STEPS = 10000
MOVING_AVERAGE_DECAY = 0.99 
MODEL_SAVE_PATH = "logs/log_sync"
DATA_PATH = "../../datasets/MNIST_data"


# 和异步模式类似的设置flags。
FLAGS = tf.app.flags.FLAGS

tf.app.flags.DEFINE_string('job_name', 'worker', ' "ps" or "worker" ')
tf.app.flags.DEFINE_string(
    'ps_hosts', ' tf-ps0:2222,tf-ps1:1111',
    'Comma-separated list of hostname:port for the parameter server jobs. e.g. "tf-ps0:2222,tf-ps1:1111" ')
tf.app.flags.DEFINE_string(
    'worker_hosts', ' tf-worker0:2222,tf-worker1:1111',
'Comma-separated list of hostname:port for the worker jobs. e.g. "tf-worker0:2222,tf-worker1:1111" ')
tf.app.flags.DEFINE_integer('task_id', 0, 'Task ID of the worker/replica running the training.')

# 和异步模式类似的定义TensorFlow的计算图。唯一的区别在于使用tf.train.SyncReplicasOptimizer函数处理同步更新。
def build_model(x, y_, n_workers, is_chief):
    regularizer = tf.contrib.layers.l2_regularizer(REGULARAZTION_RATE)
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)

    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    variables_averages_op = variable_averages.apply(tf.trainable_variables())

    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE, global_step, 60000 / BATCH_SIZE, LEARNING_RATE_DECAY)
   
    # 通过tf.train.SyncReplicasOptimizer函数实现同步更新。
    opt = tf.train.SyncReplicasOptimizer(
        tf.train.GradientDescentOptimizer(learning_rate),
        replicas_to_aggregate=n_workers,
        total_num_replicas=n_workers)

    train_op = opt.minimize(loss, global_step=global_step)     
    if is_chief:
        variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
        variables_averages_op = variable_averages.apply(tf.trainable_variables())
        with tf.control_dependencies([variables_averages_op, train_op]):
            train_op = tf.no_op()

    return global_step, loss, train_op, opt

def main(argv=None): 
    # 和异步模式类似的创建TensorFlow集群。
    ps_hosts = FLAGS.ps_hosts.split(',')
    worker_hosts = FLAGS.worker_hosts.split(',')
    print ('PS hosts are: %s' % ps_hosts)
    print ('Worker hosts are: %s' % worker_hosts)
    n_workers = len(worker_hosts)

    cluster = tf.train.ClusterSpec({"ps": ps_hosts, "worker": worker_hosts})
    server = tf.train.Server(
        cluster, job_name = FLAGS.job_name, task_index=FLAGS.task_id)

    if FLAGS.job_name == 'ps':
        with tf.device("/cpu:0"):
            server.join()

    is_chief = (FLAGS.task_id == 0)
    mnist = input_data.read_data_sets(DATA_PATH, one_hot=True)
   
    with tf.device(tf.train.replica_device_setter(
            worker_device="/job:worker/task:%d" % FLAGS.task_id, cluster=cluster)):
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
        global_step, loss, train_op, opt = build_model(x, y_, n_workers, is_chief)
        # 和异步模式类似的声明一些辅助函数。
        saver = tf.train.Saver()
        summary_op = tf.summary.merge_all()
        init_op = tf.global_variables_initializer()

        # 在同步模式下,主计算服务器需要协调不同计算服务器计算得到的参数梯度并最终更新参数。
        # 这需要主计算服务器完成一些额外的初始化工作。
        if is_chief:
            # 获取协调不同计算服务器的队列。在更新参数之前,主计算服务器需要先启动这些队列。
            chief_queue_runner = opt.get_chief_queue_runner()
            # 初始化同步更新队列的操作。
            init_tokens_op = opt.get_init_tokens_op(0)
     
        # 和异步模式类似的声明tf.train.Supervisor。
        sv = tf.train.Supervisor(is_chief=is_chief,
                                logdir=MODEL_SAVE_PATH,
                                init_op=init_op,
                                summary_op=summary_op,
                                saver = saver,
                                global_step=global_step,
                                save_model_secs=60,
                                save_summaries_secs=60)
        sess_config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
        sess = sv.prepare_or_wait_for_session(server.target, config=sess_config)        

        # 在主计算服务器上启动协调同步更新的队列并执行初始化操作。
        if is_chief:
            sv.start_queue_runners(sess, [chief_queue_runner])
            sess.run(init_tokens_op)
     
        # 和异步模式类似的运行迭代的训练过程。
        step = 0
        start_time = time.time()
        while not sv.should_stop():
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, global_step_value = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
            if global_step_value >= TRAINING_STEPS: break

            if step > 0 and step % 100 == 0:
                duration = time.time() - start_time
                sec_per_batch = duration / (global_step_value * n_workers)
                format_str = "After %d training steps (%d global steps), loss on training batch is %g.  (%.3f sec/batch)"
                print format_str % (step, global_step_value, loss_value, sec_per_batch)
            step += 1
    sv.stop()
       
if __name__ == "__main__":
    tf.app.run()

和异步模式类似,在不同机器上运行以上代码就可以启动TensorFlow集群。但和异步模式不同的是,当第一台计算服务器初始化完毕之后,它并不能直接更新参数。这是因为在程序中要求每一次参数更新都需要来自两个计算服务器的梯度。在第一个计算服务器上,可以看到与下面类似的输出:

第二个计算服务器的输出如下:

在第一个计算服务器的第一行输出中可以看到,前100轮迭代平均速度为0.176 (sec/batch),要远远慢于最后的平均速度0.042(sec/batch)。这是因为在第一轮迭代开始之前,第一个计算服务器需要等待第二个计算服务器执行初始化的过程,于是导致前100轮迭代的平均速度是最慢的。这也反映了同步更新的一个问题。当一个计算服务器被卡住时,其他所有的计算服务器都需要等待这个最慢的计算服务器。

为了解决这个问题,可以调整tf.train.SyncReplicasOptimizer函数中的replicas_to_aggreate参数。当replicas_to_aggreate小于计算服务器总数时,每一轮迭代就不需要手机所有的梯度,从而避免被最慢的计算服务器卡住。

(既是设置一个数字,当完成的服务器的个数达到这么多个的时候,就可以更新参数了)

TensorFlow也支持通过调整同步队列初始化操tf.train.SyncReplicasOptimizer.get_init_tokens_op中的参数来控制对不同计算服务器之间的同步要求。当提供给初始化函数get_init_tokens_op的参数大于0时,TensorFlow支持多次使用由同一个计算服务器得到的梯度,于是也可以缓解计算服务器性能瓶颈的问题。(没弄懂)

3.3 使用Caicloud运行分布式TensorFlow

上面所列出的使用分布式的例子都需要人工的在一个一个机器上开启程序,但是如果程序中需要100个机器的时候怎么办呢,这时候工作会变得十分繁琐,所以这里提出了一个分布式tensorflow的开发平台

(这里不做笔记了,直接看来源资料)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值