AI / ИИ на Форте.
Работа с элементарной ИИ на Форте не является сложной задачей. Продемонстрируем это.
Возьмем простой пример со следующими исходными данными (обучающая выборка):
x1=0 x2=0 y_true=0
x1=0 x2=1 y_true=0
x1=1 x2=0 y_true=0
x1=1 x2=1 y_true=1
Можете заметить, что вводам x1 и x2 сопоставляется их логическое "И".
Подключим библиотеку для работы с вещественными числами.
S" lib/include/float2.f" INCLUDED
Нам понадобятся переменные w1, w2 - веса; b - смещение (сразу инициализируем все три нулями); x1, x2 - входные значения; learning_rate - шаг обучения, для определенности взят за "0.1"; y_true - соответствующее истинное значение из тестовой выборки.
FVARIABLE w1
FVARIABLE w2
FVARIABLE b
0E w1 F!
0E w2 F!
0E b F!
FVARIABLE x1 FVARIABLE x2
FVARIABLE learning_rate
0.1E learning_rate F!
FVARIABLE y_true
Напишем слово моделирующее поведение персептрона (назовем его "P"), которое просто вычисляет выход по формуле:
y_pred = x1*w1+x2*w2+b
затем сравниваем это значение с нулем, если меньше оставляем 0, иначе 1.
Таким образом мы совместили функцию активации с расчетом линейной комбинации для персептрона с двумя входами.
\ x1 w1 x2 w2 b P - формат вызова слова
: P ( F: x1 w1 x2 w2 b -> F: y_pred ) FSWAP FROT F* F+ FSWAP FROT F* F+ F0< IF 0E ELSE 1E THEN ;
Проверим работу слова на случайных данных:
1E 2E 3E 5E 1000E P F.
1.0000000 Ok
1*2+3*5+1000 = 1017, что явно больше нуля, следовательно, результат работы 1. Персептрон работает корректно.
Самое важное слово производящее тренировку персептрона назван - "T" (от слова Train). Входом служат исходные параметры x1, x2 и соответствующее им истинное значение, а вместо выхода - изменение переменных w1, w2 и b, что обозначено в комментарии стековой нотации буквой "V:" (от слова "VARIABLE").
: T ( F: x1 x2 y_true -> V: w1 w2 b ) \ x1 x2 y_true T - формат вызова слова
y_true F! \ x1 x2 y_true -> x1 x2
FOVER x1 F! FDUP x2 F! \ x1 x2 -> x1 x2 x1=x1 x2=x2
w1 F@ FSWAP w2 F@ b F@ \ x1 x2 -> x1 w1 x2 w2 b
P \ x1 w1 x2 w2 b -> y_pred
y_true F@ FSWAP F- \ y_pred -> y_true-y_pred=error
learning_rate F@ F* \ error -> error*learning_rate
FDUP x1 F@ F* w1 F@ F+ w1 F! \ error*learning_rate -> error*learning_rate w1=error*learning_rate*x1+w1
FDUP x2 F@ F* w2 F@ F+ w2 F! \ error*learning_rate -> error*learning_rate w2=error*learning_rate*x2+w2
b F@ F+ b F! \ error*learning_rate -> b=error*learning_rate+b
." Обновленные веса: w1 = " w1 F@ F. ." w2 = " w2 F@ F. ." b = " b F@ F. CR
;
Первая строка - описание слова.
Вторая - сохранение значения "y_true" в одноименной переменной.
Третья, аналогично предыдущей, сохранение значений x1, x2.
Четвертая - получаем значения переменных w1, w2 и b, одновременно выстраиваем их в том порядке, которая соответствует стековой нотации слова "P".
Пятая вызываем "P" (работа которой описана выше).
В шестой считаем ошибку по формуле error = y_true-y_pred.
В седьмой умножаем на шаг обучения, чтобы в
в девятой, десятой и одиннадцатой корректируются значения переменных w1, w2 и b по формулам:
w1=error*learning_rate*x1
w2=error*learning_rate*x2
b=error*learning_rate
Двенадцатая - вывод значения обновленных весов (контрольная информация о работе слова).
Можно протестировать это слово на следующих данных:
0E 0E 0E T
0E 1E 0E T
1E 0E 0E T
1E 1E 1E T
Окончательно можем написать слово "epochs", которая проведет обучение на тестовой выборке.
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0E 0E 0E T
0E 1E 0E T
1E 0E 0E T
1E 1E 1E T
CR
LOOP ;
Она просто прогоняет N раз (количество эпох) обучение на тестовых данных с выводом результатов о проделанной работе.
Запустим его с параметром 10.
10 epochs
Эпоха 0
Обновленные веса: w1 = 0.1000000 w2 = 0.1000000 b = -0.1000000
Обновленные веса: w1 = 0.1000000 w2 = 0.0000000 b = -0.2000000
Обновленные веса: w1 = 0.1000000 w2 = 0.0000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.1000000
Эпоха 1
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.1000000
Обновленные веса: w1 = 0.2000000 w2 = 0.0000000 b = -0.2000000
Обновленные веса: w1 = 0.1000000 w2 = 0.0000000 b = -0.3000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 2
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 3
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 4
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 5
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 6
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 7
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 8
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Эпоха 9
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Обновленные веса: w1 = 0.2000000 w2 = 0.1000000 b = -0.2000000
Ok
Обратим внимание, что, начиная с Эпохи 2, веса w1 и w2, а также b не меняются (если предварительно не тестировать работу слова "T", то с Эпохи 3).
Проверим "натренированность" весов и смещения, для чего напишем последнее слово "PREDICT", которая будет предсказывать выход "y".
: PREDICT ( x1 x2 -> y. )
w1 F@ FSWAP w2 F@ b F@ P F.
;
Работа которого заключается в том, чтобы подготовить параметры для слова "P". Проверим качество тренировки:
0E 0E PREDICT
0.0000000 Ok
0E 1E PREDICT
0.0000000 Ok
1E 0E PREDICT
0.0000000 Ok
1E 1E PREDICT
1.0000000 Ok
Можно слегка изменить код, так чтобы вводить целые числа и получать целочисленный выход (чтобы облегчить ввод и сделать простым и наглядным вывод).
В слове "T" добавляем три строки преобразующие "x1 x2 y_true" из целочисленного в вещественный формат
: T ( x1 x2 y_true -> V: w1 w2 b ) \ x1 x2 y_true T - формат вызова слова
ROT S>D D>F
SWAP S>D D>F
S>D D>F
y_true F! \ x1 x2 y_true -> x1 x2
FOVER x1 F! FDUP x2 F! \ x1 x2 -> x1 x2 x1=x1 x2=x2
w1 F@ FSWAP w2 F@ b F@ \ x1 x2 -> x1 w1 x2 w2 b
P \ x1 w1 x2 w2 b -> y_pred
y_true F@ FSWAP F- \ y_pred -> y_true-y_pred=error
learning_rate F@ F* \ error -> error*learning_rate
FDUP x1 F@ F* w1 F@ F+ w1 F! \ error*learning_rate -> error*learning_rate w1=error*learning_rate*x1+w1
FDUP x2 F@ F* w2 F@ F+ w2 F! \ error*learning_rate -> error*learning_rate w2=error*learning_rate*x2+w2
b F@ F+ b F! \ error*learning_rate -> b=error*learning_rate+b
." Обновленные веса: w1 = " w1 F@ F. ." w2 = " w2 F@ F. ." b = " b F@ F. CR
;
Также изменится "epochs" которое вызывает уже исправленное "T".
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0 0 0 T
0 1 0 T
1 0 0 T
1 1 1 T
CR
LOOP ;
И наконец, в "PREDICT" преобразуем вход "x1 x2" в вещественный, а результат "y." из вещественного (точка в конце обозначает тот факт, что он печатается на экран, а не остается на стеке).
: PREDICT ( x1 x2 -> y. )
SWAP S>D D>F
S>D D>F
w1 F@ FSWAP w2 F@ b F@ P F>D D>S .
;
результат тренировки будет выглядеть так:
0 0 PREDICT
0 Ok
0 1 PREDICT
0 Ok
1 0 PREDICT
0 Ok
1 1 PREDICT
1 Ok
Логическое "ИЛИ"
Возьмем другую серию данных. Теперь вводам сопоставим их логическое "ИЛИ".
x1=0 x2=0 y_true=0
x1=0 x2=1 y_true=1
x1=1 x2=0 y_true=1
x1=1 x2=1 y_true=1
в коде предыдущего примера изменится только слово:
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0E 0E 0E T
0E 1E 1E T
1E 0E 1E T
1E 1E 1E T
CR
LOOP ;
После обучения, слово PREDICT выдаст следующие результаты:
0E 0E PREDICT
0.0000000 Ok
0E 1E PREDICT
1.0000000 Ok
1E 0E PREDICT
1.0000000 Ok
1E 1E PREDICT
1.0000000 Ok
Проверьте это самостоятельно. Заметим, что с Эпохи 2 коэффициенты перестают меняться.
Операция "Исключающее ИЛИ"
Теперь исходные данные будут выглядеть так:
x1=0 x2=0 y_true=0
x1=0 x2=1 y_true=1
x1=1 x2=0 y_true=1
x1=1 x2=1 y_true=0
Аналогично изменится только слово "epochs":
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0E 0E 0E T
0E 1E 1E T
1E 0E 1E T
1E 1E 0E T
CR
LOOP ;
После обучения (тоже 10 эпох), получим:
0E 0E PREDICT
1.0000000 Ok
0E 1E PREDICT
1.0000000 Ok
1E 0E PREDICT
0.0000000 Ok
1E 1E PREDICT
0.0000000 Ok
Что не является правильным результатом. Даже после Эпохи 9 коэффициенты меняются. Можете по экспериментировать самостоятельно с гораздо большим количеством эпох. Результат аналогичный даже после 1000 эпох ("1000 epochs"). Задача "Исключающее ИЛИ" не разрешима на персептроне, необходимо более сложные модели для такой простой операции.
Чтобы получить корректные результаты для каждого примера вводите код заново в новое окно консоли, или инициализируйте переменные заново:
0E w1 F!
0E w2 F!
0E b F!
Пример 1 - логическое "И" (окончательный код, можно "копи пастить")
S" lib/include/float2.f" INCLUDED
FVARIABLE w1
FVARIABLE w2
FVARIABLE b
0E w1 F!
0E w2 F!
0E b F!
FVARIABLE x1 FVARIABLE x2
FVARIABLE learning_rate
0.1E learning_rate F!
FVARIABLE y_true
: P ( F: x1 w1 x2 w2 b -> F: y_pred ) FSWAP FROT F* F+ FSWAP FROT F* F+ F0< IF 0E ELSE 1E THEN ;
: T ( F: x1 x2 y_true -> V: w1 w2 b ) \ x1 x2 y_true T - формат вызова слова
y_true F! \ x1 x2 y_true -> x1 x2
FOVER x1 F! FDUP x2 F! \ x1 x2 -> x1 x2 x1=x1 x2=x2
w1 F@ FSWAP w2 F@ b F@ \ x1 x2 -> x1 w1 x2 w2 b
P \ x1 w1 x2 w2 b -> y_pred
y_true F@ FSWAP F- \ y_pred -> y_true-y_pred=error
learning_rate F@ F* \ error -> error*learning_rate
FDUP x1 F@ F* w1 F@ F+ w1 F! \ error*learning_rate -> error*learning_rate w1=error*learning_rate*x1+w1
FDUP x2 F@ F* w2 F@ F+ w2 F! \ error*learning_rate -> error*learning_rate w2=error*learning_rate*x2+w2
b F@ F+ b F! \ error*learning_rate -> b=error*learning_rate+b
." Обновленные веса: w1 = " w1 F@ F. ." w2 = " w2 F@ F. ." b = " b F@ F. CR
;
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0E 0E 0E T
0E 1E 0E T
1E 0E 0E T
1E 1E 1E T
CR
LOOP ;
10 epochs
: PREDICT ( x1 x2 -> y. )
w1 F@ FSWAP w2 F@ b F@ P F.
;
0E 0E PREDICT
0E 1E PREDICT
1E 0E PREDICT
1E 1E PREDICT
Пример 2 - логическое "ИЛИ"
S" lib/include/float2.f" INCLUDED
FVARIABLE w1
FVARIABLE w2
FVARIABLE b
0E w1 F!
0E w2 F!
0E b F!
FVARIABLE x1 FVARIABLE x2
FVARIABLE learning_rate
0.1E learning_rate F!
FVARIABLE y_true
: P ( F: x1 w1 x2 w2 b -> F: y_pred ) FSWAP FROT F* F+ FSWAP FROT F* F+ F0< IF 0E ELSE 1E THEN ;
: T ( F: x1 x2 y_true -> V: w1 w2 b ) \ x1 x2 y_true T - формат вызова слова
y_true F! \ x1 x2 y_true -> x1 x2
FOVER x1 F! FDUP x2 F! \ x1 x2 -> x1 x2 x1=x1 x2=x2
w1 F@ FSWAP w2 F@ b F@ \ x1 x2 -> x1 w1 x2 w2 b
P \ x1 w1 x2 w2 b -> y_pred
y_true F@ FSWAP F- \ y_pred -> y_true-y_pred=error
learning_rate F@ F* \ error -> error*learning_rate
FDUP x1 F@ F* w1 F@ F+ w1 F! \ error*learning_rate -> error*learning_rate w1=error*learning_rate*x1+w1
FDUP x2 F@ F* w2 F@ F+ w2 F! \ error*learning_rate -> error*learning_rate w2=error*learning_rate*x2+w2
b F@ F+ b F! \ error*learning_rate -> b=error*learning_rate+b
." Обновленные веса: w1 = " w1 F@ F. ." w2 = " w2 F@ F. ." b = " b F@ F. CR
;
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0E 0E 0E T
0E 1E 1E T
1E 0E 1E T
1E 1E 1E T
CR
LOOP ;
10 epochs
: PREDICT ( x1 x2 -> y. )
w1 F@ FSWAP w2 F@ b F@ P F.
;
0E 0E PREDICT
0E 1E PREDICT
1E 0E PREDICT
1E 1E PREDICT
Пример 3 - "Исключающее ИЛИ"
S" lib/include/float2.f" INCLUDED
FVARIABLE w1
FVARIABLE w2
FVARIABLE b
0E w1 F!
0E w2 F!
0E b F!
FVARIABLE x1 FVARIABLE x2
FVARIABLE learning_rate
0.1E learning_rate F!
FVARIABLE y_true
: P ( F: x1 w1 x2 w2 b -> F: y_pred ) FSWAP FROT F* F+ FSWAP FROT F* F+ F0< IF 0E ELSE 1E THEN ;
: T ( F: x1 x2 y_true -> V: w1 w2 b ) \ x1 x2 y_true T - формат вызова слова
y_true F! \ x1 x2 y_true -> x1 x2
FOVER x1 F! FDUP x2 F! \ x1 x2 -> x1 x2 x1=x1 x2=x2
w1 F@ FSWAP w2 F@ b F@ \ x1 x2 -> x1 w1 x2 w2 b
P \ x1 w1 x2 w2 b -> y_pred
y_true F@ FSWAP F- \ y_pred -> y_true-y_pred=error
learning_rate F@ F* \ error -> error*learning_rate
FDUP x1 F@ F* w1 F@ F+ w1 F! \ error*learning_rate -> error*learning_rate w1=error*learning_rate*x1+w1
FDUP x2 F@ F* w2 F@ F+ w2 F! \ error*learning_rate -> error*learning_rate w2=error*learning_rate*x2+w2
b F@ F+ b F! \ error*learning_rate -> b=error*learning_rate+b
." Обновленные веса: w1 = " w1 F@ F. ." w2 = " w2 F@ F. ." b = " b F@ F. CR
;
: epochs ( N -> )
0 DO
." Эпоха " I . CR
0E 0E 0E T
0E 1E 1E T
1E 0E 1E T
1E 1E 0E T
CR
LOOP ;
10 epochs
: PREDICT ( x1 x2 -> y. )
w1 F@ FSWAP w2 F@ b F@ P F.
;
0E 0E PREDICT
0E 1E PREDICT
1E 0E PREDICT
1E 1E PREDICT
Как и было сказано ранее отличие всех трех примеров только в коде слова "epochs".