Въведение в Tensorflow за Java

1. Общ преглед

TensorFlow е библиотека с отворен код за програмиране на потока от данни . Това първоначално е разработено от Google и е достъпно за широк спектър от платформи. Въпреки че TensorFlow може да работи на едно ядро, той може лесно да се възползва от множество налични CPU, GPU или TPU .

В този урок ще разгледаме основите на TensorFlow и как да го използваме в Java. Моля, имайте предвид, че TensorFlow Java API е експериментален API и следователно не е обект на гаранция за стабилност. По-късно в урока ще разгледаме възможните случаи на използване за използване на API на TensorFlow Java.

2. Основни положения

Изчислението TensorFlow основно се върти около две основни концепции: Графика и Сесия . Нека да ги прегледаме бързо, за да получим предисторията, необходима за преминаване през останалата част от урока.

2.1. Графика на TensorFlow

Като начало, нека разберем основните градивни елементи на програмите TensorFlow. Изчисленията са представени като графики в TensorFlow . Графиката обикновено е насочена ациклична графика на операции и данни, например:

Горната снимка представлява изчислителната графика за следното уравнение:

f(x, y) = z = a*x + b*y

Изчислителната графика на TensorFlow се състои от два елемента:

  1. Tensor: Това са основната единица данни в TensorFlow. Те са представени като ръбове в изчислителна графика, изобразяващи потока от данни през графиката. Тензорът може да има форма с произволен брой размери. Броят на измеренията в тензор обикновено се нарича негов ранг. Така че скаларът е тензор на ранг 0, вектор е тензор на ранг 1, матрицата е тензор на ранг 2 и т.н.
  2. Операция: Това са възлите в изчислителна графика. Те се отнасят до голямо разнообразие от изчисления, които могат да се случат на тензорите, подаващи се в операцията. Те често водят и до тензори, които произтичат от операцията в изчислителна графика.

2.2. Сесия TensorFlow

Сега графика TensorFlow е просто схема на изчислението, която всъщност не съдържа стойности. Такава графика трябва да се изпълнява вътре в така наречената сесия TensorFlow, за да бъдат оценени тензорите в графиката . Сесията може да отнеме куп тензори, за да се оцени от графика като входни параметри. След това се изпълнява назад в графиката и изпълнява всички възли, необходими за оценка на тези тензори.

С тези знания вече сме готови да вземем това и да го приложим към Java API!

3. Настройка на Maven

Ще създадем бърз проект на Maven, за да създадем и стартираме графика TensorFlow в Java. Просто се нуждаем от зависимостта на тензорния поток :

 org.tensorflow tensorflow 1.12.0 

4. Създаване на графика

Нека сега се опитаме да изградим графиката, която обсъдихме в предишния раздел, използвайки TensorFlow Java API. По-точно, за този урок ще използваме TensorFlow Java API за решаване на функцията, представена от следното уравнение:

z = 3*x + 2*y

Първата стъпка е да декларирате и инициализирате графика:

Graph graph = new Graph()

Сега трябва да дефинираме всички необходими операции. Не забравяйте, че операциите в TensorFlow консумират и произвеждат нула или повече тензори . Освен това всеки възел в графиката е операция, включваща константи и заместители. Това може да изглежда контраинтуитивно, но изтърпете за момент!

Класът Graph има обща функция, наречена opBuilder () за изграждане на всякакъв вид операции на TensorFlow.

4.1. Определяне на константи

Като начало, нека дефинираме постоянни операции в нашата графика по-горе. Имайте предвид, че една постоянна операция ще се нуждае от тензор за стойността си :

Operation a = graph.opBuilder("Const", "a") .setAttr("dtype", DataType.fromClass(Double.class)) .setAttr("value", Tensor.create(3.0, Double.class)) .build(); Operation b = graph.opBuilder("Const", "b") .setAttr("dtype", DataType.fromClass(Double.class)) .setAttr("value", Tensor.create(2.0, Double.class)) .build();

Тук дефинирахме операция от постоянен тип, захранване в тензора с двойни стойности 2.0 и 3.0. Може да изглежда малко поразително за начало, но засега е точно така в Java API. Тези конструкции са много по-кратки в езици като Python.

4.2. Определяне на заместители

Въпреки че трябва да предоставим стойности на нашите константи, заместителите не се нуждаят от стойност по време на дефиниция . Стойностите на заместителите трябва да бъдат предоставени, когато графиката се изпълнява в рамките на сесия. Ще разгледаме тази част по-късно в урока.

Засега нека да видим как можем да дефинираме нашите заместители:

Operation x = graph.opBuilder("Placeholder", "x") .setAttr("dtype", DataType.fromClass(Double.class)) .build(); Operation y = graph.opBuilder("Placeholder", "y") .setAttr("dtype", DataType.fromClass(Double.class)) .build();

Имайте предвид, че не трябваше да предоставяме каквато и да е стойност за нашите запазени места. Тези стойности ще се подават като тензори при изпълнение.

4.3. Дефиниране на функции

И накрая, трябва да дефинираме математическите операции на нашето уравнение, а именно умножение и събиране, за да получим резултата.

Това отново не са нищо друго, освен Operation s в TensorFlow и Graph.opBuilder () отново е удобен:

Operation ax = graph.opBuilder("Mul", "ax") .addInput(a.output(0)) .addInput(x.output(0)) .build(); Operation by = graph.opBuilder("Mul", "by") .addInput(b.output(0)) .addInput(y.output(0)) .build(); Operation z = graph.opBuilder("Add", "z") .addInput(ax.output(0)) .addInput(by.output(0)) .build();

Тук дефинирахме операция , две за умножаване на нашите входове и последната за обобщаване на междинните резултати. Имайте предвид, че операциите тук получават тензори, които не са нищо друго освен резултат от нашите по-ранни операции.

