Передовые алгоритмы глубокого обучения. ml.. ml. python.. ml. python. Алгоритмы.. ml. python. Алгоритмы. Блог компании Издательский дом «Питер».. ml. python. Алгоритмы. Блог компании Издательский дом «Питер». ИИ.. ml. python. Алгоритмы. Блог компании Издательский дом «Питер». ИИ. Машинное обучение.. ml. python. Алгоритмы. Блог компании Издательский дом «Питер». ИИ. Машинное обучение. Профессиональная литература.
Передовые алгоритмы глубокого обучения - 1

Привет, Хаброжители! Мы хотим поделиться с вами главой из книги «Алгоритмы машинного обучения» , которую уже можно предзаказать на нашем сайте.

В этой главе

  • Вариационные автоэнкодеры для обнаружения аномалий временных рядов

  • Сети смешанной плотности, использующие амортизированный вариационный вывод

  • Механизм внимания и трансформеры

  • Графовые нейронные сети

  • Исследования в области ML: глубокое обучение

В этой главе мы поговорим о передовых алгоритмах глубокого обуче­ния. Они были подобраны с учетом соответствия их архитектуры современным стандартам качества и широты спектра применения. В этой главе мы изучим генеративные модели, основанные на вариационных автоэнкодерах (variational autoencoders, VAE), и рассмотрим полноценную реализацию детектора аномалий для данных временнˆых рядов. Мы продолжим наше путешествие знакомством с интригующей комбинацией нейронных сетей и классических моделей гауссовой смеси с использованием амортизированного вариационного вывода и взглянем на реализацию сети смешанной плотности. Затем мы сосредоточимся на концепции внимания и изучим реализацию с чистого листа архитектуры трансформера для задачи классификации. Наконец, мы рассмотрим графовые нейронные сети и используем одну из них для классификации вершин в графе цитирования. На протяжении всей этой главы мы будем пользоваться библиотекой глубокого обучения Keras/TensorFlow.

11.1. Автоэнкодеры

Автоэнкодер — это обучаемая в режиме без учителя нейронная сеть, которая используется для уменьшения размерности или выделения признаков. Она со­стоит из энкодера, скрытого слоя — так называемого узкого места (bottleneck) — и декодера, где энкодер и декодер являются обучаемыми нейронными сетями, как показано на рис. 11.1.

Рис. 11.1. Архитектура автоэнкодера, состоящая из энкодера, слоя «узкого места» и декодера

Рис. 11.1. Архитектура автоэнкодера, состоящая из энкодера, слоя «узкого места» и декодера

Автоэнкодеры обучаются восстанавливать свои входные данные. Другими слова­ми, выходные данные автоэнкодера должны максимально точно соответствовать его входным данным. Чтобы обучение нейросети не свелось к выучиванию ею тривиальной функции тождества, скрытый слой в середине должен быть дей­ствительно узким местом. При обучении автоэнкодера минимизируется ошибка восстановления (reconstruction error), гарантируя, что скрытые модули отражают наиболее существенную информацию во входных данных.

На практике автоэнкодеры используются для извлечения признаков и не способны к созданию хорошо структурированных скрытых пространств. Воттут-товделоивступают VAE — см. диссертациюД. П. Кингмы (D. P. Kingma) «Variational Inference and Deep Learning: A New Synthesis» (University of Amsterdam, 2017). Архитектура VAE также содержит энкодер и декодер. Однако вместо сжатия входного изображения до размеров узкого скрытого слоя VAE преобразует изобра­жение в параметры статистического распределения (например, среднее значение и дисперсию гауссовой случайной величины). Затем VAE из этого распределения генерирует выборочное значение, которое используется для декодирования об­ратно в исходное представление. Рисунок 11.2 демонстрирует архитектуру VAE.

Рис. 11.2. Архитектура вариационного автоэнкодера: энкодер, семплирующий слой и декодер

Рис. 11.2. Архитектура вариационного автоэнкодера: энкодер, семплирующий слой и декодер

Обучение VAE структурирует скрытое пространство таким образом, что каж­дая точка может быть декодирована в корректный выходной формат. Целевой функцией вариационного автоэнкодера является нижняя вариационная граница (ELBO). Пусть x представляет собой входное пространство, а z — скрытое про­странство. Пусть p(x | z) — распределение декодера с параметрами θ, которое, при условии, что выборочное значение из скрытого пространства равно z, восстанав­ливает исходное значение входных данных x. Аналогично, пусть q(z | x) — это распределение энкодера с параметрами ϕ, которое принимает входные данные x и кодирует их в скрытую переменную z. Обратите внимание, что параметры θ и ϕ получаются при обучении. Наконец, пусть p(z) — априорное распределение скрытого пространства. Поскольку мы стремимся к тому, чтобы вариационное апостериорное распределение было как можно ближе к истинному апостериор­ному, то для обучения VAE используется следующая функция потерь:

Передовые алгоритмы глубокого обучения - 4

Первое слагаемое определяет, насколько хорошо VAE восстанавливает точку данных x из выборочного значения z вариационного апостериорного распреде­ления, а второе — насколько вариационное апостериорное распределение q(z | x) близко к априорному распределению p(z). Если предположить, что априорное распределение p(z) является гауссовым, то член KL-дивергенции можно пере­писать следующим образом (см. формулу 11.2). В следующем разделе мы увидим, как VAE можно использовать для обнаружения аномалий во временных рядах.

Передовые алгоритмы глубокого обучения - 5

11.1.1. VAE: обнаружение аномалий во временных рядах

Давайте рассмотрим модель VAE, которая оперирует данными временных рядов с целью обнаружения аномалий. Эта архитектура показана на рис. 11.3:

Рис. 11.3. Архитектура детектора аномалий LSTM-VAE

Рис. 11.3. Архитектура детектора аномалий LSTM-VAE

Входные данные состоят из n сигналов x1,…, xn, а выходные представляют собой логарифмическую вероятность наблюдения входных данных xi при нормальных (неаномальных) параметрах обучения μi, σi. Это означает, что модель обучается на неаномальных данных в режиме без учителя, и когда в заданных входных данных xi возникает аномалия, соответствующий логарифм правдоподобия p(xi | {μi, σi}) падает, и по пересечению порогового значения можно судить о на­личии аномалии.

