[PYTHON] Créez facilement un modèle de traitement du langage naturel avec BERT + LightGBM + optuna

Cet article traite de la création rapide d'un modèle de classification en langage naturel avec BERT, LightGBM et optuna. Les données utilisent le corpus de nouvelles de livingoor.

En outre, tout le code utilisé dans cet article est ci-dessous. https://github.com/kazuki-hayakawa/bert_lightgbm_model

Lorsque vous l'exécutez, essayez-le à portée de main après git clone.

Flux global

  1. Téléchargez les données du corpus d'actualités de livingoor
  2. Téléchargez le modèle BERT appris en japonais
  3. Démarrez le conteneur expérimental
  4. Génération de fonctionnalités
  5. Formation sur modèle
  6. Évaluation du modèle à l'aide de données d'essai
  7. Sortir du conteneur

Télécharger les données du corpus d'actualités de Liveoor

Téléchargez les données du corpus de news de livingoor dans le répertoire data / raw. Le script de téléchargement est résumé dans src / data / download_livedoor_news.sh.

Puis exécutez src / data / preprocess.py pour prétraiter et enregistrer les données séparément pour l'entraînement et les tests.

src/data/preprocess.py


import os
import glob
from tqdm import tqdm
import pandas as pd
from sklearn.model_selection import train_test_split


def read_text(text_filepath):
    """Lire uniquement le texte à partir de la 4ème ligne selon le format des news de livingoor"""
    with open(text_filepath, 'r') as f:
        lines = f.readlines()
        lines = lines[3:]

    text = ' '.join(lines)
    #Espace pleine largeur, supprimer le code de saut de ligne
    text = text.replace('\u3000', '').replace('\n', '')
    return text


def main():
    #Téléchargez à l'avance_livedoor_news.Exécutez sh pour obtenir les données
    exclude_files = ['CHANGES.txt', 'README.txt', 'LICENSE.txt']
    all_file_paths = glob.glob('../../data/raw/text/**/*.txt', recursive=True)
    all_file_paths = [p for p in all_file_paths
                      if os.path.basename(p) not in exclude_files]

    df_processed = pd.DataFrame(columns=['id', 'media', 'text'])
    for idx, filepath in enumerate(tqdm(all_file_paths)):
        media = os.path.dirname(filepath).replace('../../data/raw/text/', '')
        text = read_text(filepath)
        row = pd.Series([idx + 1, media, text], index=df_processed.columns)
        df_processed = df_processed.append(row, ignore_index=True)

    df_train, df_test, _, _ = train_test_split(
        df_processed, df_processed['media'], test_size=0.1, random_state=0,
        stratify=df_processed['media']
    )
    df_train.to_csv('../../data/processed/train_dataset.csv', index=False)
    df_test.to_csv('../../data/processed/test_dataset.csv', index=False)


if __name__ == '__main__':
    main()

Télécharger le modèle BERT appris en japonais

Pour la procédure, reportez-vous à Création d'une instruction BERT japonaise intégrant un serveur de calcul à l'aide de bert-as-service.

Créez un répertoire models / bert_jp et téléchargez modèle BERT appris en japonais.

Renommez le fichier pour qu'il puisse être chargé par bert-as-service

mv model.ckpt-1400000.index bert_model.ckpt.index
mv model.ckpt-1400000.meta bert_model.ckpt.meta 
mv model.ckpt-1400000.data-00000-of-00001 bert_model.ckpt.data-00000-of-00001

Créer un fichier de vocabulaire

cut -f1 wiki-ja.vocab | sed -e "1 s/<unk>/[UNK]/g" > vocab.txt

Créer un fichier de configuration BERT

bert_jp/bert_config.json


{
    "attention_probs_dropout_prob" : 0.1,
    "hidden_act" : "gelu",
    "hidden_dropout_prob" : 0.1,
    "hidden_size" : 768,
    "initializer_range" : 0.02,
    "intermediate_size" : 3072,
    "max_position_embeddings" : 512,
    "num_attention_heads" : 12,
    "num_hidden_layers" : 12,
    "type_vocab_size" : 2,
    "vocab_size" : 32000
}

Lancer le conteneur expérimental

Exécutez docker-compose up -d pour démarrer le conteneur. (Reportez-vous au référentiel GitHub pour Dockerfile et docker-compose.yml) Exécutez ensuite docker-compose exec analytics / bin / bash pour entrer dans le conteneur.

Génération de fonctionnalités

La classe Bert qui gère BERT est implémentée comme suit.

src/features/bert.py


import sentencepiece as spm
from bert_serving.client import BertClient


class Bert():
    """ Bert model client
        Before usage, you need to run bert server.
    """

    def __init__(self, bert_model_path, client_ip='0.0.0.0'):
        self.bert_client = BertClient(ip=client_ip)
        self.spm_model = spm.SentencePieceProcessor()
        self.spm_model.load(bert_model_path + 'wiki-ja.model')

    def _parse(self, text):
        text = str(text).lower()
        encoded_texts = self.spm_model.EncodeAsPieces(text)
        encoded_texts = [t for t in encoded_texts if t.strip()]
        return encoded_texts

    def text2vec(self, texts):
        """

        Args:
            texts (list):Liste des chaînes japonaises

        Returns:
            numpy array:Tenseur de représentation distribuée du texte
        """

        parsed_texts = list(map(self._parse, texts))
        tensor = self.bert_client.encode(parsed_texts, is_tokenized=True)
        return tensor

