pearlll commited on
Commit
492754f
·
1 Parent(s): c8758c4

Deploy document classifier app

Browse files
app.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import numpy as np
3
+ import gradio as gr
4
+
5
+
6
+ MODEL_PATH = "models/best_model.pkl"
7
+
8
+ model = joblib.load(MODEL_PATH)
9
+
10
+
11
+ def classify_document(text):
12
+ if not text or len(text.strip()) < 5:
13
+ return "Please enter at least 5 characters.", 0.0
14
+
15
+ prediction = model.predict([text])[0]
16
+
17
+ decision_scores = model.decision_function([text])
18
+ confidence_score = float(np.max(decision_scores))
19
+
20
+ return prediction, round(confidence_score, 4)
21
+
22
+
23
+ demo = gr.Interface(
24
+ fn=classify_document,
25
+ inputs=gr.Textbox(
26
+ lines=8,
27
+ placeholder="Paste news/document text here...",
28
+ label="Input Document Text"
29
+ ),
30
+ outputs=[
31
+ gr.Textbox(label="Predicted Category"),
32
+ gr.Number(label="Confidence Score")
33
+ ],
34
+ title="BBC News Document Classifier",
35
+ description=(
36
+ "Classifies document text into one of five categories: "
37
+ "business, entertainment, politics, sport, or tech."
38
+ ),
39
+ examples=[
40
+ ["The football team won the final match after scoring two goals."],
41
+ ["The company reported strong profits and growth in global markets."],
42
+ ["New software updates improve artificial intelligence performance."]
43
+ ]
44
+ )
45
+
46
+
47
+ if __name__ == "__main__":
48
+ demo.launch()
models/baseline_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:21731e1491beae7c0b124997d41db32ef7da344687fbba9e7f54aecdd20c17e8
3
+ size 384612
models/best_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6fb04723578f6ffe13888c5b766e4716927e793f645fe25cc0be0c47af7cb13a
3
+ size 384496
requirements.txt ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.13.0
2
+ aiohappyeyeballs==2.6.1
3
+ aiohttp==3.13.5
4
+ aiohttp-retry==2.9.1
5
+ aiosignal==1.4.0
6
+ alembic==1.18.4
7
+ amqp==5.3.1
8
+ annotated-doc==0.0.4
9
+ annotated-types==0.7.0
10
+ antlr4-python3-runtime==4.9.3
11
+ anyio==4.13.0
12
+ appdirs==1.4.4
13
+ asyncssh==2.23.0
14
+ atpublic==7.0.0
15
+ attrs==26.1.0
16
+ billiard==4.2.4
17
+ black==26.5.1
18
+ blinker==1.9.0
19
+ brotli==1.2.0
20
+ cachetools==7.1.3
21
+ celery==5.6.3
22
+ certifi==2026.4.22
23
+ cffi==2.0.0
24
+ charset-normalizer==3.4.7
25
+ click==8.4.0
26
+ click-didyoumean==0.3.1
27
+ click-plugins==1.1.1.2
28
+ click-repl==0.3.0
29
+ cloudpickle==3.1.2
30
+ colorama==0.4.6
31
+ configobj==5.0.9
32
+ contourpy==1.3.3
33
+ coverage==7.14.0
34
+ cryptography==46.0.7
35
+ cycler==0.12.1
36
+ databricks-sdk==0.110.0
37
+ datasets==4.8.5
38
+ deprecation==2.1.0
39
+ dictdiffer==0.9.0
40
+ dill==0.4.1
41
+ diskcache==5.6.3
42
+ distro==1.9.0
43
+ docker==7.1.0
44
+ dpath==2.2.0
45
+ dulwich==1.2.1
46
+ dvc==3.67.1
47
+ dvc-data==3.18.3
48
+ dvc-http==2.32.0
49
+ dvc-objects==5.2.0
50
+ dvc-render==1.0.2
51
+ dvc-studio-client==0.22.0
52
+ dvc-task==0.40.2
53
+ dynaconf==3.2.13
54
+ entrypoints==0.4
55
+ evidently==0.7.21
56
+ Faker==40.18.0
57
+ fastapi==0.136.1
58
+ filelock==3.29.0
59
+ flake8==7.3.0
60
+ Flask==3.1.3
61
+ flask-cors==6.0.2
62
+ flatten-dict==0.5.0
63
+ flufl.lock==9.1.0
64
+ fonttools==4.63.0
65
+ frozenlist==1.8.0
66
+ fsspec==2026.2.0
67
+ funcy==2.0
68
+ gitdb==4.0.12
69
+ GitPython==3.1.50
70
+ google-auth==2.53.0
71
+ gradio==6.14.0
72
+ gradio_client==2.5.0
73
+ grandalf==0.8
74
+ graphene==3.4.3
75
+ graphql-core==3.2.8
76
+ graphql-relay==3.2.0
77
+ greenlet==3.5.0
78
+ groovy==0.1.2
79
+ gto==1.9.0
80
+ h11==0.16.0
81
+ hf-gradio==0.4.1
82
+ hf-xet==1.5.0
83
+ httpcore==1.0.9
84
+ httptools==0.7.1
85
+ httpx==0.28.1
86
+ huey==2.6.0
87
+ huggingface_hub==1.15.0
88
+ hydra-core==1.3.2
89
+ idna==3.15
90
+ importlib_metadata==9.0.0
91
+ iniconfig==2.3.0
92
+ iterative-telemetry==0.0.10
93
+ itsdangerous==2.2.0
94
+ Jinja2==3.1.6
95
+ joblib==1.5.3
96
+ kiwisolver==1.5.0
97
+ kombu==5.6.2
98
+ litestar==2.21.1
99
+ litestar-htmx==0.5.0
100
+ Mako==1.3.12
101
+ markdown-it-py==4.2.0
102
+ MarkupSafe==3.0.3
103
+ matplotlib==3.10.9
104
+ mccabe==0.7.0
105
+ mdurl==0.1.2
106
+ mlflow==3.12.0
107
+ mlflow-skinny==3.12.0
108
+ mlflow-tracing==3.12.0
109
+ mpmath==1.3.0
110
+ msgspec==0.21.1
111
+ multidict==6.7.1
112
+ multipart==1.3.1
113
+ multiprocess==0.70.19
114
+ mypy_extensions==1.1.0
115
+ networkx==3.6.1
116
+ nltk==3.9.4
117
+ numpy==2.4.6
118
+ omegaconf==2.3.0
119
+ opentelemetry-api==1.42.0
120
+ opentelemetry-proto==1.42.0
121
+ opentelemetry-sdk==1.42.0
122
+ opentelemetry-semantic-conventions==0.63b0
123
+ orjson==3.11.9
124
+ packaging==26.2
125
+ pandas==2.3.3
126
+ pathspec==1.1.1
127
+ patsy==1.0.2
128
+ pillow==12.2.0
129
+ platformdirs==4.9.6
130
+ plotly==5.24.1
131
+ pluggy==1.6.0
132
+ polyfactory==3.3.0
133
+ prettytable==3.17.0
134
+ prompt_toolkit==3.0.52
135
+ propcache==0.5.2
136
+ protobuf==6.33.6
137
+ psutil==7.2.2
138
+ pyarrow==23.0.1
139
+ pyasn1==0.6.3
140
+ pyasn1_modules==0.4.2
141
+ pycodestyle==2.14.0
142
+ pycparser==3.0
143
+ pydantic==2.13.4
144
+ pydantic-settings==2.14.1
145
+ pydantic_core==2.46.4
146
+ pydot==4.0.1
147
+ pydub==0.25.1
148
+ pyflakes==3.4.0
149
+ pygit2==1.19.2
150
+ Pygments==2.20.0
151
+ pygtrie==2.5.0
152
+ pyparsing==3.3.2
153
+ pytest==9.0.3
154
+ pytest-cov==7.1.0
155
+ python-dateutil==2.9.0.post0
156
+ python-dotenv==1.2.2
157
+ python-multipart==0.0.29
158
+ pytokens==0.4.1
159
+ pytz==2026.2
160
+ pywin32==311
161
+ PyYAML==6.0.3
162
+ regex==2026.5.9
163
+ requests==2.34.2
164
+ rich==15.0.0
165
+ rich-click==1.9.7
166
+ ruamel.yaml==0.19.1
167
+ safehttpx==0.1.7
168
+ safetensors==0.7.0
169
+ scikit-learn==1.8.0
170
+ scipy==1.17.1
171
+ scmrepo==3.6.2
172
+ seaborn==0.13.2
173
+ semantic-version==2.10.0
174
+ semver==3.0.4
175
+ shellingham==1.5.4
176
+ shortuuid==1.0.13
177
+ shtab==1.8.0
178
+ six==1.17.0
179
+ skops==0.14.0
180
+ smmap==5.0.3
181
+ sniffio==1.3.1
182
+ SQLAlchemy==2.0.49
183
+ sqlparse==0.5.5
184
+ sqltrie==0.11.2
185
+ starlette==0.52.1
186
+ statsmodels==0.14.6
187
+ sympy==1.14.0
188
+ tabulate==0.10.0
189
+ tenacity==9.1.4
190
+ threadpoolctl==3.6.0
191
+ tokenizers==0.22.2
192
+ tomlkit==0.14.0
193
+ torch==2.12.0
194
+ tqdm==4.67.3
195
+ transformers==5.8.1
196
+ typer==0.25.1
197
+ typing-inspect==0.9.0
198
+ typing-inspection==0.4.2
199
+ typing_extensions==4.15.0
200
+ tzdata==2026.2
201
+ tzlocal==5.3.1
202
+ urllib3==2.7.0
203
+ uuid6==2025.0.1
204
+ uvicorn==0.47.0
205
+ vine==5.1.0
206
+ voluptuous==0.16.0
207
+ waitress==3.0.2
208
+ watchdog==6.0.0
209
+ watchfiles==1.2.0
210
+ wcwidth==0.7.0
211
+ websockets==16.0
212
+ Werkzeug==3.1.8
213
+ xxhash==3.7.0
214
+ yarl==1.23.0
215
+ zc.lockfile==4.0
216
+ zipp==4.1.0
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (136 Bytes). View file
 