Исходя из предположения гауссова правдоподобия, каждый датчик для представ­ления аномалии имеет две степени свободы: (μ, σ). В результате для n входных датчиков мы обучаем 2n выходных параметров (среднее значение и дисперсию), которые позволяют отличить аномальное поведение от нормального.

Хотя входные сигналы независимы, в VAE они вкладываются (embedded) в со­вместное скрытое пространство семплирующего слоя. Структура скрытого пространства приближается к стандартному нормальному распределению N(0,1) путем минимизации KL-дивергенции. Модель обучается в режиме без учителя с помощью целевой функции, которая помогает достичь следующих двух целей: (1) максимизации логарифмического правдоподобия модели, усредненного по дат­чикам, и (2) структурирования скрытого пространства для приближения к N(0, 1):

Передовые алгоритмы глубокого обучения - 7

Теперь мы можем приступать к знакомству с полноценной реализацией детек­тора аномалий LSTM-VAE, построенной на базе библиотеки Keras/TensorFlow. В приведенном ниже коде мы загружаем набор данных NAB (который можно найти в папке данных репозитория кода) и подготавливаем их для обучения. Numenta Anomaly Benchmark (NAB) — это новый тест для оценки алгоритмов обнаружения аномалий в потоковых и онлайн-приложениях. Далее мы опре­деляем архитектуру детектора аномалий вместе с пользовательской функцией потерь и обучаем модель. Вам стоит попробовать запустить этот код в режиме блокнота на платформе Google Colab (доступна по ссылке https://colab.research.google.com/), чтобы посмотреть на результаты пошагового исполнения, а также ускорить процесс обучения модели при помощи графического процессора.

Листинг 11.1. Детектор аномалий LSTM-VAE
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
import tensorflow_probability as tfp
from keras.layers import Input, Dense, Lambda, Layer
from keras.layers import LSTM, RepeatVector
from keras.models import Model
from keras import backend as K
from keras import metrics
from keras import optimizers
import math
import json
from scipy.stats import norm
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.metrics import confusion_matrix
from sklearn.preprocessing import StandardScaler
from keras.callbacks import ModelCheckpoint
from keras.callbacks import TensorBoard
from keras.callbacks import LearningRateScheduler
from keras.callbacks import EarlyStopping
import matplotlib.pyplot as plt
tf.keras.utils.set_random_seed(42)
SAVE_PATH = "/content/drive/MyDrive/Colab Notebooks/data/"
DATA_PATH = "/content/drive/MyDrive/data/"
def scheduler(epoch, lr): 
if epoch < 4: 
return lr 
else: 
return lr * tf.math.exp(-0.1)
nab_path = DATA_PATH + 'NAB/
nab_data_path = nab_path
labels_filename = '/labels/combined_labels.json'
train_file_name = 'artificialNoAnomaly/art_daily_no_noise.csv'
test_file_name = 'artificialWithAnomaly/art_daily_jumpsup.csv'
#train_file_name = 'realAWSCloudwatch/rds_cpu_utilization_cc0c53.csv'
#test_file_name = 'realAWSCloudwatch/rds_cpu_utilization_e47b3b.csv'
labels_file = open(nab_path + labels_filename, 'r')
labels = json.loads(labels_file.read())
labels_file.close()
def load_data_frame_with_labels(file_name): 
data_frame = pd.read_csv(nab_data_path + file_name) 
data_frame['anomaly_label'] = data_frame['timestamp'].isin( 
labels[file_name]).astype(int) 
return data_frame
train_data_frame = load_data_frame_with_labels(train_file_name) test_data_frame = load_data_frame_with_labels(test_file_name)
plt.plot(train_data_frame.loc[0:3000,'value'])
plt.plot(test_data_frame['value'])
train_data_frame_final = train_data_frame.loc[0:3000,:]
test_data_frame_final = test_data_frame
data_scaler = StandardScaler()
data_scaler.fit(train_data_frame_final[['value']].values)
train_data = data_scaler.transform(train_data_frame_final[['value']].values)
test_data = data_scaler.transform(test_data_frame_final[['value']].values)
def create_dataset(dataset, look_back=64): 
dataX, dataY = [], [] 
for i in range(len(dataset)-look_back-1): 
dataX.append(dataset[i:(i+look_back),:]) 
dataY.append(dataset[i+look_back,:]) 
return np.array(dataX), np.array(dataY)
X_data, y_data = create_dataset(train_data, look_back=64) #look_back =
➥ window_size
X_train, X_val, y_train, y_val = train_test_split(X_data, y_data,
➥ test_size=0.1, random_state=42)
X_test, y_test = create_dataset(test_data, look_back=64) #look_back =
➥ window_size
batch_size = 256Параметры обучения
num_epochs = 32
timesteps = X_train.shape[1]Параметры модели
input_dim = X_train.shape[-1]
intermediate_dim = 16
latent_dim = 2
epsilon_std = 1.0
class Sampling(Layer): Семплирующий слой 
def call(self, inputs): 
z_mean, z_log_var = inputs 
batch = tf.shape(z_mean)[0] 
dim = tf.shape(z_mean)[1] 
epsilon = tf.keras.backend.random_normal(shape=(batch, dim)) 
return z_mean + tf.exp(0.5 * z_log_var) * epsilon
class Likelihood(Layer): Слой правдоподобия 
def call(self, inputs): 
x, x_decoded_mean, x_decoded_scale = inputs 
dist = tfp.distributions.MultivariateNormalDiag(x_decoded_mean, 
➥ x_decoded_scale) 
likelihood = dist.log_prob(x) 
return likelihood
# Архитектура VAE
# кодер
x = Input(shape=(timesteps, input_dim,))
h = LSTM(intermediate_dim)(x)
z_mean = Dense(latent_dim)(h)
z_log_sigma = Dense(latent_dim, activation='softplus')(h)
# семплирование
z = Sampling()((z_mean, z_log_sigma))
# декодер
decoder_h = LSTM(intermediate_dim, return_sequences=True)
decoder_loc = LSTM(input_dim, return_sequences=True)
decoder_scale = LSTM(input_dim, activation='softplus', return_sequences=True)
h_decoded = RepeatVector(timesteps)(z)
h_decoded = decoder_h(h_decoded)
x_decoded_mean = decoder_loc(h_decoded)
x_decoded_scale = decoder_scale(h_decoded)
# логарифмическое правдоподобие
llh = Likelihood()([x, x_decoded_mean, x_decoded_scale])
vae = Model(inputs=x, outputs=llh) Определение VAE-модели
# Потери: KL-дивергенция плюс логарифмическое правдоподобие
kl_loss = – 0.5 * K.mean(1 + z_log_sigma - K.square(z_mean) –
➥ K.exp(z_log_sigma))
tot_loss = –K.mean(llh - kl_loss)
vae.add_loss(tot_loss)
# Функция потерь и оптимизатор
loss_fn = tf.keras.losses.MeanSquaredError()
optimizer = tf.keras.optimizers.Adam()
@tf.function
def training_step(x): 
with tf.GradientTape() as tape: 
reconstructed = vae(x) # Восстановление входных данных 
# Вычисление потерь 
loss = 0 #loss_fn(x, reconstructed) 
loss += sum(vae.losses) 
# Обновление весов VAE 
grads = tape.gradient(loss, vae.trainable_weights) 
optimizer.apply_gradients(zip(grads, vae.trainable_weights)) 
return loss
losses = [] # Регистрация потерь во времени
dataset = tf.data.Dataset.from_tensor_slices(X_train).batch(batch_size)
for epoch in range(num_epochs): 
for step, x in enumerate(dataset): 
loss = training_step(x) 
losses.append(float(loss)) 
print("Epoch:", epoch, "Loss:", sum(losses) / len(losses))
plt.figure()
plt.plot(losses, c='b', lw=2.0, label='Обучение')
plt.title('Модель LSTM-VAE')
plt.xlabel('Эпоха')
plt.ylabel('Общие потери')
plt.legend(loc='upper right')
plt.show()
pred_test = vae.predict(X_test)
plt.plot(pred_test[:,0])
is_anomaly = pred_test[:,0] < -1e1
plt.figure()
plt.plot(test_data, color='b')
plt.figure()
plt.plot(is_anomaly, color='r')

Рисунок 11.4 демонстрирует, что в простом прямоугольном входном сигнале удается обнаружить падение логарифмической вероятности и после пересечения порогового значения выдать сигнал о наличии аномалии. Уменьшение общих потерь в процессе обучения показано на рис. 11.5.

Рис. 11.4. Результат обнаружения аномалии при помощи LSTM-VAE

Рис. 11.4. Результат обнаружения аномалии при помощи LSTM-VAE
Рис. 11.5. Потери при обучении LSTM-VAE

Рис. 11.5. Потери при обучении LSTM-VAE

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

11.2. Амортизированный вариационный вывод

Амортизированный вариационный вывод (VI) является воплощением идеи, что вместо оптимизации набора свободных параметров мы можем ввести параметризо­ванную функцию, которая сопоставляет пространство наблюдений с параметрами приближенного апостериорного распределения. В практическом плане наблюде­ния могут подаваться на вход нейросети, которая выводит параметры среднего значения и дисперсии для скрытой переменной, связанной с этими наблюдени­ями (с чем мы столкнулись в архитектуре VAE). Затем можно оптимизировать параметры этой нейросети вместо отдельных параметров каждого наблюдения.

Одним из преимуществ амортизированного VI является повторное использо­вание предыдущих выводов по аналогии с динамическим программированием, в котором мы храним решения ранее вычисленных подзадач. В качестве примера рассмотрим два запроса, приведенные на рис. 11.6, — см. книгу Сэмюэл Дж. Герш­мана (Samuel J. Gershman), Ноа Д. Гудмана (Noah D. Goodman) «Amortized Inference in Probabilistic Reasoning» (Cognitive Science, 2014):

Давайте рассмотрим один из примеров амортизированного VI, а именно сеть смешанной плотности, в которой мы будем использовать многослойный пер­цептрон (МСП) для параметризации модели гауссовой смеси.

Рис. 11.6. Простая байесовская сеть. Запрос 1 является подзапросом запроса 2

Рис. 11.6. Простая байесовская сеть. Запрос 1 является подзапросом запроса 2

Видно, что запрос 1 является подзапросом запроса 2. Тем самым условное рас­пределение, вычисленное для запроса 1, может быть повторно использовано для ответа на запрос 2. Еще одним преимуществом амортизированного VI яв­ляется то, что в нем отсутствует необходимость аналитического вывода ELBO, поскольку оптимизация происходит с помощью стохастического градиентного спуска (SGD). Ограничение амортизированного VI состоит в том, что пробел в обобщающей способности (generalization gap) модели зависит от емкости вы­бранной нейронной сети как стохастической функции.

Давайте рассмотрим один из примеров амортизированного VI, а именно сеть смешанной плотности, в которой мы будем использовать многослойный пер­цептрон (МСП) для параметризации модели гауссовой смеси.

11.2.1. Сети смешанной плотности

Сети смешанной плотности (mixture density networks, MDN) — это модели сме­си, в которых параметры, такие как среднее значение, ковариация и пропорция смеси, получаются в результате обучения нейронной сети. Сети MDN объеди­няют в себе структурированное представление данных (смесь плотностей) с не­структурированным выводом параметров (нейронная сеть МСП). При обучении MDN параметры смеси получаются за счет максимизации логарифмического правдоподобия или, что эквивалентно, минимизации потерь отрицательного логарифмического правдоподобия.

Предполагая модель гауссовой смеси с K компонентами, мы можем записать вероятность тестовой точки данных yi при условии обучающих данных x сле­дующим образом:

Передовые алгоритмы глубокого обучения - 11

Здесь параметры μk, σk, πk выучиваются нейронной сетью (например, МСП) с параметрами θ:

Передовые алгоритмы глубокого обучения - 12

Архитектура MDN представлена на рис. 11.7.

Рис. 11.7. Нейронная сеть с несколькими выходами для получения гауссовых параметров

Рис. 11.7. Нейронная сеть с несколькими выходами для получения гауссовых параметров

Как результат, нейронная сеть (НС) представляет собой модель с несколькими выходами, на которые накладываются следующие ограничения:

Передовые алгоритмы глубокого обучения - 14

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

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

Передовые алгоритмы глубокого обучения - 15

В нашем примере используется предположение об изотропности ковариацион­ной матрицы: Σk = σk2 I. Таким образом, мы можем записать d-мерный гауссиан в виде произведения. С учетом формулы для многомерного гауссиана мы полу­чаем следующее:

Передовые алгоритмы глубокого обучения - 16

Поскольку ковариационная матрица изотропна, мы можем переписать форму­лу 11.8 следующим образом:

Передовые алгоритмы глубокого обучения - 17

Давайте теперь познакомимся с гауссовой MDN, реализованной с использова­нием Keras/TensorFlow. В нашем примере мы используем синтетические данные с эталонными значениями среднего и дисперсии. Данные генерируются при помощи семплирования из многомерного распределения. Затем мы определяем архитектуру MDN с несколькими выходами с ограничениями на пропорции смеси и дисперсию. Наконец, мы вычисляем потерю отрицательного логариф­мического правдоподобия, обучаем модель и отображаем результаты предска­зания на тестовых данных. Вам стоит попробовать запустить этот код в режиме блокнота на платформе Google Colab (доступна по ссылке https://colab.research.google.com/), чтобы посмотреть на результаты пошагового исполнения, а также ускорить процесс обучения модели при помощи графического процессора.

Листинг 11.2. Сеть плотности гауссовой смеси
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from keras.models import Model
from keras.layers import concatenate, Input
from keras.layers import Dense, Activation, Dropout, Flatten
from keras.layers import BatchNormalization
from keras import regularizers
from keras import backend as K
from keras.utils import np_utils
from keras.callbacks import ModelCheckpoint
from keras.callbacks import TensorBoard
from keras.callbacks import LearningRateScheduler
from keras.callbacks import EarlyStopping
from sklearn.datasets import make_blobs
from sklearn.metrics import adjusted_rand_score
from sklearn.metrics import normalized_mutual_info_score
from sklearn.model_selection import train_test_split
import math
import matplotlib.pyplot as plt
import matplotlib.cm as cm
tf.keras.utils.set_random_seed(42)
SAVE_PATH = "/content/drive/MyDrive/Colab Notebooks/data/"
def scheduler(epoch, lr): Изменение скорости обучения 
if epoch < 4: 
return lr 
else: 
return lr * tf.math.exp(-0.1)
def generate_data(N): Синтетические эталонные данные 
pi = np.array([0.2, 0.4, 0.3, 0.1]) 
mu = [[2,2], [-2,2], [-2,-2], [2,-2]] 
std = [[0.5,0.5], [1.0,1.0], [0.5,0.5], [1.0,1.0]] 
x = np.zeros((N,2), dtype=np.float32) 
y = np.zeros((N,2), dtype=np.float32) 
z = np.zeros((N,1), dtype=np.int32) 
for n in range(N): 
k = np.argmax(np.random.multinomial(1, pi)) 
x[n,:] = np.random.multivariate_normal(mu[k], np.diag(std[k])) 
y[n,:] = mu[k] 
z[n,:] = k 
# конец for 
z = z.flatten() 
return x, y, z, pi, mu, std
def tf_normal(y, mu, sigma): Изотропный многомерный гауссиан 
y_tile = K.stack([y]*num_clusters, axis=1) #[batch_size, K, D] 
result = y_tile - mu 
sigma_tile = K.stack([sigma]*data_dim, axis=-1) #[batch_size, K, D] 
result = result * 1.0/(sigma_tile+1e-8) 
result = -K.square(result)/2.0 
oneDivSqrtTwoPI = 1.0/math.sqrt(2*math.pi)
result = K.exp(result) * (1.0/(sigma_tile + 1e-8))*oneDivSqrtTwoPI 
result = K.prod(result, axis=-1) #[batch_size, K] независимые 
➥ и одинаково распределенные гауссианы 
return result
def NLLLoss(y_true, y_pred): Потеря отрицательного логарифмического правдоподобия 
out_mu = y_pred[:,:num_clusters*data_dim] 
out_sigma = y_pred[:,num_clusters*data_dim : num_clusters*(data_dim+1)] 
out_pi = y_pred[:,num_clusters*(data_dim+1):] 
out_mu = K.reshape(out_mu, [-1, num_clusters, data_dim]) 
result = tf_normal(y_true, out_mu, out_sigma) 
result = result * out_pi 
result = K.sum(result, axis=1, keepdims=True) 
result = –K.log(result + 1e-8) 
result = K.mean(result) 
return tf.maximum(result, 0)
# генерация данных
X_data, y_data, z_data, pi_true, mu_true, sigma_true = generate_data(4096)
data_dim = X_data.shape[1]
num_clusters = len(mu_true)
num_train = 3500
X_train, X_test, y_train, y_test = X_data[:num_train,:],
➥ X_data[num_train:,:], y_data[:num_train,:], y_data[num_train:,:]
z_train, z_test = z_data[:num_train], z_data[num_train:]
# визуализация данных
plt.figure()
plt.scatter(X_train[:,0], X_train[:,1], c=z_train, cmap=cm.bwr)
plt.title('training data')
plt.show()
#plt.savefig(SAVE_PATH + '/mdn_training_data.png')
batch_size = 128Параметры обучения
num_epochs = 128
hidden_size = 32Параметры модели
weight_decay = 1e-4
# Архитектура MDN
input_data = Input(shape=(data_dim,))
x = Dense(32, activation='relu')(input_data)
x = Dropout(0.2)(x)
x = BatchNormalization()(x)
x = Dense(32, activation='relu')(x)
x = Dropout(0.2)(x)
x = BatchNormalization()(x)
mu = Dense(num_clusters * data_dim, activation
➥ ='linear')(x) Кластерные средние
sigma = Dense(num_clusters, activation=K.exp)(x) Диагональная ковариационная матрица
pi = Dense(num_clusters, activation='softmax')(x) Пропорции смеси
out = concatenate([mu, sigma, pi], axis=-1)
model = Model(input_data, out)
model.compile( 
loss=NLLLoss, 
optimizer=tf.keras.optimizers.Adam(), 
metrics=["accuracy"]
)
model.summary()
# Определение обратных вызовов
file_name = SAVE_PATH + 'mdn-weights-checkpoint.h5'
checkpoint = ModelCheckpoint(file_name, monitor='val_loss', verbose=1,
➥ save_best_only=True, mode='min')
reduce_lr = LearningRateScheduler(scheduler, verbose=1)
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0.01,
➥ patience=16, verbose=1)
#tensor_board = TensorBoard(log_dir='./logs', write_graph=True)
callbacks_list = [checkpoint, reduce_lr, early_stopping]
hist = model.fit(X_train, y_train, batch_size
➥ =batch_size, epochs=num_epochs, callbacks
➥ =callbacks_list, validation_split=0.2,
➥ shuffle=True, verbose=2) Обучение модели
y_pred = model.predict(X_test) Оценка модели
mu_pred = y_pred[:,:num_clusters*data_dim]
mu_pred = np.reshape(mu_pred, [-1, num_clusters, data_dim])
sigma_pred = y_pred[:,num_clusters*data_dim : num_clusters*(data_dim+1)]
pi_pred = y_pred[:,num_clusters*(data_dim+1):]
z_pred = np.argmax(pi_pred, axis=-1)
rand_score = adjusted_rand_score(z_test, z_pred)
print("adjusted rand score: ", rand_score)
nmi_score = normalized_mutual_info_score(z_test, z_pred)
print("normalized MI score: ", nmi_score)
mu_pred_list = []
sigma_pred_list = []
for label in np.unique(z_pred): 
z_idx = np.where(z_pred == label)[0] 
mu_pred_lbl = np.mean(mu_pred[z_idx,label,:], axis=0) 
mu_pred_list.append(mu_pred_lbl) 
sigma_pred_lbl = np.mean(sigma_pred[z_idx,label], axis=0) 
sigma_pred_list.append(sigma_pred_lbl)
# конец for
print("true means:")
print(np.array(mu_true))
print("predicted means:")
print(np.array(mu_pred_list))
print("true sigmas:")
print(np.array(sigma_true))
print("predicted sigmas:")
print(np.array(sigma_pred_list))
# создание графиков
plt.figure()
plt.scatter(X_test[:,0], X_test[:,1], c=z_pred, cmap=cm.bwr)
plt.scatter(np.array(mu_pred_list)[:,0], np.array(mu_pred_list)[:,1], 
➥ s=100, marker='x', lw=4.0, color='k')
plt.title('Тестовые данные')
plt.figure()
plt.plot(hist.history['loss'], 'b', lw=2.0, label='Обучение')
plt.plot(hist.history['val_loss'], '--r', lw=2.0, label='Валидация')
plt.title('Сеть смешанной плотности')
plt.xlabel('Эпоха')
plt.ylabel('Потеря отрицательного логарифмического правдоподобия')
plt.legend(loc='upper left')

На рис. 11.8 показаны кластерные центроиды, наложенные поверх тестовых данных, а также потери при обучении и валидации.

Рис. 11.8. Сеть смешанной плотности: центроиды кластеров и потери при обучении и валидации

Рис. 11.8. Сеть смешанной плотности: центроиды кластеров и потери при обучении и валидации

Можно видеть, что прогнозируемые средние значения близки к центрам кла­стеров. Интересно заметить, что для приведенного примера два центра кластера совпадают. Читатель может поэкспериментировать с различными значениями начального числа (seed) и количеством обучающих точек, чтобы понять пове­дение модели. Кроме того, из графиков видно, что потери как при обучении, так и при валидации уменьшаются с увеличением количества эпох. В следующем разделе мы рассмотрим мощную архитектуру трансформера, построенную по принципу обучения с самоконтролем (self-supervised learning).

11.3. Внимание и трансформеры

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

Передовые алгоритмы глубокого обучения - 19

Здесь ats — это обучаемые значения весов внимания, определяемые следующим образом:

Передовые алгоритмы глубокого обучения - 20

Существует несколько способов обучения функции оценки (score), например мультипликативный стиль (multiplicative style):

Передовые алгоритмы глубокого обучения - 21

Здесь W — промежуточная обучаемая матрица. Внимание можно обобщенно представить себе как мягкий поиск по словарю (soft dictionary lookup). Мягкий поиск по словарю относится к типу поиска, при котором точное соответствие не найдено. Это полезно при поиске слов, которые могли быть написаны с ошиб­ками или связаны с искомым термином. Мы можем рассматривать механизм внимания как сравнение набора целевых векторов, или запросов, qi с набором векторов-кандидатов, или ключей, kj. Для каждого запроса мы оцениваем, на­сколько он связан с каждым ключом, а затем используем эти оценки для взвеши­вания и суммирования значений vj, связанных с каждым ключом. Таким образом, матрицу внимания A можно определить следующим образом:

Передовые алгоритмы глубокого обучения - 22

Учитывая весовые коэффициенты внимания Aij, вычисляем взвешенную комби­нацию значений vj, связанных с каждым ключом. В результате для i-го запроса имеем следующее:

Передовые алгоритмы глубокого обучения - 23

Здесь можно выбрать нормированную мультипликативную оценку с W = 1:

Передовые алгоритмы глубокого обучения - 24

Если имеется N запросов (Q размера N × D) и N пар ключ — значение (K разме­ром N × D), то можем записать результат (взвешенный набор значений V, ключи которого K больше всего напоминают запрос Q) в матричной форме:

Передовые алгоритмы глубокого обучения - 25

Множитель softmax в произведении гарантирует, что распределение дает в сум­ме 1, и его можно рассматривать как указатель на место, куда нужно смотреть в матрице значений V.

На рис. 11.9 показана тепловая карта матрицы внимания для нейронного ма­шинного перевода (neural machine translation, NMT). В нем запрос — это целевая последовательность, в то время как ключи и значения — исходная последова­тельность. Диаграмма показывает, на какие исходные английские слова модель обращает внимание при создании целевой переводной последовательности на испанском языке.

Самовнимание — ключевой компонент архитектуры трансформера. Транс­формер — это модель Seq2Seq, которая использует внимание как в энкодере, так и в декодере, что устраняет необходимость в РНС. На рис. 11.10 показана архитектура трансформера — см. статью Ашиша Васвани (Ashish Vaswani) и др. «Attention Is All You Need» (NeurIPS, 2017). Давайте более детально рассмотрим строительные блоки трансформера.

Рис. 11.9. Тепловая карта матрицы внимания для нейронного машинного перевода

Рис. 11.9. Тепловая карта матрицы внимания для нейронного машинного перевода
Передовые алгоритмы глубокого обучения - 27

На верхнем уровне мы видим архитектуру «энкодер — декодер» с входами в ле­вой ветви и выходами в правой ветви. Также можно выделить такие элементы, как многоголовое внимание (multihead attention), позиционное кодирование, плотные слои и слои нормализации (normalization layer), а также остаточные связи (residual connection) для облегчения обратного распространения ошибки.

Идею самовнимания можно распространить на многоголовое внимание. По сути, создавая несколько «голов», мы распараллеливаем механизм вни­мания, как показано на рис. 11.11. Выходы нескольких голов затем объединя­ются (concatenate) и преобразуются при помощи плотного слоя. Благодаря многоголовому вниманию модель имеет несколько независимых способов понимания входных данных.

Передовые алгоритмы глубокого обучения - 28
Рис. 11.11. Многоголовое внимание

Рис. 11.11. Многоголовое внимание

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

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

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

Цель маскированного внимания (masked attention) — соблюдать принцип при­чинности при генерации выходного предложения. Поскольку полное предло­жение еще недоступно и генерируется по одному токену за раз, мы маскируем выходные данные, вводя матрицу масок M, которая содержит только два типа значений: ноль и отрицательную бесконечность:

Передовые алгоритмы глубокого обучения - 30

Внимание «энкодер — декодер» — это всего лишь известное нам многоголовое самовнимание, за исключением того, что запрос Q поступает из другого источни­ка, чем ключи K и значения V. Такое внимание также известно в литературе как перекрестное внимание (cross-attention). Стоит запомнить, что в примере с ма­шинным переводом наша целевая последовательность или запрос Q поступает из декодера, в то время как энкодер действует как база данных и предоставляет ключи K и значения V. Интуитивный подход, лежащий в основе слоя внимания «энкодер — декодер», заключается в соединении входных и выходных пред­ложений. Таким образом, внимание «энкодер — декодер» обучается связывать входное предложение с соответствующим выходным словом, определяя, на­сколько каждое целевое слово связано с входными словами.

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

И это все! Теперь мы готовы к знакомству с классификатором на основе транс­формера, реализованным с нуля. Вам стоит попробовать запустить этот код в режиме блокнота на платформе Google Colab (доступна по ссылке https://colab.research.google.com/), чтобы посмотреть на результаты пошагового испол­нения, а также ускорить процесс обучения модели при помощи графического процессора.

Листинг 11.3. Трансформер для текстовой классификации
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from keras.models import Model, Sequential
from keras.layers import Layer, Dense, Dropout, Activation
from keras.layers import LayerNormalization, MultiHeadAttention
from keras.layers import Input, Embedding, GlobalAveragePooling1D
from keras import regularizers
from keras.preprocessing import sequence
from keras.utils import np_utils
from keras.callbacks import ModelCheckpoint
from keras.callbacks import TensorBoard
from keras.callbacks import LearningRateScheduler
from keras.callbacks import EarlyStopping
import matplotlib.pyplot as plt
tf.keras.utils.set_random_seed(42)
SAVE_PATH = "/content/drive/MyDrive/Colab Notebooks/data/"
def scheduler(epoch, lr): Изменение скорости обучения 
if epoch < 4: 
return lr 
else: 
return lr * tf.math.exp(-0.1)
# Загрузка набора данных
max_words = 20000 Топ-20 тысяч самых частотных слов
seq_len = 200 Первые 200 слов каждой рецензии на фильм
(x_train, y_train), (x_val, y_val) =
➥ keras.datasets.imdb.load_data(num_words=max_words)
x_train = keras.utils.pad_sequences(x_train, maxlen=seq_len)
x_val = keras.utils.pad_sequences(x_val, maxlen=seq_len)
class TransformerBlock(Layer): 
def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1): 
super(TransformerBlock, self).__init__() 
self.att = MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim) 
self.ffn = Sequential( 
[Dense(ff_dim, activation="relu"), Dense(embed_dim)] 
) 
self.layernorm1 = LayerNormalization(epsilon=1e-6) 
self.layernorm2 = LayerNormalization(epsilon=1e-6) 
self.dropout1 = Dropout(rate) 
self.dropout2 = Dropout(rate) 
def call(self, inputs, training): 
attn_output = self.att(inputs, inputs) 
attn_output = self.dropout1(attn_output, training=training) 
out1 = self.layernorm1(inputs + attn_output) 
ffn_output = self.ffn(out1) 
ffn_output = self.dropout2(ffn_output, training=training) 
return self.layernorm2(out1 + ffn_output)
class TokenAndPositionEmbedding(Layer): 
def __init__(self, maxlen, vocab_size, embed_dim): 
super(TokenAndPositionEmbedding, self).__init__() 
self.token_emb = Embedding(input_dim=vocab_size, 
➥ output_dim=embed_dim) 
self.pos_emb = Embedding(input_dim=maxlen, output_dim=embed_dim) 
def call(self, x): 
maxlen = tf.shape(x)[-1] 
positions = tf.range(start=0, limit=maxlen, delta=1) 
positions = self.pos_emb(positions) 
x = self.token_emb(x) 
return x + positions
batch_size = 32Параметры обучения
num_epochs = 8
embed_dim = 32Параметры модели
num_heads = 2
ffdim = 32
# архитектура трансформера
inputs = Input(shape=(seq_len,))
embedding_layer = TokenAndPositionEmbedding(seq_len, max_words, embed_dim)
x = embedding_layer(inputs)
transformer_block = TransformerBlock(embed_dim, num_heads, ff_dim)
x = transformer_block(x)
x = GlobalAveragePooling1D()(x)
x = Dropout(0.1)(x)
x = Dense(20, activation="relu")(x)
x = Dropout(0.1)(x)
outputs = Dense(2, activation="softmax")(x)
model = Model(inputs=inputs, outputs=outputs)
model.compile( 
loss=keras.losses.SparseCategoricalCrossentropy(), 
optimizer=tf.keras.optimizers.Adam(), 
metrics=["accuracy"]
)
# Определение обратных вызовов
file_name = SAVE_PATH + 'transformer-weights-checkpoint.h5'
#checkpoint = ModelCheckpoint(file_name, monitor='val_loss', verbose=1,
➥ save_best_only=True, mode='min')
reduce_lr = LearningRateScheduler(scheduler, verbose=1)
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0.01,
➥ patience=16, verbose=1)
#tensor_board = TensorBoard(log_dir='./logs', write_graph=True)
callbacks_list = [reduce_lr, early_stopping]
hist = model.fit(x_train, y_train, batch_size=batch_size,
➥ epochs=num_epochs, callbacks=callbacks_list, validation_data=(x_val,
➥ y_val)) Оценка модели
test_scores = model.evaluate(x_val, y_val, verbose=2) Обучение модели
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])
plt.figure()
plt.plot(hist.history['loss'], 'b', lw=2.0, label='Обучение')
plt.plot(hist.history['val_loss'], '--r', lw=2.0, label='Валидация')
plt.title('Трансформерная модель')
plt.xlabel('Эпоха')
plt.ylabel('Потери кросс-энтропии')
plt.legend(loc='upper right')
plt.show()
plt.figure()
plt.plot(hist.history['accuracy'], 'b', lw=2.0, label='Обучение')
plt.plot(hist.history['val_accuracy'], '--r', lw=2.0, label='Валидация')
plt.title('Трансформерная модель')
plt.xlabel('Эпоха')
plt.ylabel('Доля верных результатов')
plt.legend(loc='upper left')
plt.show()
plt.figure()
plt.plot(hist.history['lr'], lw=2.0, label='learning rate')
plt.title('Transformer model')
plt.xlabel('Epochs')
plt.ylabel('Learning Rate')
plt.legend()
plt.show()
Рис. 11.12. Классификатор на базе трансформера: потери (слева) и доля верных результатов (справа) на обучающем и валидационном наборах данных

