3v324v23 commited on
Commit
a870ca2
·
1 Parent(s): 1b21f77

Force environment rebuild

Browse files
Files changed (2) hide show
  1. app.py +37 -59
  2. packages.txt +1 -0
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # app.py
2
- # File máy chủ Flask hoàn chỉnh để triển khai trên Hugging Face Spaces
3
 
4
  import os
5
  import joblib
@@ -7,10 +7,12 @@ import numpy as np
7
  import librosa
8
  from flask import Flask, request, jsonify, render_template
9
  from werkzeug.utils import secure_filename
 
 
 
 
10
 
11
  # --- Cấu hình TensorFlow và các thư viện AI ---
12
- # Đặt biến môi trường để giảm thiểu log không cần thiết của TensorFlow.
13
- # Phải thực hiện trước khi import tensorflow.
14
  os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
15
  import tensorflow as tf
16
  from transformers import Wav2Vec2Processor, Wav2Vec2Model
@@ -19,31 +21,22 @@ import torch
19
  # --- KHỞI TẠO ỨNG DỤNG FLASK ---
20
  app = Flask(__name__)
21
 
22
- # Cấu hình thư mục tạm để lưu file audio người dùng tải lên
23
  UPLOAD_FOLDER = 'uploads/'
24
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
25
- # Tạo thư mục nếu nó chưa tồn tại
26
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
27
 
28
 
29
  # --- TẢI TẤT CẢ CÁC MÔ HÌNH KHI SERVER KHỞI ĐỘNG ---
30
- # Đây là bước quan trọng để tối ưu hóa hiệu suất. Mô hình chỉ được tải một lần
31
- # thay vì tải lại mỗi khi có yêu cầu dự đoán.
32
  print(">>> Đang tải các mô hình AI, quá trình này có thể mất một lúc...")
33
 
34
  try:
35
  MODEL_PATH = 'models/'
36
-
37
- # Tải các thành phần tiền xử lý và các mô hình machine learning
38
  scaler = joblib.load(os.path.join(MODEL_PATH, 'scaler.pkl'))
39
  label_encoder = joblib.load(os.path.join(MODEL_PATH, 'label_encoder.pkl'))
40
  model_xgb = joblib.load(os.path.join(MODEL_PATH, 'xgboost.pkl'))
41
  model_lgb = joblib.load(os.path.join(MODEL_PATH, 'lightgbm.pkl'))
42
-
43
- # Tải mô hình deep learning (CNN)
44
  model_cnn = tf.keras.models.load_model(os.path.join(MODEL_PATH, 'cnn.keras'))
45
-
46
- # Tải mô hình xử lý ngôn ngữ tự nhiên cho audio (Wav2Vec2)
47
  wav2vec_processor = Wav2Vec2Processor.from_pretrained("facebook/wav2vec2-base")
48
  wav2vec_model = Wav2Vec2Model.from_pretrained("facebook/wav2vec2-base")
49
 
@@ -51,27 +44,22 @@ try:
51
 
52
  except Exception as e:
53
  print(f"!!! LỖI NGHIÊM TRỌNG: Không thể tải một hoặc nhiều mô hình. Lỗi: {e}")
54
- print("!!! Vui lòng kiểm tra lại đường dẫn và sự tồn tại của các file trong thư mục 'models/'.")
55
- # Thoát ứng dụng nếu không tải được mô hình
56
  exit()
57
 
58
- # --- CÁC HÀM TRÍCH XUẤT ĐẶC TRƯNG ---
59
- # Các hàm này phải giống hệt với các hàm đã được sử dụng trong quá trình huấn luyện
60
- # để đảm bảo tính nhất quán của dữ liệu đầu vào cho mô hình.
61
 
62
  # Các hằng số cấu hình
63
  SAMPLE_RATE = 22050
64
  MAX_LENGTH_SECONDS = 5.0
65
  MAX_SAMPLES = int(SAMPLE_RATE * MAX_LENGTH_SECONDS)
66
  N_MELS = 128
67
- TRADITIONAL_FEATURE_SIZE = 570 # (128*4 cho melspec + 13*2 cho mfcc + ...) - Phải khớp với lúc train
68
  WAV2VEC_FEATURE_SIZE = 768
69
  SPECTROGRAM_SHAPE = (224, 224, 3)
70
 
71
  def _extract_traditional_features(y, sr):
72
- """Trích xuất các đặc trưng âm thanh truyền thống (MFCC, Mel Spectrogram, etc.)."""
73
  try:
74
- # Mel Spectrogram features (mean, std, max, min)
75
  mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=N_MELS)
76
  mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
