Part1-Using Edge ML solutions in iOS/Android - Building a Smart Savings App with Transaction Text Classification

Introduction

This tutorial demonstrates how to build a text classification system for bank transactions using TensorFlow and deploy it on mobile platforms. The system automatically categorizes transaction descriptions and implements a micro-savings mechanism based on spending behavior analysis. This tutorial focuses on Switzerland market. Swiss Francs(CHF) is their currency.

The complete implementation covers synthetic data generation, model training in Google Colab, TensorFlow Lite conversion, and mobile integration for both iOS and Android platforms.

Github Link For below tutorial resource

Use Case: Automated Transaction Classification and Micro-Savings

Problem Statement

Traditional budgeting applications require manual transaction categorization, leading to poor user adoption and inconsistent data quality. Our solution automates this process using machine learning to classify transactions and implement behavioral-based savings.

Classification System

The model classifies bank transactions into three categories with corresponding savings rates:

Category Description Savings Rate Examples
Normal Essential expenses and regular income 1% Groceries, utilities, salary, rent
Avoidable Non-essential but reasonable expenses 2% Dining out, subscriptions, entertainment
Regrettable Impulse purchases and unnecessary expenses 5% Luxury items, duplicate purchases, late-night shopping

System Architecture

The following diagram illustrates the transaction processing workflow:

System Architecture Flowchart

Figure 1: Transaction classification and savings mechanism workflow

Savings Mechanism Workflow

  1. Transaction Input: Bank transaction description received
  2. Text Classification: TensorFlow Lite model processes the description
  3. Percentage Deduction: Apply savings rate based on classification:
    • Normal transactions: 1% deduction
    • Avoidable transactions: 2% deduction
    • Regrettable transactions: 5% deduction
  4. Fund Accumulation: Deducted amounts accumulate in savings account
  5. Automatic Investment: When fund reaches 50 CHF threshold, trigger investment

Technical Components

The implementation consists of four main components:

  1. Data Generation Pipeline
    • Synthetic transaction data generator
    • Data validation and improvement scripts
    • Training dataset preparation
  2. Model Training Infrastructure
    • Google Colab training environment
    • TensorFlow text classification model
    • Model evaluation and optimization
  3. Mobile Deployment
    • TensorFlow Lite model conversion
    • iOS Swift integration
    • Android Kotlin/Java integration

Why Local Models

This implementation uses on-device inference through TensorFlow Lite rather than cloud-based APIs for several critical reasons:

Cost Optimization

  • Eliminates per-request API costs by offloading inference to user devices
  • Reduces server infrastructure requirements and operational expenses
  • Scales efficiently as user base grows without proportional cost increases

Privacy Compliance

  • Sensitive financial data remains on the user’s device
  • Complies with GDPR, PCI DSS, and regional financial privacy regulations
  • Eliminates data transmission risks and potential breach vectors
  • No server-side storage of transaction descriptions required

Offline Availability

  • Classification works without internet connectivity
  • Critical for real-time transaction processing and savings calculations
  • You can have model dependcy management and version management via tools like Firebase

Performance Benefits

  • Sub-100ms inference (Most Times) latency on modern mobile devices
  • No network round-trip delays
  • Consistent response times independent of server load

Expected Outcomes

This tutorial will demonstrate how to:

  • Generate synthetic banking data for machine learning training
  • Build and train a text classification model using TensorFlow
  • Convert trained models to TensorFlow Lite for mobile deployment
  • Integrate ML models into native mobile applications
  • Implement automated savings logic based on classification results

The resulting system provides automated transaction categorization with 85%+ accuracy while maintaining user privacy through on-device processing.

Synthetic Data Generation

Machine learning models require substantial training data to achieve reliable performance. Since real banking transaction data is sensitive and restricted, we generate synthetic transaction descriptions that mirror real-world patterns while maintaining privacy compliance.

Data Generator Script

The primary data generation script creates realistic transaction descriptions across our three classification categories:

import random
import pandas as pd

