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

Мотивация

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

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

Но как вы тестируете свою модель машинного обучения? Одним из простых подходов является использование модульного теста:

from textblob import TextBlob


def test_sentiment_the_same_after_paraphrasing():
    sent = "The hotel room was great! It was spacious, clean and had a nice view of the city."
    sent_paraphrased = "The hotel room wasn't bad. It wasn't cramped, dirty, and had a decent view of the city."
    
    sentiment_original = TextBlob(sent).sentiment.polarity
    sentiment_paraphrased = TextBlob(sent_paraphrased).sentiment.polarity

    both_positive = (sentiment_original > 0) and (sentiment_paraphrased > 0)
    both_negative = (sentiment_original < 0) and (sentiment_paraphrased < 0)
    assert both_positive or both_negative

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

Вот когда поведение пригодится.

Не стесняйтесь играть и разветвлять исходный код этой статьи здесь:



Что такое вести себя?

behave — это фреймворк Python для разработки, основанной на поведении (BDD). BDD — это методология разработки программного обеспечения, которая:

  • Уделяет особое внимание сотрудничеству между заинтересованными сторонами (например, бизнес-аналитиками, разработчиками и тестировщиками)
  • Позволяет пользователям определять требования и спецификации для программного приложения.

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

Чтобы установить поведение, введите:

pip install behave

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

Проверка инвариантности

Тестирование инвариантности проверяет, дает ли модель ML согласованные результаты в различных условиях.

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

Файл характеристик

Чтобы использовать поведение для проверки инвариантности, создайте каталог с именем features. В этом каталоге создайте файл с именем invariant_test_sentiment.feature.

└──  features/ 
  └───  invariant_test_sentiment.feature 

В файле invariant_test_sentiment.feature мы укажем требования к проекту:

Части этого файла «Дано», «Когда» и «Тогда» представляют фактические шаги, которые будут выполняться поведением во время теста.

Реализация шага Python

Чтобы реализовать шаги, используемые в сценариях с Python, начните с создания каталога features/steps и файла с именем invariant_test_sentiment.py внутри него:

└──  features/ 
    ├────  invariant_test_sentiment.feature  
    └────  steps/ 
       └────  invariant_test_sentiment.py 

Файл invariant_test_sentiment.py содержит следующий код, который проверяет, согласуется ли тональность, создаваемая моделью TextBlob, между исходным текстом и его перефразированной версией.

from behave import given, then, when
from textblob import TextBlob


@given("a text")
def step_given_positive_sentiment(context):
    context.sent = "The hotel room was great! It was spacious, clean and had a nice view of the city."


@when("the text is paraphrased")
def step_when_paraphrased(context):
    context.sent_paraphrased = "The hotel room wasn't bad. It wasn't cramped, dirty, and had a decent view of the city."


@then("both text should have the same sentiment")
def step_then_sentiment_analysis(context):
    # Get sentiment of each sentence
    sentiment_original = TextBlob(context.sent).sentiment.polarity
    sentiment_paraphrased = TextBlob(context.sent_paraphrased).sentiment.polarity

    # Print sentiment
    print(f"Sentiment of the original text: {sentiment_original:.2f}")
    print(f"Sentiment of the paraphrased sentence: {sentiment_paraphrased:.2f}")

    # Assert that both sentences have the same sentiment
    both_positive = (sentiment_original > 0) and (sentiment_paraphrased > 0)
    both_negative = (sentiment_original < 0) and (sentiment_paraphrased < 0)
    assert both_positive or both_negative

Объяснение кода выше:

  • Шаги идентифицируются с помощью декораторов, соответствующих предикату функции: given, when и then.
  • Декоратор принимает строку, содержащую остальную часть фразы на соответствующем шаге сценария.
  • Переменная context позволяет обмениваться значениями между шагами.

Запустить тест

Чтобы запустить тест invariant_test_sentiment.feature, введите следующую команду:

behave features/invariant_test_sentiment.feature

Выход:

Feature: Sentiment Analysis # features/invariant_test_sentiment.feature:1
  As a data scientist
  I want to ensure that my model is invariant to paraphrasing
  So that my model can produce consistent results in real-world scenarios.
  Scenario: Paraphrased text                                                       
    Given a text                                                                   
    When the text is paraphrased                                                   
    Then both text should have the same sentiment
      Traceback (most recent call last):
          assert both_positive or both_negative
      AssertionError

      Captured stdout:
      Sentiment of the original text: 0.66
      Sentiment of the paraphrased sentence: -0.38

Failing scenarios:
  features/invariant_test_sentiment.feature:6  Paraphrased text

0 features passed, 1 failed, 0 skipped
0 scenarios passed, 1 failed, 0 skipped
2 steps passed, 1 failed, 0 skipped, 0 undefined

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

Направленное тестирование

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

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

Чтобы использовать поведение для направленного тестирования, мы создадим два файла directional_test_sentiment.feature и directional_test_sentiment.py.

└──  features/ 
    ├────  directional_test_sentiment.feature  
    └────  steps/ 
       └────  directional_test_sentiment.py

Файл функций

Код в directional_test_sentiment.feature определяет требования проекта следующим образом:

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

Реализация шага Python

Код indirectional_test_sentiment.py реализует тестовый сценарий, который проверяет, положительно ли влияет присутствие слова «круто» на оценку тональности, сгенерированную моделью TextBlob.

from behave import given, then, when
from textblob import TextBlob