77
  features = np.mean(mel_spec_db, axis=1)
@@ -79,34 +67,26 @@ def _extract_traditional_features(y, sr):
79
  features = np.append(features, np.max(mel_spec_db, axis=1))
80
  features = np.append(features, np.min(mel_spec_db, axis=1))
81
 
82
- # MFCC features (mean, std)
83
  mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
84
  features = np.append(features, np.mean(mfccs, axis=1))
85
  features = np.append(features, np.std(mfccs, axis=1))
86
 
87
- # Cần thêm các đặc trưng khác nếu có trong lúc train để đủ `TRADITIONAL_FEATURE_SIZE`
88
- # Ví dụ: chroma, spectral_contrast, etc.
89
- # Ở đây, chúng ta sẽ pad/truncate để đảm bảo kích thước
90
  if len(features) > TRADITIONAL_FEATURE_SIZE:
91
  features = features[:TRADITIONAL_FEATURE_SIZE]
92
  elif len(features) < TRADITIONAL_FEATURE_SIZE:
93
  features = np.pad(features, (0, TRADITIONAL_FEATURE_SIZE - len(features)), mode='constant')
94
 
95
  return features
96
-
97
  except Exception as e:
98
  print(f"Lỗi trích xuất đặc trưng truyền thống: {e}")
99
  return np.zeros(TRADITIONAL_FEATURE_SIZE)
100
 
101
  def _extract_wav2vec_features(y, sr):
102
- """Trích xuất đặc tr��ng từ mô hình Wav2Vec2."""
103
  try:
104
- # Wav2Vec2 yêu cầu sample rate 16000
105
  y_16k = librosa.resample(y, orig_sr=sr, target_sr=16000)
106
  inputs = wav2vec_processor(y_16k, sampling_rate=16000, return_tensors="pt", padding=True)
107
  with torch.no_grad():
108
  outputs = wav2vec_model(**inputs)
109
- # Lấy trung bình các hidden states cuối cùng để có một vector đại diện
110
  features = outputs.last_hidden_state.mean(dim=1).squeeze().cpu().numpy()
111
  return features
112
  except Exception as e:
@@ -114,44 +94,55 @@ def _extract_wav2vec_features(y, sr):
114
  return np.zeros(WAV2VEC_FEATURE_SIZE)
115
 
116
  def _create_spectrogram_image(y, sr):
117
- """Tạo ảnh spectrogram cho mô hình CNN."""
118
  try:
119
  mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=SPECTROGRAM_SHAPE[0])
120
  mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
121
- # Chuẩn hóa giá trị về khoảng [0, 255]
122
  mel_spec_norm = ((mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8) * 255).astype(np.uint8)
123
- # Chuyển thành ảnh 3 kênh (RGB)
124
  img = tf.keras.preprocessing.image.array_to_img(np.stack([mel_spec_norm]*3, axis=-1))
125
- # Resize về kích thước đầu vào của CNN
126
  img = img.resize((SPECTROGRAM_SHAPE[1], SPECTROGRAM_SHAPE[0]))
127
  return np.array(img)
128
  except Exception as e:
129
  print(f"Lỗi tạo ảnh spectrogram: {e}")
130
  return np.zeros(SPECTROGRAM_SHAPE)
131
 
 
132
  def process_audio_file(file_path):
133
- """Hàm tổng hợp: Tải file audio và gọi các hàm trích xuất đặc trưng."""
 
 
 
134
  try:
135
- y, sr = librosa.load(file_path, sr=SAMPLE_RATE)
136
-
137
- # Chuẩn hóa độ dài audio về MAX_SAMPLES
 
 
 
 
 
 
 
 
 
 
138
  if len(y) > MAX_SAMPLES:
139
  y = y[:MAX_SAMPLES]
140
  else:
141
  y = np.pad(y, (0, MAX_SAMPLES - len(y)), mode='constant')
142
 
143
- # Trích xuất đồng thời các bộ đặc trưng
144
- traditional_features = _extract_traditional_features(y, sr)
145
- wav2vec_features = _extract_wav2vec_features(y, sr)
146
- spectrogram = _create_spectrogram_image(y, sr)
147
 
148
  return traditional_features, wav2vec_features, spectrogram
 
149
  except Exception as e:
150
  print(f"Lỗi nghiêm trọng khi xử lý file audio {file_path}: {e}")
 
151
  return None, None, None
152
 
153
-
154
- # --- ĐỊNH NGHĨA CÁC ROUTE (API ENDPOINTS) CỦA ỨNG DỤNG ---
155
  @app.route('/', methods=['GET'])
156
  def home():
157
  """Render trang chủ của ứng dụng."""
