Адекватность и оптимальность нейронной сети на примере распознавания поворота головы (Adequacy and optimality of the neural network for detecting head rotation)

Автор: | 09.12.2019

Введение
Постановка задачи
Проверка нейронной сети на адекватность
Выбор архитектуры сети
Парадокс нейронной сети
Наклон сигмоидальной функции
Размер и соотношение разнотипных сигналов в обучающей выборке
Линия размежевания разнотипных сигналов в обучающей выборке
Обучение сети положительному (YES) и отрицательному (NO) жестам головой
Анализ и выбор данных
Общие выводы
Программный код для проведения экспериментов
Полезные ссылки

Введение

Первое, что может спросить у вас заказчик программного продукта: «В каких рамках могу я доверять вашей модели нейронной сети (НС) и на сколько она оптимальна?».

Модель адекватна. Сходимость результатов обучения к одному значению (или к очень близкому) — необходимое условие того, что между выходными и входными данными есть какой-то закон, который реализуется нейронной сетью.

Наиболее эффективный способ проверки модели НС на адекватность — сопоставление результатов с классическим решением задачи (если такое решение известно). Например, в задаче определения зависимости между градусами по Цельсию и Фаренгейту можно сопоставить 2 решения:

Достаточное условие адекватности модели — предсказуемость результатов в интервале всего множества данных, включая и те, которые не участвовали в обучении.

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

Постановка задачи

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

В статье Машинное обучение распознавать поворот лица проверялась принципиальная возможность решения этой задачи. Эксперимент проводился для архитектуры сети из 3-х слоев (2-3-1):

На вход сети подаются нормализованные расстояния (right и left) от точки носа до точек глаз. На выходе сети ожидается сигнал 1, если поворот лица вправо, и сигнал 0, если поворот лица влево.

Сеть показала себя работоспособной  — соответствующие повороты отмечаются на экране (см. рисунок выше). Теперь еще необходимо экспериментально подтвердить адекватность НС, выбрать ее оптимальные параметры и архитектуру.

От задачи распознавания поворота (влево и вправо) перейдем к задаче распознавания жестов головой.

Практически во всех странах (за исключением Болгарии): утвердительный жест — кивок головой сверху вниз; отрицательный жест — повороты головой в стороны.

Программный код для проведения экспериментов  приводится для архитектуры сети из 3-х слоев (2-3-1). В этом же разделе описывается, как можно модифицировать программу под другие архитектуры.

Проверка нейронной сети на адекватность

Для обучения берем выборку (DataSet) из 2-х разнотипных изображений лица [RIGHT и LEFT ] с сигналами на входе [0.702… 0.927…] [1.011… 0632…] и соответствующими им сигналами на выходе [1 ] и [0]. На рисунках ниже отражено, как перестраивается НС по мере обучения (сигналы на выходе приближаются к 1 и 0).

Ниже графически отображается сходимость каждого из сигналов.

На рисунке слева показано изменение 2-х сигналов на выходе в зависимости от циклов обучения (Epoch). Зеленым цветом обозначена кривая с ожидаемым значением на выходе 1 [RIGHT].  Синим цветом обозначена кривая с ожидаемым значением на выходе 0 [LEFT ].

Обычно адекватность модели оценивается через обобщающую характеристику — функцию потерь ( Loss). Cреднеквадратичное отклонение – самая простая функция потерь и наиболее часто используемая.  Для более глубокого понимания работы НС в этой работе оцениваем сходимость каждого из сигналов в отдельности.

На рисунке справа отображены результаты тестирования — показана зависимость сигнала на выходе от разницы между сигналами на входе (left-right):

  •  Output signal < 0.1 — положение головы LEFT (синие точки)
  • 0.1<Output signal < 0.9 — положение головы CENTER (красные точки)
  • Output signal > 0.9 — положение головы RIGHT (зеленые точки)

Результаты эксперимента показали адекватность модели:

  • По мере обучения сигналы на выходе приближаются к ожидаемым значениям 1 и 0 (необходимое условие). Определено оптимальное количество циклов обучения Epoch = 500
  • При распознавании сигналы на выходе соответствуют (близки к ожидаемым) сигналам на входе (достаточное условие). Это доказывается через сопоставление результатов с аналитическим решением.

