Руководство по прогнозированию диагноза инсульта с помощью нейронной сети на основе встраивания категориальных признаков.

По данным Всемирной организации здравоохранения, инсульт занимает второе место среди причин смерти в мире, на него приходится примерно 11% всех смертей.

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

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

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

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

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

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

Содержание

  1. Быстрый исследовательский анализ данных
  2. Конвейер подготовки данных
  3. Реализация модели
  4. Ограничения

1) Быстрый исследовательский анализ данных

Давайте быстро взглянем на данные.

display(df.head())

Распределение количества для целевой переменной

df.stroke.value_counts().plot.bar()

Проверьте, есть ли у нас пропущенные значения

df.isna().sum()

Индекс массы тела (ИМТ), по-видимому, имеет несколько отсутствующих значений. Поскольку это также числовая переменная, мы будем использовать простое усреднение, чтобы вменить ее пропущенные значения.

# Here we use simple mean value to impute missing values in BMI

df['bmi'] = df['bmi'].fillna(df['bmi'].mean())

2) Конвейер подготовки данных

Цель этой задачи — подготовить и отформатировать данные для подачи в нейронную сеть. Во-первых, мы укажем размеры пространства вложения для всех категориальных переменных. Мы устанавливаем размер встроенной функции равным половине числа уникальных категорий.

# extract categorical features
categorical_features = df.columns[df.dtypes == 'object'].tolist()

# extract numerical features
numerical_features = df.columns.difference(categorical_features + 
                                          ['id', 'stroke']).tolist()


embedding_dims = {
    
    k : (df[k].nunique(), df[k].nunique() // 2)
    
    for k in categorical_features
}

print( embedding_dims )

Затем мы будем обрабатывать числовые и категориальные переменные параллельно, используя приведенный ниже код.

# Convert data into a multi-input list format to match 
# the model architecture
def feature_transformer(train, test):

    input_list_train = []
    input_list_test = []
    
    # Extract all columns(categorical) to be fed into an 
    # embedding layer
    for c in categorical_features:
        # all unique categories in column `c`
        raw_vals = np.unique(train[c])
        val_map = {}
        for i in range(len(raw_vals)):
            # encode unique categories in column `c`
            val_map[raw_vals[i]] = i       
            
        input_list_train.append(train[c].map(val_map).values)
        input_list_test.append(test[c].map(val_map).values)
     
    # Extract remaining non-embedding columns i.e numeric input of 
    # the feedforward embedding layer
    num_cols = [c for c in train.columns if (not c in categorical_features + 
                                      ['id', 'stroke'])
                ]
    input_list_train.append(train[num_cols].values)
    input_list_test.append(test[num_cols].values)
    
    
    return input_list_train, input_list_test  

3) Реализация модели

Мы создадим архитектуру нашей модели, используя Keras.

def get_model(num_numeric_cols):

    act_fn = 'relu' # hidden activation function
    
    dr = 0.2 # dropout rate
    
    lr = 0.001 # learning rate

    inputs = []
    embeddings = []


    for i in embedding_dims.keys():

        input = tf.keras.layers.Input(shape=(1), 
                                      dtype = tf.int32)

        x, y = embedding_dims[i]

        emb = tf.keras.layers.Embedding(x, y,  input_length = 1)(input)

        emb = tf.keras.layers.Reshape(target_shape=(y,))(emb)

        inputs.append(input)
        embeddings.append(emb)


    inp = tf.keras.Input(shape=(num_numeric_cols, ), 
                         name ="numeric_features", 
                         dtype = tf.float32)   

    num_emb = tf.keras.layers.Dense(256, activation = act_fn)(inp)

    inputs.append(inp)
    embeddings.append(num_emb)

    x = tf.keras.layers.Concatenate()(embeddings)

    x = tf.keras.layers.Dense( 128, activation = act_fn)(x)  
    x = tf.keras.layers.BatchNormalization()(x)
    x = tf.keras.layers.Dropout(rate=dr)(x)   

    x = tf.keras.layers.Dense( 128, activation = act_fn)(x)  
    x = tf.keras.layers.BatchNormalization()(x)
    x = tf.keras.layers.Dropout(rate=dr)(x)

    preds = tf.keras.layers.Dense(1,activation = 'sigmoid', 
                                    name ="classifier")(x)

    model = tf.keras.models.Model(inputs = inputs ,outputs = preds )
    
  
    opt = tf.keras.optimizers.Adam(learning_rate=lr)

    model.compile(loss= "binary_crossentropy",
                optimizer= opt,
                metrics = ['Accuracy']
                )
    
    return model

Наш тренировочный цикл может быть выражен ниже;

# Let's create a simple 80/20 train/test split 

# we will stratify the split by the target because we have data
# imbalance problem
df_train, df_test, y_train, y_test = train_test_split(df,
                                                      df['stroke'], 
                                                      test_size =0.2,
                                                      stratify=df['stroke'], 
                                                      shuffle=True, 
                                                      random_state =2023)

# create feature transformations
X_train, X_test = feature_transformer( train = df_train, test = df_test  )

Y_train , Y_test = y_train.values, y_test.values

batch_size = 32
epochs = 100

n_num_cols = len(numerical_features)

# initialize early stopping
es = tf.keras.callbacks.EarlyStopping(patience=20, 
                                      mode='min', 
                                      verbose=1, 
                                      monitor = "val_loss",
                                      min_delta = 0.00001, 
                                      restore_best_weights=True) 

# initialize model
model = get_model(num_numeric_cols = n_num_cols)

model.fit(
        X_train, Y_train,
        validation_data = (X_test, Y_test),
        batch_size = batch_size,
        epochs = epochs,
        callbacks = [es],
        )
Epoch 1/100
128/128 [==============================] - 3s 9ms/step - loss: 0.5026 - Accuracy: 0.7921 - val_loss: 0.4618 - val_Accuracy: 0.8268
Epoch 2/100
128/128 [==============================] - 1s 7ms/step - loss: 0.2476 - Accuracy: 0.9325 - val_loss: 0.1662 - val_Accuracy: 0.9511
Epoch 3/100
128/128 [==============================] - 1s 7ms/step - loss: 0.2021 - Accuracy: 0.9408 - val_loss: 0.1742 - val_Accuracy: 0.9511
Epoch 4/100
128/128 [==============================] - 1s 8ms/step - loss: 0.1833 - Accuracy: 0.9447 - val_loss: 0.1589 - val_Accuracy: 0.9511
Epoch 5/100
128/128 [==============================] - 1s 8ms/step - loss: 0.1918 - Accuracy: 0.9437 - val_loss: 0.1688 - val_Accuracy: 0.9442

4) Ограничение

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

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

Полный код доступен здесь.

Рекомендации

[1] Набор данных Kaggle Stroke