Formation et évaluation d’un modèle de classification de texte dans Microsoft Fabric
Dans ce notebook, nous montrons comment résoudre une tâche de classification de texte avec word2vec + modèle de régression linéaire sur Spark.
Important
Microsoft Fabric est actuellement en préversion. Certaines informations portent sur un produit en préversion susceptible d’être substantiellement modifié avant sa publication. Microsoft ne donne aucune garantie, expresse ou implicite, concernant les informations fournies ici.
L’exemple de jeu de données se compose de métadonnées relatives aux livres numérisés par la British Library en partenariat avec Microsoft. Il comprend des étiquettes générées par l’homme pour classer un livre comme « fiction » ou « non-fiction ». Nous utilisons ce jeu de données pour entraîner un modèle pour la classification des genres qui prédit si un livre est « fiction » ou « non-fiction » en fonction de son titre.
ID d’enregistrement BL | Type de ressource | Nom | Dates associées au nom | Type de nom | Rôle | Tous les noms | Titre | Titres de variantes | Titre de la série | Nombre dans la série | Pays de publication | Lieu de publication | Publisher | Date de publication | Édition | Description physique | Classification Dewey | Bl shelfmark | Rubriques | Genre | Langages | Notes | ID d’enregistrement BL pour la ressource physique | classification_id | user_id | created_at | subject_ids | annotator_date_pub | annotator_normalised_date_pub | annotator_edition_statement | annotator_genre | annotator_FAST_genre_terms | annotator_FAST_subject_terms | annotator_comments | annotator_main_language | annotator_other_languages_summaries | annotator_summaries_language | annotator_translation | annotator_original_language | annotator_publisher | annotator_place_pub | annotator_country | annotator_title | Lien vers un livre numérisé | Annotée |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
014602826 | Monographie | Yearsley, Ann | 1753-1806 | personne | Plus, Hannah, 1745-1833 [personne]; Yearsley, Ann, 1753-1806 [personne] | Poèmes à plusieurs reprises [Avec une lettre préliminaire d’Hannah More.] | England | London | 1786 | Note manuscrite de la quatrième édition | Magasin numérique 11644.d.32 | Anglais | 003996603 | False | |||||||||||||||||||||||||||||||
014602830 | Monographie | A, T. | personne | Oldham, John, 1653-1683 [personne]; A, T. [person] | Une satyre contre Vertue. (Un poème : censé être prononcé par un Town-Hector [Par John Oldham. Préface signée : T. A.]) | England | London | 1679 | 15 pages (4°) | Digital Store 11602.ee.10. (2.) | Anglais | 000001143 | False |
Étape 1 : Charger les données
Configurations de notebook
Installation des bibliothèques
Dans ce notebook, nous wordcloud
, qui doit d’abord être installé. Le noyau PySpark est redémarré après %pip install
. Nous devons donc l’installer avant d’exécuter d’autres cellules. Word Cloud est un package qui nous permet d’utiliser une technique de visualisation des données pour représenter des données de texte afin d’indiquer leur fréquence dans un texte donné.
# install wordcloud for text visualization
%pip install wordcloud
En définissant les paramètres suivants, nous pouvons facilement appliquer ce notebook à différents jeux de données.
IS_CUSTOM_DATA = False # if True, dataset has to be uploaded manually by user
DATA_FOLDER = "Files/title-genre-classification"
DATA_FILE = "blbooksgenre.csv"
# data schema
TEXT_COL = "Title"
LABEL_COL = "annotator_genre"
LABELS = ["Fiction", "Non-fiction"]
EXPERIMENT_NAME = "aisample-textclassification" # mlflow experiment name
Nous définissons également certains hyperparamètres pour l’entraînement du modèle. (NE modifiez pas ces paramètres, sauf si vous en connaissez la signification).
# hyper-params
word2vec_size = 128
min_word_count = 3
max_iter = 10
k_folds = 3
Importer les dépendances
import numpy as np
from itertools import chain
from wordcloud import WordCloud
import matplotlib.pyplot as plt
import seaborn as sns
import pyspark.sql.functions as F
from pyspark.ml import Pipeline
from pyspark.ml.feature import *
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.evaluation import (
BinaryClassificationEvaluator,
MulticlassClassificationEvaluator,
)
from synapse.ml.stages import ClassBalancer
from synapse.ml.train import ComputeModelStatistics
import mlflow
Télécharger le jeu de données et le charger sur Lakehouse
Ajoutez un Lakehouse au bloc-notes avant de l’exécuter.
if not IS_CUSTOM_DATA:
# Download demo data files into lakehouse if not exist
import os, requests
remote_url = "https://synapseaisolutionsa.blob.core.windows.net/public/Title_Genre_Classification"
fname = "blbooksgenre.csv"
download_path = f"/lakehouse/default/{DATA_FOLDER}/raw"
if not os.path.exists("/lakehouse/default"):
raise FileNotFoundError(
"Default lakehouse not found, please add a lakehouse and restart the session."
)
os.makedirs(download_path, exist_ok=True)
if not os.path.exists(f"{download_path}/{fname}"):
r = requests.get(f"{remote_url}/{fname}", timeout=30)
with open(f"{download_path}/{fname}", "wb") as f:
f.write(r.content)
print("Downloaded demo data files into lakehouse.")
Lire les données de Lakehouse
raw_df = spark.read.csv(f"{DATA_FOLDER}/raw/{DATA_FILE}", header=True, inferSchema=True)
display(raw_df.limit(20))
Étape 2 : traitement préalable des données
Propre de données
df = (
raw_df.select([TEXT_COL, LABEL_COL])
.where(F.col(LABEL_COL).isin(LABELS))
.dropDuplicates([TEXT_COL])
.cache()
)
display(df.limit(20))
Gérer les données déséquilibrés
cb = ClassBalancer().setInputCol(LABEL_COL)
df = cb.fit(df).transform(df)
display(df.limit(20))
Tokenize
## text transformer
tokenizer = Tokenizer(inputCol=TEXT_COL, outputCol="tokens")
stopwords_remover = StopWordsRemover(inputCol="tokens", outputCol="filtered_tokens")
## build the pipeline
pipeline = Pipeline(stages=[tokenizer, stopwords_remover])
token_df = pipeline.fit(df).transform(df)
display(token_df.limit(20))
Visualisation
Affichez un Wordcloud pour chaque classe.
# WordCloud
for label in LABELS:
tokens = (
token_df.where(F.col(LABEL_COL) == label)
.select(F.explode("filtered_tokens").alias("token"))
.where(F.col("token").rlike(r"^\w+$"))
)
top50_tokens = (
tokens.groupBy("token").count().orderBy(F.desc("count")).limit(50).collect()
)
# Generate a word cloud image
wordcloud = WordCloud(
scale=10,
background_color="white",
random_state=42, # Make sure the output is always the same for the same input
).generate_from_frequencies(dict(top50_tokens))
# Display the generated image the matplotlib way:
plt.figure(figsize=(10, 10))
plt.title(label, fontsize=20)
plt.axis("off")
plt.imshow(wordcloud, interpolation="bilinear")
Vectoriser
Nous utilisons word2vec pour vectoriser le texte.
## label transformer
label_indexer = StringIndexer(inputCol=LABEL_COL, outputCol="labelIdx")
vectorizer = Word2Vec(
vectorSize=word2vec_size,
minCount=min_word_count,
inputCol="filtered_tokens",
outputCol="features",
)
## build the pipeline
pipeline = Pipeline(stages=[label_indexer, vectorizer])
vec_df = (
pipeline.fit(token_df)
.transform(token_df)
.select([TEXT_COL, LABEL_COL, "features", "labelIdx", "weight"])
)
display(vec_df.limit(20))
Étape 3 : Formation et évaluation des modèles
Nous avons nettoyé le jeu de données, traité les données déséquilibrés, tokenisé le texte, affiché le nuage de mots et vectorisé le texte.
Ensuite, nous entraînons un modèle de régression linéaire pour classifier le texte vectorisé.
Fractionner le jeu de données en apprentissage et test
(train_df, test_df) = vec_df.randomSplit((0.8, 0.2), seed=42)
Créer le modèle
lr = (
LogisticRegression()
.setMaxIter(max_iter)
.setFeaturesCol("features")
.setLabelCol("labelIdx")
.setWeightCol("weight")
)
Entraîner le modèle avec la validation croisée
param_grid = (
ParamGridBuilder()
.addGrid(lr.regParam, [0.03, 0.1, 0.3])
.addGrid(lr.elasticNetParam, [0.0, 0.1, 0.2])
.build()
)
if len(LABELS) > 2:
evaluator_cls = MulticlassClassificationEvaluator
evaluator_metrics = ["f1", "accuracy"]
else:
evaluator_cls = BinaryClassificationEvaluator
evaluator_metrics = ["areaUnderROC", "areaUnderPR"]
evaluator = evaluator_cls(labelCol="labelIdx", weightCol="weight")
crossval = CrossValidator(
estimator=lr, estimatorParamMaps=param_grid, evaluator=evaluator, numFolds=k_folds
)
model = crossval.fit(train_df)
Évaluer le modèle
predictions = model.transform(test_df)
display(predictions)
log_metrics = {}
for metric in evaluator_metrics:
value = evaluator.evaluate(predictions, {evaluator.metricName: metric})
log_metrics[metric] = value
print(f"{metric}: {value:.4f}")
metrics = ComputeModelStatistics(
evaluationMetric="classification", labelCol="labelIdx", scoredLabelsCol="prediction"
).transform(predictions)
display(metrics)
# collect confusion matrix value
cm = metrics.select("confusion_matrix").collect()[0][0].toArray()
print(cm)
# plot confusion matrix
sns.set(rc={"figure.figsize": (6, 4.5)})
ax = sns.heatmap(cm, annot=True, fmt=".20g")
ax.set_title("Confusion Matrix")
ax.set_xlabel("Predicted label")
ax.set_ylabel("True label")
Modèle de journal et de chargement avec MLflow
Maintenant que nous disposons d’un modèle dont nous sommes satisfaits, nous pouvons l’enregistrer pour une utilisation ultérieure. Ici, nous utilisons MLflow pour journaliser les métriques/modèles et charger les modèles à des fins de prédiction.
# setup mlflow
mlflow.set_experiment(EXPERIMENT_NAME)
# log model, metrics and params
with mlflow.start_run() as run:
print("log model:")
mlflow.spark.log_model(
model,
f"{EXPERIMENT_NAME}-lrmodel",
registered_model_name=f"{EXPERIMENT_NAME}-lrmodel",
dfs_tmpdir="Files/spark",
)
print("log metrics:")
mlflow.log_metrics(log_metrics)
print("log parameters:")
mlflow.log_params(
{
"word2vec_size": word2vec_size,
"min_word_count": min_word_count,
"max_iter": max_iter,
"k_folds": k_folds,
"DATA_FILE": DATA_FILE,
}
)
model_uri = f"runs:/{run.info.run_id}/{EXPERIMENT_NAME}-lrmodel"
print("Model saved in run %s" % run.info.run_id)
print(f"Model URI: {model_uri}")
# load model back
loaded_model = mlflow.spark.load_model(model_uri, dfs_tmpdir="Files/spark")
# verify loaded model
predictions = loaded_model.transform(test_df)
display(predictions)