Аналитическое (алгоритмическое) решение:

  • (left-right) <0, тогда Outputsignal = 0 (поворот влево)
  • (left-right) >0, тогда Outputsignal = 1 (поворот вправо)

Выбор архитектуры сети

Уберем скрытый слой в сети, получаем архитектуру (2,1).

Проводим с ней тот же эксперимент, что и для для архитектуры (2-3-1)

А теперь вместо 2-х нейронов на входе используем 1 и еще раз повторяем тот же эксперимент. 

Сигнал на входе для этой НС — разница между значениями (left-right). Напомню, что в предыдущих архитектурах НС на входе  были 2 сигнала — непосредственно значения  right и left.

Вывод. Сеть, с одним нейроном на входе оптимальна для решения поставленной задачи — в виду ее простоты и адекватных результатов эксперимента.

Парадокс нейронной сети

При выборе архитектуры нейронной сети также исследовался вариант сети (2-2-1):

Результат работы такой сети оказался парадоксальным:

При этом результаты для подобных архитектур сетей (2- 3-1), (2- 5-1) и (2-10-1) были адекватными.

Возможно, кто-то сможет объяснить этот парадокс в комментариях.

Наклон сигмоидальной функции

Продолжаем исследования сети:

Нейронная сеть дает на выходе весьма размытый результат.

Из 2-х положений головы  Left и Right большой участок вдоль шкалы сигнала на входе соответствует нейтральному (Center). Необходимо увеличить крутизну зависимости между сигналами на входе (X) и выходе(y), чтобы уменьшить этот участок.

alpha – параметр наклона сигмоидальной функции S(d). Чем больше этот параметр, тем круче функция (угол касательной в точке перегиба функции будет больше).

Возьмем alpha =10. Возникает вопрос, alpha менять только при тестировании или при обучении тоже?

Если меняем alpha при тестировании и при обучении, получим следующий результат:

Как видно из рисунка доля нейтрального участка уменьшилась в 1.5 раза.

Если меняем alpha только при тестировании результат будет следующим:

В этом эксперименте доля нейтрального участка уменьшилась в 10 раз.

Непонятно, каким образом значение параметра alpha  при обучении влияет на наклон сигмоидальной функции при тестировании.

Размер и соотношение разнотипных сигналов в обучающей выборке

До сих пор в экспериментах использовали обучающую выборку (DataSet) только из двух сигналов — с ожидаемыми значениями на выходе сети [10] :

  • 1  (Right);
  • 0 (Left).

Выборка может быть различной:

  • по размерам ([10] или [1010101010])
  • по соотношению разнотипных сигналов([100000000] или [01111111111]).

Были проведены эксперименты с указанными выборками.

При распознавании положение головы принималось экстремально-возможное  (левое или правое).

Внизу рисунка приводятся средние значения сигналов на выходе при распознавании.  Результаты немного ухудшаются для тех сигналов, которых было мало при обучении:

  • 0.941 хуже 0.993  (при ожидаемом сигнале на выходе 1)
  • 0.064 хуже 0.0095  (при ожидаемом сигнале на выходе 0)

Результат для различных выборок (см. рисунки выше) был получен практически одинаковый:

  • при обучении сигналы на выходе сходятся к ожидаемым результатам (0 или 1);
  • при распознавании также получены адекватные результаты (значения сигналов на выходе близкие к 0 или 1).

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

Линия размежевания разнотипных сигналов в обучающей выборке

Между значениями входных сигналов определена линия размежевания (left-right =0). Некоторые из разнотипных сигналов при обучении могут попасть за пределы этой линии (не в свою область).

Оценим, как это может повлиять на результаты обучения и распознавания. На рисунках ниже приводятся результаты экспериментов с выборкой для обучения с ожидаемыми результатами на выходе [101010].

На рисунке слева приведены зависимости для первых 2 сигналов из выборки для обучения.

На следующем рисунках 2 входных сигнала из 6 в выборке для обучения не соответствуют ожидаемым на выходе [101010] — при небольшом повороте головы влево отмечался сигнал 1 (вместо 0), а при повороте головы вправо отмечался сигнал 0 (вместо 1).

На рисунке выше (слева) приведены зависимости для первых 2 сигналов из выборки для обучения. На рисунке ниже приведены зависимости для последних 2 сигналов из выборки для обучения.

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

