Machine Learning (feb 2025)

Daarom

Mijn hobby project Grip Op M'n Knip voorziet banktransacties van de meest passende categorie waardoor je een goed overzicht van je inkomsten en uitgaven voorgeschoteld krijgt.

Traditioneel programmeren

De traditionele logica die ik heb gebruikt om een banktransactie in de juiste categorie te plaatsen gebeurt door steekwoorden te vergelijken met tekst die uit de banktransactie wordt gedestileerd. Deze steekwoorden zijn tot stand gekomen door logisch nadenken en handmatig vastgelegd in de database.
Voorbeeld:
- transacties met bedrag <0 die ergens eneco bevatten krijgen de categorie UITGAVEN; Vaste Lasten; Energie;
- transacties met bedrag <0 die ergens vodafone bevatten krijgen de categorie UITGAVEN; Vaste Lasten; Mobiele telefoon;
- transacties met bedrag >0 die ergens sociale verzekeringsbank bevatten krijgen de categorie INKOMSTEN; Netto loon; AOW;

Een ML model opstellen en trainen
De meer moderne (en gehypte) manier om het categoriseer probleem aan te pakken is natuurlijk om dit door de computer te laten oplossen mbv AI 😀, Machine Learning is de beter passende term voor deze toepassing.


In de literatuur vindt je dat voor classificatie de open source tool scikit-learn goed geschikt is, dus die heb ik gebruikt. scikit-learn is een gratis en open-source machine learning- bibliotheek voor Python. Het bevat verschillende classificatie- , regressie- en clusteringalgoritmen. Ik heb de computer laten bepalen welk algoritme het beste resultaat geeft en dat blijkt RandomForestClassifier te zijn. Hetzelfde geldt voor de zgn. hyperparameters.

