среда, 5 декабря 2018 г.

TensorFlow Core: переменные (variables)

TensorFlow переменная (variable) - это лучший способ представления общедоступного, хранимого состояния, используемого вашей программой.

Переменные используются через tf.Variable класс. tf.Variable представляет собой тензор, значение которого может быть изменено выполнением операций на нем. В отличие от tf.Tensor объектов, tf.Variable существует вне контекста единичного session.run вызова.

Внутри себя tf.Variable хранит постоянный тензор. Определенные операции позволяют вам читать и изменять значения этого тензора. Эти изменения видимы в нескольких tf.Session, таким образом несколько исполняемых потоков могут видеть одни и те же значения для tf.Variable.

Создание переменной

Лучший способ создать переменную - вызвать tf.get_variable функцию. Эта функция требует, чтобы вы задали имя переменной. Это имя будет использовано другими копиями сессий для получения доступа к одной и той же переменной, а также будет использовано для именования значения данной переменной при создании контрольных точек и экспортировании модулей. tf.get_variable также позволяет повторно использовать ранее созданную переменную такого же имени, облегчая создание моделей, которые используют одинаковые слои.

Для создания переменной с tf.get_variable, просто предоставьте название и форму:

my_variable = tf.get_variable("my_variable", [1, 2, 3])

Этот код создает переменную, названную "my_variable", которая является трехмерным тензором с формой [1, 2, 3]. Эта переменная будет по умолчанию иметь dtype (тип данных) tf.float32 и ее первоначальное значение будет задано случайным образом, посредством tf.glorot_uniform_initializer.

Вы также можете опционально задать тип данных (dtype) и инициализатор (initializer) для tf.get_variable. Например:

my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], 
                                  dtype=tf.int32,
                                  initializer=tf.zeros_initializer)

TensorFlow предоставляет много удобных инициализаторов. С другой стороны, вы можете инициализировать tf.Variable значением tf.Tensor. Например:

other_variable = tf.get_variable("other_variable", dtype=tf.int32,
                                 initializer=tf.constant([23, 42]))

Отметим, что в случае, когда в качестве инициализатора используется tf.Tensor, вам не следует определять форму переменной, ввиду того, что эта форма будет взята из тензора, которым инициализируется переменная.

Коллекции переменных

Поскольку несвязанные между собой части TensorFlow программы могут захотеть создать переменные, часто бывает удобно иметь единый способ доступа ко всем переменным. По этой причине TensorFlow предоставляет коллекции, который называются списками тензоров и других объектов, таких как tf.Variable инстансы.

По умолчанию каждая tf.Variable располагается в следующих двух коллекциях:

  • tf.GraphKeys.GLOBAL_VARIABLES - переменные, которые могут быть общими для нескольких устройств
  • tf.GraphKeys.TRAINABLE_VARIABLES - переменные, для которых TensorFlow будет вычислять градиенты

Если вы не хотите, чтобы переменная была тренируема, добавьте ее в коллекцию tf.GraphKeys.LOCAL_VARIABLES. Например, следующий код показывает как добавить переменную, названную my_local, в эту коллекцию:

my_local = tf.get_variable("my_local", shape=(),
                           collections=[tf.GraphKeys.LOCAL_VARIABLES])

Альтернативно, вы можете определить trainable=False как аргумент к tf.get_variable:

my_non_trainable = tf.get_variable("my_non_trainable",
                                   shape=(),
                                   trainable=False)

Вы также можете использовать свои собственные коллекции. Любая строка - это валидное имя коллекции, и нет необходимости явным образом создавать коллекцию. Для того чтобы добавить переменную (или любой другой объект) в коллекцию, после создания переменной вызовите tf.add_to_collection. Например, следующий код добавляет существующую переменную my_local в коллекцию, названную my_collection_name:

tf.add_to_collection("my_collection_name", my_local)

А чтобы извлечь список всех переменных (или других объектов), размещенных вами в коллекции, можно использовать:

tf.get_collection("my_collection_name")

Размещение на устройствах (Device placement)

Как и любую другую TensorFlow операцию, вы можете разместить переменные на определенных устройствах. Например, следующий код создает переменную, названную v, и размещает ее на втором GPU устройстве:

with tf.device("/device:GPU:1"):
  v = tf.get_variable("v", [1])

Это особенно важно для переменных - быть на правильном устройстве в распределенных установках. Случайное размещение переменных на воркерах (workers) (коде программы, непосредственно выполняющем задачу) вместо помещения переменной в параметре servers, например, может значительно замедлить тренировку или, в худшем случае, позволит каждому воркеру беспечно продвигаться вперед с его собственной копией каждой переменной. По этой причине мы предоставляем tf.train.replica_device_setter, который может автоматически размещать переменные в параметре servers. Например:

cluster_spec = {
    "ps": ["ps0:2222", "ps1:2222"],
    "worker": ["worker0:2222", "worker1:2222", "worker2:2222"]}
with tf.device(tf.train.replica_device_setter(cluster=cluster_spec)):
  v = tf.get_variable("v", shape=[20, 20])  
  # эта переменная размещена в параметре server
  # посредством replica_device_setter


Инициализация переменных

До того как вы сможете использовать переменную, она должна быть инициализирована. Если вы программируете в низкоуровневом TensorFlow API (то есть вы явным образом создаете собственные графы и сессии), вы должны явным образом инициализировать переменные. Большинство высокоуровневых фреймворков, таких как tf.contrib.slim, tf.estimator.Estimator и Keras автоматически инициализируют переменные для вас перед тренировкой модели.

В некоторых случаях полезна явная инициализация, поскольку она позволяет вам не перезапускать потенциально затратные инициализаторы при перезагрузке модели из контрольной точки, а также позволяет достичь большей ясности, когда, случайным образом инициализированные, переменные разделяются в распределенном окружении.

