Алгоритмы тестирования машинного обучения различаются для всех алгоритмов. Для некоторых алгоритмов окончательная модель может быть указана только с несколькими параметрами (линейная регрессия, веса и смещение). Для нейронных сетей нам нужно хранить гораздо более длинный набор изученных параметров, а также мы применяем нелинейные преобразования через сеть. Поэтому, даже если мы знаем все промежуточные параметры, их будет недостаточно для расчетов.
Существует множество алгоритмов тестирования машинного обучения для задач типа деревьев решений или линейной регрессии. Я хочу показать несколько хороших примеров тестирования нейронной сети.
Код находится на github: Ссылка
Я писал код в Colab и пробовал в Colab.

NNetworks также сильно нелинейны. Входные данные объединяются в плотный слой, генерируя новые признаки, являющиеся комбинацией всех входных параметров. Таким образом, нейронная сеть может вести себя очень по-разному в зависимости от данных и начальной инициализации (случайности).
В этом посте мы разработаем несколько тестов, чтобы убедиться, что поведение NNetwork по-прежнему похоже на время, когда мы ее выбираем. Будьте осторожны, мы не настраиваем параметры. Мы уже определились с архитектурой. Мы просто хотим быть уверены, что выбранная модель работает как раньше, когда мы выбираем ее в прошлом.

NСети формируют слишком много связей, что затрудняет измерение эффекта отдельных переменных. Есть такие библиотеки, как SHAP, но они работают в рамках теории игр. Их расчет является приблизительным, а не прямым параметром, таким как линейная регрессия.

  1. Проверка соответствия

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

Если я удалю почти все точки и оставлю лишь несколько, то для вашей модели эта задача будет очень простой.

Итак, 1 тестовый пример для переоснащения машинного обучения для небольшого набора образцов. Ниже вы можете видеть, что я беру первые 10 элементов из набора. Обучение модели с ними. И их предсказание. Я предполагаю, что это работает отлично. (Поскольку моя модель обычно не так хороша, я также ожидаю, что это переобучение не будет идеальным. Поэтому моя проверка ошибок не предполагает 100-процентной точности.)

#If our model will work for whole dataset
#it must work perfect with a little dataset.Success for a very small set must be so high
def test_nn_overfit(dummy_dataset):
    X_train, X_test, y_train, y_test = dummy_dataset
    #Best way is making totally random, 
    #sampled_list = random.sample(range(0,len(X_train)), 10)    
    #X_train,y_train =  X_train[sampled_list],y_train[sampled_list]

    #Get first 10 rows
    X_train,y_train =  X_train[0:10],y_train[0:10]
    
    overfit_model = get_model(X_train.shape[1])
    overfit_model.fit(X_train,y_train, epochs=20)
    pred = np.round(overfit_model.predict(X_train))

    labels = y_train.flatten().astype(int).tolist()
    pred = pred.flatten().astype(int).tolist()

    actual_sum = np.sum(labels)
    pred_sum =  np.sum(pred)
    
    error = sum( np.logical_xor(labels ,pred   )  )
    is_below = error <= ( len(labels) / 5 )

    assert  is_below, "Model should fit data perfectly "

Здесь мы получаем нашу модель, мы подвыбираем очень маленький набор (это зависит от проблемы, реального размера набора данных, здесь просто пример). И я выполняю тест, чтобы увидеть, будут ли данные изучены намного лучше, чем реальная точность обучения. Здесь я знаю, что моя точность составляет 80%. (Если мы используем getdummies для категориальных переменных, та же модель получает точность 99, но я хочу, чтобы в посте отображалась более простая модель)

Здравый смысл

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

#We must put our domain knowledge,and test very simple relations.
#Here we know for this dataset "Class" field is important, so we try manually
#to see the effect we expect
def test_common_sense(dummy_dataset,dummy_nn_model):
  X_train, X_test, y_train, y_test = dummy_dataset
  p1 = dummy_nn_model.predict(X_train[1].reshape(1,-1) ).flatten()[0] 
  #copy  change class to 2nd
  X_train_copy = X_train[1].copy()
  X_train_copy[1] = 2.0
  p2 = dummy_nn_model.predict(X_train_copy.reshape(1,-1) ).flatten()[0] 
  #copy  change class to 3rd
  X_train_copy[1] = 3.0
  p3 = dummy_nn_model.predict(X_train_copy.reshape(1,-1) ).flatten()[0] 
  print( f"1st class {p1} 2nd class {p2} 3rd {p3} ")
  assert p1 > p2 , "1st class probability must be higher than 2nd class"
  assert p2 > p3 , "2nd class probability must be higher than 3rd class"

