Не паникуйте!

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

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

Что такое нейронная сеть?

«… вычислительная система, состоящая из ряда простых, тесно взаимосвязанных обрабатывающих элементов, которые обрабатывают информацию в соответствии с их динамическим откликом состояния на внешние входные данные». - Д-р Роберт Хехт-Нильсен

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

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

Несколько слов о нейронной сети:

  1. Единственный узел в этой сети вместе с его входом и выходом может называться «персептроном».
  2. Показанная выше сеть является двухуровневой, потому что мы по соглашению не учитываем входной уровень.
  3. Количество узлов в любом слое может быть разным.
  4. Количество скрытых слоев тоже может быть разным. Мы даже можем создать сеть без скрытых слоев. Хотя нужно иметь в виду, что то, что мы можем, не означает, что мы должны.
  5. Жизненно важно правильно настроить модель нейронной сети перед ее запуском, иначе вы можете в конечном итоге взорвать всю Мультивселенную! Не совсем.

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

Мы будем использовать Python 3 и NumPy для построения нашей нейронной сети. Мы используем Python 3, потому что это круто (читайте: популярно и имеет множество суперспособностей в виде пакетов и фреймворков для машинного обучения). А NumPy - это пакет научных вычислений для Python, который упрощает реализацию большого количества математики (читайте: делает нашу жизнь немного проще и проще).

Мы не будем использовать слишком часто используемый и утомительный набор данных «Прогноз цен на жилье» в нашем руководстве. Вместо этого мы будем использовать набор данных Kaggle’s Titanic: Machine Learning from Disaster, чтобы предсказать, кто выжил бы на тонущем Титанике.

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

Что мы будем строить?

Мы будем строить двухуровневую нейронную сеть со следующими характеристиками:

  1. Количество входных узлов = Количество входных функций = 6
  2. Количество узлов в скрытом слое = 4
  3. Количество выходных узлов = 1
  4. Тип выхода = двоичный (0 - мертв, 1 - выжил)

Что такое наш набор данных?

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

Примечание. Вы можете ввести код прямо сейчас. Или просто откройте файл first neural network.py , который вы только что скачали вместе с набором данных. Я бы порекомендовал вам тип.

Файл train.csv содержит следующие сведения (а также их значение):

  1. PassengerId : уникальный идентификатор для идентификации каждого пассажира в нашем наборе данных.
  2. Посадка: порт посадки (C = Шербур; Q = Квинстаун; S = Саутгемптон).
  3. Pclass: класс билета
  4. Имя: Имя пассажира.
  5. Пол: Пол / пол пассажира.
  6. Возраст: возраст пассажира.
  7. SibSp: количество братьев и сестер / супругов на борту «Титаника».
  8. Parch: Количество родителей / детей на борту "Титаника".
  9. Билет: номер билета
  10. Тариф: тариф для пассажиров.
  11. Кабина: номер кабины.
  12. Выжившие : независимо от того, выжили они или нет (0 = нет; 1 = да)

Детали, пронумерованные с 1 по 11, называются атрибутами или функциями нашего набора данных, таким образом, формируя входные данные нашего модель. Номер детали 12 называется выходом нашего набора данных. По соглашению функции представлены X, тогда как выходные данные представлены Y.

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

А теперь давайте начнем кодирование.

import math
import os
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

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

