суббота, 1 декабря 2018 г.

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

TensorFlow, как следует из названия (tensor flow - поток тнзоров) - это фреймворк для определения и выполнения вычислений, включающих тензоры. Тензор - это обобщение векторов и матриц к потенциально более высоким измерениям. Внутри, TensorFlow представляет тензоры как n-мерные массивы базовых типов данных.

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

tf.Tensor имеет следующие свойства:

  • тип данных (float32, int32, или string, например)
  • форма (shape)

Каждый элемент в Tensor имеет один и тот же тип данных, и тип данных всегда известен. Форма (то есть, количество измерений, которые он имеет, и размер каждого измерения) могут быть только частично известны. Большинство операций производят тензоры с полностью известными формами, если формы их вводов также полностью известны, но в некоторых случаях определить форму тензора возможно только во время исполнения графа.

Основные типы тензоров следующие: .

  • tf.Variable
  • tf.constant
  • tf.placeholder
  • tf.SparseTensor

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

Разряд (Rank)

Разряд tf.Tensor объекта - это количество его измерений. Синонимы разряда в данном случае - это порядок, степень, размерность. Следует отметить, что разряд в TensorFlow - это не то же самое, что и разряд матрицы в математике. Как показывает следующая таблица каждый разряд в TensorFlow соответствует различным математическим сущностям:

РазрядМатематическая сущность
0Скаляр (только величина)
1Вектор (величина и направление)
2Матрица (таблица чисел)
33-Тензор (куб чисел)
nn-Тензор (n-мерный тензор)

Разряд 0

Следующий код демонстрирует создание нескольких переменных 0 разряда:

mammal = tf.Variable("Elephant", tf.string)
ignition = tf.Variable(451, tf.int16)
floating = tf.Variable(3.14159265359, tf.float64)
its_complicated = tf.Variable(12.3 - 4.85j, tf.complex64)

Следует отметить, что строка в TensorFlow рассматривается как единый элемент, а не как последовательность символов. Таким образом возможно иметь скалярные строки, векторы строк и т.д.

Разряд 1

Для того чтобы создать объект tf.Tensor 1 разряда, можно передать список элементов, как первоначальное значение. Например:

mystr = tf.Variable(["Hello"], tf.string)
cool_numbers  = tf.Variable([3.14159, 2.71828], tf.float32)
first_primes = tf.Variable([2, 3, 5, 7, 11], tf.int32)
its_very_complicated = tf.Variable([12.3 - 4.85j, 7.5 - 6.23j], 
                                   tf.complex64)

Более высокие разряды

tf.Tensor объект 2 разряда состоит по крайней мере из одного ряда (row) и одной колонки (cell):

mymat = tf.Variable([[7],[11]], tf.int16)
myxor = tf.Variable([[False, True],[True, False]], tf.bool)
linear_squares = tf.Variable([[4], [9], [16], [25]], tf.int32)
squarish_squares = tf.Variable([ [4, 9], [16, 25] ], tf.int32)
rank_of_squares = tf.rank(squarish_squares)
mymatC = tf.Variable([[7],[11]], tf.int32)

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

my_image = tf.zeros([10, 299, 299, 3])  
# пакет x высота x ширина x цвет

Получение разряда tf.Tensor объекта

Для того чтобы определить разряд tf.Tensor объекта вызываем tf.rank метод. Например, следующий метод программно определяет разряд tf.Tensor объекта, созданного в предыдущей секции:

r = tf.rank(my_image) 
# после исполнения графа, r будет содержать значение 4

Ссылка на tf.Tensor слои

Ввиду того, что tf.Tensor - это n-мерный массив ячеек, для того чтобы получить доступ к единичной ячейке в tf.Tensor вам необходимо определить n индексы.

Для тензора 0 разряда (скаляр) индексы не требуются, ввиду того, что это единственное число.

Для тензора 1 разряда (вектор), передавая единственный индекса позволяет получить доступ к числу.

my_scalar = my_vector[2]

Отметим, что индекс переданный в [] сам может быть tf.Tensor скаляром, если вы хотите динамически выбирать элемент из вектора.

Для тензоров разряда 2 или выше ситуация более интересная. Для tf.Tensor разряда 2 передача двух чисел возвращает скаляр:

my_scalar = my_matrix[1, 2]

Передача единственного числа, однако, возвращает подвектор матрицы:

my_row_vector = my_matrix[2]
my_column_vector = my_matrix[:, 3]

: нотация - это python синтаксис для получения слоев - "оставить это измерение целиком". Это полезно для тензоров высоких рангов, ввиду того что это позволяет получать доступ к субвекторам, субматрицам и даже другим субтензорам.

Форма (Shape)

Форма тензора - это количество элементов в каждом измерении. TensorFlow автоматически выводит формы в ходе построения графа. Эти выведенные формы могут иметь известный или неизвестный разряд. Если разряд известен, размеры каждого измерения могут быть известны или неизвестны.

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

РазрядФормаРазмер измеренияПример
0[]0-D0-D тензор. Скаляр.
1[D0]1-D1-D тензор с формой [5].
2[D0,D1]2-D2-D тензор с формой [3,4].
3[D0,D1,D2]3-D3-D тензор с формой [1,4,3].
n[D0,D1,... Dn-1]n-Dn-D тензор с формой [D0, D1, ... Dn-1].

Формы могут быть представлены через Python листы(lists) / таплы(tuples) чисел, или с tf.TensorShape.

Получение формы tf.Tensor объекта

