
Машинное обучение и тестирование
Мы пишем код машинного обучения в очень специфическом контексте. Но из того, что я видел до сих пор, ничто не убедило меня в том, что код машинного обучения принципиально отличается от любого другого типа кода.
Это означает, что применяются стандартные методы разработки, при этом тестирование является их очень важным компонентом.
Вознаграждение за тестирование может быть огромным, но это может быть и цена, которую придется заплатить за плохое тестирование или его отсутствие.
Давайте подробнее рассмотрим, как выглядит тестирование в контексте различных приложений машинного обучения.
Сценарий 1 - написание единого целевого кода
Это суть работы машинного обучения. Вы работаете над проблемой, которая в широком смысле определяется типом имеющихся у вас данных и целью. Ваша функция затрат может измениться, ваши данные могут измениться, но вы не ожидаете, что проект НЛП внезапно превратится в распознавание изображений и наоборот.
Конкурс Kaggle - хорошее приближение к этому. Я работал над довольно некоторыми из них с некоторым успехом и недавно начал делиться своим кодом.
Но написал ли я хотя бы один тест?
Нет, и я не планирую ничего писать.
Код ML может быть довольно сложным для написания. Устранение неполадок может оказаться непростым даже с использованием выдающихся инструментов, имеющихся в нашем распоряжении.
Учитывая все это, автоматическое тестирование может показаться подходящим вариантом, но решение, к которому пришло сообщество, намного лучше и эффективнее.
Он отражает преимущества автоматического тестирования и достигается за счет особого подхода к проекту. И, выполняя множество мелких действий на этом пути… действия, которые мы все делаем, если хотим иметь шанс предоставить работающее решение. Действия, которые имеют определенные названия в разработке программного обеспечения, но пока их не приняло сообщество.
Так как же все это работает на практике? Как вы работаете над проектом машинного обучения и сохраняете рассудок?
Вы начинаете с малого и разрабатываете полностью рабочее решение. Тот, который начинается с чтения данных и дает результат в конце. Это может быть очень просто, например, прогнозирование наиболее распространенного класса для всех примеров в наборе проверки.
Это устанавливает базовый уровень, к которому вы можете вернуться. Вы добавляете новые функции небольшими приращениями, время от времени перезапуская весь конвейер.
Произошла ошибка или производительность снижается? Не проблема - есть только этот небольшой фрагмент кода, который вы добавили с момента последнего запуска конвейера, поэтому найти проблему будет легко.
Это называется интеграционным тестированием.
Но это еще не все. В зависимости от сложности кода, который вы пишете, довольно часто вам может потребоваться протестировать каждую его строку. Ноутбук Jupyter делает это очень просто.
Скажем, я хотел бы нормализовать свои вводные данные. Не проблема. Я пишу код и запускаю его на своих данных. Esc, b открывает новую ячейку ниже. Быстрый расчет нового среднего и std dev (или быстрый взгляд на полученное изображение), и я могу сказать, работает ли код правильно или нет. Esc, d, d и ячейка исчезла.
Это эквивалентно модульному тестированию.
Если мы протестируем, разве автоматизация не будет лучше? Может показаться, что это так, но как автоматизация действия, так и организация и сохранение кода имеют свою цену. Тот, за который обычно не стоит платить в контексте одноцелевого кода.
Работая над ноутбуком jupyter, я, вероятно, провожу сотни, если не тысячи специальных тестов. После прохождения теста я уверен, что код работает, и мне не нужно тестировать его снова.
Я не планирую реорганизовывать код, перемещать его, добавлять функциональность. Этот код должен работать с данными, предоставленными в рамках ограничений задачи.
Я получаю код для запуска на входах определенной формы в определенный момент времени. Я проверяю, что он работает с помощью окончательного системного теста - потери на правильно построенном валидационном наборе. И я иду дальше.
Ограничения проблемы обычно таковы, что мы не ожидаем частых серьезных изменений. И даже если мы внесем изменения, код все равно будет служить единственной цели - решать только текущую проблему.
Причина, по которой я не ожидаю значительных или частых изменений кода, означает, что ручного тестирования может быть достаточно. А как насчет написания библиотеки? Тот, который можно использовать способами, которые мы едва ли можем предвидеть? Или написать приложение для удовлетворения потребностей живой, развивающейся организации?
Сегодня мы пишем цикл обучения для использования с табличными данными, но через месяц наш босс сообщает нам, что отдел X также должен обучаться на изображениях. Что теперь? О, кстати, вашим коллегам с 1-го этажа нужно обучаться табличным данным, но из реляционной базы данных, а не csvs.
Это полностью меняет ситуацию.
Сценарий 2 - написание кода, который со временем развивается и используется в непредвиденных ситуациях.
Тот факт, что код долгосрочного проекта изменится, является основной причиной автоматического тестирования. Каждый раз, когда вы вносите изменения, вам нужно тестировать.
Добавление новых функций - источник энтропии в вашей кодовой базе. Чтобы с этим бороться, вам нужно провести рефакторинг. Рефакторинг невозможен без тестов. Как следствие, без тестирования ваше приложение развалится. И причина инвестиций в автоматизацию тестов - снижение затрат с течением времени.
Эта ситуация точно такая же, как и для любого другого типа кода, и код ML в этом отношении не уникален.
Но возвращаясь к нашей теме - есть ли другие причины для тестирования повторно используемого кода? Оказывается, те самые функции, которые сделают ваш код легким для тестирования, - это те же самые функции, которые делают его расширяемым и компонуемым! Если вы напишете свой код таким образом, чтобы достичь основной цели, вы получите вторичную выгоду бесплатно.
Если вы хотите узнать больше о технических аспектах тестирования, вот отличная конференция на эту тему.
Сценарий 3 - написание кода, влияющего на жизни
Каким бы ни был код, кто бы его ни написал, код, который потенциально может повлиять на жизнь, должен быть тщательно протестирован. Мы, вероятно, не только должны проверить, что код работает правильно и будет правильно работать в естественных условиях, но по мере того, как алгоритмы начинают играть все более и более значительную роль в функционировании нашего общества, нам также может потребоваться начать тестирование влияния изменений кода на окружающий мир.
Если YouTube продвигает изменение своего алгоритма рекомендаций, которое будет стимулировать вовлеченность (и доход от рекламы), но будет делать это за счет продвижения видео с заговорами, следует ли проверять влияние на общество? да. Нет прощения в том, что вы что-то не рассмотрели. Чем более мощными становятся алгоритмы, формирующие нашу реальность, тем больше ответственности мы, как авторы, должны брать на себя. Вы не можете руководствоваться прибылью и заявлять, что не подозреваете о влиянии, которое вы оказываете. Это определение внешнего эффекта. Независимо от того, разрушает ли корпорация экосистему, сбрасывая отходы в реку, или отравляет умы людей, продвигая вредоносный контент, конечный результат один и тот же.
Во многом этот пост является продолжением одного, написанного мной во время прохождения фастайского курса глубокого обучения. Многие идеи по структурированию кода ML взяты непосредственно из лекций.
Спасибо за внимание! Я редко веду блог, но пишу в Твиттере довольно часто - если вам интересно оставаться на связи, вы можете найти меня в Twitter здесь.