Para etiquetar una secuencia reducimos este problema a un problema de clasificación. Una historia es una instancia de clasificación, que es una tupla con los siguientes elementos:
sent
: La oración entera.prev_tags
: Una tupla con las n
etiquetas anterioresi
: El índice de la palabra a etiquetar.from collections import namedtuple
History = namedtuple('History', 'sent prev_tags i')
Por ejemplo, para la oración "El gato come pescado ." con n = 2
tenemos las siguientes historias válidas:
sent = 'El gato come pescado .'.split()
histories = [
History(sent, ('<s>', '<s>'), 0),
History(sent, ('<s>', 'D'), 1),
History(sent, ('D', 'N'), 2),
History(sent, ('N', 'V'), 3),
History(sent, ('V', 'N'), 4)
]
histories
[History(sent=['El', 'gato', 'come', 'pescado', '.'], prev_tags=('<s>', '<s>'), i=0), History(sent=['El', 'gato', 'come', 'pescado', '.'], prev_tags=('<s>', 'D'), i=1), History(sent=['El', 'gato', 'come', 'pescado', '.'], prev_tags=('D', 'N'), i=2), History(sent=['El', 'gato', 'come', 'pescado', '.'], prev_tags=('N', 'V'), i=3), History(sent=['El', 'gato', 'come', 'pescado', '.'], prev_tags=('V', 'N'), i=4)]
Para cada instancia de clasificación (historias) debemos calcular un conjunto de características relevantes (features) para la clasificación.
Por ejemplo, podemos usar como feature la palabra a etiquetar en minúsculas (un feature de tipo string):
def word_lower(h):
"""Feature: current lowercased word.
h -- a history.
"""
sent, i = h.sent, h.i
return sent[i].lower()
[word_lower(h) for h in histories]
['el', 'gato', 'come', 'pescado', '.']
También podemos usar como feature si la palabra comienza con mayúsculas o no (un feature de tipo booleano):
def word_istitle(h):
"""Feature: is the current word titlecased?
h -- a history.
"""
sent, i = h.sent, h.i
return sent[i].istitle()
[word_istitle(h) for h in histories]
[True, False, False, False, False]
Para usar los features que definimos debemos construir un vectorizador, que convierta los valores de los features en entradas en una matriz:
from featureforge.vectorizer import Vectorizer
features = [word_lower, word_istitle]
vect = Vectorizer(features)
Como cualquier componente de scikit-learn, el vectorizador debe entrenarse para definir los features concretos y el mapeo a columnas de una matriz:
vect.fit(histories)
<featureforge.vectorizer.Vectorizer at 0x7f24447a1160>
Luego podemos vectorizar cualquier historia, como por ejemplo la que corresponde a etiquetar la primer palabra de la oración "Come salmón el mormón .".
h = History('Come salmón el mormón .'.split(), ('<s>', '<s>'), 0)
m = vect.transform([h])
m.toarray()
array([[ 1., 0., 0., 1., 0., 0.]])
Los features activos son el de la columna 0 y el de la columna 3. Podemos ver qué significa cada uno de ellos:
print(vect.column_to_feature(0))
print(vect.column_to_feature(0)[0]._name)
print(vect.column_to_feature(5))
print(vect.column_to_feature(3)[0]._name, vect.column_to_feature(3)[1])
(<featureforge.feature.Feature object at 0x7f24447b3780>, None) word_istitle (<featureforge.feature.Feature object at 0x7f24447ab4a8>, '.') word_lower come
La columna 0 indica que la palabra comienza en mayúsculas, mientras que la columna 3 indica que la palabra en minúsculas es "come".
Algunos features pueden tener parámetros que permitan diferentes instanciaciones. Estos features se pueden definir como subclases de la clase Feature
. Por ejemplo, podemos definir un feature booleano que indica si una palabra es más larga que un número dado:
from featureforge.feature import Feature
class WordLongerThan(Feature):
def __init__(self, n):
self.n = n
self._name = 'word_longer_than_{}'.format(n)
def _evaluate(self, h):
"""Feature: is the current word longer than n?
h -- a history.
"""
sent, i = h.sent, h.i
return len(sent[i]) > self.n
Una vez definido, podemos instanciar el feature con un valor particular:
word_longer_than_two = WordLongerThan(2)
Luego, podemos usarlo como un feature normal, por ejemplo evaluándolo sobre las historias o incluyéndolo en un vectorizador. Por ejemplo, lo evaluamos para las historias correspondientes a la oración "El gato come pescado.":
list(zip('El gato come pescado .'.split(), [word_longer_than_two(h) for h in histories]))
[('El', False), ('gato', True), ('come', True), ('pescado', True), ('.', False)]
También podemos agregar el feature al vectorizador:
features = [word_lower, word_istitle, word_longer_than_two]
vect = Vectorizer(features)
vect.fit(histories)
h = History('Come salmón el mormón .'.split(), ('<s>', '<s>'), 0)
m = vect.transform([h])
m.toarray()
array([[ 1., 1., 0., 0., 1., 0., 0.]])
En este caso la segunda columna corresponde al nuevo feature:
vect.column_to_feature(1)[0]._name
'word_longer_than_2'