Рубрики
Без рубрики

Как обрабатывать переопределение в глубоких моделях обучения

Автор оригинала: Bert Carremans.

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

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

Есть несколько манеров, в которых мы можем уменьшить перегрузочные модели в глубоких учебных моделях. Лучший вариант – это Получите больше тренировочных данных Отказ К сожалению, в реальных ситуациях у вас часто нет этой возможности из-за времени, бюджета или технических ограничений.

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

Начнем, импортируя необходимые пакеты и настроив некоторые параметры. Мы будем использовать Керас Чтобы соответствовать глубоким моделям обучения. Учебные данные – это Данные настроения Airline Airline Twitter набор из Kaggle Отказ

# Basic packages
import pandas as pd 
import numpy as np
import re
import collections
import matplotlib.pyplot as plt
from pathlib import Path
# Packages for data preparation
from sklearn.model_selection import train_test_split
from nltk.corpus import stopwords
from keras.preprocessing.text import Tokenizer
from keras.utils.np_utils import to_categorical
from sklearn.preprocessing import LabelEncoder
# Packages for modeling
from keras import models
from keras import layers
from keras import regularizers
NB_WORDS = 10000  # Parameter indicating the number of words we'll put in the dictionary
NB_START_EPOCHS = 20  # Number of epochs we usually start to train with
BATCH_SIZE = 512  # Size of the batches used in the mini-batch gradient descent
MAX_LEN = 20  # Maximum number of words in a sequence
root = Path('../')
input_path = root / 'input/' 
ouput_path = root / 'output/'
source_path = root / 'source/'

Мы будем использовать некоторые функции помощника на протяжении всего этого поста.

def deep_model(model, X_train, y_train, X_valid, y_valid):
    '''
    Function to train a multi-class model. The number of epochs and 
    batch_size are set by the constants at the top of the
    notebook. 
    
    Parameters:
        model : model with the chosen architecture
        X_train : training features
        y_train : training target
        X_valid : validation features
        Y_valid : validation target
    Output:
        model training history
    '''
    model.compile(optimizer='rmsprop'
                  , loss='categorical_crossentropy'
                  , metrics=['accuracy'])
    
    history = model.fit(X_train
                       , y_train
                       , epochs=NB_START_EPOCHS
                       , batch_size=BATCH_SIZE
                       , validation_data=(X_valid, y_valid)
                       , verbose=0)
    return history
def eval_metric(model, history, metric_name):
    '''
    Function to evaluate a trained model on a chosen metric. 
    Training and validation metric are plotted in a
    line chart for each epoch.
    
    Parameters:
        history : model training history
        metric_name : loss or accuracy
    Output:
        line chart with epochs of x-axis and metric on
        y-axis
    '''
    metric = history.history[metric_name]
    val_metric = history.history['val_' + metric_name]
    e = range(1, NB_START_EPOCHS + 1)
    plt.plot(e, metric, 'bo', label='Train ' + metric_name)
    plt.plot(e, val_metric, 'b', label='Validation ' + metric_name)
    plt.xlabel('Epoch number')
    plt.ylabel(metric_name)
    plt.title('Comparing training and validation ' + metric_name + ' for ' + model.name)
    plt.legend()
    plt.show()
def test_model(model, X_train, y_train, X_test, y_test, epoch_stop):
    '''
    Function to test the model on new data after training it
    on the full training data with the optimal number of epochs.
    
    Parameters:
        model : trained model
        X_train : training features
        y_train : training target
        X_test : test features
        y_test : test target
        epochs : optimal number of epochs
    Output:
        test accuracy and test loss
    '''
    model.fit(X_train
              , y_train
              , epochs=epoch_stop
              , batch_size=BATCH_SIZE
              , verbose=0)
    results = model.evaluate(X_test, y_test)
    print()
    print('Test accuracy: {0:.2f}%'.format(results[1]*100))
    return results
    
def remove_stopwords(input_text):
    '''
    Function to remove English stopwords from a Pandas Series.
    
    Parameters:
        input_text : text to clean
    Output:
        cleaned Pandas Series 
    '''
    stopwords_list = stopwords.words('english')
    # Some words which might indicate a certain sentiment are kept via a whitelist
    whitelist = ["n't", "not", "no"]
    words = input_text.split() 
    clean_words = [word for word in words if (word not in stopwords_list or word in whitelist) and len(word) > 1] 
    return " ".join(clean_words) 
    
def remove_mentions(input_text):
    '''
    Function to remove mentions, preceded by @, in a Pandas Series
    
    Parameters:
        input_text : text to clean
    Output:
        cleaned Pandas Series 
    '''
    return re.sub(r'@\w+', '', input_text)
