Desenvolvimento

20 abr, 2017

Aprendendo Tensorflow com Crystal

Publicidade

Neste artigo, eu gostaria de descrever:

  • Como construir uma biblioteca Tensorflow;
  • Como gerar ligações para o Crystal;
  • Como utilizar uma biblioteca Tensorflow em Crystal;
  • Compartilhar os resultados do meu trabalho: tensorflow.cr.

Links úteis

Instale e construa Tensorflow

Para instalar e compilar o Tensorflow, precisaremos instalar estes três itens:

  • bazel
  • Python
  • numpy (pacote Python)

Primeiro, precisamos clonar o repositório Tensorflow inteiro

$ git clone https://github.com/tensorflow/tensorflow

Então, precisamos mudar o diretório e executar a configuração:

$ cd tensorflow
$ ./configure

Precisamos responder algumas perguntas sobre o Python e as funcionalidades que nossa biblioteca suportará. Podemos somente apertar “Enter”. Após isso, o bazel irá baixar todas as dependências e configurar tudo.

Precisamos construir apenas uma biblioteca. Vamos fazer isso.

$ cd tensorflow # yes! tensorflow/tensorflow
$ bazel build :libtensorflow.so

Conseguimos! O último passo, instalar!

cp ../bazel-bin/tensorflow/libtensorflow.so /usr/local/lib/ libtensorflow.so

Biblioteca instalada.

Gerando ligações

Para trabalhar com a biblioteca Tensorflow, precisamos gerar ligações. O Crystal tem uma aplicação para isso. Tudo o que você precisa é clonar a biblioteca crystal_lab e adicionar o arquivo lib_tensorflow.cr ao diretório de exemplos. Lembre-se de substituir {tensorflow_dir} pelo diretório no qual você clonou o Tensorflow.

@[Include(
  "tensorflow/c/c_api.h",
  flags: "
    -I/{tensorflow_dir}/tensorflow/ 
    -I/{tensorflow_dir}/tensorflow/bazel-genfiles 
    -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1
  ",
  prefix: %w(TF_ Tf)
)]
@[Link("tensorflow")]
lib LibTensorFlow
end

Agora execute o gerador:

$ crystal src/main.cr -- examples/lib_tensorflow.cr


Feito. Temos as ligações. Podemos copiar a saída para nosso projeto Crystal e começar a brincar com o Tensorflow. Vamos nomeá-lo como lib_tensorflow.cr.

Utilizando o Tensorflow a partir do Crystal

Devido ao fato de a biblioteca Tensorflow não fornecer maneiras fáceis de construir os gráficos, precisaremos utilizar Python.

import tensorflow as tf

with tf.Session() as sess:
    a = tf.Variable(5.0, name='a')
    b = tf.Variable(6.0, name='b')
    c = tf.multiply(a, b, name='c')

sess.run(tf.global_variables_initializer())

tf.train.write_graph(sess.graph_def, '.', 'graph.pb', as_text=False)

Esse script simples gerará um gráfico e o salvará no arquivo graph.pb.

Geramos o gráfico que precisa de duas variáveis para entrar e retornar um número. É a multiplicação das entradas. Também as nomeamos como “a”, “b”, “c”.

Agora, vamos para o Crystal. Precisamos solicitar nossas ligações primeiro.

require 'lib_tensorflow'

Agora estamos prontos! Precisamos inicializar uma session.

opts = LibTensorflow.new_session_options
status = LibTensorflow.new_status
graph = LibTensorflow.new_graph
session = LibTensorflow.new_session(graph, opts, status)

Como você pode ver, precisamos de um gráfico, opções e estados. Tudo isso pode ser feito utilizando as funções das ligações. Também precisamos verificar se a criação da sessão foi bem sucedida.

puts LibTensorflow.get_code(status)

Tudo o que precisamos fazer é pegar um estado e verificar seu código. Estamos utilizando a função das ligações. A linha acima deve exibir “OK” se tudo estiver bem. Se não estiver, podemos exibir uma mensagem de erro mais detalhada como esta:

puts String.new(LibTensorflow.message(status))

Se temos uma sessão, precisamos carregar um gráfico nela.

file = File.read("./graph.pb")
buffer = LibTensorflow.new_buffer_from_string(file, file.size)
import_opts = LibTensorflow.new_import_graph_def_options
LibTensorflow.graph_import_graph_def(graph, buffer, import_opts, status)

