Первые шаги. Часть 1. Логистическая регрессия и "Титаник" - aeDeaf/physlearn GitHub Wiki

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


Исходный код

from physlearn.NeuralNet.NeuralNet import NeuralNet
from physlearn.examples import Titanic
import matplotlib.pyplot as plt
%matplotlib inline

В первой строке мы импортируем класс NeuralNet, предоставляющий нам возможность работать с нейронными сетями на высоком уровне.

Во второй строке импортируется модуль Titanic, который подготовит данные для обучения. Далее импортируется модуль matplotlib, который отвечает за вывод графиков.

Последняя строка необходима только в том случае, если вы работаете в среде Jupyter Notebook. В противном случае, данная строка приведет только к ошибке исполнения, и никакого смысла в ней нет.


Далее, получим данные при помощи модуля Titanic:

(learn_data, learn_output), (cv_data, cv_output) = Titanic.create_datasets(0.2)

Для получения данных используется функция create_datasets. Как легко заметить, данная функция возвращает два набора данных - learn и cv, которые представляют из себя обучающую и тестовую выборки. Единственный аргумент данной функции - это процент (если быть более точным - отношение) размера тестовой выборки к общему размеру набора данных. Стандартным является разбиение 80% / 20% - то есть обучение происходит на 80% данных, а проверка - на оставшихся 20%.


Далее, создается объект типа NeuralNet:

net = NeuralNet(-7, 7)

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


Затем описывается вся архитектура нейронной сети:

net.add_input_layer(3)
net.add(5, net.sigmoid)
net.add(5, net.sigmoid)
net.add_output_layer(1, net.sigmoid)

Методы add_input_layer и add_output_layer добавляют входной и выходной слои нейронной сети соответственно. Метод add добавляет скрытый слой. Во всех случаях первый параметр - это количество нейронов в слое, второй параметр (когда он присутствует) - функция активации слоя. Указывать функцию активации входного слоя нельзя (да не очень то и нужно).

В данном случае у нас три входных нейрона (поскольку предсказание ведется на основе трех параметров - класса каюты, пола и возраста), по пять нейронов в двух скрытых слоях, а так же один выходной нейрон (поскольку нам надо классифицировать все данные на два класса - выжил / не выжил, или, что тоже самое 1 / 0).


Далее необходимо "скомпиилировать" нашу нейронную сеть:

net.compile()

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


Далее установим тип нашей нейронной сети:

net.set_train_type('logistic')

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


Далее происходит обучение нейронной сети и вывод графика ценовой функции:

cost_list = net.train(learn_data, learn_output, 50, 100000, 0.001)
plt.plot(cost_list)
plt.show()

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

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

Предпоследний параметр - это количество итераций обучения, а последний - скорость обучения. Помимо этого, в данном участке кода происходит отрисовка графика зависимости ценовой функции от номера итерации. Конкретный вид графика зависит от начальных условий (которые случайны) но приблизительно он должен выглядеть В случае работы в среде Jupyter Notebook строчку plt.show() можно опустить.


Затем можно посчитать процент "правильных ответов" нашей нейронной сети на обучающих и тестовых данных:

ok_class = 0
output = net.run(learn_data)[0]
pred_clases = []
for item in output:
    if item >= 0.5:
        pred_clases.append(1)
    else:
        pred_clases.append(0)
for index, _ in enumerate(pred_clases):
    if pred_clases[index] == learn_output[0][index]:
        ok_class += 1
        
ok_percent = (ok_class / output.shape[0]) * 100
print('ok percent on learn data: ', ok_percent, '%')
ok percent on learn data:  81.08581436077058 %
ok_class = 0
output = net.run(cv_data)[0]
pred_clases = []
for item in output:
    if item >= 0.5:
        pred_clases.append(1)
    else:
        pred_clases.append(0)
for index, _ in enumerate(pred_clases):
    if pred_clases[index] == cv_output[0][index]:
        ok_class += 1
        
ok_percent = (ok_class / output.shape[0]) * 100
print('ok percent on cv data: ', ok_percent, '%')
ok percent on cv data:  77.62237762237763 %

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

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


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