Введение

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

Справочная информация и описание проблемы

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

Вспышка коронавируса сделала здоровье и чистоту главными приоритетами в нашей повседневной жизни. По мере того, как рестораны начинают открываться, им предлагается обновить свой подход к чистоте. Министерство здравоохранения опубликовало список новых руководящих принципов, требующих принятия дополнительных мер. Инициатива создает логистическую проблему для департамента здравоохранения, чтобы он разработал план по мониторингу и обеспечению соблюдения этих руководящих принципов. Исследование направлено на анализ проверок с течением времени и получение приемлемого показателя прогноза (~ 90%) при выводе баллов и помощи Министерству здравоохранения Сан-Франциско в определении приоритетов своих ресурсов для проверок после COVID-19.

Данные

Одной из причин, по которой я выбрал эту тему, была доступность и социальный характер данных. Набор данных взят из DataSF и является частью стандарта открытых данных с API для доступа. Доступ к API может быть удобен при создании непрерывного конвейера данных для будущей работы. Набор данных размещен в единой табличной таблице с 54 КБ строк. В настоящее время он настроен на еженедельное обновление. Однако он не обновлялся с ноября 2019 года из-за общегородской остановки из-за COVID.

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

Исследовательский анализ

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

Одна вещь, о которой я никогда не думал, и которую мне повезло, - это почтовые индексы компаний. Город Сан-Франциско начинается только с цифры «9». Обычно ожидается отсутствие почтовых индексов в наборе данных, но я был удивлен, увидев неправильно введенные почтовые индексы. На диаграмме показано распределение после обширного форматирования. Для неправильных почтовых индексов в некоторых записях были доступны широта и долгота. Я сделал всплеск обратного геокодирования и измерения расстояний, которые также доступны в записной книжке для исследовательского анализа.

Другой проблемой было решить, как разделить данные по географическому принципу. Единственным пригодным для использования значением в исходном наборе были названия улиц. Когда я впервые переехал в Сан-Франциско, мне сказали, что пейзаж меняется с каждой улицей. Повернувшись, вы увидите обновленные здания викторианской эпохи 100-летней давности, новенькие кондоминиумы или полностью полуразрушенные здания в пределах досягаемости. Ниже приведен список улиц с наиболее и наименее санитарной безопасностью. Я метко назвал его «Улицы Сан-Франциско: вкусная и грязная дюжина». Вертикальная линия - средний балл за инспекцию. Приятно видеть информацию на уровне улиц, но мне нужно знать больше.

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

Категория риска и оценка инспекции - интересная корреляция. Сначала я хотел бы узнать больше о выбросе, поэтому коробчатая диаграмма казалась лучшим выбором. Я специально использовал оттенки синего, чтобы увидеть различия в прямоугольных диаграммах для каждой категории риска. Категория низкого риска, на первый взгляд, выглядит типично. У него наименьший межквартильный размах с наивысшим средним значением из всех категорий. Это ожидается по определению. Это означает, что система баллов напрямую связана со степенями в категории риска. Кроме того, нижняя часть IQ3 составляет ~ 70, что очень близко к общему среднему значению, а также к среднемесячному значению. Есть лишь небольшая разница. Следует обратить внимание на количество выпадающих нарушений. Это самый высокий показатель из всех категорий. Я предполагаю, что рестораны, получившие низкую оценку во время посещения без предупреждения, могут исправить нарушения и заплатить штраф в размере 199 долларов за повторную проверку в течение 30 дней. Это может быть включено в создание функции, рестораны, получившие низкие оценки во время неожиданного посещения, могут исправить свои оценки в течение 30 дней. Категория умеренного риска имеет самый низкий IQR из всех категорий. У него также есть изрядная доля выбросов. При быстром поиске я обнаружил, что нарушения могут варьироваться от «Несоответствие сточных вод или отвода сточных вод» до «Неадекватно очищенные или продезинфицированные поверхности, контактирующие с пищевыми продуктами» и вплоть до «Неправильные методы оттаивания». Это довольно серьезные правонарушения, которые могут вызвать заболевание. Я думаю, что самое страшное, что медиана умеренного риска также довольно велика. Наконец, категория повышенного риска. У него очень мало выбросов, что не может не радовать. Эта категория наиболее опасна для здоровья человека. Его IQR является самым большим, и меня больше всего беспокоит то, что он имеет максимальное значение 100. Это означает, что даже если вы обедаете в ресторане со 100 баллами, есть вероятность, что они ранее имели нарушение с высоким риском и были исправлены. Это. Это, на мой взгляд, делает гораздо более важным отчет о трассе с оценкой проверки от 90 и выше. я учту это, когда я переклассифицирую свой предсказатель.