@@ -168,7 +159,6 @@ def predict():
168
  return jsonify({'error': 'Tên file không hợp lệ.'}), 400
169
 
170
  try:
171
- # Lưu file audio vào thư mục tạm một cách an toàn
172
  filename = secure_filename(file.filename)
173
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
174
  file.save(filepath)
@@ -179,34 +169,26 @@ def predict():
179
  if trad_feats is None:
180
  return jsonify({'error': 'Không thể xử lý file audio.'}), 500
181
 
182
- # --- Chuẩn bị dữ liệu đầu vào cho từng mô hình ---
183
- # 1. Dữ liệu cho XGBoost và LightGBM (kết hợp và scale)
184
  combined_feats = np.concatenate([trad_feats, w2v_feats]).reshape(1, -1)
185
  scaled_feats = scaler.transform(combined_feats)
186
-
187
- # 2. Dữ liệu cho CNN (chuẩn hóa và thêm chiều batch)
188
  spec_img = spec_img / 255.0
189
  spec_img = np.expand_dims(spec_img, axis=0)
190
 
191
- # --- Lấy dự đoán từ tất cả các mô hình ---
192
  pred_xgb = model_xgb.predict_proba(scaled_feats)[0][1]
193
  pred_lgb = model_lgb.predict_proba(scaled_feats)[0][1]
194
  pred_cnn = model_cnn.predict(spec_img, verbose=0)[0][0]
195
 
196
- # --- Ensemble: Kết hợp kết quả bằng cách lấy trung bình xác suất ---
197
  final_prediction_prob = (pred_xgb + pred_lgb + pred_cnn) / 3
198
- # Quyết định nhãn cuối cùng dựa trên ngưỡng 0.5
199
  final_prediction_label_index = 1 if final_prediction_prob > 0.5 else 0
200
-
201
- # Chuyển đổi chỉ số nhãn (0 hoặc 1) thành chuỗi ('male'/'female')
202
  result_label_text = label_encoder.inverse_transform([final_prediction_label_index])[0]
203
 
204
- # Xóa file audio tạm sau khi xử lý xong
205
  os.remove(filepath)
206
 
207
  print(f"Phân tích hoàn tất. Kết quả: {result_label_text.upper()} (Xác suất: {final_prediction_prob:.2f})")
208
 
