Oplæring og evaluering af en model til tekstklassificering i Microsoft Fabric

I denne notesbog demonstrerer vi, hvordan du løser en tekstklassificeringsopgave med word2vec + lineær regressionsmodel på Spark.

Vigtigt

Microsoft Fabric findes i øjeblikket i PRØVEVERSION. Disse oplysninger er relateret til et foreløbig produkt, der kan blive ændret væsentligt, før det udgives. Microsoft giver ingen garantier, udtrykt eller stiltiende, med hensyn til de oplysninger, der er angivet her.

Eksempeldatasættet består af metadata vedrørende bøger, der er digitaliseret af British Library i samarbejde med Microsoft. Det omfatter mærkater, der er genereret af mennesker, til klassificering af en bog som "fiction" eller "non-fiction". Vi bruger dette datasæt til at oplære en model til genreklassificering, der forudsiger, om en bog er 'fiction' eller 'non-fiction' baseret på dens titel.

BL-post-id Ressourcetype Name Datoer, der er knyttet til navnet Navnstype Rolle Alle navne Titel Varianttitler Serietitel Tal i serie Udgivelsesland Udgivelsessted Publisher Udgivelsesdato Udgave Fysisk beskrivelse Dewey-klassificering BL-hyldemærke Emner Genre Sprog Noter Post-id for bl for fysisk ressource 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 Link til digitaliseret bog Kommenteret
014602826 Monografi Yearsley, Ann 1753-1806 Person Mere, Hannah, 1745-1833 [person]; Yearsley, Ann, 1753-1806 [person] Digte ved flere lejligheder [Med en prefatory brev af Hannah More.] England London 1786 4. udgave MANUSKRIPT Digital butik 11644.d.32 Engelsk 003996603 Falsk
014602830 Monografi A, T. Person Oldham, John, 1653-1683 [person]; A, T. [person] En Satyr mod Vertue. (Et digt: formodes at blive talt af en Town-Hector [Af John Oldham. Forordet underskrevet: T. A.]) England London 1679 15 sider (4°) Digital Store 11602.ee.10. (2.) Engelsk 000001143 Falsk

Trin 1: Indlæs dataene

Konfigurationer af notesbog

Installér biblioteker

I denne notesbog skal vi wordcloud, som først skal installeres. PySpark-kernen genstartes efter %pip install, og derfor skal vi installere den, før vi kører andre celler. Word Cloud er en pakke, der giver os mulighed for at bruge en teknik til datavisualisering til at repræsentere tekstdata for at angive hyppigheden i et bestemt stykke tekst.

# install wordcloud for text visualization
%pip install wordcloud

Ved at definere følgende parametre kan vi nemt anvende denne notesbog på forskellige datasæt.

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å nogle hyperparametre til modeltræning. (Du må ikke ændre disse parametre, medmindre du er klar over betydningen).

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

Importafhængigheder

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

Download datasæt, og upload til Lakehouse

Føj et Lakehouse til notesbogen, før du kø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.")

Læs data fra Lakehouse

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

display(raw_df.limit(20))

Trin 2: Forbehandler data

Data rene

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

Vectorize

Vi bruger word2vec til at 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))

Trin 3: Modeltræning og -evaluering

Vi har renset datasættet, behandlet ubalancerede data, tokeniseret teksten, vist ordsky og vektoriseret teksten.

Derefter oplæres en lineær regressionsmodel til at klassificere den vektoriserede tekst.

Opdel datasæt i tog og test

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

Opret modellen

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

Oplær model med krydsvalidering

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

Logér og indlæs model med MLflow

Nu, hvor vi har en model, vi er tilfredse med, kan vi gemme den til senere brug. Her bruger vi MLflow til at logføre målepunkter/modeller og indlæse modeller tilbage til forudsigelse.

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