Tesla coil

[Feel the power of Artificial Intelligence]

В этой заметке я опишу процесс создания генератора цитат.

TL;DR

Для обучения и генерации текста – использовать библиотеку textgenrnn, для фильтрации фраз нужно использовать проверку орфографии с помощью утилиты hunspell и ее библиотеки для C/python. После обучения в Colaboratory, можно приступать к генерации текста. Примерно 90% текста будет абсолютно не читаемым, однако оставшиеся 10% будут содержать толику смысла, а при ручной доработке фразы будут выглядеть вполне неплохо.
Проще всего запустить готовую нейросеть в Colaboratory:
https://colab.research.google.com/drive/1-wbZMmxvsm3SoclJv11villo9VbUesbc

Исходный код

https://gitlab.com/demensdeum/MachineLearning/tree/master/3quotesGenerator

Источники

https://minimaxir.com/2018/05/text-neural-networks/
http://karpathy.github.io/2015/05/21/rnn-effectiveness/
https://medium.com/deep-learning-turkey/google-colab-free-gpu-tutorial-e113627b9f5d
https://github.com/wooorm/dictionaries

Bugs count estimation

[Feel the power of Artificial Intelligence]

На Hacker News нашел очень интересную статью в которой автор предлагает использовать метод Петерсена-Линкольна, который используется биологами для подсчета популяции птичек, обезьянок и прочих животных, для *барабанная дробь* подсчета багов в приложении.

Баг в естественной среде обитания – Bigfoot Sighting by Derek Hatfield

Метод очень прост, берем двух орнитологов, они находят птичек какого-то определенного вида, их задача – определить размер популяции этих птичек. Найденные птички помечаются обоими орнитологами, далее подсчитывается количество общих, подставляется в формулу индекса Линкольна и мы получаем примерный размер популяции.
Теперь для приложений – метод также очень прост, берем двух QA и они находят баги в приложении. Допустим один тестировщик нашел 10 багов (E1), а второй 20 багов (E2), теперь берем число общих багов – 3 (S), далее по формуле получаем индекс Линкольна:

Это и есть прогноз числа багов во всем приложении, в приведенном примере ~66 багов.

Пример на Swift

Я реализовал тестовый стенд для проверки метода, посмотреть можно здесь:
https://paiza.io/projects/AY_9T3oaN9a-xICAx_H4qw?language=swift

Параметры которые можно менять:

let aliceErrorFindProbability = 20 – процент нахождения багов у QA Alice (20%)
let bobErrorFindProbability = 60 – процент нахождения багов у QA Bob (60%)
let actualBugsCount = 200 – сколько багов в приложении на самом деле

В последнем запуске я получил следующие данные:
Estimation bugs count: 213
Actual bugs count: 200

Тоесть в приложении есть 200 багов, индекс Линкольна дает прогноз – 213:
“Alice found 36 bugs”
“Bob found 89 bugs”
“Common bugs count: 15”

Estimation bugs count: 213
Actual bugs count: 200

Слабые стороны

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

Источники

How many errors are left to find? – John D. Cook, PhD, President
The thrill of the chase – Brian Hayes

Исходный код

https://paiza.io/projects/AY_9T3oaN9a-xICAx_H4qw?language=swift
https://gitlab.com/demensdeum/statistics/tree/master/1_BugsCountEstimation/src

Black Shaped Box

[English translation may be some day]

К любому разработчику на OpenGL периодически приходит Малевич. Происходит это неожиданно и дерзко, ты просто запускаешь проект и видишь черный квадрат вместо чудесного рендера:

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

Используй инструменты

Для отладки OpenGL мне помогли два инструмента: renderdoc и apitrace. Renderdoc – инструмент для отладки процесса рендеринга OpenGL, просматривать можно все – вертексы, шейдеры, текстуры, отладочные сообщения от драйвера. Apitrace – инструмент для трейсинга вызовов графического API, делает дамп вызовов и показывает аргументы. Также есть великолепная возможность сравнивать два дампа через wdiff (или без него, но не так удобно)

Проверяй с кем работаешь

У меня есть операционная система Ubuntu 16.10 со старыми зависимостями SDL2, GLM, assimp, GLEW. В последней версии Ubuntu 18.04 я получаю сборку игры Death-Mask которая ничего не показывает на экране (только черный квадрат). При использовании chroot и сборке в 16.10 я получаю рабочую сборку игры с графикой.


Похоже что-то сломалось в Ubuntu 18.04

LDD показал линковку к идентичным библиотекам SDL2, GL. Прогоняя нерабочий билд в renderdoc, я увидел мусор на входе в вертексный шейдер, но мне нужно было более солидное подтверждение. Для того чтобы разобраться в разнице между бинариками я прогнал их оба через apitrace. Сравнение дампов показало мне что сборка на свежей убунте ломает передачу матриц перспективы в OpenGL, фактически отправляя туда мусор:

Матрицы собираются в библиотеке GLM. После копирования GLM из 16.04 – я снова получил рабочий билд игры. Проблема оказалась в разнице инициализации единичной матрицы в GLM 9.9.0, в ней необходивно явно указывать аргумент mat4(1.0f) в конструкторе. Поменяв инициализацию и отписав автору библиотеки, я принялся делать тесты для FSGL. в процессе написания которых я обнаружил недоработки в FSGL, их опишу далее.

Определись ты кто по жизни

Для корректной работы с OpenGL нужно в добровольно принудительном порядке запросить контекст определенной версии. Так это выглядит для SDL2 (проставлять версию нужно строго до инициализации контекста):

    SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2);
    SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );   

Например Renderdoc не работает с контекстами ниже 3.2. Хочется отметить что после переключения контекста высока вероятность увидеть тот самый черный экран. Почему?
Потому что контекст OpenGL 3.2 обязательно требует наличие VAO буфера, без которого не работают 99% графических драйверов. Добавить его легко:

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

Не спи, замерзнешь

Также я встретился с интересной проблемой на Kubuntu, вместо черного квадрата у меня выводился прозрачный, а иногда все рендерилось корректно. Решение этой проблемы я нашел на Stack Overflow:
https://stackoverflow.com/questions/38411515/sdl2-opengl-window-appears-semi-transparent-sometimes

В коде тестового рендера FSGL тоже присутствовал sleep(2s); Так вот на Xubuntu и Ubuntu я получал корректный рендер и отправлял приложение спать, однако на Kubuntu я получил прозрачный экран в 80% случаев запуска из Dolphin и 30% запусков и терминала. Для решения данной проблемы я добавил рендеринг в каждом кадре, после опроса SDLEvent, как это рекомендуется делать в документации.

Код теста:
https://gitlab.com/demensdeum/FSGLtests/blob/master/renderModelTest/

Поговори с драйвером

OpenGL поддерживает канал связи между приложением и драйвером, для его активации нужно включить флаги GL_DEBUG_OUTPUT, GL_DEBUG_OUTPUT_SYNCHRONOUS, проставить оповещение glDebugMessageControl и привязать каллбек через glDebugMessageCallback.
Пример инициализации можно взять здесь:
https://github.com/rock-core/gui-vizkit3d/blob/master/src/EnableGLDebugOperation.cpp

It grows!

[English translation may be some day]

В данной заметке я расскажу о своих злоключениях с умными указателями shared_ptr. После реализации генерации следующего уровня в своей игре Death-Mask, я заметил утечку памяти. Каждый новый уровень давал прирост + 1 мегабайт к потребляемой оперативной памяти. Очевидно что какие-то объекты оставались в памяти и не освобождали ее. Для исправления данного факта необходимо было реализовать корректную реализацию ресурсов при перегрузке уровня, чего видимо сделано не было. Так как я использовал умные указатели, то вариантов решения данной задачи было несколько, первый заключался в ручном отсмотре кода (долго и скучно), второй же предполагал исследование возможностей дебагера lldb, исходного кода libstdc++ на предмет возможности автоматического отслеживания изменений счетчика.

В интернете все советы сводились к тому чтобы вручную отсматривать код, исправить и бить себя плетями после нахождения проблемной строчки кода. Также предлагалось реализовать свою собственную систему работы с памятью, как это делают все крупные проекты разрабатываемые еще с 90-х и нулевых, до прихода умных указателей в стандарт C++11. Мною была предпринята попытка использовать брейкпоинты на конструкторе копии всех shared_ptr, после нескольких дней ничего дельного не получилось. Была идея добавить логирование в библиотеку libstdc++, однако трудозатраты (о)казались чудовищными.


Cowboy Bebop (1998)

Решение пришло мне в голову внезапно в виде отслеживания изменений приватной переменной shared_ptr – use_count. Сделать это можно с помощью встроенных в lldb ватчпоинтов (watchpoint) После создания shared_ptr через make_shared, изменения счетчика в lldb можно отслеживать с помощью строки:

watch set var camera._M_refcount._M_pi->_M_use_count

Где “camera” это shared_ptr объект состояние счетчика которого необходимо отследить. Конечно внутренности shared_ptr будут различаться в зависимости от версии libstdc++, но общий принцип понять можно. После установки ватчпоинта запускаем приложения и читаем стектрейс каждого изменения счетчика, потом отсматриваем код (sic!) находим проблему и исправляем. В моем случае объекты не освобождались из таблиц-кешэй и таблиц игровой логики. Надеюсь данный метод поможет вам разобраться с утечками при работе с shared_ptr, и полюбить этот инструмент работы с памятью еще больше. Удачного дебага.

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