ms-ai-architect/skills/ms-ai-engineering/references/data-engineering/data-sampling-labeling.md
Kjell Tore Guttormsen 544934dc57
refactor(examples): replace sector-specific example material with generic, fictitious examples
Reference files, test fixtures, the playground demo project and one design
document now use generic, fictitious examples (buildings, energy, water,
grants, municipal services). The playground demo (17 fixtures plus the
embedded demo state) tells one consistent story: a municipal customer
chatbot that pre-screens housing-benefit applications, classified under
Annex III point 5(a). The embedded demo copies were edited in place rather
than regenerated, because they already carry newer AI Act dates than the
fixture files.

Legal text is unchanged. Test semantics are unchanged. Four dark-theme
onboarding screenshots with outdated placeholder text are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:03:53 +02:00

18 KiB

Data Sampling and Labeling Strategies

Last updated: 2026-06-24 Status: GA Category: Data Engineering for AI Type: reference Source: https://learn.microsoft.com/azure/machine-learning/how-to-label-data


Innhold

Introduksjon

Kvaliteten pa treningsdata er den viktigste faktoren for ytelsen til ML-modeller. Effektiv datasampling sikrer at treningsdatasettet er representativt og balansert, mens systematisk datamerking (labeling) gir modellene de korrekte signalene a laere fra. Azure Machine Learning tilbyr en komplett plattform for datamerking med stotte for bade bilde- og tekstdata, inkludert ML-assistert merking som akselererer prosessen vesentlig.

For norsk offentlig sektor, der data ofte er ubalansert (f.eks. svaert fa svindeltilfeller vs. legitime transaksjoner, eller sjaeldne hendelser i driftsdata), er stratifisert sampling og aktiv laering spesielt viktig. Riktig sampling reduserer merkebehovet med 50-80%, noe som sparer bade tid og kostnader i prosjekter med stramme budsjetter.

Denne referansen dekker hele livssyklusen fra datautvalg gjennom merkeprosesser til kvalitetskontroll, med fokus pa teknikker som er relevante for Microsoft AI-stakken og Azure Machine Learning.


Stratified Sampling for Class Balance

Problemet med ubalanserte datasett

Scenario Positiv klasse Negativ klasse Ubalanse-ratio
Svindeldeteksjon 0.1% svindel 99.9% legitim 1:1000
Reinnleggelsesprediksjon 2% reinnlagt 98% ikke reinnlagt 1:50
Dokumentklassifisering 5% sensitiv 95% ikke-sensitiv 1:19
Feildeteksjon (IoT) 0.5% feil 99.5% normal 1:200

Stratifisert sampling i PySpark

from pyspark.sql import functions as F

def stratified_sample(df, label_column, sample_fractions, seed=42):
    """
    Utfor stratifisert sampling for a balansere klasser.

    Args:
        df: Input DataFrame
        label_column: Kolonnen som inneholder klassen
        sample_fractions: Dict med {klasse: samplingandel}
        seed: Random seed for reproduserbarhet
    """
    sampled = df.sampleBy(label_column, fractions=sample_fractions, seed=seed)
    return sampled

# Eksempel: Balanser svindeldatasett
# Original: 99.9% legitim, 0.1% svindel
sample_fractions = {
    "legitimate": 0.01,   # Sample 1% av legitime (reduser fra 99.9k til ~1k)
    "fraud": 1.0          # Behold alle svindeltilfeller (~100)
}

balanced_df = stratified_sample(
    df_transactions,
    label_column="transaction_type",
    sample_fractions=sample_fractions
)

print(f"Original: {df_transactions.count()} rader")
print(f"Balansert: {balanced_df.count()} rader")
print("Klassefordeling:")
balanced_df.groupBy("transaction_type").count().show()

Oversampling og undersampling-teknikker

def oversample_minority_class(df, label_column, minority_class, target_ratio=0.5):
    """
    Oversample minoritetsklassen ved a duplisere rader.

    Args:
        target_ratio: Onsket andel av minoritetsklassen
    """
    class_counts = df.groupBy(label_column).count().collect()
    counts = {row[label_column]: row["count"] for row in class_counts}

    minority_count = counts[minority_class]
    majority_count = sum(v for k, v in counts.items() if k != minority_class)

    # Beregn hvor mange ganger minoritetsklassen ma dupliseres
    desired_minority = int(majority_count * target_ratio / (1 - target_ratio))
    oversample_factor = desired_minority / minority_count

    # Oversample
    minority_df = df.filter(F.col(label_column) == minority_class)
    majority_df = df.filter(F.col(label_column) != minority_class)

    oversampled = minority_df.sample(
        withReplacement=True,
        fraction=oversample_factor,
        seed=42
    )

    return majority_df.unionByName(oversampled)

