Initial addition of ms-ai-architect plugin to the open-source marketplace. Private content excluded: orchestrator/ (Linear tooling), docs/utredning/ (client investigation), generated test reports and PDF export script. skill-gen tooling moved from orchestrator/ to scripts/skill-gen/. Security scan: WARNING (risk 20/100) — no secrets, no injection found. False positive fixed: added gitleaks:allow to Python variable reference in output-validation-grounding-verification.md line 109. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
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
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="incident_type",
minority_class="accident",
target_ratio=0.3 # 30% ulykker 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="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
# 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
- Set up an image labeling project -- Bildedatamerking i Azure ML
- Set up a text labeling project -- Tekstdatamerking i Azure ML
- Labeling images and text documents -- Merkerverktoy og ML-assistert merking
- Prepare data for computer vision tasks -- Data for AutoML-bildemodeller
- Label text data for training -- Merking for Custom Language Models
- Create and explore datasets with labels -- 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.