Подтвердить что ты не робот

Открытие поэтической формы с помощью NLTK и CMU Dict

Изменить: этот код был обработан и выпущен в качестве базового модуля: https://github.com/hyperreality/Poetry-Tools

Я лингвист, который недавно взял питон, и я работаю над проектом, который надеется автоматически анализировать стихи, включая обнаружение формы стихотворения. То есть если он найдет 10 слоговых строк с рисунком напряжения 0101010101, он объявит, что это ямбический пентаметр. Стихотворение с 5-7-5 слогами будет хайку.

Я использую следующий код, часть более крупного script, но у меня есть ряд проблем, которые перечислены ниже программы:

corpus в script - это просто исходный ввод текста стихотворения.

import sys, getopt, nltk, re, string
from nltk.tokenize import RegexpTokenizer
from nltk.util import bigrams, trigrams
from nltk.corpus import cmudict
from curses.ascii import isdigit

...

def cmuform():
    tokens = [word for sent in nltk.sent_tokenize(corpus) for word in nltk.word_tokenize(sent)]
    d = cmudict.dict()
    text = nltk.Text(tokens)
    words = [w.lower() for w in text]
    regexp = "[A-Za-z]+"
    exp = re.compile(regexp)

    def nsyl(word):
        lowercase = word.lower()
        if lowercase not in d:
                return 0
        else:
            first = [' '.join([str(c) for c in lst]) for lst in max(d[lowercase])]
            second = ''.join(first)
            third = ''.join([i for i in second if i.isdigit()]).replace('2', '1')
            return third 
                #return max([len([y for y in x if isdigit(y[-1])]) for x in d[lowercase]])      

    sum1 = 0
    for a in words:
            if exp.match(a):
            print a,nsyl(a),
                sum1 = sum1 + len(str(nsyl(a)))

    print "\nTotal syllables:",sum1

Я предполагаю, что результат, который я хочу, будет таким:

1101111101

0101111001

1101010111

Первая проблема заключается в том, что я потерял разрывы строк во время токенизации, и мне действительно нужны разрывы строк, чтобы иметь возможность идентифицировать форму. С этим не должно быть слишком сложно справиться. Большие проблемы заключаются в следующем:

  • Я не могу иметь дело с словарями, отличными от словаря. В данный момент я возвращаю 0 для них, но это будет путать любую попытку идентифицировать стихотворение, поскольку слоговое число строки, вероятно, уменьшится.
  • Кроме того, в словаре CMU часто говорится, что есть стресс на слово - "1" - когда нет - "0". Вот почему результат выглядит следующим образом: 1101111101, когда это должно быть напряжение ямбического пентаметра: 0101010101
    Итак, как бы я мог добавить некоторый fudging фактор, чтобы стихотворение все еще идентифицировалось как ямбический пентаметр, когда оно только приближалось к шаблону? Нехорошо кодировать функцию, которая идентифицирует строки 01, когда словарь CMU не будет выводить такой чистый результат. Полагаю, я спрашиваю, как закодировать алгоритм "частичного совпадения".
4b9b3361

Ответ 1

Добро пожаловать в переполнение стека. Я не знаком с Python, но вижу, что вы еще не получили много ответов, поэтому я постараюсь помочь вам в ваших запросах.

Сначала совет: вы обнаружите, что если сосредоточить свои вопросы, ваши шансы получить ответы значительно улучшится. Ваш пост слишком длинный и содержит несколько разных вопросов, поэтому он не подходит для большинства людей, которые отвечают на вопросы здесь.

Вернуться к теме:

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

  • разбивать корпус на строки
  • Для каждой строки: найдите длину слога и шаблон напряжения.
  • Классифицируйте модели стресса.

Вы обнаружите, что первым шагом является один вызов функции в python:

corpus.split("\n");

и может оставаться в основной функции, но второй шаг будет лучше помещен в его собственную функцию, и третий шаг потребует разделения на себя и, вероятно, будет лучше решен с объектно-ориентированным подходом. Если вы находитесь в академии, вы можете убедить преподавательский состав CS предоставить вам пост-град на пару месяцев и помочь вам вместо того, чтобы выполнять некоторые требования к мастерству.

Теперь к вашим другим вопросам:

Не теряйте разрывы строк: как упоминал @ykaganovich, вы, вероятно, захотите разбить корпус на строки и передать их токенизатору.

Слова не в словаре/ошибках: Главная страница словаря CMU говорит:

Найти ошибку? Пожалуйста, свяжитесь с разработчиками. Мы рассмотрим проблему и улучшим словарь. (См. Ниже контактную информацию.)

Вероятно, есть способ добавить пользовательские слова в словарь/изменить существующие, посмотреть на их сайте или напрямую связаться со сторонними операторами. Вы также можете задать здесь отдельный вопрос, если вы не можете понять это. Там должен быть кто-то из stackoverflow, который знает ответ или может указать вам на правильный ресурс. Независимо от того, что вы решите, вы хотите связаться с сопровождающими и предложить им любые дополнительные слова и исправления в любом случае, чтобы улучшить словарь.