# Bruk
balanced = oversample_minority_class(
    df_training,
    label_column="readmission_status",
    minority_class="readmitted",
    target_ratio=0.3  # 30% reinnleggelser i treningsdatasettet
)

SMOTE-lignende syntetisk oversampling

from pyspark.ml.feature import VectorAssembler
from pyspark.ml.clustering import KMeans
import numpy as np

def synthetic_oversampling(df, feature_columns, label_column, minority_class, n_synthetic):
    """
    Generer syntetiske minoritetseksempler basert pa naeromrade-interpolering.
    """
    minority_df = df.filter(F.col(label_column) == minority_class)

    # Vektoriser features
    assembler = VectorAssembler(inputCols=feature_columns, outputCol="features")
    vectorized = assembler.transform(minority_df)

    # For hver minoritetsrad: finn naermeste nabo og interpolder
    # Forenklet implementering med KMeans for cluster-sentre
    kmeans = KMeans(k=min(n_synthetic, minority_df.count()), seed=42)
    model = kmeans.fit(vectorized)

    # Bruk cluster-sentrene som syntetiske punkter
    centers = model.clusterCenters()

    synthetic_rows = []
    for center in centers:
        row = {col: float(center[i]) for i, col in enumerate(feature_columns)}
        row[label_column] = minority_class
        row["_synthetic"] = True
        synthetic_rows.append(row)

    synthetic_df = spark.createDataFrame(synthetic_rows)
    return df.withColumn("_synthetic", F.lit(False)).unionByName(synthetic_df, allowMissingColumns=True)

Active Learning and Uncertainty Sampling

Prinsippet bak aktiv laering

Aktiv laering velger de mest informative eksemplene for merking, i stedet for a merke tilfeldig:

+-- Umerkede data (pool) --+
|                           |
| [Hogt sikker] -> Hopp over, allerede laert
| [Moderat sikker] -> Hopp over
| [Usikker] -> MERK DENNE! <-- Mest laererik
| [Veldig usikker] -> MERK DENNE! <-- Hoyest prioritet
|                           |
+---------------------------+

Usikkerhetssamplings-strategier

Strategi Formel Best for
Least Confidence 1 - max(P(y|x)) Generell klassifisering
Margin Sampling P(y1|x) - P(y2|x) Naere beslutningsgrenser
Entropy Sampling -sum(P(y|x) * log P(y|x)) Multi-class problemer
Query-by-Committee Uenighet mellom modeller Ensemble-basert

Implementering av aktiv laering

import numpy as np
from sklearn.ensemble import RandomForestClassifier

class ActiveLearner:
    """
    Aktiv laering med usikkerhetssampling for iterativ datamerking.
    """

    def __init__(self, model=None, strategy="entropy"):
        self.model = model or RandomForestClassifier(n_estimators=100)
        self.strategy = strategy
        self.labeled_indices = []
        self.labels = []

    def initial_sample(self, X, n_initial=100):
        """Velg et tilfeldig initialt treningssett."""
        indices = np.random.choice(len(X), n_initial, replace=False)
        self.labeled_indices = list(indices)
        return indices

    def query(self, X, n_samples=50):
        """Velg de mest informative eksemplene for merking."""
        # Prediksjonssannsynligheter for umerkede data
        unlabeled_mask = np.ones(len(X), dtype=bool)
        unlabeled_mask[self.labeled_indices] = False
        unlabeled_indices = np.where(unlabeled_mask)[0]

        if len(unlabeled_indices) == 0:
            return np.array([])

        X_unlabeled = X[unlabeled_indices]
        probas = self.model.predict_proba(X_unlabeled)

        # Beregn usikkerhetsscorer
        if self.strategy == "entropy":
            scores = -np.sum(probas * np.log(probas + 1e-10), axis=1)
        elif self.strategy == "least_confidence":
            scores = 1 - np.max(probas, axis=1)
        elif self.strategy == "margin":
            sorted_probas = np.sort(probas, axis=1)
            scores = 1 - (sorted_probas[:, -1] - sorted_probas[:, -2])

        # Velg top-n mest usikre
        top_indices = np.argsort(scores)[-n_samples:]
        return unlabeled_indices[top_indices]

    def teach(self, X, y, indices, labels):
        """Oppdater modellen med nylig merkede data."""
        self.labeled_indices.extend(indices)
        self.labels.extend(labels)

        X_labeled = X[self.labeled_indices]
        y_labeled = np.array(self.labels)

        self.model.fit(X_labeled, y_labeled)

# Brukseksempel
learner = ActiveLearner(strategy="entropy")

