Дърво на вероятностите за изречения в nltk, използващо както предварителни, така и ретроспективни зависимости

Позволява ли nltk или друг NLP инструмент да се конструират вероятностни дървета въз основа на входни изречения, като по този начин се съхранява езиковият модел на входния текст в дърво на речника, следното example дава грубата идея, но имам нужда от същата функционалност, така че една дума Wt да не се моделира вероятностно само върху минали въведени думи (история) Wt-n, но също и върху думи за прогнозиране като Wt+m. Също така броят на думите за ретроспективен и предварителен преглед също трябва да бъде 2 или повече, т.е. биграми или повече. Има ли други библиотеки в python, които постигат това?

from collections import defaultdict
import nltk
import math

ngram = defaultdict(lambda: defaultdict(int))
corpus = "The cat is cute. He jumps and he is happy."
for sentence in nltk.sent_tokenize(corpus):
    tokens = map(str.lower, nltk.word_tokenize(sentence))
    for token, next_token in zip(tokens, tokens[1:]):
        ngram[token][next_token] += 1
for token in ngram:
    total = math.log10(sum(ngram[token].values()))
    ngram[token] = {nxt: math.log10(v) - total for nxt, v in ngram[token].items()}

решението изисква както поглед напред, така и преглед назад и специално подкласиран речник може да помогне при решаването на този проблем. Може също да посочи подходящи ресурси, които говорят за внедряване на такава система. Изглеждаше, че nltk.models прави нещо подобно, но вече не е наличен. Има ли някакви съществуващи дизайнерски модели в НЛП, които реализират тази идея? моделите, базирани на прескачане на грам, също са подобни на тази идея, но смятам, че това вече трябваше да бъде приложено някъде.


person stackit    schedule 13.08.2015    source източник
comment
Можете да повторите по този начин, за да получите желания отрязък: for t, token in enumerate(tokens): do_something(tokens[t-n:t+m])   -  person clemtoy    schedule 17.08.2015
comment
@clemtoy да, мога да направя това, но тогава как да го моделирам, за да изградя дърво?   -  person stackit    schedule 17.08.2015
comment
Можете ли да поясните какво имате предвид под дърво на вероятностите?   -  person CentAu    schedule 17.08.2015
comment
подобно на вероятностите на езиковия модел, както в горния пример, като се има предвид набор от последователни думи и неговия ляв и десен контекст, каква е вероятността това да е правилна последователност от думи? @CentAu   -  person stackit    schedule 17.08.2015


Отговори (2)


Ако разбирам правилно въпроса ви, вие търсите начин да предвидите вероятността за дадена дума, като се има предвид заобикалящият я контекст (не само обратен контекст, но и преден контекст). Един бърз хак за вашата цел е да обучите два различни езикови модела. Едната от дясно на ляво, а другата от ляво на дясно и след това вероятността за дума, дадена в нейния контекст, ще бъде нормализираната сума от предния и обратния контекст.

Разширяване на вашия код:

from collections import defaultdict
import nltk
from nltk.tokenize import word_tokenize
import numpy as np


ngram = defaultdict(lambda: defaultdict(int))
ngram_rev = defaultdict(lambda: defaultdict(int)) #reversed n-grams
corpus = "The cat is cute. He jumps and he is happy."

for sentence in nltk.sent_tokenize(corpus):
    tokens = map(str.lower, nltk.word_tokenize(sentence))
    for token, next_token in zip(tokens, tokens[1:]):
        ngram[token][next_token] += 1
    for token, rev_token in zip(tokens[1:], tokens):
        ngram_rev[token][rev_token] += 1
for token in ngram:
    total = np.log(np.sum(ngram[token].values()))
    total_rev = np.log(np.sum(ngram_rev[token].values()))
    ngram[token] = {nxt: np.log(v) - total 
                    for nxt, v in ngram[token].items()}
    ngram_rev[token] = {prv: np.log(v) - total_rev 
                    for prv, v in ngram_rev[token].items()}

Сега контекстът е както в ngram, така и в ngram_rev, които съответно държат контекстите напред и назад.

Трябва да отчетете и изглаждането. Тоест, ако дадена фраза не се вижда във вашия учебен корпус, вие просто ще получите нулеви вероятности. За да се избегне това, има много техники за изглаждане, най-простата от които е добавката изглаждане.

