TensorFlow Simple Example

[English translation may be some day]

Представляю вашему вниманию простейший пример работы с фреймворком для работы с Deep Learning – TensorFlow. В этом примере мы научим нейросеть определять положительние, отрицательные числа и ноль. Установку TensorFlow и CUDA я поручаю вам, эта задачка действительно не из легких)

Для решения задач классификации используются классификаторы. TensorFlow имеет несколько готовых высокоуровневых классификаторов, которые требуют минимальной конфигурации для работы. Сначала мы потренируем DNNClassifier с помощью датасета с положительными, отрицательными числами и нулем – с корректными “лейблами”. На человеческом уровне датасет представляет из себя набор чисел с результатом классификации (лейблами):

10 – положительное
-22 – отрицательное
0 – ноль
42 – положительное
… другие числа с классификацией

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

import tensorflow
import itertools
import random

from time import time

class ClassifiedNumber:
    
    __number = 0
    __classifiedAs = 3
    
    def __init__(self, number):
        
        self.__number = number
        
        if number == 0:
            self.__classifiedAs = 0 # zero
            
        elif number > 0:
            self.__classifiedAs = 1 # positive
            
        elif number < 0:
            self.__classifiedAs = 2 # negative
            
    def number(self):
        return self.__number
    
    def classifiedAs(self):
        return self.__classifiedAs
    
def classifiedAsString(classifiedAs):
    
    if classifiedAs == 0:
        return "Zero"
    
    elif classifiedAs == 1:
        return "Positive"
    
    elif classifiedAs == 2:
        return "Negative"

def trainDatasetFunction():
    
    trainNumbers = []
    trainNumberLabels = []
    
    for i in range(-1000, 1001):    
        number = ClassifiedNumber(i)
        trainNumbers.append(number.number())
        trainNumberLabels.append(number.classifiedAs())
    
    return ( {"number" : trainNumbers } , trainNumberLabels )

def inputDatasetFunction():
    
    global randomSeed
    random.seed(randomSeed) # to get same result
    
    numbers = []
    
    for i in range(0, 4):
        numbers.append(random.randint(-9999999, 9999999))
    
    return {"number" : numbers }
    
def main():
    
    print("TensorFlow Positive-Negative-Zero numbers classifier test by demensdeum 2017 (demensdeum@gmail.com)")
    
    maximalClassesCount = len(set(trainDatasetFunction()[1])) + 1
    
    numberFeature = tensorflow.feature_column.numeric_column("number")
    classifier = tensorflow.estimator.DNNClassifier(feature_columns = [numberFeature], hidden_units = [10, 20, 10], n_classes = maximalClassesCount)
    generator = classifier.train(input_fn = trainDatasetFunction, steps = 1000).predict(input_fn = inputDatasetFunction)
    
    inputDataset = inputDatasetFunction()
    
    results = list(itertools.islice(generator, len(inputDatasetFunction()["number"])))
    
    i = 0
    for result in results:
        print("number: %d classified as %s" % (inputDataset["number"][i], classifiedAsString(result["class_ids"][0])))
        i += 1

randomSeed = time()

main()

Все начинается в методе main(), мы задаем числовую колонку с которой будет работать классификатор – tensorflow.feature_column.numeric_column(“number”) далее задаются параметры классификатора. Описывать текущие аргументы инициализации бесполезно, так как API меняется каждый день, и обязательно нужно смотреть документацию именно установленной версии TensorFlow, не полагаться на устаревшие мануалы.

Далее запускается обучение с указанием на функцию которая возвращает датасет из чисел от -1000 до 1000 (trainDatasetFunction), с правильной классификацией этих чисел по признаку положительного, отрицательного либо нуля. Следом подаем на вход числа которых не было в обучающем датасете – случайные от -9999999 до 9999999 (inputDatasetFunction) для их классификации.

В финале запускаем итерации по количеству входных данных (itertools.islice) печатаем результат, запускаем и удивляемся:

number: 4063470 classified as Positive
number: 6006715 classified as Positive
number: -5367127 classified as Negative
number: -7834276 classified as Negative

iT’S ALIVE

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

GitLab:
https://gitlab.com/demensdeum/MachineLearning

Ссылки:
https://developers.googleblog.com/2017/09/introducing-tensorflow-datasets.html
https://www.tensorflow.org/versions/master/api_docs/python/tf/estimator/DNNClassifier

WebGL + SDL + Emscripten

[Translation may be some day]

