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)