** L'extraction d'expressions uniques ** est une technologie qui extrait la nomenclature appropriée telle que ** nom de personne ** et ** nom de lieu ** qui apparaissent dans le texte, et des expressions numériques telles que ** date ** et ** heure **. .. L'extraction d'expressions unique est également utilisée comme technologie élémentaire pour les applications appliquées qui utilisent le traitement du langage naturel comme le ** système de questions-réponses **, ** le système de dialogue ** et ** l'extraction d'informations **.
Cette fois, je vais créer un extracteur d'expression unique en utilisant la ** technologie d'apprentissage automatique **.
※Remarques Aucune histoire théorique ne sort. Si vous voulez connaître la théorie, veuillez frapper l'autre. </ font>
Cette section fournit une vue d'ensemble et une méthode d'extraction d'expressions propres.
L'extraction d'expressions uniques est une technologie qui extrait la nomenclature appropriée, comme les noms de personnes et les noms de lieux qui apparaissent dans les textes, et les expressions numériques telles que la date et l'heure. Regardons un exemple concret. Extrayons l'expression appropriée de la phrase suivante.
Taro est allé voir Hanako à 9 heures le 18 mai.
Lorsque les expressions uniques contenues dans la phrase ci-dessus sont extraites, ** Taro ** et ** Hanako ** comme ** nom de la personne **, ** 18 mai ** comme ** date **, ** heure ** comme ** 9 h ** peut être extrait.
Dans l'exemple ci-dessus, le nom, la date et l'heure de la personne ont été extraits en tant que classes d'expressions uniques. En général, les huit classes suivantes (Exercice de récupération et d'extraction d'informations (IREX) Tâche d'extraction d'expressions spécifiques Définition) dans: //nlp.cs.nyu.edu/irex/NE/) est souvent utilisé.
classe | Exemple |
---|---|
Nom de produit unique ART | Prix Nobel de littérature, Windows 7 |
Nom du lieu LOC | Préfecture de Chiba, États-Unis |
Organisation ORG | LDP, NHK |
Nom de la personne PSN | Shinzo Abe, Mercel |
Date DAT | 29 janvier 2016/01/29 |
Heure TIM | 15 h, 10:30 |
Montant MNY | 241 yens, 8 dollars |
Rapport PNT | 10%, 30% |
Une façon d'extraire des expressions propres consiste à étiqueter des phrases qui ont été analysées morphologiquement. Ce qui suit est un exemple d'analyse morphologique de la phrase "Taro est à 9 heures le 18 mai ..." puis de l'étiquetage.
Les étiquettes B-XXX et I-XXX indiquent que ces chaînes sont des expressions uniques. B-XXX signifie le début de la chaîne d'expression appropriée et I-XXX signifie que la chaîne d'expression appropriée continue. La partie XXX contient une classe d'expression unique telle que ORG ou PSN. La partie non propriétaire est étiquetée O.
L'étiquetage peut être effectué à l'aide de règles, mais cette fois, il le sera à l'aide de la ** technologie d'apprentissage automatique **. Autrement dit, il crée un modèle à partir de données d'entraînement pré-étiquetées et utilise ce modèle pour étiqueter les instructions non étiquetées. Plus précisément, nous allons apprendre à utiliser un algorithme appelé CRF.
Bougeons réellement nos mains.
Commencez par installer les modules Python requis. Exécutez la commande suivante dans le terminal pour installer le module. J'ai installé CRFsuite en tant que bibliothèque de CRF.
pip install numpy
pip install scipy
pip install sklearn
pip install python-crfsuite
Une fois installés, importez les modules requis. Exécutez le code suivant.
from itertools import chain
import pycrfsuite
import sklearn
from sklearn.metrics import classification_report
from sklearn.preprocessing import LabelBinarizer
Puisque CRF est un apprentissage supervisé, nous avons besoin de données étiquetées avec les données des enseignants. Cette fois, j'ai préparé les données marquées à l'avance. Veuillez télécharger depuis ici. Le nom du fichier est "hironsan.txt".
Maintenant, définissons d'abord une classe pour lire les données téléchargées.
import codecs
class CorpusReader(object):
def __init__(self, path):
with codecs.open(path, encoding='utf-8') as f:
sent = []
sents = []
for line in f:
if line == '\n':
sents.append(sent)
sent = []
continue
morph_info = line.strip().split('\t')
sent.append(morph_info)
train_num = int(len(sents) * 0.9)
self.__train_sents = sents[:train_num]
self.__test_sents = sents[train_num:]
def iob_sents(self, name):
if name == 'train':
return self.__train_sents
elif name == 'test':
return self.__test_sents
else:
return None
Ensuite, chargez les données téléchargées à l'aide de la classe créée. Le nombre de données d'apprentissage est de 450 phrases et le nombre de données de test est de 50 phrases.
c = CorpusReader('hironsan.txt')
train_sents = c.iob_sents('train')
test_sents = c.iob_sents('test')
Le format des données lues est le suivant. L'étiquette IOB2 est attachée après avoir effectué une analyse morphologique avec l'analyseur morphologique "MeCab". Les données sont divisées en phrases, et chaque phrase consiste en une collection d'informations morphologiques multiples.
>>> train_sents[0]
[['2005', 'nom', 'nombre', '*', '*', '*', '*', '*', 'B-DAT'],
['Année', 'nom', 'suffixe', 'Mots auxiliaires', '*', '*', '*', 'Année', 'Nen', 'Nen', 'I-DAT'],
['7', 'nom', 'nombre', '*', '*', '*', '*', '*', 'I-DAT'],
['Mois', 'nom', 'Général', '*', '*', '*', '*', 'Mois', 'Lune', 'Lune', 'I-DAT'],
['14', 'nom', 'nombre', '*', '*', '*', '*', '*', 'I-DAT'],
['journée', 'nom', 'suffixe', 'Mots auxiliaires', '*', '*', '*', 'journée', 'Nichi', 'Nichi', 'I-DAT'],
['、', 'symbole', 'Point de lecture', '*', '*', '*', '*', '、', '、', '、', 'O'],
...
]
Ensuite, j'expliquerai les fonctionnalités utilisées pour extraire des expressions uniques.
Nous vous donnerons un aperçu de ce que vous allez utiliser, puis le coderons.
Ensuite, j'expliquerai la nature à utiliser. Cette fois, nous utiliserons des mots de deux lettres avant et après, une sous-classification des paroles de partie, un type de caractère et une étiquette d'expression unique. Un exemple d'utilisation de ces caractéristiques est présenté ci-dessous. La partie entourée par le cadre est la nature utilisée.
La classification des types de caractères est la suivante. Il existe 7 types en tout.
Balise de type de caractère | La description |
---|---|
ZSPACE | Vide |
ZDIGIT | Chiffres arabes |
ZLLET | Caractères alphabétiques inférieurs |
ZULET | Capitale de l'alphabet |
HIRAG | Hiragana |
KATAK | Katakana |
OTHER | Autre |
Le type de caractère utilisé comme identité est une combinaison de tous les types de caractères contenus dans le mot. Par exemple, le mot «plusieurs» inclut les kanji et les hiragana. La balise de type de caractère Hiragana est HIRAG et la balise de type de caractère Kanji est OTHER. Par conséquent, le type de caractère du mot "plusieurs" est "HIRAG-AUTRE".
Le code pour déterminer le type de caractère est le suivant. Tous les types de caractères contenus dans la chaîne de caractères sont combinés avec- (tiret).
def is_hiragana(ch):
return 0x3040 <= ord(ch) <= 0x309F
def is_katakana(ch):
return 0x30A0 <= ord(ch) <= 0x30FF
def get_character_type(ch):
if ch.isspace():
return 'ZSPACE'
elif ch.isdigit():
return 'ZDIGIT'
elif ch.islower():
return 'ZLLET'
elif ch.isupper():
return 'ZULET'
elif is_hiragana(ch):
return 'HIRAG'
elif is_katakana(ch):
return 'KATAK'
else:
return 'OTHER'
def get_character_types(string):
character_types = map(get_character_type, string)
character_types_str = '-'.join(sorted(set(character_types)))
return character_types_str
Le code pour extraire la sous-classe de mot partiel des informations morphologiques est le suivant.
def extract_pos_with_subtype(morph):
idx = morph.index('*')
return '-'.join(morph[1:idx])
Sur la base de ce qui précède, le code pour extraire l'identité de chaque mot est le suivant. C'est un peu redondant, mais vous pouvez le voir.
def word2features(sent, i):
word = sent[i][0]
chtype = get_character_types(sent[i][0])
postag = extract_pos_with_subtype(sent[i])
features = [
'bias',
'word=' + word,
'type=' + chtype,
'postag=' + postag,
]
if i >= 2:
word2 = sent[i-2][0]
chtype2 = get_character_types(sent[i-2][0])
postag2 = extract_pos_with_subtype(sent[i-2])
iobtag2 = sent[i-2][-1]
features.extend([
'-2:word=' + word2,
'-2:type=' + chtype2,
'-2:postag=' + postag2,
'-2:iobtag=' + iobtag2,
])
else:
features.append('BOS')
if i >= 1:
word1 = sent[i-1][0]
chtype1 = get_character_types(sent[i-1][0])
postag1 = extract_pos_with_subtype(sent[i-1])
iobtag1 = sent[i-1][-1]
features.extend([
'-1:word=' + word1,
'-1:type=' + chtype1,
'-1:postag=' + postag1,
'-1:iobtag=' + iobtag1,
])
else:
features.append('BOS')
if i < len(sent)-1:
word1 = sent[i+1][0]
chtype1 = get_character_types(sent[i+1][0])
postag1 = extract_pos_with_subtype(sent[i+1])
features.extend([
'+1:word=' + word1,
'+1:type=' + chtype1,
'+1:postag=' + postag1,
])
else:
features.append('EOS')
if i < len(sent)-2:
word2 = sent[i+2][0]
chtype2 = get_character_types(sent[i+2][0])
postag2 = extract_pos_with_subtype(sent[i+2])
features.extend([
'+2:word=' + word2,
'+2:type=' + chtype2,
'+2:postag=' + postag2,
])
else:
features.append('EOS')
return features
def sent2features(sent):
return [word2features(sent, i) for i in range(len(sent))]
def sent2labels(sent):
return [morph[-1] for morph in sent]
def sent2tokens(sent):
return [morph[0] for morph in sent]
Extrayez l'identité de la phrase avec sent2features. Les propriétés réellement extraites sont les suivantes.
>>> sent2features(train_sents[0])[0]
['bias',
'word=2005',
'type=ZDIGIT',
'postag=nom-nombre',
'BOS',
'BOS',
'+1:word=Année',
'+1:type=OTHER',
'+1:postag=nom-suffixe-Mots auxiliaires',
'+2:word=7',
'+2:type=ZDIGIT',
'+2:postag=nom-nombre']
Il s'avère que l'identité peut être extraite des données. Extraire les qualités et les étiquettes pour la formation et tester les données à partir des données pour une utilisation ultérieure.
X_train = [sent2features(s) for s in train_sents]
y_train = [sent2labels(s) for s in train_sents]
X_test = [sent2features(s) for s in test_sents]
y_test = [sent2labels(s) for s in test_sents]
Pour entraîner le modèle, créez un objet pycrfsuite.Trainer, chargez les données d'entraînement, puis appelez la méthode train. Tout d'abord, créez un objet Trainer et chargez les données d'entraînement.
trainer = pycrfsuite.Trainer(verbose=False)
for xseq, yseq in zip(X_train, y_train):
trainer.append(xseq, yseq)
Ensuite, définissez les paramètres d'apprentissage. À l'origine, il devrait être décidé en utilisant les données de développement, mais cette fois, il sera corrigé.
trainer.set_params({
'c1': 1.0, # coefficient for L1 penalty
'c2': 1e-3, # coefficient for L2 penalty
'max_iterations': 50, # stop earlier
# include transitions that are possible, but not observed
'feature.possible_transitions': True
})
Maintenant que nous sommes prêts, formons le modèle. Spécifiez le nom du fichier et exécutez la méthode de train.
trainer.train('model.crfsuite')
Lorsque l'exécution est terminée, un fichier avec le nom de fichier spécifié sera créé. Le modèle entraîné y est stocké.
Pour utiliser le modèle entraîné, créez un objet pycrfsuite.Tagger, chargez le modèle entraîné et utilisez la méthode de balise. Tout d'abord, créez un objet Tagger et chargez le modèle entraîné.
tagger = pycrfsuite.Tagger()
tagger.open('model.crfsuite')
Maintenant, marquons la phrase.
example_sent = test_sents[0]
print(' '.join(sent2tokens(example_sent)))
print("Predicted:", ' '.join(tagger.tag(sent2features(example_sent))))
print("Correct: ", ' '.join(sent2labels(example_sent)))
Vous devriez obtenir le résultat suivant: Prédite est la chaîne de balise prédite à l'aide du modèle créé et Correct est la chaîne de balise correcte. Dans le cas de cette phrase, le résultat attendu du modèle et les données de réponse correctes correspondaient.
En octobre de l'année dernière, 34 personnes ont été tuées dans une explosion à Taba, en Égypte, près du site.
Predicted: B-DAT I-DAT I-DAT O O O O O O O O O O O O B-LOC O B-LOC O O O O O O O O O O
Correct: B-DAT I-DAT I-DAT O O O O O O O O O O O O B-LOC O B-LOC O O O O O O O O O O
Ceci termine la construction de l'extracteur d'expression propre.
J'ai créé un modèle, mais je ne sais pas si c'est bon ou mauvais. Par conséquent, il est important d'évaluer le modèle créé. Évaluons maintenant le modèle créé. L'évaluation est basée sur le taux de précision, le taux de rappel et la valeur F. Voici le code à évaluer.
def bio_classification_report(y_true, y_pred):
lb = LabelBinarizer()
y_true_combined = lb.fit_transform(list(chain.from_iterable(y_true)))
y_pred_combined = lb.transform(list(chain.from_iterable(y_pred)))
tagset = set(lb.classes_) - {'O'}
tagset = sorted(tagset, key=lambda tag: tag.split('-', 1)[::-1])
class_indices = {cls: idx for idx, cls in enumerate(lb.classes_)}
return classification_report(
y_true_combined,
y_pred_combined,
labels = [class_indices[cls] for cls in tagset],
target_names = tagset,
)
Instructions de balise dans l'ensemble de données de test à utiliser dans l'évaluation
y_pred = [tagger.tag(xseq) for xseq in X_test]
Les données étiquetées à l'aide du modèle entraîné et les données de réponse correctes sont transmises à la fonction d'évaluation et le résultat est affiché. Pour chaque catégorie, le taux de précision, le taux de rappel, la valeur F et le nombre de balises sont affichés.
>>> print(bio_classification_report(y_test, y_pred))
precision recall f1-score support
B-ART 1.00 0.89 0.94 9
I-ART 0.92 1.00 0.96 12
B-DAT 1.00 1.00 1.00 12
I-DAT 1.00 1.00 1.00 22
B-LOC 1.00 0.95 0.97 55
I-LOC 0.94 0.94 0.94 17
B-ORG 0.75 0.86 0.80 14
I-ORG 1.00 0.90 0.95 10
B-PSN 0.00 0.00 0.00 3
B-TIM 1.00 0.71 0.83 7
I-TIM 1.00 0.81 0.90 16
avg / total 0.95 0.91 0.93 177
Je pense que le résultat est un peu trop bon, mais les données utilisées contenaient probablement des déclarations similaires.
※Mise en garde Vous pouvez obtenir un UndefinedMetricWarning. Il semble qu'il n'est pas possible de définir le taux de précision, etc. pour les étiquettes qui n'existent pas dans l'échantillon prédit. Parce que le nombre de données préparées est petit ...
Cette fois, j'ai pu créer facilement un extracteur d'expressions unique en utilisant crfsuite, qui est une bibliothèque Python. Huit expressions uniques sont ajoutées au balisage en fonction de la définition d'IREX. Cependant, la définition d'IREX est souvent approximative pour une utilisation pratique. Par conséquent, lors de l'utilisation de l'extraction d'expressions propres pour une tâche, il est nécessaire de préparer les données avec les balises nécessaires en fonction de la tâche.
De plus, je pense que c'est une bonne idée de rechercher de meilleures caractéristiques et paramètres de modèle.