Привет читателям Habrahabr! В этой статье я продемонстрирую вам пример простой нейронной сети на языке Golang с использованием готовой библиотеки.

Немного предисловия


Начав изучать язык программирования Golang, мне стало интересно, что может этот язык в сфере машинного обучения. Тогда я начал искать примеры кода какой-либо НС на этом языке. К сожалению, ничего толкового найти не получилось. И вот тогда я решил переписать НС из этой статьи под GO.

Нейросеть


Задача нейронной сети — решить, что делать персонажу, исходя из 3х параметров:

  • Количество здоровья (от 1 до 100)
  • Наличие оружия
  • Количество врагов

В зависимости от результата, может быть принято одно из следующих решений:

  • Атаковать
  • Красться
  • Убегать
  • Ничего не делать

Примеры:
Здоровье Оружие Враги Решение
50 1 1 Атаковать
90 1 2 Атаковать
80 0 1 Атаковать
30 1 1 Красться
60 1 2 Красться
40 0 1 Красться
90 1 7 Убегать
60 1 4 Убегать
10 0 1 Убегать
60 1 0 Ничего не делать
100 0 0 Ничего не делать

Подготовка


Мы будем использовать библиотеку GoNN.

Установка:

go get github.com/fxsjy/gonn/gonn

Приступим!


Для начала зададим импорт:

import (
     "fmt"
     "github.com/fxsjy/gonn/gonn"
)

Теперь приступим к созданию нейронной сети:


func CreateNN() {
    // Создаём НС с 3 входными нейронами (столько же входных параметров),
    // 16 скрытыми нейронами и
    // 4 выходными нейронами (столько же вариантов ответа)
    nn := gonn.DefaultNetwork(3, 16, 4, false)
     
    // Создаём массив входящих параметров:
    // 1 параметр - количество здоровья (0.1 - 1.0)
    // 2 параметр - наличие оружия (0 - нет, 1 - есть)
    // 3 параметр - количество врагов
    input := [][]float64 {
             []float64{0.5, 1, 1}, []float64{0.9, 1, 2}, []float64{0.8, 0, 1}, 
             []float64{0.3, 1, 1}, []float64{0.6, 1, 2}, []float64{0.4, 0, 1},
             []float64{0.9, 1, 7}, []float64{0.6, 1, 4}, []float64{0.1, 0, 1}, 
             []float64{0.6, 1, 0}, []float64{1, 0, 0} }
    
    // Теперь создаём "цели" - те результаты, которые нужно получить
    target := [][]float64 {
             []float64{1, 0, 0, 0}, []float64{1, 0, 0, 0}, []float64{1, 0, 0, 0},
             []float64{0, 1, 0, 0}, []float64{0, 1, 0, 0}, []float64{0, 1, 0, 0},
             []float64{0, 0, 1, 0}, []float64{0, 0, 1, 0}, []float64{0, 0, 1, 0},
             []float64{0, 0, 0, 1}, []float64{0, 0, 0, 1} }

    // Начинаем обучать нашу НС.
    // Количество итераций - 100000
    nn.Train(input, target, 100000)

    // Сохраняем готовую НС в файл.
    gonn.DumpNN("gonn", nn)
}

Теперь нам нужно написать функцию, которая выберет ответ нейрона с самым большим весом.


func GetResult(output []float64) string {
    max := -99999
    pos := -1
    // Ищем позицию нейрона с самым большим весом.
    for i, value := range output {
        if (value > max) {
            max = value
            pos = i
        }
    }

    // Теперь, в зависимости от позиции, возвращаем решение.
    switch pos {
    case 0: return "Атаковать"
    case 1: return "Красться"
    case 2: return "Убегать"
    case 3: return "Ничего не делать"
    }
    return ""
}

И теперь пишем функцию main.


func main() {
    CreateNN()
    // Загружем НС из файла.
    nn := gonn.LoadNN("gonn")

    // Записываем значения в переменные:
    // hp - здоровье (0.1 - 1.0)
    // weapon - наличие оружия (0 - нет, 1 - есть)
    // enemyCount - количество врагов
    var hp float64 = 0.7
    var weapon float64 = 1.0
    var enemyCount float64 = 1.0

    // Получаем ответ от НС (массив весов)
    out := nn.Forward([]float64{ hp, weapon, enemyCount })
    // Печатаем ответ на экран.
    fmt.Println(GetResult(out))
}

В данном случае ответ — «Атаковать»

Заключение


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

