[Из песочницы] Нейросети для самых маленьких

Habrahabr 1

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

Я сам, как и многие в один день решил поинтересоваться программным обучением, ИИ и нейро сетями. Благо в сети есть много разборов и примеров, но все они оперируют изобилием формул функции и если ты не подкован в математике(как я), постараюсь продемонстрировать простой пример сети Хопфилда с использованием языка Golang(GO). Математическое описание сети — Сеть Хопфилда

Почему именно сеть Хофпилда?

Достаточно быстрый и более менее понятный пример если можно оперировать такими терминами как простой и понятный в мире ИИ.

В этом примере мы попробуем распознавать образы из черно-белой картинки размером 20х20 пикселей.

Попробуем разобраться в шагах которые нам предстоит выполнить прежде чем мы получим наш желанный результат:

  1. Преобразовать изображение в вектор
  2. Преобразовать вектора в матрицы
  3. Просуммировать матрицы и получить одну единую матрицу (W)
  4. Обнулить единую матрицу по диагонали
  5. Умножать матрицу W на вектор входящего изображения
  6. Пропускать полученный вектор через функцию активации для данной сети(F)
  7. Подставлять новый вектор к пункту 5 и продолжать операцию до тех пор пока мы не получим устойчивое состояние сети(на выходе будем получать один и тот же вектор).
Перейдем к коду с подробным описанием. Все библиотеки которые нам понадобятся:
package main

import (
        "github.com/oelmekki/matrix"
        "fmt"
        "os"
        "log"
        "image"
        "math"
        "image/color"
        "image/png"
        _ "image/jpeg"
)

Создаем массив векторов из 3 элементов(количество образцов), конвертируем изображения в вектора и добавляем образцы в массив. 'Y' вектор это тот образ который мы хотим распознать.
   
        vectorArrays := [3][]float64{}

        x1 := getVectorFromImage("Images/А.jpg")
        x2 := getVectorFromImage("Images/Б.jpg")
        x3 := getVectorFromImage("Images/О.jpg")

        y := getVectorFromImage("Images/Income.jpg")

        // Add images to the array
        vectorArrays[0] = x1
        vectorArrays[1] = x2
        vectorArrays[2] = x3


Создаем массив матриц, конвертируем все вектора в матрицы и добовляем их в масив матриц. Создаем заготовку матрицы W и начинаем сумирование всех матриц, результат кладем в W
   
        matrixArray := [len(vectorArrays)]matrix.Matrix{}

        for i, vInArray := range vectorArrays {

                matrixArray[i] = vectorToMatrix(vInArray, vInArray)
        }


        W := matrix.Matrix{}

        
        for i, matrixInArray := range matrixArray {
                if i == 0 {
                        W = matrixInArray
                        continue
                }
                W, _ = W.Add(matrixInArray)
        }

Обнуляем матрицу по диагонали
for i := 0; i < W.Rows(); i++ {
                W.SetAt(i, i, 0)
        }

Создаем заготовку выходного вектора, умножаем матрицу на Y вектор, полученый результат кладем в вектор S и подставляем его обратно на умножение.
S := make([]float64, 400)

        for II := 0; II < 100; II++ {
                if II == 0 {
                        S, _ = W.VectorMultiply(y)
                        for i, element := range S {

                                // Activation Func "sigmod"
                                S[i] = sigmod(element)
                        }
                        continue

                } else {

                        S, _ = W.VectorMultiply(S)
                        for i, element := range S {

                                // Activation Func "sigmod"
                                S[i] = sigmod( element)
                        }
                }

        }

Подробнее по поводу функции sigmod().

Это функция активации F и принцип ее работы(в нашем примере) заключается в том что бы преобразовать данные в выходящем векторе и привести их или к 1, или к -1.

Так как мы работаем с биполярной сетью то и данные могут быть только 1 и -1.

Изображение следует привести из RGB к 1 и -1, где сумма всех точек деленная на 3(условная яркость пикселя) должна стремиться к черному или белому цвету. Так как R = 255, G = 255, B = 255 это белый цвет, а R = 0, G = 0, B = 0 черный. Я выбрал порог в 150, следовательно что больше или равно 150 будет белым(1) все что меньше черным(-1), где выбор между черным в -1 и белым в 1 может быть условным, все что нам нужно это разложить черный и белый по значениям. Белый может быть так же -1, а черный 1, в данном случае это не играет роли. Так же стоит учесть что мы работаем с симметричной матрицей и изображения должны быть равносторонними.

Для того что бы преобразовать изображение в вектор нужно представить изображение как матрицу которую мы режем по горизонтали и каждый отрезанный слой (а у нас их будет 20) добавляем в конец предыдущего слоя и получаем вектор длиной в 400 (20x20).

