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...
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.
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 regelHet 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 hyperparametersEen 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 nauwkeurigheidModelevaluatie 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)