209
- # Trả về kết quả dưới dạng JSON
210
  return jsonify({
211
  'prediction': result_label_text.capitalize(),
212
  'probability': f"{final_prediction_prob:.2f}"
@@ -214,15 +196,11 @@ def predict():
214
 
215
  except Exception as e:
216
  print(f"Đã xảy ra lỗi trong quá trình dự đoán: {e}")
217
- import traceback
218
  traceback.print_exc()
219
  return jsonify({'error': 'Đã xảy ra lỗi không xác định trên máy chủ.'}), 500
220
 
221
  # --- ĐIỂM BẮT ĐẦU CHẠY ỨNG DỤNG ---
222
- # Đoạn mã này được cấu hình để hoạt động tốt trên cả máy local và Hugging Face Spaces.
223
  if __name__ == '__main__':
224
- # Hugging Face Spaces sẽ đặt biến môi trường PORT. Nếu không có, dùng 7860 làm mặc định.
225
  port = int(os.environ.get("PORT", 7860))
226
- # Chạy trên host '0.0.0.0' để ứng dụng có thể được truy cập từ bên ngoài container Docker.
227
  app.run(host='0.0.0.0', port=port)
228
 
 
1
  # app.py
2
+ # Phiên bản hoàn chỉnh, đã sửa lỗi đọc file audio và đồng bộ hóa phiên bản thư viện.
3
 
4
  import os
5
  import joblib
 
7
  import librosa
8
  from flask import Flask, request, jsonify, render_template
9
  from werkzeug.utils import secure_filename
10
+ import traceback
11
+
12
+ # --- Thư viện mới để đọc audio một cách mạnh mẽ ---
13
+ from pydub import AudioSegment
14
 
15
  # --- Cấu hình TensorFlow và các thư viện AI ---
 
 
16
  os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
17
  import tensorflow as tf
18
  from transformers import Wav2Vec2Processor, Wav2Vec2Model
 
21
  # --- KHỞI TẠO ỨNG DỤNG FLASK ---
22
  app = Flask(__name__)
23
 
24
+ # Cấu hình thư mục tạm để lưu file audio
25
  UPLOAD_FOLDER = 'uploads/'
26
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
 
27
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
28
 
29
 
30
  # --- TẢI TẤT CẢ CÁC MÔ HÌNH KHI SERVER KHỞI ĐỘNG ---
 
 
31
  print(">>> Đang tải các mô hình AI, quá trình này có thể mất một lúc...")
32
 
33
  try:
34
  MODEL_PATH = 'models/'
 
 
35
  scaler = joblib.load(os.path.join(MODEL_PATH, 'scaler.pkl'))
36
  label_encoder = joblib.load(os.path.join(MODEL_PATH, 'label_encoder.pkl'))
37
  model_xgb = joblib.load(os.path.join(MODEL_PATH, 'xgboost.pkl'))
38
  model_lgb = joblib.load(os.path.join(MODEL_PATH, 'lightgbm.pkl'))
 
 
39
  model_cnn = tf.keras.models.load_model(os.path.join(MODEL_PATH, 'cnn.keras'))
 
 
40
  wav2vec_processor = Wav2Vec2Processor.from_pretrained("facebook/wav2vec2-base")
41
  wav2vec_model = Wav2Vec2Model.from_pretrained("facebook/wav2vec2-base")
42
 
 
44
 
45
  except Exception as e:
46
  print(f"!!! LỖI NGHIÊM TRỌNG: Không thể tải một hoặc nhiều mô hình. Lỗi: {e}")
47
+ traceback.print_exc()
 
48
  exit()
49
 
50
+ # --- CÁC HÀM TRÍCH XUẤT ĐẶC TRƯNG (KHÔNG ĐỔI) ---
 
 
51
 
52
  # Các hằng số cấu hình
53
  SAMPLE_RATE = 22050
54
  MAX_LENGTH_SECONDS = 5.0
55
  MAX_SAMPLES = int(SAMPLE_RATE * MAX_LENGTH_SECONDS)
56
  N_MELS = 128
57
+ TRADITIONAL_FEATURE_SIZE = 570
58
  WAV2VEC_FEATURE_SIZE = 768
59
  SPECTROGRAM_SHAPE = (224, 224, 3)
60
 
61
  def _extract_traditional_features(y, sr):
 
62
  try:
 
63
  mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=N_MELS)
64
  mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
65
  features = np.mean(mel_spec_db, axis=1)
 
67
  features = np.append(features, np.max(mel_spec_db, axis=1))
68
  features = np.append(features, np.min(mel_spec_db, axis=1))
69
 
 
70
  mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
71
  features = np.append(features, np.mean(mfccs, axis=1))
72
  features = np.append(features, np.std(mfccs, axis=1))
73
 
 
 
 
74
  if len(features) > TRADITIONAL_FEATURE_SIZE:
75
  features = features[:TRADITIONAL_FEATURE_SIZE]
76
  elif len(features) < TRADITIONAL_FEATURE_SIZE:
77
  features = np.pad(features, (0, TRADITIONAL_FEATURE_SIZE - len(features)), mode='constant')
78
 
79
  return features
 
80
  except Exception as e:
81
  print(f"Lỗi trích xuất đặc trưng truyền thống: {e}")
82
  return np.zeros(TRADITIONAL_FEATURE_SIZE)
83
 
84
  def _extract_wav2vec_features(y, sr):
 
85
  try:
 
86
  y_16k = librosa.resample(y, orig_sr=sr, target_sr=16000)
87
  inputs = wav2vec_processor(y_16k, sampling_rate=16000, return_tensors="pt", padding=True)
88
  with torch.no_grad():
89
  outputs = wav2vec_model(**inputs)
 
90
  features = outputs.last_hidden_state.mean(dim=1).squeeze().cpu().numpy()
91
  return features
92
  except Exception as e:
 
94
  return np.zeros(WAV2VEC_FEATURE_SIZE)
95
 
96
  def _create_spectrogram_image(y, sr):
 
97
  try:
98
  mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=SPECTROGRAM_SHAPE[0])
99
  mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
 
100
  mel_spec_norm = ((mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8) * 255).astype(np.uint8)
 
101
  img = tf.keras.preprocessing.image.array_to_img(np.stack([mel_spec_norm]*3, axis=-1))
 
102
  img = img.resize((SPECTROGRAM_SHAPE[1], SPECTROGRAM_SHAPE[0]))
103
  return np.array(img)
104
  except Exception as e:
105
  print(f"Lỗi tạo ảnh spectrogram: {e}")
106
  return np.zeros(SPECTROGRAM_SHAPE)
107
 
108
+ # --- HÀM XỬ LÝ AUDIO ĐÃ ĐƯỢC CẬP NHẬT ---
109
  def process_audio_file(file_path):
