Tensorflow для всего

Как построить обычную нейронную сеть с помощью Tensorflow?

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

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

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

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

Интерпретация ванильной нейронной сети

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

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

Создайте простую нейронную сеть с прямой связью

Для начала давайте начнем с очень простой сети с:

  • Две функции ввода
  • Два скрытых слоя, каждый с двумя нейронами
  • Функция активации сигмоина

В Tensorflow нам нужно объявить заполнители и переменные перед установлением каких-либо отношений. Заполнители - это способ tenorflow для объявления функций ввода и вывода. В нашем случае X и Y - заполнители. Значения-заполнители передаются в сеть на каждом шаге прямого и обратного распространения.
Переменные в тензорном потоке - это те, которые обучаются. В этом случае переменными являются веса и смещения.

Сначала мы объявляем заполнители следующим образом:

n_features = 2
n_dense_neurons_1 = 2
x = tf.placeholder(float, (None, n_features))
y = tf.placeholder(dtype=float)

Затем мы объявляем веса и смещения для первого скрытого слоя и вставляем их в сигмовидную активацию.

W1 = tf.Variable(tf.random_normal([n_features, n_dense_neurons_1]))
b1 = tf.Variable(tf.zeros(n_dense_neurons_1))
z1 = tf.add(tf.matmul(x, W1), b1)
a1 = tf.sigmoid(z1)

Затем мы устанавливаем второй скрытый слой

n_dense_neurons_2 = 2
W2 = tf.Variable(tf.random_normal([n_dense_neurons_1, n_dense_neurons_2]))
b2 = tf.Variable(tf.zeros(n_dense_neurons_2))
z2 = tf.add(tf.matmul(a1, W2), b2)
a2 = tf.sigmoid(z2)

И выходной слой

n_dense_neurons_3 = 1
W3 = tf.Variable(tf.random_normal([n_dense_neurons_2, n_dense_neurons_3]))
b3 = tf.Variable(tf.zeros(n_dense_neurons_3))
z3 = tf.add(tf.matmul(a2, W3), b3)
a3 = tf.sigmoid(z3)

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

Слегка сложная сеть с регуляризацией

Для своего проекта я разработал более глубокую нейронную сеть с

  • 21 функция,
  • 2 скрытых слоя по 20 и 10 нейронов соответственно
  • Регуляризация отсева для борьбы с переобучением

n_features = 21
n_dense_neurons_1 = 20

x = tf.placeholder(float, (None, n_features))
y = tf.placeholder(dtype=float)

W1 = tf.Variable(tf.random_normal([n_features, n_dense_neurons_1]))
b1 = tf.Variable(tf.zeros(n_dense_neurons_1))

z1 = tf.add(tf.matmul(x, W1), b1)
a1 = tf.sigmoid(z1)

drop_out_1 = tf.nn.dropout(a1, 0.5)
n_dense_neurons_2 = 10

W2 = tf.Variable(tf.random_normal([n_dense_neurons_1, n_dense_neurons_2]))
b2 = tf.Variable(tf.zeros(n_dense_neurons_2))

z2 = tf.add(tf.matmul(drop_out_1, W2), b2)
a2 = tf.sigmoid(z2)

drop_out_2 = tf.nn.dropout(a2, 0.5)
n_dense_neurons_3 = 5

W3 = tf.Variable(tf.random_normal([n_dense_neurons_2, n_dense_neurons_3]))
b3 = tf.Variable(tf.zeros(n_dense_neurons_3))

z3 = tf.add(tf.matmul(drop_out_2, W3), b3)
a3 = tf.sigmoid(z3)
n_dense_neurons_4 = 1

W4 = tf.Variable(tf.random_normal([n_dense_neurons_3, n_dense_neurons_4]))
b4 = tf.Variable(tf.zeros(n_dense_neurons_4))

z4 = tf.add(tf.matmul(a3, W4), b4)
a4 = tf.sigmoid(z4)

Большая часть кода здесь остается неизменной, за исключением того, что после каждой активации скрытого слоя мы помещаем слой исключения с помощью метода tenorflow nn.dropout (), передавая дробную часть входам, которые необходимо сохранить, в качестве второго аргумента.

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

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

loss = tf.reduce_sum(-(y*tf.math.log(a4) + (1-y)*tf.math.log(1-a4)))

и оптимизатор Адамса для оптимизации потерь

optimizer = tf.train.AdamOptimizer(learning_rate=0.0001, beta1=0.9, beta2=0.999, epsilon=1e-08, use_locking=False,
    name='Adam')
train = optimizer.minimize(loss)

Обучение

Чтобы обучить нашу модель тензорному потоку, нам сначала нужно инициализировать переменные, используя

sess = tf.Session()
init = tf.global_variables_initializer()  
sess.run(init)

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

sess.run(train, feed_dict = {x:x_example, y:y_example})

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

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

training_epochs = 200
batch_size = 16
train_size = x_data.shape[0]

for epoch in tqdm(range(training_epochs)):
    X = x_data.copy()
    Y = y_data.copy()
    for batch in range(int(train_size/batch_size)+1):
        
        if X.shape[0] > batch_size:
            random_index = random.sample(list(range(X.shape[0])), batch_size)

            x_sample = np.array(X.loc[random_index, :])
            y_sample = np.array(Y.loc[random_index])

            X = X[~X.index.isin(random_index)].reset_index(drop=True)
            Y = Y[~Y.index.isin(random_index)].reset_index(drop=True)
        else:
            x_sample = np.array(X)
            y_sample = np.array(Y)
            
        sess.run(train, feed_dict = {x:x_sample, y:y_sample})
        
    if (epoch+1)%10 == 0:
        c_train = sess.run(loss, feed_dict = {x:np.array(x_data), y:np.array(y_data)})
        training_pred = sess.run(a4, feed_dict = {x:np.array(x_data), y:np.array(y_data)})
        training_threshold = np.median(training_pred)
        training_pred = [1 if i>training_threshold else 0 for i in training_pred]
        training_accuracy = accuracy_score(y_data, training_pred)
        training_f1 = f1_score(y_data, training_pred)
        
        print("Epoch", (epoch + 1), ": Train loss =", c_train/x_data.shape[0], "Training accuracy =", training_accuracy, 'f1_score:', training_f1)

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

Надеюсь, статья вам понравилась. Далее идут RNN и LSTM с Tensorflow.

А пока спасибо и будьте в безопасности.

Никакая сумма денег не куплена за секунду - Тони Старк, MCU