Модальные глаголы — это вспомогательные глаголы, которые указывают семантическую информацию о действии, то есть вероятность (будет, должен), разрешение (может, может), обязательство (должен / должен). Одна интересная концепция, которую нужно исследовать, заключается в том, варьируется ли присутствие этих глаголов в разных типах текста и означает ли это что-нибудь.
« Обработка естественного языка с Python » ( прочитайте мой обзор ) есть пример того, как начать этот процесс, сравнивая частоты глаголов в разных жанрах текста, используя коричневый корпус, известную коллекцию текстов, собранных в 60-х годах для языка. исследовательская работа.
Я расширил пример, включив в него дополнительный корпус судебных дел и дополнительные вспомогательные глаголы. Это включает в себя содержание ~ 15 000 юридических документов.
Сначала мы определяем функцию для поиска жанров литературы, а затем — для поиска слов из жанра. Для юридических документов я читаю из индекса, который я ранее построил из n-грамм [ссылка] (т.е. количество слов / фраз).
import nltk import os def get_genres(): yield 'legal' for genre in brown.categories(): yield genre modals = ['can', 'could', 'may', 'might', 'must', 'will', 'would', 'should'] def get_words(genre): if (genre == 'legal'): grams = open('1gram', 'rU') for line in grams: vals = line.split(' ') word = vals[0] count = int(vals[1]) if (word in modals): for index in range(0, count): yield word else: yield word grams.close() else: for word in brown.words(categories=genre): yield word |
Natural Language Toolkit предоставляет класс для отслеживания частоты результатов «эксперимента» — здесь мы отслеживаем использование различных времен глаголов.
cfd = nltk.ConditionalFreqDist( (genre, word) for genre in get_genres() for word in get_words(genre) ) genres = [g for g in get_genres()] cfd.tabulate(conditions=genres, samples=modals) cfd.tabulate(conditions=genres, samples=modals) |
Метод tabulate предоставляется NTLK и создает красиво отформатированный график (в командной строке он выстраивает все аккуратно)
может | мог | мая | может быть | должен | буду | бы | должен | |
правовой | 13059 | 7849 | 26968 | 1762 | 15974 | 20757 | 19931 | 13916 |
приключение | 46 | 151 | 5 | 58 | 27 | 50 | 191 | 15 |
belles_lettres | 246 | 213 | 207 | 113 | 170 | 236 | 392 | 102 |
редакционный | 121 | 56 | 74 | 39 | 53 | 233 | 180 | 88 |
художественная литература | 37 | 166 | 8 | 44 | 55 | 52 | 287 | 35 |
правительство | 117 | 38 | 153 | 13 | 102 | 244 | 120 | 112 |
хобби | 268 | 58 | 131 | 22 | 83 | 264 | 78 | 73 |
юмор | 16 | 30 | 8 | 8 | 9 | 13 | 56 | 7 |
научился | 365 | 159 | 324 | 128 | 202 | 340 | 319 | 171 |
уздечка | 170 | 141 | 165 | 49 | 96 | 175 | 186 | 76 |
тайна | 42 | 141 | 13 | 57 | 30 | 20 | 186 | 29 |
Новости | 93 | 86 | 66 | 38 | 50 | 389 | 244 | 59 |
религия | 82 | 59 | 78 | 12 | 54 | 71 | 68 | 45 |
отзывы | 45 | 40 | 45 | 26 | 19 | 58 | 47 | 18 |
романс | 74 | 193 | 11 | 51 | 45 | 43 | 244 | 32 |
научная фантастика | 16 | 49 | 4 | 12 | 8 | 16 | 79 | 3 |
Глядя на эти цифры, становится ясно, что нам нужно добавить концепцию нормализации. Мой добавленный корпус содержит намного больше жетонов, чем Коричневый, что затрудняет сравнение.
Класс распределения частот существует для подсчета вещей, и я не нашел хорошего способа нормализации строк. Я переписал функцию tabulate для этого — она просто находит максимум для каждой строки, делит на это и умножает на 100.
def tabulate(cfd, conditions, samples): max_len = max(len(w) for w in conditions) sys.stdout.write(" " * (max_len + 1)) for c in samples: sys.stdout.write("%-s\t" % c) sys.stdout.write("\n") for c in conditions: sys.stdout.write(" " * (max_len - len(c))) sys.stdout.write("%-s" % c) sys.stdout.write(" ") dist = cfd[c] norm = sum([dist[w] for w in modals]) for s in samples: value = 100 * dist[s] / norm sys.stdout.write("%-d\t" % value) sys.stdout.write("\n") tabulate(cfd, genres, modals) |
Это облегчает сканирование вверх и вниз по графику.
может | мог | мая | может быть | должен | буду | бы | должен | |
правовой | 10 | 6 | 22 | 1 | 13 | 17 | 16 | 11 |
приключение | 8 | 27 | 0 | 10 | 4 | 9 | 35 | 2 |
belles_lettres | 14 | 12 | 12 | 6 | 10 | 14 | 23 | 6 |
редакционный | 14 | 6 | 8 | 4 | 6 | 27 | 21 | 10 |
художественная литература | 5 | 24 | 1 | 6 | 8 | 7 | 41 | 5 |
правительство | 13 | 4 | 17 | 1 | 11 | 27 | 13 | 12 |
хобби | 27 | 5 | 13 | 2 | 8 | 27 | 7 | 7 |
юмор | 10 | 20 | 5 | 5 | 6 | 8 | 38 | 4 |
научился | 18 | 7 | 16 | 6 | 10 | 16 | 15 | 8 |
уздечка | 16 | 13 | 15 | 4 | 9 | 16 | 17 | 7 |
тайна | 8 | 27 | 2 | 11 | 5 | 3 | 35 | 5 |
Новости | 9 | 8 | 6 | 3 | 4 | 37 | 23 | 5 |
религия | 17 | 12 | 16 | 2 | 11 | 15 | 14 | 9 |
отзывы | 15 | 13 | 15 | 8 | 6 | 19 | 15 | 6 |
романс | 10 | 27 | 1 | 7 | 6 | 6 | 35 | 4 |
научная фантастика | 8 | 26 | 2 | 6 | 4 | 8 | 42 | 1 |
Это ясно из того, что большинство жанров имеют многочисленные ссылки на «хотел», и немногие имеют «должен».
Было бы неплохо увидеть их в масштабе 1-10 — видя, что столбцы чисел сообщают что-то длинное.
def tabulate(cfd, conditions, samples): max_len = max(len(w) for w in conditions) sys.stdout.write(" " * (max_len + 1)) for c in samples: sys.stdout.write("%-s\t" % c) sys.stdout.write("\n") for c in conditions: sys.stdout.write(" " * (max_len - len(c))) sys.stdout.write("%-s" % c) sys.stdout.write(" ") dist = cfd[c] norm = sum([dist[w] for w in modals]) for s in samples: value = 10 * float(dist[s]) / norm sys.stdout.write("%.1f\t" % value) sys.stdout.write("\n") tabulate(cfd, genres, modals) |
может | мог | мая | может быть | должен | буду | бы | должен | |
правовой | 1,1 | 0.7 | 2,2 | 0,1 | 1,3 | 1,7 | 1,7 | 1.2 |
приключение | 0.8 | 2,8 | 0,1 | 1,1 | 0,5 | 0.9 | 3,5 | 0,3 |
belles_lettres | 1,5 | 1,3 | 1.2 | 0.7 | 1,0 | 1.4 | 2,3 | 0.6 |
редакционный | 1.4 | 0.7 | 0.9 | 0,5 | 0.6 | 2,8 | 2,1 | 1,0 |
художественная литература | 0,5 | 2,4 | 0,1 | 0.6 | 0.8 | 0.8 | 4,2 | 0,5 |
правительство | 1,3 | 0,4 | 1,7 | 0,1 | 1,1 | 2,7 | 1,3 | 1.2 |
хобби | 2,7 | 0.6 | 1,3 | 0.2 | 0.8 | 2,7 | 0.8 | 0.7 |
юмор | 1,1 | 2,0 | 0,5 | 0,5 | 0.6 | 0.9 | 3,8 | 0,5 |
научился | 1,8 | 0.8 | 1,6 | 0.6 | 1,0 | 1,7 | 1,6 | 0.9 |
уздечка | 1,6 | 1,3 | 1,6 | 0,5 | 0.9 | 1,7 | 1,8 | 0.7 |
тайна | 0.8 | 2,7 | 0,3 | 1,1 | 0.6 | 0,4 | 3,6 | 0.6 |
Новости | 0.9 | 0.8 | 0.6 | 0,4 | 0,5 | 3,8 | 2,4 | 0.6 |
религия | 1,7 | 1,3 | 1,7 | 0,3 | 1.2 | 1,5 | 1.4 | 1,0 |
отзывы | 1,5 | 1,3 | 1,5 | 0.9 | 0.6 | 1,9 | 1,6 | 0.6 |
романс | 1,1 | 2,8 | 0.2 | 0.7 | 0.6 | 0.6 | 3,5 | 0,5 |
научная фантастика | 0.9 | 2,6 | 0.2 | 0.6 | 0,4 | 0.9 | 4,2 | 0.2 |
Было бы неплохо увидеть, насколько похожи эти жанры — мы можем вычислить это, представив количество модалов как описание векторов. Угол между векторами приближается к «подобию». Хорошая особенность этой меры состоит в том, что она удаляет другие слова (слова, которые могут существовать только в одном тексте — отчасти это будет связано с тем, насколько хорошо очищены данные, что не отражается на жанре литературы).
import math def distance(cfd, conditions, samples, base): base_cond = cfd[base] base_vector = [base_cond[w] for w in samples] base_length = math.sqrt(sum(a * a for a in base_vector)) for c in conditions: cond = cfd[c] cond_vector = [cond[w] for w in samples] dotp = sum(a * b for (a,b) in zip(base_vector, cond_vector)) cond_length = math.sqrt(sum(a * a for a in cond_vector)) angle = math.acos(dotp / (cond_length * base_length)) percent = (math.pi / 2 - angle) / (math.pi / 2) * 100 print "%-s similarity to %-s: %-.1f" % (c, base, percent) |
Результат интересный — жанры, показывающие близость к легальному, в данном случае — это правительство и религия.
В качестве интересного дополнения belles_lettres означает «хорошее письмо», то есть стихи, драма, художественная литература
legal similarity to legal: 100.0 adventure similarity to legal: 41.6 belles_lettres similarity to legal: 72.4 editorial similarity to legal: 68.8 fiction similarity to legal: 42.9 government similarity to legal: 80.6 hobbies similarity to legal: 63.5 humor similarity to legal: 50.1 learned similarity to legal: 80.6 lore similarity to legal: 78.6 mystery similarity to legal: 41.3 news similarity to legal: 58.1 religion similarity to legal: 81.2 reviews similarity to legal: 73.5 romance similarity to legal: 42.9 science_fiction similarity to legal: 41.8
Некоторые жанры похожи на юридические документы — возможно, однако, что некоторые глаголы не являются независимыми. Например, вы можете увидеть «может» и «может» с одинаковым сходством. Один из способов проверить это, возможно, состоит в том, чтобы отразить то, что мы отслеживаем для расстояния (сделать вектор для каждого модального, а не жанра)
Следующий код отслеживает расстояние между каждым модальным и средним, используя разные жанры в качестве измерений. Поскольку каждый из них вносит некоторый вклад в среднее значение, гарантируется некоторое сходство, но обратите внимание, что некоторые из них ближе, чем другие. Также обратите внимание, что они должны быть нормализованы, как в последнем примере, или ответ будет определяться жанром «правовой».
def distance(cfd, conditions, samples): base_vector = [0.0 for w in conditions] norm = {} for c_i in range(0, len(conditions)): cond_name = conditions[c_i] cond = cfd[cond_name] norm[cond_name] = float(sum(cond[s] for s in samples)) for s in samples: base_vector[c_i] = base_vector[c_i] + float(cond[s]) / norm[cond_name] base_length = math.sqrt(sum(a * a for a in base_vector)) for s in samples: # compute each vector - which, might, etc sample_vector = [] for c in conditions: # find condition for each vector sample_vector.append(cfd[c][s] / norm[c]) dotp = sum(a * b for (a,b) in zip(base_vector, sample_vector)) sample_length = math.sqrt(sum(a * a for a in sample_vector)) angle = math.acos(dotp / (sample_length * base_length)) percent = (math.pi / 2 - angle) / (math.pi / 2) * 100 print "%-s similarity to mean: %-.1f" % (s, percent) distance(cfd, genres, modals) |
Из этого я могу сделать вывод, что наименее полезным глаголом для различения жанров является «должен», а самым полезным — «может».
can similarity to mean: 76.0 could similarity to mean: 67.6 may similarity to mean: 61.5 might similarity to mean: 70.0 must similarity to mean: 79.7 will similarity to mean: 67.7 would similarity to mean: 73.6 should similarity to mean: 74.2