Что и почему

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

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

Так, где это оставляет нас? Что ж, я считаю, что, по возможности, первоначальные решения машинного обучения должны быть простыми и должны надлежащим образом увеличивать свою сложность по мере необходимости, если это вообще возможно. Черт, может быть, вам просто нужна база данных вместо 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_lowercaseparam следует поднять как настраиваемую часть класса в какой-то момент в будущем?

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

# 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