В итоге я портировал Мику на WebGL, с помощью SDL 1 и Emscripten.

http://demensdeum.com/demos/mikuWebGL/

Дальше я опишу что нужно было изменить в коде чтобы сборка в JavaScript завершилась успешно.

  1. Использовать SDL 1 вместо SDL 2. На данный момент существует порт SDL 2 для emscripten, однако использовать встроенный в emscripten SDL 1 я посчитал более целесобразным. Инициализация контекста происходит не в окне, а с помощью SDL_SetVideoMode и флага SDL_OPENGL. Отрисовка буфера производится командой SDL_GL_SwapBuffers()
  2. Из-за особенностей выполения циклов в JavaScript – рендеринг вынесен в отдельную функцию и его периодический вызов проставляется с помощью функции emscripten_set_main_loop
  3. Также сборку нужно осуществлять с ключом “-s FULL_ES2=1
  4. Пришлось отказаться от библиотеки assimp, от загрузки модели из файловой системы, от загрузки текстуры с диска. Все необходимые буферы были прогружены на деcктоп версии, и прокинуты в c-header файл для сборки с помощью emscripten.

Код:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/9-sdl-gles-obj-textured-assimp-miku-webgl/mikuWebGL

Статьи:
http://blog.scottlogic.com/2014/03/12/native-code-emscripten-webgl-simmer-gently.html
https://kripken.github.io/emscripten-site/docs/porting/multimedia_and_graphics/OpenGL-support.html

Модель:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

FSGL Miku

[Translation may be some day]

Результат работы над библиотекой FSGL с OpenGL ES и код:

Дальше я опишу как это все программировалось, решались разные интересные проблемы.

Сначала мы проинициализируем OpenGL ES контекст, как это делается я писал в прошлой заметке. Дальше будет рассматриваться только отрисовка, краткое описание кода.

Матрица следит за тобой

Данная фигура Мику на видео состоит из треугольников. Чтобы нарисовать треугольник в OpenGL, нужно задать три точки к координатами x, y, z. в 2D координатах контекста OpenGL.
Так как нам нужно отрисовать фигуру содержащую 3D координаты, нам нужно использовать матрицу проекции (projection). Также нам нужно крутить, увеличивать, или что угодно делать с моделью – для этого используется матрица модели (model). Понятия камеры в OpenGL нет, на самом деле объекты крутятся, поворачиваются вокруг статичной камеры. Для этого используется матрица вида (view).

Для упрощения реализации OpenGL ES – в нем данные матрицы отсутствуют. Вы можете использовать библиотеки которые добавляют отсутствующий функционал, например GLM.

Шейдеры

Для того чтобы позволить разработчику рисовать что угодно, и как угодно, в OpenGL ES нужно обязательно реализовать вертексные и фрагментные шейдеры. Вертексный шейдер должен получить на вход координаты отрисовки, произвести преобразования с помощью матриц, и передать координаты в gl_Position. Фрагментный или пиксельный шейдер – уже отрисовывает цвет/текстуру, применяет наложение и пр.

Шейдеры я писал на языке GLSL. В моей текущей реализации шейдеры встроены прямо в основной код приложения как C-строки.

Буферы

Вертексный буфер содержит координаты вершин (вертексов), в данный буфер также попадают координаты для текстурирования и прочие необходимые для шейдеров данные. После генерации вертексного буфера, нужно забиндить указатель на данные для вертексного шейдера. Это делается командой glVertexAttribPointer, там необходимо указать количество элементов, указатель на начало данных и размер шага, который будет использоваться для прохода по буферу. В моей реализации сделан биндинг координат вершин и текстурные координаты для пиксельного шейдера. Однако стоит сказать что передача данных (текстурных координат) во фрагментный шейдер осуществляется через вертексный шейдер. Для этого координаты объявлены с помощью varying.

Для того чтобы OpenGL знал в каком порядке отрисовывать точки для треугольников – вам понадобится индексный буфер (index). Индексный буфер содержит номер вертекса в массиве, с помощью трех таких индексов получается треугольник.

Текстуры

Для начала нужно прогрузить/сгенерировать текстуру для OpenGL. Для этого я использовал SDL_LoadBMP, загрузка текстуры происходит из bmp файла. Однако стоит отметить что годятся только 24-битные BMP, также цвета в них хранятся не в привычном порядке RGB, а в BGR. Тоесть после прогрузки нужно осуществить замену красного канала на синий.
Текстурные координаты задаются в формате UV, тоесть необходимо передать всего две координаты. Вывод текстуры осуществляется во фрагментном шейдере. Для этого необходимо осуществить биндинг текстуры во фрагментный шейдер.