Вывод. Допустимы в обучающей выборке разнотипные сигналы за пределами линии размежевания сигналов. Но это возможно только в разумных пределах.

Обучение сети положительному (YES) и отрицательному (NO) жестам головой

А теперь попробуем научить сеть с архитектурой (1,1) распознавать утвердительный и отрицательный жесты головой.

Прежде всего необходимо формализовать задачу. Для этого необходимо  ответить на следующие вопросы. Какие данные будем использовать на входе и по каким выходным данным будем принимать решение?

При формализации задачи машинного обучения с помощью НС желательно представить  в той или иной мере аналитический способ решения этой задачи.

Аналитическое (алгоритмическое) решение:

  •  — 0.1< (left-right) <0.1, тогда   YES
  • (left-right) <-0.1 OR (left-right) >0.1, тогда NO

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

  •  Outputsignals = 1   if    -1 < Inputsignals <1
  • Outputsignals = 0   if    Inputsignals<-0.1  OR  Inputsignals >0.1

Для эксперимента используем обучающую выборку   [10111000]  —  4 положения головы сверху вниз и 4 поворота головы в стороны (по 2 влево и вправо). Значения первых 2-х сигналов из выборки выводятся на печать:

Результат просто никакой, даже нечего анализировать, не говоря уже о поисках возможного улучшения.

Попробуем добиться успеха с архитектурой нейронной сети из 3-х слоев (2-3-1). Проводим тот же эксперимент:

Наметилась тенденция к улучшению, но результат все же далек от желаемого. Увеличиваем обучающую выборку [10111111111000000000].

Теперь сделаем эксперимент с архитектурой нейронной сети (2-6-1) и выборкой для обучения [10111111111000000000]:

Меняем параметр наклона сигмоидальной функции alpha=5 (вместо alpha=1) — только для тестирования:

Уже вплотную приблизились к желаемому результату.  Посмотрим еще, какой результат будет для архитектуры сети (5-5-1):

К двум сигналам на входе (расстояниям от точек глаз до точки носа) были добавлены еще 3 параметра:

  • расстояние между угловыми точками рта;
  • расстояние от точки левого угла рта до точки носа;
  • расстояние от точки левого угла рта до точки носа.

Все 5 сигналов нормализованы через деление на расстояние между точками глаз.

Весьма любопытный результат получился. Сколько  «умозаключений» пришлось сделать нейронной сети прежде чем выйти в конце обучения на стабильный и верный путь!

Были еще несколько запусков с архитектурой сети (5-6-1), (5-10-1). В них сеть так и не вышла на правильный путь обучения. Очевидно, для этой задачи имеет смысл вернуться к сети (3-6-1), которая проще и показала стабильные результаты при обучении а также распознавании.

Анализ и выбор данных

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

В последнем эксперименте к задаче обучения сети распознавать положительный (YES) и отрицательный (NO) жесты головой использовались 5 параметров (сигналов на входе):

  1. расстояние от точки левого глаза до точки носа;
  2. расстояние от точки левого глаза до точки носа;
  3. расстояние между угловыми точками рта;
  4. расстояние от точки левого угла рта до точки носа;
  5. расстояние от точки левого угла рта до точки носа.

Все 5 сигналов нормализованы через деление на расстояние между точками глаз.

Оценим, насколько каждый из параметров может быть эффективным.

Первые 2 параметра позволили решить задачу с архитектурой сети (2-3-1). Но все ли так хорошо с ними? Соответствующий результат на выходе получен на основе того, что разница между расстояниями до глаз меняется с положительного значения на отрицательное при повороте головы и близка к нулю при положении головы вдоль вертикальной линии. Но теоретически разница между указанными расстояниями может оставаться равной нулю и при поворотах (влево и вправо), если голова поворачивается  без наклона вбок.

А как влияет 3-й параметр на выбор сетью решения? Практически никак, поскольку его значение при повороте головы не меняется, т.к. расстояние между угловыми точками рта отнесено к расстоянию между точками глаз.  Линии, которые проходят через эти точки, параллельны, следовательно искажения для обоих расстояний при повороте головы одинаковы согласно свойству аффинных преобразований. Так что влияние 3-го параметра может быть даже отрицательным из-за случайного характера его изменений.

Последние 2-а параметра по сути дублируют первые 2. Поэтому пользы от них никакой, лишь бы не навредили.