Моля, обърнете внимание, че получаваме изходния тензор от операцията, използвайки индекс „0“. Както обсъждахме по-рано, една операция може да доведе до един или повече тензори и следователно, докато извличаме манипулатор за него, трябва да споменем индекса. Тъй като знаем, че нашите операции връщат само един тензор , '0' работи добре!

5. Визуализиране на графиката

Трудно е да запазите раздела на графиката, тъй като тя нараства по размер. Това го прави важно да го визуализирате по някакъв начин . Винаги можем да създадем ръчна рисунка като малката графика, която създадохме преди, но не е практично за по-големи графики. TensorFlow предоставя помощна програма, наречена TensorBoard, за да улесни това .

За съжаление Java API няма възможност да генерира файл на събитие, който се консумира от TensorBoard. Но използвайки API в Python, можем да генерираме файл на събитие като:

writer = tf.summary.FileWriter('.') ...... writer.add_graph(tf.get_default_graph()) writer.flush()

Моля, не се притеснявайте, ако това няма смисъл в контекста на Java, това е добавено тук само за пълнота и не е необходимо да продължите останалата част от урока.

Вече можем да зареждаме и визуализираме файла на събитието в TensorBoard като:

tensorboard --logdir .

TensorBoard се предлага като част от инсталацията на TensorFlow.

Обърнете внимание на сходството между това и ръчно изчертаната графика по-рано!

6. Работа със сесия

We have now created a computational graph for our simple equation in TensorFlow Java API. But how do we run it? Before addressing that, let's see what is the state of Graph we have just created at this point. If we try to print the output of our final Operation “z”:

System.out.println(z.output(0));

This will result in something like:


    

This isn't what we expected! But if we recall what we discussed earlier, this actually makes sense. The Graph we have just defined has not been run yet, so the tensors therein do not actually hold any actual value. The output above just says that this will be a Tensor of type Double.

Let's now define a Session to run our Graph:

Session sess = new Session(graph)

Finally, we are now ready to run our Graph and get the output we have been expecting:

Tensor tensor = sess.runner().fetch("z") .feed("x", Tensor.create(3.0, Double.class)) .feed("y", Tensor.create(6.0, Double.class)) .run().get(0).expect(Double.class); System.out.println(tensor.doubleValue());

So what are we doing here? It should be fairly intuitive:

  • Get a Runner from the Session
  • Define the Operation to fetch by its name “z”
  • Feed in tensors for our placeholders “x” and “y”
  • Run the Graph in the Session

And now we see the scalar output:

21.0

This is what we expected, isn't it!

7. The Use Case for Java API

At this point, TensorFlow may sound like overkill for performing basic operations. But, of course, TensorFlow is meant to run graphs much much larger than this.

Additionally, the tensors it deals with in real-world models are much larger in size and rank. These are the actual machine learning models where TensorFlow finds its real use.

It's not difficult to see that working with the core API in TensorFlow can become very cumbersome as the size of the graph increases. To this end, TensorFlow provides high-level APIs like Keras to work with complex models. Unfortunately, there is little to no official support for Keras on Java just yet.

However, we can use Python to define and train complex models either directly in TensorFlow or using high-level APIs like Keras. Subsequently, we can export a trained model and use that in Java using the TensorFlow Java API.

Now, why would we want to do something like that? This is particularly useful for situations where we want to use machine learning enabled features in existing clients running on Java. For instance, recommending caption for user images on an Android device. Nevertheless, there are several instances where we are interested in the output of a machine learning model but do not necessarily want to create and train that model in Java.

This is where TensorFlow Java API finds the bulk of its use. We'll go through how this can be achieved in the next section.

8. Using Saved Models

We'll now understand how we can save a model in TensorFlow to the file system and load that back possibly in a completely different language and platform. TensorFlow provides APIs to generate model files in a language and platform neutral structure called Protocol Buffer.

8.1. Saving Models to the File System

We'll begin by defining the same graph we created earlier in Python and saving that to the file system.

Let's see we can do this in Python:

import tensorflow as tf graph = tf.Graph() builder = tf.saved_model.builder.SavedModelBuilder('./model') with graph.as_default(): a = tf.constant(2, name="a") b = tf.constant(3, name="b") x = tf.placeholder(tf.int32, name="x") y = tf.placeholder(tf.int32, name="y") z = tf.math.add(a*x, b*y, name="z") sess = tf.Session() sess.run(z, feed_dict = {x: 2, y: 3}) builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING]) builder.save()

As the focus of this tutorial in Java, let's not pay much attention to the details of this code in Python, except for the fact that it generates a file called “saved_model.pb”. Do note in passing the brevity in defining a similar graph compared to Java!

8.2. Loading Models from the File System

We'll now load “saved_model.pb” into Java. Java TensorFlow API has SavedModelBundle to work with saved models:

SavedModelBundle model = SavedModelBundle.load("./model", "serve"); Tensor tensor = model.session().runner().fetch("z") .feed("x", Tensor.create(3, Integer.class)) .feed("y", Tensor.create(3, Integer.class)) .run().get(0).expect(Integer.class); System.out.println(tensor.intValue());

It should by now be fairly intuitive to understand what the above code is doing. It simply loads the model graph from the protocol buffer and makes available the session therein. From there onward, we can pretty much do anything with this graph as we would have done for a locally-defined graph.

9. Conclusion

To sum up, in this tutorial we went through the basic concepts related to the TensorFlow computational graph. We saw how to use the TensorFlow Java API to create and run such a graph. Then, we talked about the use cases for the Java API with respect to TensorFlow.

In the process, we also understood how to visualize the graph using TensorBoard, and save and reload a model using Protocol Buffer.

Както винаги, кодът за примерите е достъпен в GitHub.