src/api/__init__.py ADDED
File without changes
src/api/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (140 Bytes). View file
 
src/api/__pycache__/main.cpython-311.pyc ADDED
Binary file (1.81 kB). View file
 
src/api/__pycache__/schemas.cpython-311.pyc ADDED
Binary file (900 Bytes). View file
 
src/api/main.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import joblib
2
+ import numpy as np
3
+ from fastapi import FastAPI
4
+ from src.api.schemas import PredictionRequest, PredictionResponse
5
+
6
+ app = FastAPI(
7
+ title="BBC Document Classification API",
8
+ version="1.0.0"
9
+ )
10
+
11
+ model = joblib.load("models/best_model.pkl")
12
+
13
+
14
+ @app.get("/")
15
+ def home():
16
+ return {"message": "BBC Document Classification API is running"}
17
+
18
+
19
+ @app.get("/health")
20
+ def health():
21
+ return {"status": "healthy"}
22
+
23
+
24
+ @app.post("/predict", response_model=PredictionResponse)
25
+ def predict(request: PredictionRequest):
26
+ prediction = model.predict([request.text])[0]
27
+
28
+ decision_scores = model.decision_function([request.text])
29
+ confidence_score = float(np.max(decision_scores))
30
+
31
+ return {
32
+ "predicted_class": prediction,
33
+ "confidence_score": round(confidence_score, 4)
34
+ }
src/api/schemas.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class PredictionRequest(BaseModel):
5
+ text: str = Field(..., min_length=5)
6
+
7
+
8
+ class PredictionResponse(BaseModel):
9
+ predicted_class: str
10
+ confidence_score: float
src/data/__init__.py ADDED
File without changes
src/data/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (141 Bytes). View file
 