Рис. 11.12. Классификатор на базе трансформера: потери (слева) и доля верных результатов (справа) на обучающем и валидационном наборах данных

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

11.4. Графовые нейронные сети

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

Пусть G = (V, E) — граф с вершинами V и ребрами E. Чтобы отразить реберную связь, можно построить матрицу смежности A, причем Aij = 1, если ребро суще­ствует между вершинами i и j, и 0 в противном случае. Для неориентированных графов матрица смежности будет симметричной: A = AT. А еще при обучении ГНС будет полезна матрица признаков вершин X. Если у нас есть N вершин и F признаков на вершину, то размер X равен N Ч F.

Например, в наборе данных CORA каждая вершина представляет собой до­кумент, а каждое ребро — цитату, которая образует направленное ребро между двумя вершинами. Мы можем зафиксировать отношения цитирования с помо­щью матрицы смежности A. Поскольку каждый документ представляет собой набор слов, то можно ввести признаки — индикаторы, которые сигнализируют нам, присутствует ли в документе то или иное словарное слово. В этом случае N будет количеством документов, а F — размером словаря. Таким образом, мы можем представить текстовую информацию в каждом документе с помощью двоичной матрицы X размером N Ч F.

Важно заметить, что ребра также могут иметь свой собственный набор призна­ков. В этом случае, если размер признаков ребер равен S, а количество ребер равно M, мы можем построить матрицу признаков ребер E размером M Ч S. Также важно провести различие между классификацией всего графа как целого (на­пример, при классификации молекул) и классификацией вершин внутри графа (например, как в сети цитирования CORA). В первом случае мы имеем дело с классификацией в пакетном режиме (batch mode classification), а во втором — с последовательной классификацией (single mode classification).

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