Ничего лишнего

Так как, по нашему указанию, OpenGL рисует 3D через 2D – то для реализации глубины, и выборки невидимых треугольников – нужно использовать выборку (culling) и буфер глубины (Z-Buffer). В моей реализации удалось избежать ручной генерации буфера глубины, с помощью двух команд glEnable(GL_DEPTH_TEST); и выборки glEnable(GL_CULL_FACE);
Также обязательно проверьте что near plane для матрицы проекции больше нуля, т.к. проверка глубины с нулевым near plane работать не будет.

Рендеринг

Чтобы заполнить вертексный буфер, индексный буфер чем-то осознанным, например моделью Мику, нужно осуществить загрузку данной модели. Для этого я использовал библиотеку assimp. Мику была помещена в файл формата Wavefront OBJ, прогружена с помощью assimp, и реализована конвертация данных из assimp в вертексный, индексный буферы.

Рендеринг проходит в несколько этапов:

  1. Поворот Мику с помощью поворота матрицы модели
  2. Очистка экрана и буфера глубины
  3. Отрисовка треугольников с помощью команды glDrawElements.

Следующий этап – реализация рендеринга в WebGL с помощью Emscripten.

Исходный код:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/8-sdl-gles-obj-textured-assimp-miku
Модель:
https://sketchfab.com/models/7310aaeb8370428e966bdcff414273e7

OGLES – Quick Overview

I just drew a red monkey head in 3D, and here is my overview of OpenGL ES.

What if I told you that modern OpenGL can’t draw 3D? Yeah, you need to make almost everything by yourself.
To present something in OpenGL context, you need to chose drawing mode – (triangles, points, etc.), provide vertex buffer, code vertex shader, provide matrices (projection, view, model), bind all values for vertex shader, code pixel shader, and call OpenGL specific drawing method. Easy isn’t it?


Ok so what the hell is vertex buffer? It’s just list with coordinates (x, y, z) to draw.
Vertex shader is the little program that tells GPU coordinates to draw.
Pixel shader is the little program that draws pixels of some color, textures, etc.
Matrices translates your 3D coordinates into 2D screen surface coordinates, that OpenGL can draw.

In upcoming articles we will go deeper and I will show code and results.

SDL2 – OpenGL ES

I love Panda3D game engine. But right now this engine is very hard to compile and debug on Microsoft Windows operation system. So as I said some time ago, I begin to develop my own graphics library. Right now it’s based on OpenGL ES and SDL2.
In this article I am going to tell how to initialize OpenGL ES context and how SDL2 helps in this task. We are going to show nothing.

King Nothing