Utilisez ceci pour convertir le langage naturel en vecteur. En même temps, le nom de média qui est la variable objectif est également converti en une étiquette de type entier. Exécutez src / features / build_features.py.

src/features/build_features.py


import subprocess
import numpy as np
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from bert import Bert


def build_features(df, bert_client):
    vectors = bert_client.text2vec(df['text'])
    le = LabelEncoder()
    targets = le.fit_transform(df['media'])
    return vectors, targets


def main():
    BERT_MODEL_PATH = '../../models/bert_jp/'

    # start bert server
    commands = ['bert-serving-start', '-model_dir',
                BERT_MODEL_PATH, '-num_worker=1', '-cpu']
    p = subprocess.Popen(commands, shell=False,
                         stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    # start bert client
    bert = Bert(bert_model_path=BERT_MODEL_PATH, client_ip='0.0.0.0')

    # build train features
    train_dataset = pd.read_csv('../../data/processed/train_dataset.csv')
    train_vectors, train_targets = build_features(train_dataset, bert)
    np.save('../../data/features/train_vectors', train_vectors)
    np.save('../../data/features/train_targets', train_targets)

    # build test features
    test_dataset = pd.read_csv('../../data/processed/test_dataset.csv')
    test_vectors, test_targets = build_features(test_dataset, bert)
    np.save('../../data/features/test_vectors', test_vectors)
    np.save('../../data/features/test_targets', test_targets)

    p.terminate()


if __name__ == '__main__':
    main()

Formation modèle

La classe «MediaClassifier», qui définit un modèle de classification des médias d'information, est implémentée comme suit.

src/models/classifier.py


import os
import uuid
import pickle
import numpy as np
import lightgbm as lgb
import optuna
from datetime import datetime, timedelta, timezone
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


class MediaClassifier():
    """modèle de classification multi-classes de Livingoor News Corpus"""

    def __init__(self, output_dir, use_gpu=False):
        JST = timezone(timedelta(hours=+9), 'JST')
        dt_now = datetime.now(JST)
        training_date = dt_now.strftime("%Y%m%d_%H%M%S")
        self.output_dir = os.path.join(output_dir, training_date)
        os.makedirs(self.output_dir, exist_ok=True)
        
        self.device = 'gpu' if use_gpu else 'cpu'

    def train(self, features, targets):
        X_train, X_test, y_train, y_test = train_test_split(
            features, targets, test_size=0.2, random_state=0)

        def objectives(trial):
            trial_uuid = str(uuid.uuid4())
            trial.set_user_attr("trial_uuid", trial_uuid)

            #Paramètres et paramètres de rappel
            params = {
                #Puisque le nombre de médias dans le corpus de nouvelles de liverdoor est de 9, 9 classifications multi-classes
                'objective': 'multiclass',
                'num_class': 9,
                'metric': 'multi_logloss',
                'num_leaves': trial.suggest_int("num_leaves", 10, 500),
                'feature_fraction': trial.suggest_uniform("feature_fraction", 0.0, 1.0),
                'class_weight': 'balanced',
                'device': self.device,
                'verbose': -1
            }

            pruning_callback = optuna.integration.LightGBMPruningCallback(
                trial, "multi_logloss")

            # training
            lgb_model = lgb.train(params, lgb.Dataset(X_train, y_train), num_boost_round=100,
                                  valid_sets=lgb.Dataset(X_test, y_test), callbacks=[pruning_callback])

            y_pred_train = np.argmax(lgb_model.predict(X_train), axis=1)
            y_pred_test = np.argmax(lgb_model.predict(X_test), axis=1)
            accuracy_train = accuracy_score(y_train, y_pred_train)
            accuracy_test = accuracy_score(y_test, y_pred_test)

            trial.set_user_attr("accuracy_train", accuracy_train)
            trial.set_user_attr("accuracy_test", accuracy_test)

            #Enregistrer le modèle
            output_file = os.path.join(self.output_dir, f"{trial_uuid}.pkl")
            with open(output_file, "wb") as fp:
                pickle.dump(lgb_model, fp)

            return 1.0 - accuracy_test

        study = optuna.create_study()
        study.optimize(objectives, n_trials=100)

        result_df = study.trials_dataframe()
        result_csv = os.path.join(self.output_dir, "result.csv")
        result_df.to_csv(result_csv, index=False)

        return study.best_trial.user_attrs

Effectuez une formation sur le modèle ci-dessus.

src/models/train_model.py


import numpy as np
from classifier import MediaClassifier


def main():
    train_vectors = np.load('../../data/features/train_vectors.npy')
    train_targets = np.load('../../data/features/train_targets.npy')

    model = MediaClassifier(output_dir='../../models/training_models',
                            use_gpu=False)

    best_result = model.train(train_vectors, train_targets)
    print('best result \n', best_result)


if __name__ == '__main__':
    main()

Une fois l'exécution terminée, l'uuid d'essai et le score du modèle le plus performant seront affichés comme suit, alors notez-le.

{'trial_uuid': 'BEST_MODEL_TRIAL_UUID', 'accuracy_train': 1.0, 'accuracy_test': 0.7398190045248869}

La partie BEST_MODEL_TRIAL_UUID est en fait uuid.

Évaluation du modèle avec des données de test

L'évaluation à l'aide des données de test est effectuée comme suit.

src/models/predict_model.py


import argparse
import pickle
import numpy as np
from sklearn.metrics import accuracy_score


def main(args):
    test_vectors = np.load('../../data/features/test_vectors.npy')
    test_targets = np.load('../../data/features/test_targets.npy')

    with open(args.best_model, 'rb') as f:
        model = pickle.load(f)

    pred_targets = np.argmax(model.predict(test_vectors), axis=1)
    accuracy = accuracy_score(test_targets, pred_targets)
    print('test accuracy : {:.2f}'.format(accuracy))

    
if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('--best_model', help='best model pickle file path.')

    args = parser.parse_args()

    main(args)

Un répertoire est automatiquement généré lorsque vous commencez à entraîner le modèle, spécifiez donc le chemin. Voici un exemple d'exécution.

$ cd src/models
$ python predict_model.py --best_model='../../models/training_models/TRINING_DATE/BEST_MODEL_TRIAL_UUID.pkl'

test accuracy : 0.73

Le taux de réponse correct était de "0,73" dans le modèle que j'ai créé. Ce n'est pas très cher, mais je pense que cela peut être augmenté un peu plus en augmentant la quantité de données et en imaginant des moyens de séparer les textes.

Fin du conteneur

Quittez le conteneur avec ʻexit et sortez avec docker-compose down`.

Recommended Posts

Créez facilement un modèle de traitement du langage naturel avec BERT + LightGBM + optuna
Créez facilement un environnement de développement avec Laragon
3. Traitement du langage naturel par Python 2-1. Réseau de co-occurrence
3. Traitement du langage naturel par Python 1-1. Word N-gram
J'ai essayé le traitement du langage naturel avec des transformateurs.
3. Traitement du langage naturel avec Python 1-2. Comment créer un corpus: Aozora Bunko
Créer un environnement de développement de langage C avec un conteneur
3. Traitement du langage naturel par Python 2-2. Réseau de co-occurrence [mecab-ipadic-NEologd]
[Python] J'ai joué avec le traitement du langage naturel ~ transformers ~
Profitons du traitement du langage naturel à l'aide de l'API COTOHA
Mettre en place un environnement de développement pour le traitement du langage naturel
Python: traitement du langage naturel
Modèle utilisant un réseau neuronal convolutif dans le traitement du langage naturel
RNN_LSTM2 Traitement du langage naturel
Créer un environnement pour le traitement du langage naturel avec Python
Traitement du langage naturel (données originales) avec Word2Vec développé par des chercheurs Google américains
[Pratique] Créez une application Watson avec Python! # 3 [Classification du langage naturel]
J'ai essayé d'écrire dans un modèle de langage profondément appris
100 coups de traitement du langage avec Python 2015
Traitement du langage naturel 1 Analyse morphologique
Ajustement des paramètres LightGBM avec Optuna
Traitement du langage naturel 3 Continuité des mots
Créez facilement des CNN avec Keras
Créez facilement un profil avec un décorateur
Traitement du langage naturel 2 similitude de mots
J'écrirai une explication détaillée à mort en résolvant 100 traitements de langage naturel Knock 2020 avec Python
Dockerfile avec les bibliothèques nécessaires pour le traitement du langage naturel avec python
Résumez comment prétraiter le texte (traitement du langage naturel) avec l'api tf.data.Dataset
Étude de cas sur le traitement du langage naturel: Fréquence des mots dans'Anne avec un E '
100 traitements du langage naturel frappent le chapitre 4 Commentaire
100 traitements de langage avec Python
Créer un fichier deb avec Docker
Langage naturel: Word2Vec Part3 - Modèle CBOW
100 traitements de langage avec Python (chapitre 3)
Logivan du langage artificiel et traitement du langage naturel (traitement du langage artificiel)
Créer une application Web avec Django
Créez facilement des machines virtuelles avec Vagrant
Créer un itérateur de modèle avec PySide
Se préparer à démarrer le traitement du langage naturel
Résumé de l'installation de l'analyseur de traitement du langage naturel
Dessinez facilement une carte avec matplotlib.basemap
Langage naturel: Word2Vec Part2 - Modèle de saut-gramme
Optimisez RF ou LightGBM avec Optuna
Apprenez les bases de la classification de documents par traitement du langage naturel, modèle de sujet
C'est vrai, mangeons-le. [Traitement du langage naturel à partir de Kyoto Ben]
[Traitement du langage naturel] Extraire les mots-clés de la base de données Kakenhi avec MeCab-ipadic-neologd et termextract