Классификация входного корпуса, когда он точно не соответствует шаблону: вы можете посмотреть ссылку ykaganovich, предназначенную для сравнения нечетких строк. Некоторые алгоритмы поиска:

  • Расстояние Левенштейна: дает вам представление о том, как разные две строки являются числом изменений, необходимых для превращения одной строки в другую. Плюсы: легко реализовать, Минусы: не нормировано, оценка 2 означает хорошее соответствие для шаблона длиной 20, но плохое совпадение для шаблона длины 3.
  • Показатель сходства строки Jaro-Winkler: похоже на Levenshtein, но на основе того, сколько последовательностей символов отображается в одном порядке в обеих строках. Это немного сложнее реализовать, но дает нормализованные значения (0.0 - совершенно разные, 1.0 - то же самое) и подходит для классификации шаблонов стресса. Преподаватель CS postgrad или прошлый год не должен иметь с ним слишком много проблем (подсказка подсказки).

Я думаю, что это были ваши вопросы. Надеюсь, это немного помогает.

Ответ 2

Чтобы сохранить новые строки, выполните синтаксический разбор строки за строкой перед отправкой каждой строки в парсер cmu.

Для работы с односложными словами вы, вероятно, захотите попробовать как 0, так и 1 для него, когда nltk вернет 1 (выглядит как nltk уже возвращает 0 для некоторых слов, которые никогда не получат стресс, например "the" ). Итак, вы получите множество перестановок: 1101111101 0101010101 1101010101

и т.д. Затем вам нужно выбрать те, которые выглядят как известные формы.

Для слов, не связанных с словарем, я также буду выдумывать их так же: выяснить количество слогов (самый тупой путь - подсчет гласных) и перестановить все возможные стрессы. Возможно, добавьте еще несколько правил, таких как "ea - это один слог, trailing e is silent"...

Я никогда не работал с другими видами fuzzying, но вы можете проверить https://stackoverflow.com/questions/682367/good-python-modules-for-fuzzy-string-comparison некоторые идеи.

Ответ 3

Это мой первый пост в stackoverflow. И я новичок в python, поэтому, пожалуйста, извините недостатки в стиле кода. Но я тоже пытаюсь извлечь точный метр из стихотворений. И код, включенный в этот вопрос, помог мне, поэтому я публикую то, что я придумал, основываясь на этом фундаменте. Это один из способов извлечь стресс в виде одной строки, исправить "фьюджинг-фактор" для смещения смеха и не потерять слова, которые не относятся к мучительному.

import nltk
from nltk.corpus import cmudict

prondict = cmudict.dict()

#
# parseStressOfLine(line) 
# function that takes a line
# parses it for stress
# corrects the cmudict bias toward 1
# and returns two strings 
#
# 'stress' in form '0101*,*110110'
#   -- 'stress' also returns words not in cmudict '0101*,*1*zeon*10110'
# 'stress_no_punct' in form '0101110110'


def parseStressOfLine(line):

    stress=""
    stress_no_punct=""
    print line

    tokens = [words.lower() for words in nltk.word_tokenize(line)] 
    for word in tokens:        

        word_punct =  strip_punctuation_stressed(word.lower())
        word = word_punct['word']
        punct = word_punct['punct']

        #print word

        if word not in prondict:
            # if word is not in dictionary
            # add it to the string that includes punctuation
            stress= stress+"*"+word+"*"
        else:
            zero_bool=True
            for s in prondict[word]:
                # oppose the cmudict bias toward 1
                # search for a zero in array returned from prondict
                # if it exists use it
                # print strip_letters(s),word
                if strip_letters(s)=="0":
                    stress = stress + "0"
                    stress_no_punct = stress_no_punct + "0"
                    zero_bool=False
                    break

            if zero_bool:
                stress = stress + strip_letters(prondict[word][0])
                stress_no_punct=stress_no_punct + strip_letters(prondict[word][0])

        if len(punct)>0:
            stress= stress+"*"+punct+"*"

    return {'stress':stress,'stress_no_punct':stress_no_punct}



# STRIP PUNCTUATION but keep it
def strip_punctuation_stressed(word):
    # define punctuations
    punctuations = '!()-[]{};:"\,<>./[email protected]#$%^&*_~'
    my_str = word

    # remove punctuations from the string
    no_punct = ""
    punct=""
    for char in my_str:
        if char not in punctuations:
            no_punct = no_punct + char
        else:
            punct = punct+char

    return {'word':no_punct,'punct':punct}


# CONVERT the cmudict prondict into just numbers
def strip_letters(ls):
    #print "strip_letters"
    nm = ''
    for ws in ls:
        #print "ws",ws
        for ch in list(ws):
            #print "ch",ch
            if ch.isdigit():
                nm=nm+ch
                #print "ad to nm",nm, type(nm)
    return nm


# TESTING  results 
# i do not correct for the '2'
line = "This day (the year I dare not tell)"
print parseStressOfLine(line)
line = "Apollo play'd the midwife part;"
print parseStressOfLine(line)
line = "Into the world Corinna fell,"
print parseStressOfLine(line)


""" 

OUTPUT 

This day (the year I dare not tell)
{'stress': '01***(*011111***)*', 'stress_no_punct': '01011111'}
Apollo play'd the midwife part;
{'stress': "0101*'d*01211***;*", 'stress_no_punct': '010101211'}
Into the world Corinna fell,
{'stress': '01012101*,*', 'stress_no_punct': '01012101'}