Verwaltung und Inbetriebnahme von ML-Modellen

Seite 2: Data Version Control

Inhaltsverzeichnis

In Softwareprojekten gehört die Codeverwaltung mit Werkzeugen wie Subversion und Git zum Alltag. Ein wichtiger Aspekt für die Nachvollziehbarkeit von KI-Projekten ist die Reproduzierbarkeit der Experimente. Dabei spielt neben der Codeverwaltung auch die Daten- und Modellverwaltung eine wichtige Rolle. Für große Datenmengen ist Git nicht geeignet, da es darauf spezialisiert ist, Textdateien zu versionieren. Abhilfe hierfür schafft Data Version Control (DVC), eine quelloffene Kommandozeilenanwendung für die Verwaltung von Daten und Modellen, die sich ähnlich wie Git bedienen lässt.

DVC ist ein Python-Paket, das man mit dem Paketmanager pip installiert und in Kombination mit Git verwendet. Dabei werden zuerst Git und im Anschluss DVC initialisiert. Nach der Initialisierung legt das System im Pendant zum Ordner .git/ den Ordner .dvc/ an. In diesem Verzeichnis finden sich alle relevanten Metadaten von DVC.

git init
dvc init
dvc add research/data/raw
git add Research/data/.gitignore research/data/raw.dvc

In DVC fügt der Befehl dvc add Daten hinzu. Für jeden Ordner und jede Datei berechnet das Tool ein md5-Hash, der im .dvc/-Verzeichnis hinterlegt wird.

- md5: 570cd340b97f7b4629aa3670a1694a62
wdir: ..
outs:
- md5: f98b70d0adcf1eb0d30a5b95ec8900d7.dir
  path: research/data
  cache: true
  metric: false
  persist: false

DVC verwendet den Hash, um Veränderungen in den einzelnen Dateien festzuhalten. Weiterhin generiert DVC eine Gitignore-Datei, die sicherstellt, dass keine Daten aus dem hinzugefügten Verzeichnis im Git Repository landen. Zu guter Letzt erzeugt dvc add die Datei research/data/raw.dvc. Die DVC-Datei umfasst die relevanten Metainformationen. Im Anschluss übernimmt Git die Versionierung.

Zu diesem Zeitpunkt liegen alle Daten lokal auf dem Rechner. Das kann in einem größeren Projekt die Zusammenarbeit erschweren, da sich Veränderungen am Datensatz nicht zentral erfassen lassen. Weiterhin hat die Verwaltung der Daten auf einem Rechner den Nachteil, dass beim Ausfall des Rechners ein Datenverlust entstehen kann. Um dem vorzubeugen, bietet es sich an, die Daten zentral zu persistieren und dadurch eine Redundanz zu gewährleisten. Ähnlich wie bei Git lassen sich die Daten mit DVC zentral in einem Remote-Speicher hinterlegen. DVC unterstützt viele Speichermöglichkeiten. In diesem Fall fiel die Wahl auf Amazon S3. DVC speichert die Metainformationen über den Remote-Speicher in der Datei ./dvc/config. Der Befehl push veranlasst das Hochladen der Dateien.

dvc remote add -d myremote s3://XXXXX/fashiondvc 
git commit .dvc/config -m "Remotespeicher Konfiguration"
dvc push

DVC führt die Experimente mit dem Befehl dvc run aus. Dieser dient als Schnittstelle, um Befehle auszuführen. Über verschiedene Flags werden Ein- und Ausgaben und die generierte DVC-Datei für die Reproduzierbarkeit angegeben (s. Abb. 3):

  • -d: Abhängigkeiten
  • -o: Ausgaben
  • -M: Metriken
  • -f: DVC-Datei

Kommt dvc run erneut zum Einsatz und bleiben die Abhängigkeiten unverändert, erspart sich DVC durch den Vergleich der DVC-Datei den Rechenaufwand.

Übersicht der Stages sowie der einzelnen DVC-Befehle mit Ergebnis (Abb. 3)

