[PYTHON] [Tutoriel] Créez un extracteur d'expressions unique en 30 minutes à l'aide de l'apprentissage automatique

introduction

** 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>

Public cible

  • Ceux qui connaissent un peu l'extraction d'expressions uniques
  • Ceux qui veulent faire un extracteur d'expression unique
  • Ceux qui peuvent lire le code Python

Qu'est-ce que l'extraction d'expressions propres?

Cette section fournit une vue d'ensemble et une méthode d'extraction d'expressions propres.

Aperçu

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%

Méthode

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.

スクリーンショット 2016-01-28 14.35.17.png

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.

Installation

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

Données utilisées pour créer l'extracteur d'expression propre

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.

Identité à utiliser

Nous vous donnerons un aperçu de ce que vous allez utiliser, puis le coderons.

Aperçu

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. ner.png

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".

Codage de l'extraction d'identité

Jugement du type de personnage

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

Extraction de la sous-classification du mot partiel

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])

Extraction d'identité à partir de phrases

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]

Apprentissage de modèle

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é.

Prédiction des données de test

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.

Évaluation du modèle

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 ...

en conclusion

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.

référence

Recommended Posts

[Tutoriel] Créez un extracteur d'expressions unique en 30 minutes à l'aide de l'apprentissage automatique
Une histoire sur l'apprentissage automatique simple avec TensorFlow
Astuces de fourniture de données utilisant deque dans l'apprentissage automatique
Touchons une partie de l'apprentissage automatique avec Python
Remplacez la représentation unique du fichier texte lu par une étiquette (en utilisant GiNZA)
Un mémorandum de méthode souvent utilisé dans l'apprentissage automatique utilisant scikit-learn (pour les débutants)
Résumé du didacticiel d'apprentissage automatique
Les débutants en apprentissage automatique essaient de créer un arbre de décision
Mémo de construction d'environnement d'apprentissage automatique par Python
MALSS (introduction), un outil qui prend en charge l'apprentissage automatique en Python
Apprentissage automatique Une histoire sur des personnes qui ne sont pas familiarisées avec GBDT utilisant GBDT en Python
Lancement d'un environnement d'apprentissage automatique à l'aide de Google Compute Engine (GCE)
J'ai essayé d'utiliser Tensorboard, un outil de visualisation pour l'apprentissage automatique
J'ai fait un chronomètre en utilisant tkinter avec python
Apprentissage automatique dans Delemas (s'entraîner)
Tutoriel Pepper (5): Utilisation d'une tablette
Créer un bookmarklet en Python
Créer un environnement d'apprentissage automatique
Utilisé en EDA pour l'apprentissage automatique
Que diriez-vous d'Anaconda pour créer un environnement d'apprentissage automatique avec Python?
Créer un outil de reconnaissance de visage à l'aide de TensorFlow
Automatisez les tâches de routine dans l'apprentissage automatique
Mémorandum d'introduction au tutoriel d'apprentissage automatique de Chainer
Classification et régression dans l'apprentissage automatique
Analyse inverse du modèle d'apprentissage automatique
Apprentissage automatique dans Delemas (acquisition de données)
Python: prétraitement dans l'apprentissage automatique: présentation
Prétraitement dans l'apprentissage automatique 2 Acquisition de données
Rendre matplotlib compatible avec le japonais en 3 minutes
Créer un modèle d'apprentissage à l'aide de MNIST
Recherche de semences aléatoires dans l'apprentissage automatique
Déployez Django en 3 minutes à l'aide de docker-compose
Créez un générateur de rideaux avec Blender
Prétraitement dans l'apprentissage automatique 4 Conversion de données
Développement d'applications à l'aide d'Azure Machine Learning
Comment faire un modèle pour la détection d'objets avec YOLO en 3 heures
Viser à devenir un ingénieur en apprentissage automatique en utilisant des MOOC depuis des postes de vente
J'ai essayé de classer les accords de guitare en temps réel en utilisant l'apprentissage automatique
J'ai écrit FizzBuzz en python en utilisant la machine à vecteurs de support (bibliothèque LIVSVM).
Amener l'apprentissage automatique à un niveau pratique en un mois # 1 (édition de départ)
Comment créer un ensemble de données d'image de visage utilisé dans l'apprentissage automatique (1: Acquérir des images de candidats à l'aide du service API Web)