.. -*- mode: rst -*-
.. include:: ../definitions.txt
=======
Tagging
=======
:Autores: Steven Bird, Ewan Klein, Edward Loper
:Contato: sb@csse.unimelb.edu.au
:Versão: |version|
:Revisão: $Revision: 4647 $
:Data: $Date: 2007-06-11 10:49:32 +1000 (Mon, 11 Jun 2007) $
:Copyright: |copy| |copyrightinfo|
:Licença: |license|
.. Note:: Este é um esboço. Por favor, envie seus comentários aos autores.
----------
Introdução
----------
Muitos enunciados de uma linguagem natural são ambíguos, obrigando-nos
a utilizar outras fontes de informação para ajudar na interpretação. Por
exemplo, a nossa interpretação do enunciado em inglês ``fruit flies like
a banana`` depende da presença de marcas contextuais que nos predispõem
a interpretar ``flies`` como um substantivo ou como um verbo. Antes mesmo
que possamos abordar esta discussão, é preciso que sejamos capazes de
representar as informações lingüísticas necessárias. Eis uma representação
possível:
========= ========= ======== ===== ==========
``Fruit`` ``flies`` ``like`` ``a`` ``banana``
nome verbo prep det nome
========= ========= ======== ===== ==========
========= ========= ======== ===== ==========
``Fruit`` ``flies`` ``like`` ``a`` ``banana``
nome nome verbo det nome
========= ========= ======== ===== ==========
A maioria dos sistemas de processamento lingüístico necessita
reconhecer e interpretar as estruturas lingüísticas que existem numa
seqüência de palavras. Esta tarefa é virtualmente impossível se tudo
que soubermos sobre cada palavra é sua representação textual. Para
determinar se uma dada string de palavras possui a estrutura de, por
exemplo, um sintagma nominal, é impraticável verificar uma lista
(potencialmente infinita) de todas as strings que podem ser
classificadas como sintagma nominal. Ao invés disso, queremos ter a
capacidade de generalizar quanto a *classes* de palavras. Estas classes
de palavras são normalmente "etiquetas" que reportam informações como
'artigo', 'adjetivo' ou 'substantivo'. De forma contrária, para
interpretar palavras somos obrigados a saber discriminar entre as
diferentes utilizações de uma mesma palavra, como ``deal`` que pode
ser utilizada tanto como substantivo quanto como verbo. O processo
de classificação de palavras neste sentido, e sua etiquetação em
conformidade com esta classificação, é conhecido por *tagging das
partes-do-discurso* (ou 'tagging de classes gramaticais'),
*POS-tagging* (a partir da abreviatura inglesa) ou simplesmente
*tagging*. O conjunto de tags utilizado para determinada tarefa é
conhecido por *tag set* (ou 'conjunto de tags').
Apresentamos anteriormente duas interpretações para *Fruit flies like
a banana*, exemplos de como uma string de palavras pode ser complementada
com informações referentes às classes gramaticais às quais suas
palavras pertencem. Na verdade, o que fizemos foi executar um processo
de tagging para a string ``fruit flies likes a banana``. Porém, as tags
são geralmente incluídas ao lado do texto aos quais estão associadas.
Isto é ilustrado pelo seguinte trecho extraído do Brown Corpus:
``The/at Pantheon's/np$ interior/nn ,/, still/rb in/in its/pp$ original/jj
form/nn ,/, is/bez truly/ql majestic/jj and/cc an/at architectural/jj
triumph/nn ./.``.
.. Conforme nossa tabela a seguir, ./. deveria ser, na verdade, ./end
Neste exemplo, a seqüência ``The/at`` indica que o toquen de palavra
``The`` carrega consigo a tag ``at``, que é a tag do Brown Corpus para
'artigo'. (O leitor pode ficar inicialmente perplexo com strings
como ``,/,``; isto simplesmente significa que a tag para a vírgula
é ``','``)
Podemos pensar no processo de tagging como uma forma de *anotar*
um corpus textual. Anotações são uma forma de adicionar-se informações
a um texto -- de fato, podemos pensar nestas como uma forma de tornar
explícitas informações já presentes implicitamente no texto.
Qual o propósito de anotar-se um texto desta forma? Uma demonstração é a
utilização de corpora com tags para estudar padrões de utilização de
palavras em diferentes gêneros literários (*estilística*). Por exemplo,
podemos utilizar as tags para identificar todas as palavras de uma
determinada classe, como os verbos modais em inglês, e então tabular suas
freqüências de ocorrência nos diferentes gêneros, como mostrado
abaixo:
================== === ===== === ===== ==== ====
Uso de verbos modais no Brown Corpus, por gênero
------------------------------------------------------
Gênero can could may might must will
================== === ===== === ===== ==== ====
skill and hobbies 273 59 130 22 83 259
humor 17 33 8 8 9 13
fiction: science 16 49 4 12 8 16
press: reportage 94 86 66 36 50 387
fiction: romance 79 195 11 51 46 43
religion 84 59 79 12 54 64
================== === ===== === ===== ==== ====
Este tutorial foca-se no tagging de partes-do-discurso, por ser um passo
inicial no processamento lingüístico que não depende de uma profunda
análise lingüística. Os leitores devem lembrar-se de que existem muitos
outros tipos de tagging. As palavras podem receber tags com diretivas
para um sintetizador de voz, indicando quais palavras deveriam ser
enfatizadas. As palavras também podem receber tags com índices numéricos
de sentido, referendio-se a qual sentido está sendo utilizado naquela
ocorrência. As palavras também podem receber tags referentes a
propriedades morfológicas. Exemplos de cada um destes tipos de tags são
mostrados abaixo. Note que por motivos de espaço somente reportamos a
tag da palavra em itálico. Note também que os dois primeiros exemplos
utilizam tags no estilo XML, no qual os elementos entre sinais de maior
e menor cercam a palavra à qual a tag se refere.
1. *Speech Synthesis Markup Language (W3C SSML):*
``That is a *big* car!``
#. *SemCor: Brown Corpus tagged with WordNet senses:*
``Space in any *form*
is completely measured by the three dimensions.``
(Wordnet form/nn sense 4: "shape, form, configuration,
contour, conformation")
#. *Morphological tagging, from the Turin University Italian Treebank:*
``E' italiano , come progetto e realizzazione , il
*primo* (PRIMO ADJ ORDIN M SING) porto turistico dell' Albania .``
Já dissemos que as tags referentes às partes-do-discurso estão em forte
relação com a noção de classe gramatical utilizada na sintaxe. O princípio
assumido pela lingüística teórica é de que todo tipo distinto de palavra
estará listado em um léxico (ou dicionário), com informações quanto à
sua pronúncia, propriedades sintáticas e significado. Uma componente
chave das propriedades sintáticas de uma palavra será a classe à qual
ela pertence. Quando realizamos uma análise sintática de nosso exemplo
anterior ``fruit flies like a banana``, procuramos por cada palavra em
nosso léxico, determinamos qual é sua classe gramatical e finalmente
a agrupamos em uma hierarquia de frases, como ilustrado na árvore de
análise ('parse tree') a seguir.
.. figure:: ../images/syntax-tree.png
:scale: 1
Syntactic Parse Tree
Nesta árvore, utilizamos as abreviações sintáticas padrão para as
classes gramaticais. Estas são listadas na tabela a seguir, juntamente
a seus correspondentes no tag-set do Brown Corpus.
============================= ========= ===========================
Etiquetas de classes gramaticais e tags do Brown Corpus
-------------------------------------------------------------------------
Etiqueta de classe gramatical Tag Brown Classe gramatical
============================= ========= ===========================
Det at Artigo
N nn Substantivo
V vb Verbo
Adj jj Adjetivo
P in Preposição
Card cd Numeral
- end Pontuação de final de frase
============================= ========= ===========================
Em análises sintáticas, há normalmente uma estreita correlação entre a
classe à qual uma palavra pertence e as frases e sintagmas que esta forma
com as palavras vizinhas. Assim, por exemplo, um sintagma nominal (designado
por NP) consiste normalmente de um substantivo (N), opcionalmente
acompanhado por um artigo (Det) e alguns modificadores como adjetivos
(Adj). Podemos expressar esta generalização em termos de uma regra de produção:
NP |rarr| Det Adj* N
Nestes casos, dizemos que o substantivo é o *head* (em português 'cabeça'
ou 'chefe', mas no Brasil costuma-se utilizar a terminologia inglesa) do
sintagma nominal; basicamente podemos dizer que o head de um sintagma é
o elemento essencial que pode ocorrer em qualquer contexto em que o
sintagma como um todo pode ocorrer. Porém, de um ponto de vista
puramente notacional, temos liberdade para utilizar qualquer etiqueta
que desejarmos para as classes de palavras e estas podem ser as tags
fornecidas pelo tag-set. Por exemplo, poderíamos substituir a regra
anterior pela seguinte:
NP |rarr| at jj* nn
Isto é conveniente, pois em algumas tarefas práticas podemos utilizar
um tagger automático para etiquetar as palavras de nosso exemplo com
as tags retiradas do tag-set Brown, e utilizar o resultado como base
para a construção de uma árvore de análises sintática como vimos
acima.
Até agora, discorremos apenas sobre as tags utilizadas para referir-se
a informações quanto à classe das palavras. Porém, tag-sets comuns como
aqueles utilizados no Brown Corpus costumam incluir também informações
de cunho *morfo-sintático*. Considere, por exemplo, a seleção de formas
distintas da palavra ``be`` ilustrada nas sentenças a seguir:
Be still!
Being still is hard.
I am still.
I have been still all day.
I was still all day.
Dizemos que estas formas são morfo-sintáticamente distintas pois elas
apresentam diferentes flexões morfológicas e diferentes restrições
co-ocorrentes com as palavras vizinhas. Por exemplo, ``am`` não pode
substituir nenhum dos dois primeiros exemplos:
\*Am still!
\*Am still is hard.
Estas diferenças entre as formas são codificadas em suas tags do
Brown Corpus: ``be/be, being/beg, am/bem, been/ben`` e ``was/bedz``.
Isto significa que um tagger automático que utilize este tag-set está
na verdade efetuando uma limitada análise morfológica.
---------------
Taggers simples
---------------
Nesta seção iremos abordar três taggers simples. Todos eles processam
os toquens fornecidos ('input tokens') um a um, adicionando uma tag a
cada toquen. Em todos os casos eles trabalham sobre texto toquenizado.
Podemos criar facilmente um exemplo de texto toquenizado da seguinte
forma:
>>> from nltk import tokenize
>>> text = "John saw 3 polar bears ."
>>> tokens = list(tokenize.whitespace(text))
>>> print tokens
['John', 'saw', '3', 'polar', 'bears', '.']
.. Note:: O toquenizador é um *gerador* de tokens. Não podemos
exibí-lo diretamente, mas podemos convertê-lo em uma lista para
exibição, como mostrado no programa acima. Note como podemos utilizar
um gerador uma única vez, mas se o salvarmos na forma lista podemos
utilizar esta última quantas vezes for necessário.
O Default Tagger (Tagger por defeito)
-------------------------------------
O tagger mais simples possível atribui uma mesma tag a qualquer toquen
que lhe seja fornecindo, não importando o texto do toquen. A classe
``DefaultTagger`` implementa este tipo de tagger. No programa abaixo,
iremos criar um tagger chamado ``my_tagger`` que marca qualquer toquen
como sendo um substantivo.
>>> from nltk import tag
>>> my_tagger = tag.Default('nn')
>>> list(my_tagger.tag(tokens))
[('John', 'nn'), ('saw', 'nn'), ('3', 'nn'), ('polar', 'nn'), ('bears', 'nn'), ('.', 'nn')]
Trata-se de um algoritmo muito simples e que fornece resultados
precários quando usado isoladamente. Em um corpus típico, apenas
20-30% dos tokens receberão uma tag adequada. É porém razoável utilizar
este tipo de tagger como "tagger por defeito" (ou seja, como última
alternativa), se taggers mais sofisticados falharem em determinar a
tag correta para um toquen. Quando usado em conjunto com outros taggers,
um ``DefaultTagger`` pode melhorar significativamente o desempenho.
.. Important:: Default Taggers atribuem sua tag para qualquer palavra,
mesmo àquelas que nunca foram encontradas antes. Assim, eles ajudam
a tornar o sistema de processamento lingüístico mais sólido. Iremos
abordá-los novamente a seguir, no contexto de nossa discussão
sobre o *backoff*.
O Tagger por Expressões Regulares
---------------------------------
O tagger por expressões regulares atribui tags aos toquens com base
em padrões ('matching patterns') aplicados ao texto dos toquens. Por
exemplo, o tagger a seguir atribui a tag ``cd`` aos numerais cardinais e
``nn`` aos demais itens::
>>> patterns = [(r'^[0-9]+(.[0-9]+)?$', 'cd'), (r'.*', 'nn')]
>>> nn_cd_tagger = tag.Regexp(patterns)
>>> list(nn_cd_tagger.tag(tokens))
[('John', 'nn'), ('saw', 'nn'), ('3', 'cd'), ('polar', 'nn'), ('bears', 'nn'), ('.', 'nn')]
Podemos generalizar este método para estimar qual a tag correta para uma
palavra com base na presença de prefixos ou sufixos. Por exemplo, as
palavras em inglês que começam por ``un-`` são provavelmente adjetivos.
O Unigram Tagger
------------------
A classe ``UnigramTagger`` implementa um algoritmo estatístico simples
para o processo de tagging: a cada toquen é atribuída a tag mais provável
em função de seu texto. Por exemplo, este tipo de tagger irá atribuir a
tag ``jj`` a qualquer ocorrência da palavra ``frequent``, pois esta aparece
mais freqüentemente como um adjetivo (como em ``a frequent word``) do que
como um verbo (como em ``I frequent this cafe``).
Antes que um ``UnigramTagger`` possa ser utilizado no processo de tagging
é necessário trainá-lo em *corpus de treinamento*. O tagger irá utilizar
este corpus para determinar qual a tag mais comum para cada palavra. Os
``UnigramTaggers`` são treinados usando-se o método ``train()``, que
recebe como argumento um corpus com informações de tags::
>>> from nltk.corpora import brown
>>> from itertools import islice
>>> train_sents = list(islice(brown.tagged(), 500)) # sents 0..499
>>> unigram_tagger = tag.Unigram()
>>> unigram_tagger.train(train_sents)
Uma vez que um ``UnigramTagger`` tenha sido treinado, o método ``tag()``
pode ser utilizado no processo de tagging de novos textos::
>>> text = "John saw the book on the table"
>>> tokens = list(tokenize.whitespace(text))
>>> list(unigram_tagger.tag(tokens))
[('John', 'np'), ('saw', 'vbd'), ('the', 'at'), ('book', None), ('on', 'in'), ('the', 'at'), ('table', None)]
Como dissemos anteriormente, um ``UnigramTagger`` atribuirá a tag padrão
``None`` ("nada") a qualquer token que não tenha sido encontrado durante
o treinamento. Podemos instruí-lo a *backoff* para nosso tagger
``nn_cd_tagger`` nestes casos em que não é capaz de atribuir uma tag
por conta própria::
>>> unigram_tagger = tag.Unigram(backoff=nn_cd_tagger)
>>> unigram_tagger.train(train_sents)
>>> list(unigram_tagger.tag(tokens))
[('John', 'np'), ('saw', 'vbd'), ('the', 'at'), ('book', 'nn'), ('on', 'in'), ('the', 'at'), ('table', 'nn')]
Temos agora certeza de que todas as palavras receberam tags.
Os Affix Taggers
----------------
Os Affix Taggers são similares aos Unigram Taggers, diferindo destes pelo
fato de serem treinados sobre prefixos ou sufixos de comprimentos
pré-determinados (lembre-se de que neste caso estamos usando *prefixo* e
*sufixo* no sentido de partes de uma strings, e não no sentido
morfológico). Por exemplo, o tagger a seguir irá considerar sufixos
de três letras (como *-ize* e *-ion*) para palavras que tenham ao menos
cinco letras de comprimento.
>>> affix_tagger = Affix(-3, 5, backoff=unigram_tagger)
-----------------
Avaliando taggers
-----------------
Conforme testamos diferentes tipos de taggers, é importante dispormos de
um método objetiva para medição de desempenhos. Por sorte já dispomos de
um corpus de treinamento verificado manualmente (o corpus com informações
de tags original) que poderemos utilizar para avaliar nossos taggers.
Considere as sentenças a seguir retiradas do Brown Corpus. As tags 'Gold
Standard' ('Padrão de Ouro', as tags corretas) do corpus são reportadas
na segunda coluna, enquanto as tags atribuídas pelo Unigram Tagger
aparecem na terceira coluna. Os dois erros cometidos pelo Unigram Tagger
são assinalados em itálico.
=============== ============= ==============
Avaliando taggers
----------------------------------------------
Sentença Gold Standard Unigram Tagger
=============== ============= ==============
The at at
President nn-tl nn-tl
said vbd vbd
he pps pps
will md md
ask vb vb
Congress np np
to to to
increase vb *nn*
grants nns nns
to in *to*
states nns nns
for in in
vocational jj jj
rehabilitation nn nn
. . .
=============== ============= ==============
O tagger atribuiu tags corretas a 14 das 16 palavras, resultando em um
índice de acerto de 14/16 ou 87,5%. Evidentemente, o desempenho deveria
ser avaliado com base em um conteúdo maior de dados. O NLTK disponibiliza
uma função chamada ``tag.accuracy`` que automatiza esta tarefa. No caso
mais simples, podemos testar o tagger utilizando o mesmo conjunto de
dados sobre o qual ele foi treinado:
>>> acc = tag.accuracy(unigram_tagger, train_sents)
>>> print 'Accuracy = %4.1f%%' % (100 * acc)
Accuracy = 84.7%
Todavia, testar um sistema de processamento lingüístico sobre os mesmos
dados que foram utilizados para seu treinamento não é muito sábio. Um
sistema que simplesmente memorizasse os dados de treinamento obteria um
resultado perfeito sem realizar qualquer tipo de modelagem lingüística.
Ao invés disso, é importante valorizar sistemas que consigam traçar
boas generalizações, de tal forma que possamos aplicá-los a *dados
não-vistos* e substituir o ``train_sents`` ('tokens de treinamento', em
inglês) acima por ``unseen_sents`` ('tokens não-vistos', em inglês).
Podemos então definir dois conjuntos de dados como a seguir:
>>> train_sents = islice(brown.tagged('a'))[:500]
>>> unseen_sents = islice(brown.tagged('a'))[500:600] # sents 500-599
Agora é possível treinar o tagger utilizando o ``train_sents`` e avaliar
seu desempenho utilizando o ``unseen_sents``, como mostrado a seguir:
>>> unigram_tagger = tag.Unigram(backoff=nn_cd_tagger)
>>> unigram_tagger.train(train_sents)
>>> acc = tag.accuracy(unigram_tagger, unseen_sents)
>>> print 'Accuracy = %4.1f%%' % (100 * acc)
Accuracy = 74.7%
Os resultados de desempenho produzidos por este método de avaliação são
inferiores mas fornecem-nos uma imagem mais realista do desempenho do
tagger. Note que o desempenho de qualquer tagger estatístico depende
em grande parte da qualidade dos dados utilizados para treiná-lo. Em
especial, se a quantidade de dados de treinamento for pequena, o tagger
não será capaz de estimar a tag mais provável para cada palavra de forma
confiável. O desempenho também sofrerá se os dados de treinamento
forem significativamente diferentes dos textos aos quais desejamos aplicar
o processo de tagging.
Durante o processo de desenvolvimento de um tagger, podemos utilizar o
índice de desempenho como uma medida objetiva das melhorias aportadas
ao sistema. Inicialmente, o índice de desempenho crescerá rapidamente a
medida que solucionamos problemas óbvios no tagger. Após certo tempo,
porém, as melhorias tornam-se mais difíceis e o índice cresce pouco.
Mesmo sendo o índice de desempenho extremamente útil, ele não nos informa
sobre como melhorar o tagger. Para isto é necessário que realizemos uma
análise de erro. Por exemplo, podemos construir uma *matriz de confusão*, com
uma linha e uma coluna para cada tag possível, e entradas que armazenem
o quão freqüentemente a tag *T* :subscript:`i` é incorretamente utilizada
no lugar de *T* :subscript:`j`. Outra abordagem é uma análise do contexto
dos erros.
>>> errors = {}
>>> for i in range(len(unseen_sents)):
... raw_sent = tag.untag(unseen_sents[i])
... test_sent = list(unigram_tagger.tag(raw_sent))
... unseen_sent = unseen_sents[i]
... for j in range(len(test_sent)):
... if test_sent[j][1] != unseen_sent[j][1]:
... test_context = test_sent[j-1:j+1]
... gold_context = unseen_sent[j-1:j+1]
... if None not in test_context:
... pair = (tuple(test_context), tuple(gold_context))
... errors[pair] = errors.get(pair, 0) + 1
O programa acima cataloga todos os erros juntamente com a tag à esquerda
de cada um (a tag da palavra que os antecede) e suas freqüências de
ocorrência. O dicionário ``errors`` possui entradas na forma
``((t1,t2),(g1,g2))``, nas quais ``(t1,t2)`` são as tags de teste e
``(g1,g2)`` são as tags do Gold Standard. Os valores armazenados no
dicionário ``errors`` são simples contagens da freqüência com a qual os
erros são encontrados. Com algum processamento adicional, podemos
construir a lista ``counted_errors`` contendo tuples que consistam das
contagens e dos erros, revertendo sua ordem ao final para obter os
erros mais significativos primeiro:
>>> counted_errors = [(errors[k], k) for k in errors.keys()]
>>> counted_errors.sort()
>>> counted_errors.reverse()
>>> for err in counted_errors[:5]:
... print err
(32, ((), ()))
(5, ((('the', 'at'), ('Rev.', 'nn')), (('the', 'at'), ('Rev.', 'np'))))
(5, ((('Assemblies', 'nn'), ('of', 'in')), (('Assemblies', 'nns-tl'), ('of', 'in-tl'))))
(4, ((('of', 'in'), ('God', 'nn')), (('of', 'in-tl'), ('God', 'np-tl'))))
(3, ((('to', 'to'), ('form', 'nn')), (('to', 'to'), ('form', 'vb'))))
A quinta linha do output (saída, resultado do programa) demonstra o fato
de que há 3 casos em que o Unigram Tagger atribuiu errôneamente a tag de verbo
a um substantivo que seguia a palavra ``to``. (Encontramos o inverso deste
erro para a palavra ``increase`` na tabela de avaliação acima, na qual
o Unigram Tagger havia marcado ``increase`` como um verbo ao invés de um
substantivo, pois esta ocorre mais freqüentemente nos dados de treinamento
como um verbo) Aqui, quando ``form`` segue a palavra ``to``, esta é
invariavelmente um verbo. Evidentemente, o desempenho do tagger melhoraria
se este fosse modificado para que passasse a considerar não apenas a palavra
que está sendo processada a cada momento, mas também a tag já atribuída à
palavra que a antecede. Este tipo de tagger é conhecido por Bigram Tagger e
será o próximo tipo a ser abordado.
---------------------------------------------------
Os Taggers de Ordem Superior (Higher Order Taggers)
---------------------------------------------------
Nas seções anteriores estudamos o ``UnigramTagger``, que atribui uma tag
a uma palavra com base na identidade desta palavra. Nesta seção iremos
considerar os taggers que adoperam uma quantidade maior de contexto durante
o processo de atribuição de uma tag.
Bigram Taggers
--------------
Como seu nome sugere, os bigram taggers utilizam
dois pedaços de informação para cada decisão quanto ao tagging.
Normalmente esta informação é constituída pelo texto da palavra que
está sendo analisada no momento e a tag da palavra anterior. Estes dois
pedaços de informação constituem o contexto para o
toquen ao qual será atribuída uma tag. Dado o contexto, o tagger busca
atribuir a tag mais provável. Podemos visualizar este processo com
a ajuda da tabela de bigramas abaixo, um pequeno fragmento da estrutura
interna de dados que é construída por um Bigram Tagger.
==== ==== ========= ==== ======== ====== ==== ======
Fragmento de uma tabela de bigramas
--------------------------------------------------------------
ask Congress to increase grants to states
==== ==== ========= ==== ======== ====== ==== ======
at nn
tl to to
bd to nns to
md *vb* vb
vb *np* to *nns* to nns
np *to* to
to vb *vb*
nn np to nn nns to
nns to *to*
in np in in *nns*
jj to nns to nns
==== ==== ========= ==== ======== ====== ==== ======
.. source code for bigram table
from nltk.tagger import *
from nltk.corpus import brown
train_tokens = []
for item in brown.items()[:10]:
train_tokens.append(brown.read(item))
mytagger = tag.ngram(2)
for tok in train_tokens: mytagger.train(tok)
words = '''ask Congress to increase grants to states'''.split()
tags = '''at nn-tl vbd md vb np to nn nns in jj'''.split()
print " ",
for word in words:
print " %s " % word,
print
for tag in tags:
print "%5s" % tag,
for word in words:
guess = mytagger._freqdist[((tag,), word)].max()
if not guess: guess=""
print " %s " % guess,
print
A melhor forma para se entender esta tabela é trabalhar com esta
sobre um exemplo. Suponha que já tenhamos processado a sentença
``The President will ask Congress to increase grants to states for
vocational rehabilitation .`` até a altura do toquen ``will/md``.
Podemos utilizar esta tabela para entender quais tags seriam atribuídas
ao restante da sentença. Quando antecedida por ``md``, o tagger considera
que a palavra ``ask`` deve receber a tag ``vb`` (em itálico na tabela).
Continuando para a próxima palavra, sabemos que a tag anterior será
agora ``vb``, e buscando por esta linha podemos encontrar ``Congress``
com a atribuição da tag ``np``. O processo segue até o final da frase.
No momento em que a palavra ``increase`` é encontrada, atribuímos-lhe
corretamente a tag ``vb`` (ao contrário do Unigram Tagger que a
marcaria como ``nn``). Ainda assim, o Bigram Tagger errôneamente atribui
a tag de "infinitivante" (ou seja, que em língua inglesa torna o verbo
sucessivo um verbo no infinitivo) à palavra ``to`` que antecede ``states``
e não a tag correta que indica tratar-se de uma preposição. Isto sugere
que pode ser necessário considerar um contexto ainda maior para obter
a tag correta.
-----------------------------------
Taggers de ordem-N (N-Gram Taggers)
-----------------------------------
Como acabamos de ver, pode ser interessante considerar mais dados além
da tag da palavra antecedente durante um processo de taggin. Um *tagger
de ordem N* é uma generalização de um Bigram Tagger, cujo contexto é o
texto do toquen atual juntamente com as tags dos *n* toquens anteriores,
como mostrado no diagrama abaixo. O tagger seleciona a tag mais provável
com base nas informações deste contexto. A tag a ser escolhida, *t*
:subscript:`k`, está circulada e o contexto está hachurado em tons de
cinza. Neste exemplo de tagger de ordem N, temos *n=3*; em outras palavras,
estamos inspecionando as duas palavras que antecedem a atual.
.. figure:: ../images/tag-context.png
:scale: 1
Contexto do tagger
.. note:: Um tagger de ordem 1 é outra denominação para o Unigram Tagger,
ou seja o contexto utilizando para atribuir uma tag a um toquen é
apenas o texto do próprio. Taggers de ordem 2 também são conhecidos
por *Bigram Taggers* e taggers de ordem 3 por *trigram taggers*.
Definimos, assim, os bigram e trigram taggers como casos especiais
dos taggers de ordem N.
``tag.Ngram`` utiliza um corpus de treinamento com tags para determinar
qual tag de parte-do-discurso é a mais provável para cada contexto.
Aqui vemos um caso especial do tagger de ordem-N, chamado de
Bigram Tagger::
>>> bigram_tagger = tag.Bigram()
>>> bigram_tagger.train(brown.tagged(['a','b']))
Uma vez que o Bigram Tagger tenha sido treinado, podemos utilizá-lo para
atribuir tags a corpora ainda sem estas informações:
>>> text = "John saw the book on the table"
>>> tokens = list(tokenize.whitespace(text))
>>> list(tagger.tag(tokens))
[('John', 'np'), ('saw', 'vbd'), ('the', 'at'), ('book', 'nn'), ('on', 'in'), ('the', 'at'), ('table', None)]
Como os outros taggers considerados anteriormente, os taggers de ordem
N também atribuirão a tag padrão ``None`` a qualquer toquen cujo contexto
não tenha sido encontrado em seus dados de treinamento. Note que, à medida
que *n* aumenta, a especificidade do contexto também aumenta e, com esta,
as possibilidades de que os dados que desejamos treinar contenham contextos
que não estão presentes nos dados de treinamento. Este problema é
normalmente conhecido por *dados esparsos*. Assim, é necessário encontrar
um eqüilíbrio entre os índices de acerto e abrangência de nossos
resultados. Este é um fator habitual no processamento de linguagem natural.
Ele está intimamente relacionado ao *problema do precision/recall* que
encontraremos depois ao discutirmos a obtenção de informações.
.. Note:: taggers de ordem N não deveriam considerar contextos que
ultrapassam os limites das sentenças. Em conformidade com isto, os
taggers do NLTK foram projetados para trabalhar com listas de
sentenças, na qual cada sentença constitui uma lista de palavras,
um estrutura duplamente aninhada.
------------------
Combinando taggers
------------------
One way to address the trade-off between accuracy and coverage is to
use the more accurate algorithms when we can, but to fall back on
algorithms with wider coverage when necessary. For example, we could
combine the results of a bigram tagger, a unigram tagger, and
a ``NN_CD_Tagger``, as follows:
Uma forma de lidarmos com o trade-off entre índice de acerto e extensão é
utilizar os algoritmos mais precisos quando pudermos, mas permitindo-se
utilizar algoritmos de abordagem mais ampla quando necessário. Por exemplo,
poderíamos combinar os resultados de um Bigram Tagger, de um Unigram
Tagger e de um ``nn_cd_tagger`` da seguinte forma:
1. Tenta-se atribuir uma tag ao toquen com o Bigram Tagger.
2. Se o Bigram Tagger for incapaz de encontrar uma tag para o toquen,
tenta-se encontrar uma com o Unigram Tagger.
3. Se o Unigram Tagger também é incapaz de encontrar uma tag,
utiliza-se o tagger padrão para encontrar uma tag.
Cada tagger do NLTK permite que se especifique um backoff-tagger.
O backoff-tagger também pode, por sua vez, ter um backoff-tagger::
>>> t0 = tag.Default('nn')
>>> t1 = tag.Unigram(backoff=t0)
>>> t2 = tag.Bigram(backoff=t1)
>>> t1.train(brown.tagged('a'))
>>> t2.train(brown.tagged('a'))
.. Note:: Determina-se o backoff tagger no momento da inicialização do
tagger, de tal forma que o treinamento possa valer-se do backing off.
Assim, caso o Bigram Tagger e seu Unigram Tagger de backoff fossem
atribuir a mesma tag em um contexto específico, a etapa de treinamento
é descartada. Isto mantém o modelo do Bigram Tagger na menor extensão
possível. Adicionalmente, podemos determinar que um tagger necessite
de mais de uma ocorrência de um contexto para mantê-lo, por exemplo
com ``Bigram(cutoff=2, backoff=t1)``, que desconsideraria contextos
encontrados apenas uma ou duas vezes.
Podemos agora testar os taggers::
>>> accuracy0 = tag.accuracy(t0, unseen_sents)
>>> accuracy1 = tag.accuracy(t1, unseen_sents)
>>> accuracy2 = tag.accuracy(t2, unseen_sents)
>>> print 'Default Accuracy = %4.1f%%' % (100 * accuracy0)
Default Accuracy = 13.1%
>>> print 'Unigram Accuracy = %4.1f%%' % (100 * accuracy1)
Unigram Accuracy = 87.9%
>>> print 'Bigram Accuracy = %4.1f%%' % (100 * accuracy2)
Bigram Accuracy = 85.2%
--------------
O Brill Tagger
--------------
Um problema em potencial com taggers de ordem N é seu tamanho. Se o
processo de tagging vier a ser desempenhado por uma variedade de
tecnologias da linguagem em dispositivos portáteis de computação, é
importante encontrar formas de reduzir o tamanho dos modelos sem
comprometer demasiadamente o desempenho. Uma combinação de diferentes
taggers de ordem N pode armazenar tabelas de trigram e bigram, enormes
matrizes esparsas que podem conter centenas de milhões de entradas. Como
vimos na tabela de bigramas acima, modelos de ordem N apenas consideram
as tags das palavras dentro de determinado contexto. Uma conseqüência do
tamanho destes modelos é simplesmente a impraticabilidade de se
condicionar modelos de ordem N quanto às características das palavras
no contexto. Nesta seção iremos examinar o método Brill para tagging, um
método de tagging estatístico que apresenta um desempenho muito bom
utilizando modelos que correspondem a uma pequena fração do tamanho de
taggers de ordem N.
O Brill tagging é um tipo de *aprendizagem baseada em transformações*
("transformation-based learning", em inglês). A idéia geral é bastante
simples: primeiramente, atribui-se a tag mais provável a cada palavra
e então retorna-se ao início para corrigir os erros. Desta forma, um
Brill Tagger transforma aos poucos um texto com tags incorretamente marcadas
em um com tags corretamente marcadas. Como no processo de tagging por
ordem N, trata-se de um método de *aprendizagem supervisionada*
("supervised learning", em inglês), já que é necessário um conjundo de
dados anotados sobre os quais efetuar o treinamento. Porém, ao contrário
do tagging de ordem N, ele não efetua uma contagem das observações mas
compila uma lista de regras de correção transformacionais.
O processo de Brill Tagging é geralmente explicado por meio de uma
analogia com a pintura. Imagine que estejamos pintando uma árvore, com
todos seus detalhes de ramos, galhos e folhas sobre um fundo uniforme
de cor azul-celeste. Ao invés de pintarmos primeiro a árvore e então pintar
em azul os espaços entre seus ramos, é muito mais simples pintar a
tela inteira de azul e então "corrigir" nesta a seção da árvore, pintando
sobre o fundo azul. Da mesma forma podemos pintar o tronco com um marrom
uniforme antes de voltar para pincelar sobre este detalhes mais específicos.
O Brill Tagging vale-se da mesma idíea: obtém-se inicialmente o "grosso"
da pintura com pinceladas bruscas e então cuida-se dos detalhes. Com o
andamento do processo, pincéis cada vez menores são utilizados e a escala
das mudanças torna-se arbitrariamente pequena. A decisão quanto ao momento
certo para parar também é, de certa forma, arbitrária. A tabela a seguir
ilustra este processo utilizando inicialmente um unigram tagger e
corrigindo os erros logo após.
============== ======= ======== ===================================== =================================
Passos no Brill Tagging
-----------------------------------------------------------------------------------------------------------
Sentence: Gold: Unigram: Substitui ``nn`` por ``vb`` Substitui ``to`` por ``in``
quando a palavra anterior for ``to`` quando a tag a seguir for ``nns``
The at at
President nn-tl nn-tl
said vbd vbd
he pps pps
will md md
ask vb vb
Congress np np
to to to
increase vb *nn* *vb*
grants nns nns
to in *to* *to* *in*
states nns nns
for in in
vocational jj jj
rehabilitation nn nn
============== ======= ======== ===================================== =================================
Nesta tabela vemos duas regras. Todas estas regras são geradas a partir
de um modelo com a seguinte forma: "substitui *T* :subscript:`1` por
*T* :subscript:`2` no contexto *C*".Contextos típicos são a identidade
ou a tag das palavras que precedem ou seguem a atual ou a presença de
determinada tag num intervalo de 2-3 palavras de distância da atual.
Durante sua etapa de treinamento, o tagger estima os valores para
*T* :subscript:`1`, *T* :subscript:`2` e *C* para criar milhares de
regras em potencial. A cada regra é então atribuído um coeficiente de
desempenho baseado em seu sucesso global: o número de tags incorretas que
ele corrige menos o número de tags corretas que ele incorretamente
modifica. Este processo é ilustrado melhor por meio da listagem do output
do Brill Tagger do NLTK (aqui sendo executado no corpus com tags do
Wall Street Journal distribuído com o Penn Treebank). [#]_
.. [#] Agradecemos a Christopher Maloof por desenvolver este Brill Tagger
para o NLTK.
::
Loading tagged data...
Training unigram tagger: [accuracy: 0.820940]
Training Brill tagger on 37168 tokens...
Iteration 1: 1482 errors; ranking 23989 rules;
Found: "Replace POS with VBZ if the preceding word is tagged PRP"
Apply: [changed 39 tags: 39 correct; 0 incorrect]
Iteration 2: 1443 errors; ranking 23662 rules;
Found: "Replace VBP with VB if one of the 3 preceding words is tagged MD"
Apply: [changed 36 tags: 36 correct; 0 incorrect]
Iteration 3: 1407 errors; ranking 23308 rules;
Found: "Replace VBP with VB if the preceding word is tagged TO"
Apply: [changed 24 tags: 23 correct; 1 incorrect]
Iteration 4: 1384 errors; ranking 23057 rules;
Found: "Replace NN with VB if the preceding word is to"
Apply: [changed 67 tags: 22 correct; 45 incorrect]
...
Iteration 20: 1138 errors; ranking 20717 rules;
Found: "Replace RBR with JJR if one of the 2 following words is tagged NNS"
Apply: [changed 14 tags: 10 correct; 4 incorrect]
Iteration 21: 1128 errors; ranking 20569 rules;
Found: "Replace VBD with VBN if the preceding word is tagged VBD"
[insufficient improvement; stopping]
Brill accuracy: 0.835145
---------
Conclusão
---------
Este capítulo introduziu a tarefa de processamento lingüístico conhecida
por tagging, com ênfase no tagging das partes do discurso. Classes de
palavra da língua inglesa e suas tags correspondentes foram apresentadas.
Mostramos como tokens com tags e corpora com tags podem ser
representados e discutimos uma série de taggers: default tagger, taggers
por expressões regulares, unigram taggers, taggers de ordem N e o
Brill tagger. Também descrevemos alguns métodos de avaliação objetivos. No
processo, o leitor foi introduzido a dois importantes paradigmas do
processamento lingüístico, a *modelagem lingüística* e a *aprendizagem
baseada em transformações*. A primeira é extremamente geral e será
discutida novamente nos próximos capítulos. A última foi adaptada
especialmente para a tarefa de tagging, mas resultou em modelos menores
e lingüisticamente interpretáveis.
.. Discussion of how statistical methods have been used to
reach a linguistically interpretable, symbolic result.
Contrast between n-gram and Brill tagger about whether
we can learn anything from inspecting the model itself
(n-gram data vs transformational rules). Comparing accuracy
of these two methods: 2D graph showing how accuracy changes
for the two methods as training size increases.
Há várias outras abordagens importandes ao tagging que envolvem
*Hidden Markov Models* (veja ``nltk.hmm``) e *transdutores de estado
finito*, mas uma discussão sobre estas abordagens não entra no
objetivo deste capítulo. Mais tarde veremos uma generalização do
tagging chamada *chunking* na qual a uma seqüência contínua de
palavras é atribuída uma única tag.
.. Leituras adicionais
.. Brill tagging: Manning 361ff; Jurafsky 307ff
.. HMM tagging: Manning 345ff
----------
Exercícios
----------
1. **Experimentações com corpora com tags de partes do discurso**:
Toquenize o Brown Corpus e construa uma ou mais estruturas de dados
apropriadas para que você possa responder às seguintes questões.
a) Qual é a tag mais freqüÇente? (Esta é a tag que deveria ser
atribuída por ``tag.Default``)
#) Qual palavra possui o maior número de tags distintas?
#) Qual é a razão entre pronomes masculinos e femininos?
#) Quantas palavras são ambíguas, no sentido que podem receber no
mínimo duas tags diferentes?
#) Qual porcentagem de *ocorrências* de palavras do Brown Corpus envolve
estas palavras ambíguas?
#) Quais substantivos são mias comuns em sua forma plural que em sua
forma singular? (Considere apenas plurais regulares, formados pelo
sufixo ``-s``)
#) Produza uma lista ordenada alfabeticamente das palavras distintas que
recebem a tag ``md``.
#) Identifique palavras que podem ser tanto substantivos no plural quanto
verbos na terceira pessoa do singular (como ``deals``).
#) Identifique frases preposicionais de três palavras na forma preposição
+ determinante + substantivo.
#) Há 264 palavras distintas com exatamente três tags possíveis. Exiba uma
tabela com os números de 1 a 10 em uma coluna e o número de palavras
distintas do corpus com um número de a 1 a 10 tags possíveis.
#) A palavra still pode receber sete tags diferentes. Exiba sete sentenças
que contenham esta palavra, uma para cada tag diferente.
2. **Tagging por expressões regulares**:
No capítulo, definimos o ``nn_cd_tagger`` que pode ser utilizado como um
tagger de fall-back para palavras desconhecidas. Este tagger verifica
apenas a presença ou não de número cardinais. Testando strings de prefixos
e sufixos particulares, deveria ser possível adivinhar outras tags. Por
exemplo, poderíamos atribuir a tag de substantivo plural a toda palavra
terminada em ``-s``.
Defina um tagger por expressões regulares (utilizando o ``tag.Regexp``
que teste por ao menos cinco outros padrões na grafia das palavras.
(Utilize documentação inline para documentar as regras.)
Avalie a precisão do tagger utilizando o ``tag.accuracy()`` e discuta
quanto aos resultados.
3. **Unigram Tagging**:
Treie um Unigram Tagger e execute-o sobre um texto novo.
Avalie o tagger como antes e discuta quanto aos resultados.
Observe como a algumas palavra não é atribuída nenhuma tag. Por quê?
4. **Bigram Tagging**:
Treine um Bigram Tagger sem tagger de backoff e execute-o sobre parte
dos dados de treinamento. Após isto, execute-o sobre dados novos. O
que acontece com o desempenho do tagger? Por quê?
5. **Combinando taggers**:
Tipicamente, há um trade-off entre o desempenho e a abrangência dos
taggers: taggers que utilizam conceitos mais especificos geralmente
produzem resultados mais precisos quando foram treinados sobre estes
contextos; mas como os dados de treinamento são limitados, é menor a
probabilidade que eles encontrem cada contexto. O argumento backoff
da inicialização dos taggers permite-nos endereçar este problema tentando
incialmente com tagger de contextos mais específicos; e valendo-se
de taggers mais gerais quando necessário. Crie um default tagger e vários
taggers de tipo Unigram e de Ordem-N, incorporando backoffs, e treine-os
sobre uma parte do Brown corpus.
a) Crie três diferentes combinações de taggers. Teste a precisão de
cada tagger combinado. Qual combinação apresenta os melhores
resultados?
#) Tente alterar o tamanho do corpus de treinamento. Como isto afeta os
resultados?
6. **Tagger context** (avançado):
Taggers de ordem-N escolhem a tag para cada toquen com base em seu
tipo e na tag dos *n-1* toquens anteriores. Este é um contexto comum
para utilização no processo de tagging, mas certamente não é o
único contexto possível. Construa um novo tagger, derivando da classe
``SequentialTagger``, que utilize um contexto diferente. Se o contexto
de teu tagger contém elementos múltiplos, deverias combiná-los em um
``tuple``. Entre as possibilidades para elementos a serem incluídos
estão: (i) o texto do toquen atual, ou de um toquen anterior;
(ii) o comprimento do texto do toquen atual, ou do texto de um toquen
anterior; (iii) a primeira letra do texto do toquen atual, ou do texto
de um toquen anterior; (iv) a tag de um toquen anterior. Tente
escolher elementos de contexto que pensas possam ajudar o tagger a
decidir qual a tag mais apropriada. Lembre-se do trade-off entre taggers
específicos com resultados mais precisos e taggers mais gerais
de abrangência maior. Combine seu tagger com outros taggers utilizando
o método do backoff.
a) Compare o desempenho do tagger combiando com o do tagger básico.
#) Compare o desempenho do tagger combinado com o dos taggers combinados
que você criou no exercício anterior.
7. **Taggers seqüenciais reversos** (avançado):
Como taggers seqüenciais atribuem as tags em ordem, uma por vez, eles
podem utilizar apenas as tags à *esquerda* do toquen atual para decidir
qual a tag atribuir a um toquen. Mas, em alguns casos, o contexto à
*direita* pode fornecer mais informações sobre qual tag deveria ser
utilizada. Um tagger seqüencialmente reverso é um tagger que: (i)
atribui tags em ordem, uma por vez, iniciando pelo último toquen de um
texto e prosseguindo da direita para a esqueda. (ii) decide qual tag
atribuir a um toquen com base neste toquen, nos toquens que o seguem
e nas tags previstas para os toquens que o seguem.
Não é necessário criar novas classes para executar um tagging em
seqüência reversa. Revertendo os textos no momento apropriado, podemos
utilizar classes de taggers seqüenciais para executar tagging por
seqüência reversa. Além disso, deveríamos reverter o texto de treinamento
antes de treinar estes taggers e reverter o texto que submeteremos ao
tagging tanto antes quanto depois da utilização do tagger seqüencial.
Utilize esta técnica para criar um Bigram Tagger de seqüência reversa.
a) Meça sua precisão numa seção com tags do Brown Corpus. Assegure-se
de estar utilizando seções diferentes do corpus para o treinamento
e para a avaliação.
b) Compare o seu desempenho com um tagger de primeira ordem seqüencial,
utilizando os mesmos dados de treinamento e avaliação.
8. **Alternativas ao backoff**:
Crie um novo tipo de tagger que combine vários subtaggers utilizando
um novo mecanismo ao invés do backoff (como por votação). Para um
desempenho robusto frente a palavras desconhecidas, inclua um tagger
por expressões regulares, um Unigram Tagger que remova um pequeno
número de caracteres de prefixo ou sufixo até que a palavra seja
reconhecida ou um tagger de Ordem-N que não considere o texto do
token que está sendo processado.
9. **Comparando taggers de ordem-N com Brill Taggers**:
Investigue a precisão relativa de taggers de ordem N com backoff e
taggers tipo Brill quando o tamanho dos dados de treinamento é
aumentado. Analise e compare os erros cometidos por cada tipo de tagger.
10. **Aplicando a outras línguas**:
Obtenha alguns dados com tags para outra língua e treine e avalie um
série de taggers sobre estes. Se a língua em questão for morfologicamente
complexa ou se houverem marcas ortográficas para as classes de palavras
(como inicias maiúsculas), considere o desenvolvimento de um tagger por
expressões regulares para estes casos (que será executado após o unigram
tagger e antes do default tagger). Como a precisão deste(s) tagger(s)
se compara com os taggers de mesmo tipo para a língua inglesa? Discuta
qualquer problema que você tenha encontrado na aplicação destes métodos
à língua em questão.
----
NLTK_
.. _NLTK: http://nltk.sourceforge.net/