def compare_models_by_metric(model_1, model_2, model_hist_1, model_hist_2, metric):
    '''
    Function to compare a metric between two models 
    
    Parameters:
        model_hist_1 : training history of model 1
        model_hist_2 : training history of model 2
        metrix : metric to compare, loss, acc, val_loss or val_acc
        
    Output:
        plot of metrics of both models
    '''
    metric_model_1 = model_hist_1.history[metric]
    metric_model_2 = model_hist_2.history[metric]
    e = range(1, NB_START_EPOCHS + 1)
    
    metrics_dict = {
        'acc' : 'Training Accuracy',
        'loss' : 'Training Loss',
        'val_acc' : 'Validation accuracy',
        'val_loss' : 'Validation loss'
    }
    
    metric_label = metrics_dict[metric]
    plt.plot(e, metric_model_1, 'bo', label=model_1.name)
    plt.plot(e, metric_model_2, 'b', label=model_2.name)
    plt.xlabel('Epoch number')
    plt.ylabel(metric_label)
    plt.title('Comparing ' + metric_label + ' between models')
    plt.legend()
    plt.show()
    
def optimal_epoch(model_hist):
    '''
    Function to return the epoch number where the validation loss is
    at its minimum
    
    Parameters:
        model_hist : training history of model
    Output:
        epoch number with minimum validation loss
    '''
    min_epoch = np.argmin(model_hist.history['val_loss']) + 1
    print("Minimum validation loss reached in epoch {}".format(min_epoch))
    return min_epoch

Очистка данных

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

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

df = pd.read_csv(input_path / 'Tweets.csv')
df = df.reindex(np.random.permutation(df.index))  
df = df[['text', 'airline_sentiment']]
df.text = df.text.apply(remove_stopwords).apply(remove_mentions)

ТЕХНИЧЕСКИЙ ТЕСТ СПАДА

Оценка производительности модели должна быть выполнена на отдельном тестовом наборе. Таким образом, мы можем оценить, насколько хорошо модель обобщает. Это делается с rain_test_split Метод Scikit-Surve.

X_train, X_test, y_train, y_test = train_test_split(df.text, df.airline_sentiment, test_size=0.1, random_state=37)

Преобразование слов на цифры

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

Мы убираем текст, применяя Фильтры и положить слова на строчные буквы Отказ Слова разделены пробелами.

tk = Tokenizer(num_words=NB_WORDS,
               filters='!"#$%&()*+,-./:;<=>?@[\\]^_`{"}~\t\n',
               lower=True,
               char_level=False,
               split=' ')
tk.fit_on_texts(X_train)

После создания словаря мы можем преобразовать текст Tweet в вектор с значениями NB_Words. С Режим = двоичный Он содержит индикатор, появлялось ли слово в Tweet или нет. Это делается с texts_to_matrix Способ токенализатора.

X_train_oh = tk.texts_to_matrix(X_train, mode='binary')
X_test_oh = tk.texts_to_matrix(X_test, mode='binary')

Преобразование целевых классов на цифры

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

le = LabelEncoder()
y_train_le = le.fit_transform(y_train)
y_test_le = le.transform(y_test)
y_train_oh = to_categorical(y_train_le)
y_test_oh = to_categorical(y_test_le)

Разделение набора валидации

Теперь, когда наши данные готовы, мы разделили набор валидации. Этот набор валидации будет использоваться для оценки производительности модели, когда мы настраиваем параметры модели.

X_train_rest, X_valid, y_train_rest, y_valid = train_test_split(X_train_oh, y_train_oh, test_size=0.1, random_state=37)

Создание модели, которая пересекает

Начнем с модели, которая завышена. Он имеет 2 плотно связанных слоев 64 элементов. input_shape Для первого слоя равен количеству слов, которые мы сохраняли в словаре, и для которых мы создали функции с горячей кодировкой.

Как нам нужно прогнозировать 3 различных классов настроения, последний слой имеет 3 элемента. Softmax Функция активации обеспечивает сумму три вероятности до 1.

Количество параметров на тренировку вычисляется как (NB-входы X NB элементы в скрытом слое) + NB-сгибание. Отказ Количество входов для первого слоя равна количеству слов в нашем корпусе. Последующие слои имеют количество выходов предыдущего слоя в качестве входных данных. Таким образом, количество параметров на уровень:

  • Первый слой: (10000 х 64) +
  • Второй слой: (64 х 64) +
  • Последний слой: (64 х 3) +
base_model = models.Sequential()
base_model.add(layers.Dense(64, activation='relu', input_shape=(NB_WORDS,)))
base_model.add(layers.Dense(64, activation='relu'))
base_model.add(layers.Dense(3, activation='softmax'))
base_model.name = 'Baseline model'