Так вот почему архитектура нейронной сети (5-5-1) показала худшие результаты, нежели архитектура сети (3-6-1)!

На основе проведенного анализа давайте заново решим, какие выбрать параметры и архитектуру нейронной сети для эффективного решения поставленной задачи. Наиболее подвержены изменениям при повороте головы (влево и вправо) и стабильны при кивке головой сверху вниз 2 расстояния:

  1. расстояние между точками глаз;
  2. расстояние между угловыми точками рта.

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

Параметр необходимо нормализовать. При выборе расстояния для нормализации необходимо учесть 2 фактора:

  • небольшое влияние на это расстояние поворотов головы;
  • независимость параметра от расстояния до камеры.

В качестве такого расстояния можно было бы использовать расстояние от середины между глазами до середины рта. Однако, это не лучшее решение. Все-таки этот размер немного меняется при горизонтальных поворотах (влево и вправо), и еще больше — при кивках головы в вертикальной плоскости.

И тут появилась здравая мысль. А не сравнивать ли нам 2 длины:

  • расстояние между точкой от середины глаз до середины рта;
  • длина ломаной через 3 точки — середина глаз, точка носа и середина рта

Если длины равны, то положение головы соответствует YES, в противном случае NO.  Если брать отношение длин, то получаем нормализованный признак.

Общие выводы

Одним из главных выводов можно сделать следующий. Для простой сети, при малых наборах входных данных и выборки для обучения  могут быть получены лучшие результаты. Как это можно объяснить по простому?

В черчении есть правило нанесения размеров — количество размеров должно быть минимальное и достаточное. Если размеров меньше, то фигура, не может быть определена. Если размеров больше, чем необходимо, то они могут противоречить друг другу, когда по разному определяют объект. Более подробно с этой проблематикой можно ознакомиться в статье Параметризация геометрических моделей.

В математике для решения уравнений с неизвестными требуется соответствующее количество уравнений. Лишние уравнения в лучшем случае есть тождественными, а в худшем — могут по разному описывать задачу.

По аналогии сложность архитектуры нейронной сети обусловлена сложностью решаемой задачи. Для нейронной сети с большим количеством сигналов на входе или выходе требуется разнообразие данных в обучающей выборке, большее количество скрытых слоев и нейронов. Но здесь главное не переусердствовать — данных должно быть минимально-достаточное количество. При этом следует помнить, что плохие данные могут уменьшить положительное влияние хороших данных.

Чаще всего выбор архитектуры сети, проверка ее адекватности и оптимальности, выбор данных для обучения — предмет многочисленных экспериментов. Сеть при этом оказывается чёрным ящиком, происходящее в котором загадочно даже для её учителя. Но это не совсем так. Советую ознакомиться со статьей Нейронные сети: 1. Основы или A friendly introduction to Support Vector Machines(SVM). В них совмещаются эмпирическое и математическое понимания природы нейронов как разделяющих гиперплоскостей и функций нечёткой логики. На основе этого вырабатывается  интуитивное понимание для выбора архитектуры сети и соответствующих экспериментов для проверки ее на адекватность и оптимальность.

За 60 лет пути технологии искусственного интеллекта фантастически усовершенствовались. Но ни одна из проблем, объявленных на старте важными, так толком и не решена. Не ищите специалистов по искусственному интеллекту. Ищите специалистов по решению ваших задач.

Программный код для проведения экспериментов

Тестовые примеры вы можете провести самостоятельно, ознакомившись с интерфейсом и программным кодом.

Интерфейс практически такой же, как и в программе из статьи Машинное обучение распознавать поворот лица. Отличия лишь в следующем. После обучения появляется окно, в котором отображены результаты. Для продолжения работы программы нажимается клавиша Q. После этого начинается распознавание. Когда снова нажимаете клавишу Q,  появляется окно, в котором отображаются накопленные результаты распознавания.

Ниже приводится программный код для архитектуры сети 2-3-1. Его можно легко модифицировать под другие используемые в экспериментах архитектуры сетей: 2-6-1, 2-2-1, 5-5-1, 2-1, 1-1.

import time
import sys
from tkinter import *
from tkinter import messagebox as mb
import tkinter as tk
import numpy as np
import cv2
from PIL import Image, ImageTk
from mtcnn.mtcnn import MTCNN
from numpy import exp, array, random, dot
import matplotlib.pyplot as plt

