SicheateR99 commited on
Commit
f730dfe
·
verified ·
1 Parent(s): 056af62

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +27 -161
app.py CHANGED
@@ -1,20 +1,15 @@
1
  import os
2
- import re
3
- import pickle
4
- import zipfile
5
  import numpy as np
6
  import tensorflow as tf
7
  from fastapi import FastAPI
8
  from pydantic import BaseModel
9
  from fastapi.middleware.cors import CORSMiddleware
10
 
11
- # Optimasi RAM (Wajib untuk Hugging Face Free Tier)
12
  os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
13
  os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
14
- os.environ["TF_NUM_INTEROP_THREADS"] = "1"
15
- os.environ["TF_NUM_INTRAOP_THREADS"] = "1"
16
 
17
- app = FastAPI(title="NER & Classification API Service")
18
  app.add_middleware(
19
  CORSMiddleware,
20
  allow_origins=["*"],
@@ -23,174 +18,45 @@ app.add_middleware(
23
  allow_headers=["*"],
24
  )
25
 
26
- # Global Variables
27
- ner_model = None
28
- vocab = None
29
  clf_model = None
30
 
31
- MAX_SEQ_LEN = 64
32
- IDX2LABEL = {0: "<PAD>", 1: "O", 2: "B-MERCHANT", 3: "I-MERCHANT", 4: "B-ITEM", 5: "I-ITEM", 6: "B-TOTAL", 7: "I-TOTAL"}
33
- UNK_TOKEN = "<UNK>"
34
- NUM_TOKEN = "<NUM>"
35
- PRICE_TOKEN = "<PRICE>"
36
-
37
- class TextRequest(BaseModel):
38
- text: str
39
-
40
- # =========================================================
41
- # JURUS 1: BYPASS NER (Menghindari bug quantization_config)
42
- # =========================================================
43
- def build_manual_ner_model(vocab_size=7929, max_len=64, num_classes=8):
44
- inputs = tf.keras.Input(shape=(max_len,), dtype='int32', name='tokens')
45
- x = tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=64, mask_zero=True, name='embedding')(inputs)
46
- x = tf.keras.layers.SpatialDropout1D(0.3, name='spatial_dropout1d')(x)
47
-
48
- lstm1_f = tf.keras.layers.LSTM(128, return_sequences=True, dropout=0.4, name='forward_lstm')
49
- lstm1_b = tf.keras.layers.LSTM(128, return_sequences=True, go_backwards=True, dropout=0.4, name='backward_lstm')
50
- x = tf.keras.layers.Bidirectional(lstm1_f, backward_layer=lstm1_b, name='bilstm_1')(x)
51
-
52
- lstm2_f = tf.keras.layers.LSTM(64, return_sequences=True, dropout=0.4, name='forward_lstm_1')
53
- lstm2_b = tf.keras.layers.LSTM(64, return_sequences=True, go_backwards=True, dropout=0.4, name='backward_lstm_1')
54
- x = tf.keras.layers.Bidirectional(lstm2_f, backward_layer=lstm2_b, name='bilstm_2')(x)
55
-
56
- dense1 = tf.keras.layers.Dense(64, activation='relu', name='dense')
57
- x = tf.keras.layers.TimeDistributed(dense1, name='proj')(x)
58
- x = tf.keras.layers.Dropout(0.4, name='dropout')(x)
59
-
60
- dense2 = tf.keras.layers.Dense(num_classes, activation='linear', name='dense_1')
61
- x = tf.keras.layers.TimeDistributed(dense2, name='logits')(x)
62
- probs = tf.keras.layers.Activation('softmax', name='probabilities')(x)
63
-
64
- return tf.keras.Model(inputs=inputs, outputs=probs, name='BiLSTM_NER')
65
-
66
- def get_ner_model():
67
- global ner_model, vocab
68
- if ner_model is None:
69
- with open("vocab.pkl", "rb") as f:
70
- vocab = pickle.load(f)
71
- ner_model = build_manual_ner_model()
72
- ner_model.load_weights("receipt_ner_model.keras")
73
- return ner_model, vocab
74
 
