Case Study / 2025

Pharmazeutische Nebenwirkungs-Klassifikation

Ein produktionsreifes Python-Paket, das Freitext-Beschreibungen von Arzneimittel-Nebenwirkungen in eine MedDRA-orientierte Taxonomie aus zehn klinischen Kategorien klassifiziert.

3 min Lesezeit

Thumbnail Pharmazeutische Nebenwirkungs-Klassifikation mit gestapelten Klassifikationsbalken

Problem.

Die manuelle Triage von pharmazeutischen Nebenwirkungs-Berichten ist bei hohem Berichtsvolumen langsam, inkonsistent und teuer. Pharmakovigilanz-Teams, die diese Berichte nach standardisierten Taxonomien klassifizieren, sind durch das Volumen ausgebremst, und die Klassifikationsqualität schwankt von Analyst zu Analyst.

Die Frage hinter diesem Projekt: Kann ein Multi-Class-Classifier die Qualität menschlicher Triage genau genug treffen, um als Decision-Support-Tool sinnvoll deployt zu werden? Und kann in einer regulierten Domäne wie der Pharmakovigilanz die erklärbare Baseline nahe genug an das komplexere Modell herankommen, dass man die erklärbare Variante ausliefert?

Mein Beitrag.

Eigenständig, end-to-end:

  • Die Feature-Pipeline gebaut. TF-IDF-Vektorisierung von Medikamentennamen, Zusammensetzung, Indikationen und Nebenwirkungs-Text, dazu Ordinal-Encoding der Hersteller (mit sicherer Behandlung unbekannter Hersteller zur Inferenz-Zeit) und Review-Prozent-Features, alles hinter einem einzigen sklearn ColumnTransformer + FeatureUnion.
  • Zwei komplementäre Modelle auf 11.825 marktverfügbaren Medikamenten trainiert, die einer MedDRA-orientierten Taxonomie aus zehn klinischen Kategorien zugeordnet sind. Random Forest als primäres Modell, Logistic Regression als interpretierbarer Vergleich.
  • Beide in einer serialisierbaren sklearn Pipeline verpackt, sodass joblib.dump ein einziges End-to-End-Artefakt erzeugt und die Inferenz keine manuelle Feature-Vorbereitung mehr braucht.
  • Jeden Hyperparameter in eine YAML-Config ausgelagert, in frozen Dataclasses geladen. Keine Magic Numbers im Code.
  • Das CLI (train, predict) und die pytest-Suite geschrieben (synthetische 60-Zeilen-Fixture, damit Tests nicht von der 4-MB-xls abhängen).
  • Die GitHub-Actions-Matrix-CI auf Python 3.10, 3.11 und 3.12 aufgesetzt, mit ruff- und black-Gating.

Architektur.

+------------------+    +-----------------------+    +------------------+
| Medicine_Details | -> | Category mapping      | -> | Stratified split |
| (.xls, 11,825)   |    | (10 MedDRA-aligned    |    | (80/20, seed 42) |
+------------------+    |  categories)          |    +------------------+
                        +-----------------------+              |
                                                               v
                                              +----------------------------+
                                              | sklearn Pipeline           |
                                              |   ColumnTransformer        |
                                              |     TF-IDF (name, comp,    |
                                              |             uses, side fx) |
                                              |     OrdinalEncoder (mfr)   |
                                              |     StandardScaler (rev %) |
                                              |   Classifier               |
                                              |     RF or LR (registry)    |
                                              +----------------------------+
                                                               |
                                                               v
                                              joblib artefact + metrics + plots

Ergebnisse.

Die Trainings-Pipeline gibt Accuracy, Macro F1, Weighted F1, Precision und Recall pro Klasse sowie eine Confusion-Matrix-Grafik aus. Random Forest erreicht 98,5 Prozent Accuracy auf dem zurückgehaltenen, stratifizierten Testset, die Logistic-Regression-Baseline 97,2 Prozent. Random Forest liegt in jeder beibehaltenen Kategorie über 95 Prozent F1.

Erkenntnisse.

Die nützlichste Erkenntnis war, dass das deutlich einfachere Modell nur wenige Prozentpunkte hinter dem komplexeren lag. Das zeigt, dass das kategoriale Signal in den Daten stark genug ist, damit ein flaches, interpretierbares Modell ohne tiefe Architektur fast gleich gut abschneidet. In einer regulierten Domäne wie der Pharmakovigilanz, in der jede Modellentscheidung erklärbar sein muss, beeinflusst dieses Ergebnis, welches Modell deployt wird. Man liefert das erklärbare Modell aus, es sei denn, man braucht die marginalen Extra-Punkte unbedingt.

Die zweite Erkenntnis betraf Scope-Disziplin. Eine erste Version hatte eine Streamlit-Demo, eine Hugging-Face-Transformer-Baseline und einen Deployment-Workflow. Nichts davon war fertig. Ich habe alle drei gestrichen und den produktionsreifen Kern ausgeliefert: Feature-Pipeline, zwei Modelle, Evaluation, Inferenz-CLI, Tests, CI-Matrix. Das Ergebnis hat weniger Funktionen, die dafür vollständig umgesetzt sind.

Die dritte Erkenntnis betraf die Konsistenz zwischen Training und Inferenz. Der ursprüngliche Prototyp hatte den Hersteller mit einem einmaligen LabelEncoder außerhalb der Pipeline kodiert, was zur Inferenz-Zeit bei jedem unbekannten Hersteller abgestürzt wäre. Ich habe das Encoding als OrdinalEncoder(handle_unknown="use_encoded_value") in die Pipeline verschoben; der Fix ändert die Metriken nicht, verhindert aber, dass das Modell bei unbekannten Herstellern in Produktivdaten abstürzt.

Stack.

Python · scikit-learn · pandas · numpy · TF-IDF · Random Forest · Logistic Regression · joblib · pytest · ruff · black · GitHub Actions

Auf GitHub ansehen