Підручник з RNN (рекурентна нейронна мережа): приклад TensorFlow
Навіщо нам потрібна рекурентна нейронна мережа (RNN)?
Повторювана нейронна мережа (RNN) дозволяє моделювати одиниці пам’яті для збереження даних і моделювати короткочасні залежності. Він також використовується в прогнозуванні часових рядів для виявлення кореляції даних і закономірностей. Це також допомагає отримати прогнозні результати для послідовних даних, забезпечуючи поведінку, подібну до людського мозку.
Структура штучної нейронної мережі є відносно простою і в основному стосується множення матриць. Під час першого кроку вхідні дані множаться на початково випадкові ваги, а зсув, перетворений за допомогою функції активації, і вихідні значення використовуються для прогнозування. Цей крок дає уявлення про те, наскільки далека мережа від реальності.
Застосованим показником є втрата. Чим вище функція втрат, тим дурніша модель. Щоб покращити знання мережі, потрібна деяка оптимізація шляхом коригування ваг мережі. Стохастичний градієнтний спуск — це метод, який використовується для зміни значень ваг у правильному напрямку. Після того, як коригування зроблено, мережа може використовувати інший пакет даних для перевірки своїх нових знань.
Помилка, на щастя, нижча, ніж раніше, але недостатньо мала. Етап оптимізації виконується ітераційно, доки помилка не буде мінімізована, тобто більше інформації не можна буде отримати.
Проблема з моделлю цього типу полягає в тому, що вона не має пам’яті. Це означає, що вхід і вихід незалежні. Іншими словами, моделі все одно, що було раніше. Це викликає деякі запитання, коли вам потрібно передбачити часові ряди або речення, оскільки мережа потребує інформації про історичні дані або минулі слова.
Щоб подолати цю проблему, було розроблено новий тип архітектури: повторювана нейронна мережа (далі — RNN).
Що таке рекурентна нейронна мережа (RNN)?
A Повторювана нейронна мережа (RNN) є класом Штучна нейронна мережа у якому зв’язок між різними вузлами утворює орієнтований граф, що забезпечує часову динамічну поведінку. Це допомагає моделювати послідовні дані, отримані з мереж прямого зв’язку. Він працює подібно до людського мозку, щоб забезпечити прогнозні результати.
Повторювана нейронна мережа виглядає дуже схожою на традиційну нейронну мережу, за винятком того, що до нейронів додається стан пам’яті. Розрахунок для включення пам’яті простий.
Уявіть просту модель лише з одним нейроном, що живиться пакетом даних. У традиційній нейронній мережі модель створює вихідні дані шляхом множення вхідних даних на вагу та функцію активації. З RNN цей вихід надсилається назад самому собі певну кількість разів. Ми називаємо крок за часом проміжок часу, протягом якого вихід стає вхідним для наступного множення матриці.
Наприклад, на малюнку нижче ви бачите, що мережа складається з одного нейрона. Мережа обчислює множення матриць між введенням і вагою та додає нелінійність за допомогою функції активації. Він стає вихідним за t-1. Цей вихід є входом другого множення матриці.
Нижче ми кодуємо простий RNN у TensorFlow, щоб зрозуміти крок, а також форму результату.
Мережа складається з:
- Чотири входи
- Шість нейронів
- 2-разові кроки
Мережа працюватиме так, як показано на малюнку нижче.
Мережа називається «рекурентною», тому що вона виконує ту саму операцію в кожному квадраті активації. Перед використанням функції активації мережа обчислила ваги входів і попереднього виходу.
import numpy as np import tensorflow as tf n_inputs = 4 n_neurons = 6 n_timesteps = 2 The data is a sequence of a number from 0 to 9 and divided into three batches of data. ## Data X_batch = np.array([ [[0, 1, 2, 5], [9, 8, 7, 4]], # Batch 1 [[3, 4, 5, 2], [0, 0, 0, 0]], # Batch 2 [[6, 7, 8, 5], [6, 5, 4, 2]], # Batch 3 ])
Ми можемо побудувати мережу з заповнювачем для даних, повторюваного етапу та результату.
- Визначте заповнювач для даних
X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs])
тут:
- Немає: невідомо та буде прийнято розмір партії
- n_timesteps: скільки разів мережа надсилатиме вихідні дані нейрону
- n_inputs: кількість вхідних даних на пакет
- Визначте рекурентну мережу
Як зазначено на малюнку вище, мережа складається з 6 нейронів. Мережа обчислить два скалярних добутку:
- Вхідні дані з першим набором ваг (тобто 6: дорівнює кількості нейронів)
- Попередній вихід із другим набором ваг (тобто 6: відповідає номеру виводу)
Зауважте, що під час першого передавання значення попереднього результату дорівнюють нулю, оскільки ми не маємо жодного доступного значення.
Об’єктом для створення RNN є tf.contrib.rnn.BasicRNNCell з аргументом num_units для визначення кількості вхідних даних
basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons)
Тепер, коли мережу визначено, ви можете обчислити виходи та стани
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Цей об’єкт використовує внутрішній цикл для множення матриць відповідну кількість разів.
Зверніть увагу, що рекурентний нейрон є функцією всіх входів попередніх часових кроків. Так мережа створює власну пам’ять. Інформація з попереднього часу може поширюватися в майбутньому. Це магія рекурентної нейронної мережі
## Define the shape of the tensor X = tf.placeholder(tf.float32, [None, n_timesteps, n_inputs]) ## Define the network basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=n_neurons) outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) init = tf.global_variables_initializer() init = tf.global_variables_initializer() with tf.Session() as sess: init.run() outputs_val = outputs.eval(feed_dict={X: X_batch}) print(states.eval(feed_dict={X: X_batch})) [[ 0.38941205 -0.9980438 0.99750966 0.7892596 0.9978241 0.9999997 ] [ 0.61096436 0.7255889 0.82977575 -0.88226104 0.29261455 -0.15597084] [ 0.62091285 -0.87023467 0.99729395 -0.58261937 0.9811445 0.99969864]]
Для пояснення ви друкуєте значення попереднього стану. Результат, надрукований вище, показує вихід з останнього стану. Тепер надрукуйте всі результати, ви можете помітити, що стани є попередніми результатами кожної партії. Тобто попередній вихід містить інформацію про всю послідовність.e
print(outputs_val) print(outputs_val.shape) [[[-0.75934666 -0.99537754 0.9735819 -0.9722234 -0.14234993 -0.9984044 ] [ 0.99975264 -0.9983206 0.9999993 -1. -0.9997506 -1. ]] [[ 0.97486496 -0.98773265 0.9969686 -0.99950117 -0.7092863 -0.99998885] [ 0.9326837 0.2673438 0.2808514 -0.7535883 -0.43337247 0.5700631 ]] [[ 0.99628735 -0.9998728 0.99999213 -0.99999976 -0.9884324 -1. ] [ 0.99962527 -0.9467421 0.9997403 -0.99999714 -0.99929446 -0.9999795 ]]] (3, 2, 6)
Вихід має вигляд (3, 2, 6):
- 3: Кількість партій
- 2: Номер тимчасового кроку
- 6: Кількість нейронів
Оптимізація рекурентної нейронної мережі ідентична традиційній нейронній мережі. Ви побачите більш детальну інформацію про оптимізацію коду в наступній частині цього навчального посібника з рекурентної нейронної мережі.
Застосування RNN
RNN має багато застосувань, особливо коли йдеться про передбачення майбутнього. У фінансовій індустрії RNN може бути корисним для прогнозування цін на акції або знака напрямку фондового ринку (тобто позитивного чи негативного).
RNN корисний для автономного автомобіля, оскільки він може уникнути автомобільної аварії, передбачаючи траєкторію транспортного засобу.
RNN широко використовується в аналізі тексту, підписах до зображень, аналізі настроїв і машинному перекладі. Наприклад, можна використовувати рецензію на фільм, щоб зрозуміти відчуття, які відчував глядач після перегляду фільму. Автоматизація цього завдання дуже корисна, коли кінокомпанія не має достатньо часу для перегляду, маркування, консолідації та аналізу оглядів. Машина може виконувати роботу з більш високою точністю.
Обмеження RNN
Теоретично RNN має передавати інформацію до разів. Однак поширювати всю цю інформацію, коли часовий крок занадто великий, досить складно. Коли мережа має занадто багато глибоких шарів, вона стає непридатною для навчання. Ця проблема називається: проблема зникаючого градієнта. Якщо ви пам'ятаєте, нейронна мережа оновлює вагу за допомогою алгоритму градієнтного спуску. Градієнти стають меншими, коли мережа просувається вниз до нижчих рівнів.
Підсумовуючи, градієнти залишаються постійними, тобто немає місця для вдосконалення. Модель навчається зі зміни градієнта; ця зміна впливає на вихід мережі. Однак, якщо різниця в градієнті надто мала (тобто вагові коефіцієнти трохи змінюються), мережа нічого не може дізнатися, а отже, і вихід. Таким чином, мережа, яка стикається з проблемою зникаючого градієнта, не може знайти хороше рішення.
Удосконалення ЛСТМ
Щоб подолати потенційну проблему зникнення градієнта, з якою стикається RNN, троє дослідників, Hochreiter, Schmidhuber і Bengio вдосконалили RNN за допомогою архітектури під назвою Довга короткочасна пам’ять (LSTM). Коротше кажучи, LSMT надає в мережу відповідну минулу інформацію до пізнішого часу. Машина використовує кращу архітектуру для вибору та перенесення інформації на пізніший час.
Архітектура LSTM доступна в TensorFlow, tf.contrib.rnn.LSTMCell. LSTM виходить за межі підручника. Можна звернутися до офіц документація Для отримання додаткової інформації
RNN у часових рядах
У цьому підручнику TensorFlow RNN ви використовуватимете RNN із даними часових рядів. Часові ряди залежать від попереднього часу, що означає, що минулі значення містять відповідну інформацію, з якої мережа може навчитися. Ідея прогнозування часових рядів полягає в тому, щоб оцінити майбутню вартість ряду, скажімо, ціни акцій, температури, ВВП тощо.
Підготовка даних для Keras RNN і часових рядів може бути дещо складною. Перш за все, мета полягає в тому, щоб передбачити наступне значення ряду, тобто ви будете використовувати минулу інформацію для оцінки значення при t + 1. Мітка дорівнює вхідній послідовності та зсувається на один період вперед. По-друге, число вхідних даних встановлено на 1, тобто одне спостереження за час. Нарешті, крок у часі дорівнює послідовності числових значень. Наприклад, якщо встановити часовий крок на 10, послідовність введення повертатиметься десять разів поспіль.
Подивіться на графік нижче, ми представили дані часового ряду ліворуч і фіктивну послідовність введення праворуч. Ви створюєте функцію для повернення набору даних із випадковим значенням для кожного дня з січня 2001 року по грудень 2016 року.
# To plot pretty figures %matplotlib inline import matplotlib import matplotlib.pyplot as plt import pandas as pd def create_ts(start = '2001', n = 201, freq = 'M'): rng = pd.date_range(start=start, periods=n, freq=freq) ts = pd.Series(np.random.uniform(-18, 18, size=len(rng)), rng).cumsum() return ts ts= create_ts(start = '2001', n = 192, freq = 'M') ts.tail(5)
Вихід
2016-08-31 -93.459631 2016-09-30 -95.264791 2016-10-31 -95.551935 2016-11-30 -105.879611 2016-12-31 -123.729319 Freq: M, dtype: float64
ts = create_ts(start = '2001', n = 222) # Left plt.figure(figsize=(11,4)) plt.subplot(121) plt.plot(ts.index, ts) plt.plot(ts.index[90:100], ts[90:100], "b-", linewidth=3, label="A training instance") plt.title("A time series (generated)", fontsize=14) # Right plt.subplot(122) plt.title("A training instance", fontsize=14) plt.plot(ts.index[90:100], ts[90:100], "b-", markersize=8, label="instance") plt.plot(ts.index[91:101], ts[91:101], "bo", markersize=10, label="target", markerfacecolor='red') plt.legend(loc="upper left") plt.xlabel("Time") plt.show()
Права частина графіка показує всі серії. Він почався з 2001 року і закінчується в 2019. Немає сенсу передавати всі дані в мережу, натомість потрібно створити пакет даних довжиною, що дорівнює часовому кроку. Цей пакет буде змінною X. Змінна Y така сама, як X, але зміщена на один період (тобто ви хочете спрогнозувати t+1).
Обидва вектори мають однакову довжину. Ви можете побачити це в правій частині графіка вище. Лінія представляє десять значень вхідного X, а червоні крапки — десять значень мітки Y. Зауважте, що мітка починається на один період перед X і закінчується на один період після.
Створіть RNN для прогнозування часових рядів у TensorFlow
Тепер у цьому навчанні RNN настав час створити свій перший RNN для прогнозування серії вище. Вам потрібно вказати деякі гіперпараметри (параметри моделі, тобто кількість нейронів тощо) для моделі:
- Кількість входів: 1
- Часовий крок (вікна в часовому ряді): 10
- Кількість нейронів: 120
- Кількість виходів: 1
Ваша мережа навчатиметься з послідовності 10 днів і міститиме 120 рекурентних нейронів. Ви живите модель одним входом, тобто одним днем. Не соромтеся змінити значення, щоб побачити, чи покращилася модель.
Перш ніж побудувати модель, вам потрібно розділити набір даних на набір поїздів і тестовий набір. Повний набір даних містить 222 точки даних; ви будете використовувати першу 201 точку для навчання моделі, а останні 21 точку для тестування моделі.
Після того, як ви визначите набір тренувань і тестів, вам потрібно створити об’єкт, що містить пакети. У цих пакетах у вас є значення X і значення Y. Пам’ятайте, що значення X є із запізненням на один період. Тому ви використовуєте перші 200 спостережень, а часовий крок дорівнює 10. Об’єкт X_batches має містити 20 пакетів розміром 10*1. y_batches має ту саму форму, що й об’єкт X_batches, але з крапкою вперед.
Крок 1) Створіть потяг і випробуйте
Перш за все, ви перетворюєте серію в a нумпі масив; потім ви визначаєте вікна (тобто кількість часу, з якого мережа навчатиметься), кількість входів, виходів і розмір набору поїздів, як показано в прикладі TensorFlow RNN нижче.
series = np.array(ts) n_windows = 20 n_input = 1 n_output = 1 size_train = 201
Після цього ви просто розділите масив на два набори даних.
## Split data train = series[:size_train] test = series[size_train:] print(train.shape, test.shape) (201,) (21,)
Крок 2) Створіть функцію для повернення X_batches і y_batches
Щоб зробити це простіше, ви можете створити функцію, яка повертає два різні масиви: один для X_batches і один для y_batches.
Давайте напишемо функцію RNN TensorFlow для створення пакетів.
Зверніть увагу, що партії X відстають на один період (ми беремо значення t-1). Вихідні дані функції повинні мати три виміри. Перший розмір дорівнює кількості пакетів, другий - розміру вікон, а останній - кількості вхідних даних.
Найскладніше – правильно вибрати точки даних. Для точок даних X ви вибираєте спостереження від t = 1 до t = 200, тоді як для точки даних Y ви повертаєте спостереження від t = 2 до 201. Отримавши правильні точки даних, можна легко змінити форму серії.
Щоб побудувати об’єкт із пакетами, вам потрібно розділити набір даних на десять пакетів однакової довжини (тобто 20). Ви можете використати метод зміни форми та передати -1, щоб серія була подібною до розміру партії. Значення 20 — це кількість спостережень на партію, а 1 — кількість введених даних.
Вам потрібно зробити той самий крок, але для етикетки.
Зауважте, що вам потрібно зсунути дані на кількість разів, яку ви хочете спрогнозувати. Наприклад, якщо ви хочете спрогнозувати на один час наперед, ви зрушуєте ряд на 1. Якщо ви хочете спрогнозувати два дні, тоді зсуваєте дані на 2.
x_data = train[:size_train-1]: Select all the training instance minus one day X_batches = x_data.reshape(-1, windows, input): create the right shape for the batch e.g (10, 20, 1) def create_batches(df, windows, input, output): ## Create X x_data = train[:size_train-1] # Select the data X_batches = x_data.reshape(-1, windows, input) # Reshape the data ## Create y y_data = train[n_output:size_train] y_batches = y_data.reshape(-1, windows, output) return X_batches, y_batches
Тепер, коли функцію визначено, ви можете викликати її для створення пакетів, як показано в наведеному нижче прикладі RNN.
X_batches, y_batches = create_batches(df = train, windows = n_windows, input = n_input, output = n_output)
Ви можете роздрукувати форму, щоб переконатися, що розміри правильні.
print(X_batches.shape, y_batches.shape) (10, 20, 1) (10, 20, 1)
Вам потрібно створити тестовий набір лише з однієї партії даних і 20 спостережень.
Зауважте, що якщо ви прогнозуєте день за днем, це означає, що друге прогнозоване значення базуватиметься на справжньому значенні першого дня (t+1) тестового набору даних. Власне, буде відома справжня вартість.
Якщо ви хочете спрогнозувати t+2 (тобто на два дні вперед), вам потрібно використовувати прогнозоване значення t+1; якщо ви збираєтеся передбачити t+3 (на три дні вперед), вам потрібно використовувати прогнозовані значення t+1 і t+2. Цілком зрозуміло, що важко точно передбачити t+n днів наперед.
X_test, y_test = create_batches(df = test, windows = 20,input = 1, output = 1) print(X_test.shape, y_test.shape) (10, 20, 1) (10, 20, 1)
Гаразд, розмір вашої партії готовий, ви можете створювати архітектуру RNN. Пам’ятайте, що у вас є 120 рекурентних нейронів.
Крок 3) Побудуйте модель
Щоб створити модель, потрібно визначити три частини:
- Змінна з тензорами
- РНН
- Втрати та оптимізація
Крок 3.1) Змінні
Потрібно вказати змінні X і y з відповідною формою. Цей крок є тривіальним. Тензор має ту саму розмірність, що й об’єкти X_batches і y_batches.
Наприклад, тензор X є заповнювачем (перегляньте підручник на Вступ до Тензорний потік щоб оновити ваші думки про оголошення змінних) має три виміри:
- Примітка: розмір партії
- n_windows: довжина вікон. тобто кількість разів, коли модель дивиться назад
- n_input: номер введення
Результат:
tf.placeholder(tf.float32, [None, n_windows, n_input])
## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output])
Крок 3.2) Створіть RNN
У другій частині цього прикладу RNN TensorFlow вам потрібно визначити архітектуру мережі. Як і раніше, ви використовуєте об’єкт BasicRNNCell і dynamic_rnn з оцінювача TensorFlow.
## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
Наступна частина трохи складніша, але дозволяє швидше обчислювати. Вам потрібно перетворити результат циклу на щільний шар, а потім знову перетворити його, щоб мати той самий розмір, що й вхід.
stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Крок 3.3) Створення втрат і оптимізація
Оптимізація моделі залежить від завдання, яке ви виконуєте. У попередньому підручнику на CNN, вашою метою було класифікувати зображення, у цьому підручнику RNN мета дещо інша. Вас просять спрогнозувати безперервну змінну в порівнянні з класом.
Ця відмінність важлива, оскільки вона змінить проблему оптимізації. Проблема оптимізації для безперервної змінної полягає в мінімізації середньої квадратичної помилки. Щоб побудувати ці показники в TF, ви можете використовувати:
- tf.reduce_sum(tf.square(виходи – y))
Решта коду RNN така ж, як і раніше; Ви використовуєте оптимізатор Adam для зменшення втрат (тобто MSE):
- tf.train.AdamOptimizer(швидкість_навчання=швидкість_навчання)
- optimizer.minimize(loss)
Ось і все, ви можете пакувати все разом, і ваша модель готова до тренувань.
tf.reset_default_graph() r_neuron = 120 ## 1. Construct the tensors X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) ## 2. create the model basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output]) ## 3. Loss + optimization learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss) init = tf.global_variables_initializer()
Ви навчатимете модель за допомогою 1500 епох і друкуватимете втрати кожні 150 ітерацій. Коли модель навчена, ви оцінюєте її на тестовому наборі та створюєте об’єкт, що містить прогнози, як показано в наведеному нижче прикладі повторюваної нейронної мережі.
iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test}) 0 MSE: 502893.34 150 MSE: 13839.129 300 MSE: 3964.835 450 MSE: 2619.885 600 MSE: 2418.772 750 MSE: 2110.5923 900 MSE: 1887.9644 1050 MSE: 1747.1377 1200 MSE: 1556.3398 1350 MSE: 1384.6113
Нарешті в цьому посібнику з глибокого навчання RNN ви можете побудувати фактичне значення ряду з прогнозованим значенням. Якщо вашу модель виправлено, передбачувані значення слід поставити поверх фактичних значень.
Як бачите, модель має куди вдосконалюватися. Ви самі можете змінити гіперпараметри, такі як вікна, розмір партії кількості рекурентних нейронів.
plt.title("Forecast vs Actual", fontsize=14) plt.plot(pd.Series(np.ravel(y_test)), "bo", markersize=8, label="Actual", color='green') plt.plot(pd.Series(np.ravel(y_pred)), "r.", markersize=8, label="Forecast", color='red') plt.legend(loc="lower left") plt.xlabel("Time") plt.show()
Підсумки
Рекурентна нейронна мережа — це надійна архітектура для аналізу часових рядів або тексту. Вихід попереднього стану є зворотним зв'язком для збереження пам'яті мережі протягом часу або послідовності слів.
У TensorFlow ви можете використовувати наступні коди для навчання повторюваної нейронної мережі TensorFlow для часових рядів:
Параметри моделі
n_windows = 20 n_input = 1 n_output = 1 size_train = 201
Дайте визначення моделі
X = tf.placeholder(tf.float32, [None, n_windows, n_input]) y = tf.placeholder(tf.float32, [None, n_windows, n_output]) basic_cell = tf.contrib.rnn.BasicRNNCell(num_units=r_neuron, activation=tf.nn.relu) rnn_output, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32) stacked_rnn_output = tf.reshape(rnn_output, [-1, r_neuron]) stacked_outputs = tf.layers.dense(stacked_rnn_output, n_output) outputs = tf.reshape(stacked_outputs, [-1, n_windows, n_output])
Побудуйте оптимізацію
learning_rate = 0.001 loss = tf.reduce_sum(tf.square(outputs - y)) optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) training_op = optimizer.minimize(loss)
Тренуйте модель
init = tf.global_variables_initializer() iteration = 1500 with tf.Session() as sess: init.run() for iters in range(iteration): sess.run(training_op, feed_dict={X: X_batches, y: y_batches}) if iters % 150 == 0: mse = loss.eval(feed_dict={X: X_batches, y: y_batches}) print(iters, "\tMSE:", mse) y_pred = sess.run(outputs, feed_dict={X: X_test})