# Data Sampling and Labeling Strategies **Last updated:** 2026-02 **Status:** GA **Category:** Data Engineering for AI --- ## 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 trafikkdata), 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 | | Ulykkesprediksjon | 2% ulykker | 98% normal trafikk | 1:50 | | Dokumentklassifisering | 5% sensitiv | 95% ikke-sensitiv | 1:19 | | Feildeteksjon (IoT) | 0.5% feil | 99.5% normal | 1:200 | ### Stratifisert sampling i PySpark ```python 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 ```python 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="incident_type", minority_class="accident", target_ratio=0.3 # 30% ulykker i treningsdatasettet ) ``` ### SMOTE-lignende syntetisk oversampling ```python 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 ```python 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 ```python # 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="traffic-sign-classification", description="Klassifiser trafikkskilt fra vegkamera", labeling_job_type="ImageClassificationMulticlass", data={"uri": "azureml://datastores/images/paths/traffic_signs/"}, labels={ "classes": [ {"name": "speed_limit", "display_name": "Fartsgrense"}, {"name": "stop", "display_name": "Stopp"}, {"name": "yield", "display_name": "Vikeplikt"}, {"name": "no_entry", "display_name": "Innkjoring forbudt"}, {"name": "pedestrian", "display_name": "Fotgjenger"}, {"name": "construction", "display_name": "Veiarbeid"}, {"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 ```python # 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 ```python 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 ```python 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 ```python 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 - [Set up an image labeling project](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-create-image-labeling-projects) -- Bildedatamerking i Azure ML - [Set up a text labeling project](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-create-text-labeling-projects) -- Tekstdatamerking i Azure ML - [Labeling images and text documents](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-label-data) -- Merkerverktoy og ML-assistert merking - [Prepare data for computer vision tasks](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-prepare-datasets-for-automl-images) -- Data for AutoML-bildemodeller - [Label text data for training](https://learn.microsoft.com/en-us/azure/ai-services/language-service/custom-text-classification/how-to/tag-data) -- Merking for Custom Language Models - [Create and explore datasets with labels](https://learn.microsoft.com/en-us/azure/machine-learning/how-to-use-labeled-dataset) -- Bruk av merkede datasett i Azure ML --- ## For Cosmo - **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.