Для того чтобы инициализировать все тренируемые переменные в один заход, перед началом тренировки, вызовите tf.global_variables_initializer(). Эта функция возвращает единственную операцию, ответственную за инициализацию всех переменных в tf.GraphKeys.GLOBAL_VARIABLES коллекции. Выполнение этой операции инициализирует все переменные. Например:

session.run(tf.global_variables_initializer())
# Теперь все переменные инициализированы.

Если вам не требуется самим инициализировать переменные, вы можете выполнить операцию инициализатора переменной. Например:

session.run(my_variable.initializer)

Может возникнуть потребность узнать - какие переменные до сих пор не инициализированы. Например, следующий код печатает имена всех переменных, которые еще не были инициализированы:

print(session.run(tf.report_uninitialized_variables()))

Отметим, что, по умолчанию, tf.global_variables_initializer не определяет порядок в котором переменные инициализированы. Поэтому, если первоначальное значение переменной зависит от значения другой переменной, есть вероятность, что вы получите ошибку. В любое время, когда вы используете значение переменной в контексте, где не все переменные инициализированы (скажем, если вы используете значение переменной во время инициализации другой переменной), лучше использовать variable.initialized_value() вместо переменной:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
w = tf.get_variable("w", initializer=v.initialized_value() + 1)


Использование переменных

Для того чтобы использовать значение tf.Variable в TensorFlow графе, просто рассматривайте его как обычный tf.Tensor:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
w = v + 1  
# w - это tf.Tensor, который вычислен на основании значения v.
# В любом случае, когда переменная использована в выражении
# она автоматически преобразована в tf.Tensor, 
# представляющий ее значение

Для того чтобы назначить значение переменной, используйте методы assign, assign_add, и содружественные с ними в tf.Variable классе. Например, вот как можно вызывать эти методы:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
tf.global_variables_initializer().run()
sess.run(assignment)  # или assignment.op.run(), или assignment.eval()

Большинство TensorFlow оптимизаторов имеют специализированные операции, которые эффективным способом обновляют значения переменных согласно какому-либо, подобному градиентному спуску, алгоритму.

Ввиду того, что переменные изменяемы (mutable), иногда полезно знать какая версия значения переменной была использована в любой конкретной точке времени. Для того чтобы принудительно заставлять перечитывать значение переменной после того как что-то произошло, вы можете использовать tf.Variable.read_value. Например:

v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
with tf.control_dependencies([assignment]):
  w = v.read_value()  
  # w гаранитированно отражает значение v
  # после assign_add операции


Предоставление общего доступа к переменным (Sharing variables)

TensorFlow поддерживает два способа предоставления общего доступа к переменным:

  • Явная передача tf.Variable объектов.
  • Неявное обертывание tf.Variable объектов в tf.variable_scope объекты.

Хотя код, который явным образом передает переменные, чище и понятнее, иногда удобно писать TensorFlow функции, которые неявно используют переменные в своих реализациях. Большинство функиональных слоев из tf.layers используют этот подход, также как все tf.metrics, и некоторые другие библиотечные утилиты.

Области видимости переменных позволяют контролировать переиспользование переменной, когда вызываются функции, которые неявно создают и используют переменные. Они также позволяют вам называть ваши переменные иерархическим и понятным способом.

Например, напишем функцию, которая создает сверточный / relu слой:

def conv_relu(input, kernel_shape, bias_shape):
    # Создаем переменную с именем "weights".
    weights = tf.get_variable("weights", kernel_shape,
        initializer=tf.random_normal_initializer())
    # Создаем переменную с именем "biases".
    biases = tf.get_variable("biases", bias_shape,
        initializer=tf.constant_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)

Эта функция использует короткие имена weights и biases, что удобнее для понимания содержимого переменных. В настоящей модели, однако, нам потребуется много таких сверточных слоев, и повторный вызов этой функции не будет работать так как предполагается:

input1 = tf.random_normal([1,10,10,32])
input2 = tf.random_normal([1,20,20,32])
x = conv_relu(input1, kernel_shape=[5, 5, 32, 32], bias_shape=[32])
x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32])
# Такой код не удастся.

Ввиду того, что желаемое поведение не ясно (создавать новые переменные или переиспользовать существующие?), TensorFlow не удастся выполнить требуемые действия. Вызов conv_relu в разных областях видимости, однако, проясняет, что мы хотим создать новые переменные:

def my_image_filter(input_images):
    with tf.variable_scope("conv1"):
        # Переменные, созданные здесь, будут названы 
        # "conv1/weights", "conv1/biases".
        relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Переменные, созданные здесь, будут названы 
        # "conv2/weights", "conv2/biases".
        return conv_relu(relu1, [5, 5, 32, 32], [32])

Если вы хотите предоставить общий доступ к переменным, тогда у вас есть два способа. Первый, вы можете создать область видимости с таким же именем, используя reuse=True:

with tf.variable_scope("model"):
  output1 = my_image_filter(input1)
with tf.variable_scope("model", reuse=True):
  output2 = my_image_filter(input2)

Вы можете также вызвать scope.reuse_variables(), чтобы запустить переиспользование:

with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
  scope.reuse_variables()
  output2 = my_image_filter(input2)

Ввиду того, что в зависимость от точного строкового имени областей видимости может быть опасной, также возможно инициализировать область видимости переменной, основываясь на другой области видимости:

with tf.variable_scope("model") as scope:
  output1 = my_image_filter(input1)
with tf.variable_scope(scope, reuse=True):
  output2 = my_image_filter(input2)


Читайте также другие статьи по этой теме в нашем блоге:

Основы TensorFlow Core

TensorFlow Core: тензоры (tensors)