75
  # =========================================================
76
- # JURUS 2: BYPASS KLASIFIKASI + PAKSA BANGUN KAMUS
77
  # =========================================================
78
- def build_manual_clf_model():
79
- inputs = tf.keras.Input(shape=(1,), dtype=tf.string, name='input_layer')
80
- x = tf.keras.layers.TextVectorization(
81
- max_tokens=5000,
82
- output_sequence_length=50,
83
- name='text_vectorization'
84
- )(inputs)
85
- x = tf.keras.layers.Embedding(input_dim=5000, output_dim=64, name='embedding')(x)
86
- x = tf.keras.layers.GlobalAveragePooling1D(name='global_average_pooling1d')(x)
87
- x = tf.keras.layers.Dense(64, activation='relu', name='dense')(x)
88
- x = tf.keras.layers.Dropout(0.2, name='dropout')(x)
89
- outputs = tf.keras.layers.Dense(7, activation='softmax', name='dense_1')(x)
90
-
91
- return tf.keras.Model(inputs=inputs, outputs=outputs, name='functional')
92
-
93
- import zipfile # Pastikan import ini ada di bagian atas file
94
-
95
  def get_clf_model():
96
  global clf_model
97
  if clf_model is None:
98
- clf_model = build_manual_clf_model()
99
- clf_model.load_weights("model_strukly.keras")
100
-
101
- # --- JURUS HACKER: EKSTRAK KAMUS LANGSUNG DARI FILE .KERAS (ZIP) ---
102
- tv_layer = clf_model.get_layer('text_vectorization')
103
- try:
104
- # Buka model .keras sebagai file ZIP
105
- with zipfile.ZipFile("model_strukly.keras", 'r') as z:
106
- for name in z.namelist():
107
- # Cari file teks di dalam folder assets
108
- if 'assets/' in name and name.endswith('.txt'):
109
- with z.open(name) as f:
110
- # Baca isi file, pisahkan per baris, dan jadikan kamus
111
- vocab_list = f.read().decode('utf-8').splitlines()
112
- tv_layer.set_vocabulary(vocab_list)
113
- print(f"Kamus berhasil disuntikkan secara paksa dari: {name}")
114
- break
115
- except Exception as e:
116
- print(f"Gagal mengekstrak kamus dari dalam ZIP: {e}")
117
- # -------------------------------------------------------------------
118
-
119
  return clf_model
120
 
121
- # =========================================================
122
- # FUNGSI UTAMA (Inference API)
123
- # =========================================================
124
- def normalize_token(token: str):
125
- token = token.strip()
126
- if re.fullmatch(r"\d{1,3}([.,]\d{3})*", token): return PRICE_TOKEN
127
- if re.fullmatch(r"\d+", token): return NUM_TOKEN
128
- token = re.sub(r"^[^a-zA-Z0-9']+|[^a-zA-Z0-9']+$", "", token)
129
- return token.upper() if token else UNK_TOKEN
130
-
131
- @app.post("/predict-ner/")
132
- def predict_ner(request: TextRequest):
133
  try:
134
- raw_text = request.text
135
- entities = {"MERCHANT": [], "ITEM": [], "TOTAL": []}
136
- class_id = None
137
 
138
- if not raw_text.strip():
139
- return {"success": True, "entities": entities, "class_id": class_id}
140
-
141
- # --- 1. PROSES NER ---
142
- model, vcb = get_ner_model()
143
- tokens = [t for line in raw_text.split("\n") for t in line.strip().split() if t]
144
 
