Понадобилось мне для экспериментов сделать простейшую универсальную нейронную сеть, которая будет полностью понятной и будет давать более менее адекватный результат на произвольном наборе данных и для произвольной задачи. И почему-то в интернете какой-то совсем простой реализации найти не удалось. Пришлось сделать самому с использованием только Excel, который стоит на любом офисном компьютере. А теперь я предлагаю и вам вникнуть в эту тему и, вероятно, самим написать что-то аналогичное. А я постараюсь максимально доходчиво показать, как.
Как я говорил в прошлый раз, нам понадобятся данные для обработки. Будут они числовые. Сразу их приведём к виду "от нуля до одного". Я взял для общего случая 5 параметров со случайными значениями. В качестве фактического результата я беру простую сумму этих параметров с некоторыми случайно заданными коэффициентами. Выходит примерно так:
Теперь определим случайные коэффициенты, с которых мы начнём обучать нашу нейронную сеть. Я снова воспользовался встроенным в excel генератором случайных чисел и задал произвольные числа от -1 до 1.
Расчёты мы будем проводить с помощью макросов. Для этого можно нажать Alt+F11 и в любом доступном месте написать следующий код:
Public Const step = 0.1
Public d As Worksheet
Public o As Worksheet
Sub Init_()
Call Calc(True)
End Sub
Sub Calc(T As Boolean)
Set d = IIf(T, ThisWorkbook.Sheets("Data"), ThisWorkbook.Sheets("Test"))
Set o = ThisWorkbook.Sheets("Out")
Dim i As Integer
i = 2
While d.Cells(i, 1) <> 0 'Перебираем все входы
d.Cells(i, 8) = 0 'Обнуляем исходное значение
For j = 1 To 5 'В цикле суммируем функцию активации
d.Cells(i, 8) = d.Cells(i, 8) + d.Cells(i, 1 + j) * o.Cells(2, 1 + j)
Next j
i = i + 1
Wend
End Sub
В данном случае функция Init_ запускает расчёт по нашей простейшей функции пока что со случайными параметрами. И мы уже можем посмотреть, как наша пока ещё совсем не обученная нейронная сеть, которая по сути состоит из всего лишь одного нейрона с пятью коэффициентами, справилась с задачей. Для моих случайных данных получилось следующее:
В качестве меры ошибки я взял обычное среднеквадратичное отклонение. Другими словами мы вычисляем разницу между ожиданием и реальностью в каждом конкретном случае, возводим в квадрат, а затем берём корень от среднего значения. И у нас получилось аж 0.89. С учётом того, что сами значения обычно пляшут недалеко от единицы, то точность на старте у нас оказалась совсем плохой. Но это не беда, сейчас мы обучим нашу сеть.
Sub Teach()
Dim i As Integer, j As Integer
Set d = ThisWorkbook.Sheets("Data")
Set o = ThisWorkbook.Sheets("Out")
Dim s As Double
Dim delta As Double
i = 2
While d.Cells(i, 1) <> 0 'Перебираем все строчки
For j = 1 To 5 'Перебираем все входы
s = d.Cells(i, 7) - d.Cells(i, 8) 'Как сильно мы ошиблись
delta = res(i, j, s) - d.Cells(i, 8) 'Если немного поменять вес у j-ого входа, то станет лучше?
If Abs(delta) > Abs(s) Then 'Если стало хуже, пробуем менять вес в другую сторону
s = -s
delta = res(i, j, s) - d.Cells(i, 8)
End If
If Abs(delta) < Abs(s) Then 'Если стало лучше, меняем вес j-ого входа
o.Cells(2, j + 1) = o.Cells(2, j + 1) + s * step
End If
If o.Cells(2, j + 1) > 1 Then o.Cells(2, j + 1) = 1 'ограничиваем веса, чтобы они не уходили далеко
If o.Cells(2, j + 1) < -1 Then o.Cells(2, j + 1) = -1 'ограничиваем веса, чтобы они не уходили далеко
Call Calc1(i) 'Пересчитываем i-ый результат
Next j
i = i + 1
Wend
End Sub
Function res(r As Integer, j As Integer, de As Double)
res = 0
Dim w(1 To 5) As Double
For i = 1 To 5
w(i) = o.Cells(2, 1 + i) + IIf(j = i, de, 0) * step
res = res + d.Cells(r, 1 + i) * w(i)
Next i
End Function
Sub Calc1(r As Integer)
d.Cells(r, 8) = 0 'Обнуляем исходное значение
For k = 1 To 5 'В цикле суммируем функцию активации
d.Cells(r, 8) = d.Cells(r, 8) + d.Cells(r, 1 + k) * o.Cells(2, 1 + k)
Next k
End Sub
В данном случае функция res выдаёт нам результат функции для входящих данных с номером r при изменении веса номер j на величину de, умноженную на параметр step, который играет роль скорости обучения. Если step будет очень маленьким, то наша нейронная сеть будет обучаться очень медленно, что потребует существенных затрат времени и мощностей компьютера. Если step очень большое, то мы можем столкнуться с ситуацией, что будем бесконечно "перепрыгивать" правильное решение, из-за чего никогда не обучим нашу сеть. Поэтому подбирать этот параметр надо более менее разумно.
Сама функция res используется для того, чтобы сравнить текущий результат с тем, что получится, если немного поменять один из весов. В функции Teach мы пробегаем все наши входные данные, для каждого из них смотрим ошибку, которую нам дала наша нейронная сеть, затем смотрим, станет ли лучше, если мы изменим один из коэффициентов. И если стало лучше, то сохраняем новые значения коэффициентов и идём дальше.
Важно помнить, что каждая конкретная итерация не обязательно будет нас приводить ближе к верному значению коэффициентов. Но чисто статистически мы после достаточно длительного обучения придём к почти идеальному совпадению с целевой функцией.
Теперь запустим обучение несколько раз, чтобы подобрать хорошие коэффициенты. Для этого я воспользовался следующим кодом:
Sub run()
For i = 1 To 25
Call Calc(True)
Call Teach
Next i
End Sub
Я 25 раз последовательно запускаю расчёт значений ошибки для всей выборки и обучение на полученных коэффициентах. Т.е. программа 25 раз пробегает все значения, подстраивая коэффициенты всё более и более точно.
Как видите, результат превзошёл все ожидания. Ошибка стала нулевой. Если мы посмотрим на полученные коэффициенты, то увидим, что они теперь очень близки:
Но это не всё. Теперь, чтобы убедиться, что мы получили достаточно хороший результат, нам нужно взять данные, которых не было в обучающей выборке. Для этого я создал лист "Test", который по структуре повторяет лист "Data", но содержит другие случайные значения. И запустил расчёт с помощью следующего кода:
Sub test()
Call Calc(False)
End Sub
Результат по-прежнему хороший. Пришлось даже раскрыть больше знаков числа, чтобы увидеть, что это не нулевая ошибка:
Не ноль у нас получился по той причине, что нейронная сеть в принципе не даёт точного решения. Потому на тех данных, которые не попали в обучающую выборку, могут быть некоторые расхождения. И это нормально.
В данном случае у нас и фактическая функция линейная, и у нейронной сети линейное суммирование. Т.е. они в принципе могут друг с другом совпасть. Но, как было сказано в прошлой статье, не всегда такое возможно. И для этого мы можем попробовать например написать другую логику, которая не может быть описана прямой линией. Я навскидку беру такое условие: Если пятый параметр больше второго, то единица. В противном случае ноль. Обучаю нашу нейросеть и смотрю, что же получилось. Если считать число больше 0.5 уверенностью нейронной сети в успехе, то на обучающей выборке точность составила 98,5%. Но на тестовой выборке результат не такой хороший. Всего 73%. И это рядовая ситуация. Чтобы нейронная сеть выдавала хороший результат на тех данных, которые она не видела в глаза, нужно хорошо постараться.
Безусловно, полученная в этой статье программа нейронной сетью может называться очень условно. Но и она уже может решать произвольного рода задачи с некоторой более менее адекватной точностью. Я, конечно, попробую со временем сделать что-то более сложное, что сможет решать более широкий спектр задач. Обязательно расскажу в следующих статьях!