Что и почему
Машинное обучение сложно; модели должны быть ограничены, данные должны быть собраны, помечены (и отлажены) и дорогостоящее оборудование должно быть настроено. Весь процесс необходимо повторять в обычном ритме, как только все будет запущено и запущено. Другими словами, успешное выполнение одной итерации жизненного цикла машинного обучения может повлечь за собой много технических накладных расходов и сложностей.
Часто эта сложность вытесняет насущную потребность в прикладном машинном обучении, чтобы фактически «двигать иглу» и делать что-то полезное для организации, потому что основная истина заключается в том, что большинству клиентов просто нужна модель, которая работает с минимальными затратами и сложностью.
Так, где это оставляет нас? Что ж, я считаю, что, по возможности, первоначальные решения машинного обучения должны быть простыми и должны надлежащим образом увеличивать свою сложность по мере необходимости, если это вообще возможно. Черт, может быть, вам просто нужна база данных вместо BERT? С уверенностью можно сказать, что также крайне неудобно, когда в конце проекта в комнате, полной заинтересованных сторон, вам указывают на что-то подобное, так почему бы не поставить телегу позади лошади и сначала не попробовать что-нибудь подешевле?
Связывая это со сферой НЛП и, в частности, с классификацией документов, один из самых эффективных типов моделей, которые я начал использовать, чтобы проверить, есть ли у модели ноги в первую очередь и/или установить контролируемую базовую линию, — это использование словаря. классификационная модель. Проще говоря, классификаторы на основе словаря предлагают:
- Гибкость. Быстро добавляйте и удаляйте новые метки в пространстве меток в последовательных итерациях без дополнительных затрат на распространение таких изменений в наборе данных с метками. Это позволяет пользователям сосредоточиться на релевантности модели предметной области и связывается с более широкой задачей высвобождения времени/ресурсов, чтобы сосредоточиться на функциональной релевантности модели, которую вы создаете, а не на отладке сложности.
- Скорость и простота. Как мы увидим в разделе реализации, классификатор на основе словаря может быть построен с использованием (почти) чистого python, в то время как основной операцией является проверка принадлежности множества. Никаких слез CUDA.
- Прозрачность и программируемость. В классификатор можно явно запрограммировать экспертную эвристику предметной области и отраслевые знания. Списки терминов часто появляются в работе аналитиков и часто существуют перед конкретными проектами машинного обучения. Там, где они существуют, словарные классификаторы обеспечивают эффективное повторное использование ресурсов.
- Независимость от данных. Вероятно, главная причина использования словарных классификаторов заключается в том, что они не требуют размеченных данных для начала работы и могут нормально функционировать, если проявить немного воображения. Действительно, я обнаружил, что словарные классификаторы являются отличной отправной точкой для создания помеченных наборов данных с помощью предварительной аннотации, которая в конечном итоге превращается в более сложные модели.
Так что это хорошо, но есть и некоторые недостатки, очевидные из которых:
- Обобщаемость. Поскольку классификаторы словарей запрограммированы явно, существует риск того, что входные данные, лежащие за пределами этих явных границ, будут отброшены и/или неправильно классифицированы.
- Отсутствие ортодоксальности. Насколько мне известно, и в широком смысле в библиотеках НЛП с открытым исходным кодом разработка моделей на основе эвристики существует как компонентная концепция в рамках более широких, слабо контролируемых структур, таких как snorkel и skweak, которые часто рекомбинируют. функции маркировки в рамках последующего процесса моделирования.
Существующие библиотеки
Прежде чем создавать новую библиотеку, вероятно, разумно убедиться, что такой вещи еще не существует в мире. Sklearn — очевидная отправная точка. Я нашел явную ссылку на словарное изучение (никакой связи), а также вышеупомянутый фиктивный классификатор (слишком жесткий). Самое близкое, что я смог найти, это текстовые экстракторы признаков sklearn, которые инкапсулируют структуру данных словаря word:count
, которая нам нужна в идеале. Ниже представлено традиционное применение (подгонка/преобразование) CountVectorizer
:
from sklearn.feature_extraction.text import CountVectorizer
# retrieve BOW vocab using usual sklearn fit method texts = ["hello", "world", "this", "is", "me"] cv = CountVectorizer()
cv.fit(texts) cv.vocabulary_ # {'hello': 0, 'world': 4, 'this': 3, 'is': 1, 'me': 2}
Просто и понятно, но одна проблема заключается в том, что мы бы предпочли «внедрить» знания о нашей области в момент создания экземпляра способом, специфичным для класса/метки, а не ПОСЛЕ во время последующего вызова fit (что также требует помеченный набор данных). Возможно что-то вроде этого:
from sklearn.feature_extraction.text import CountVectorizer
# explicitly define vocabulary at point of instantiation texts = ["hello", "world", "this", "is", "me"] cv = CountVectorizer(vocabulary=texts) # err
Таким образом, sklearn приближается к тому, что мы хотим, но не позволяет нам указывать детали наших классов. Круто, с меня достаточно должной осмотрительности, на чистый BOW.
Очистить ЛУК
Давать имена сложно, и этот проект, вероятно, заслуживает лучшего названия, но суть, к которой я пришел, заключалась в том, что вы можете clearly
определить Bag of Words
, существующее в классификаторе, вместо того, чтобы генерировать сумку обычным способом. Итак, как это работает? Основной IO заключается в том, что вы создаете экземпляр DictionaryClassifier
и предоставляете словарь str:list
, содержащий интересующие классы и некоторые связанные термины:
# some class definitions for a superannuatation-themed topic classifier
super_dict = {
"regulation": ["asic", "government", "federal", "tax"],
"contribution": ["contribution", "concession", "personal", "after tax", "10%", "10.5%"],
"covid": ["covid", "lockdown", "downturn", "effect"],
"retirement": ["retire", "house", "annuity", "age"],
"fund": ["unisuper", "aus super", "australian super", "sun super", "qsuper", "rest", "cbus"],
}
dc = DictionaryClassifier(label_dictionary=super_dict)
В то время как выходные данные точно имитируют выходные данные традиционного классификатора. В приведенном ниже примере сумма значений мультиклассового прогноза равна 1,0, что имитирует эффект применения функции softmax к логитам модели:
dc.predict_single("A 10% contribution is not enough for a well balanced super fund!")
# {'regulation': 0.0878,
# 'contribution': 0.6488,
# 'covid': 0.0878,
# 'retirement': 0.0878,
# 'fund': 0.0878}
Классно классно; так что его вывод выглядит достаточно похоже и не делает ничего слишком причудливого, но как он работает? Что ж, взяв приведенный выше пример пенсионного возраста и некоторые примеры документов, можно выделить две основные внутренние функции, которые стоит распаковать в классе DictionaryClassifier
:
def _get_label_word_count(self, text): tally = {} for k, v in self.label_dictionary.items(): tally_temp = sum(e in text.lower() for e in v) tally[k] = tally_temp return tally
def _transform_predict_dict(self, pred_dict): # if all word counts are 0 if all(x == 0 for x in pred_dict.values()): prob_dict = {k: 0.0 for k in pred_dict.keys()} prob_dict["no_label"] = 1.0 return prob_dict
elif self.classifier_type == "multi_class": return dict(zip(pred_dict.keys(), self._softmax_array(list(pred_dict.values()))))
elif self.classifier_type == "multi_label": return dict(zip(pred_dict.keys(), self._sigmoid_array(list(pred_dict.values()))))
Когда делается вызов DictionaryClassifier.predict
, количество слов для всех связанных терминов в каждом классе вычисляется и подсчитывается через _get_label_word_count
. Здесь также выполняется некоторое базовое преобразование нижнего регистра в качестве ленивой попытки обобщить совпадения для максимально возможного количества входных данных. Конечно, если в конкретном домене много аббревиатур или важный контекст, где регистр имеет значение, это может стать проблемой. Возможно, use_lowercase
param следует поднять как настраиваемую часть класса в какой-то момент в будущем?
На данный момент у нас есть подсчет частоты слов для каждого интересующего класса. Используя приведенный выше пример, подсчет слов выглядит следующим образом:
# ding ding the answer is probably going to be "contribution" {'regulation': 0, 'contribution': 2, 'covid': 0, 'retirement': 0, 'fund': 0}
# also, .values() kind of looks like a vector.. [0, 2, 0, 0, 0]
На этом этапе мы вызываем _transform_predict_dict
, который применяет либо функцию softmax (многоклассовая), либо сигмовидную (многометковую) функцию к значениям словаря подсчета слов, и bada bing bada boom у нас есть предсказания модели, которые выглядят и пахнут как типичные модельные прогнозы.
Некоторые дополнительные полезные функции — это методы сериализации, которые я смоделировал с помощью пространственного дизайна:
# I always thought the to/from_disk pattern for models/components was neat dc.to_disk('/Users/samhardy/Desktop/dict_classifier')
# reload, use as before etc. dc = DictionaryClassifier('/Users/samhardy/Desktop/dict_classifier')
Это позволяет DictionaryClassifiers
встраиваться в общий шаблон ML-операций для загрузки модели из URI корзины, как и другие модели, требующие загрузки весов/двоичных файлов. Или вы можете просто сохранить словарь в JSON-форме где-нибудь еще и переопределить на лету!
Дай дай
Репо здесь, и/или пусть рипится через pip install clear_bow