Глубокое погружение в алгоритм фразировки

Фраза - это многословное выражение или словесная н-грамма, которая выражает действительный смысл и очень полезна при индексировании документа, резюме, рекомендациях и т. Д.

В этой статье я расскажу о модуле параллельной фразировки с очень высокой точностью, используя всего 10000 неструктурированных новостных статей с точностью более 95%.

Расскажет, как создавать правильные двухграммы и триграммы.

Вам может быть интересно, что существуют такие модули Phrase, как gensim и т. Д.

Фразы, генерируемые gensim, не на должном уровне и могут потребовать огромного корпуса для генерации фраз, основанных на словосочетании. (Я думаю, около 1 миллиона новостных статей)

Шаги, которым нужно следовать, чтобы создать осмысленную фразу

  1. Предварительная обработка и нормализация текста (есть много сообщений о предварительной обработке текста, поэтому здесь не будем вдаваться в подробности. Пример ссылка.)
  2. Создайте n граммов (1 (uni), 2 (bi), 3 (tri)) из новостных статей (параллельно с процессором) (версия Spark будет обсуждена позже)
  3. Обучите модель
  4. Извлеките действительные фразы

Алгоритм и дизайн основных фраз

Инициализировать простор и нормализацию текста

Сначала давайте нормализуем текст (необработанная текстовая строка)

#initlize spacy in your class init
self.nlp = spacy.load(lang)
#also need cpu count for parallelism  
partitions = cpu_count()
normalized_text = self.normalize_text(doc)
doc = self.nlp(normalized_text)

Параллельная сборка n граммов

uni_grams = self.parallel_build_n_grams(doc, 1)
bi_grams = self.parallel_build_n_grams(doc, 2)
tri_grams = self.parallel_build_n_grams(doc, 3)

сгенерировать n граммов и разделить промежутки на несколько разделов (документ - это nlp-документ)

def generate_ngrams(self, doc, n_gram):
    return [doc[i:i + n_gram] for i in range(len(doc) - n_gram + 1)]

расколоть

splits = np.array_split(self.generate_ngrams(doc, n_gram), partitions)

Для каждого сплита создайте n граммов, а также добавьте теги pos для последующего анализа. Посмотрите на многопроцессорность python, чтобы понять процесс.

for s in splits:
    q = Queue()
    p = Process(target=self.build_n_grams_from_span_list, args=(s, q))

Создавая Ngram из диапазона, а также добавляя теги pos, мы будем использовать только лемму текста.

def build_n_grams_from_span_list(self, sp, q):
    ph_pos = []
    for s in sp:
        ngram = []
        pos_tag = []
        for p in s:
            ngram.append(p.lemma_)
            pos_tag.append(p.pos_)
        phrase = " ".join(ngram)
        pos = "_".join(pos_tag)

        ph_pos.append((phrase, pos))

    q.put(ph_pos)

Вот полный код параллельной сборки n граммов

def parallel_build_n_grams(self, doc, n_gram):
    print('function name: parallel_build_n_grams')
    ngrams = []

    splits = np.array_split(self.generate_ngrams(doc, n_gram), partitions)
    ps = []
    qs = []
    for s in splits:
        q = Queue()
        p = Process(target=self.build_n_grams_from_span_list, args=(s, q))
        p.start()
        ps.append(p)
        qs.append(q)

    rqs = [q.get() for q in qs]

    for q in rqs:
        for ph, po in q:
            ngrams.append((ph, po))

    for p in ps:
        p.join()

    return ngrams

Вот как это должно выглядеть в фреймворке. Должно быть три основных фрейма данных, которые необходимо объединить с новым фреймом данных, генерируемым из каждой новостной статьи.

#for bi grams we need c21, cw2, cw12 (will explain later what these means and why required)
#3 master dataframe merge the ngrams reqturned from above with unigram dataframe bigram data trigram data frame
self.uni_gram_df = pd.DataFrame(columns=['pos_tag', 'freq'])
self.bi_gram_df = pd.DataFrame(columns=['pos_tag', 'freq', 'pmi', 'cw1w2', 'cw1', 'cw2'])
self.tri_gram_df = pd.DataFrame(columns=['pos_tag', 'freq', 'pmi', 'cw1w2w3', 'cw1w2', 'cw1', 'cw2', 'cw3'])

N-gram dataframe выводит как он должен выглядеть. Обратите внимание: эти фреймы данных будут расти по мере поступления все большего количества новостей. Я поделюсь статистикой позже, поэтому нам нужна искра для параллелизма данных.

Сохранить н-грамм

Нам нужно переобучить систему для биграмм и триграмм, частота юниграмм обновляется при построении униграмм и их объединении.

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

splits = np.array_split(self.dict_n_gram_df[2], partitions)
ps = []
qs = []
for s in splits:
    q = Queue()
    p = Process(target=self.retrain_2_grams, args=(self.dict_n_gram_df[1], s, q))
    p.start()
    ps.append(p)
    qs.append(q)

dfs = [q.get() for q in qs]
df = pd.concat(dfs)
for p in ps:
    p.join()

Как генерировать высокоточные фразы (биграммы, триграммы)

Давайте разберемся, как генерировать биграммы и триграммы высокой точности и что делает этот код переобучения.

Короче говоря, что нам действительно нужно знать, является ли биграмма word1 (w1) word2 (w2) действительной биграммой.

