Около года назад я создал приложение на 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.