110
+ """
111
+ Hàm tổng hợp phiên bản mới: Dùng pydub để đọc file audio một cách mạnh mẽ,
112
+ sau đó chuyển đổi sang định dạng mà librosa có thể xử lý an toàn.
113
+ """
114
  try:
115
+ # 1. Dùng pydub để mở file audio (hỗ trợ nhiều định dạng)
116
+ audio = AudioSegment.from_file(file_path)
117
+
118
+ # 2. Đảm bảo audio là mono (1 kênh) và có sample rate đúng
119
+ audio = audio.set_channels(1)
120
+ audio = audio.set_frame_rate(SAMPLE_RATE)
121
+
122
+ # 3. Chuyển đổi audio của pydub thành mảng NumPy cho librosa
123
+ # Chuẩn hóa về khoảng [-1, 1]
124
+ samples = np.array(audio.get_array_of_samples()).astype(np.float32)
125
+ y = samples / (2**(audio.sample_width * 8 - 1))
126
+
127
+ # 4. Chuẩn hóa độ dài audio về MAX_SAMPLES
128
  if len(y) > MAX_SAMPLES:
129
  y = y[:MAX_SAMPLES]
130
  else:
131
  y = np.pad(y, (0, MAX_SAMPLES - len(y)), mode='constant')
132
 
133
+ # 5. Trích xuất đồng thời các bộ đặc trưng (code này không đổi)
134
+ traditional_features = _extract_traditional_features(y, SAMPLE_RATE)
135
+ wav2vec_features = _extract_wav2vec_features(y, SAMPLE_RATE)
136
+ spectrogram = _create_spectrogram_image(y, SAMPLE_RATE)
137
 
138
  return traditional_features, wav2vec_features, spectrogram
139
+
140
  except Exception as e:
141
  print(f"Lỗi nghiêm trọng khi xử lý file audio {file_path}: {e}")
142
+ traceback.print_exc()
143
  return None, None, None
144
 
145
+ # --- ĐỊNH NGHĨA CÁC ROUTE CỦA ỨNG DỤNG ---
 
146
  @app.route('/', methods=['GET'])
147
  def home():
148
  """Render trang chủ của ứng dụng."""
 
159
  return jsonify({'error': 'Tên file không hợp lệ.'}), 400
160
 
161
  try:
 
162
  filename = secure_filename(file.filename)
163
  filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
164
  file.save(filepath)
 
169
  if trad_feats is None:
170
  return jsonify({'error': 'Không thể xử lý file audio.'}), 500
171
 
172
+ # Chuẩn bị dữ liệu đầu vào cho các mô hình
 
173
  combined_feats = np.concatenate([trad_feats, w2v_feats]).reshape(1, -1)
174
  scaled_feats = scaler.transform(combined_feats)
 
 
175
  spec_img = spec_img / 255.0
176
  spec_img = np.expand_dims(spec_img, axis=0)
177
 
178
+ # Lấy dự đoán từ tất cả các mô hình
179
  pred_xgb = model_xgb.predict_proba(scaled_feats)[0][1]
180
  pred_lgb = model_lgb.predict_proba(scaled_feats)[0][1]
181
  pred_cnn = model_cnn.predict(spec_img, verbose=0)[0][0]
182
 
183
+ # Ensemble: Kết hợp kết quả bằng cách lấy trung bình xác suất
184
  final_prediction_prob = (pred_xgb + pred_lgb + pred_cnn) / 3
 
185
  final_prediction_label_index = 1 if final_prediction_prob > 0.5 else 0
 
 
186
  result_label_text = label_encoder.inverse_transform([final_prediction_label_index])[0]
187
 
 
188
  os.remove(filepath)
189
 
190
  print(f"Phân tích hoàn tất. Kết quả: {result_label_text.upper()} (Xác suất: {final_prediction_prob:.2f})")
191
 
 
192
  return jsonify({
193
  'prediction': result_label_text.capitalize(),
194
  'probability': f"{final_prediction_prob:.2f}"
 
196
 
197
  except Exception as e:
198
  print(f"Đã xảy ra lỗi trong quá trình dự đoán: {e}")
 
199
  traceback.print_exc()
200
  return jsonify({'error': 'Đã xảy ra lỗi không xác định trên máy chủ.'}), 500
201
 
202
  # --- ĐIỂM BẮT ĐẦU CHẠY ỨNG DỤNG ---
 
203
  if __name__ == '__main__':
 
204
  port = int(os.environ.get("PORT", 7860))
 
205
  app.run(host='0.0.0.0', port=port)
206
 
packages.txt CHANGED
@@ -1 +1,2 @@
1
  libsndfile1
 
 
1
  libsndfile1
2
+ ffmpeg