src/data/__pycache__/preprocess.cpython-311.pyc ADDED
Binary file (1.62 kB). View file
 
src/data/check_data.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+
3
+ df = pd.read_csv("data/raw/bbc-text.csv")
4
+
5
+ print("\nFirst 5 Rows:")
6
+ print(df.head())
7
+
8
+ print("\nDataset Shape:")
9
+ print(df.shape)
10
+
11
+ print("\nColumns:")
12
+ print(df.columns)
13
+
14
+ print("\nMissing Values:")
15
+ print(df.isnull().sum())
16
+
17
+ print("\nClass Distribution:")
18
+ print(df["label_text"].value_counts())
src/data/load_data.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ import pandas as pd
3
+ import os
4
+
5
+ dataset = load_dataset("SetFit/bbc-news")
6
+
7
+ train_df = pd.DataFrame(dataset["train"])
8
+ test_df = pd.DataFrame(dataset["test"])
9
+
10
+ df = pd.concat([train_df, test_df], ignore_index=True)
11
+
12
+ os.makedirs("data/raw", exist_ok=True)
13
+
14
+ df.to_csv("data/raw/bbc-text.csv", index=False)
15
+
16
+ print(df.shape)
17
+ print(df.head())
src/data/preprocess.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import pandas as pd
4
+
5
+
6
+ def clean_text(text):
7
+ text = str(text).lower()
8
+ text = re.sub(r"[^a-zA-Z0-9\s]", "", text)
9
+ text = re.sub(r"\s+", " ", text).strip()
10
+ return text
11
+
12
+
13
+ def preprocess_data(
14
+ input_path="data/raw/bbc-text.csv",
15
+ output_path="data/processed/processed_bbc.csv"
16
+ ):
17
+ df = pd.read_csv(input_path)
18
+
19
+ df["clean_text"] = df["text"].apply(clean_text)
20
+
21
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
22
+
23
+ df.to_csv(output_path, index=False)
24
+
25
+ print("Preprocessing completed!")
26
+ print(df.head())
27
+
28
+
29
+ if __name__ == "__main__":
30
+ preprocess_data()
src/data/split_data.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ from sklearn.model_selection import train_test_split
4
+
5
+ RANDOM_STATE = 42
6
+
7
+ df = pd.read_csv("data/processed/processed_bbc.csv")
8
+
9
+ train_df, temp_df = train_test_split(
10
+ df,
11
+ test_size=0.30,
12
+ random_state=RANDOM_STATE,
13
+ stratify=df["label_text"]
14
+ )
15
+
16
+ val_df, test_df = train_test_split(
17
+ temp_df,
18
+ test_size=0.50,
19
+ random_state=RANDOM_STATE,
20
+ stratify=temp_df["label_text"]
21
+ )
22
+
23
+ os.makedirs("data/splits", exist_ok=True)
24
+
25
+ train_df.to_csv("data/splits/train.csv", index=False)
26
+ val_df.to_csv("data/splits/val.csv", index=False)
27
+ test_df.to_csv("data/splits/test.csv", index=False)
28
+
29
+ print("Train/Validation/Test split completed.")
30
+ print("Train shape:", train_df.shape)
31
+ print("Validation shape:", val_df.shape)
32
+ print("Test shape:", test_df.shape)
33
+
34
+ print("\nTrain class distribution:")
35
+ print(train_df["label_text"].value_counts())
src/models/__init__.py ADDED
File without changes
src/models/train_baseline.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mlflow
3
+ import mlflow.sklearn
4
+ import pandas as pd
5
+
6
+ from sklearn.feature_extraction.text import TfidfVectorizer
7
+ from sklearn.linear_model import LogisticRegression
8
+ from sklearn.pipeline import Pipeline
9
+ from sklearn.metrics import accuracy_score, f1_score, classification_report
10
+ import joblib
11
+
12
+
13
+ train_df = pd.read_csv("data/splits/train.csv")
14
+ val_df = pd.read_csv("data/splits/val.csv")
15
+
16
+ X_train = train_df["clean_text"]
17
+ y_train = train_df["label_text"]
18
+
19
+ X_val = val_df["clean_text"]
20
+ y_val = val_df["label_text"]
21
+
22
+ os.makedirs("models", exist_ok=True)
23
+
24
+ mlflow.set_experiment("bbc-document-classification")
25
+
26
+ with mlflow.start_run(run_name="tfidf_logistic_regression"):
27
+
28
+ model = Pipeline([
29
+ ("tfidf", TfidfVectorizer(max_features=5000)),
30
+ ("classifier", LogisticRegression(max_iter=1000))
31
+ ])
32
+
33
+ model.fit(X_train, y_train)
34
+
35
+ y_pred = model.predict(X_val)
36
+
37
+ accuracy = accuracy_score(y_val, y_pred)
38
+ f1 = f1_score(y_val, y_pred, average="weighted")
39
+
40
+ print("Accuracy:", accuracy)
41
+ print("F1 Score:", f1)
42
+ print("\nClassification Report:")
43
+ print(classification_report(y_val, y_pred))
44
+
45
+ mlflow.log_param("model", "TF-IDF + Logistic Regression")
46
+ mlflow.log_param("max_features", 5000)
47
+ mlflow.log_metric("accuracy", accuracy)
48
+ mlflow.log_metric("f1_score", f1)
49
+
50
+ joblib.dump(model, "models/baseline_model.pkl")
51
+ mlflow.sklearn.log_model(model, "baseline_model")
52
+
53
+ print("Baseline model training completed.")
src/models/train_compare_models.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mlflow
3
+ import mlflow.sklearn
4
+ import pandas as pd
5
+ import joblib
6
+
7
+ from sklearn.pipeline import Pipeline
8
+ from sklearn.feature_extraction.text import TfidfVectorizer
9
+ from sklearn.linear_model import LogisticRegression
10
+ from sklearn.svm import LinearSVC
11
+ from sklearn.ensemble import RandomForestClassifier
12
+ from sklearn.metrics import accuracy_score, f1_score, classification_report
13
+
14
+
15
+ train_df = pd.read_csv("data/splits/train.csv")
16
+ val_df = pd.read_csv("data/splits/val.csv")
17
+
18
+ X_train = train_df["clean_text"]
19
+ y_train = train_df["label_text"]
20
+
21
+ X_val = val_df["clean_text"]
22
+ y_val = val_df["label_text"]
23
+
24
+ os.makedirs("models", exist_ok=True)
25
+
26
+ models = {
27
+ "logistic_regression": LogisticRegression(max_iter=1000),
28
+ "linear_svm": LinearSVC(),
29
+ "random_forest": RandomForestClassifier(n_estimators=100, random_state=42),
30
+ }
31
+
32
+ mlflow.set_experiment("bbc-document-classification")
33
+
34
+ best_model = None
35
+ best_model_name = None
36
+ best_f1 = 0
37
+
38
+ for model_name, classifier in models.items():
39
+ with mlflow.start_run(run_name=model_name):
40
+
41
+ pipeline = Pipeline([
42
+ ("tfidf", TfidfVectorizer(max_features=5000)),
43
+ ("classifier", classifier)
44
+ ])
45
+
46
+ pipeline.fit(X_train, y_train)
47
+
48
+ y_pred = pipeline.predict(X_val)
49
+
50
+ accuracy = accuracy_score(y_val, y_pred)
51
+ f1 = f1_score(y_val, y_pred, average="weighted")
52
+
53
+ print("\n==============================")
54
+ print(f"Model: {model_name}")
55
+ print("Accuracy:", accuracy)
56
+ print("F1 Score:", f1)
57
+ print(classification_report(y_val, y_pred))
58
+
59
+ mlflow.log_param("model_name", model_name)
60
+ mlflow.log_param("vectorizer", "TF-IDF")
61
+ mlflow.log_param("max_features", 5000)
62
+ mlflow.log_metric("accuracy", accuracy)
63
+ mlflow.log_metric("f1_score", f1)
64
+
65
+ mlflow.sklearn.log_model(pipeline, model_name)
66
+
67
+ if f1 > best_f1:
68
+ best_f1 = f1
69
+ best_model = pipeline
70
+ best_model_name = model_name
71
+
72
+ joblib.dump(best_model, "models/best_model.pkl")
73
+
74
+ print("\nBest model:", best_model_name)
75
+ print("Best F1:", best_f1)
76
+ print("Saved to models/best_model.pkl")
src/models/train_transformer.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mlflow
3
+ import mlflow.transformers
4
+ import pandas as pd
5
+ import numpy as np
6
+
7
+ from datasets import Dataset
8
+ from transformers import (
9
+ DistilBertTokenizerFast,
10
+ DistilBertForSequenceClassification,
11
+ TrainingArguments,
12
+ Trainer
13
+ )
14
+
15
+ from sklearn.metrics import accuracy_score, f1_score
16
+
17
+
18
+ # Load datasets
19
+ train_df = pd.read_csv("data/splits/train.csv")
20
+ val_df = pd.read_csv("data/splits/val.csv")
21
+
22
+ # Label mapping
23
+ labels = sorted(train_df["label_text"].unique())
24
+
25
+ label2id = {label: idx for idx, label in enumerate(labels)}
26
+ id2label = {idx: label for label, idx in label2id.items()}
27
+
28
+ train_df["label_id"] = train_df["label_text"].map(label2id)
29
+ val_df["label_id"] = val_df["label_text"].map(label2id)
30
+
31
+ # Convert to Hugging Face Dataset
32
+ train_dataset = Dataset.from_pandas(
33
+ train_df[["clean_text", "label_id"]]
34
+ )
35
+
36
+ val_dataset = Dataset.from_pandas(
37
+ val_df[["clean_text", "label_id"]]
38
+ )
39
+
40
+ # Load tokenizer
41
+ tokenizer = DistilBertTokenizerFast.from_pretrained(
42
+ "distilbert-base-uncased"
43
+ )
44
+
45
+
46
+ # Tokenization function
47
+ def tokenize(batch):
48
+ return tokenizer(
49
+ batch["clean_text"],
50
+ padding="max_length",
51
+ truncation=True,
52
+ max_length=256
53
+ )
54
+
55
+
56
+ train_dataset = train_dataset.map(tokenize, batched=True)
57
+ val_dataset = val_dataset.map(tokenize, batched=True)
58
+
59
+ # Model
60
+ model = DistilBertForSequenceClassification.from_pretrained(
61
+ "distilbert-base-uncased",
62
+ num_labels=len(labels),
63
+ id2label=id2label,
64
+ label2id=label2id
65
+ )
66
+
67
+
68
+ # Metrics
69
+ def compute_metrics(eval_pred):
70
+ logits, labels = eval_pred
71
+
72
+ predictions = np.argmax(logits, axis=-1)
73
+
74
+ accuracy = accuracy_score(labels, predictions)
75
+
76
+ f1 = f1_score(
77
+ labels,
78
+ predictions,
79
+ average="weighted"
80
+ )
81
+
82
+ return {
83
+ "accuracy": accuracy,
84
+ "f1": f1
85
+ }
86
+
87
+
88
+ # Training arguments
89
+ training_args = TrainingArguments(
90
+ output_dir="models/distilbert_output",
91
+ evaluation_strategy="epoch",
92
+ save_strategy="epoch",
93
+ learning_rate=2e-5,
94
+ per_device_train_batch_size=8,
95
+ per_device_eval_batch_size=8,
96
+ num_train_epochs=2,
97
+ weight_decay=0.01,
98
+ logging_dir="./logs",
99
+ load_best_model_at_end=True
100
+ )
101
+
102
+ # Trainer
103
+ trainer = Trainer(
104
+ model=model,
105
+ args=training_args,
106
+ train_dataset=train_dataset,
107
+ eval_dataset=val_dataset,
108
+ compute_metrics=compute_metrics
109
+ )
110
+
111
+ # MLflow
112
+ mlflow.set_experiment("bbc-document-classification")
113
+
114
+ with mlflow.start_run(run_name="distilbert_classifier"):
115
+
116
+ trainer.train()
117
+
118
+ metrics = trainer.evaluate()
119
+
120
+ print(metrics)
121
+
122
+ mlflow.log_params({
123
+ "model": "DistilBERT",
124
+ "epochs": 2,
125
+ "batch_size": 8,
126
+ "learning_rate": 2e-5
127
+ })
128
+
129
+ mlflow.log_metrics(metrics)
130
+
131
+ trainer.save_model("models/distilbert_model")
132
+
133
+ mlflow.transformers.log_model(
134
+ transformers_model={
135
+ "model": model,
136
+ "tokenizer": tokenizer
137
+ },
138
+ artifact_path="distilbert_model"
139
+ )
140
+
141
+ print("Transformer training completed!")
src/monitoring/__init__.py ADDED
File without changes
src/monitoring/drift_report.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+
4
+ from evidently import Report
5
+ from evidently.presets import DataDriftPreset
6
+
7
+
8
+ REFERENCE_DATA_PATH = "data/splits/train.csv"
9
+ CURRENT_DATA_PATH = "data/splits/test.csv"
10
+ REPORT_OUTPUT_PATH = "reports/data_drift_report.html"
11
+
12
+
13
+ def generate_drift_report():
14
+ reference_data = pd.read_csv(REFERENCE_DATA_PATH)
15
+ current_data = pd.read_csv(CURRENT_DATA_PATH)
16
+
17
+ # Use simple numerical features for drift monitoring
18
+ reference_data["text_length"] = reference_data["clean_text"].apply(len)
19
+ reference_data["word_count"] = reference_data["clean_text"].apply(lambda x: len(str(x).split()))
20
+
21
+ current_data["text_length"] = current_data["clean_text"].apply(len)
22
+ current_data["word_count"] = current_data["clean_text"].apply(lambda x: len(str(x).split()))
23
+
24
+ reference_features = reference_data[["text_length", "word_count"]]
25
+ current_features = current_data[["text_length", "word_count"]]
26
+
27
+ report = Report([
28
+ DataDriftPreset()
29
+ ])
30
+
31
+ result = report.run(
32
+ reference_data=reference_features,
33
+ current_data=current_features
34
+ )
35
+
36
+ os.makedirs("reports", exist_ok=True)
37
+ result.save_html(REPORT_OUTPUT_PATH)
38
+
39
+ print("Data drift report generated successfully.")
40
+ print(f"Report saved at: {REPORT_OUTPUT_PATH}")
41
+
42
+
43
+ if __name__ == "__main__":
44
+ generate_drift_report()
src/utils/__init__.py ADDED
File without changes