Геокодирование

Учебник, который я нашел на среднем уровне, рекомендовал OpenCage GeoCoder. Пакет интуитивно понятен и прост в использовании, однако имеет ограничения API. Я нашел альтернативный пакет под названием GeoPy, который работал намного быстрее без ограничений.

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

Кроме того, вместо отправки всего набора данных для генерации местоположения, чтобы ускорить процесс, я создал фрейм данных с бизнес-идентификаторами без местоположения, что снизило мое число до 3500. С исходных данных с 55 тысяч до простых трех с половиной тысяч, не так уж плохо. Что ж, на пробежку все равно ушло больше часа. Еще один совет - убедитесь, что вы не выходите из своего экземпляра в течение всего запуска, чтобы вы могли поймать случайные обновления входа. Ниже приведен код API. Геолокатор возвращает информацию о местоположении, из которой я анализирую поле точки и извлекаю из нее широту и долготу. В поле возвращаемой точки также указана высота. Просто из любопытства вернул и это, но все оказались нулями.

Раньше я разбирал названия улиц с адресов и, хотя результаты были интересными, детализация не соответствовала той, которую я искал. На этапе геокодирования я добавил новое поле для окрестностей. Список был составлен путем сопоставления поля business_postal_code с использованием словаря, построенного на веб-сайте Департамента здравоохранения Сан-Франциско.

Предварительная обработка и сопоставление

На этом этапе моей целью было начать создавать функции, которые помогут мне делать выводы о прогнозах. Поскольку набор данных был на уровне проверки, имело смысл узнать больше о типах проверок и их частоте. Это позволило мне стратегически агрегировать данные. Как только я получил агрегированные данные, я не смог устоять перед одной вещью, так это построить график лучших и худших ресторанов Сан-Франциско по среднему баллу. Ниже представлена ​​карта для лучшего списка. Если вы предпочитаете интерактивную версию или карту на худой конец, зайдите в мой проект GitHub и посмотрите записную книжку 3_Preprocessing_Mapping. Стоит отметить, что лучшее - действительно лучшее. Эти рестораны никогда не подвергались каким-либо нарушениям. Всегда!

Просмотр данных на уровне района помог мне увидеть, что было доступно для создания функций. Используя единственный числовой столбец (мой классификатор) и агрегируя информацию о инспекционных посещениях на бизнес-уровне, я смог получить множество предикторов, таких как минуты, итоги, количество типов нарушений и даже общее количество нарушений. Этот новый набор данных, состоящий теперь из 5K строк и 9 столбцов, был экспортирован в df_features_business.csv. Затем я перешел в R Studio с набором данных, но поле «abuse_description» вызвало у меня интерес. Поскольку одна из моих целей практического проекта - показать как можно больше техник, я решил глубже изучить содержание поля.

Обработка естественного языка для «abuse_description»

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

!python3 -m spacy download en_core_web_sm 
import en_core_web_sm
nlp = en_core_web_sm.load()

Поле «Описание_ нарушения» представляет собой столбец с произвольным текстом. Это означает, что инспекторы могут добавлять информацию о конкретном нарушении любым способом. Удивительно, но распределение содержимого колонки было довольно равномерным. Из ~ 35К нарушений было 66 уникальных записей. Ниже моя функция чистого текста:

После прохождения функции столбец «exception_description» очищается, лемматизируется и готов к кластеризации. Я собираюсь использовать кластеризацию k-средних, однако я неоднократно читал, что в этой ситуации лучше использовать k-medoids. Причина в том, что обычно L1 работает лучше, чем расстояние L2 для наборов данных с высоким разрешением, таких как векторы TFIDF. К сожалению, я не могу загрузить библиотеки k-medoids из-за нескольких других зависимостей. Возможно, это один из недостатков использования готового образа data science.