В примере я не проверяю выходной вектор на устойчивость, а просто прохожу по циклу 100 раз, и в конце проверяю на какой из образцов похож наш результат. Сеть или отгадывает или же нет при этом выдавая так называемую химеру или вольную интерпретацию того что она смогла увидеть. Это результат я сохраняю в изображение.

Так ка мы используем синхронный режим сети Хопфилда, то и результат будет слабый. Конечно можно использовать асинхронный, что займет больше времени и ресурсов, но и результат будет на много лучше.

Пример работы:

Входной образ —

Ответ —

Исходные образы

Весь код
package main
package main

import (
        "github.com/oelmekki/matrix"
        "fmt"
        "os"
        "log"
        "image"
        "math"
        "image/color"
        "image/png"
        _ "image/jpeg"
)

func vectorToMatrix(v1 []float64, v2 []float64) (matrix.Matrix) {

        m := matrix.GenerateMatrix(len(v1), len(v2))

        for i, elem := range v1 {
                for i2, elem2 := range v1 {
                        m.SetAt(i, i2, elem2*elem)
                }
        }

        return m
}

func sigmod(v float64) (float64) {

        if v >= 0 {

                return 1

        } else {

                return -1
        }

}

func getVectorFromImage(path string) ([] float64) {

        reader, err := os.Open(path)
        if err != nil {
                log.Fatal(err)
        }
        defer reader.Close()
        m, _, err := image.Decode(reader)
        if err != nil {
                log.Fatal(err)
        }

        v := make([]float64, 400)
        vectorIteration := 0
        for x := 0; x < 20; x++ {
                for y := 0; y < 20; y++ {
                        r, g, b, _ := m.At(x, y).RGBA()
                        normalVal := float64(r+g+b) / 3 / 257
                        if normalVal >= 150 {

                                v[vectorIteration] = 1

                        } else {

                                v[vectorIteration] = -1
                        }

                        vectorIteration ++
                }

        }

        return v
}

func main() {

        fmt.Println("Memory size ~ ", int(400/(2*math.Log2(400))), " objects")
        fmt.Println("1 - А")
        fmt.Println("2 - Б")
        fmt.Println("3 - О")
        fmt.Println("-----Start------")


        vectorArrays := [3][]float64{}

        x1 := getVectorFromImage("Images/А.jpg")
        x2 := getVectorFromImage("Images/Б.jpg")
        x3 := getVectorFromImage("Images/О.jpg")


        y := getVectorFromImage("Images/Income.jpg")


        vectorArrays[0] = x1
        vectorArrays[1] = x2
        vectorArrays[2] = x3


        matrixArray := [len(vectorArrays)]matrix.Matrix{}


        for i, vInArray := range vectorArrays {

                matrixArray[i] = vectorToMatrix(vInArray, vInArray)
        }


        W := matrix.Matrix{}


        for i, matrixInArray := range matrixArray {
                if i == 0 {
                        W = matrixInArray
                        continue
                }
                W, _ = W.Add(matrixInArray)
        }


        for i := 0; i < W.Rows(); i++ {
                W.SetAt(i, i, 0)
        }


        S := make([]float64, 400)

        for II := 0; II < 100; II++ {
                if II == 0 {
                        S, _ = W.VectorMultiply(y)
                        for i, element := range S {

                                S[i] = sigmod(element)
                        }
                        continue

                } else {

                        S, _ = W.VectorMultiply(S)
                        for i, element := range S {

                                S[i] = sigmod(element)
                        }
                }

        }

        ar := [3]int{1, 1, 1}

        for vectorI, v := range vectorArrays {
                for i, elem := range v {
                        if elem != S[i] {
                                ar[vectorI] = 0
                                break
                        }
                }
        }

        for i, el := range ar {
                if el == 1 {
                        fmt.Println("Looks like", i+1)
                }
        }

        img := image.NewRGBA(image.Rect(0, 0, 20, 20))
        xx := 0
        yy := 0

        for i := 0; i < 400; i++ {
                if i%20 == 0 {
                        yy++
                        xx = 0
                } else {
                        xx++
                }
                if S[i] == -1 {
                        img.Set(xx, yy, color.RGBA{0, 0, 0, 255})
                } else {
                        img.Set(xx, yy, color.RGBA{255, 255, 255, 255})
                }

        }

        f, _ := os.OpenFile("Images/out.png", os.O_WRONLY|os.O_CREATE, 0600)
        png.Encode(f, img)
        f.Close()
        var str string
        fmt.Scanln(&str)
}