def preprocess():
    # Reading data from the CSV file
    data = pd.read_csv(os.path.join(
                        os.path.dirname(
                            __file__), 'train.csv'),
                                header=0, delimiter=",", quoting=3)
    #Converting data from Pandas DataFrame to Numpy Arrays
    data_np = data.values[:, [0, 1, 4, 5, 6, 7, 11]]
    X = data_np[:, 1:]
    Y = data_np[:, [0]]
    
    # Converting string values into numeric values
    for i in range(X.shape[0]):
        if X[i][1] == 'male':
            X[i][1] = 1
        else:
            X[i][1] = 2
        if X[i][5] == 'C':
            X[i][5] = 1
        elif X[i][5] == 'Q':
            X[i][5] = 2
        else:
            X[i][5] = 3
        if math.isnan(X[i][2]):
            X[i][2] = 0
        else:
            X[i][2] = int(X[i][2])
    # Creating training and test sets
    X_train = np.array(X[:624, :].T, dtype=np.float64)
    X_train[2, :] = X_train[2, :]/max(X_train[2, :])#Normalizing Age
    Y_train = np.array(Y[:624, :].T, dtype=np.float64)
    X_test = np.array(X[624:891, :].T, dtype=np.float64)
    X_test[2, :] = X_test[2, :]/max(X_test[2, :])  # Normalizing Age
    Y_test = np.array(Y[624:891, :].T, dtype=np.float64)
    return X_train, Y_train, X_test, Y_test

Уф! Это много кода, а мы даже не начали создавать нашу нейронную сеть. Многие учебники предпочитают пропустить этот шаг и предоставить вам данные, которые можно легко использовать в нашей модели нейронной сети. Но я считаю, что это чрезвычайно важный шаг. Поэтому я решил включить этот шаг предварительной обработки в это руководство. Так что же именно происходит? Вот что.

Функция def preprocess() считывает данные из файла "train.csv", который мы загрузили, и создает фрейм данных Pandas, который представляет собой структуру данных, предоставляемую Pandas. (Не нужно сейчас особо беспокоиться о Pandas и DataFrames)

Мы берем наши данные из Pandas DataFrame и создаем массив NumPy. X - это массив NumPy, содержащий наши атрибуты, а Y - массив NumPy, содержащий наши выходные данные. Кроме того, вместо использования всех данных из файла мы используем только 6 атрибутов, а именно (вместе с их измененными представлениями):

  1. Pclass
  2. Пол: 1 для мужчин; 2 для женщин
  3. Возраст
  4. Сибсп
  5. Перчин
  6. Посадили: 1 для C; 2 для Q; 3 для S

Мы конвертируем все данные типа string в значения с плавающей запятой. Мы представляем строковые значения некоторым числовым значением. ПОЧЕМУ? Это сделано потому, что нейронные сети принимают только числовые значения в качестве входных данных. Мы не можем использовать строки в качестве входных данных. По этой причине все данные преобразуются в целые числа / значения с плавающей запятой.

Наконец, мы разделяем наши данные на обучающие и тестовые наборы. Набор данных содержит в общей сложности 891 пример. Итак, мы разделяем наши данные как:

  1. Обучающий набор ~ 70% данных (624 примера)
  2. Тестовый набор ~ 30% данных (267 примеров)

X_train и Y_train содержат обучающий ввод и обучающий вывод соответственно.

X_test и Y_test содержат тестовый вход и тестовый выход соответственно.

Наборы тестов содержат информацию о том, выжил ли пассажир.

Теперь вы можете спросить: «Что только что произошло? Почему мы просто разделили наши данные на обучающие и тестовые наборы? Зачем это нужно? Что такое тренировочный и тестовый наборы? Почему пицца не полезна для здоровья ??? »

Честно говоря, я действительно не знаю ответа на последний вопрос. Мне жаль. Но я могу помочь вам с другими вопросами.

Прежде чем мы попытаемся понять необходимость различных наборов для обучения и тестирования, нам нужно понять, что именно делает нейронная сеть?

Нейронная сеть находит (или пытается найти) корреляцию между входами и выходами.

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

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

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

Цель набора тестов - дать вам объективную оценку производительности вашей конечной сети.

Как правило, мы используем 70% всех доступных данных в качестве данных для обучения, а остальные 30% - в качестве данных тестирования. Это, конечно, не строгое правило и меняется в зависимости от обстоятельств.

Примечание. Кроме того, существует «Набор для перекрестной проверки выдержки» или «Набор для разработки». Этот набор используется для проверки того, какая модель работает лучше всего. Набор проверки (также называемый набором разработчика) является чрезвычайно важным аспектом, и его создание / использование считается важной практикой в ​​машинном обучении. Я пропустил это в этом руководстве по причинам, известным только лорду Волан-де-Морту (хорошо! Я немного поленился).