Tudo o que precisamos é ler os dados do arquivo, criar um buffer com uma função das ligações, e passar o buffer para uma função que importará nossos dados.

Novamente, precisamos lembrar de verificar se está tudo bem:

puts LibTensorflow.get_code(status)

Gráfico carregado. Agora precisamos criar os tensors. Eles tratam a entrada e saída de dados. Os tensors tratarão os dados.

Mais uma coisa: o tensor de inicialização precisará de uma função de desalocação. Criamos somente uma função vazia. Criar a função apropriada está além do escopo deste artigo.

deloc = ->(a : Pointer(Void), b : UInt64, c: Pointer(Void)) {}

Agora focamos nas entradas. Precisamos de dois valores. Existem tensors para isso.

a_dims = [] of Int64
a_data = [3.0_f32] of Float32

a_tensor = LibTensorflow.new_tensor(
  LibTensorflow::Datatype::Float,
  a_dims, a_dims.size,
  a_data, a_data.size,
  deloc, nil)

b_dims = [] of Int64
b_data = [5.0_f32] of Float32

b_tensor = LibTensorflow.new_tensor(
  LibTensorflow::Datatype::Float,
  b_dims, b_dims.size,
  b_data, b_data.size,
  deloc, nil)

E um vazio para saída.

c_dims = [] of Int64
c_data = [] of Float32

c_tensor = LibTensorflow.new_tensor(
  LibTensorflow::Datatype::Float,
  c_dims, c_dims.size,
  c_data, c_data.size,
  deloc, nil)

Outra coisa são as operações. As operações pegam os dados dos tensors e escrevem dados nos tensors. O tensor de variáveis também está em operação para que nós possamos ler os dados dele. Precisaremos de três operações. Elas estão definidas no gráfico. Lembra de “a”, “b”, “c”?

i1 = LibTensorflow::Output.new
i1.oper = LibTensorflow.graph_operation_by_name(graph, "a")
i1.index = 0

i2 = LibTensorflow::Output.new
i2.oper = LibTensorflow.graph_operation_by_name(graph, "b")
i2.index = 0

o1 = LibTensorflow::Output.new
o1.oper = LibTensorflow.graph_operation_by_name(graph, "c")
o1.index = 0

Para tornar o código mais legível, criamos algumas variáveis para entradas e saídas:

inputs = [i1, i2] of LibTensorflow::Output
input_values = [a_tensor, b_tensor] of LibTensorflow::X_Tensor

outputs = [o1] of LibTensorflow::Output
outputs_values = [c_tensor] of LibTensorflow::X_Tensor

Agora que quase temos todos os dados necessários para executar nosso gráfico, vamos fazer isto:

optss = LibTensorflow.new_buffer
meta = LibTensorflow.new_buffer

target = [] of LibTensorflow::X_Operation

LibTensorflow.session_run(session, nil,
                    inputs, input_values, inputs.size,
                    outputs, outputs_values, outputs.size,
                    target, target.size,
                    nil, status)

Isso! Vamos verificar o estado:

 

puts LibTensorflow.get_code(status)

Onde está nosso retorno? Claro, vamos pegá-lo do tensor e exibi-lo:

o = outputs_values[0]
out_data = LibTensorflow.tensor_data(o)
out_value = out_data.as(Float32*)

puts out_value.value

Como as saídas dos tensors podem ser diferentes, precisamos colocar ele para o valor apropriado e seguir um ponteiro.

Agora podemos executar nosso programa.

$ crystal src/ok.cr

Depois de alguns “ok”, recebemos “15”, que é o correto.

Primeiro sucesso!

O que vem depois?

Claro, esse não é o fim! Eu quero aprender mais!

Criei o projeto tensorflow.cr. Por enquanto, são simples ligações, mas estou trabalhando em um bonita DSL Crystal para a biblioteca e criar a habilidade de geração de gráficos. É fascinante a maneira como o aprendizado do Tensorflow funciona. Se você está interessado em me ajudar com esse projeto, me avise. Eu apreciarei.

Parcialmente, existe também o repositório tensorflow.cr_examples. Existe um que eu descrevi neste artigo, e também outros que utilizarão o DSL Crystal (essa parte pode não funcionar durante o processo de desenvolvimento).

Espero que tenha sido útil para você. Obrigado por ler!

***

Michał Kalbarczyk faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://blog.fazibear.me/learning-tensorflow-the-other-way-1f0e1764e0a7.