Существует два способа получения формы tf.Tensor объекта. В ходе построения графа, часто требуется спрашивать что уже известно о форме тензора. Это может быть сделано посредством метода shape tf.Tensor объекта. Этот метод возвращает TensorShape объект, который является удобным путем представления частично-определенных форм (ввиду того что при построении графа не все формы могут быть полностью известны).

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

Например, вот как можно создать вектор нулей с тем же размером что и количество колонок в заданной матрице:

zeros = tf.zeros(my_matrix.shape[1])

Изменение формы tf.Tensor объекта

Количество элементов в тензоре - это продукт размеров всех его форм. Количество элементов скаляра всегда 1. Ввиду того что часто существует много различных форм, которые имеют одинаковое количество элементов, зачастую удобно иметь возможность изменять форму tf.Tensor, сохраняя его элементы фиксированными. Этого можно достигнуть с tf.reshape.

Следующие примеры показывают как менять форму (переформировать)(reshape) тензоров:

При исполнении кода с русскоязычным комментарием не забывайте удалять комментарий либо выставлять строку
# -*- coding: utf-8 -*-
в начале скрипта.

rank_three_tensor = tf.ones([3, 4, 5])

# Переформируем существующее содержимое
# в 6x10 матрицу
matrix = tf.reshape(rank_three_tensor, [6, 10])  

# Переформируем существующее содержимое
# в 3x20 матрицу.
# -1 сообщает методу reshape
# вычислить размер этого измерения.

matrixB = tf.reshape(matrix, [3, -1]) 


# Переформируем существующее содержимое
# в 4x3x5 тензор.

matrixAlt = tf.reshape(matrixB, [4, 3, -1])  

# Следует отметить, что количество элементов
# переформируемых тензоров должно совпадать
# с первоначальным количеством элементов.
# Поэтому в следующем примере
# генерируется ошибка, потому что 
# никакое возможное значение для последнего измерения 
# не будет соответствовать числу элементов.

yet_another = tf.reshape(matrixAlt, [13, 2, -1])  # ERROR!

Типы данных (Data types)

В дополнение к размерности, тензоры имеют типы данных (tf.DType).

Невозможно иметь tf.Tensor с более чем одним типом данных. Возможно, однако, сериализовать произвольные структуры данных как строки и сохранить их в tf.Tensor.

Также доступно менять (cast) tf.Tensors из одного типа данных в другой, используя tf.cast:

# Переводим константный цельночисловой тензор в нецельночисловой.
float_tensor = tf.cast(tf.constant([1, 2, 3]), dtype=tf.float32)

Для того чтобы выяснить текущий тип данных тензора используйте Tensor.dtype свойство.

При создании tf.Tensor из python объекта можно опционально задать тип данных. Если тип данных не задан TensorFlow выберет тип данных, который может представлять ваши данные. TensorFlow преобразует Python целый числа (integer) в tf.int32, Python нецелые числа (float) в tf.float32. В остальных случаях TensorFlow использует правила, которые использует numpy при преобразовании массивов.

Оценка тензоров

Как только построен вычислительный граф, вы можете выполнять вычисление, которое производит отдельный tf.Tensor и получать значение, назначенное ему. Зачастую это удобно для отладки, а также это в основном необходимо для работы TensorFlow.

Простейший способ оценить тензор - это использовать Tensor.eval метод. Например:

constant = tf.constant([1, 2, 3])
tensor = constant * constant
print(tensor.eval())

eval метод работает только когда дефолтная tf.Session активна.

Tensor.eval возвращает numpy массив с тем же содержимым, что и тензор.

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

p = tf.placeholder(tf.float32)
t = p + 1.0

# Данная операция не удастся,
# ввиду того, что заполнитель не получил значения.
t.eval() 

# Эта операция удастся, потому что мы передали значение для заполнителя.
t.eval(feed_dict={p:2.0})  

Отметим, что доступно наполнение любого tf.Tensor, а не только заполнителей.

Другие конструкции моделей могут сделать оценку tf.Tensor сложной. TensorFlow не может напрямую оценивать tf.Tensor определенный внутри функций или конструкции вне потока контроля. Если tf.Tensor зависит от значения из очереди, оценка tf.Tensor будет работать только когда что-либо пришло из очереди, в ином случае оценка зависнет. При работе с очередями вызывайте tf.train.start_queue_runners до оценки любого tf.Tensor.

Печать тензоров

Для целей отладки вам может потребоваться напечатать значение tf.Tensor. Хотя tfdbg предоставляет поддержку сложной отладки, TensorFlow также имеет операцию для печати значения tf.Tensor напрямую.

Следующий способ редко используется для печати tf.Tensor:

t = <<какая-либо tensorflow операция>>
print(t)  
# Это напечатает символический тензор, когда граф построен.
# Этот тензор не имеет значения в этом контексте.

Этот код печатает tf.Tensor объект (который представляет отложенное вычисление), а не его значение. Вместо этого TensorFlow предоставляет tf.Print операцию, которая возвращает ее первый тензор аргумент не измененным, в то время как печатает набор тензоров, переданных вторым аргументом.

Для корректного использования tf.Print, ее возвращаемое значение должно быть использовано. Взлянем на следующий пример:

t = <<какая-либо tensorflow операция>>
tf.Print(t, [t])  # Это ничего не делает

# Здесь мы используем значение, возвращенное tf.Print
t = tf.Print(t, [t])  

# Теперь, когда result оценен, значение `t` будет напечатано.
result = t + 1  

Когда вы оцениваете result, вы оцениваете все от чего зависит result. Ввиду того, что result зависит от t, оценка t имеет сторонний эффект печати его значения (старое значение t), t оказывается напечатан.