AI staat ondertussen al gauw gelijk aan de heilige graal maar...

  • Is dat wel zo en hoe pas je het toe in praktijk?
  • Hoe kun je het gebruiken in je eigen toepassing zodat deze nog slimmer wordt?
  • Hoe pas je het toe zonder dat je eerst een van de nieuwe AI master opleidingen moet hebben afgerond?
  • Ik zat met die vragen en ben me er wat in gaan verdiepen om te kijken of ik er wat mee kan in mijn hobbyproject. Zoals verderop te zien is valt het toepassen van ML best mee. Al moet ik bekennen dat het meest complexe gedeelte reeds is gedaan door veel knappere koppen dan ik. De makers van scikit zijn waarschijnlijk wel masters in wiskunde. Daarnaast moet ik bekennen dat ik ondertussen door heb hoe ik de tools kan inzetten omdat er genoeg trainingen en voorbeelden te vinden zijn op het internet en ChatGPT en Deepseek je ook een handje kunnen helpen. Maar... ik heb nog wel minder gevoel bij de uitgevoerde regels dieper weg in de Python code, ik gebruik ze gewoon.


    Spoiler alert... Het model werkt naar behoren in een testomgeving maar... De traditionele regels, opgesteld door de inzet van mijn eigen hersenwerk, werken momenteel (nog) beter dan het AI model. Mijn verwachting is dat naarmate er (veel) meer trainingsdata beschikbaar komt het model beter zal gaan presteren! In mijn huidige dataset heb ik ca. 4000 banktransacties beschikbaar om te trainen en te testen.
    Daarnaast neemt de tool een een paar honderd Mb in beslag en beslaat daarmee een substantieel deel van mijn beschikbare cloud ruimte.
    Om die redenen heb ik besloten om de onderstaande AI magie (nog) niet op te nemen in de productie versie van mijn Grip Op M'n Knip applicatie.

    Filmpje met de AI Magie in bedrijf in mijn testomgeving. Zoals je daar kunt zien is het resultaat al best indrukwekkend vind ik maar... nog niet beter dan de traditionele code gebaseerd op string vergelijkingen.

    Hierna probeer ik stap voor stap uit te leggen hoe ik het model heb opgesteld, getraind en toegepast mbv scikit dat ik wilde inzetten voor het voorspellen van de juiste categorie in mijn applicatie.

    (her)Trainen van het model
    """ML train the GOMK dataset using sklearn and pandas"""
    # import the used modules

    import pandas as pd
    import sqlite3
    from sklearn.model_selection import train_test_split
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.metrics import classification_report, accuracy_score, mean_squared_error
    import joblib

    Hoofprogramma waar de onderdelen achtereenvolgens worden aangeroepen
    # Hoofdprogramma
    def train():

    # Laden, bruikbaar maken en verwerken van de data
    csvfile = db_to_csv()
    df = load_data()
    X, y = preprocess_data(df)

    # Data splitsen in training en test
    X_train, X_test, y_train, y_test = split_data(X, y)

    # Vectorisatie; woorden omzetten naar getallen zodat dit verwerkbaar is voor de computer
    X_train_vec, X_test_vec, vectorizer = vectorize_data(X_train, X_test)

    # Train het model
    model = train_model(X_train_vec, y_train)

    # Evalueren en bepaal de nauwkeurigheid
    evaluate_model(model, X_test_vec, y_test)

    # Veiligstellen van het getrainde model voor gebruik later
    save_model(model)

    Natuurlijk geldt zoals altijd dat het van belang om allereerst de (transactie) data goed geschikt te maken voor gebruik.

    Data geschikt maken
    # 0. db data naar CSV

    def db_to_csv():

    con = sqlite3.connect("trans_cat.db")

    # Selecteer data die reeds voorzien is van een categorie om hiervan te leren
    # Sla de geselecteerde data op in een CSV bestand
    sql_stmt = "SELECT * FROM transacties WHERE userid == ? AND Categorie != '' "
    df = pd.read_sql_query(
    sql=sql_stmt,
    con=con,
    params=[session["userid"]],
    )

    folder_file = session["folder_name"] + "transacties.csv"

    csvfile = df.to_csv(folder_file, index=False)
    print("Step 0: Get data from database")
    return csvfile
    Laad de dataset
    # 1. Laad de dataset als dataframe

    def load_data():
    folder_file = session["folder_name"] + "transacties.csv"
    df = pd.read_csv(folder_file)
    # stel de transactie string samen die bepalend is
    df["Transactie"] = df["Naam"] + " " + df["Tegenrekening"] + " " + df["Mededeling"]

    # drop irrelevante kolommen
    df.drop(
    [
    "Datum",
    "Code",
    "AfBij",
    ...,
    ],
    axis="columns",
    inplace=True,
    )

    # maak de data bruikbaar voor het model
    df["Transactie"] = df["Transactie"].str.lower()
    remove_list = ["naam", "iban", "kenmerk", "valutadatum", "machtiging id", "tikkie"] # deze woorden voegen niet veel toe
    df["Transactie"] = df["Transactie"].replace("|".join(remove_list), "", regex=True)
    df["Transactie"] = df["Transactie"].replace({":": "", "!": ""}, regex=False)
    df["Transactie"] = df["Transactie"].str.replace(r"[^a-z0-9\s]", "", regex=False)
    df["Transactie"] = df["Transactie"].str.replace(r"\s+", " ", regex=False)

    df_specific_cleaned = df.dropna(subset=["Transactie"])
    df = df_specific_cleaned

    # verwijder sommige categorieen uit de traingset
    for index, row in df.iterrows():
    if row["Categorie"] in (
    "UITGAVEN; Kleine uitgaven; Kleine uitgaven;",
    "UITGAVEN; Onbekend; Onbekend;",
    "INKOMSTEN; Onbekend; Onbekend;",
    ):
    df.drop(index, inplace=True)

    print("Step 1: Getting dataframe ready to use")
    return df
    Bepaal de Transacties-kolom (X) en de gevraagde Categorie_kolom (y)
    # 2. Preprocessing
    def preprocess_data(df):

    X = df["Transactie"]
    y = df["Categorie"]
    print("Step 2: Preprocessed dataframe")
    return X, y
    Splits de beschikbare data in een train- en een test-deel obv 80-20 regel

    Het model, RandomForestClassifier in dit geval, wordt getraind op de trainingsdataset. Ten slotte is de testdataset een dataset die wordt gebruikt om een evaluatie te geven van het opgestelde model.

    # 3. Train en test splits

    def split_data(X, y):
    print("Step 3: Train en test split")
    return train_test_split(X, y, test_size=0.2, random_state=42)

    Vertorisatie; oftewel zet de transactietekst om naar vectoren zodat de computer er wat mee kan
    Vectoren worden opgebouwd uit componenten, gewone getallen. Je kunt een vector zien als een lijst met getallen en vectoralgebra als bewerkingen die worden uitgevoerd op de getallen in de lijst.

    # 4. Vectorisatie
    def vectorize_data(X_train, X_test):

    vectorizer = TfidfVectorizer()
    X_train_vec = vectorizer.fit_transform(X_train)
    X_test_vec = vectorizer.transform(X_test)
    print("Step 4: Vectorise")

    # save the vectorization
    folder_vect = session["folder_name"] + "GOMK_classify_vect.sav"
    joblib.dump(vectorizer, folder_vect)
    return X_train_vec, X_test_vec, vectorizer
    Train het model en gebruik de voorgestelde hyperparameters

    Een supervised learning-algoritme neemt een bekende set Transacties (de invoer) en bekende Categorieen op de die Transacties (de uitvoer) en traint een model om redelijke voorspellingen te genereren van de Categorie op nieuwe Transacties.

    # 5. Model trainen
    def train_model(X_train_vec, y_train):

    model = RandomForestClassifier(
    random_state=42,
    n_estimators=200,
    max_depth=None,
    max_features="log2",
    max_leaf_nodes=None,
    )

    model.fit(X_train_vec, y_train)
    print("Step 5: Model training")
    return model
    Evalueer het model en bepaal de nauwkeurigheid

    Modelevaluatie is het proces waarbij verschillende evaluatiemetrieken worden gebruikt om inzicht te krijgen in de prestaties van een machine learning-model en de sterke en zwakke punten ervan. Industrienormen liggen tussen 70% en 90% . Alles boven de 70% is acceptabel als een realistische en waardevolle modeldata-output.

    # 6. Model evalueren
    def evaluate_model(model, X_test_vec, y_test):

    y_pred = model.predict(X_test_vec)
    print("Step 6: Model evaluation")
    print("-----------------------------------")

    print("Classification Report:")
    print(classification_report(y_test, y_pred, zero_division=0))
    print("Accuracy:", accuracy_score(y_test, y_pred))
    Stel het model veilig voor later gebruik
    # 7. Save the model
    def save_model(model):

    folder_model = session["folder_name"] + "GOMK_classify_model.sav"
    joblib.dump(model, folder_model)
    De output als de training wordt uitgevoerd...

    93% naukeurigheid (zie heronder) vind ik al een hele mooie score. Met meer data en nog wat uurtjes fine tuning krijg je er waarschijnlijk nog wel een procentje bij.

    # Output na een runtime van ca. 2 seconden voor het trainen




    Het ML model gebruiken in je toepassing

    Het model toepassen bij een voorspelling gaat vrij eenvoudig. In mijn geval pas ik het toe in een Python Flask applicatie. Je gebruikt het eerder opgeslagen model en de opgeslagen vectorisatie bij de voorspelling van de Categorie op basis van een gegeven Transactie. Nogmaals een link naar het filmpje met de code in bedrijf in mijn testomgeving.

    # Maak de transactiedata geschikt voor gebruik zoals dat ook bij de training is gedaan (zie hiervoor)
    df["Transactie"] = df["Transactie"].str.lower()
    remove_list = [ "naam", "iban", "kenmerk", "valutadatum", "machtiging id", "tikkie", ]
    df["Transactie"] = df["Transactie"].replace( "|".join(remove_list), "", regex=True )
    df["Transactie"] = df["Transactie"].replace({":": "", "!": ""}, regex=False)
    df["Transactie"] = df["Transactie"].str.replace(r"[^a-z0-9\s]", "", regex=False)
    df["Transactie"] = df["Transactie"].str.replace(r"\s+", " ", regex=False)
    df_specific_cleaned = df.dropna(subset=["Transactie"])
    df = df_specific_cleaned

    # Haal de opgeslagen vectorisatie op
    folder_vect = session["folder_name"] + "GOMK_classify_vect.sav"
    vectorizer = load(folder_vect)

    # Haal het getrainde model op
    folder_model = session["folder_name"] + "GOMK_classify_model.sav"
    model = load(folder_model)

    # Gebruik het model en vectorisatie om de transactie te voorzien van een categorie
    transacties_vector = vectorizer.transform(df["Transactie"])
    # voorspel de categorie
    df["Categorie"] = model.predict(transacties_vector)
    newcat = df.iloc[0]["Categorie"]

    # Geef de categorievoorspelling terug aan de client
    return render_template("ai_tran.html", newcat=newcat)