def opencvToTk(frame):
    #Convert an opencv image to a tkinter image, to display in canvas
    rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    pil_img = Image.fromarray(rgb_image)
    tk_image = ImageTk.PhotoImage(image=pil_img)
    return tk_image

def mousePressed(event, data):
    pass

def keyPressed(event, data):
    if event.keysym == "q":
        data.root.destroy()
        plt.title("Testing Data")
        plt.xlabel('Input signal')
        plt.ylabel('Output signal')
        plt.show()
    if event.keysym == "w":
        Save()
        pass

def timerFired(data):
        pass

def Save(event):
 global option
 global X
 global y
 global input_left
 global input_right
 l = input_left
 r = input_right
 X = np.concatenate((X, [[l,r]]))
 print ("Сигналы на входе")
 print (X)
 print ("Сигналы на выходе")
 value = option.get()
 if value == 0:
   y = np.concatenate((y, [[1]]))
   print (y)
 else:
   y = np.concatenate((y, [[0]]))
   print (y)
 print ("Следующий кадр или переходите к обучению (Train)")

def Teach(event): 
    global synapses_hidden
    global synapses_output
    global X
    global y
    global tst
    print ( 'Обучение началось') 
    # обучение сети - цикл из 500 повторений
    for j in range(500):
        print(j)
        # Входной слой ( 2 входа )
        l0 = X
        # Скрытый слой ( 3 скрытых нейрона )
        l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
        # Выходной слой ( 1 выходной нейрон )
        l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
        z=l2[0]
        z=z[0]
        print(z)
        plt.scatter(j,z,label='line', color='green')
        z=l2[1]
        z=z[0]
        print(z)
        plt.scatter(j,z,label='line', color='blue')
        # вычисляем ошибку (используем дельта-правило)
        l2_delta = (y - l2) * (l2 * (1 - l2))
        # получаем ошибку на скрытом слое (используем дельта-правило)
        l1_delta = l2_delta.dot(synapses_output.T) * (l1 * (1 - l1))
        # корректируем веса от скрытых нейронов к выходу
        synapses_output += l1.T.dot(l2_delta)
        # корректируем веса от входов к скрытым нейронам
        synapses_hidden += l0.T.dot(l1_delta)
    print ( 'Обучение закончилось')
    # Печать сигналов на выходе после последнего цикла обучения
    print ( 'Сигналы на выходе после последнего цикла обучения')
    print(l2)
    tst = 1
    plt.title("Training Data")
    plt.xlabel('Epoch')
    plt.ylabel('Output signals')
    plt.show()

def Test(data,l,r):
    global tst
    global synapses_hidden
    global synapses_output
    l0 = np.array([l,r])
    d = r-l
    # Скрытый слой ( 3 скрытых нейрона )
    l1 = 1 / (1 + np.exp(-(l0.dot(synapses_hidden))))
    # Выходной слой ( 1 выходной нейрон )
    l2 = 1 / (1 + np.exp(-(l1.dot(synapses_output))))
    z=0
    if tst == 1: 
        print('Распознавание')
        print('Сигнал на входе')
        print(d) 
        z=l2[0]
        print('Сигнал на выходе')
        print(z)
        #plt.scatter(d,z,label='line', color='blue')
        if z > 0.9: # RIGHT YES
            plt.scatter(d,z,label='line', color='green')
            cv2.putText(data.frame, 'RIGHT', (500,50), 
                    cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2,lineType=cv2.LINE_AA) 
        else: 
         if z >= 0.1:
           plt.scatter(d,z,label='line', color='red')
           cv2.putText(data.frame, 'CENTER', (500,50), 
                 cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 2, lineType=cv2.LINE_AA) 
         else: # LEFT NO
           plt.scatter(d,z,label='line', color='blue')
           cv2.putText(data.frame, 'LEFT', (500,50), 
                   cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), 2, lineType=cv2.LINE_AA) 
 
