The repository contains an application of deep learning model for classifying movie reviews. The model is based on the Keras library and the TensorFlow framework.
The goal is to deliver a production‑ready sentiment‑analysis service that scores IMDb movie reviews as positive or negative.
The repository covers the full MLOps life‑cycle:
- Training pipeline – data cleaning, SentencePiece tokenizer, CNN model, evaluation.
- Model export – SavedModel format + tokenizer artefacts.
- Serving stack –
- TensorFlow Serving for high‑performance inference,
- a thin Flask gateway that handles all pre/post‑processing and exposes a simple REST API,
- a minimal web UI for manual testing and CSV batch uploads.
- Containerisation – three independent images (model, gateway, web) for clear separation of concerns.
- Orchestration – Docker Compose for local development and Kubernetes manifests for production, including Secrets for sensitive config.
- Upgrade/rollback workflow – immutable version tags for every image and zero‑downtime roll‑outs via Kubernetes
kubectl set image.
Link to the dataset: kaggle
On the validation set the model achieves ≈ 90 % accuracy (macro F1 ≈ 0.90).
| precision | recall | f1-score | support | |
|---|---|---|---|---|
| negative | 0.90 | 0.91 | 0.91 | 4000 |
| positive | 0.91 | 0.90 | 0.91 | 4000 |
| accuracy | 0.91 | 8000 | ||
| macro avg | 0.91 | 0.91 | 0.91 | 8000 |
| weighted avg | 0.91 | 0.91 | 0.91 | 8000 |
| Tool | Version (tested) |
|---|---|
| Python | 3.10 + |
| Docker | 24 + |
| Kubernetes CLI | 1.30 + |
| kubectl context | points at your target cluster |
git clone git@github.com:Edyarich/movie-reviews-classification.git && cd movie-reviews-classification
# optional: create a virtual env for training
python3 -m venv env
source env/bin/activateThe application is split into three cooperative services, each packaged as an independent Docker image and deployed as its own Deployment ➜ Service pair in Kubernetes.
flowchart LR
%% ─────────── USER ───────────
subgraph User
BROWSER["Browser<br/>or curl"]:::client
end
%% ─────────── WEB UI ─────────
subgraph Web["Web UI: 8080"]
UI[/"index.html<br/>single-text form<br/>CSV upload"/]:::web
end
%% ────────── GATEWAY ─────────
subgraph Gateway["Gateway Flask: 5000 CPU Pod"]
PREP[/"Tokenise<br/>Pad<br/>JSON -> Tensor"/]:::proc
THRESH[">= 0.5 ?"]:::proc
end
%% ──────── TF-SERVING ────────
subgraph TF["TensorFlow Serving: 8501 GPU CPU Pod"]
MODEL["SavedModel v1"]:::model
end
%% ────────── FLOW ────────────
BROWSER -->|HTTP| UI
UI -->|REST JSON| PREP
PREP -->|gRPC REST instances list| MODEL
MODEL -->|raw score| THRESH
THRESH -->|JSON score prediction| UI
UI -->|HTTP| BROWSER
%% ───────── STYLE ───────────
classDef client fill:#fff,stroke:#777,stroke-width:1px
classDef web fill:#c6e0ff,stroke:#1a73e8,color:#000
classDef proc fill:#d0ffd8,stroke:#008a00,color:#000
classDef model fill:#ffe8c6,stroke:#ff9500,color:#000
| Layer | Goal / responsibility | Input | Output |
|---|---|---|---|
Web interface (webapp.py + index.html) |
Give users a friendly way to test the model. • Single‑text box for ad‑hoc experiments. • CSV upload ( text column) for batch scoring. |
Browser HTTP requests | • JSON response for single text. • Downloadable CSV with score & prediction columns. |
Gateway (gateway.py) |
All pre‑ and post‑processing in Python. • Lower‑case, clean HTML. • Tokenise with SentencePiece. • Pad/trim to max length. • Call TF‑Serving. • Apply 0.5 threshold. • Hide TF‑Serving details from outside world. |
JSON {"features":"<raw text>"} |
JSON {"score":0.93,"prediction":"positive"} |
TF‑Serving (custom image built from docker/model.dockerfile) |
High‑performance inference on the exported SavedModel. Handles model versioning, hot‑reload, batching, metrics. |
gRPC / REST Predict request with an instances list of padded integer IDs |
Raw tensor predictions ([[0.93]]) |
Positive review:
Negative review:
Upload ./data/test-web.csv:
Wait ~5 seconds to get results:
To begin with, install Docker and Docker Compose. After that, run the commands:
python generate_env_secret.py
docker compose up # spins tf‑serving, gateway, web on ports 8501,5000,8080
open http://localhost:8080 # interact via browserTo begin with, install kubectl and (optionally) kind. After that, run the commands:
kubectl create secret generic web-secret \
--from-literal=FLASK_SECRET=$(openssl rand -hex 32)
kubectl apply -f kube-config/model-deployment.yaml
kubectl apply -f kube-config/model-service.yaml
kubectl apply -f kube-config/gateway-deployment.yaml
kubectl apply -f kube-config/gateway-service.yaml
kubectl apply -f kube-config/web-deployment.yaml
kubectl apply -f kube-config/web-service.yaml
# Web UI
kubectl port-forward svc/sentiment-web 8080:80 &
open http://localhost:8080pip install -r requirements_train.txt
# split dataset (one‑off); creates data/train.csv, data/val.csv, data/test.csv
python src/split_data.py
# train & export SavedModel to ./sentiment/1/
python train.py data/train.csv data/val.csv --model_dir sentiment/1Don't forget to create an appropriate repository in Docker Hub.
At the end of the step, you can run docker compose up --build after you replace prepared images with your own.
docker build -f docker/model.dockerfile -t <REG>/sentiment-tf-serving:v1 .
docker push <REG>/sentiment-tf-serving:v1docker build -f docker/gateway.dockerfile -t <REG>/sentiment-gateway:v1 .
docker push <REG>/sentiment-gateway:v1docker build -f docker/web.dockerfile -t <REG>/sentiment-web:v1 .
docker push <REG>/sentiment-web:v1Before deployment, change the containers.image in kube-config/model-deployment.yaml to point to your registry.
kubectl apply -f kube-config/model-deployment.yaml # TF‑Serving
kubectl apply -f kube-config/model-service.yaml
kubectl apply -f kube-config/gateway-deployment.yaml # HTTP gateway
kubectl apply -f kube-config/gateway-service.yaml
kubectl apply -f kube-config/web-deployment.yaml # Web server
kubectl apply -f kube-config/web-service.yamlWait for the pods:
kubectl rollout status deployment/sentiment-tf-serving
kubectl rollout status deployment/sentiment-gateway
kubectl rollout status deployment/sentiment-webSmoke‑test:
# Gateway only
kubectl port-forward svc/sentiment-gateway 5000:80 &
curl -X POST http://localhost:5000/predict \
-H 'Content-Type: application/json' \
-d '{"features":"Absolute cinema!"}'
# Web UI
kubectl port-forward svc/sentiment-web 8080:80 &
open http://localhost:8080The web Deployment references a Kubernetes Secret named web-secret that provides FLASK_SECRET. To rotate:
kubectl delete secret web-secret
kubectl create secret generic web-secret \
--from-literal=FLASK_SECRET=$(openssl rand -hex 32)
kubectl rollout restart deployment/sentiment-web-
Re‑train, export to a new version folder e.g.
sentiment/2/. -
Copy
sentiment/2/intomodels/and rebuild image or mount it viasubPath. -
Push as
sentiment-tf-serving:v2, run:kubectl set image deployment/sentiment-tf-serving \ tf-serving=<REG>/sentiment-tf-serving:v2
Zero‑downtime roll‑out; gateway & web stay unchanged.
.
├── data/ ← raw & split datasets + sample `test-web.csv`
│ └── test-web.csv ← tiny CSV for UI smoke‑tests
├── docker/
│ ├── gateway.dockerfile ← gateway container recipe
│ ├── model.dockerfile ← TF‑Serving container recipe
│ └── web.dockerfile ← web‑UI container recipe
├── docker-compose.yaml ← local 3‑service stack (TF‑Serving, gateway, web)
├── download_data.sh ← pulls IMDB dataset & triggers split
├── gateway.py ← HTTP façade that calls TF‑Serving
├── generate_env_secret.py ← helper to create/update .env FLASK_SECRET
├── index.html ← Jinja template copied into /app/templates
├── kube-config/ ← 6 Kubernetes manifests (model / gateway / web)
│ ├── gateway-deployment.yaml
│ ├── gateway-service.yaml
│ ├── model-deployment.yaml
│ ├── model-service.yaml
│ ├── web-deployment.yaml
│ └── web-service.yaml
├── logs.txt ← handy CLI snippets & troubleshooting logs
├── notebooks/ ← exploratory notebooks (Jupyter)
├── proto.py ← shared dataclasses + preprocessing utils
├── requirements.txt ← runtime deps (Flask, pandas, requests …)
├── requirements_train.txt ← heavier training deps (TensorFlow, etc.)
├── sentiment/1/ ← current SavedModel version + metadata
├── src/ ← data pipeline & CNN model definition
│ ├── data_processing.py
│ ├── model.py
│ └── split_data.py
├── train.py ← train & export entry‑point
└── webapp.py ← Flask web interface code