Примечание. В будущем я напишу подробный пост о распределении наборов данных. В этом посте я расскажу обо всех подробностях и деталях.

А теперь двинемся вперед.

def weight_initialization(number_of_hidden_nodes):
    # For the hidden layer
    W1 = np.random.randn(number_of_hidden_nodes, 6) 
                                          # A [4x6] weight  matrix
    b1 = np.zeros([number_of_hidden_nodes, 1]) # A [4x1] bias matrix
    # For the output layer
    W2 = np.random.randn(1, number_of_hidden_nodes) 
                                           # A [1x4] weight matrix
    b2 = np.zeros([1, 1]) # A [1x1] bias matrix
    
    return W1, b1, W2, b2

Функция def weight_initialization() инициализирует наши веса и смещения для скрытого слоя и выходного слоя. W1 и W2 - веса для скрытого слоя и выходного слоя соответственно. b1 и b2 - смещения для скрытого слоя и выходного слоя соответственно. W1, W2, b1, b2 - матрицы NumPy. Но как мы узнаем, каковы будут размеры этих матриц?

Размеры следующие:

  1. W⁰ = (n⁰, n¹)
  2. b⁰ = (n⁰, 1)

куда; 0 = текущий уровень, 1 = предыдущий уровень, n = количество узлов

Поскольку мы создаем двухуровневую нейронную сеть с 6 входными узлами, 4 скрытыми узлами и 1 выходным узлом, мы используем размеры, как показано в коде.

Зачем инициализировать веса случайными значениями, а не нулями (например, смещением)?

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

Y = W*X + b

Для лучшей аналогии прочтите это: https://stackoverflow.com/a/40525812/4855012

Но подождите, что такое вес и предвзятость?

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

Двигаемся дальше.

def forward_propagation(W1, b1, W2, b2, X):
    Z1 = np.dot(W1, X) + b1 # Analogous to Y = W*X + b
    A1 = sigmoid(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)
    return A2, A1

Функция def forward_propagation() определяет шаг прямого распространения нашего обучения. Так что здесь происходит?

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

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

Но что такое функция активации?

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

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

def compute_cost(A2, Y):
    cost = -(np.sum(np.multiply(
                Y, np.log(A2)) + np.multiply(
                    1-Y,  np.log(1-A2)))/Y.shape[1])
    return cost

Функция def compute_cost() вычисляет стоимость нашей сети. Теперь помните, не паникуйте! Эта одна строка загружена, поэтому не торопитесь, чтобы понять, что она на самом деле делает. Формула расчета стоимости:

стоимость = -1 / м [суммирование (y * log (y ’) + (1-y) * log (1-y’))]

куда; m = общее количество примеров выходных данных, y = фактическое выходное значение, y ’= прогнозируемое выходное значение

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

def back_propagation_and_weight_updation(
                A2, A1, X_train, W2, W1,  b2, b1,
                     learning_rate=0.01):
    dZ2 = A2 - Y_train
    dW2 = np.dot(dZ2, A1.T)/X_train.shape[1]
    db2 = np.sum(dZ2, axis=1, keepdims=True)/X_train.shape[1]
    dZ1 = np.dot(W2.T, dZ2) * (1 - np.power(A1, 2))
    dW1 = np.dot(dZ1, X_train.T)/X_train.shape[1]
    db1 = np.sum(dZ1, axis=1, keepdims=True)/X_train.shape[1]
    W1 = W1 - learning_rate * dW1
    b1 = b1 - learning_rate * db1
    W2 = W2 - learning_rate * dW2
    b2 = b2 - learning_rate * db2
    
    return W1, W2, b1, b2

Функция def back_propagation_and_weight_updation() - это наш шаг обратного распространения и обновления веса. Ну да.

Так что же такое обратное распространение?

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

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

Таким образом, путем обратного распространения ошибки мы обнаруживаем dW1, dW2, db1, db2, которые говорят нам, насколько мы должны изменить наши веса и смещения, чтобы уменьшить ошибку, тем самым снижая стоимость нашей сети. Итак, мы обновляем наши веса и предубеждения.

