Opplæring og evaluering av en tekstklassifiseringsmodell i Microsoft Fabric

I denne notatblokken demonstrerer vi hvordan du løser en tekstklassifiseringsoppgave med word2vec + lineær regresjonsmodell på Spark.

Viktig

Microsoft Fabric er for øyeblikket i FORHÅNDSVERSJON. Denne informasjonen er knyttet til et forhåndsutgitt produkt som kan endres vesentlig før det utgis. Microsoft gir ingen garantier, uttrykt eller underforstått, med hensyn til informasjonen som er oppgitt her.

Eksempeldatasettet består av metadata relatert til bøker digitalisert av British Library i samarbeid med Microsoft. Den inneholder menneskelige genererte etiketter for å klassifisere en bok som "fiksjon" eller "sakprosa". Vi bruker dette datasettet til å lære opp en modell for sjangerklassifisering som forutsier om en bok er "fiksjon" eller "sakprosa" basert på tittelen.

BL-post-ID Ressurstype Navn Datoer knyttet til navn Navnetype Rolle Alle navn Tittel Varianttitler Serietittel Tall i serier Publiseringsland Publiseringssted Publisher Publiseringsdato Edition Fysisk beskrivelse Dewey-klassifisering BL-hyllemerke Emner Sjanger Språk Notater BL-post-ID for fysisk ressurs 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 Kobling til digitalisert bok Kommenterte
014602826 Monografi Yearsley, Ann 1753-1806 person Mer, Hannah, 1745-1833 [person]; Yearsley, Ann, 1753-1806 [person] Dikt ved flere anledninger [Med et prefatory brev av Hannah More.] England London 1786 Manuskriptnotat for fjerde utgave Digital Store 11644.d.32 Engelsk 003996603 Usant
014602830 Monografi A, T. person Oldham, John, 1653-1683 [person]; A, T. [person] En Satyr mot Vertue. (Et dikt: ment å bli snakket av en Town-Hector [Av John Oldham. Forordet signert: T. A.]) England London 1679 15 sider (4°) Digital Store 11602.ee.10. (2.) Engelsk 000001143 Usant

Trinn 1: Laste inn dataene

Notatblokkkonfigurasjoner

Installere biblioteker

I denne notatblokken må vi wordcloud, som først må installeres. PySpark-kjernen startes på nytt etter %pip install, og derfor må vi installere den før vi kjører andre celler. Word Cloud er en pakke som gjør det mulig for oss å bruke en datavisualiseringsteknikk for å representere tekstdata for å indikere hyppigheten i en gitt tekstdel.

# install wordcloud for text visualization
%pip install wordcloud

Ved å definere følgende parametere kan vi enkelt bruke denne notatblokken på forskjellige datasett.

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

Vi definerer også noen hyperparametere for modellopplæring. (IKKE endre disse parameterne med mindre du er klar over betydningen).

# hyper-params
word2vec_size = 128
min_word_count = 3
max_iter = 10
k_folds = 3

Importavhengigheter

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

Last ned datasett og last opp til Lakehouse

Legg til et Lakehouse i notatblokken før du kjører den.

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

Les data fra Lakehouse

raw_df = spark.read.csv(f"{DATA_FOLDER}/raw/{DATA_FILE}", header=True, inferSchema=True)

display(raw_df.limit(20))

Trinn 2: Forhåndsbelønnede data

Rengjør data

df = (
    raw_df.select([TEXT_COL, LABEL_COL])
    .where(F.col(LABEL_COL).isin(LABELS))
    .dropDuplicates([TEXT_COL])
    .cache()
)

display(df.limit(20))

Håndtere ubalanserte data

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

Visualisering

Vis en Wordcloud for hver klasse.

# 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")

Vektorisere

Vi bruker word2vec til å vektorisere tekst.

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

Trinn 3: Modellopplæring og evaluering

Vi har renset datasettet, behandlet ubalanserte data, tokenisert teksten, vist ordet sky og vektorisert teksten.

Deretter kalibrerer vi en lineær regresjonsmodell for å klassifisere den vektoriserte teksten.

Del datasett i kalibrer og test

(train_df, test_df) = vec_df.randomSplit((0.8, 0.2), seed=42)

Opprett modellen

lr = (
    LogisticRegression()
    .setMaxIter(max_iter)
    .setFeaturesCol("features")
    .setLabelCol("labelIdx")
    .setWeightCol("weight")
)

Kalibrer modell med kryssvalidering

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)

Evaluer modellen

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

Logg og last inn modell med MLflow

Nå som vi har en modell vi er fornøyd med, kan vi lagre den til senere bruk. Her bruker vi MLflow til å logge måledata/modeller og laste inn modeller tilbake for prognose.

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