Потому что этот проект – многоклассный прогноз одноклассников, мы используем категория_crossentropy как функция потери и Softmax как функция окончательной активации. Мы подходим модель в данных поездов и проверяйте на наборе проверки. Мы запускаем заранее определенное количество эпох и увидим, когда модель начинает переполнять.

base_history = deep_model(base_model, X_train_rest, y_train_rest, X_valid, y_valid)
base_min = optimal_epoch(base_history)
eval_metric(base_model, base_history, 'loss')

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

УПРАВЛЕНИЕ ОБУЧЕНИЯ Продолжает идти вниз и почти достигает нуля в эпоху 20. Это нормально, поскольку модель обучается, чтобы вписать данные поезда, а также возможнее.

Теперь мы можем попытаться сделать что-то о переоценке. Есть разные варианты для этого.

  • Уменьшить емкость сети удаляя слои или уменьшая количество элементов в скрытых слоях
  • Применить регуляризация , что сводится к добавлению затрат на функцию потери для больших весов
  • Использовать Падение слоев , что случайным образом удалит определенные функции, установив их до нуля

Уменьшение мощности сети

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

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

Мы сокращаем мощность сети, удалив один скрытый слой и снижение количества элементов в остальном слое до 16.

reduced_model = models.Sequential()
reduced_model.add(layers.Dense(16, activation='relu', input_shape=(NB_WORDS,)))
reduced_model.add(layers.Dense(3, activation='softmax'))
reduced_model.name = 'Reduced model'
reduced_history = deep_model(reduced_model, X_train_rest, y_train_rest, X_valid, y_valid)
reduced_min = optimal_epoch(reduced_history)
eval_metric(reduced_model, reduced_history, 'loss')

Мы видим, что требуется больше эпох, прежде чем уменьшенная модель начинает перегружаться. Потеря проверки также увеличивается медленнее, чем наша первая модель.

compare_models_by_metric(base_model, reduced_model, base_history, reduced_history, 'val_loss')

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

Применение регуляризации

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

Есть L1 Регуляризация и регуляризация L2 Отказ

  • Организация L1 добавит стоимость в отношении Абсолютное значение параметров Отказ Это приведет к тому, что некоторые из весов равны нулю.
  • L2 Регуляризация добавит стоимость в отношении Квадратная ценность параметров Отказ Это приводит к меньшим весам.

Давайте попробуем с помощью L2 регуляризации.

reg_model = models.Sequential()
reg_model.add(layers.Dense(64, kernel_regularizer=regularizers.l2(0.001), activation='relu', input_shape=(NB_WORDS,)))
reg_model.add(layers.Dense(64, kernel_regularizer=regularizers.l2(0.001), activation='relu'))
reg_model.add(layers.Dense(3, activation='softmax'))
reg_model.name = 'L2 Regularization model'
reg_history = deep_model(reg_model, X_train_rest, y_train_rest, X_valid, y_valid)
reg_min = optimal_epoch(reg_history)

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

eval_metric(reg_model, reg_history, 'loss')
compare_models_by_metric(base_model, reg_model, base_history, reg_history, 'val_loss')

Добавление слоев выпадения

Последний вариант мы попробуем – добавить слои для выпадения. Опытный слой будет случайным образом установить выходные особенности слоя до нуля.

drop_model = models.Sequential()
drop_model.add(layers.Dense(64, activation='relu', input_shape=(NB_WORDS,)))
drop_model.add(layers.Dropout(0.5))
drop_model.add(layers.Dense(64, activation='relu'))
drop_model.add(layers.Dropout(0.5))
drop_model.add(layers.Dense(3, activation='softmax'))
drop_model.name = 'Dropout layers model'
drop_history = deep_model(drop_model, X_train_rest, y_train_rest, X_valid, y_valid)
drop_min = optimal_epoch(drop_history)
eval_metric(drop_model, drop_history, 'loss')

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

compare_models_by_metric(base_model, drop_model, base_history, drop_history, 'val_loss')

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

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

base_results = test_model(base_model, X_train_oh, y_train_oh, X_test_oh, y_test_oh, base_min)
reduced_results = test_model(reduced_model, X_train_oh, y_train_oh, X_test_oh, y_test_oh, reduced_min)
reg_results = test_model(reg_model, X_train_oh, y_train_oh, X_test_oh, y_test_oh, reg_min)
drop_results = test_model(drop_model, X_train_oh, y_train_oh, X_test_oh, y_test_oh, drop_min)

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

Вы можете найти ноутбук на Github Отказ Веселиться с этим!