# Runde 1: Tilfeldig initialt sett
initial_idx = learner.initial_sample(X_pool, n_initial=100)
initial_labels = get_labels_from_labelers(X_pool[initial_idx])
learner.teach(X_pool, None, initial_idx, initial_labels)

# Runde 2-N: Aktiv laering
for round_num in range(10):
    query_idx = learner.query(X_pool, n_samples=50)
    new_labels = get_labels_from_labelers(X_pool[query_idx])
    learner.teach(X_pool, None, query_idx, new_labels)
    print(f"Runde {round_num + 1}: Totalt merket = {len(learner.labeled_indices)}")

Crowdsourcing and Labeling Platforms

Azure Machine Learning Data Labeling

Azure ML tilbyr en komplett merkeplattform med stotte for:

Funksjon Beskrivelse
Bildeklassifisering Multi-class og multi-label
Objektdeteksjon Bounding boxes
Instanssegmentering Polygoner
Semantisk segmentering Piksel-niva (preview)
Tekstklassifisering Single og multi-label
Named Entity Recognition Tekst-span-merking

Opprette et merkeprosjekt

# Azure ML SDK v2 - Opprett bildeklassifiseringsprosjekt
from azure.ai.ml import MLClient
from azure.ai.ml.entities import DataLabelingJob

# Opprett klient
ml_client = MLClient(credential, subscription_id, resource_group, workspace_name)

# Definer merkeprosjekt
labeling_job = DataLabelingJob(
    display_name="waste-type-classification",
    description="Klassifiser avfallstyper fra bilder ved gjenvinningsstasjon",
    labeling_job_type="ImageClassificationMulticlass",
    data={"uri": "azureml://datastores/images/paths/waste_images/"},
    labels={
        "classes": [
            {"name": "paper", "display_name": "Papir"},
            {"name": "plastic", "display_name": "Plast"},
            {"name": "glass", "display_name": "Glass"},
            {"name": "metal", "display_name": "Metall"},
            {"name": "food", "display_name": "Matavfall"},
            {"name": "hazardous", "display_name": "Farlig avfall"},
            {"name": "other", "display_name": "Annet"}
        ]
    },
    properties={
        "ml_assist_enabled": True,  # Aktiver ML-assistert merking
        "consensus_labeling_enabled": True,  # Krev konsensus
        "min_label_count": 2  # Minimum 2 merkere per bilde
    }
)

# Opprett prosjekt
created_job = ml_client.labeling_jobs.begin_create_or_update(labeling_job)

ML-Assisted Labeling

ML-assistert merking i Azure ML akselererer prosessen gjennom to faser:

Fase 1: CLUSTERING
+-- Merkere merker ~300 bilder manuelt
+-- ML-modell grupperer lignende bilder
+-- Merkere ser klynger av like bilder (raskere merking)

Fase 2: PRE-LABELING
+-- Modell trenes pa merkede data
+-- Modell foreslaar etiketter for umerkede bilder
+-- Merkere bekrefter/korrigerer forhondsetiketter
+-- Prosessen gjentas iterativt

Viktige hensyn:

  • Transfer learning akselererer opplaering: Noen ganger trengs kun 300 merkede eksempler
  • Konsensus-etiketter brukes for trening naar aktivert
  • Bilder shuffles tilfeldig for a redusere bias
  • Tekstinnholdet begrenses til ~128 ord for treningseffektivitet

Quality Control and Inter-Rater Agreement

Konsensus-merking

# Implementer konsensusbasert kvalitetskontroll
from collections import Counter

def calculate_inter_rater_agreement(labels_per_item: dict) -> dict:
    """
    Beregn inter-rater agreement (IRA) for merkede data.

    Args:
        labels_per_item: {item_id: [label_from_rater1, label_from_rater2, ...]}

    Returns:
        Statistikk over enighet
    """
    agreements = []
    disagreements = []

    for item_id, labels in labels_per_item.items():
        counter = Counter(labels)
        most_common_label, most_common_count = counter.most_common(1)[0]
        total_raters = len(labels)

        agreement_ratio = most_common_count / total_raters

        if agreement_ratio >= 0.8:
            agreements.append({
                "item_id": item_id,
                "consensus_label": most_common_label,
                "agreement": agreement_ratio
            })
        else:
            disagreements.append({
                "item_id": item_id,
                "labels": dict(counter),
                "agreement": agreement_ratio
            })

    total = len(labels_per_item)
    return {
        "total_items": total,
        "agreed": len(agreements),
        "disagreed": len(disagreements),
        "agreement_rate": round(len(agreements) / max(total, 1) * 100, 1),
        "disagreed_items": disagreements[:10]  # Vis topp 10 uenigheter
    }

Cohens Kappa for kvalitetsmalinger