# 20 merchants per category
MERCHANT_CATEGORIES = {
    "normal": [
        "Migros Supermarket", "Coop Groceries", "Pharmacy Zürich", "SBB Ticket", "PostFinance Bill Pay",
        "Denner", "Lidl", "Aldi", "Swisscom", "Manor", "Tchibo", "Helsana Health", "City Bus Ticket",
        "Swiss Post", "Bookstore", "Local Bakery", "Apotheke Zürich", "Mobile Top-Up", "Cablecom", "Sunrise Telecom"
    ],
    "avoidable": [
        "Amazon Online", "Starbucks", "H&M Clothing", "Zara Outlet", "Gas Station", "C&A", "MediaMarkt", "Burger King",
        "Mobile Accessories", "Spotify", "Netflix", "IKEA", "Decathlon", "Uber Eats", "Globus", "AliExpress",
        "Online Gaming", "Domino's Pizza", "Electronics Mall", "Snack Vending Machine"
    ],
    "regrettable": [
        "McDonald's", "Cigarette Shop", "Bar Zürich", "Late Night Kebab", "Liquor Store", "24/7 Shop",
        "Fast Food Van", "Sports Betting", "Hookah Lounge", "After Party Club", "Beer Shop", "Fried Chicken Stand",
        "Mini Bar", "Nightlife Lounge", "Vodka & More", "Late Night Donuts", "Whiskey World", "Shisha Zone",
        "Pub Crawl ZH", "Late Night Snacks"
    ]
}

def generate_transaction():
    category = random.choice(list(MERCHANT_CATEGORIES.keys()))
    merchant = random.choice(MERCHANT_CATEGORIES[category])
    
    # Randomize amount range per category
    if category == "normal":
        amount = round(random.uniform(5, 80), 2)
    elif category == "avoidable":
        amount = round(random.uniform(10, 100), 2)
    else:  # regrettable
        amount = round(random.uniform(5, 50), 2)
    
    transaction_text = f"{merchant} - CHF {amount}"
    
    return {
        "merchant_name": merchant,
        "transaction_amount": amount,
        "transaction_text": transaction_text,
        "transaction_type": category
    }

def generate_dataset(num_samples=5000, output_csv="synthetic_transactions.csv"):
    data = [generate_transaction() for _ in range(num_samples)]
    df = pd.DataFrame(data)
    df.to_csv(output_csv, index=False)
    print(f"Generated {num_samples} synthetic transactions to: {output_csv}")

if __name__ == "__main__":
    generate_dataset(num_samples=5000)

Key Design Decisions

Merchant Categorization Strategy

  • Normal: Essential services (groceries, utilities, healthcare, transportation)
  • Avoidable: Discretionary but reasonable spending (retail, subscriptions, dining)
  • Regrettable: Impulse and potentially harmful purchases (fast food, gambling, alcohol)

Amount Distribution Logic

  • Normal transactions: 5-80 CHF (broader range for essentials)
  • Avoidable transactions: 10-100 CHF (higher ceiling for retail purchases)
  • Regrettable transactions: 5-50 CHF (lower range for impulse buys)

Transaction Format Follows standard Swiss banking description format: "Merchant Name - CHF Amount"

Data Improvement Script

The data improvement script normalizes text formatting for consistent model training:

import pandas as pd

# Load the generated dataset
df = pd.read_csv("synthetic_transactions.csv")

# Normalize transaction text to lowercase
df['transaction_text'] = df['transaction_text'].str.lower()

# Save processed data
df.to_csv("synthetic_transactions_processed.csv", index=False)
print("Data preprocessing completed")

Text Normalization Benefits

Consistency: Eliminates case sensitivity variations in merchant names Model Performance: Reduces vocabulary size and improves pattern recognition Real-world Compatibility: Matches typical banking data format variations

Generated Dataset Structure

The resulting CSV contains the following columns:

Column Type Description Example
merchant_name String Business name “Migros Supermarket”
transaction_amount Float Transaction value 45.67
transaction_text String Full description “migros supermarket - chf 45.67”
transaction_type String Classification label “normal”

Usage Instructions

  1. Generate initial dataset:
    python data_generator.py
    
  2. Apply text normalization:
    python data_improver.py
    
  3. Verify output:
    import pandas as pd
    df = pd.read_csv("synthetic_transactions_processed.csv")
    print(df.head())
    print(f"Dataset size: {len(df)} transactions")
    print(f"Category distribution:\n{df['transaction_type'].value_counts()}")
    

The processed dataset provides 5,000 balanced training samples ready for TensorFlow model training. The synthetic approach ensures privacy compliance while generating realistic transaction patterns for effective classification model development.

TensorFlow Model Training in Google Colab

Note: When I tried from Tensorflow documentation, some code were outdated. I have to search, read through documentation, use chatgpt/gemini to figure out missing items. If you want to try this sometime later, and if the below code is outdated, feel free to repeat what i did. World moves fast!😅

This section covers training a text classification model using TensorFlow in Google Colab and converting it to TensorFlow Lite for mobile deployment.

Environment Setup

Create a new Google Colab notebook and install the required dependencies:

import pandas as pd
import numpy as np
import tensorflow as tf
from sklearn.model_selection import train_test_split
from tensorflow.keras.layers import TextVectorization
from google.colab import files

Data Loading and Preprocessing