@given("a sentence")
def step_given_positive_word(context):
    context.sent = "I love this product" 


@given("the same sentence with the addition of the word '{word}'")
def step_given_a_positive_word(context, word):
    context.new_sent = f"I love this {word} product"


@when("I input the new sentence into the model")
def step_when_use_model(context):
    context.sentiment_score = TextBlob(context.sent).sentiment.polarity
    context.adjusted_score = TextBlob(context.new_sent).sentiment.polarity


@then("the sentiment score should increase")
def step_then_positive(context):
    assert context.adjusted_score > context.sentiment_score

Второй шаг использует синтаксис параметра {word}. При запуске файла .feature значение, указанное для {word} в сценарии, автоматически передается соответствующей пошаговой функции.

Это означает, что если в сценарии указано, что одно и то же предложение должно включать слово «потрясающий», поведение автоматически заменит {word} на «потрясающий».

Это преобразование полезно, когда вы хотите использовать разные значения для параметра {word}, не изменяя ни файл .feature, ни файл .py.

Запустить тест

behave features/directional_test_sentiment.feature

Выход:

Feature: Sentiment Analysis with Specific Word 
  As a data scientist
  I want to ensure that the presence of a specific word has a positive or negative effect on the sentiment score of a text
  Scenario: Sentiment analysis with specific word                 
    Given a sentence                                              
    And the same sentence with the addition of the word 'awesome' 
    When I input the new sentence into the model                  
    Then the sentiment score should increase                      

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
4 steps passed, 0 failed, 0 skipped, 0 undefined

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

Минимальное функциональное тестирование

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

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

Чтобы использовать тестирование минимальной функциональности для проверки ввода, создайте два файла minimum_func_test_input.feature и minimum_func_test_input.py .

└──  features/ 
    ├────  minimum_func_test_input.feature  
    └────  steps/ 
       └────  minimum_func_test_input.py

Файл функций

Код в minimum_func_test_input.feature определяет требования к проекту следующим образом:

Реализация шага Python

Код в minimum_func_test_input.py реализует требования, проверяя, соответствует ли вывод, созданный predict для определенного типа ввода, ожиданиям.

from behave import given, then, when

import numpy as np
from sklearn.linear_model import LinearRegression
from typing import Union


def predict(input_data: Union[int, float, str, list]):
    """Create a model to predict input data"""

    # Reshape the input data
    if isinstance(input_data, (int, float, list)):
        input_array = np.array(input_data).reshape(-1, 1)
    else:
        raise ValueError("Input type not supported")

    # Create a linear regression model
    model = LinearRegression()

    # Train the model on a sample dataset
    X = np.array([[1], [2], [3], [4], [5]])
    y = np.array([2, 4, 6, 8, 10])
    model.fit(X, y)

    # Predict the output using the input array
    return model.predict(input_array)


@given("I have an integer input of {input_value}")
def step_given_integer_input(context, input_value):
    context.input_value = int(input_value)


@given("I have a float input of {input_value}")
def step_given_float_input(context, input_value):
    context.input_value = float(input_value)


@given("I have a list input of {input_value}")
def step_given_list_input(context, input_value):
    context.input_value = eval(input_value)


@when("I run the model")
def step_when_run_model(context):
    context.output = predict(context.input_value)


@then("the output should be an array of one number")
def step_then_check_output(context):
    assert isinstance(context.output, np.ndarray)
    assert all(isinstance(x, (int, float)) for x in context.output)
    assert len(context.output) == 1


@then("the output should be an array of three numbers")
def step_then_check_output(context):
    assert isinstance(context.output, np.ndarray)
    assert all(isinstance(x, (int, float)) for x in context.output)
    assert len(context.output) == 3

Запустить тест

behave features/minimum_func_test_input.feature

Выход:

Feature: Test my_ml_model 

  Scenario: Test integer input                       
    Given I have an integer input of 42              
    When I run the model                             
    Then the output should be an array of one number 

  Scenario: Test float input                         
    Given I have a float input of 3.14               
    When I run the model                            
    Then the output should be an array of one number 

  Scenario: Test list input                             
    Given I have a list input of [1, 2, 3]              
    When I run the model                                
    Then the output should be an array of three numbers

1 feature passed, 0 failed, 0 skipped
3 scenarios passed, 0 failed, 0 skipped
9 steps passed, 0 failed, 0 skipped, 0 undefined

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

Недостатки поведения и почему вы все равно должны его использовать

В этом разделе будут описаны некоторые недостатки использования поведения по сравнению с pytest, а также объяснено, почему этот инструмент все же стоит рассмотреть.

Кривая обучения

Использование Behavior-Driven Development (BDD) в поведении может привести к более крутой кривой обучения, чем более традиционный подход к тестированию, используемый pytest.

Контраргумент: акцент на совместной работе в BDD может привести к лучшему согласованию между бизнес-требованиями и разработкой программного обеспечения, что приведет к более эффективному процессу разработки в целом.

Более низкая производительность

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

Контраргумент: фокус поведения на четко определенных шагах может привести к тому, что тесты будет легче понять и модифицировать, что сократит общие усилия, необходимые для обслуживания тестов.

Меньше гибкости

поведение более жестко по своему синтаксису, в то время как pytest обеспечивает большую гибкость в определении тестов и фикстур.

Контраргумент:жесткая структура поведения может помочь обеспечить согласованность и удобочитаемость тестов, упрощая их понимание и поддержку с течением времени.

Краткое содержание

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

Заключение

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

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

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