Около года назад я создал приложение на Python, которое создало корпус музыкальных текстов и выполнило несколько анализов НЛП. Тексты в корпусе классифицируются по музыкальным жанрам (таким как рок, хип-хоп, первая волна, кантри) и отождествляются с исполнителем. С тех пор я пересмотрел приложение для аналитики социальных сетей, которое я создал, и реорганизовал его, чтобы оно содержало данные в объектах, созданных с помощью пользовательских определений классов, подобно моему приложению для текстов песен. Все это дало мне некоторые идеи об управлении контентом для приложений НЛП и управлении часто утомительным процессом форматирования и очистки данных.
У меня было несколько целей более высокого уровня, когда я приступил к разработке и созданию приложения. Я думал, что природа этого корпуса: совокупность тысяч относительно небольших единиц (каждая песня или «дорожка»), необходимость группировки и анализа по категориям, таким как жанр и исполнитель, и набор общих задач для выполнения на единицы или группы, умоляли о создании объектно-ориентированных контейнеров с использованием пользовательских классов в python. Я мог бы даже использовать наследование классов, чтобы помочь с некоторым полиморфным поведением, которое я хотел от различных типов контейнеров корпуса.
Затем я хотел обучить векторы документов из контейнеров текстов песен как на уровне жанра, так и на уровне исполнителя. Определение класса Python функции для языковых объектов отлично подходили для поддержки этого. Благодаря определению генератора-итератора во встроенном методе __init__, который является общим для всех объектов Python, я мог легко передать любой из моих экземпляров контейнера текстов в алгоритмы обучения пакета векторизации, такого как gensim(я не могу сказать достаточно о gensim - очень мощный, но даже разработчик с частичной занятостью, такой как я можно эффективно использовать). Вместо того, чтобы писать скрипт для выбора, форматирования и потоковой передачи отдельных текстов, как только я создал экземпляр группы текстов, я просто передал объект методу gensim Doc2Vec.
Я работал с Python в течение четырех лет и около 20 лет знаком с корпоративной Java, но я никогда не видел проблемы, которая так идеально подходила бы для использования определений объектно-ориентированных классов, включая наследование, полиморфизм и инкапсуляция данных. Сейчас я работаю с НЛП над несколькими различными типами письменного контента — официальными документами и пресс-релизами, твитами и короткими сообщениями в социальных сетях, а также текстами песен. У каждого есть отличия от использования сленга, символики или разговорной речи до грамматики и структуры. Есть исследователи, которые потратили годы на эти проблемы и решили многие из них — я не стремлюсь изобретать велосипед, я просто ищу наиболее гибкий подход, чтобы я мог применять лучшие практики к определенному типу контента.
Best Case Matching of unseen lyrics to trained Doc2Vec model: 19 out of 23 unseen Madness lyrics had top match on Madness, 5 out of 23 had 100% of their top ten matches with Madness tracks Each test evaluated 7343 tracks from 27 different artists
Форматирование и очистка данных занимают большую часть общих усилий в этих проектах, но их следует применять разумно, исходя из потребностей последующих потоков. Например, пакет Vader отлично подходит для расчета тональности с помощью твитов, но он работает лучше всего и обеспечивает согласованность результатов, если ему подается необработанный контент. Твиттер имеет свою собственную грамматику, набор идиом и использование пунктуации и символов, и они могут различаться в зависимости от местности, поколения или других социальных групп. Vader был создан, чтобы справиться с этим, а также позволяет настраивать лексиконы, идиомы и правила (я предлагаю сопротивляться искушению возиться с этим, если только не было обнаружено какое-то упущение в пакете, необходимое для особого случая). Может показаться заманчивым запустить базовую очистку данных как часть восходящей обработки, но это испортит оценку тональности. Класс Python, который служит «контейнером», а также применяет и отслеживает форматирование и очистку, применяемые к его данным, защищает ваше здравомыслие! Прежде чем я принял этот метод для работы с НЛП, я иногда пропускал или дублировал шаги, пытаясь определить состояние данных и «оптимальную» последовательность задач.
Существуют некоторые промежуточные задачи, которые необходимо выполнить для поддержки определенных НЛП и векторного анализа, таких как расчет tf*idf, таблицы частоты слов или анализ BOW (мешок слов). Я добавил их как методы экземпляра для своих классов-контейнеров и добавил логику для отслеживания того, вызывались ли они или нужно ли их запускать снова, прежде чем предоставлять свои результаты. Я создал словари художников по жанрам, которые «контролировали» процесс создания корпуса. Как только я был удовлетворен полученным корпусом для каждого жанра, я запустил создание экземпляра контейнера. В этом случае, когда мои базовые данные остаются постоянными, я могу запускать свои промежуточные задачи, такие как tf*idf by artist, один раз для каждого экземпляра контейнера. Я не хотел проектировать, основываясь на этом предположении, так как я мог добавить в жанр дополнительных исполнителей в любое время, и я мог видеть, что контейнеры чаще должны были реагировать на изменения в базовых данных и обновлять их. производные расчеты.
Почему лирика?
Я много лет был музыкантом-любителем. Любитель может преувеличить это! В основном я играю на гитаре, укулеле и мандолине и пою дома, а также записываю несколько треков для SoundCloud или для отправки друзьям. Мои дети открыли мне доступ к большему количеству музыкальных стилей, поэтому, хотя «рок» или «первой волны» были моими главными жанрами, я также заинтересовался «хип-хопом». Я хотел совместить свои музыкальные интересы с интересами к python-аналитике и посмотреть на сходства и различия в текстах как на уровне музыкального жанра, так и на уровне отдельного исполнителя.
Почему классы контейнеров?
В этой статье я сосредоточился на своих выводах относительно дизайна приложений. Я определил свою модель данных и цели, а также написал сценарии и определил функции для создания своего корпуса и начала его анализировать. Именно в тот момент, когда я увидел появляющуюся сложность, у меня появилось прозрение относительно контейнерных классов. Я определил 8 музыкальных жанров и около 135 исполнителей, которых хотел для репрезентативных корпусов. Занятия упростили выполнение многоэтапного НЛП-анализа различных групп жанров и/или исполнителей. Наследование позволило мне переопределить мои первые попытки. В некоторых случаях мои методы были слишком неэффективны или не могли справиться с дополнительной иерархической сегментацией корпуса, поэтому я улучшил свою модель, унаследовав новый класс и добавив более надежные методы. Это оказалось не расточительным, поскольку подтолкнуло меня к полиморфизму, применяя более общие методы с классами-предками и более подробные, детализированные методы с классами-потомками. Это просто вопрос создания экземпляра правильного класса на основе данные и степень детализации результатов, которые мне нужны.
С каждым приложением NLP, которое я создал, я формировал концептуальную модель, которая позволяла мне повторять итерации для повышения эффективности, а также простоты работы. Я начал с интерактивного извлечения, форматирования, очистки и вычисления производных или промежуточных данных. Затем я провел рефакторинг с определениями функций и переданными параметрами. Наконец, я сосредоточился на пользовательских классах-контейнерах и в первую очередь на методах экземпляров для них. Теперь я могу передавать контейнеры и передавать из них определенные данные, используя функции генератора-итератора Python. Все в питоне является объектом, и у каждого объекта есть определение класса. Определение управляет тем, как создается экземпляр соответствующего объекта и как он будет вести себя во время своего существования. Это мое непрофессиональное объяснение — Python имеет необычайно чистый, продуманный до мелочей дизайн — настолько, что я чувствую, что могу его описать.
# get core attributes from aggregator- helper to instantiate MusicalMeg objects # aggregator collects artists, tracks, and folders by genre- rap_core = Genre_Aggregator(gen='rap') firstw_core = Genre_Aggregator(gen='firstwave') # instantiate genre containers from core mm_rap = MusicalMeg(ga_obj=rap_core) mm_firstw = MusicalMeg(ga_obj=firstw_core) # show tf*idf histograms for all rap artists for x in range(len(mm_rap.artists)): plot_artist_tfidf(lobj=mm_rap, artst=mm_rap.artists[x]) # MusicalMeg - class definition: class MusicalMeg(): def __init__(self, ga_obj: Genre_Aggregator, lydir: str=LYRICDIR): self.filter_tfidf: bool = True self.stream_td: bool = True self.folder = lydir if isinstance(ga_obj, Genre_Aggregator): self.genre = ga_obj.genre self.trax = ga_obj.trax self.artists = ga_obj.artist_list self.words_raw_count: int = 0 self.word_freq: dict = {} self.corpbow = [] self.corpdic: Dictionary = {} self.word_artists: dict = {} self.artist_words: dict = {} self.words_trak: list = [] self.idf_words: dict = {} self.tf_by_artist: dict = {} self.tf_by_trak: dict = {} self.tfidf_artist: dict = {} self.tfidf_trak: dict = {} self.tfidf_cutoff_pct: int = 30 self.calc_word_freq() self.calc_tf_by_trak() self.calc_idf_for_words() # iter method- uses python generator via ‘yield’ to stream lyrics def __iter__(self): if self.artists: for artist in self.artists: trak_ct: int = 0 artst_words: int = 0 tmp: str = str(artist).replace(" ", "").replace(".", "") art_lyrfile: str = str(self.genre) + "_" + tmp + ".lyr" if self.trax.get(artist): traxdict = self.trax.get(artist) art_taglst = list(traxdict.keys()) else: art_taglst = [] # this function below streams each track for a given artist for lyrs, artst in feed_specific_artists(artlst=art_lyrfile, srcdir=self.folder): if art_taglst: try: doctag = art_taglst[trak_ct] except IndexError: doctag = artist + str(trak_ct).rjust(3, "0") print("Index out-of-range for registry: %s" % doctag) else: print("custom tag needed %s trak %d" % (artist, trak_ct)) doctag = artist + str(trak_ct).rjust(3, "0") lyrics_tok: list = [] for wrd in lyrs: lyrics_tok.append(wrd) if lyrics_tok: # self.stream_td is boolean that can feed doc or word vector training if self.stream_td: yield TaggedDocument(words=lyrics_tok, tags=[doctag]) else: # if not tagged doc, just stream word tokenized lyrics yield lyrics_tok artst_words += len(lyrics_tok) trak_ct += 1 self.artist_words[artst] = artst_words
Как я уже упоминал, сравнивая официальные документы, твиты и тексты песен, каждый тип текстового контента имеет характерные черты: от «документа», размера и количества файлов до использования слов и символов. Несмотря на различия, типы, а иногда и последовательность задач значительно пересекаются. Независимо от типа текстового контента, также необходимо упростить и сообщить об обработке с помощью некоторого подхода к метаданным. Все это облегчается использованием ОО-контейнеров. В приведенном выше определении MusicalMeg, в дополнение к показанным переменным, я могу добавить переменные для отслеживания операций форматирования и очистки содержимого. Это может быть номинальное значение, которое идентифицирует состояние — тогда любые последующие задачи могут проверить, находится ли содержимое контейнера в приемлемом состоянии, и, если нет, могут выполнить требуемое форматирование или очистку.
У меня есть старая версия gs_lyrics, загруженная в репозиторий моей учетной записи github по адресу https://github.com/briangalindoherbert. На этой неделе я загружу обновленную версию с улучшенными классами контейнеров и некоторыми дополнительными функциями с анализом Doc2Vec и LDA.