First of all you need to install OpenGL ES3 – GLES 3 libraries. This operation is platform dependant, for Ubuntu Linux you can just type sudo apt-get install libgles2-mesa-dev. To work with OpenGL you need to initialize OpenGL context. There is many ways to do that, by using one of libraries – SDL2, GLFW, GLFM etc. Actually there is no one right way to initialize OpenGL context, but I chose SDL2 because it’s cross-platform solution, code will look same for Windows/*nix/HTML5/iOS/Android/etc.

To install sdl2 on Ubuntu use this command sudo apt-get install libsdl2-dev

So here is OpenGL context initialization code with SDL2:

    SDL_Window *window = SDL_CreateWindow(
            "SDL2 - OGLES",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            640,
            480,
            SDL_WINDOW_OPENGL
            );
	    

    SDL_GLContext glContext = SDL_GL_CreateContext(window);

After that, you can use any OpenGL calls in that context.

Here is example code for this article:
https://github.com/demensdeum/OpenGLES3-Experiments/tree/master/3sdl-gles
https://github.com/demensdeum/OpenGLES3-Experiments/blob/master/3sdl-gles/sdlgles.cpp

You can build and test it with command cmake . && make && ./SDLGles

Russian Quantum Hack and Number Generator

[Translation may be, some day]

Эта заметка увеличит длину вашего резюме на 5 см!

Без лишних слов о крутости квантовых компьютеров и всего такого, сегодня я покажу как сделать генератор чисел на реальном квантовом процессоре IBM.
Для этого мы будем использовать всего один кубит, фреймворк для разработки квантового ПО для python – ProjectQ, и 16 кубитовый процессор от IBM, онлайн доступ к которому открыт любому желающему по программе IBM Quantum Experience.

Установка ProjectQ

Для начала у вас должен быть Linux, Python и pip. Какие либо инструкции по установке этих базовых вещей приводить бесполезно, т.к. в любом случае инструкции устареют через неделю, поэтому просто найдите гайд по установке на официальном сайте. Далее устанавливаем ProjectQ, гайд по установке приведен в документации. На данный момент все свелось к установке пакета ProjectQ через pip, одной командой: python -m pip install –user projectq

Ставим кубит в суперпозицию

Создаем файл quantumNumberGenerator.py и берем пример генератора бинарного числа из документации ProjectQ, просто добавляем в него цикл на 32 шага, собираем бинарную строку и переводим в 32-битное число:

import projectq.setups.ibm
from projectq.ops import H, Measure
from projectq import MainEngine
from projectq.backends import IBMBackend

binaryString = ""

eng = MainEngine()

for i in range(1, 33):

 qubit = eng.allocate_qubit()

 H | qubit

 Measure | qubit

 eng.flush()

 binaryString = binaryString + str(int(qubit))

 print("Step " + str(i))

number = int(binaryString, 2)

print("\n--- Quantum 32-Bit Number Generator by demensdeum@gmail.com (2017) ---\n")
print("Binary: " + binaryString)
print("Number: " + str(number))
print("\n---")

Запускаем и получаем число из квантового симулятора с помощью команды python quantumNumberGenerator.py

Незнаю как вы, но я получил вывод и число 3974719468:

--- Quantum 32-Bit Number Generator by demensdeum@gmail.com (2017) ---

Binary: 11101100111010010110011111101100
Number: 3974719468

---

Хорошо, теперь мы запустим наш генератор на реальном квантовом процессоре IBM.

Хакаем IBM

Проходим регистрацию на сайте IBM Quantum Experience, подтверждаем email, в итоге должен остаться email и пароль для доступа.
Далее включаем айбиэмовский движок, меняем строку eng = MainEngine() -> eng = MainEngine(IBMBackend())
В теории после этого вы запускаете код снова и теперь он работает на реальном квантовом процессоре, используя один кубит. Однако после запуска вам придется 32 раза набрать свой email и пароль при каждой аллокации реального кубита. Обойти это можно прописав свой email и пароль прямо в библиотеки ProjectQ.

Заходим в папку где лежит фреймворк ProjectQ, ищем файл с помощью grep по строке IBM QE user (e-mail).
В итоге я исправил строки в файле projectq/backends/_ibm/_ibm_http_client.py:

email = input_fun('IBM QE user (e-mail) > ') -> email = "quantumPsycho@aport.ru"

password = getpass.getpass(prompt='IBM QE password > ') -> password = "ilovequbitsandicannotlie"

Напишите свой email и password со-но.

После этого IBM будет отправлять результаты работы с кубитом онлайн прямо в ваш скрипт, процесс генерации занимает около 20 секунд.

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

Статья на похожую тему:
Introducing the world’s first game for a quantum computer

Bright Future – WebAssembly

[Translation may be some day]

Несколько лет назад я прочитал о начале проекта WebAssembly (wasm), идея разработчиков звучала примерно так – разработать байт-код для запуска приложений на разных платформах, архитектурах, акцент делается на запуске приложений в браузере.

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

Здесь я опишу как мне это удалось.

Компиляция WebAssembly с помощью Emscripten

Установка Emscripten описана в прошлой заметке. Допустим у вас уже есть проект который корректно собирается из C++ в javascript, для сборки в wasm вам нужно добавить ключи:

-s WASM=1 -s ""BINARYEN_METHOD='native-wasm'""

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

Включение WebAssembly

На данный момент идет процесс активной разработки, поэтому в стабильных версиях браузеров поддержки последней версии wasm нет. Для запуска wasm кода я использовал браузер Firefox Nightly для Ubuntu. Для включения wasm, нужно зайти в about:config и включить его:

Также заявлена поддержка в других браузерах (Chrome).

WebAssembly в действии

Проверить как работает WebAssembly вы можете открыв страницу игры Tanks движка Unity. Есть подозрение что там используется fallback на javascript, т.к. работает даже в обычном браузере.

Также можете попробовать запустить тестовую версию моей игры Bad Robots для wasm.

Если ваш браузер показывает черный экран и ошибку “Exception thrown, see JavaScript console” и в отладочной консоли текст “uncaught exception: no binaryen method succeeded. consider enabling more options, like interpreting, if you want that: https://github.com/kripken/emscripten/wiki/WebAssembly#binaryen-methods“, тогда устанавливайте лучший браузер Firefox Nightly и включайте WebAssembly по инструкции выше.

Всем удачной компиляции.

Porting SDL C++ Game to HTML5 (Emscripten)

[Translation may be some day]

За последний год я написал простейший движок Flame Steel Engine и набор классов для игровой разработки Flame Steel Engine Game Toolkit. В данной статье я опишу как производил портирование движка и SDL игры Bad Robots на HTML 5, с использованием компилятора Emscripten.

Установка Hello World – Emscripten

Для начала нужно установить Emscripten. Простейшим вариантом оказалось использование скрипта emsdk для Linux. На официальном сайте данный тип установки называется как “Portable Emscripten SDK for Linux and OS X“. Внутри архива есть инструкция по установке с использованием скрипта. Я производил установку в директорию ~/emsdk/emsdk_portable.

После установки emscripten нужно проверить корректность работы компилятора, для этого создаем простейший hello_world.cpp и собираем его в hello_world.html с помощью команд:

source ~/emsdk/emsdk_portable/emsdk_env.sh
emcc hello_world.cpp -o hello_world.html

После компиляции в папке появится hello_world.html и вспомогательные файлы, откройте его в лучшем браузере Firefox, проверьте что все работает корректно.

Портирование кода игры

В javascript нежелательно вызывать бесконечный цикл – это приводит к зависанию браузера. На данный момент корректная стратегия – запрашивать один шаг цикла у браузера с помощью вызова window.requestAnimationFrame(callback)

В Emscripten данное обстоятельство решено с помощью вызова:

emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop);

Таким образом, нужно изменить код игры для корректного вызова метода emscripten. Для этого я сделал глобальный метод GLOBAL_fsegt_emscripten_gameLoop, в котором вызываю шаг цикла игрового контроллера. Главный игровой контроллер также вынесен в глобальную видимость:

#ifdef __EMSCRIPTEN__

void GLOBAL_fsegt_emscripten_gameLoop() {

GLOBAL_fsegt_emscripten_gameController->gameLoop();

}
#endif

Также для обработки специфических для Emscripten моментов, нужно использовать макрос __EMSCRIPTEN__.

Ресурсы и оптимизация

Emscripten поддерживает ресурсы и сборку с оптимизацией.

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

emcc <файлы для сборки> –use-preload-plugins –preload-file data

Флаг –use-preload-plugins включает красивый прелоадер в углу экрана, –preload-file добавляет указанный ресурс в файл <имя проекта>.data
Код постоянно останавливался с ошибками доступа к ресурсам, пока я не включил оба этих флага. Также стоит заметить что для корректного доступа к ресурсам, желательно запускать игру на https (возможно и http) сервере, или отключить защиту локального доступа к файлам в вашем браузере.

Для включения оптимизации добавьте флаги:

-s TOTAL_MEMORY=67108864 -O3 -ffast-math

TOTAL_MEMORY – оперативная память в байтах(?) необходимая для корректной работы игры. Вы можете использовать флаг для динамического выделения памяти, но тогда часть оптимизаций работать не будет.

Производительность

Код javascript из C++ работает гораздо медленнее, даже со включенными оптимизациями. Поэтому если ваша цель это разработка для HTML5, то приготовьтесь к ручной оптимизации алгоритмов игры, паралелльному тестированию, также к написанию javascript кода вручную в особо узких местах. Для написания javascript кода используется макрос EM_ASM. Во время реализации рейкастера на emscripten, мне удалось добиться повышения fps с 2-4 до 30 с помощью прямого использования методов canvas.drawImage, в обход обертки SDL->Canvas, что почти приравнялось к написанию всего на javascript.

Поддержка SDL

На данный момент почти не работает SDL_TTF, поэтому отрисовка шрифта для Game Score в BadRobots очень проста. SDL_Image, SDL_Mixer работают корректно, в mixer я проверил только проигрывание музыки.

Исходный код Flame Steel Engine, Flame Steel Engine Game Toolkit, игры Bad Robots:

https://github.com/demensdeum/BadRobots
https://github.com/demensdeum/FlameSteelEngine
https://github.com/demensdeum/FlameSteelEngineGameToolkit

Статья на эту тему:

https://hacks.mozilla.org/2012/04/porting-me-my-shadow-to-the-web-c-to-javascriptcanvas-via-emscripten/