Например, ниже приведены некоторые из допустимых биграмм.

Например, ниже приведены некоторые из недопустимых биграмм. Обратите внимание, что некоторые из биграмм действительно действительны при уровне значимости P ‹0,05. Подробнее об этом см. Ниже в разделе« Основная идея ».

Например, ниже приведены некоторые из допустимых триграмм.

Например, ниже приведены некоторые из недопустимых триграмм.

Основная идея

Таким образом, мы действительно хотим знать в случае биограмм w1 и w2, возникли ли они чисто случайно или это действительное колокация.

Итак, давайте иметь нулевую гипотезу H0 о том, что нет никакой связи между w1 и w2, и это происходит чисто случайно.

Таким образом, мы говорим, что вероятность появления w1 и w2 является чисто случайной, это наша нулевая гипотеза, поэтому после вычисления вероятности биграмм P (w1w2) или триграмм P (w1w2w3) мы можем отклонить нулевую гипотезу при уровне достоверности альфа. = 0,005 (95,95% доверительный интервал) или альфа = 0,001 (99,99% доверительный интервал), в противном случае мы сохраняем нулевую гипотезу (это недопустимая фраза, а размещение происходит случайно).

В случае системы высокой точности вы можете выбрать уровень значимости 0,01.

Мы не будем использовать простой P (w1w2w3) = P (w1) * P (w2) * P (w3) в качестве вероятности колокации, поскольку это не точная оценка колокации, хотя мы примем независимость в качестве нулевого значения. гипотеза.

t-тест

Некоторое объяснение статистики t можно найти по ссылкам ниже.

Википедия т статистика

Ханская академия

Статистика t широко используется для теста colocation.

Вот выдержка из сборника статистики

Давайте посмотрим на пример биграммы rajkumar hirani:

Используя оценку максимального правдоподобия, мы можем вычислить вероятности раджкумара и хирани следующим образом. В нашем корпусе rajkumar (cw1) и hirani (cw2) произошли следующим образом.
cw1 97
cw2 209

Длина корпуса = 689768 (это только в 3000 новостной статье)

Р (раджкумар) = 97/689768

П (хирани) = 209/689768

что дает P (w1) и P (w2) следующим образом

P(w1) 0.000140627
P(w2) 0.000303

Нулевая гипотеза заключается в том, что слова раджкумар и хирани встречаются независимо.

H0: P (rajkumar hirani) = P (rajkumar) * P (hirani) = 4,261e-08

Если нулевая гипотеза верна, то процесс случайного генерирования биграмм слов и присвоения 1 результату rajkumar hirani и 0 любому другому результату фактически является Испытание Бернулли с p = 4,261e-08 для вероятности появления rajkumar hirani. Среднее значение для этого распределения составляет μ = 4,261e-08, а дисперсия σ2 = p (1 − p), что приблизительно равно p. Приближение σ2 = p (1 − p) выполняется, поскольку для большинства биграмм p мало.

Всего в корпусе содержится 89 упоминаний слова rajkumar hirani.

cw1w2 = 89

Таким образом, наше выборочное среднее x = 89 / 689768 = 0,000129029

Теперь у нас есть все необходимое для применения t-теста:

t stat = 0.000129029–4.261e-08 / np.sqrt (0.000129029 / 689768)

= 9.43086968147722

Критическое значение для альфа = 0,005 или доверительной вероятности 99,95% составляет 2,576. Таким образом, мы можем отклонить нулевую гипотезу, поскольку 9,43 намного больше, чем это, и можем заключить, что словосочетание rajkumar hirani не является чисто случайным и действительной биграммой.

Вот код для переобучения биграммы t stat, аналогичные справедливы для триграмм

def update_2_gram_t_stats(self, df):
    df2_len = len(df.index)
    df['pw1'] = df['cw1']/df2_len
    df['pw2'] = df['cw2']/df2_len
    df['p(w1)p(w2)'] = df['pw1'] * df['pw2']
    df['p(w1w2)'] = df['cw1w2']/df2_len
    df['s2/n'] = np.sqrt(df['p(w1w2)']/df2_len)

    df['stat'] = (df['p(w1w2)'] - df['p(w1)p(w2)'])/df['s2/n']

    df['valid_01'] = False
    df['valid_01'] = df['stat'] > 3

    df['valid_05'] = False
    df['valid_05'] = df['stat'] > 2.6
    return df

Надеюсь, вам понравится читать о том, как сгенерировать допустимые фразы с помощью коллокации. Вышеупомянутая реализация точна на 95%, и в следующей части мы перенесем эту реализацию в Apache Spark.

Эта реализация намного лучше, чем словосочетание на основе PMI, PMI не дает достаточного понимания правильных фраз. Вы можете легко изменить приведенный выше код и реализовать функцию оценки pmi.

Оценка PMI для кода триграммы

pmi_score = np.log(cw1w2w3 * N / (cw1w2*cw3))

Также посмотрите мою другую серию статей о построении личного помощника и графа знаний.

Спасибо..

Ссылки:

Https://en.wikipedia.org/wiki/N-gram

[1] https://radimrehurek.com/gensim/models/phrases.html

[2] https://nlp.stanford.edu/fsnlp/promo/colloc.pdf

[3] https://en.wikipedia.org/wiki/N-gram

[4] https://wiki.python.org/moin/ParallelProcessing