Это шестая статья из моей серии «Автоматизация игр с помощью Python».

"источник"

Часть 1: Как я создал бота на питоне для автоматизации тактической MMORPG. Это объясняет мою мотивацию и игру, которую я автоматизирую.

Часть 2: Как управлять мышью и клавиатурой с помощью Python для автоматизации. В ней подробно рассматриваются основные функции, необходимые для автоматизации.

Часть 3: Обертка, которую я сделал, чтобы легко реализовать поиск изображений в вашей программе на Python и как ее использовать: Простое распознавание изображений для автоматизации с помощью Python

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

Часть 5: Как я создал собственный набор данных для машинного обучения говорит сама за себя.

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

  • Изображения - это просто массивы пикселей, содержащие числа.
  • Нам нужно разделить данные на две части (одну для обучения и одну для тестирования).
  • Как собственно получить эти массивы.
  • Что такое одно горячее кодирование.

Это важные концепции, если вы не знакомы с ними, прочтите статью.

Нейронные сети

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

Так как же преобразовать вектор (наши пиксели, массив размером 1d [3, 6, 8 ……]) в вероятность? Например: «Я на 94% уверен, что это изображение кошки».

Входные веса, смещение и линейный классификатор

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

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

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

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

Это изображение из Стэнфордского CS231 действительно передает интуицию:

Предположим, что изображение имеет длину 4 пикселя, и мы хотим классифицировать, если это кошка, собака или корабль:

Для каждого пикселя мы умножаем его на веса категорий и добавляем смещение категории, например, чтобы получить оценку кошки, которую вы делаете:

0.2 * 56 + -0.5 * 231 + 0.1 * 24 + 2.0 * 2 + 1.1 = — 96.8

Теперь, в этом простом примере, оценка собаки самая высокая, что означает, что веса установлены неправильно. Но вы интуитивно понимаете, как веса влияют на классификатор.

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

на этом уровне нет понятия нейронной сети. Но эти концепции лежат в основе того, как нейронные сети действительно работают.

Нейроны

В нейронной сети нейроны играют огромную роль и они не сложны:

Нейрон - это просто функция, которая принимает некоторый ввод, вычисляет на нем свою функцию активации и выводит результат.

Функция активации - это функция, которая применяется к входу, в простой модели, которую мы собираемся использовать, это будет сигмовидная функция. Это очень простая математическая функция:

источник: Википедия

Видите, что мы сделали выше с весами и всем остальным? Фактически это можно рассматривать как нейронную сеть с одним нейроном:

источник: мои потрясающие навыки рисования, кот: пексели

Этот маленький сигма-знак в центре нейрона (кружок) - это функция активации сигмовидной кишки.
Как вы можете видеть, нейрон получает результат уравнения:

a = W.T * X + b (a - действительное число)

примечание: мы производим умножение сразу для всех массивов, потому что это проще и быстрее. По этой же причине мы используем W.T, что означает транспонирование W. Если вы не разбираетесь в линейной алгебре, не беспокойтесь об этом.

А затем он вычисляет сигмовидную функцию по:

выход = сигмоид (а)

Это даст нам результат от 0 до 1. И из этого мы можем быстро сделать, если вывести результат:

if result < 0.5 :
    print("not a cat")
else :
    print("it's a cat !")

Нелинейные модели

Это было круто, но это все еще линейная модель, а это значит, что она не может правильно классифицировать сложные данные, а изображение довольно сложное. Итак, как мы это делаем?

Укладывайте больше нейронов!

В нейронной сети есть так называемые слои. Есть три типа слоев:

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

Источник: все еще рисую, чего вы ожидали?

Этот рисунок может показаться трудным для понимания, но на самом деле его очень легко понять.

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

На самом деле это работает точно так же, за исключением того, что у нас больше весов и смещений:

Каждый узел имеет свой собственный набор весов и смещений, которые используются, когда данные отправляются с одного уровня на другой.
Итак, у нас есть 2 слоя, которые отправляют свой вывод другому (входной и скрытый) .
поэтому весовой массив W имеет форму (3, 2):

Например :

[

[0.2, 4, 5] (синяя линия)
[2, -0.1, 5] (зеленая линия)
]

Гири W1 имеют вид (2,1)

Что касается предвзятости, это просто количество нейронов, которые пересылаются к следующему, так что:

b.shape = (2,1),
b1.shape = (1,1) (в основном число)

Активация нейронов

Каждый нейрон имеет сигмовидную функцию активации, которая выводит число от 0 до 1. Это означает, что каждый нейрон будет «срабатывать» (выводить высокое значение) или нет (выводить низкое значение). в зависимости от его ввода. Вот почему нейронные сети так сильны:
Это позволяет нейронной сети самостоятельно изучать функции.

Например, чтобы обнаружить «8», если у вас сработал нейрон, потому что он увидел верхнее «о» и еще одно срабатывание для нижнего «о», а все остальные нейроны молчали, тогда вы можете с уверенностью предположить, что это «8». И чем больше нейронов и слоев вы складываете, тем более сложные функции можно обнаружить.

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

в нашем предыдущем примере обнаружения числа 8: первый слой нейронов обнаружит верхний и нижний «о», а второй слой будет иметь нейрон срабатывания, если эти два нейрона сработали. Очевидно, для этого потребуется гораздо больше нейронов, но идею вы поняли.

Пример прямого распространения:

Давайте воспользуемся той же нейронной сетью, описанной выше:

Источник: неподвижная краска

Мы будем предсказать результат по трем пикселям (например, изображение кошки или нет).

Наши переменные следующие:

Входной слой X с весами и смещениями W и b.
Второй слой с результатом первого слоя в X1 и весами и смещениями W1 и b1

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

Итак, давайте посчитаем значение первого нейрона: X1 [0]

Выход - это просто каждый входной пиксель с определенными весами для этого нейрона плюс смещение нейрона:

a = X[0] * W[0][0] + X[1] * W[0][1] + X[2] * W[0][2] + b[0]  
X1[0] =  sigmoid(a) # activation function

То же самое и для X1 [1], за исключением того, что мы используем разные веса и смещения.

Упражнение: попробуйте посчитать, как мы получаем X2 [0]

это просто сигмовидная (X1 [0] * W1 [0] + X1 [1] * W1 [1] + b1)

и это все ! Это не сложнее. Но имейте в виду, что это простой пример с 3 входными нейронами.
Чтобы дать вам ощущение масштаба, цветное изображение 1920 * 1080 приведет к 6 220 800 входным нейронам. Глубокая нейронная сеть может иметь около 1000 или более слоев, поэтому на каждый слой приходится миллионы весов и смещений.

Так вы поймете, почему ИИ часто требует огромных вычислительных мощностей.

Классификация по нескольким классам

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

источник: я и я

Все почти так же, за исключением того, что теперь у нас есть 3 выходных нейрона. И вместо того, чтобы использовать фразу «если выше 0,5, то это кошка», мы просто берем выходной нейрон, который активировал больше всего (тот, у которого наибольшее значение).

И вот здесь-то и интересно "горячее" кодирование, потому что мы можем просто взять выходной вектор-столбец X2. Примените к нему максимальную функцию (максимальное значение станет единицей, а остальное - 0) и бум, у вас есть правильная кодировка.

Пример :

Самое популярное кодирование для лодки:

[0, 0, 1]

И выход (X2) был

[0.3, 0.2, 0.8]

Применяем максимальную функцию

[0, 0, 1]

Таким образом, мы можем извлечь прогноз «лодки» прямо из выходных данных.

Обучение

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

Функция затрат

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

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

Для простоты я сейчас определю его для одного нейрона.

источник: я и LateX

Итак, давайте посмотрим, что у нас здесь:

n - количество элементов в обучающих данных (например: 455)
Суммируем по каждому элементу в обучающих данных
a - вывод сети (например: 0,384)
y - желаемый результат (например: 1).

И это дает нам положительное число (C ›0), которое будет обозначать, насколько мы хороши. Чем ближе к 0 функция стоимости, тем лучше нейронная сеть.

Чтобы вы поняли, как это работает, давайте посчитаем несколько.
(мы будем использовать только один пример, потому что он упрощает расчет)

Вот пример, когда сеть довольно близко подошла к желаемому результату:

n = 1
a = 0.95
y = 1

источник: я и LateX

Это дает нам: 0,0512933, низкий балл, что хорошо.

С другой стороны, если бы у нас было

a = 0.33

источник: я и LateX

Получаем: 1.10866

Обратите внимание, что функция стоимости работает с несколькими нейронами и несколькими примерами, что позволяет нам сразу увидеть, как мы работаем на всем обучающем наборе.

Теперь у нас есть функция, которая сообщает нам, как работают наши параметры W и b. Итак, теперь нам нужно только минимизировать эту функцию (например, найти параметры W и b, которые будут выводить наименьшее возможное значение)

Градиентный спуск

Когда вы изучаете новый навык, вы не сразу овладеваете им, но, изучая, вы становитесь лучше мало-помалу, ну и машинное обучение точно так же. Итак, сначала мы инициализируем веса и смещения случайными числами, очень маленькими числами в диапазоне 0,01, потому что в этом случае алгоритм работает лучше. Это связано с функцией активации.

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

Это очень простой алгоритм:

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

Если бы у вас был только один нейрон, у вас было бы только одно W и b, поэтому график выглядел бы так:

График, построенный с использованием Wolfram Alpha

Вы можете видеть каждый шаг градиентного спуска к минимуму.

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

Применение градиентного спуска к нашей нейронной сети

Так как же найти самый крутой маршрут? Мы вычисляем частные производные функции стоимости J и обновляем наши параметры (например, здесь W):

источник: я и LateX

Альфа здесь - это скорость обучения, то есть насколько большой шаг мы делаем под гору. Важно не устанавливать слишком высокое значение, иначе вы превысите минимальный уровень и действительно усугубите ситуацию. Например :

отредактированный график из wolframAlpha

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

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

Обратное распространение

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

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

Я знаю, что это расстраивает, но если вы действительно хотите погрузиться в это, вот отличное объяснение:

Http://neuralnetworksanddeeplearning.com/chap2.html

Предупреждаю: если вам показалось, что мой пост длинный, это ничто по сравнению с той статьей.

Вывод

И это все ! Это может показаться обманом. Мы получаем огромный набор данных с изображениями кошек, позволяем алгоритму научиться устанавливать миллионы параметров, чтобы обнаруживать кошку с терафлопами вычислительной мощности, и, тем не менее, он работает.

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

Если вам понравилась моя статья, загляните в мой блог, чтобы узнать больше на https://brokencode.io/ :)