145
- if tokens:
146
- norm_tokens = [normalize_token(t) for t in tokens]
147
- X = np.zeros((1, MAX_SEQ_LEN), dtype=np.int32)
148
- length = min(len(norm_tokens), MAX_SEQ_LEN)
149
-
150
- for i in range(length):
151
- X[0, i] = vcb.get(norm_tokens[i], vcb.get(UNK_TOKEN, 0))
152
-
153
- probs = model(X, training=False).numpy()
154
- labels = [IDX2LABEL[int(np.argmax(probs[0, i]))] for i in range(length)]
155
-
156
- current_type = None
157
- span = []
158
- for tok, lbl in zip(tokens[:length], labels):
159
- if lbl.startswith("B-"):
160
- if current_type: entities[current_type].append(" ".join(span))
161
- current_type = lbl[2:]
162
- span = [tok]
163
- elif lbl.startswith("I-") and current_type == lbl[2:]:
164
- span.append(tok)
165
- else:
166
- if current_type: entities[current_type].append(" ".join(span))
167
- current_type = None
168
- span = []
169
- if current_type: entities[current_type].append(" ".join(span))
170
-
171
- # --- 2. PROSES KLASIFIKASI ---
172
- items_list = entities.get("ITEM", [])
173
- teks_untuk_klasifikasi = " ".join(items_list)
174
 
175
- if teks_untuk_klasifikasi:
176
- try:
177
- c_model = get_clf_model()
178
- input_data = np.array([teks_untuk_klasifikasi])
179
-
180
- hasil_prediksi = c_model(input_data, training=False).numpy()
181
-
182
- # Langsung mengembalikan index klasifikasi tertinggi (0 - 6)
183
- class_id = int(np.argmax(hasil_prediksi[0]))
184
- except Exception as e:
185
- print(f"Error Klasifikasi internal: {e}")
186
-
187
- return {
188
- "success": True,
189
- "entities": entities,
190
- "class_id": class_id
191
- }
192
 
193
  except Exception as e:
194
  import traceback
195
- print(f"Error NER/CLF detail: {traceback.format_exc()}")
196
- return {"success": False, "error": str(e), "entities": {"MERCHANT": [], "ITEM": [], "TOTAL": []}, "class_id": None}
 
1
  import os
 
 
 
2
  import numpy as np
3
  import tensorflow as tf
4
  from fastapi import FastAPI
5
  from pydantic import BaseModel
6
  from fastapi.middleware.cors import CORSMiddleware
7
 
8
+ # Optimasi RAM server HF
9
  os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
10
  os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
 
 
11
 
12
+ app = FastAPI(title="AI Classification Service")
13
  app.add_middleware(
14
  CORSMiddleware,
15
  allow_origins=["*"],
 
18
  allow_headers=["*"],
19
  )
20
 
 
 
 
21
  clf_model = None
22
 
23
+ # Schema request dari Space A
24
+ class ItemRequest(BaseModel):
25
+ items_text: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  # =========================================================
28
+ # LOAD MODEL SECARA NORMAL
29
  # =========================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def get_clf_model():
31
  global clf_model
32
  if clf_model is None:
33
+ print("Memuat model klasifikasi secara normal...")
34
+ # Load langsung file .keras bawaan Anda
35
+ clf_model = tf.keras.models.load_model("model_strukly.keras", compile=False)
36
+ print("Model berhasil dimuat!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  return clf_model
38
 
39
+ @app.post("/classify/")
40
+ def classify_items(request: ItemRequest):
 
 
 
 
 
 
 
 
 
 
41
  try:
42
+ raw_items = request.items_text
 
 
43
 
44
+ # Jika Space A mengirim string kosong (tidak ada item terdeteksi)
45
+ if not raw_items.strip():
46
+ return {"success": True, "class_id": None}
47
+
48
+ model = get_clf_model()
49
+ input_data = np.array([raw_items])
50
 
51
+ # Prediksi dengan model AI
52
+ hasil_prediksi = model(input_data, training=False).numpy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ # Ambil indeks dengan probabilitas tertinggi
55
+ class_id = int(np.argmax(hasil_prediksi[0]))
56
+
57
+ return {"success": True, "class_id": class_id}
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
  except Exception as e:
60
  import traceback
61
+ print(f"Error AI Klasifikasi: {traceback.format_exc()}")
62
+ return {"success": False, "error": str(e), "class_id": None}