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)