from sklearn.metrics import cohen_kappa_score

def evaluate_labeler_quality(rater1_labels, rater2_labels):
    """
    Beregn Cohens Kappa mellom to merkere.

    Tolkning:
    - < 0.20: Darlig enighet
    - 0.21-0.40: Moderat enighet
    - 0.41-0.60: Moderat enighet
    - 0.61-0.80: Substansiell enighet
    - 0.81-1.00: Naer perfekt enighet
    """
    kappa = cohen_kappa_score(rater1_labels, rater2_labels)

    if kappa < 0.40:
        quality = "LAV - Gjennomga retningslinjer og gi oppfolging"
    elif kappa < 0.60:
        quality = "MODERAT - Akseptabel for screening, ikke for endelig trening"
    elif kappa < 0.80:
        quality = "GOD - Akseptabel for de fleste ML-oppgaver"
    else:
        quality = "UTMERKET - Hoy kvalitet for treningsdata"

    return {"kappa": round(kappa, 3), "quality": quality}

# Eksempel
result = evaluate_labeler_quality(
    rater1_labels=["positive", "negative", "positive", "neutral", "positive"],
    rater2_labels=["positive", "negative", "neutral", "neutral", "positive"]
)

Kvalitetskontrollpipeline

1. Initial merking (2-3 merkere per element)
        |
2. Beregn inter-rater agreement (IRA)
        |
3. IRA >= 80%? --> Bruk konsensus-etikett
        |
4. IRA < 80%? --> Send til ekspert-merker (adjudicator)
        |
5. Ekspert avgjer endelig etikett
        |
6. Oppdater merkeretningslinjer basert pa uenigheter
        |
7. Re-tren ML-assist-modell med nye etiketter

Feedback Loops for Continuous Labeling

Produksjonsdata tilbake til merking

def identify_candidates_for_relabeling(model, new_data_df, confidence_threshold=0.6):
    """
    Identifiser prediksjoner med lav konfidens for manuell gjennomgang.
    """
    predictions = model.predict_proba(new_data_df)

    low_confidence = new_data_df.filter(
        F.col("prediction_confidence") < confidence_threshold
    )

    # Prioriter etter usikkerhet
    candidates = low_confidence.orderBy(F.col("prediction_confidence").asc())

    # Legg til i merke-ko
    candidates.select(
        "record_id", "features", "prediction", "prediction_confidence"
    ).write.format("delta").mode("append") \
        .saveAsTable("lakehouse.default.labeling_queue")

    return candidates.count()

# Kjor daglig
n_candidates = identify_candidates_for_relabeling(
    model=production_model,
    new_data_df=todays_predictions,
    confidence_threshold=0.6
)
print(f"{n_candidates} nye elementer lagt til merke-koen")

Drift-deteksjon og re-merking

def detect_label_drift(historical_labels_df, recent_labels_df, columns):
    """
    Oppdager endringer i etikettdistribusjon over tid.
    """
    from scipy.stats import chi2_contingency

    for col in columns:
        hist_dist = historical_labels_df.groupBy(col).count().toPandas()
        recent_dist = recent_labels_df.groupBy(col).count().toPandas()

        # Chi-kvadrat-test
        contingency = hist_dist.merge(recent_dist, on=col, suffixes=["_hist", "_recent"])
        chi2, p_value, dof, expected = chi2_contingency(
            contingency[["count_hist", "count_recent"]].values.T
        )

        if p_value < 0.05:
            print(f"DRIFT OPPDAGET i '{col}': p={p_value:.4f}")
            print(f"  Historisk: {dict(zip(hist_dist[col], hist_dist['count']))}")
            print(f"  Nylig:    {dict(zip(recent_dist[col], recent_dist['count']))}")

Referanser


For arkitekten

  • Bruk denne referansen naar kunder planlegger datamerkings-prosjekter for ML-modeller, eller naar de trenger strategier for a hondtere ubalanserte datasett.
  • Azure ML Data Labeling er forstevalget for merkingsprosjekter i Microsoft-stakken. ML-assistert merking kan redusere manuelt arbeid med 50-80% etter initiell opplaering.
  • Aktiv laering bor alltid vurderes for store umerkede datasett -- det reduserer merkekostnader dramatisk ved a prioritere de mest informative eksemplene.
  • Kvalitetskontroll er ikke valgfritt: Krev konsensus mellom merkere (minimum 2), mal inter-rater agreement, og ha en ekspert-adjudicator for uenigheter.
  • For norsk offentlig sektor: Vurder personvernaspekter ved merking av data som kan inneholde personopplysninger. Bruk PII-deteksjon for a fjerne sensitiv informasjon for merkerne ser dataene.