person CentAu    schedule 17.08.2015
comment
Всъщност контекстът трябва да е най-малко две думи от всяка страна, възможно е само търсене с униграми - person stackit; 18.08.2015
comment
Вие специално предоставихте кода за биграми и затова го разширих и на базата на биграми. Можете лесно да го обобщите до ngram чрез нещо като: for token, next_token, next_token_2 in zip (tokens, tokens[1:], tokens[2:]): ngram[token]['%s-%s' % (next_token, next_token_2)] += 1 - person CentAu; 19.08.2015
comment
той използва последните две думи като постоянен суфикс, като по този начин ограничава броя на валидните ngrams - person stackit; 21.08.2015
comment
можете ли да коментирате отговора си? - person stackit; 24.08.2015
comment
Можете да го промените, за да отчетете последните две думи отделно, напр. for token, next_token, next_token_2 in zip (tokens, tokens[1:], tokens[2:]): ngram[token][next_token] += 1; ngram[token]['%s-%s' % (next_token, next_token_2)] += 1 По този начин така или иначе добавяте следващия непосредствен контекст и предотвратявате проблема, който споменахте. - person CentAu; 24.08.2015
comment
защо не използвате вложен речник? - person stackit; 28.08.2015
comment
@stackit, тук използвах вложен dict (ngram е вложен). - person CentAu; 31.08.2015

Нормалният алгоритъм на ngram традиционно работи само с предварителен контекст и има основателна причина: биграмният маркер взема решения, като взема предвид таговете на последните две думи, плюс текущата дума. Така че, освен ако не маркирате в две преминавания, етикетът на следващата дума все още не е известен. Но вие се интересувате от думи ngrams, а не от ngrams етикети, така че нищо не ви пречи да обучите маркер за ngram, където ngram се състои от думи от двете страни. И вие наистина можете да го направите лесно с NLTK.

Всички ngram тагери на NLTK правят тагове ngrams, отляво; но можете лесно да извлечете свой собствен маркер от техния абстрактен базов клас, ContextTagger:

import nltk
from nltk.tag import ContextTagger

class TwoSidedTagger(ContextTagger):
        left = 2
        right = 1

    def context(self, tokens, index, history):
        left = self.left
        right = self.right
        tokens = tuple(t.lower() for t in tokens)
        if index < left:
            tokens = ("<start>",) * left + tokens
            index += left
        if index + right >= len(tokens):
            tokens = tokens + ("<end>",) * right

        return tokens[index-left:index+right+1]

Това дефинира маркер за тетраграма (2+1+1), където текущата дума е трета в ngram, а не последна, както обикновено. След това можете да инициализирате и обучите маркер точно като обикновените маркери на ngram (вижте глава 5 от книгата NLTK, особено раздели 5.4ff). Нека първо да видим как бихте изградили маркер за част от речта, като използвате част от корпуса на Браун като данни за обучение:

data = list(nltk.corpus.brown.tagged_sents(categories="news"))
train_sents = data[400:]
test_sents = data[:400]
twosidedtagger = TwoSidedTagger({}, backoff=nltk.DefaultTagger('NN'))
twosidedtagger._train(train_sents)

Подобно на всички ngram тагери в NLTK, този ще делегира на backoff tagger, ако бъде помолен да тагне ngram, който не е видял по време на обучението. За простота използвах прост "тагер по подразбиране" като маркер за отстъпване, но вероятно ще трябва да използвате нещо по-мощно (вижте отново главата за NLTK).

След това можете да използвате маркера си, за да маркирате нов текст или да го оцените с вече маркиран тестов набор:

>>> print(twosidedtagger.tag("There were dogs everywhere .".split()))
>>> print(twosidedtagger.evaluate(test_sents))

Предсказващи думи:

Горният маркер присвоява POS етикет, като взема предвид близките думи; но вашата цел е да предвидите самата дума, така че имате нужда от различни данни за обучение и различен тагер по подразбиране. NLTK API очаква данни за обучение във формата (word, LABEL), където LABEL е стойността, която искате да генерирате. Във вашия случай LABEL е само текущата дума; така че направете данните си за обучение, както следва:

data = [ zip(s,s) for s in nltk.corpus.brown.sents(categories="news") ]
train_sents = data[400:]
test_sents = data[:400]
twosidedtagger = TwoSidedTagger({}, backoff=nltk.DefaultTagger('the')) # most common word
twosidedtagger._train(train_sents)