Мы можем рассматривать распространение информации в спектральной ГСС как распространение сигнала вдоль вершин. Спектральная ГСС использует раз­ложение по собственным векторам матрицы Лапласа графа для распространения сигнала с помощью следующего ключевого уравнения прямого распространения:

Передовые алгоритмы глубокого обучения - 32

Давайте подробно разберем это уравнение. Если мы возьмем матрицу смеж­ности A и умножим ее на матрицу признаков X, то произведение AX будет представлять собой сумму признаков соседних вершин. Однако суммирование производится по всем соседям, за исключением самой вершины. Чтобы испра­вить это, мы добавляем петлю в матрицу смежности, складывая ее с единичной матрицей. Мы получаем (A + I) X, но нам не хватает нормировки. Чтобы ввести ее, мы добавляем деление на степени всех вершин. Таким образом, мы форми­руем диагональную степенную матрицу D и умножаем наше выражение на D в степени –1/2 с левой и правой стороны. Затем мы производим знакомые нам операции: добавляем умножение на обучаемую матрицу весов W и слагаемое смещения b. Мы добавляем поверх этого нелинейность — и вуаля! Уравнение прямого распространения ГСС готово.

Теперь у нас есть все составляющие для реализации графовой нейронной сети с использованием библиотеки Spektral Keras/Tensorflow. В листинге 11.4 мы начинаем с импорта набора данных, который можно найти в папке данных репо­зитория кода на GitHub. Мы подготавливаем данные к обработке и определяем архитектуру графовой нейронной сети. Далее мы обучаем модель и отображаем результаты. Вам стоит попробовать запустить этот код в режиме блокнота на платформе Google Colab (доступна по ссылке https://colab.research.google.com/), чтобы посмотреть на результаты пошагового исполнения, а также ускорить про­цесс обучения модели при помощи графического процессора.

Графовая сверточная нейронная сеть для классификации графов цитирования
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow import keras
import networkx as nx
from tensorflow.keras.utils import to_categorical
from sklearn.preprocessing import LabelEncoder
from sklearn.utils import shuffle
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from spektral.layers import GCNConv
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dropout, Dense
from tensorflow.keras import Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import TensorBoard, EarlyStopping
from tensorflow.keras.regularizers import l2
import os
from collections import Counter
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
tf.keras.utils.set_random_seed(42)
SAVE_PATH = "/content/drive/MyDrive/Colab Notebooks/data/"
DATA_PATH = "/content/drive/MyDrive/data/cora/"
column_names = ["paper_id"] + [f"term_{idx}" for idx in range(1433)] +
➥ ["subject"]
node_df = pd.read_csv(DATA_PATH + "cora.content", sep="t", header=None,
➥ names=column_names)
print("Node df shape:", node_df.shape)
edge_df = pd.read_csv(DATA_PATH + "cora.cites", sep="t", header=None,
➥ names=["target", "source"])
print("Edge df shape:", edge_df.shape)
nodes = nodedf.iloc[:,0].tolist()Парсинг данных вершин
labels = node_df.iloc[:,-1].tolist()
X = node_df.iloc[:,1:-1].values
X = np.array(X,dtype=int)
N = X.shape[0] # количество вершин
F = X.shape[1] # размер признаков вершин
edge_list = [(x, y) for x, y in zip(edge_df['target'],
➥ edge_df['source'])] Парсинг данных ребер
num_classes = len(set(labels))
print('Number of nodes:', N)
print('Number of features of each node:', F)
print('Labels:', set(labels))
print('Number of classes:', num_classes)
def sample_data(labels, limit=20, val_num=500, test_num=1000): 
label_counter = dict((l, 0) for l in labels) 
train_idx = [] 
for i in range(len(labels)): 
label = labels[i] 
if label_counter[label]<limit: 
# добавление примера к обучающим данным 
train_idx.append(i) 
label_counter[label]+=1
# выход из цикла при нахождении 20 примеров каждого класса 
if all(count == limit for count in label_counter.values()): 
break 
# получение индексов, не попадающих в обучающие данные 
rest_idx = [x for x in range(len(labels)) if x not in train_idx] 
# получение val_num первых индексов 
val_idx = rest_idx[:val_num] 
test_idx = rest_idx[val_num:(val_num+test_num)] 
return train_idx, val_idx,test_idx
train_idx,val_idx,test_idx = sample_data(labels)
# назначение маски
train_mask = np.zeros((N,),dtype=bool)
train_mask[train_idx] = True
val_mask = np.zeros((N,),dtype=bool)
val_mask[val_idx] = True
test_mask = np.zeros((N,),dtype=bool)
test_mask[test_idx] = True
print("Training data distribution:n{}".format(Counter([labels[i] for i in
➥ train_idx])))
print("Validation data distribution:n{}".format(Counter([labels[i] for i
➥ in val_idx])))
def encode_label(labels): 
label_encoder = LabelEncoder() 
labels = label_encoder.fit_transform(labels) 
labels = to_categorical(labels) 
return labels, label_encoder.classes_
labels_encoded, classes = encode_label(labels)
G = nx.Graph()Построение графа
G.add_nodes_from(nodes)
G.add_edges_from(edge_list)
A = nx.adjacency_matrix(G) Получение матрицы смежности
print('Graph info: ', nx.info(G))
# Параметры
channels = 16 Количество каналов в первом слое
dropout = 0.5 Коэффициент прореживания признаков
l2_reg = 5e-4 Коэффициент регуляризации L2
learning_rate = 1e-2 Скорость обучения
epochs = 200 Количество эпох обучения
es_patience = 10 Параметр терпения для раннего останова
# Операции предварительной обработки
A = GCNConv.preprocess(A).astype('f4')
# Определение модели
X_in = Input(shape=(F, ))
fltr_in = Input((N, ), sparse=True)
dropout_1 = Dropout(dropout)(X_in)
graph_conv_1 = GCNConv(channels, 
activation='relu', 
kernel_regularizer=l2(l2_reg), 
use_bias=False)([dropout_1, fltr_in])
dropout_2 = Dropout(dropout)(graph_conv_1)
graph_conv_2 = GCNConv(num_classes, 
activation='softmax', 
use_bias=False)([dropout_2, fltr_in])
# Построение модели
model = Model(inputs=[X_in, fltr_in], outputs=graph_conv_2)
model.compile(optimizer=Adam(learning_rate=learning_rate), 
loss='categorical_crossentropy', 
weighted_metrics=['accuracy'])
model.summary()
# Обучение модели
validation_data = ([X, A], labels_encoded, val_mask)
hist = model.fit([X, A], 
labels_encoded, 
sample_weight=train_mask, 
epochs=epochs, 
batch_size=N, 
validation_data=validation_data, 
shuffle=False, 
callbacks=[ 
EarlyStopping(patience=es_patience, restore_best_weights=True) 
]) Обучение модели
# Оценка модели
X_test = X
A_test = A
y_test = labels_encoded
y_pred = model.predict([X_test, A_test], batch_size=N) Оценка модели
report = classification_report(np.argmax(y_test,axis=1),
➥ np.argmax(y_pred,axis=1), target_names=classes)
print('GCN Classification Report: n {}'.format(report))
layer_outputs = [layer.output for layer in model.layers]
activation_model = Model(inputs=model.input, outputs=layer_outputs)
activations = activation_model.predict([X,A],batch_size=N)
# Получение результатов t-SNE 
# получение представления скрытого слоя после первого слоя ГСС
x_tsne = TSNE(n_components=2).fit_transform(activations[3])
def plot_tSNE(labels_encoded,x_tsne): 
color_map = np.argmax(labels_encoded, axis=1) 
plt.figure(figsize=(10,10)) 
for cl in range(num_classes): 
indices = np.where(color_map==cl) 
indices = indices[0] 
plt.scatter(x_tsne[indices,0], x_tsne[indices, 1], label=cl) 
plt.legend() 
plt.show()
plot_tSNE(labels_encoded,x_tsne)
plt.figure()
plt.plot(hist.history['loss'], 'b', lw=2.0, label='Обучение')
plt.plot(hist.history['val_loss'], '--r', lw=2.0, label='Валидация')
plt.title('ГНС-модель')
plt.xlabel('Эпоха')
plt.ylabel('Потери кросс-энтропии')
plt.legend(loc='upper right')
plt.show()
plt.figure()
plt.plot(hist.history['accuracy'], 'b', lw=2.0, label='Обучение') 
plt.plot(hist.history['val_accuracy'], '--r', lw=2.0, label='Валидация') 
plt.title('ГНС-модель')
plt.xlabel('Эпоха')
plt.ylabel('Доля верных результатов')
plt.legend(loc='upper left')
plt.show()

На рис. 11.13 показаны потери классификации и верность результатов для обучающего и валидационного наборов данных:

Рис. 11.13. Потери и доля верных результатов ГНС-модели для обучающего и валидационного наборов данных

Рис. 11.13. Потери и доля верных результатов ГНС-модели для обучающего и валидационного наборов данных

На рис. 11.14 показано представление скрытого слоя ГСС, визуализированное с помощью метода t-SNE, для набора данных CORA:

Рис. 11.14. t-SNE-визуализация представления скрытого слоя после первого слоя ГСС для набора данных CORA

Рис. 11.14. t-SNE-визуализация представления скрытого слоя после первого слоя ГСС для набора данных CORA

Оформить предзаказ на книгу «Алгоритмы машинного обучения» со скидкой 30%
можно на нашем сайте по промокоду – Manning

Автор: ph_piter

Источник

Rambler's Top100