Spaces:
Sleeping
Sleeping
Upload 11 files
Browse files- .gitattributes +1 -0
- README.md +116 -0
- models/amazon_classifier.pkl +3 -0
- models/label_encoder.pkl +3 -0
- models/numeric_cols.pkl +3 -0
- models/optuna_amazon_pro.db +3 -0
- models/pca_pro.pkl +3 -0
- models/scaler_pro.pkl +3 -0
- models/svd_pro.pkl +3 -0
- models/tfidf_pro.pkl +3 -0
- requirements.txt +9 -0
- src/app.py +197 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
models/optuna_amazon_pro.db filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Amazon Product Classifier
|
| 3 |
+
sdk: gradio
|
| 4 |
+
sdk_version: 4.44.0
|
| 5 |
+
app_file: src/app.py
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# Amazon Product Classifier
|
| 9 |
+
|
| 10 |
+
**Dự đoán danh mục sản phẩm Amazon bằng NLP & Học Máy – từ dữ liệu chưa có nhãn**
|
| 11 |
+
|
| 12 |
+
[](https://colab.research.google.com/drive/1vQbgzVixv8uaIGvLJACGdlxaM2jeR4vL?usp=sharing)
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## Giới thiệu
|
| 17 |
+
|
| 18 |
+
Hệ thống học máy pipeline hoàn chỉnh, giải quyết bài toán **gán nhãn tự động và phân loại sản phẩm** khi dữ liệu gốc **không có cột danh mục (category)**. Toàn bộ quá trình từ xử lý thô, tạo nhãn thông minh, trích xuất đặc trưng đa chiều cho đến huấn luyện & triển khai đều được xây dựng từ đầu.
|
| 19 |
+
|
| 20 |
+
- **Bài toán:** Phân loại đa lớp (Multi-class Classification)
|
| 21 |
+
- **Nguồn dữ liệu:** [Amazon Products Sales Dataset 42k Items (2025)](https://www.kaggle.com/datasets/ikramshah512/amazon-products-sales-dataset-42k-items-2025?select=amazon_products_sales_data_uncleaned.csv) từ Kaggle.
|
| 22 |
+
**Đặc điểm cốt lõi:** Dữ liệu chỉ bao gồm tên sản phẩm, giá, đánh giá,... **hoàn toàn không có nhãn danh mục**. Điều này biến bài toán trở thành tự động tạo ra “ground truth” trước khi có thể huấn luyện mô hình phân loại.
|
| 23 |
+
|
| 24 |
+
- ** 10 danh mục mục tiêu do tôi định nghĩa** (dựa trên phân tích thị trường và đặc trưng sản phẩm):
|
| 25 |
+
1. `Laptops`
|
| 26 |
+
2. `Phones`
|
| 27 |
+
3. `Audio`
|
| 28 |
+
4. `Cameras`
|
| 29 |
+
5. `TV & Display`
|
| 30 |
+
6. `Computers & Accessories`
|
| 31 |
+
7. `Other Electronics`
|
| 32 |
+
8. `Wearables`
|
| 33 |
+
9. `Gaming`
|
| 34 |
+
10. `Home & Kitchen`
|
| 35 |
+
|
| 36 |
+
- ** Phân tích chi tiết (code, biểu đồ, bản đồ phân cụm, ma trận nhầm lẫn, feature importance,...):** [Google Colab Notebook](https://colab.research.google.com/drive/1vQbgzVixv8uaIGvLJACGdlxaM2jeR4vL?usp=sharing)
|
| 37 |
+
|
| 38 |
+
> **Điểm đặc biệt của bài toán**
|
| 39 |
+
> - **Không có nhãn mục tiêu** – toàn bộ quá trình gán nhãn được thiết kế thủ công bằng luật và embedding AI, sau đó lan truyền nhãn bằng học máy.
|
| 40 |
+
> - **Mất cân bằng lớp nghiêm trọng** – đồ điện tử áp đảo, các lớp như `Wearables`, `Home & Kitchen` vô cùng ít mẫu. Hệ thống dùng SMOTE, class weighting và cơ chế cảnh báo độ tin cậy để đảm bảo công bằng và an toàn cho người dùng.
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## Pipeline tổng thể
|
| 45 |
+
|
| 46 |
+
| Giai đoạn | Nội dung chính | Kỹ thuật / Công nghệ |
|
| 47 |
+
|---|---|---|
|
| 48 |
+
| **1. Gán nhãn tự động** | Tạo nhãn từ dữ liệu không có target | Keyword Matching, Sentence-BERT (`all-MiniLM-L6-v2`) + Cosine Similarity, Ensemble, Label Propagation (Logistic Regression) |
|
| 49 |
+
| **2. Làm sạch & lọc bẫy** | Loại bỏ sản phẩm “gây nhiễu” | `trap_word_filter` (bảo hành, ốp lưng, sticker…), `price_sanity_check` |
|
| 50 |
+
| **3. Trích xuất đặc trưng** | Số hóa mọi tín hiệu từ sản phẩm | TF‑IDF, TruncatedSVD (LSA), PCA, Social Proof, Brand Tier, Word Count, Discount %, Log Transform |
|
| 51 |
+
| **4. Tiền xử lý & chọn lọc** | Giảm chiều, loại bỏ đa cộng tuyến | `StandardScaler`, VIF, `np.hstack` |
|
| 52 |
+
| **5. Huấn luyện & tối ưu** | Xây dựng mô hình phân loại cuối cùng | LightGBM, Optuna (TPE, SQLite), `StratifiedKFold`, SMOTE, `class_weight='balanced'`, Early Stopping |
|
| 53 |
+
| **6. Triển khai** | Ứng dụng thực tế | Gradio, `ClassificationModel`, ngưỡng tin cậy linh hoạt |
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Công nghệ sử dụng – Chi tiết
|
| 58 |
+
|
| 59 |
+
### Xử lý ngôn ngữ & Gán nhãn
|
| 60 |
+
- **Sentence-BERT** (`all-MiniLM-L6-v2`): embedding 384 chiều cho tên sản phẩm, so khớp với tập nhãn ứng viên qua **Cosine Similarity**.
|
| 61 |
+
- **Keyword Matching (Expert Rules):** bộ từ khóa chuyên biệt cho 10 ngành hàng, hỗ trợ regex linh hoạt (`flexible_kw_match`) bắt cả số nhiều, viết liền hay cách.
|
| 62 |
+
- **Ensemble Keyword + AI:** kết hợp có trọng số (alpha/beta) giữa độ tin cậy từ khóa và similarity score.
|
| 63 |
+
- **Label Propagation:** dùng Logistic Regression huấn luyện trên các mẫu tự tin (≥60%), lan truyền nhãn cho phần còn lại với ngưỡng xác suất ≥45%.
|
| 64 |
+
|
| 65 |
+
### Làm sạch & Logic nghiệp vụ
|
| 66 |
+
- **`trap_word_filter`:** phát hiện các cụm như *protection plan, warranty, case for, guide, sticker* → đưa về nhóm `Other Electronics` hoặc `Other Category`.
|
| 67 |
+
- **`price_sanity_check`:** dùng logic giá để sửa nhãn (VD: Laptop giá dưới $80 → không thể là laptop).
|
| 68 |
+
|
| 69 |
+
### Trích xuất đặc trưng (Feature Engineering)
|
| 70 |
+
- **TF‑IDF** (5000 features, 1‑2 grams) → **TruncatedSVD (256 chiều)**: nắm bắt ngữ nghĩa ẩn từ văn bản.
|
| 71 |
+
- **PCA** trên embedding (giữ 90% variance): giảm chiều, chống overfitting.
|
| 72 |
+
- **Social Proof:** `log(rating × number_of_reviews)` – đại diện cho độ uy tín.
|
| 73 |
+
- **Brand Tier:** mã hóa thương hiệu 3 mức (0‑thường, 1‑top brand, 2‑premium) dựa trên danh sách thương hiệu được định nghĩa sẵn.
|
| 74 |
+
- **Word Count, Discount %, Log Transform:** các đặc trưng thống kê giúp mô hình nhận diện kiểu sản phẩm.
|
| 75 |
+
|
| 76 |
+
### Cân bằng dữ liệu & Chọn biến
|
| 77 |
+
- **SMOTE** (Synthetic Minority Over-sampling): sinh mẫu tổng hợp trong mỗi fold khi cross‑validation.
|
| 78 |
+
- **Class Weight = 'balanced'** trong LightGBM.
|
| 79 |
+
- **VIF (Variance Inflation Factor):** loại bỏ tuần tự biến có VIF > 5 để giảm đa cộng tuyến, giữ lại tập đặc trưng ổn định.
|
| 80 |
+
|
| 81 |
+
### Mô hình & Tối ưu
|
| 82 |
+
- **LightGBM Classifier** (Gradient Boosting): mô hình chính, hỗ trợ GPU, huấn luyện trên dữ liệu lớn.
|
| 83 |
+
- **Optuna** (TPE Sampler, SQLite storage): tối ưu `learning_rate`, `num_leaves`, `max_depth`, `reg_alpha`, `reg_lambda`, `subsample`, `colsample_bytree` với 50 trials trong 4 giờ.
|
| 84 |
+
- **Stratified K‑Fold (k=5) + Early Stopping:** đảm bảo đánh giá khách quan và dừng đúng lúc.
|
| 85 |
+
|
| 86 |
+
### Triển khai
|
| 87 |
+
- **Gradio:** giao diện web trực quan.
|
| 88 |
+
- **Cảnh báo độ tin cậy:** tự động hiển thị cảnh báo nếu xác suất cao nhất < ngưỡng (50%) để cảnh báo người dùng khỏi các dự đoán kém chắc chắn.
|
| 89 |
+
|
| 90 |
+
---
|
| 91 |
+
|
| 92 |
+
## Kết quả huấn luyện
|
| 93 |
+
|
| 94 |
+
| Chỉ số | Giá trị |
|
| 95 |
+
|---|---|
|
| 96 |
+
| **Overall Accuracy** | ~96% |
|
| 97 |
+
| **Balanced Accuracy** | ~0.94 |
|
| 98 |
+
| **Macro F1‑Score** | ~0.956 |
|
| 99 |
+
|
| 100 |
+
> Mặc dù điểm số tổng rất cao, các lớp thiểu số vẫn là thách thức. Cơ chế cảnh báo khi độ tin cậy thấp giúp sản phẩm trở nên đáng tin cậy hơn.
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Hướng dẫn cài đặt & Chạy Demo
|
| 105 |
+
|
| 106 |
+
1. **Yêu cầu:** Python 3.9+
|
| 107 |
+
2. **Clone repository:**
|
| 108 |
+
```bash
|
| 109 |
+
git clone https://github.com/PhTee2412/Amazon_Classifier
|
| 110 |
+
|
| 111 |
+
cd Amazon_Classifier
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
- **Tên**: Phương Thảo
|
| 116 |
+
- **Email**: phtee2412@gmail.com
|
models/amazon_classifier.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5be582c6634b52711b0a9a1da4f675daf0c72a79851a7913109c196afe978d45
|
| 3 |
+
size 27612484
|
models/label_encoder.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:86832bfa98eff76fdfec076cf266b70b8a2e99672e0f3fa526e1e9560e32eda7
|
| 3 |
+
size 1029
|
models/numeric_cols.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a575dfd1794e0f856cfbce9cfc626623ffc67e3c8b400c79f09b827f3839d919
|
| 3 |
+
size 152
|
models/optuna_amazon_pro.db
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2fd750ea5b08f6bf14a482c7553a415bf86253e415992446bfec70e1e1f9b946
|
| 3 |
+
size 122880
|
models/pca_pro.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:98ec0a42b4d9012f8d687ad6de208f3bf2ec4484495c6fef5701f545d7e80ba1
|
| 3 |
+
size 168195
|
models/scaler_pro.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d67e2b82211bcdfe5bc7edbc16c54184100813c8fcfadb48aca076e904bfcdb5
|
| 3 |
+
size 1255
|
models/svd_pro.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f293891f0bce6443f855a6a51f7460de7d47a485ddfdbf0d2c47acc54e38214e
|
| 3 |
+
size 10246887
|
models/tfidf_pro.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0b8858376bd6d026b28fbe3604fcecf65a27b3265d97e9551ed11fa4f1745128
|
| 3 |
+
size 197296
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
pandas
|
| 3 |
+
numpy
|
| 4 |
+
scikit-learn
|
| 5 |
+
sentence-transformers
|
| 6 |
+
lightgbm
|
| 7 |
+
joblib
|
| 8 |
+
matplotlib
|
| 9 |
+
seaborn
|
src/app.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import joblib
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from sentence_transformers import SentenceTransformer
|
| 7 |
+
import lightgbm as lgb
|
| 8 |
+
import gradio as gr
|
| 9 |
+
|
| 10 |
+
stop_words = set([
|
| 11 |
+
'the', 'a', 'an', 'and', 'or', 'but', 'is', 'are', 'am', 'to', 'for',
|
| 12 |
+
'in', 'on', 'at', 'by', 'with', 'this', 'that', 'of', 'from', 'it',
|
| 13 |
+
'its', 'they', 'them', 'their', 'was', 'were', 'be', 'been', 'being',
|
| 14 |
+
'have', 'has', 'had', 'do', 'does', 'did', 'which', 'who', 'whom',
|
| 15 |
+
'whose', 'where', 'when', 'how', 'all', 'any', 'both', 'each',
|
| 16 |
+
'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not',
|
| 17 |
+
'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can',
|
| 18 |
+
'will', 'just', 'don', 'should', 'now', 'i', 'me', 'my', 'myself',
|
| 19 |
+
'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself',
|
| 20 |
+
'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers',
|
| 21 |
+
'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs',
|
| 22 |
+
'themselves'
|
| 23 |
+
])
|
| 24 |
+
|
| 25 |
+
top_brands_list = [
|
| 26 |
+
'western digital', 'apple', 'samsung', 'dell', 'hp', 'lenovo', 'asus',
|
| 27 |
+
'acer', 'sony', 'logitech', 'jbl', 'bose', 'tp-link', 'sandisk',
|
| 28 |
+
'xiaomi', 'oppo', 'vivo', 'oneplus', 'google', 'amazon', 'nintendo',
|
| 29 |
+
'playstation', 'xbox', 'msi', 'gigabyte'
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
premium_brands = [
|
| 33 |
+
'apple', 'samsung', 'sony', 'dell', 'hp', 'bose', 'playstation',
|
| 34 |
+
'xbox', 'nintendo'
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
# ============================ HELPER FUNCTIONS ============================
|
| 38 |
+
def clean_text_pro(text):
|
| 39 |
+
"""Làm sạch tên sản phẩm: lowercase, xoá ký tự đặc biệt, loại bỏ stopwords."""
|
| 40 |
+
if not isinstance(text, str):
|
| 41 |
+
return ""
|
| 42 |
+
text = text.lower()
|
| 43 |
+
text = re.sub(r'[^a-z0-9\s\.]', ' ', text)
|
| 44 |
+
words = [w for w in text.split() if w not in stop_words and len(w) > 1]
|
| 45 |
+
return " ".join(words)
|
| 46 |
+
|
| 47 |
+
def get_brand_tier(name):
|
| 48 |
+
"""Phân hạng thương hiệu: 2 = cao cấp, 1 = phổ biến, 0 = khác."""
|
| 49 |
+
name_lower = str(name).lower()
|
| 50 |
+
for brand in premium_brands:
|
| 51 |
+
if re.search(r'\b' + re.escape(brand) + r'\b', name_lower):
|
| 52 |
+
return 2
|
| 53 |
+
for brand in top_brands_list:
|
| 54 |
+
if re.search(r'\b' + re.escape(brand) + r'\b', name_lower):
|
| 55 |
+
return 1
|
| 56 |
+
return 0
|
| 57 |
+
|
| 58 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 59 |
+
MODEL_DIR = os.path.join(BASE_DIR, '..', 'models')
|
| 60 |
+
|
| 61 |
+
model = joblib.load(os.path.join(MODEL_DIR, 'amazon_classifier.pkl'))
|
| 62 |
+
le = joblib.load(os.path.join(MODEL_DIR, 'label_encoder.pkl'))
|
| 63 |
+
pca = joblib.load(os.path.join(MODEL_DIR, 'pca_pro.pkl'))
|
| 64 |
+
svd = joblib.load(os.path.join(MODEL_DIR, 'svd_pro.pkl'))
|
| 65 |
+
tfidf = joblib.load(os.path.join(MODEL_DIR, 'tfidf_pro.pkl'))
|
| 66 |
+
scaler = joblib.load(os.path.join(MODEL_DIR, 'scaler_pro.pkl'))
|
| 67 |
+
numeric_cols = joblib.load(os.path.join(MODEL_DIR, 'numeric_cols.pkl'))
|
| 68 |
+
|
| 69 |
+
sent_model = SentenceTransformer('all-MiniLM-L6-v2')
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def preprocess_and_predict(name, price, rating, reviews_count, bought_count, actual_price):
|
| 74 |
+
"""
|
| 75 |
+
Nhận dữ liệu từ UI, biến đổi thành vector đặc trưng và dự đoán.
|
| 76 |
+
Trả về dictionary {tên_lớp: xác_suất} cho Gradio hiển thị.
|
| 77 |
+
"""
|
| 78 |
+
# 1. Văn bản (Text Features)
|
| 79 |
+
text = clean_text_pro(str(name))
|
| 80 |
+
emb = pca.transform(sent_model.encode([text]))
|
| 81 |
+
tfidf_vec = svd.transform(tfidf.transform([text]))
|
| 82 |
+
|
| 83 |
+
# 2. Biến số (Numerical Features)
|
| 84 |
+
feat = {
|
| 85 |
+
'price_log': np.log1p(price),
|
| 86 |
+
'rating_clean': float(rating) if rating else 4.2,
|
| 87 |
+
'reviews_log': np.log1p(float(reviews_count)) if reviews_count else 0,
|
| 88 |
+
'bought_count_log': np.log1p(bought_count),
|
| 89 |
+
'discount_pct': (actual_price - price) / actual_price if actual_price > price else 0,
|
| 90 |
+
'social_proof': np.log1p(float(rating) * float(reviews_count)) if rating and reviews_count else 0,
|
| 91 |
+
'word_count': len(str(name).split()),
|
| 92 |
+
'brand_tier': get_brand_tier(name),
|
| 93 |
+
'is_best_seller_flag': 0,
|
| 94 |
+
'has_coupon': 0
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
# 3. Scale các đặc trưng liên tục (Numerical scaling)
|
| 98 |
+
scaler_input_cols = [
|
| 99 |
+
'price_log', 'rating_clean', 'reviews_log', 'bought_count_log',
|
| 100 |
+
'discount_pct', 'social_proof', 'word_count', 'brand_tier',
|
| 101 |
+
'is_best_seller_flag', 'has_coupon'
|
| 102 |
+
]
|
| 103 |
+
df_input = pd.DataFrame([[feat.get(c, 0) for c in scaler_input_cols]], columns=scaler_input_cols)
|
| 104 |
+
scaled_input = pd.DataFrame(scaler.transform(df_input), columns=scaler_input_cols)
|
| 105 |
+
final_num = scaled_input[numeric_cols].values # Chỉ giữ cột có sau VIF
|
| 106 |
+
|
| 107 |
+
X = np.hstack([final_num, emb, tfidf_vec])
|
| 108 |
+
|
| 109 |
+
# 5. Dự đoán
|
| 110 |
+
proba = model.predict_proba(X)[0]
|
| 111 |
+
result = {le.classes_[i]: float(proba[i]) for i in range(len(le.classes_))}
|
| 112 |
+
sorted_result = dict(sorted(result.items(), key=lambda item: item[1], reverse=True))
|
| 113 |
+
max_conf = max(proba)
|
| 114 |
+
|
| 115 |
+
if max_conf < 0.5:
|
| 116 |
+
top_result = dict(list(sorted_result.items())[:1])
|
| 117 |
+
warning_msg = f"**Cảnh báo**: Mức độ tin cậy cao nhất chỉ đạt **{max_conf*100:.1f}%**. Sản phẩm này có thông tin không rõ ràng hoặc có thể thuộc danh mục khác."
|
| 118 |
+
else:
|
| 119 |
+
top_result = dict(list(sorted_result.items())[:3])
|
| 120 |
+
warning_msg = f"Dự đoán thành công với mức độ tin cậy (**{max_conf*100:.1f}%**)."
|
| 121 |
+
|
| 122 |
+
return top_result, warning_msg
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
custom_theme = gr.themes.Soft(
|
| 126 |
+
primary_hue="blue",
|
| 127 |
+
secondary_hue="indigo",
|
| 128 |
+
neutral_hue="slate",
|
| 129 |
+
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"]
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
with gr.Blocks(theme=custom_theme, title="Amazon Product Classifier", css="footer {visibility: hidden}") as demo:
|
| 133 |
+
gr.Markdown(
|
| 134 |
+
"""
|
| 135 |
+
# Ứng dụng Phân loại Sản phẩm Amazon (Amazon Product Classifier)
|
| 136 |
+
**Mô hình phân loại Machine Learning** dự đoán danh mục sản phẩm trên Amazon thông qua **LightGBM** và **Sentence-BERT**.
|
| 137 |
+
Mô hình đạt độ chính xác **~96%**, phân loại thành 1 trong 10 danh mục khác nhau.
|
| 138 |
+
"""
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
with gr.Row():
|
| 142 |
+
# Cột Input
|
| 143 |
+
with gr.Column(scale=2):
|
| 144 |
+
gr.Markdown("### Nhập thông tin Sản phẩm")
|
| 145 |
+
name = gr.Textbox(
|
| 146 |
+
label="Tên sản phẩm",
|
| 147 |
+
placeholder="VD: Apple iPhone 15 Pro Max 256GB Titanium, Blue",
|
| 148 |
+
lines=2
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
with gr.Row():
|
| 152 |
+
price = gr.Number(label="Giá khuyến mãi ($)", value=0.0, precision=2)
|
| 153 |
+
actual_price = gr.Number(label="Giá gốc ($)", value=0.0, precision=2)
|
| 154 |
+
|
| 155 |
+
with gr.Row():
|
| 156 |
+
rating = gr.Slider(label="Điểm đánh giá (Rating)", minimum=1.0, maximum=5.0, step=0.1, value=4.5)
|
| 157 |
+
reviews_count = gr.Number(label="Số lượt đánh giá (Reviews)", value=100, precision=0)
|
| 158 |
+
bought_count = gr.Number(label="Lượt mua tháng trước", value=0, precision=0)
|
| 159 |
+
|
| 160 |
+
predict_btn = gr.Button(" Dự đoán Danh mục", variant="primary", size="lg")
|
| 161 |
+
|
| 162 |
+
# Cột Output
|
| 163 |
+
with gr.Column(scale=1):
|
| 164 |
+
gr.Markdown("### Kết quả Dự đoán")
|
| 165 |
+
output_label = gr.Label(label="Top 3 danh mục phù hợp nhất", num_top_classes=3)
|
| 166 |
+
warning_text = gr.Markdown(value="", visible=True)
|
| 167 |
+
|
| 168 |
+
# Sự kiện khi bấm nút Predict
|
| 169 |
+
predict_btn.click(
|
| 170 |
+
fn=preprocess_and_predict,
|
| 171 |
+
inputs=[name, price, rating, reviews_count, bought_count, actual_price],
|
| 172 |
+
outputs=[output_label, warning_text]
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
# Bảng Ví dụ để test nhanh
|
| 176 |
+
gr.Markdown("### Ví dụ (Click để điền tự động)")
|
| 177 |
+
gr.Examples(
|
| 178 |
+
examples=[
|
| 179 |
+
["Apple iPhone 15 Pro Max 256GB Titanium", 1199, 4.8, 5000, 1000, 1299],
|
| 180 |
+
["Samsung 32-inch Odyssey G5 Gaming Monitor", 300, 4.5, 1200, 500, 350],
|
| 181 |
+
["Logitech MX Master 3S Wireless Mouse", 99, 4.9, 3000, 800, 99],
|
| 182 |
+
["PlayStation 5 Console (PS5)", 499, 4.9, 55000, 5000, 499],
|
| 183 |
+
["Premium Yoga Mat for Exercise", 20, 4.0, 150, 50, 30]
|
| 184 |
+
],
|
| 185 |
+
inputs=[name, price, rating, reviews_count, bought_count, actual_price],
|
| 186 |
+
label="Dữ liệu mẫu"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
gr.Markdown(
|
| 190 |
+
"""
|
| 191 |
+
---
|
| 192 |
+
**Tác giả**: Phương Thảo | **Email**: phtee2412@gmail.com
|
| 193 |
+
"""
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
if __name__ == "__main__":
|
| 197 |
+
demo.launch(share=False)
|