Цель
В этой статье мы расскажем, как генерировать табличные синтетические данные с помощью GAN. Ожидается, что сгенерированные данные будут похожи на реальные данные для обучения и тестирования модели.
Введение
В работе с машинным обучением мы часто сталкиваемся с ситуацией, когда данных недостаточно для обучения моделей, и нам нужно больше искусственных данных. GAN (Generative Adversarial Networks) - это архитектура глубокого обучения, представленная Яном Гудфеллоу и др. В 2014 году (1). Сети GAN могут генерировать синтетические данные с нуля и состоять из двух компонентов: генератора и дискриминатора. Генератор используется для создания поддельных данных из входного случайного шума; Дискриминатор используется для классификации образцов, настоящих или поддельных (произведенных генератором). Производительность дискриминатора используется для обновления и оптимизации генератора и дискриминатора. В настоящее время GANS широко применяется для создания данных изображений, но не во многих статьях о табличных данных. Одна из причин заключается в том, что синтетические данные, не являющиеся изображениями, сложно оценить по качеству. В этом посте мы попробуем сгенерировать одномерные синтетические данные с нуля.
Набор данных
Набор данных о диабете взят из общедоступных наборов данных Kaggle: https://www.kaggle.com/uciml/pima-indians-diabetes-database
Базовая точность для реального набора данных
В этом разделе мы будем использовать реальные данные для обучения модели случайного леса и получения точности модели. Точность модели, обученной на реальных данных, используется в качестве базовой точности для сравнения с сгенерированными поддельными данными. Полные коды доступны в репозитории git: https://github.com/fzhurd/fzwork/tree/master/medium/ganspost
Сначала мы вводим все запрошенные модули python, читаем csv-файл в pandas как Dataframe и примерно исследуем набор данных.
import numpy as np import pandas as pd import os import matplotlib.pyplot as plt from keras.models import Sequential from keras.layers import Dense from numpy.random import randn from matplotlib import pyplot from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn import metrics data = pd.read_csv('/content/diabetes.csv') print (data.shape) print (data.tail()) print (data.columns)
Набор данных диабета включает 9 столбцов: беременность, глюкоза, артериальное давление, толщина кожи, инсулин, индекс массы тела, функция диабета, родословная, возраст и исход. Столбец OutCome будет меткой.
(768, 9) Pregnancies Glucose ... Age Outcome 763 10 101 ... 63 0 764 2 122 ... 27 0 765 5 121 ... 30 0 766 1 126 ... 47 1 767 1 93 ... 23 0 [5 rows x 9 columns] Index(['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age', 'Outcome'], dtype='object')
Мы будем использовать все столбцы, кроме столбца «Результат», как функции для обучения модели. Столбец результатов будет использоваться в качестве метки модели.
features = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age'] label = ['Outcome'] X = data[features] y = data[label]
Реальный набор данных разделен на набор данных для обучения и тестирования. Модель классификатора случайного леса обучается и оценивается точность.
X_true_train, X_true_test, y_true_train, y_true_test = train_test_split(X, y, test_size=0.30, random_state=42) clf_true = RandomForestClassifier(n_estimators=100) clf_true.fit(X_true_train,y_true_train) y_true_pred=clf_true.predict(X_true_test) print("Base Accuracy:",metrics.accuracy_score(y_true_test, y_true_pred)) print("Base classification report:",metrics.classification_report(y_true_test, y_true_pred))
Получаем, что точность базовой модели для реальных данных составляет около 0,76; Точность составляет около 0,82. Точность модели, обученной на реальных данных, будет базовой точностью для сравнения с моделью, обученной на сгенерированных поддельных данных на дальнейших этапах.
Base Accuracy: 0.7575757575757576 Base classification report: precision recall f1-score support 0 0.82 0.81 0.81 151 1 0.65 0.66 0.65 80 accuracy 0.76 231 macro avg 0.73 0.74 0.73 231 weighted avg 0.76 0.76 0.76 231
Генерация синтетических данных
В этом разделе мы начнем генерировать поддельные данные с помощью GAN. На первом шаге мы определяем функцию generate_latent_points, она будет создавать случайный шум в скрытом пространстве и преобразовывать ее в размеры для соответствия входным данным модели генератора.
def generate_latent_points(latent_dim, n_samples): x_input = randn(latent_dim * n_samples) x_input = x_input.reshape(n_samples, latent_dim) return x_input
Мы определяем функцию generate_fake_samples для создания поддельных данных. На входе генератора будут создаваемые скрытые точки (случайный шум). Генератор предсказывает входной случайный шум и выводит массив numpy. Поскольку это поддельные данные, метка будет равна 0.
# use the generator to generate n fake examples, with class labels def generate_fake_samples(generator, latent_dim, n_samples): x_input = generate_latent_points(latent_dim, n_samples) X = generator.predict(x_input) y = np.zeros((n_samples, 1)) return X, y
Мы определим другую функцию для генерации реальных выборок, она будет случайным образом выбирать образцы из реального набора данных. Метка для реальной выборки данных - 1.
# generate n real samples with class labels; We randomly select n samples from the real data def generate_real_samples(n): X = data.sample(n) y = np.ones((n, 1)) return X, y
Мы создадим простую последовательную модель в виде генератора с модулем Keras. Размер ввода будет таким же, как размер входных образцов. Ядро будет инициализировано "he_uniform". Модель будет иметь 3 слоя, два слоя будут активированы функцией relu. Выходной слой будет активирован «линейной» функцией, и размер выходного слоя будет таким же, как размер набора данных (9 столбцов).
def define_generator(latent_dim, n_outputs=9): model = Sequential() model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=latent_dim)) model.add(Dense(30, activation='relu')) model.add(Dense(n_outputs, activation='linear')) return model
Мы можем проверить информацию о модели генератора, введя значения некоторых параметров.
generator1 = define_generator(10, 9) generator1.summary()
Результат модели генератора следующий:
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 15) 165 _________________________________________________________________ dense_1 (Dense) (None, 30) 480 _________________________________________________________________ dense_2 (Dense) (None, 9) 279 ================================================================= Total params: 924 Trainable params: 924 Non-trainable params: 0 _________________________________________________________________
После того, как мы определили генератор, мы определим дискриминатор на следующем шаге. Дискриминатор также представляет собой простую последовательную модель, включающую 3 плотных слоя. Первые два уровня активируются функцией «relu», выходной слой активируется функцией «сигмоида», потому что он распознает, что входные выборки настоящие (Истина) или поддельные (Ложь).
def define_discriminator(n_inputs=9): model = Sequential() model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=n_inputs)) model.add(Dense(50, activation='relu')) model.add(Dense(1, activation='sigmoid')) # compile model model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) return model
Мы могли проверить информацию о модели дискриминатора, введя значения некоторых параметров.
discriminator1 = define_discriminator(9) discriminator1.summary()
Вывод сводки дискриминатора следующий:
Model: "sequential_7" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_15 (Dense) (None, 25) 250 _________________________________________________________________ dense_16 (Dense) (None, 50) 1300 _________________________________________________________________ dense_17 (Dense) (None, 1) 51 ================================================================= Total params: 1,601 Trainable params: 1,601 Non-trainable params: 0
Мы определим модель Гана после того, как определим модели генератора и дискриминатора. Это также последовательная модель и комбинируют генератор с дискриминатором. ПРИМЕЧАНИЕ: вес модели дискриминатора не должен быть обучаемым.
# define the combined generator and discriminator model, for updating the generator def define_gan(generator, discriminator): # make weights in the discriminator not trainable discriminator.trainable = False model = Sequential() # add generator model.add(generator) # add the discriminator model.add(discriminator) # compile model model.compile(loss='binary_crossentropy', optimizer='adam') return model
Мы создадим функцию plot_history для визуализации окончательной потери генератора и дискриминатора на графике.
# create a line plot of loss for the gan and save to file def plot_history(d_hist, g_hist): # plot loss plt.subplot(1, 1, 1) plt.plot(d_hist, label='d') plt.plot(g_hist, label='gen') plt.show() plt.close()
Наконец, обучим генератор и дискриминатор. Для каждой эпохи мы объединим половину пакета реальных данных и половину пакета поддельных данных, а затем вычислим средние потери. Комбинированная модель будет обновлена на основе функции train_on_batch. Обученный генератор будет сохранен для дальнейшего использования.
# train the generator and discriminator def train(g_model, d_model, gan_model, latent_dim, n_epochs=10000, n_batch=128, n_eval=200): # determine half the size of one batch, for updating the discriminator half_batch = int(n_batch / 2) d_history = [] g_history = [] # manually enumerate epochs for epoch in range(n_epochs): # prepare real samples x_real, y_real = generate_real_samples(half_batch) # prepare fake examples x_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch) # update discriminator d_loss_real, d_real_acc = d_model.train_on_batch(x_real, y_real) d_loss_fake, d_fake_acc = d_model.train_on_batch(x_fake, y_fake) d_loss = 0.5 * np.add(d_loss_real, d_loss_fake) # prepare points in latent space as input for the generator x_gan = generate_latent_points(latent_dim, n_batch) # create inverted labels for the fake samples y_gan = np.ones((n_batch, 1)) # update the generator via the discriminator's error g_loss_fake = gan_model.train_on_batch(x_gan, y_gan) print('>%d, d1=%.3f, d2=%.3f d=%.3f g=%.3f' % (epoch+1, d_loss_real, d_loss_fake, d_loss, g_loss_fake)) d_history.append(d_loss) g_history.append(g_loss_fake) plot_history(d_history, g_history) g_model.save('trained_generated_model.h5')
Мы вводим значение latent_dim равное 10, чтобы начать обучение.
# size of the latent space latent_dim = 10 # create the discriminator discriminator = define_discriminator() # create the generator generator = define_generator(latent_dim) # create the gan gan_model = define_gan(generator, discriminator) # train model train(generator, discriminator, gan_model, latent_dim)
Процедура обучения займет несколько минут в зависимости от вашего компьютера и выглядит следующим образом:
......... >9991, d1=0.858, d2=0.674 d=0.766 g=0.904 >9992, d1=1.023, d2=0.833 d=0.928 g=0.816 >9993, d1=0.737, d2=0.863 d=0.800 g=0.910 >9994, d1=0.780, d2=0.890 d=0.835 g=0.846 >9995, d1=0.837, d2=0.773 d=0.805 g=0.960 >9996, d1=0.762, d2=0.683 d=0.723 g=1.193 >9997, d1=0.906, d2=0.515 d=0.710 g=1.275 >9998, d1=0.814, d2=0.412 d=0.613 g=1.228 >9999, d1=0.701, d2=0.668 d=0.685 g=1.105 >10000, d1=0.461, d2=0.814 d=0.638 g=1.097
Изменения потери генератора и дискриминатора отображаются следующим образом: потеря синего цвета в дискриминаторе; оранжевый-потеря генератора
Оцените качество созданных поддельных данных с помощью модели
Мы успешно обучили генератор на вышеуказанных шагах. В этом разделе мы создадим поддельные данные с помощью обученной модели и проверим качество поддельных данных. Сначала загрузим модель обученного генератора.
from keras.models import load_model model =load_model('/content/trained_generated_model')
Мы создадим поддельные данные с помощью модели обученного генератора. Поддельные данные - 750 строк. Затем мы конвертируем созданные поддельные данные в pandas Dataframe.
latent_points = generate_latent_points(10, 750) X = model.predict(latent_points) data_fake = pd.DataFrame(data=X, columns=['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age', 'Outcome']) data_fake.head()
Вывод информации о 5 строках фальшивых данных выглядит следующим образом:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome 3.042421 84.372429 41.264584 15.499371 75.576080 16.862654 0.643298 30.715979 0.131986 2.379814 65.569473 34.632591 9.681239 153.032700 14.792008 0.301202 11.963096 -0.200955 -0.212970 104.455383 40.059303 9.538709 0.783831 20.410034 0.439094 13.447835 0.229936 12.437524 257.148895 125.773453 2.465484 1.408619 50.760799 0.756833 113.432060 0.949813 3.571342 34.856190 30.242983 17.523539 1.804614 18.132822 0.289309 23.509460 -0.023842
Столбец «Результат» в реальных данных равен 0 или 1. Следовательно, нам нужно сопоставить значение сгенерированных поддельных данных с 0 или 1.
outcome_mean = data_fake.Outcome.mean() data_fake['Outcome'] = data_fake['Outcome'] > outcome_mean data_fake["Outcome"] = data_fake["Outcome"].astype(int)
Мы сделаем то же самое для фальшивых данных. Ярлык - столбец «Результат».
features = ['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI', 'DiabetesPedigreeFunction', 'Age'] label = ['Outcome'] X_fake_created = data_fake[features] y_fake_created = data_fake[label]
Мы обучим модель классификатора случайного леса на поддельных данных и получим точность. Он будет использоваться для сравнения с точностью базовой модели.
X_fake_train, X_fake_test, y_fake_train, y_fake_test = train_test_split(X_fake_created, y_fake_created, test_size=0.30, random_state=42) clf_fake = RandomForestClassifier(n_estimators=100) clf_fake.fit(X_fake_train,y_fake_train) y_fake_pred=clf_fake.predict(X_fake_test) print("Accuracy of fake data model:",metrics.accuracy_score(y_fake_test, y_fake_pred)) print("Classification report of fake data model:",metrics.classification_report(y_fake_test, y_fake_pred))
Выходы режима следующие:
Accuracy of fake data model: 0.88 Classification report of fake data model: precision recall f1-score support 0 0.86 0.94 0.90 127 1 0.92 0.80 0.85 98 accuracy 0.88 225 macro avg 0.89 0.87 0.88 225 weighted avg 0.88 0.88 0.88 225
Точность новой обученной модели с сгенерированными поддельными данными составляет около 0,88; По сравнению с моделью, обученной на реальных данных, составляет около 0,75. Кажется, что модель поддельных данных все еще искажена по сравнению с реальными данными.
Оцените качество созданных поддельных данных с помощью Table_evaluator
Table_evaluator - это библиотека для оценки того, насколько синтезированный набор данных похож на реальный набор данных. Он подходит для оценки сгенерированных синтетических данных. Сначала мы установим модуль table_evaluator.
!pip install table_evaluator
После установки мы будем использовать table_evaluator для анализа столбца Outcome по сравнению со столбцом Outcome в реальных данных.
from table_evaluator import load_data, TableEvaluator table_evaluator = TableEvaluator(data, data_fake) table_evaluator.evaluate(target_col='Outcome')
Вывод сходства следующий. Мы могли обнаружить, что сгенерированные синтетические данные похожи на реальные. Средняя корреляция между поддельными и настоящими столбцами составляет 0,9359, а оценка сходства составляет около 0,6011.
Correlation metric: pearsonr Classifier F1-scores and their Jaccard similarities: f1_real f1_fake jaccard_similarity index LogisticRegression_real_testset 0.7467 0.6333 0.5075 LogisticRegression_fake_testset 0.4867 0.9267 0.3514 RandomForestClassifier_real_testset 0.7267 0.6133 0.4634 RandomForestClassifier_fake_testset 0.4467 0.9200 0.2658 DecisionTreeClassifier_real_testset 0.7200 0.6333 0.4634 DecisionTreeClassifier_fake_testset 0.4600 0.8733 0.3043 MLPClassifier_real_testset 0.6800 0.5600 0.3393 MLPClassifier_fake_testset 0.3800 0.9133 0.2000 Miscellaneous results: Result Column Correlation Distance RMSE 0.4230 Column Correlation distance MAE 0.3552 Duplicate rows between sets (real/fake) (0, 0) nearest neighbor mean 1.5898 nearest neighbor std 0.7154 Results: Result Basic statistics 0.9364 Correlation column correlations 0.1430 Mean Correlation between fake and real columns 0.9337 1 - MAPE Estimator results 0.3912 Similarity Score 0.6011 {'1 - MAPE Estimator results': 0.3911924307763016, 'Basic statistics': 0.9364221364221365, 'Correlation column correlations': 0.1430372959033057, 'Mean Correlation between fake and real columns': 0.9336602090844196, 'Similarity Score': 0.6010780180465408}
С помощью инструмента table_evaluator мы также могли исследовать реальные и синтетические данные с помощью графика визуализации, как показано ниже:
table_evaluator.visual_evaluation()
Резюме
Из оценки точности модели и оценки table_evaluator мы могли бы сделать вывод, что некоторые из функций точно соответствуют характеристикам реальных данных. Некоторые другие функции нуждаются в дальнейшем обучении. Мы могли бы продолжить работу над обучением модели и нормализацией данных, чтобы получить лучшие результаты.