Цель

В этой статье мы расскажем, как генерировать табличные синтетические данные с помощью 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 мы могли бы сделать вывод, что некоторые из функций точно соответствуют характеристикам реальных данных. Некоторые другие функции нуждаются в дальнейшем обучении. Мы могли бы продолжить работу над обучением модели и нормализацией данных, чтобы получить лучшие результаты.

Ссылка

  1. Https://arxiv.org/abs/1406.2661
  2. Https://machinelearningmaster.com/how-to-develop-a-generative-adversarial-network-for-a-1-dimensional-function-from-scratch-in-keras/