def cameraFired(data):
    global input_left
    global input_right
 
    data.frame = cv2.GaussianBlur(data.frame, (11, 11), 0)
    image = cv2.cvtColor(data.frame, cv2.COLOR_BGR2RGB)
    result = detector.detect_faces(image)
    try:
    # Capture frame-by-frame
    # Result is an array with all the bounding boxes detected.
        bounding_box = result[0]['box']
        keypoints = result[0]['keypoints']
        cv2.rectangle(data.frame,
          (bounding_box[0], bounding_box[1]),
          (bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]),
          (0,155,255),
          2)
        cv2.circle(data.frame,(keypoints['left_eye']), 3, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['right_eye']), 2, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['nose']), 3, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['mouth_left']), 3, (0,155,255), 2)
        cv2.circle(data.frame,(keypoints['mouth_right']), 3, (0,155,255), 2)

        cv2.line(data.frame,(keypoints['left_eye']),keypoints['right_eye'], (0,0,255), 1)
        cv2.line(data.frame,(keypoints['left_eye']),keypoints['nose'], (0,255,0), 2)
        cv2.line(data.frame,(keypoints['right_eye']),keypoints['nose'], (255,0,0), 2)
 
        dX = keypoints['right_eye'][0] - keypoints['left_eye'][0]
        dY = keypoints['right_eye'][1] - keypoints['left_eye'][1]
        dist_norm = np.sqrt((dX ** 2) + (dY ** 2))
 
        dX = keypoints['left_eye'][0] - keypoints['nose'][0]
        dY = keypoints['left_eye'][1] - keypoints['nose'][1]
        dist_left = np.sqrt((dX ** 2) + (dY ** 2))

        dX = keypoints['right_eye'][0] - keypoints['nose'][0]
        dY = keypoints['right_eye'][1] - keypoints['nose'][1]
        dist_right = np.sqrt((dX ** 2) + (dY ** 2))
        # Normalized Features-distances
        input_left = dist_left/dist_norm
        input_right = dist_right/dist_norm
        # Постоянно идет тестирование (даже еще до обучения)
        Test(data, input_left,input_right)
 
        cv2.putText(data.frame, str(input_left), (50,50), 
            cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), lineType=cv2.LINE_AA) 
        cv2.putText(data.frame, str(input_right), (50,100), 
            cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), lineType=cv2.LINE_AA)
 
    except IndexError:
     gotdata = 'null'

def drawCamera(canvas, data):
    data.tk_image = opencvToTk(data.frame)
    canvas.create_image(data.width / 2, data.height / 2, image=data.tk_image)

def redrawAll(canvas, data):
    drawCamera(canvas, data)


def run(width=300, height=300):
    global option
    data.width = width
    data.height = height
    data.camera_index = 0

    data.timer_delay = 100 # ms
    data.redraw_delay = 50 # ms
 
    # Initialize the webcams
    camera = cv2.VideoCapture(data.camera_index)
    data.camera = camera
 
    # Make tkinter window and canvas
    data.root = Tk()
 
    canvas = Canvas(data.root, width=data.width, height=data.height)
    canvas.pack()

    option = BooleanVar()
    R1 = Radiobutton(data.root, text="Right", value = 0, var = option)
    #R1.pack()
    R1.place(x = 50, y = 10, width = 50, height = 20)
    R2 = Radiobutton(data.root, text="Left ", value = 1, var = option)
    #R2.pack()
    R2.place(x = 50, y = 30, width = 50, height = 20)
    option.set(0)

    #print (value)
    btn = Button(data.root, #родительское окно
             text="Save face", #надпись на кнопке
             width=30,height=5, #ширина и высота
             bg="white",fg="black")
    btn.bind("<Button-1>", Save)
    btn1 = Button(data.root, #родительское окно
             text="Train", #надпись на кнопке
             width=30,height=5, #ширина и высота
             bg="white",fg="black")
    btn1.bind("<Button-1>", Teach)
    btn.place(x = 110, y = 10, width = 70, height = 40)
    btn1.place(x = 200, y = 10, width = 70, height = 40)
    # Basic bindings. Note that only timer events will redraw.
    data.root.bind("<Button-1>", lambda event: mousePressed(event, data))
    data.root.bind("<Key>", lambda event: keyPressed(event, data))

    # Timer fired needs a wrapper. This is for periodic events.
    def timerFiredWrapper(data):
        # Ensuring that the code runs at roughly the right periodicity
        start = time.time()
        timerFired(data)
        end = time.time()
        diff_ms = (end - start) * 1000
        delay = int(max(data.timer_delay - diff_ms, 0))
        data.root.after(delay, lambda: timerFiredWrapper(data))

    # Wait a timer delay before beginning, to allow everything else to
    # initialize first.
    data.root.after(data.timer_delay, 
        lambda: timerFiredWrapper(data))

    def redrawAllWrapper(canvas, data):
        start = time.time()

        # Get the camera frame and get it processed.
        _, data.frame = data.camera.read()
        cameraFired(data)

        # Redrawing code
        canvas.delete(ALL)
        redrawAll(canvas, data)

        # Calculate delay accordingly
        end = time.time()
        diff_ms = (end - start) * 1000

        # Have at least a 5ms delay between redraw. Ideally higher is better.
        delay = int(max(data.redraw_delay - diff_ms, 5))

        data.root.after(delay, lambda: redrawAllWrapper(canvas, data))

    # Start drawing immediately
    data.root.after(0, lambda: redrawAllWrapper(canvas, data))

    # Loop tkinter
    data.root.mainloop()

    # Once the loop is done, release the camera.
    print("Releasing camera!")
    data.camera.release()