Подобрать оптимальное количество кластеров было непросто. Не только прогоны заняли слишком много времени, и поэтому мне пришлось корректировать количество моих функций. Добавление мощности процессора (не пробовал GPU) не помогло. Обычно при использовании k-средних для текстовых данных наклон не так сильно сглаживается. Я собираюсь выбрать 20 просто потому, что это круглое число, и обучу свою модель с 20 кластерами. Нет необходимости пробовать большое число и выполнять несколько прогонов. Кроме того, я много раз читал, что практическое правило гласит: «k равно квадратному корню из числа точек в (обучающем) наборе данных. С этим набором данных их будет около 70. Если вы в конечном итоге попробуете, дайте мне знать ваши результаты в комментариях, пожалуйста.

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

cluster 3
number of datapoints in cluster: 5747
top 10 words and counts:
[('unclean', 5747), ('degraded', 3230), ('floor', 3230), ('wall', 3230), ('ceiling', 3230), ('contact', 2517), ('surface', 2517), ('nonfood', 1365), ('unsanitary', 1152), ('food', 1152)]

a few random violations from the cluster:
Unclean or degraded floors walls or ceilings
Unclean or degraded floors walls or ceilings


cluster 4
number of datapoints in cluster: 7434
top 10 words and counts:
[('risk', 7434), ('moderate', 4084), ('food', 3849), ('hold', 3849), ('temperature', 3849), ('vermin', 3508), ('infestation', 3508), ('high', 2284), ('low', 1066), ('violation', 77)]

a few random violations from the cluster:
Moderate risk food holding temperature
Moderate risk vermin infestation

Облако слов

Облако слов подтвердило результаты кластера, но в совокупности. Ниже в облаке слов представлена ​​частота встречаемости слов всего корпуса «ault_description ». Излишне говорить, что видеть рядом слова паразиты и еда - это немного жутковато.

Биграммы и триграммы

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

"moderate risk" shows up 4029 times
"risk food" shows up 3849 times
"hold temperature" shows up 3849 times
"food hold" shows up 3849 times
"risk food hold" shows up 3849 times
"food hold temperature" shows up 3849 times
"degraded floor wall" shows up 3230 times
"unclean degraded floor" shows up 3230 times

Временной ряд

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

KNN и RandomForest

Что касается части проекта, связанной с машинным обучением, я решил перейти на R Studio Cloud. Еще один совет касается предупреждающих сообщений. В R Studio Cloud можно получить предупреждающее сообщение для многих вещей, и если вы, как я, потратили бессмысленное время после предупреждения, а не на саму работу, вы должны использовать следующее, чтобы отключить предупреждения: suppressWarnings (suppressMessages () ). Моим источником данных был ранее созданный набор данных функции, df_features_business. В нем у меня есть производные функции, такие как количество рисков в каждой категории, общее количество нарушений, а также минимальные баллы на бизнес-уровне. Мне нравится экспортировать в .csv после каждого раздела. Он создает несколько тщательно отобранных наборов данных, чтобы анализ можно было начать с нужной точки. Кроме того, тщательно отобранные / предварительно агрегированные данные обрабатываются быстрее, что является хорошим способом повышения эффективности, особенно при использовании нескольких алгоритмов.

Есть четыре причины, по которым я решил начать с алгоритма k-NN для решения этой проблемы. Первая причина - это характер набора данных, с которым я работаю. k классификаторов ближайших соседей определяются их характеристикой классификации немаркированных примеров путем присвоения им класса аналогичных помеченных примеров. Задача состоит в том, чтобы предсказать рейтинг ресторанов на основе прошлых данных, а мой набор данных уже помечен. Вторая причина - корреляция между функциями в наборе данных. Классификаторы k ближайших соседей хорошо подходят для задач классификации, в которых отношения между функциями и целевыми классами многочисленны, сложны или чрезвычайно трудны для понимания иным образом, но элементы аналогичного типа класса имеют тенденцию быть довольно однородными. Это похоже на набор данных проверки ресторана. В-третьих, у меня есть четыре различных значения для классов. Это мультиклассовая задача, а не двоичная, и k-NN подходит для мультиклассовых задач. Наконец, k-NN считается одним из простейших алгоритмов машинного обучения. Какой лучший способ начать с простых вещей, а затем развивать их. Ниже представлена ​​корреляционная матрица:

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

