Cet article est l'article du 20e jour du "Calendrier de l'Avent Django 2019 - Qiita".
C'est siny.
Dans cet article, j'ai résumé les défis de la création d'une API Rest qui renvoie des résultats d'inférence négatifs-positifs à l'aide du ** Django REST framework ** et du ** BERT model **.
Cet article ne couvre pas les parties liées à l'implémentation du modèle BERT car Django est l'objectif principal. Pour la mise en œuvre et l'apprentissage du modèle BERT à l'aide de l'ensemble de données japonais utilisé dans cet article, voir [** Natural Language Processing Advent Calendar 2019 Day 25 (Creation of Negative-Positive Classifier Using BERT) **]( Veuillez consulter https://qiita.com/ysiny/items/b01250228e0c5cc0e647) ".
Tout d'abord, il s'agit d'un diagramme schématique du traitement global de l'environnement DRF supposé cette fois.
Ce que vous faites est simple, c'est une API REST qui déduit (classification binaire) si la phrase donnée en entrée est négative ou positive avec un modèle BERT et renvoie le résultat côté client.
[Vidéo de démonstration de l'API]
Les modules implémentés dans cet article peuvent être trouvés dans ce référentiel git, veuillez donc les télécharger et les utiliser comme il convient.
Concernant BERT, [Livre "Apprendre en créant! Apprentissage en profondeur développé par PyTorch"](https://www.amazon.co.jp/%E3%81%A4%E3%81%8F%E3%82%8A % E3% 81% AA% E3% 81% 8C% E3% 82% 89% E5% AD% A6% E3% 81% B6-PyTorch% E3% 81% AB% E3% 82% 88% E3% 82% 8B % E7% 99% BA% E5% B1% 95% E3% 83% 87% E3% 82% A3% E3% 83% BC% E3% 83% 97% E3% 83% A9% E3% 83% BC% E3 % 83% 8B% E3% 83% B3% E3% 82% B0-% E5% B0% 8F% E5% B7% 9D% E9% 9B% 84% E5% A4% AA% E9% 83% 8E / dp / Il a été créé en référence à 4839970254). Dans le livre ci-dessus, la classification négative / positive du modèle BERT était basée sur des données anglaises, nous l'avons donc améliorée afin qu'elle puisse être classée comme négative / positive dans l'ensemble de données japonais basé sur le livre.
Il a été confirmé que le contenu de cet article fonctionne dans les environnements suivants.
article | sens |
---|---|
OS | Ubuntu sur Windows 10 |
Modèle BERT | Publié par l'Université de Kyotopytorch-pretrained-Modèle BERTLe réglage fin est effectué en fonction de. |
Analyse morphologique | Juman++ (v2.0.0-rc2) or (v2.0.0-rc3) |
Django | 2.2.5 |
djangorestframework | 3.10.3 |
Cette fois, nous allons créer un DRF qui fonctionne dans l'environnement Ubuntu de Windows 10. Tout d'abord, créez un environnement virtuel et installez les modules nécessaires avec conda.
conda create -n drf python=3.6
conda activate drf
conda install pytorch=0.4 torchvision -c pytorch
conda install pytorch=0.4 torchvision cudatoolkit -c pytorch
conda install pandas scikit-learn django
Si conda ne fonctionne pas, installez-le avec pip.
pip install mojimoji
pip install attrdict
pip install torchtext
pip install pyknp
pip install djangorestframework
Le modèle pré-entraîné japonais BERT utilisé cette fois utilise Human ++ (v2.0.0-rc2) pour l'analyse morphologique du texte d'entrée, de sorte que cet article fait également correspondre l'outil d'analyse morphologique à ** Human ++ **. La procédure d'installation de Juman ++ est résumée dans un article séparé, veuillez donc vous référer à ce qui suit.
[** Résumé de la procédure d'installation de JUMAN ++ **] https://sinyblog.com/deaplearning/juman/
Après avoir installé Juman ++, il est OK si l'analyse morphologique est disponible dans l'environnement local comme suit.
#Contrôle du fonctionnement JUMAN
from pyknp import Juman
text = "J'apprends le traitement du langage naturel."
juman = Juman()
result =juman.analysis(text)
result = [mrph.midasi for mrph in result.mrph_list()]
print(text)
J'apprends le traitement du langage naturel.
print(result)
['La nature', 'Langue', 'En traitement', 'À', 'sur', 'Apprentissage', 'Pendant ~', 'est', '。']
Tout d'abord, créez un projet django. (Nom du projet: drf)
django-admin startproject drf
Ensuite, créez une application (nom de l'application: appv1)
cd drf
python manage.py startapp appv1
Ajoutez rest_framework et application (appv1) à INSTALLED_APPS dans settings.py.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', #add
'appv1.apps.Appv1Config', #add
]
Créez les dossiers suivants directement sous le dossier de l'application (appv1) et placez les modules comme spécifié.
Nom de dossier | Module de placement | Utilisation |
---|---|---|
vocab | vocab.txt | Fichier de dictionnaire de lexique BERT |
weights | bert_config.json | Fichier de configuration BERT |
weights | pytorch_model.bin | HP publié par l'Université de KyotoFichiertéléchargédepuis(modèleentraîné) |
weights | bert_fine_tuning_chABSA.pth | BERT modèle formé au réglage fin |
data | **.4 fichiers tsv | Données d'entraînement, données de test, etc. |
Placez les fichiers suivants directement sous l'application (appv1).
nom de fichier | sens |
---|---|
config.py | Divers fichiers de paramètres |
dataloader.py | fichier de génération du chargeur de données torchtext |
predict.py | Pour raisonner |
tokenizer.py | Shell associé à la séparation de mots BERT |
bert.py | Définition du modèle BERT |
Démarrez le mode shell de Django et générez un fichier de données de lexique à utiliser pour l'inférence.
python manage.py shell
from appv1.config import *
from appv1.predict import create_vocab_text
TEXT = create_vocab_text()
L'exécution de ce qui précède générera appv1 / data / text.pkl.
La structure générale des répertoires est la suivante.
├─drf
│ │ db.sqlite3
│ │ manage.py
│ │
│ ├─appv1
│ │ │ admin.py
│ │ │ apps.py
│ │ │ bert.py #Définition du modèle BERT
│ │ │ config.py #Divers fichiers de paramètres
│ │ │ dataloader.py #fichier de génération du chargeur de données torchtext
│ │ │ models.py
│ │ │ predict.py #Pour raisonner
│ │ │ serializers.py #Sérialiseur
│ │ │ tests.py
│ │ │ tokenizer.py #Shell associé à la séparation de mots BERT
│ │ │ views.py
│ │ ├─data
│ │ │ test_dumy.tsv #Données factices
│ │ │ train_dumy.tsv #Données factices
│ │ │ text.pkl #Données de Wordbook utilisées pour l'inférence
│ │ │
│ │ ├─vocab
│ │ │ vocab.txt #Données du lexique de Bert
│ │ │
│ │ ├─weights
│ │ │ bert_config.json
│ │ │ bert_fine_tuning_chABSA.pth #Modèle Bert ajusté
│ │ │ pytorch_model.bin
│ │ │
│ ├─drf
│ │ │ settings.py
│ │ │ urls.py
│ │ │ wsgi.py
Assurez-vous que le processus d'inférence fonctionne correctement en utilisant le modèle entraîné par BERT sur l'environnement django.
Après avoir démarré le mode shell de Django, exécutez la commande suivante pour donner des exemples de données de texte et confirmer qu'un jugement négatif ou positif peut être fait.
python manage.py shell
-----------------------------------------------------------------------------
#Mode Shell à partir d'ici
In [1]: from appv1.config import *
In [2]: from appv1.predict import predict2, create_vocab_text, build_bert_model
In [3]: from appv1.bert import get_config, BertModel,BertForchABSA, set_learned_params
In [4]: import torch
In [5]: config = get_config(file_path=BERT_CONFIG) #Chargement des paramètres de configuration BERT
In [6]: net_bert = BertModel(config) #Génération de modèles BERT
In [7]: net_trained = BertForchABSA(net_bert) #Combinez le classificateur négatif / positif avec le modèle BERT
In [8]: net_trained.load_state_dict(torch.load(MODEL_FILE, map_location='cpu')) #Faible poids appris
...:Faire
Out[8]: IncompatibleKeys(missing_keys=[], unexpected_keys=[])
In [9]: net_trained.eval() #Mettre en mode inférence
Out[9]:
BertForchABSA(
(bert): BertModel(
(embeddings): BertEmbeddings(
(word_embeddings): Embedding(32006, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): BertLayerNorm()
(dropout): Dropout(p=0.1)
)
(encoder): BertEncoder(
(layer): ModuleList(
(0): BertLayer(
(attention): BertAttention(
(selfattn): BertSelfAttention(
(query): Linear(in_features=768, out_features=768, bias=True)
(key): Linear(in_features=768, out_features=768, bias=True)
(value): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.1)
)
~~Comme il existe de nombreux résultats de sortie, certains sont omis.
(pooler): BertPooler(
(dense): Linear(in_features=768, out_features=768, bias=True)
(activation): Tanh()
)
)
(cls): Linear(in_features=768, out_features=2, bias=True)
)
In [10]: input_text = "En termes de résultat, le résultat ordinaire est dû à une baisse des intérêts sur prêts et des gains sur cessions de titres.
...:Il a diminué de 7 273 millions de yens à 67 413 millions de yens."
In [11]: result = predict2(input_text, net_trained).numpy()[0] #Exécuter l'inférence(La valeur de retour est négative ou positive
...:Tib)
['[UNK]', 'surface', 'À', 'OK je', 'sans parler de', 'Est', '、', '[UNK]', 'Revenu', 'Est', '、', 'Prêt', 'Argent', 'Intérêt', 'Ou', 'De valeur', 'Titres', 'vente', 'Profit', 'de', 'Diminution', 'À', 'Que', '、', 'Premier mandat', 'rapport', '[UNK]', 'Cercle', 'Diminution', 'de', '[UNK]', 'Cercle', 'Quand', 'Nari', 'Était']
[2, 1, 534, 8, 7779, 26207, 9, 6, 1, 7919, 9, 6, 15123, 306, 28611, 34, 27042, 4190, 3305, 8995, 5, 1586, 8, 52, 6, 4523, 2460, 1, 387, 1586, 5, 1, 387, 12, 105, 4561, 3]
In [12]: print(result)
0
Comme mentionné ci-dessus, si une valeur négative (0) ou positive (1) est renvoyée comme valeur de retour dans la variable de résultat sans aucune erreur, l'opération est OK.
Ensuite, créez un sérialiseur DRF. En outre, la sérialisation et la désérialisation dans DRF sont les processus suivants.
En traitement | sens |
---|---|
Sérialiser | Conversion de chaînes JSON, etc. en objets de modèle Django |
Désérialiser | Conversion d'un objet de modèle au format JSON, etc. |
Dans ce cas, le modèle de Django n'est pas géré, donc la conversion en objet modèle n'est pas effectuée. Au lieu de cela, créez un sérialiseur qui dit "** Recevez le texte d'entrée que vous voulez faire un jugement négatif / positif, entrez-le dans le modèle entraîné par BERT et sortez le résultat de l'inférence **".
Il existe trois principaux types de sérialiseurs DRF, mais cette fois, nous voulons implémenter un traitement personnalisé qui ne dépend pas du modèle, nous allons donc utiliser la ** classe Serializer ** de rest_framework.
|traitement du sérialiseur|sens| |:--|:--|:--| | ModelSerializer |Utiliser un seul objet modèle| |Serializer|Gérez une seule ressource ou implémentez un traitement personnalisé indépendant du modèle| |ListSerializer|Gérez plusieurs ressources|
Créez un fichier appelé serializers.py directement sous appv1 et ajoutez le code suivant.
appv1\serializers.py
from rest_framework import serializers
from appv1.config import *
from appv1.predict import predict2
from appv1.bert import get_config, BertModel,BertForchABSA
import torch
class BertPredictSerializer(serializers.Serializer):
"""Sérialiseur pour obtenir des résultats de classification BERT négatifs / positifs"""
input_text = serializers.CharField()
neg_pos = serializers.SerializerMethodField()
def get_neg_pos(self, obj):
config = get_config(file_path=BERT_CONFIG) #Chargement du fichier de configuration de bert
net_bert = BertModel(config) #Génération de modèles BERT
net_trained = BertForchABSA(net_bert) # #Combinez le classificateur négatif / positif avec le modèle BERT
net_trained.load_state_dict(torch.load(MODEL_FILE, map_location='cpu')) #Charger les poids appris
net_trained.eval() #Mettre en mode inférence
label = predict2(obj['input_text'], net_trained).numpy()[0] #Obtenir le résultat de l'inférence
return label
Tout d'abord, créez un sérialiseur appelé ** BertPredictSerializer ** en héritant de ** serializers.Serializer ** de rest_framework.
class BertPredictSerializer(serializers.Serializer):
"""Sérialiseur pour obtenir des résultats de classification BERT négatifs / positifs"""
input_text = serializers.CharField()
neg_pos = serializers.SerializerMethodField()
Un type de chaîne CharField est défini comme entrée (** input_text **). La sortie étant une valeur dynamique (résultat de l'inférence), la valeur du champ peut être déterminée par le résultat de la méthode. ** serializers.SerializerMethodField () ** est utilisé pour définir le champ ** neg_pos **. ..
Lorsque vous utilisez SerializerMethodField (), définissez le nom de la méthode appliquée comme ** get_ + field name **. Dans ce cas, nous définissons la méthode ** get_neg_pos **.
Si vous définissez un tel sérialiseur, la méthode get_neg_pos sera exécutée lorsque le texte d'entrée est POSTé dans la vue API à l'aide de BertPredictSerializer, et le résultat du traitement sera renvoyé sous forme de chaîne de caractères JSON.
Cette fois, nous voulons donner les données de phrase d'entrée au modèle d'apprentissage BERT et obtenir le résultat du jugement négatif / positif, le traitement suivant est donc décrit dans la méthode get_neg_pos.
def get_neg_pos(self, obj):
config = get_config(file_path=BERT_CONFIG) #Chargement du fichier de configuration de bert
net_bert = BertModel(config) #Génération de modèles BERT
net_trained = BertForchABSA(net_bert) # #Combinez le classificateur négatif / positif avec le modèle BERT
net_trained.load_state_dict(torch.load(MODEL_FILE, map_location='cpu')) #Charger les poids appris
net_trained.eval() #Mettre en mode inférence
label = predict2(obj['input_text'], net_trained).numpy()[0] #Obtenir le résultat de l'inférence
return label
Vous pouvez voir ce que nous faisons en regardant les commentaires, mais je l'ajouterai ci-dessous.
Les quatre classes de méthodes ci-dessus sont définies dans ** bert.py **. Chargez les paramètres du modèle entraîné avec ** net_trained.load_state_dict **. La méthode ** predict2 ** est définie dans ** predict.py **, et si vous passez la phrase d'entrée ** dans le premier argument et l'instance de modèle entraîné ** dans le deuxième argument, ** infer C'est une méthode ** qui renvoie le résultat (0 ou 1).
Ceci termine la création du sérialiseur.
La vue DRF peut être implémentée sous la forme d'une vue basée sur une classe ou d'une vue basée sur une fonction.
Cette fois, j'ai essayé d'utiliser une vue basée sur les classes appelée GenericAPIView.
from django.shortcuts import render
from rest_framework import generics, status
from rest_framework.response import Response
from appv1.serializers import BertPredictSerializer
class BertPredictAPIView(generics.GenericAPIView):
"""Classe de prédiction de classification négative / positive BERT"""
serializer_class = BertPredictSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(request.data)
return Response(serializer.data, status=status.HTTP_200_OK)
Il hérite de ** GenericAPIView ** et définit ** BertPredictAPIView **. Spécifiez la classe de sérialisation (BertPredictSerialzer) définie dans serializer.py dans l'attribut ** serializer_class **. Définissez ensuite la méthode ** post ** dans BertPredictAPIView et utilisez en interne la méthode ** get_serializer ** de GenericAPIView pour obtenir l'instance de sérialiseur utilisée pour la validation. Spécifiez ** request.data ** comme argument.
serializer = self.get_serializer(request.data)
** request.data ** fonctionne avec les méthodes "POST", "PUT" et "PATCH" pour traiter des données arbitraires. (Fonction similaire à request.POST) Enfin, spécifiez ** serializer.data ** comme argument de ** Response ** pour terminer la vue qui renvoie le résultat de l'inférence.
Enfin, ajoutez la définition d'URL dans drf \ urlspy.
from django.contrib import admin
from django.urls import path
from appv1 import views #add
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/predict/', views.BertPredictAPIView.as_view()), #add
]
La vue d'API BertPredict définie dans views.py est définie comme un modèle d'URL appelé api / v1 / predict. Vous avez maintenant défini ** http://127.0.0.1/api/v1/predict ** comme point de terminaison de l'API Rest.
À ce stade, exécutez une fois la migration de djagno et exécutez runserver.
python manage.py migrate
python manage.py runserver
Après cela, lorsque vous accédez à ** http://127.0.0.1:8000/api/v1/predict/**, l'écran suivant s'affiche.
Entrez le texte que vous souhaitez juger négatif / positif dans "** input text **" en bas de l'écran et appuyez sur le bouton ** POST ** pour faire une inférence en utilisant le modèle entraîné de BERT et renvoyer le résultat. ..
L'écran suivant montre le résultat de la saisie de la phrase «Dans Tokyu Community Co., Ltd., le stock de gestion a augmenté pour les appartements et les immeubles, ce qui entraîne une augmentation des ventes et des bénéfices» et en appuyant sur le bouton POST.
En tant que valeur de retour, ** input_text ** et ** neg_pos (0 ou 1) ** représentant négatif / positif sont renvoyés.
Dans l'exemple ci-dessus, positif (= 1) est renvoyé comme résultat de l'inférence.
Ensuite, essayez d'appeler l'API REST créée à partir du client local et recevez le résultat de l'inférence. Cette fois, le client et le serveur sont simplement exécutés sur le même PC.
Après avoir démarré le serveur de développement avec python manage.py runserver, ouvrez l'écran DOS etc. dans une autre fenêtre et exécutez la commande python pour permettre d'exécuter le code python et d'exécuter le code suivant.
import urllib.request
import urllib.parse
import json
def predict(input_text):
URL = "http://127.0.0.1:8000/api/v1/predict/"
values = {
"format": "json",
"input_text": input_text,
}
data = urllib.parse.urlencode({'input_text': input_text}).encode('utf-8')
request = urllib.request.Request(URL, data)
response = urllib.request.urlopen(request)
result= json.loads(response.read())
return result
input_text = "En termes de résultat, le revenu ordinaire a diminué de 7 273 millions de yens par rapport à l'exercice précédent pour s'établir à 67 413 millions de yens en raison d'une baisse des intérêts sur les prêts et des gains sur les ventes de titres."
result = predict(input_text)
print(result)
{'input_text': 'En termes de résultat, le revenu ordinaire a diminué de 7 273 millions de yens par rapport à l'exercice précédent pour s'établir à 67 413 millions de yens en raison d'une baisse des intérêts sur les prêts et des gains sur les ventes de titres.', 'neg_pos': 0}
print(result['input_text'])
En termes de résultat, le revenu ordinaire a diminué de 7 273 millions de yens par rapport à l'exercice précédent pour s'établir à 67 413 millions de yens en raison d'une baisse des intérêts sur les prêts et des gains sur les ventes de titres.
print(result['neg_pos'])
0
Spécifiez ** "http://127.0.0.1:8000/api/v1/predict/"** comme point de terminaison. Donnez json comme format et input_text comme données d'entrée aux valeurs de variable de type dictionnaire.
Après cela, si vous le lancez avec la méthode POST avec les données de phrase encodées pour le point de terminaison à l'aide de urllib.request.Request, la classe BertPredictAPIView définie dans views.py sera appelée et le processus se déplacera dans le flux de sérialisation → exécution de l'inférence Aller. Je vais convertir le résultat du traitement avec json.loads afin qu'il puisse être traité comme des données de type dictionnaire du côté python.
Ensuite, il sera converti en données de type dictionnaire comme indiqué ci-dessous, afin que vous puissiez accéder aux informations souhaitées par nom de clé (input_text, neg_pos).
{'input_text': 'En termes de résultat, le revenu ordinaire a diminué de 7 273 millions de yens par rapport à l'exercice précédent pour s'établir à 67 413 millions de yens en raison d'une baisse des intérêts sur les prêts et des gains sur les ventes de titres.', 'neg_pos': 0}
Enfin, créez une commande simple qui envoie automatiquement une grande quantité de données d'entrée à l'API REST et génère le résultat de l'inférence. Les fichiers csv et les programmes utilisés ci-dessous sont sous django-drf-dl / drf / tools / dans git repository.
Préparez le fichier test.csv suivant que vous souhaitez prédire. (Le nom de la colonne est INPUT)
Placez predict.py avec le code suivant dans le même dossier que test.csv.
import pandas as pd
import numpy as np
import urllib.request
import urllib.parse
import json
def predict(input_text):
URL = "http://127.0.0.1:8000/api/v1/predict/"
values = {
"format": "json",
"input_text": input_text,
}
data = urllib.parse.urlencode({'input_text': input_text}).encode('utf-8')
request = urllib.request.Request(URL, data)
response = urllib.request.urlopen(request)
result= json.loads(response.read())
return result['neg_pos'][1]
if __name__ == '__main__':
print("Start if __name__ == '__main__'")
print('load csv file ....')
df = pd.read_csv("test.csv", engine="python", encoding="utf-8-sig")
df["PREDICT"] = np.nan #Ajouter une colonne de prédiction
print('Getting prediction results ....')
for index, row in df.iterrows():
df.at[index, "PREDICT"] = predict(row['INPUT'])
print('save results to csv file')
df.to_csv("predicted_test .csv", encoding="utf-8-sig", index=False)
print('Processing terminated normally.')
Lorsque vous démarrez l'écran DOS et exécutez la commande suivante, test.csv est lu ligne par ligne et renvoyé à l'API REST pour recevoir le résultat de l'inférence.
python predict.py
----------------------------------
Start if __name__ == '__main__'
load csv file ....
Getting prediction results ....
save results to csv file
Processing terminated normally.
Lorsque les dernières données sont complétées, le fichier predicted_test.csv avec le résultat de l'inférence sera généré dans le même dossier.
Cette fois, j'ai créé une API REST qui juge les négatifs et les positifs simples à l'aide d'un modèle de classification binaire de DRF et BERT dans l'environnement local. À l'avenir, j'aimerais créer une API REST sur la plate-forme Azure et l'appliquer à des tâches telles que la multi-classification et la FAQ au lieu de la classification binaire.
Le DRF créé dans cet article a été créé en appliquant un peu basé sur le contenu du manuel Django REST Framework qui peut être utilisé sur le terrain. J'ai appris DRF avec ce livre pour la première fois cette fois, mais c'est un livre très utile, donc il est recommandé pour ceux qui veulent apprendre DRF à partir de maintenant.
Demain, c'est l'article du 21e jour du "Calendrier de l'Avent Django 2019 - Qiita" par ssh22. Je vous remercie!
Recommended Posts