Upload the synthetic dataset generated in the previous step and prepare it for training:


df = pd.read_csv("synthetic_transactions.csv")

# Create label mapping for categorical encoding
label_to_index = {'normal': 0, 'avoidable': 1, 'regrettable': 2}
index_to_label = {v: k for k, v in label_to_index.items()}
df['label'] = df['transaction_type'].map(label_to_index)

# Split dataset into training and validation sets
X_train_raw, X_val_raw, y_train, y_val = train_test_split(
    df['transaction_text'].values,
    df['label'].values,
    test_size=0.2,
    random_state=42
)

Text Vectorization Configuration

Configure text preprocessing parameters for optimal mobile performance:

# Vectorization parameters optimized for mobile deployment
max_tokens = 1000        # Vocabulary size limit
sequence_length = 20     # Maximum input sequence length

# Initialize text vectorizer
vectorizer = TextVectorization(
    max_tokens=max_tokens,
    output_mode='int',
    output_sequence_length=sequence_length
)

# Adapt vectorizer to training data and transform datasets
vectorizer.adapt(X_train_raw)
X_train = vectorizer(X_train_raw)
X_val = vectorizer(X_val_raw)

Model Architecture

We can now build a lightweight neural network suitable for our use case

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(
        input_dim=max_tokens, 
        output_dim=16, 
        input_length=sequence_length
    ),
    tf.keras.layers.GlobalAveragePooling1D(),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(3, activation='softmax')  # 3 output classes
])

# Compile model with standard classification settings
model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

Model Training

Train the model with the synthetic dataset:

# Train model for 10 epochs
history = model.fit(
    X_train, y_train,
    validation_data=(X_val, y_val),
    epochs=10,
    batch_size=32
)

# Export trained model in SavedModel format
model.export("transaction_classifier_savedmodel")

Expected Training Results:

  • Training accuracy: ~100% (synthetic data with clear patterns)
  • Validation accuracy: ~100%
  • Training time: <2 minutes on Colab

TensorFlow Lite Conversion

Convert the trained model to TensorFlow Lite format for mobile deployment:

# Initialize TFLite converter from SavedModel
converter = tf.lite.TFLiteConverter.from_saved_model("transaction_classifier_savedmodel")

# Convert model to TensorFlow Lite format
tflite_model = converter.convert()

# Save TFLite model file
with open("transaction_classifier.tflite", "wb") as f:
    f.write(tflite_model)
    print("TFLite model saved as 'new_transaction_classifier.tflite'")

Model Validation

Test the trained model with sample transactions to verify classification accuracy:

# Define test samples covering all categories
sample_texts = [
    "Night club - CHF 14.90",      # regrettable
    "Coop Groceries - CHF 25.00",  # normal
    "Amazon Online - CHF 49.99",   # avoidable
    "Liquor Store - CHF 18.00",    # regrettable
    "Pharmacy Zürich - CHF 32.10"  # normal
]

# Run inference on test samples
X_test = vectorizer(tf.constant(sample_texts))
predictions = model.predict(X_test)
predicted_labels = [np.argmax(p) for p in predictions]

# Display prediction results
print("\nSample Predictions:")
for text, pred in zip(sample_texts, predicted_labels):
    print(f"{text} --> Predicted: {index_to_label[pred]}")

Model Download

Download the TensorFlow Lite model for mobile integration:

from google.colab import files

# Download TFLite model to local machine
files.download("new_transaction_classifier.tflite")

Model Specifications

The resulting TensorFlow Lite model has the following characteristics:

Specification Value
Model size ~68 KB
Input shape (1, 20) integers
Output shape (1, 3) probabilities
Vocabulary size 1000 tokens
Inference time <50ms on mobile

Key Implementation Details

Embedding Layer: Converts text tokens to dense vectors (16 dimensions) GlobalAveragePooling1D: Aggregates sequence information into fixed-size representation Dense Layers: Fully connected layers for classification with ReLU activation Softmax Output: Probability distribution over three transaction categories

The trained model achieves perfect accuracy on synthetic data due to clear categorical patterns in the generated dataset. Real-world performance may vary but should maintain high accuracy for similar transaction patterns.

Vocabulary Download

We need to download this vectorizer, which we will need in next part for loading same vocabulary in iOS and Android TensorFlow interpreter.

vocabulary = vectorizer.get_vocabulary()
vocab_dict = {word: idx for idx, word in enumerate(vocabulary)}

# Save vocabulary to JSON file
import json
with open("vocabulary.json", "w") as f:
    json.dump(vocab_dict, f)
    print(f"Vocabulary saved with {len(vocab_dict)} tokens")

        # Also download the vocabulary file
    files.download("vocabulary.json")