Я хочу найти лучший и более простой метод прогнозирования результатов проверки ресторана. Из набора данных я смотрю на Inspection_scores_mean, и он колеблется от 100 до 50. Существует более 1000 различных категорий. Для принятия решения об оценке может не потребоваться слишком много категорий, поэтому я сгруппировал эту переменную по группам в соответствии со старой системой оценки санитарного надзора, которая представляет собой буквенную оценку A, B, C и D. A представляет собой оценку 100–95. , B представляет собой баллы 94–90, C представляет баллы 89–85, D представляет баллы ниже 85. Я знаю, что диапазоны немного крутые, но я должен был знать о его распределении. Когда я делю это на наборы данных для обучения и тестирования, пропорции переменной оценки не будут согласованными. Если бы это было так, для модели было бы менее эффективно обучаться по категории D, чем по категории B. По этой причине я манипулировал распределением, изменяя разрывы. Мне потребовалось несколько попыток, но я смог разделить их пропорционально. Я считаю, что это безобидный способ перераспределить значения для достижения большей точности.

К сожалению, на knn это не сработало и с треском провалилось. Я могу утверждать, что, поскольку моей основной целью было вычислить оценки D и C (худшие оценки), то кнн работает нормально. Возможно, мне нужно классифицировать их только на 2 уровня, а не на 4. Успешно / Неудачно. Однако я добился лучших результатов, используя следующие деревья решений.

Деревья решений, обрезка и случайный лес

Выбор алгоритма случайного леса был вызван двумя основными причинами. Во-первых, набор данных Inspection_scores показывает однородные линейные данные. И, во-вторых, оценки являются деликатным вопросом для владельцев ресторанов, поэтому мой метод должен быть прозрачным по юридическим причинам, а мои результаты должны быть легко расшифрованы для передачи другим лицам. Результаты для дерева решений и обрезанного дерева были идентичны. А случайный лес был немного выше. Также нормализация не повлияла на результаты. Я выяснил, почему обрезка не может улучшить результаты, и этому может быть много причин. По умолчанию модель использует 0,01 для значения CP, которое является минимальным деревом ошибок с перекрестной проверкой. Для проверки я вручную изменил значение CP, однако точность не улучшилась. Ниже приведен код, позволяющий увидеть все три модели рядом. Это хороший трюк, который я использую, когда мне нужно сравнить несколько классов нескольких методов:

Чувствительность и специфичность

Чувствительность модели (также называемая показателем истинных положительных результатов) измеряет долю положительных примеров, которые были правильно классифицированы. В случайном лесу для класса A чувствительность выше, чем для классов B, C и D.
Специфичность модели (также называемая истинным отрицательным коэффициентом) измеряет долю отрицательных примеров, которые были правильно классифицированы. Значения очень хороши для класса D и класса A. Это одна из причин, я думаю, что оба классификатора могут работать лучше с логическим предиктором, однако это не моя цель здесь, моя цель - сначала успешно разделить D, а затем C.

На этом этапе я подумал, что, возможно, когда я разделю наборы данных для обучения и тестирования, у меня не будет каких-либо связанных значений в тестах, чтобы правильно проверить точность. Думая об этом, я также провел несколько прогонов, изменив распределение набора данных обучения и тестирования. Я пробовал разделить 60/40, 75/25 и 90/10. При разбиении 90/10 точность немного увеличилась, однако деревья узлов и решений по-прежнему давали схожую производительность.

Визуализация деревьев решений