Комментарии (18)


  1. GH0st3rs
    29.11.2017 13:45

    Не знаю как у вас работает этот пример, но как минимум следующие исправления надо внести (чтобы компилятор не ругался)


    @@ -61,6 +61,7 @@
        case 3:
            return "Ничего не делать"
        }
    +   return "" // Функция должна возвращать значение
     }
    
     func main() {
    @@ -73,8 +74,8 @@
        // weapon - наличие оружия (0 - нет, 1 - есть)
        // enemyCount - количество врагов
        hp := 0.7
    -   weapon := 1
    -   enemyCount := 1
    +   var weapon float64 = 1 // По умолчанию, GO воспринимает 1 как int
    +   var enemyCount float64 = 1 // По умолчанию, GO воспринимает 1 как int
    
        // Получаем ответ от НС (массив весов)
        out := nn.Forward([]float64{hp, weapon, enemyCount})


    1. SenDu Автор
      29.11.2017 20:10

      Спасибо! Не внимательно переписал код, уже исправил.


  1. WebConn
    29.11.2017 15:38

    Есть мнение, что на таком большом количестве итераций такая сеть просто заучит входные данные и выход на новых будет не очень адекватным.

    На самом деле, интересней узнать, как у gonn обстоят дела с платформо-зависимыми оптимизациями. Может ли оно использовать gpu для математики, например. Вообще, для подобной статьи неплохо предложить сравнение с другими библиотеками.


    1. grossws
      29.11.2017 18:44

      Есть мнение, что на таком большом количестве итераций такая сеть просто заучит входные данные и выход на новых будет не очень адекватным.

      Эта сеть, очевидно, переобучится со свистом. Другое дело, что здесь нет никакого сплита на train/test или train/dev/test и оценки базовых параметров получившегося результата (хотя бы банальные fp, fn, precision, recall и f1).


    1. RPG18
      29.11.2017 19:25

      gonn написан на Go без CGO, поэтому не может задействовать GPU и SSE/MMX/и т.д.


      1. quasilyte
        30.11.2017 11:35

        Через Go ассемблер можно использовать и SSE, и AVX, а MMX я бы просто не рекомендовал использовать. :)
        И это добро реально используют при оптимизации, например, стандартной библиотеки Go.


        1. RPG18
          30.11.2017 12:43

          Знаю. Вот пример использования. Но в 2017 писать на ассемблере ML библиотеки, это грустно.


    1. JekaMas
      29.11.2017 20:35
      +2

      В Golang очень плохо с такими вещами, как cuda.
      Я на go три года и… освоил python для ml, dl. Он лучше. В го очень неудобно и многословно, когда речь заходит про данные и ml — нет именованных параметров, значений по умолчанию, нет коротких конструкций для работы со списками и хэштаблицами, отсутствие удобного функционального подхода, вызовы cgo стали пошустрее, но до python еще далеко.
      Всему свой инструмент. Для ds — это python или java.


  1. grossws
    29.11.2017 18:42

    Как видим, процесс работы с нейронными сетями на Golang такой же, как и с другими языками программирования.

    "Такой же" — это наличие метода train? Что-то не очень видно настроек количества скрытых слоёв, всяких развлечений типа dropout'а и подобных регуляризаций, метода спуска и критерия остановки (кроме количества итераций).


    Если посмотреть в их примеры, то количество сугубо вспомогательного кода, некритичного к производительности (начальная загрузка и обработка данных) превышает все разумные пределы. Проще и лаконичней написать на Си с использованием cilkplus.


    Кому это gonn'о нужно после python'а с приличными библиотеками? Умеет ли оно хотя бы в нативный blas/mkl?


    1. QtRoS
      29.11.2017 20:22

      Как я понимаю мотив библиотеки — Python это эксперименты, а Go продакшн (на самом деле надо понимать, что все годные ML-либы в Python'е на C++ или numpy сделаны, поэтому по скорости не уступают, но это не суть). Мне кажется идея такая, но это неточно.


      1. grossws
        29.11.2017 20:39

        Столь примитивная библиотека выглядит просто смешно как решение для прода (сравните со всякими TensorFlow, Theano, Torch).


        Естественно, на python'е в ML почти всегда используется обвязка над нативными реализациями того же BLAS'а. Иии..? Почему это является внезапно минусом?


        А насчёт "не уступают" мне интересно увидеть бенчмарк, который это подтвердит. Против numpy+icc+mkl или numpy+gcc+atlas (собранный под конкретный хост, а не из репозитория). Я сильно сомневаюсь, что оно сможет догнать даже numpy+gcc+openblas.


        1. JekaMas
          29.11.2017 20:42
          +2

          Библиотека как 4 года не поддерживается (2 года назад readme поправили только).
          Думаю, кто-то просто пробовал golang и учился. Обучение идет в один поток, параллельная версия медленнее однопоточной (в readme указано). Просто учебный проект, на мой взгляд.


  1. artemmityushov
    29.11.2017 20:11

    Что-то мне не понятно, он просто выбирает из матрицы готовых решений?
    В чем фишка использования нейросетей то в данном случае?


    1. SenDu Автор
      29.11.2017 20:11

      Это простой пример. И он не выбирает из матрицы. Это обучение.


      1. artemmityushov
        30.11.2017 08:43

        А вот здесь вопрос — как и чему он обучается? По мне так это просто красивое слово «обучается», как добавление слова «блокчейн» ко всему чему можно.


        1. SenDu Автор
          02.12.2017 10:58

          Он обучается на простых примерах. Это как показать человеку, чем он должен заниматься: сначала говорим, какие инструменты и материалы у него есть, а потом говорим, что у него должно получиться. Представим, что этот человек в принципе такими вещами не занимался. Поэтому он будет учиться это делать. И со временем, после обучения, он сможет делать то, что у него просят.
          Тут аналогично: я обучил нейросеть на простых примерах, а она теперь сама может «решать», что будет лучше, даже вводить числа з 6 знаками и больше.


  1. peinguin
    30.11.2017 09:32

    Я новичок в ML. Не могли бы вы рассказать почему скрытых нейронов именно 16? И как вы выбирали это значение?


    1. SenDu Автор
      02.12.2017 11:07

      Честно, не знаю, почему автор этой нейронной сети (я просто перевёл её на другой ЯП) выбрал именно число 16. В общем, количество скрытых нейронов подбирается экспериментально. В зависимости от желаемого результата, надо корректировать это значение.