PhucTG2k5P commited on
Commit
008cd30
·
1 Parent(s): f6373d7
Files changed (3) hide show
  1. README.md +50 -5
  2. app.py +232 -0
  3. requirements.txt +15 -0
README.md CHANGED
@@ -1,12 +1,57 @@
1
  ---
2
- title: Test6
3
- emoji: 🏢
4
  colorFrom: blue
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.1.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Image Captioning
3
+ emoji: 🖼️
4
  colorFrom: blue
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: "4.40.0"
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # Image Captioning Hugging Face Space
13
+
14
+ Triển khai inference cho mô hình image captioning bằng TensorFlow/Keras, với giao diện Gradio đơn giản cho upload ảnh và nhận caption.
15
+
16
+
17
+ # Image Captioning — Hugging Face Space
18
+
19
+ Triển khai inference cho mô hình image captioning dùng TensorFlow/Keras, EfficientNetV2B0 và giao diện Gradio.
20
+
21
+ ## Cấu trúc tệp cần có
22
+ ```text
23
+ .
24
+ ├── app.py # UI Gradio cho Hugging Face Space
25
+ ├── flickr30k.py # Logic model + tiền xử lý (đã cung cấp)
26
+ ├── best_model.keras # Trọng số mô hình (đặt cùng thư mục)
27
+ ├── tokenizer.pkl # Tokenizer đã fit
28
+ ├── model_config.pkl # Chứa max_length, vocab_size
29
+ ├── requirements.txt
30
+ └── README.md
31
+ ```
32
+
33
+ Các hàm sử dụng trực tiếp từ `flickr30k.py`: `load_caption_model`, `load_tokenizer_and_config`, `load_feature_extractor`, `extract_features_from_image`, `generate_caption`.
34
+
35
+ ## Chạy cục bộ
36
+ ```bash
37
+ python -m venv .venv
38
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
39
+ pip install --upgrade pip
40
+ pip install -r requirements.txt
41
+
42
+ # Đảm bảo 3 tệp đã có:
43
+ # best_model.keras, tokenizer.pkl, model_config.pkl
44
+
45
+ python app.py
46
+ ```
47
+ Mở URL Gradio hiển thị trong terminal.
48
+
49
+ ## Triển khai lên Hugging Face Spaces
50
+ 1) Tạo Space mới: SDK = Gradio, chọn CPU hoặc GPU tùy trọng số.
51
+ 2) Đẩy các tệp: `app.py`, `flickr30k.py`, `requirements.txt`, `README.md`, và 3 tệp trọng số/cấu hình.
52
+ 3) Sau khi build hoàn tất, Space sẽ mở UI upload ảnh và trả caption.
53
+
54
+ ## Ghi chú tương thích
55
+ - Mặc định dùng `tensorflow==2.12.0`. Nếu bạn dùng trọng số huấn luyện ở phiên bản khác, cần đồng bộ phiên bản TensorFlow/Keras tương ứng.
56
+ - Sử dụng `opencv-python-headless` thay vì `opencv-python` để tránh lỗi GUI trên môi trường server.
57
+ - Nếu thiếu tài nguyên trên Space Free, hạ kích thước mô hình hoặc chuyển phần cứng sang GPU trả phí.
app.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ import pickle
5
+ from PIL import Image
6
+ import matplotlib.pyplot as plt
7
+ import tensorflow as tf
8
+ from tensorflow.keras import layers
9
+ from tensorflow.keras.models import load_model, Model
10
+ from tensorflow.keras.applications import EfficientNetV2B0
11
+ from tensorflow.keras.applications.efficientnet import preprocess_input as efficientnet_preprocess
12
+ from tensorflow.keras.preprocessing.sequence import pad_sequences
13
+ from tensorflow.keras.preprocessing.image import img_to_array
14
+ from tqdm import tqdm
15
+ import random
16
+ from tensorflow.keras.preprocessing.sequence import pad_sequences
17
+
18
+ import tempfile
19
+ import traceback
20
+ from pathlib import Path
21
+ from huggingface_hub import hf_hub_download
22
+
23
+ import gradio as gr
24
+ from PIL import Image
25
+ import pickle
26
+
27
+
28
+
29
+ # -----------------------------
30
+ # Custom attention layers
31
+ # -----------------------------
32
+
33
+ class ChannelAttention(layers.Layer):
34
+ def __init__(self, ratio=8, **kwargs):
35
+ super(ChannelAttention, self).__init__(**kwargs)
36
+ self.ratio = ratio
37
+
38
+ def build(self, input_shape):
39
+ self.gap = layers.GlobalAveragePooling1D()
40
+ self.gmp = layers.GlobalMaxPooling1D()
41
+ self.shared_mlp = tf.keras.Sequential([
42
+ layers.Dense(units=1280 // self.ratio, activation='relu'),
43
+ layers.Dense(units=1280)
44
+ ])
45
+ self.sigmoid = layers.Activation('sigmoid')
46
+ super(ChannelAttention, self).build(input_shape)
47
+
48
+ def call(self, inputs):
49
+ gap = self.gap(inputs)
50
+ gmp = self.gmp(inputs)
51
+ gap_mlp = self.shared_mlp(gap)
52
+ gmp_mlp = self.shared_mlp(gmp)
53
+ channel_attention = self.sigmoid(gap_mlp + gmp_mlp)
54
+ return inputs * tf.expand_dims(channel_attention, axis=1)
55
+
56
+ def get_config(self):
57
+ config = super(ChannelAttention, self).get_config()
58
+ config.update({'ratio': self.ratio})
59
+ return config
60
+
61
+ @classmethod
62
+ def from_config(cls, config):
63
+ return cls(**config)
64
+
65
+
66
+
67
+ class SpatialAttention(layers.Layer):
68
+ def __init__(self, **kwargs):
69
+ super(SpatialAttention, self).__init__(**kwargs)
70
+
71
+ def build(self, input_shape):
72
+ self.conv = layers.Conv1D(1, kernel_size=3, padding='same', activation='sigmoid')
73
+ super(SpatialAttention, self).build(input_shape)
74
+
75
+ def call(self, inputs):
76
+ spatial_attention = self.conv(inputs)
77
+ return inputs * spatial_attention
78
+
79
+ def get_config(self):
80
+ return super(SpatialAttention, self).get_config()
81
+
82
+ @classmethod
83
+ def from_config(cls, config):
84
+ return cls(**config)
85
+
86
+
87
+
88
+ # -----------------------------
89
+ # Load model + tokenizer
90
+ # -----------------------------
91
+
92
+ def load_caption_model(model_path):
93
+ custom_objects = {
94
+ 'ChannelAttention': ChannelAttention,
95
+ 'SpatialAttention': SpatialAttention
96
+ }
97
+ model = load_model(model_path, custom_objects=custom_objects)
98
+ print("✅ Đã load model thành công!")
99
+ return model
100
+
101
+
102
+ def load_tokenizer_and_config(tokenizer_path, config_path):
103
+ with open(tokenizer_path, 'rb') as f:
104
+ tokenizer = pickle.load(f)
105
+ with open(config_path, 'rb') as f:
106
+ config = pickle.load(f)
107
+ return tokenizer, config['max_length'], config['vocab_size']
108
+
109
+
110
+ # -----------------------------
111
+ # Feature extractor - EfficientNetV2B0
112
+ # -----------------------------
113
+
114
+ def load_feature_extractor():
115
+ base_model = EfficientNetV2B0(include_top=False, weights='imagenet', pooling='avg')
116
+ return Model(inputs=base_model.input, outputs=base_model.output)
117
+
118
+
119
+ def extract_features_from_image(image_path, extractor):
120
+ image = cv2.imread(image_path)
121
+ if image is None:
122
+ print(f"❌ Không đọc được ảnh: {image_path}")
123
+ return None
124
+ image = cv2.resize(image, (224, 224))
125
+ image = img_to_array(image)
126
+ image = np.expand_dims(image, axis=0)
127
+ image = efficientnet_preprocess(image)
128
+ feature = extractor.predict(image, verbose=0)
129
+ return feature
130
+
131
+
132
+ # -----------------------------
133
+ # Generate caption
134
+ # -----------------------------
135
+
136
+ def generate_caption(model, tokenizer, image_features, max_length):
137
+ in_text = 'startseq'
138
+ for _ in range(max_length):
139
+ sequence = tokenizer.texts_to_sequences([in_text])[0]
140
+ sequence = pad_sequences([sequence], maxlen=max_length)
141
+ yhat = model.predict([image_features, sequence], verbose=0)
142
+ yhat = np.argmax(yhat)
143
+ word = tokenizer.index_word.get(yhat)
144
+ if word is None or word == 'endseq':
145
+ break
146
+ in_text += ' ' + word
147
+ return in_text.replace('startseq ', '')
148
+
149
+
150
+ # -----------------------------
151
+ # Chạy test
152
+ # -----------------------------
153
+
154
+ MODEL_REPO = "slyviee/img_cap"
155
+
156
+ # Khởi tạo tài nguyên toàn cục khi app start
157
+ model_path = hf_hub_download(repo_id=MODEL_REPO, filename="best_model.keras")
158
+ tokenizer_path = hf_hub_download(repo_id=MODEL_REPO, filename="tokenizer.pkl")
159
+ config_path = hf_hub_download(repo_id=MODEL_REPO, filename="model_config.pkl")
160
+
161
+ model = None
162
+ tokenizer = None
163
+ max_length = None
164
+ vocab_size = None
165
+ extractor = None
166
+ ready = False
167
+ startup_error = ""
168
+
169
+
170
+ def _startup():
171
+ global model, tokenizer, max_length, vocab_size, extractor, ready, startup_error
172
+ try:
173
+ # Kiểm tra sự tồn tại của các tệp cần thiết
174
+ missing = [p for p in [model_path, tokenizer_path, config_path] if not Path(p).exists()]
175
+ if missing:
176
+ startup_error = "Thiếu tệp: " + ", ".join(missing)
177
+ ready = False
178
+ return
179
+
180
+ print("🔄 Đang tải model...")
181
+ model = load_caption_model(model_path)
182
+ print("✅ Model đã được tải.")
183
+
184
+ print("🔄 Đang tải tokenizer và config...")
185
+ tokenizer, max_length, vocab_size = load_tokenizer_and_config(tokenizer_path, config_path)
186
+ print("✅ Tokenizer và config đã được tải.")
187
+
188
+ print("🔄 Đang tải feature extractor...")
189
+ extractor = load_feature_extractor()
190
+ print("✅ Feature extractor đã được tải.")
191
+
192
+ ready = True
193
+ except Exception as e:
194
+ startup_error = f"Khởi tạo lỗi: {e}\n{traceback.format_exc()}"
195
+ ready = False
196
+
197
+
198
+ def predict(pil_image: Image.Image):
199
+ if not ready:
200
+ return f"Hệ thống chưa sẵn sàng. {startup_error or 'Thiếu model/tokenizer/config.'}"
201
+
202
+ try:
203
+ # Lưu ảnh tạm để tái sử dụng hàm extract_features_from_image (đọc bằng cv2)
204
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
205
+ pil_image.convert("RGB").save(tmp.name, format="JPEG")
206
+ tmp_path = tmp.name
207
+
208
+ features = extract_features_from_image(tmp_path, extractor)
209
+ os.unlink(tmp_path)
210
+
211
+ if features is None:
212
+ return "Không đọc được ảnh đầu vào."
213
+ caption = generate_caption(model, tokenizer, features, max_length)
214
+ return caption
215
+ except Exception as e:
216
+ return f"Lỗi trong quá trình dự đoán: {e}\n{traceback.format_exc()}"
217
+
218
+ DESCRIPTION = (
219
+ "Upload ảnh và nhận caption sinh ra bởi mô hình. "
220
+ )
221
+
222
+ demo = gr.Interface(
223
+ fn=predict,
224
+ inputs=gr.Image(type="pil", label="Ảnh vào"),
225
+ outputs=gr.Textbox(label="Caption"),
226
+ title="Image Captioning — Gradio",
227
+ description=DESCRIPTION,
228
+ )
229
+
230
+ if __name__ == '__main__':
231
+ _startup()
232
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core runtime
2
+ tensorflow==2.20
3
+ numpy<2
4
+ pillow>=9.5.0
5
+ opencv-python-headless==4.9.0.80
6
+ matplotlib>=3.7.0
7
+
8
+ # NLP + metrics
9
+ nltk>=3.8.1
10
+
11
+ # UI
12
+ gradio
13
+
14
+ # Progress bars
15
+ tqdm>=4.66.0