Один из способов оценить производительность модели - обучить ее на нескольких разных меньших наборах данных и оценить их по другому меньшему набору тестирования, F-кратной перекрестной проверке. В R есть функция случайного разделения набора данных почти одинакового размера. Например, если k = 9, модель оценивается по девяти папкам и тестируется на оставшейся тестовой выборке. Этот процесс повторяется до тех пор, пока не будут оценены все подмножества. Этот метод широко используется для выбора модели, особенно когда у модели есть параметры для настройки. Теперь, когда у нас есть способ оценить нашу модель, нам нужно выяснить, как выбрать параметры, которые лучше всего обобщают данные. Случайный лес выбирает случайное подмножество функций и строит множество деревьев решений. Модель усредняет все прогнозы деревьев решений.

Я также пробовал использовать целые данные в качестве обучающих данных. Я уверен, что если используется больший объем данных и классификатор установлен в двоичный режим, точность будет намного выше. Также следует отметить ошибку класса. Ранее я упоминал, что меня интересуют в основном оценки D (4) и C (3). Хотя частота приемлема для D, частота ошибок для C немного значительна. Вот моя матрица путаницы для случайного прогона леса. Точность ~ 90% - это неплохо.

Оценка эффективности модели (k-кратная перекрестная проверка)

Вся идея перекрестной проверки состоит в том, чтобы оптимизировать сложность модели при минимизации недостаточного / переобучения. Цель состоит в том, чтобы модель хорошо обобщалась без переобучения. Чтобы гарантировать это, модель проходит (перекрестную) валидацию / квалификацию. Ниже приведен код для автоматизации 10-кратного CV для randomForest с использованием lapply (): Я использую 10-кратное CV, поскольку это наиболее распространенное соглашение. В любом случае пользы от использования большего числа мало. Для каждого из 10 вариантов модель машинного обучения построена на оставшихся 90% данных. Затем для оценки модели используется 10% выборка складки. После того, как процесс обучения и оценки модели произошел в 10 раз, сообщается средняя производительность по всем складкам.

set.seed(99998)
folds <- createFolds(inspection_scoresV2$score, k = 10) #command to create 10 folds. Datasets for CV are created. createFolds is part of caret function.
cv_results <- lapply(folds, function(x) {
 inspection_scoresV2_train <- inspection_scoresV2[-x, ]
 inspection_scoresV2_test <- inspection_scoresV2[x, ]
 inspection_scoresV2_model <- randomForest(score ~ ., data = inspection_scoresV2_train)
 inspection_scoresV2_pred <- predict(inspection_scoresV2_model, inspection_scoresV2_test)
 inspection_scoresV2_actual <- inspection_scoresV2_test$score
 kappa <- kappa2(data.frame(inspection_scoresV2_actual, inspection_scoresV2_pred))$value
 return(kappa)
})

Это заняло несколько минут, что и следовало ожидать. Вся идея перекрестной проверки заключалась в оптимизации сложности модели при минимизации недостаточного / избыточного соответствия. Цель состоит в том, чтобы модель хорошо обобщалась без переобучения. k-fold не работала так, как предполагалось для randomForest.

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

Задержки инспекторов - день первый

Мне удалось достичь своей цели ~ 90% точности в прогнозировании оценок ресторанов после проверки Covid. Я думаю, что существует достаточно ресторанов, которые исторически систематически нарушали кодекс Министерства здравоохранения Сан-Франциско. Инспекторы могут начать свои проверки с нижеследующего списка из 50 ресторанов с прогнозируемой оценкой «D».

Для отображения я использовал folium. Я думаю, что он создает красивые интерактивные карты с минимальными усилиями.

Последние мысли

Прежде всего, работайте со всем маленьким. Небольшие наборы данных, образцы файлов. Он не только работает быстрее, но и легче воспринимает результаты. С учетом сказанного, во время этого проекта я напрасно потерял время, не анализируя весь набор данных. При выполнении ресурсоемких вычислений приятно видеть индикатор выполнения. Tdqm - это пакет, который я использую, и вот отличный пост в блоге, который познакомил меня с ним. НЛП - это отдельная ветвь машинного обучения. Я чувствую, что мог бы создать дополнительные функции на основе частоты слов, сходства и тональности. Набор данных требует больше функций и еще больше данных. По этой причине, возможно, обучение на более крупном наборе, таком как Нью-Йорк или Портленд, могло бы быть лучше, однако это потребует значительных усилий с использованием регулярных выражений, поскольку форматы сбора данных сильно отличаются друг от друга.