Няма смисъл целевата дума да се появява в "контекстната" ngram, така че трябва също да промените метода context(), така че върнатата ngram да не го включва:

    def context(self, tokens, index, history):
        ...
        return tokens[index-left:index] + tokens[index+1:index+right+1]

Този маркер използва триграми, състоящи се от две думи отляво и една отдясно на текущата дума.

С тези модификации ще създадете маркер, който извежда най-вероятната дума на всяка позиция. Опитайте и както ви харесва.

Прогноза:

Моите очаквания са, че ще имате нужда от огромно количество данни за обучение, преди да можете да постигнете прилична производителност. Проблемът е, че маркерите на ngram могат да предложат етикет само за контексти, които са видели по време на обучението.

За да създадете тагер, който обобщава, обмислете използването на NLTK за обучение на „последователен класификатор“. Можете да използвате каквито функции искате, включително думите преди и след - разбира се, колко добре ще работи е ваш проблем. NLTK класификаторът API е подобен на този за ContextTagger, но контекстната функция (известна още като функционална функция) връща речник, а не кортеж. Отново вижте книгата за NLTK и изходния код.

person alexis    schedule 27.08.2015
comment
Благодаря. как това може да се използва за предсказване на следващата дума предвид контекста (и двете ляво и дясно)? какъв беше въпросът? - person stackit; 28.08.2015
comment
също така това просто маркира думите с POS етикет, по-добре го маркирайте с най-вероятната дума - person stackit; 28.08.2015
comment
Трябва да призная, че не разбирам какво приложение имате предвид: Ако вече знаете думите на позиции i+1, i+2 и т.н., как предвиждате думата на позиция аз? Но така или иначе, ако искате маркерът да произвежда думи, просто заменете данните за обучение с (word_i, word_i+1) кортежи. Това е едноредов, имате ли нужда от помощ с него? - person alexis; 28.08.2015
comment
приложението е основно средство за подсказване на думи\ коригиране, приемайки неправилно изречение, дадено с неправилна употреба на думи, граматика и т.н., коригирайте го. - person stackit; 28.08.2015
comment
как бихте използвали това, за да предложите най-вероятната дума за текущата дума, дайте и двете страни на контекста, както и текущата дума? - person stackit; 29.08.2015
comment
В следното, напр., как да обучите dwdiff изхода да получава предложения за грешки въз основа на посочения по-горе модел: Какво е {+a+} генетичен риск? Генетичният риск се отнася [-more-] до [-your-] шанса за наследяване на разстройство или заболяване. Хората получават [-определено заболяване-] {+заболявания+} поради генетични промени. Доколко една генетична промяна ни казва за шанса ви да развиете разстройство, не винаги е ясно. Ако вашите генетични резултати [-indicate-] {+indicated+}... - person stackit; 29.08.2015
comment
Можете ли да конструирате обучителен набор, който сдвоява всяка дума в корпус със следващата дума, или не? - person alexis; 29.08.2015
comment
Искам да кажа, добре, получавам това, за което го искаш. Но достатъчно ли е обяснението в коментара ми или не? - person alexis; 29.08.2015
comment
да, имам този комплект за обучение, който има съпоставяне между правилните думи и неправилните думи - person stackit; 29.08.2015
comment
можете ли да напишете пример как всъщност да го използвате за прогнозиране и оценка на точността? и генерира ли дърво вътрешно? - person stackit; 29.08.2015
comment
Генериране на дърво вътрешно? За какво говориш? Ревизирах кода, за да предвидя текущата дума на всяка позиция, вместо нейния POS, и да покажа как да създам данните за обучение. - person alexis; 29.08.2015
comment
така че използва ли вътрешно класификатор или чисто съвпадение на ngram? и за последователен класификатор как може да се приложи това? - person stackit; 31.08.2015
comment
може ли да използва стойностите на pos етикета на контекста? - person stackit; 31.08.2015
comment
Вашият въпрос беше как да направите маркер за ngram, който използва думи от двете страни, и как да го обучите да оценява текущата дума вместо POS етикет. Отговорих и на двете части. Прочетете отново моя отговор и книгата за NLTK за отговорите на вашите последващи въпроси. (Но да, можете да използвате и POS етикети) - person alexis; 31.08.2015
comment
И разбира се, ако сте заседнали с някой друг аспект от вашия проект, винаги сте насърчавани да зададете нов въпрос. - person alexis; 31.08.2015