Так что же такое градиентный спуск?

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

А что такое learning_rate?

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

  1. Превышение: это означает, что мы перепрыгнули минимумы и теперь застряли в подвешенном состоянии. Значение функции затрат будет нестабильно, увеличиваться и уменьшаться, и делать все, что угодно, но не достигнет глобальных минимумов.
  2. Сверхдлительное время тренировки: сеть тренируется так долго, что ваше тело превращается в скелет.

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

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

Итак, связав все эти функции вместе, мы напишем финальную функцию.

if __name__ == "__main__":
    # STEP 1: LOADING AND PREPROCESSING DATA
    X_train, Y_train, X_test, Y_test = preprocess()
    
    # STEP 2: INITIALIZING WEIGHTS AND BIASES
    number_of_hidden_nodes = 4
    W1, b1, W2, b2 = weight_initialization(
                        number_of_hidden_nodes)
    
    # Setting the number of iterations for gradient descent
    num_of_iterations = 50000
    all_costs = []
    
    for i in range(0, num_of_iterations):
        # STEP 3: FORWARD PROPAGATION
        A2, A1 = forward_propagation(W1, b1, W2, b2, X_train)
        
        # STEP 4: COMPUTING COST
        cost = compute_cost(A2, Y_train)
        all_costs.append(cost)
        
        # STEP 5: BACKPROPAGATION AND PARAMETER UPDATTION
        W1, W2, b1, b2 = back_propagation_and_weight_updation(
                        A2, A1,X_train, W2, W1, b2, b1)
        
        if i % 1000 == 0:
            print("Cost after iteration "+str(i)+": "+str(cost))
    
    # STEP 6: EVALUATION METRICS
    # To Show accuracy of our training set
    A2, _ = forward_propagation(W1, b1, W2, b2, X_train)
    pred = (A2 > 0.5)
    print('Accuracy for training set: %d' % float((
            np.dot(Y_train, pred.T) + np.dot(
               1-Y_train, 1-pred.T))/float(Y_train.size)*100) + '%')
    # To show accuracy of our test set
    A2, _ = forward_propagation(W1, b1, W2, b2, X_test)
    pred = (A2 > 0.5)
    print('Accuracy for test set: %d' % float((
            np.dot(Y_test, pred.T) + np.dot(
                1-Y_test, 1-pred.T))/float(Y_test.size)*100) + '%')
    # STEP 7: VISUALIZING EVALUATION METRICS
    # Plot graph for gradient descent
    plt.plot(np.squeeze(all_costs))
    plt.ylabel('Cost')
    plt.xlabel('Number of Iterations')
    plt.show()

Мы делаем 50 000 повторений нашего обучения. num_of_iterations также является гиперпараметром. После обучения мы вычисляем точность нашей обученной сети на данных обучения. Получается 79%.

Затем мы проверяем, насколько хороша наша сеть в плане обобщения, используя наши тестовые данные. Эта точность составляет 81%.

Функция стоимости уменьшается, как показано на следующем графике. Потому что графики классные!

Как видите, стоимость нашей сети сначала резко снижается, а затем медленно снижается, прежде чем стабилизироваться.

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

Таким образом, мы можем предсказать, кто выжил бы при затоплении Титаника RMS с точностью 81%. Неплохо для простой двухслойной модели. Поздравляю. Вы создали свою первую нейронную сеть.

Теперь ты волшебник!

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

Сделай сам: попробуйте изменить гиперпараметры (скорость обучения, количество итераций и количество скрытых узлов), чтобы увидеть, как это повлияет на сеть. Кроме того, попробуйте изменить функцию активации в A1 с сигмоидной на tanh.

Итак, как нам улучшить эту сеть? Как нам это улучшить? Есть много советов, приемов и приемов, которые можно использовать, и вы можете узнать о них в Университете Интернета.

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

Есть вопросы или отзывы? Оставьте комментарий