SicheateR99 commited on
Commit
6bd1ca8
·
verified ·
1 Parent(s): 5531419

Init commit

Browse files
Files changed (4) hide show
  1. .gitattributes +1 -0
  2. app.py +114 -0
  3. receipt_ner_model.keras +3 -0
  4. requirements.txt +5 -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
+ receipt_ner_model.keras filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import pickle
4
+ import numpy as np
5
+ import tensorflow as tf
6
+ from fastapi import FastAPI
7
+ from pydantic import BaseModel
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+
10
+ # Optimasi Memory (Wajib untuk HF Free Tier)
11
+ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
12
+ os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
13
+ os.environ["TF_NUM_INTEROP_THREADS"] = "1"
14
+ os.environ["TF_NUM_INTRAOP_THREADS"] = "1"
15
+
16
+ app = FastAPI(title="NER API Service")
17
+
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+
26
+ # Global variables untuk Lazy Loading
27
+ ner_model = None
28
+ vocab = None
29
+
30
+ # Konfigurasi NER
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
+ # Schema untuk menerima data JSON dari Space A
38
+ class TextRequest(BaseModel):
39
+ text: str
40
+
41
+ def get_ner_model():
42
+ global ner_model, vocab
43
+ if ner_model is None:
44
+ with open("data/processed/vocab.pkl", "rb") as f:
45
+ vocab = pickle.load(f)
46
+ # compile=False penting agar tidak error saat mencari custom loss function
47
+ ner_model = tf.keras.models.load_model("receipt_ner_model.keras", compile=False)
48
+ return ner_model, vocab
49
+
50
+ def normalize_token(token: str):
51
+ token = token.strip()
52
+ if re.fullmatch(r"\d{1,3}([.,]\d{3})*", token):
53
+ return PRICE_TOKEN
54
+ if re.fullmatch(r"\d+", token):
55
+ return NUM_TOKEN
56
+ token = re.sub(r"^[^a-zA-Z0-9']+|[^a-zA-Z0-9']+$", "", token)
57
+ return token.upper() if token else UNK_TOKEN
58
+
59
+ @app.post("/predict-ner/")
60
+ def predict_ner(request: TextRequest):
61
+ try:
62
+ raw_text = request.text
63
+ entities = {"MERCHANT": [], "ITEM": [], "TOTAL": []}
64
+
65
+ # Jika teks kosong, langsung kembalikan entitas kosong
66
+ if not raw_text.strip():
67
+ return {"success": True, "entities": entities}
68
+
69
+ model, vcb = get_ner_model()
70
+
71
+ # Tokenisasi sederhana
72
+ tokens = [t for line in raw_text.split("\n") for t in line.strip().split() if t]
73
+ if not tokens:
74
+ return {"success": True, "entities": entities}
75
+
76
+ # Encoding
77
+ norm_tokens = [normalize_token(t) for t in tokens]
78
+ X = np.zeros((1, MAX_SEQ_LEN), dtype=np.int32)
79
+ mask = np.zeros((1, MAX_SEQ_LEN), dtype=np.float32)
80
+
81
+ length = min(len(norm_tokens), MAX_SEQ_LEN)
82
+ for i in range(length):
83
+ X[0, i] = vcb.get(norm_tokens[i], vcb.get(UNK_TOKEN, 0))
84
+ mask[0, i] = 1.0
85
+
86
+ # Prediksi menggunakan TensorFlow
87
+ probs = model([X, mask], training=False).numpy()
88
+ labels = [IDX2LABEL[int(np.argmax(probs[0, i]))] for i in range(length)]
89
+
90
+ # Ekstraksi IOB (Inside-Outside-Beginning)
91
+ current_type = None
92
+ span = []
93
+ for tok, lbl in zip(tokens[:length], labels):
94
+ if lbl.startswith("B-"):
95
+ if current_type:
96
+ entities[current_type].append(" ".join(span))
97
+ current_type = lbl[2:]
98
+ span = [tok]
99
+ elif lbl.startswith("I-") and current_type == lbl[2:]:
100
+ span.append(tok)
101
+ else:
102
+ if current_type:
103
+ entities[current_type].append(" ".join(span))
104
+ current_type = None
105
+ span = []
106
+ if current_type:
107
+ entities[current_type].append(" ".join(span))
108
+
109
+ return {"success": True, "entities": entities}
110
+
111
+ except Exception as e:
112
+ print(f"Error NER: {e}")
113
+ # Jika terjadi error internal, kembalikan objek kosong agar Space A tidak crash
114
+ return {"success": False, "error": str(e), "entities": {"MERCHANT": [], "ITEM": [], "TOTAL": []}}
receipt_ner_model.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b91e1ef3320864814b5dfda22768766247b98f4246bc64a63c870ab0ed86e737
3
+ size 10616552
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ tensorflow>=2.16.1
4
+ pydantic
5
+ numpy