# ОСНОВНАЯ ПРОГРАММА
detector = MTCNN()
class Struct(object): pass
data = Struct()
var_1 = 0
tst = 0
X = np.array([[0.0,0.0]])
y = np.array([[0.0]])
# Удаляются первые элементы из массива
X = np.delete(X,0,axis=0)
y = np.delete(y,0,axis=0)

#y = np.array([])
input_left = 0.6
input_right = 1.0
# инициализация случайных весов (от -1 до +1) синапсов
np.random.seed(1)
synapses_hidden = 2 * np.random.random((2,3)) - 1 # 2*3 - hidden слой 
synapses_output = 2 * np.random.random((3,1)) - 1 # 3*1 - output слой
print("Подготовьте данные для обучения!")

if __name__ == "__main__":
    run(700, 600)

Для модификации модели НС из архитектуры 2-3-1, например в сеть 2-6-1 в исходном коде достаточно внести изменения в 2-х строчках:

synapses_hidden = 2 * np.random.random((2,6)) - 1 # 2*3 - hidden слой
synapses_output = 2 * np.random.random((6,1)) - 1 # 3*1 - output слой

Для модификации модели НС из архитектуры 2-3-1 в сеть 5-5-1 необходимо увеличить количество параметров в  функции  Test

def Test(data,l,r,m,lm,rm):
 global tst
 global synapses_hidden
 global synapses_output
 l0 = np.array([l,r,m,lm,rm])
...

Также в  функции cameraFired необходимо подготовить эти  параметры  перед запуском функции  Test:

 Test(data, input_left,input_right,input_mouth,input_left_mouth,input_right_mouth)

Делается это аналогично параметрам input_left,input_right.

Для модификации модели НС из архитектуры 2-3-1 в сеть 2-1 в исходном коде необходимо внести изменения в  функции Teach и Test:

def Teach(event): 
    #global synapses_hidden
    global synapses_output
    global X
    global y
    global tst
    print ('Обучение началось') 
    # обучение сети - цикл из 1000 повторений
    for j in range(1000):
        print(j)
        # Входной слой ( 2 входа )
        l0 = X
        # Выходной слой ( 1 выходной нейрон )
        l2 = 1 / (1 + np.exp(-(l0.dot(synapses_output))))
        print(l2)
        # вычисляем ошибку (используем дельта-правило)
        l2_delta = (y - l2) * (l2 * (1 - l2))
        # корректируем веса от входов к выходу 
        synapses_output += l0.T.dot(l2_delta)
    print ( 'Обучение закончилось')
    # Печать сигналов на выходе после последнего цикла обучения
    print ( 'Сигналы на выходе после последнего цикла обучения')
    print(l2)
    tst = 1
def Test(data,l,r):
    global tst
    #global synapses_hidden
    global synapses_output
    l0 = np.array([l,r])
    # Выходной слой ( 1 выходной нейрон )
    l2 = 1 / (1 + np.exp(-(l0.dot(synapses_output))))
    ...

Также в исходном коде необходимо изменить параметры метода random с (3,1) на (2,1) —  в строке инициализации веса synapses_output

#synapses_hidden = 2 * np.random.random((2,3)) - 1 # 2*3 - hidden слой
synapses_output = 2 * np.random.random((2,1)) - 1 # 3*1 - output слой

 

Полезные ссылки:

 

Автор: Николай Свирневский

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *