В предыдущей части этого руководства я немного познакомил с Ruby TensorFlow и показал несколько примеров использования Ruby Tensorflow.

В этой части я остановлюсь на простых и полезных идеях, которые помогут разработчикам понять, как google protobuf используется в Ruby Tensorflow. Несмотря на то, что основная цель сообщения в блоге - объяснить Ruby API, я уверен, что разработчики, специализирующиеся на разных языках, могут извлечь большую пользу из представленных здесь идей.

Google Protobuf

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

Он помогает определять структуры данных в текстовых файлах, а инструменты protobuf генерируют классы на Ruby, C, Python и других языках, которые могут загружать, сохранять и получать доступ к данным в удобной форме. Google разработал Protocol Buffers для внутреннего использования и предоставил генератор кода для нескольких языков. В этом блоге я сосредоточусь на ruby protobuf, поэтому для начала стоит познакомиться с тем, как они работают.

GraphDef

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

Класс GraphDef - это объект, созданный библиотекой ProtoBuf из определения в tensorflow / core / framework / graph.proto. Инструменты protobuf анализируют этот текстовый файл и генерируют код для загрузки, хранения и управления определениями графов. Если вы видите автономный файл TensorFlow, представляющий модель, скорее всего, он содержит сериализованную версию одного из этих объектов GraphDef, сохраненную кодом protobuf.

Этот сгенерированный код используется для сохранения и загрузки файлов GraphDef с диска. Хороший пример для изучения, когда мы углубимся в это, - graph_metrics.py. Этот сценарий Python использует сохраненное определение графика и анализирует модель для оценки производительности и статистики ресурсов. Код, который фактически загружает модель, выглядит так:

require 'tensorflow'
graph_def = Tensorflow::GraphDef.new

Эта строка создает пустой объект GraphDef, класс, созданный из текстового определения в graph.proto. Это объект, который мы собираемся заполнить данными из нашего файла в irb.

reader = File.read('graph.pb')
graph_def = Tensorflow::GraphDef.parse(reader)
graph_def.node[0].name 
=> "input1"
graph_def.node[1].name
=> "input2"
graph_def.node[2]
 => <Tensorflow::NodeDef: name: "output", op: "Add", input: ["input1", "input2"], device: "", attr: {"T"=><Tensorflow::AttrValue: list: nil, s: "", i: 0, f: 0.0, b: false, type: :DT_INT64, shape: nil, tensor: nil, placeholder: "", func: nil>}>

Узлы

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

Каждый узел - это объект NodeDef, также определенный в graph.proto. Это фундаментальные строительные блоки графов TensorFlow, каждый из которых определяет одну операцию вместе со своими входными соединениями. Вот члены NodeDef и их значение.

Имя: каждый узел должен иметь уникальный идентификатор, который не используется другими узлами на графике. Имя используется при определении соединений между узлами, а также при настройке входов и выходов для всего графа при его запуске.

op: Это определяет, какую операцию выполнять, например Добавить, MatMul или Conv2D. Когда граф запускается, это имя операции ищется в реестре, чтобы найти реализацию. Чтобы лучше понять работу Tensorflow, взгляните на ops.pbtxt.

input: список строк, каждая из которых является именем другого узла, за которым может следовать двоеточие и номер порта вывода.

attr: Это хранилище "ключ-значение", содержащее все атрибуты узла. Это постоянные свойства узлов, вещи, которые не меняются во время выполнения, такие как размер фильтров для сверток или значения постоянных операций. Поскольку может быть очень много разных типов значений атрибутов, от строк до целых чисел и массивов тензорных значений, существует отдельный файл protobuf, определяющий структуру данных, которая их хранит, в tensorflow / core / framework / attr_value.proto.

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

Текстовый или двоичный?

На самом деле существует два разных формата, в которых может быть сохранен ProtoBuf. Текстовый формат - это удобочитаемая форма, которая удобна для отладки и редактирования, но может увеличиваться, если в нем хранятся числовые данные, такие как веса. Вы можете увидеть небольшой пример этого в graph_run_run2.pbtxt.

Файлы двоичного формата намного меньше, чем их текстовые эквиваленты, хотя для нас они не так удобочитаемы. В этом сценарии мы просим пользователя указать флаг, указывающий, является ли входной файл двоичным или текстовым, чтобы мы знали, какую функцию нужно вызывать. Вы можете найти пример большого двоичного файла внутри архива inception_dec_2015.zip, как tensorflow_inception_graph.pb (этот файл используется в учебнике по распознаванию изображений). В ruby ​​protobuf поддерживается только двоичный формат, что немного усложняет его использование разработчиками, но я добавил простой способ достичь обратного и обратного преобразования из двоичного в удобочитаемый формат, используя этот файл.

Пример

Теперь давайте рассмотрим простой пример, чтобы понять, как использовать все идеи, которые я упомянул выше. Но прежде чем продолжить, у вас должны быть установлены полностью работающие tensorflow.rb и tensorflow.

А теперь начнем с питона

Это создает простой файл .proto

Все файлы, необходимые для использования Tensorflow с Google Protobuf, находятся здесь. Теперь я попытаюсь создать тот же файл на Ruby, но мы сделаем это вручную.

Вы можете увидеть, как я вручную определил каждый узел и атрибут здесь. Единственная проблема с этим файлом заключается в том, что он создает protobuf в двоичном формате, поэтому вы не можете его прочитать. Следовательно, нам нужно преобразовать файл в удобочитаемый формат, для которого вы можете использовать этот файл.

Теперь вы можете посмотреть тот же файл, созданный Ruby в удобочитаемом формате.

Я понимаю, что этот пример на первый взгляд может показаться тривиальным, но он подчеркивает, как легко можно использовать Google protobuf. Список операций четко определен здесь: ops.pbtxt, чтобы вы могли его прочитать и лучше понять. Я призываю всех поиграть с ruby ​​protobuf, а также еще раз взглянуть. Чтобы упростить работу с операциями, я сделал общую функцию, которая помогает определять операции, но очевидно, что все можно улучшить.

Чтобы дать вам лучшее понимание, я собираюсь предложить простой способ создания множества графиков в Ruby и их понимания? Перейдите к спецификациям (например, math spec) и сразу после session.extend_graph (graph) добавьте еще одну строку

File.open(‘graph.pb’,’w’) {|file| file.write(graph.graph_def_raw)} 

И снова запустите спецификации. Это сохранит определение графика в файле graph.pb, а затем вы сможете преобразовать его с помощью файла pb_to_pbtxt (python).

На этом я отмечу завершение этого блога. Надеюсь, вы найдете это интересным и полезным. Если вы обнаружите что-то противоречащее, не стесняйтесь комментировать ниже.

Благодарности

Особая благодарность Джейсону Той (основателю somatic), Soon Hin Khor (доктор философии Токийского университета) и Самиру Дешмуку (основателю Daru и участнику Sciruby) за то, что они наставники для этого проекта. Они очень меня поддерживали во всем, и я им очень благодарен.

Список авторов

  1. Кристиан Хансен
  2. Джеффри Литт
  3. Себастьян Дойч
  4. Виктор Шепелев