Alternative: CreateML Training

TLDR: Good for iOS/macOS and not optimized for andorid

For iOS-focused development, Apple’s CreateML provides a streamlined approach to train text classification models without coding. This method produces CoreML models (.mlmodel) optimized for Apple platforms.

Prerequisites

  • macOS device with Xcode installed
  • CreateML app (available through Xcode or Mac App Store)
  • Synthetic transaction dataset (CSV format)

Training Process

Step 1: Launch CreateML Open CreateML application and select “Text Classification” template.

Step 2: Data Upload

  • Click “Training Data” section
  • Upload the synthetic_transactions.csv file generated previously
  • CreateML automatically detects columns: transaction_text (input) and transaction_type (label)

Step 3: Model Training

  • Click “Train” button
  • Training completes in 1-2 minutes
  • Achieves 100% accuracy on synthetic dataset

Step 4: Model Validation (Optional)

  • Upload test dataset in “Testing Data” section
  • CreateML evaluates model performance on unseen data

Step 5: Interactive Testing Use the Preview panel to test individual predictions:

Input: "Night Bar" 
Output: regrettable (98% confidence)

Input: "Night Club"
Output: regrettable (77% confidence)

Step 6: Model Export

  • Navigate to Output tab
  • Click “Get” to save the trained model
  • Save as TransactionClassifier.mlmodel

Model Performance Comparison

Test Input Prediction Confidence Notes
“Night Bar” regrettable 98% High confidence for exact match
“Night Club” regrettable 77% Lower confidence for similar pattern
“Coop Groceries” normal 99% Clear essential category
“Amazon Online” avoidable 95% Recognized shopping pattern

CreateML vs TensorFlow Comparison

Aspect CreateML TensorFlow
Platform Support iOS/macOS only Cross-platform
Training Complexity GUI-based, no coding Code-based implementation
Model Format CoreML (.mlmodel) TensorFlow Lite (.tflite)
Training Time 1-2 minutes 2-3 minutes
Model Size ~45 KB ~68 KB
Integration Native iOS/macOS Requires TensorFlow Lite runtime

iOS Integration Benefits

Native Performance: CoreML models run directly on Apple’s Neural Engine Automatic Optimization: CreateML optimizes models for specific device capabilities Seamless Integration: Direct import into Xcode projects without external dependencies On-device Processing: Full privacy compliance with local inference

Limitations

Platform Restriction: Models only compatible with iOS and macOS applications Limited Customization: Less control over model architecture compared to TensorFlow Vendor Lock-in: Dependency on Apple’s machine learning ecosystem

When to Choose CreateML

  • Developing exclusively for iOS/macOS platforms
  • Prefer visual training interface over code
  • Need maximum performance on Apple devices
  • Want native integration without external dependencies

For cross-platform applications requiring Android support, TensorFlow remains the recommended approach despite CreateML’s simplicity advantages.

Will CreateML work for Android

I would not recommend any Create ML models to work in android as the CreateML optimizations don’t translate well to ONNX or TFLite. You may lose quantization, custom layers, or platform-specific acceleration (like Metal on iOS). It may work for simple text classification or regression, but you will see differences in complex tabular classifications or regressions or recommendations.

1. Convert Core ML to ONNX

# Install coremltools and onnxmltools
pip install coremltools onnxmltools

# Convert Core ML to ONNX (Python)
import coremltools as ct
import onnxmltools

coreml_model = ct.models.MLModel("model.mlmodel")
onnx_model = onnxmltools.convert_coreml(coreml_model, target_opset=13)
onnxmltools.utils.save_model(onnx_model, "model.onnx")

2. Convert ONNX to TFLite

# Install tf2onnx and TensorFlow
pip install tf2onnx tensorflow

# Convert ONNX to TensorFlow SavedModel
python -m tf2onnx.convert --onnx model.onnx --output model.pb

# Convert SavedModel to TFLite
import tensorflow as tf

converter = tf.lite.TFLiteConverter.from_saved_model("model.pb")
tflite_model = converter.convert()
open("model.tflite", "wb").write(tflite_model)

3. Use TFLite Model in Android

// Load TFLite model
Interpreter tflite = new Interpreter(loadModelFile("model.tflite"));

// Run inference
float[][] input = new float[1][...];
float[][] output = new float[1][...];
tflite.run(input, output);

Important Info

The trained model achieves perfect accuracy on synthetic data due to clear categorical patterns in the generated dataset.

Real-world performance may vary but should maintain high accuracy for similar transaction patterns. Expect more anamolies and imperfections in real world though.

Now we will have final part to integrate in native iOS and Android