Vor dem Training eines Modells ist die Vorbereitung der Rohdaten zwingend notwendig. Dafür speichert das Tool die Bilddaten als NumPy-Array ab und skaliert die Pixel zwischen 0 und 1. Diese Schritte erfolgen in der Prepare-Stage. Dazu legt das System die Datei src/prepare.py an und ruft sie anschließend mit dem folgenden Befehl auf:

dvc run -f data/prepare.dvc -d src/prepare.py -d data/raw -o data/prepared python src/prepare.py

src/prepare.py führt die Datenvorbereitung durch und erstellt für das Training und Testing die dazugehörigen Numpy-Arrays mit den Labels:

from pathlib import Path
import numpy as np
import imageio

mapping = {"Tshirt_top": "0", "Trouser": "1",
"Pullover": "2", "Dress": "3", "Coat": "4",
"Sandal": "5", "Shirt": "6", "Sneaker": "7",
"Bag": "8", "Ankle_boot": "9"}

def create_images_labels(input: Path):
    labels = []
    images = []
    for image_path in input.glob("*/*.png"):
        parent_name = image_path.parent.name
        label = int(mapping[parent_name])
        image = imageio.imread(image_path)
        images.append(image)
        labels.append(label)
    images = np.array(images)
    labels = np.array(labels)
    scaled = images / 255.0
    reshaped = scaled.reshape(len(scaled), 28, 28)
    return reshaped, labels

data_path = Path("./data")
data_raw_path = data_path.joinpath("raw")
train_input = data_raw_path.joinpath("train")
test_input = data_raw_path.joinpath("test")

train_images, train_labels = create_images_labels(train_input)
test_images, test_labels = create_images_labels(test_input)
output_path = data_path.joinpath("prepared")

np.savez(f"{output_path}/train.npz", images=train_images, labels=train_labels)
np.savez(f"{output_path}/test.npz", images=test_images, labels=test_labels)

Nach der Vorbereitung der Rohdaten lässt sich ein Baseline-Modell mit XGBoost trainieren. Es dient als Benchmark, um weitere Experimente in der Zukunft zu bewerten. Die Trainings- und Testdaten lassen sich aus dem vorherigen Schritt laden, um den Klassifizierer zu trainieren. Um Modelle zu einem späteren Zeitpunkt miteinander vergleichen zu können, speichert das Tool zusätzlich die Genauigkeit als Metrik ab. DVC kann die Metriken zu den Experimenten ebenfalls verwalten, indem Entwickler*innen eine Datei mit dem Flag -M angeben:

dvc run -f model/xgb.dvc -d src/xgb_train.py -d data/prepared -o model/xgb/model.pickle -M model/xgb/score.metrics python src/xgb_train.py

src/xgb_train.py lädt die vorbereiteten Daten, trainiert einen XGB Klassifizierer, speichert Modell und Ergebnisse nach model/xgb.

import numpy as np
from pathlib import Path
import pickle

prepared_train_file = Path("./data/prepared/train.npz")
train_data = np.load(f"{prepared_train_file}")
X_train = train_data["images"]
X_train = X_train.reshape(len(X_train), -1)
y_train = train_data["labels"]

xgbc = XGBClassifier()
xgbc.fit(X_train, y_train)

prepared_test_file = Path("./data/prepared/test.npz")
test_data = np.load(f"{prepared_test_file}")
X_test = test_data["images"]
X_test = X_test.reshape(len(X_test), -1)
y_test = test_data["labels"]

xgb_folder = Path("./model/xgb")
if not xgb_folder.exists():
    xgb_folder.mkdir()

model_out = xgb_folder.joinpath("model.pickle")
with model_out.open("wb") as fd:
    pickle.dump(xgbc, fd)

scored = xgbc.score(X_test, y_test)
score_out = xgb_folder.joinpath("score.metrics")
with score_out.open("w") as fd:
    fd.write(f"{scored:.2f}")