Вышеприведенный тест:
берет образец из 1-го класса, прогнозирует = p1
Сохраняет все тот же класс изменения на 2-й класс = p2
Сохраняет все тот же класс изменения на 3-й класс = p3

очень просто мы предполагаем p1 › p2 › p3

Тестирование глубины и количества нейронов
При настройке параметров мы пробовали множество моделей с разными размерами и глубиной нейронов. Теперь у нас есть модель. МЫ НЕ БУДЕМ НАСТРОЙКИ СНОВА. Но для простого теста мы хотим проверить, лучше ли наша топология, чем некоторые известные нам образцы.
Ниже мы генерируем глубина и количество нейронов динамически. Мы можем отправлять разные массивы для уровней, чтобы он генерировал другую топологию.

def get_model(inputdim,levels=[80,50,10]):
  model = Sequential()

  # layers
  model.add(Dense(levels[0],  activation = 'relu', input_dim = inputdim) )
  for level in levels[1:]:    
    model.add(Dense(level,  activation = 'relu'))
  
  model.add(Dense(1,  activation = 'sigmoid'))
  # summary
  model.summary()
  # Compile
  model.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy'])
  return model  

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

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 80)                400       
                                                                 
 dense_1 (Dense)             (None, 50)                4050      
                                                                 
 dense_2 (Dense)             (None, 10)                510       
                                                                 
 dense_3 (Dense)             (None, 1)                 11

СОХРАНЯЙТЕ ОДНУ ГЛУБИНУ С РАЗНЫМИ НЕЙРОНАМИ

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

#After finetuning we decided on a model architecture,
#we still want to check beginning from a very simple architecture
#to see if the number of neurons, we choose before makes sense aganist so dummy architectures
def test_width_acc(dummy_dataset):    
    X_train, X_test, y_train, y_test = dummy_dataset
    levels_list = [  [4,3,2] , [20,10,5], [80,50,10] ] 

    acc_list = []    
    for levels in levels_list:
        model_ = get_model(X_train.shape[1] ,levels )
        model_.fit(X_train, y_train,batch_size =64, epochs = 40,verbose=0)
        pred_binary = np.round( model_.predict(X_train) )
        acc_list.append(accuracy_score(y_train, pred_binary))        
    
    assert sorted(acc_list) == acc_list, 'Accuracy should increase as comlexity increases.'

ИЗМЕНЯЙТЕ ГЛУБИНУ С РАЗЛИЧНЫМИ НЕЙРОНАМИ

При настройке мы определили наилучшую глубину для нашей задачи. Таким образом, во время тестирования мы все еще можем сделать 1 проход, чтобы увидеть, была ли эта глубина оптимальной. Ниже мы видим, что пробуем глубины 3,4,5…

#After finetuning we decided on a model architecture,
#we still want to check beginning from a very simple architecture
#to see if the depth we choose before makes sense aganist so dummy architectures
def test_depth_acc(dummy_dataset):
    
    X_train, X_test, y_train, y_test = dummy_dataset
    levels_list = [  [4,3,] , [20,10,5,], [80,50,10,5] ] 

    acc_list = []
    for levels in levels_list:
        model_ = get_model(X_train.shape[1] ,levels )
        model_.fit(X_train, y_train,batch_size =64, epochs = 40,verbose=0)
        pred_binary = np.round( model_.predict(X_train) )
        acc_list.append(accuracy_score(y_train, pred_binary))        
                
    assert sorted(acc_list) == acc_list, 'Accuracy should increase as comlexity increases.'

Оценочный тест

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

def test_dt_evaluation(dummy_dataset,dummy_nn_model):
    X_train, X_test, y_train, y_test = dummy_dataset
    
    pred_test = dummy_nn_model.predict(X_test)
    pred_test_binary = np.round(pred_test)
    acc_test = accuracy_score(y_test, pred_test_binary)
    auc_test = roc_auc_score(y_test, pred_test)
    assert acc_test > 0.78, 'Accuracy on test should be > 0.78'

В конце вы увидите результат, как показано ниже.

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