PhTee commited on
Commit
2b61832
·
verified ·
1 Parent(s): 6ab639b

Update src/app.py

Browse files
Files changed (1) hide show
  1. src/app.py +197 -197
src/app.py CHANGED
@@ -1,197 +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)
 
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(title="Amazon Product Classifier") 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(theme=custom_theme, css="footer {visibility: hidden}")