diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..85e0edef3395994809da4048f30122be90a10fd5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/fine_tuned_bert_ner
+/logs
+/results
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142232 b/.history/.gitignore_20251026142232
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.history/.gitignore_20251026142256 b/.history/.gitignore_20251026142256
new file mode 100644
index 0000000000000000000000000000000000000000..24ebf82dc1214f1c17a08e1774d93171ecc40170
--- /dev/null
+++ b/.history/.gitignore_20251026142256
@@ -0,0 +1 @@
+/fine_tuned_bert_ner
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142259 b/.history/.gitignore_20251026142259
new file mode 100644
index 0000000000000000000000000000000000000000..24ebf82dc1214f1c17a08e1774d93171ecc40170
--- /dev/null
+++ b/.history/.gitignore_20251026142259
@@ -0,0 +1 @@
+/fine_tuned_bert_ner
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142438 b/.history/.gitignore_20251026142438
new file mode 100644
index 0000000000000000000000000000000000000000..e601899d998f6665dec6b7de759af506565e7037
--- /dev/null
+++ b/.history/.gitignore_20251026142438
@@ -0,0 +1 @@
+/fine_tuned_bert_ner
diff --git a/.history/.gitignore_20251026142440 b/.history/.gitignore_20251026142440
new file mode 100644
index 0000000000000000000000000000000000000000..6de79994e18aba9ebb536f4e02642a3140b426ef
--- /dev/null
+++ b/.history/.gitignore_20251026142440
@@ -0,0 +1,2 @@
+/fine_tuned_bert_ner
+/l
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142441 b/.history/.gitignore_20251026142441
new file mode 100644
index 0000000000000000000000000000000000000000..9e2a7a4daa31760e2b698e29db0b9e793c1205ed
--- /dev/null
+++ b/.history/.gitignore_20251026142441
@@ -0,0 +1,2 @@
+/fine_tuned_bert_ner
+/
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142443 b/.history/.gitignore_20251026142443
new file mode 100644
index 0000000000000000000000000000000000000000..c11e66308b8bd41b0a3146fe40278b3684f41547
--- /dev/null
+++ b/.history/.gitignore_20251026142443
@@ -0,0 +1,2 @@
+/fine_tuned_bert_ner
+/logs
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142445 b/.history/.gitignore_20251026142445
new file mode 100644
index 0000000000000000000000000000000000000000..43db1ce896d1b3cd99a9ca66e948d82199b68c36
--- /dev/null
+++ b/.history/.gitignore_20251026142445
@@ -0,0 +1,2 @@
+/fine_tuned_bert_ner
+/logs
diff --git a/.history/.gitignore_20251026142503 b/.history/.gitignore_20251026142503
new file mode 100644
index 0000000000000000000000000000000000000000..f3c9e676674f0b546df194f98a2342c2f66e6bee
--- /dev/null
+++ b/.history/.gitignore_20251026142503
@@ -0,0 +1,3 @@
+/fine_tuned_bert_ner
+/logs
+/result
\ No newline at end of file
diff --git a/.history/.gitignore_20251026142504 b/.history/.gitignore_20251026142504
new file mode 100644
index 0000000000000000000000000000000000000000..85e0edef3395994809da4048f30122be90a10fd5
--- /dev/null
+++ b/.history/.gitignore_20251026142504
@@ -0,0 +1,3 @@
+/fine_tuned_bert_ner
+/logs
+/results
\ No newline at end of file
diff --git a/.history/README_20251026141542.md b/.history/README_20251026141542.md
new file mode 100644
index 0000000000000000000000000000000000000000..ef4c5b274d93158784cfd95620e6c14965f88f32
Binary files /dev/null and b/.history/README_20251026141542.md differ
diff --git a/.history/README_20251026143510.md b/.history/README_20251026143510.md
new file mode 100644
index 0000000000000000000000000000000000000000..5489c8e4cbfb979c4825de75437fa0e6bef2da8e
Binary files /dev/null and b/.history/README_20251026143510.md differ
diff --git a/.history/README_20251026143515.md b/.history/README_20251026143515.md
new file mode 100644
index 0000000000000000000000000000000000000000..c7d1437b08ddb59df01dd62ce171b3c76eb099d0
Binary files /dev/null and b/.history/README_20251026143515.md differ
diff --git a/.history/README_20251026143518.md b/.history/README_20251026143518.md
new file mode 100644
index 0000000000000000000000000000000000000000..99a446ce7d56b744e80f325e172a5ee3fa367762
Binary files /dev/null and b/.history/README_20251026143518.md differ
diff --git a/.history/README_20251026143525.md b/.history/README_20251026143525.md
new file mode 100644
index 0000000000000000000000000000000000000000..5d0375bf37cb5cb419a8ebf56e75fd4fd36129d0
Binary files /dev/null and b/.history/README_20251026143525.md differ
diff --git a/.history/README_20251026143535.md b/.history/README_20251026143535.md
new file mode 100644
index 0000000000000000000000000000000000000000..8d5f8e8899a9389a92e337b09d27919e22cbfc02
Binary files /dev/null and b/.history/README_20251026143535.md differ
diff --git a/.history/README_20251026143548.md b/.history/README_20251026143548.md
new file mode 100644
index 0000000000000000000000000000000000000000..1a3efe4c3461aa5955ac48552b88dde8ff236a9e
Binary files /dev/null and b/.history/README_20251026143548.md differ
diff --git a/.history/README_20251026143602.md b/.history/README_20251026143602.md
new file mode 100644
index 0000000000000000000000000000000000000000..1a3efe4c3461aa5955ac48552b88dde8ff236a9e
Binary files /dev/null and b/.history/README_20251026143602.md differ
diff --git a/.history/README_20251026143605.md b/.history/README_20251026143605.md
new file mode 100644
index 0000000000000000000000000000000000000000..151ff74d55aa42a52e9457f00ef49cfaca49b2a8
Binary files /dev/null and b/.history/README_20251026143605.md differ
diff --git a/.history/README_20251026143615.md b/.history/README_20251026143615.md
new file mode 100644
index 0000000000000000000000000000000000000000..29eefd523957014ca144bb463e9511d62c65e4c3
Binary files /dev/null and b/.history/README_20251026143615.md differ
diff --git a/.history/README_20251026143617.md b/.history/README_20251026143617.md
new file mode 100644
index 0000000000000000000000000000000000000000..b50e7e32a7fbf7fcc76e9ee92385545309ffbbc8
Binary files /dev/null and b/.history/README_20251026143617.md differ
diff --git a/.history/README_20251026143620.md b/.history/README_20251026143620.md
new file mode 100644
index 0000000000000000000000000000000000000000..1dc978ecac098204913cae43caa9114e3cb9ab72
Binary files /dev/null and b/.history/README_20251026143620.md differ
diff --git a/.history/README_20251026143621.md b/.history/README_20251026143621.md
new file mode 100644
index 0000000000000000000000000000000000000000..b50e7e32a7fbf7fcc76e9ee92385545309ffbbc8
Binary files /dev/null and b/.history/README_20251026143621.md differ
diff --git a/.history/README_20251026143623.md b/.history/README_20251026143623.md
new file mode 100644
index 0000000000000000000000000000000000000000..a87ab11ebf3f7e7d10002610e4813859d846e9cb
Binary files /dev/null and b/.history/README_20251026143623.md differ
diff --git a/.history/README_20251026143627.md b/.history/README_20251026143627.md
new file mode 100644
index 0000000000000000000000000000000000000000..7de7dbb7024a9cd3ffcfec21ce8c6ba022c9fcc2
Binary files /dev/null and b/.history/README_20251026143627.md differ
diff --git a/.history/README_20251026143629.md b/.history/README_20251026143629.md
new file mode 100644
index 0000000000000000000000000000000000000000..79c407a89d06bd55a8d305b499a2be44afea543b
Binary files /dev/null and b/.history/README_20251026143629.md differ
diff --git a/.history/README_20251026143631.md b/.history/README_20251026143631.md
new file mode 100644
index 0000000000000000000000000000000000000000..74a6da920a947d1cb4bcce0b62d420240729988f
Binary files /dev/null and b/.history/README_20251026143631.md differ
diff --git a/.history/README_20251026143636.md b/.history/README_20251026143636.md
new file mode 100644
index 0000000000000000000000000000000000000000..664d778b6389b93ce72bb9e375ce84a3e8742471
Binary files /dev/null and b/.history/README_20251026143636.md differ
diff --git a/.history/README_20251026143639.md b/.history/README_20251026143639.md
new file mode 100644
index 0000000000000000000000000000000000000000..ce959a9eb9d334848e888033528538d49d30520e
Binary files /dev/null and b/.history/README_20251026143639.md differ
diff --git a/.history/README_20251026143642.md b/.history/README_20251026143642.md
new file mode 100644
index 0000000000000000000000000000000000000000..929b91cf79d898788788a5623a3872862d2ea319
Binary files /dev/null and b/.history/README_20251026143642.md differ
diff --git a/.history/README_20251026143646.md b/.history/README_20251026143646.md
new file mode 100644
index 0000000000000000000000000000000000000000..d6b1ed6eeca1d5802cbd1c634cc79ac62deff3bc
Binary files /dev/null and b/.history/README_20251026143646.md differ
diff --git a/.history/README_20251026143650.md b/.history/README_20251026143650.md
new file mode 100644
index 0000000000000000000000000000000000000000..9181afa9c502aedfb9ad5b59b2a7d1a4233210a4
Binary files /dev/null and b/.history/README_20251026143650.md differ
diff --git a/.history/README_20251026143651.md b/.history/README_20251026143651.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe9995a91cddcb12bb772ec29adc01d48c5f8fc2
Binary files /dev/null and b/.history/README_20251026143651.md differ
diff --git a/.history/README_20251026143703.md b/.history/README_20251026143703.md
new file mode 100644
index 0000000000000000000000000000000000000000..b164eab53b9f6f8fcb78fd155694f9aa8439ff54
Binary files /dev/null and b/.history/README_20251026143703.md differ
diff --git a/.history/README_20251026143705.md b/.history/README_20251026143705.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe9995a91cddcb12bb772ec29adc01d48c5f8fc2
Binary files /dev/null and b/.history/README_20251026143705.md differ
diff --git a/.history/README_20251026143708.md b/.history/README_20251026143708.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe9995a91cddcb12bb772ec29adc01d48c5f8fc2
Binary files /dev/null and b/.history/README_20251026143708.md differ
diff --git a/.history/README_20251026143817.md b/.history/README_20251026143817.md
new file mode 100644
index 0000000000000000000000000000000000000000..7aca7c8bf497f430224cc7625993886daa62fd0d
Binary files /dev/null and b/.history/README_20251026143817.md differ
diff --git a/.history/README_20251026143819.md b/.history/README_20251026143819.md
new file mode 100644
index 0000000000000000000000000000000000000000..fe9995a91cddcb12bb772ec29adc01d48c5f8fc2
Binary files /dev/null and b/.history/README_20251026143819.md differ
diff --git a/.history/app_20251026125228.py b/.history/app_20251026125228.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.history/app_20251026134547.py b/.history/app_20251026134547.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2cd25dcef3b97873ac68a3023342b209f5000b8
--- /dev/null
+++ b/.history/app_20251026134547.py
@@ -0,0 +1,107 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+
+# Tentukan path ke model yang sudah disimpan
+MODEL_DIR = "./ner_bert_model"
+
+# --- Fungsi untuk Memuat Model dan Tokenizer ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # Muat tag_values
+ with open(os.path.join(model_dir, 'tag_values.json'), 'r') as f:
+ tag_values = json.load(f)
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi
+
+ return model, tokenizer, tag_values, device
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ return None, None, None, None
+
+# --- Fungsi untuk Prediksi ---
+def predict(text, model, tokenizer, tag_values, device):
+ tokenized_sentence = tokenizer.encode(text)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # Logika dari sel 36 (menggabungkan token BPE '##')
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token.startswith("##"):
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ # Menggabungkan token dan label
+ results = []
+ for token, label in zip(new_tokens, new_labels):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token not in ['[CLS]', '[SEP]']:
+ results.append((token, label))
+ return results
+
+# --- Setup UI Streamlit ---
+st.set_page_config(layout="wide")
+st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+st.write("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+# Muat model
+model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+if model:
+ # Ambil contoh teks dari notebook Anda
+ default_text = """
+Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4.
+Evaluation of transdermal penetration enhancers using a novel skin alternative.
+ """
+
+ # Buat Text Area untuk input pengguna
+ user_input = st.text_area("Masukkan teks untuk dianalisis:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks"):
+ if user_input:
+ with st.spinner("Menganalisis..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis:")
+
+ # Menampilkan hasil dengan styling
+ # (Ini adalah cara sederhana, bisa juga pakai st.dataframe)
+
+ # Kita buat 2 kolom agar lebih rapi
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("**Token**")
+ for token, label in results:
+ st.write(token)
+
+ with col2:
+ st.markdown("**Tag (Entitas)**")
+ for token, label in results:
+ if label == "O":
+ st.write(label)
+ else:
+ # Beri tanda jika bukan 'O'
+ st.success(f"**{label}**")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+else:
+ st.error("Model tidak dapat dimuat. Pastikan folder `ner_bert_model` ada di direktori yang sama dengan `app.py`.")
\ No newline at end of file
diff --git a/.history/app_20251026141042.py b/.history/app_20251026141042.py
new file mode 100644
index 0000000000000000000000000000000000000000..c062ccff3bcf0204de41d8224495aa40a2ac6a92
--- /dev/null
+++ b/.history/app_20251026141042.py
@@ -0,0 +1,107 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+
+# Tentukan path ke model yang sudah disimpan
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- Fungsi untuk Memuat Model dan Tokenizer ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # Muat tag_values
+ with open(os.path.join(model_dir, 'tag_values.json'), 'r') as f:
+ tag_values = json.load(f)
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi
+
+ return model, tokenizer, tag_values, device
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ return None, None, None, None
+
+# --- Fungsi untuk Prediksi ---
+def predict(text, model, tokenizer, tag_values, device):
+ tokenized_sentence = tokenizer.encode(text)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # Logika dari sel 36 (menggabungkan token BPE '##')
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token.startswith("##"):
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ # Menggabungkan token dan label
+ results = []
+ for token, label in zip(new_tokens, new_labels):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token not in ['[CLS]', '[SEP]']:
+ results.append((token, label))
+ return results
+
+# --- Setup UI Streamlit ---
+st.set_page_config(layout="wide")
+st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+st.write("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+# Muat model
+model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+if model:
+ # Ambil contoh teks dari notebook Anda
+ default_text = """
+Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4.
+Evaluation of transdermal penetration enhancers using a novel skin alternative.
+ """
+
+ # Buat Text Area untuk input pengguna
+ user_input = st.text_area("Masukkan teks untuk dianalisis:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks"):
+ if user_input:
+ with st.spinner("Menganalisis..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis:")
+
+ # Menampilkan hasil dengan styling
+ # (Ini adalah cara sederhana, bisa juga pakai st.dataframe)
+
+ # Kita buat 2 kolom agar lebih rapi
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("**Token**")
+ for token, label in results:
+ st.write(token)
+
+ with col2:
+ st.markdown("**Tag (Entitas)**")
+ for token, label in results:
+ if label == "O":
+ st.write(label)
+ else:
+ # Beri tanda jika bukan 'O'
+ st.success(f"**{label}**")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+else:
+ st.error("Model tidak dapat dimuat. Pastikan folder `ner_bert_model` ada di direktori yang sama dengan `app.py`.")
\ No newline at end of file
diff --git a/.history/app_20251026141101.py b/.history/app_20251026141101.py
new file mode 100644
index 0000000000000000000000000000000000000000..c062ccff3bcf0204de41d8224495aa40a2ac6a92
--- /dev/null
+++ b/.history/app_20251026141101.py
@@ -0,0 +1,107 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+
+# Tentukan path ke model yang sudah disimpan
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- Fungsi untuk Memuat Model dan Tokenizer ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # Muat tag_values
+ with open(os.path.join(model_dir, 'tag_values.json'), 'r') as f:
+ tag_values = json.load(f)
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi
+
+ return model, tokenizer, tag_values, device
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ return None, None, None, None
+
+# --- Fungsi untuk Prediksi ---
+def predict(text, model, tokenizer, tag_values, device):
+ tokenized_sentence = tokenizer.encode(text)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # Logika dari sel 36 (menggabungkan token BPE '##')
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token.startswith("##"):
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ # Menggabungkan token dan label
+ results = []
+ for token, label in zip(new_tokens, new_labels):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token not in ['[CLS]', '[SEP]']:
+ results.append((token, label))
+ return results
+
+# --- Setup UI Streamlit ---
+st.set_page_config(layout="wide")
+st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+st.write("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+# Muat model
+model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+if model:
+ # Ambil contoh teks dari notebook Anda
+ default_text = """
+Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4.
+Evaluation of transdermal penetration enhancers using a novel skin alternative.
+ """
+
+ # Buat Text Area untuk input pengguna
+ user_input = st.text_area("Masukkan teks untuk dianalisis:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks"):
+ if user_input:
+ with st.spinner("Menganalisis..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis:")
+
+ # Menampilkan hasil dengan styling
+ # (Ini adalah cara sederhana, bisa juga pakai st.dataframe)
+
+ # Kita buat 2 kolom agar lebih rapi
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("**Token**")
+ for token, label in results:
+ st.write(token)
+
+ with col2:
+ st.markdown("**Tag (Entitas)**")
+ for token, label in results:
+ if label == "O":
+ st.write(label)
+ else:
+ # Beri tanda jika bukan 'O'
+ st.success(f"**{label}**")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+else:
+ st.error("Model tidak dapat dimuat. Pastikan folder `ner_bert_model` ada di direktori yang sama dengan `app.py`.")
\ No newline at end of file
diff --git a/.history/app_20251026141102.py b/.history/app_20251026141102.py
new file mode 100644
index 0000000000000000000000000000000000000000..c062ccff3bcf0204de41d8224495aa40a2ac6a92
--- /dev/null
+++ b/.history/app_20251026141102.py
@@ -0,0 +1,107 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+
+# Tentukan path ke model yang sudah disimpan
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- Fungsi untuk Memuat Model dan Tokenizer ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # Muat tag_values
+ with open(os.path.join(model_dir, 'tag_values.json'), 'r') as f:
+ tag_values = json.load(f)
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi
+
+ return model, tokenizer, tag_values, device
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ return None, None, None, None
+
+# --- Fungsi untuk Prediksi ---
+def predict(text, model, tokenizer, tag_values, device):
+ tokenized_sentence = tokenizer.encode(text)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # Logika dari sel 36 (menggabungkan token BPE '##')
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token.startswith("##"):
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ # Menggabungkan token dan label
+ results = []
+ for token, label in zip(new_tokens, new_labels):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token not in ['[CLS]', '[SEP]']:
+ results.append((token, label))
+ return results
+
+# --- Setup UI Streamlit ---
+st.set_page_config(layout="wide")
+st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+st.write("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+# Muat model
+model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+if model:
+ # Ambil contoh teks dari notebook Anda
+ default_text = """
+Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4.
+Evaluation of transdermal penetration enhancers using a novel skin alternative.
+ """
+
+ # Buat Text Area untuk input pengguna
+ user_input = st.text_area("Masukkan teks untuk dianalisis:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks"):
+ if user_input:
+ with st.spinner("Menganalisis..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis:")
+
+ # Menampilkan hasil dengan styling
+ # (Ini adalah cara sederhana, bisa juga pakai st.dataframe)
+
+ # Kita buat 2 kolom agar lebih rapi
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("**Token**")
+ for token, label in results:
+ st.write(token)
+
+ with col2:
+ st.markdown("**Tag (Entitas)**")
+ for token, label in results:
+ if label == "O":
+ st.write(label)
+ else:
+ # Beri tanda jika bukan 'O'
+ st.success(f"**{label}**")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+else:
+ st.error("Model tidak dapat dimuat. Pastikan folder `ner_bert_model` ada di direktori yang sama dengan `app.py`.")
\ No newline at end of file
diff --git a/.history/app_20251026141641.py b/.history/app_20251026141641.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a64fce76a095617ec97c45fa80620488f0caf4c
--- /dev/null
+++ b/.history/app_20251026141641.py
@@ -0,0 +1,189 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {"0": "O", "1": "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+ tag_values = [model.config.id2label[str(i)] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ # Tangani error jika folder model tidak ditemukan
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = '
'
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
diff --git a/.history/app_20251026141642.py b/.history/app_20251026141642.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a64fce76a095617ec97c45fa80620488f0caf4c
--- /dev/null
+++ b/.history/app_20251026141642.py
@@ -0,0 +1,189 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {"0": "O", "1": "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+ tag_values = [model.config.id2label[str(i)] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ # Tangani error jika folder model tidak ditemukan
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
diff --git a/.history/app_20251026141819.py b/.history/app_20251026141819.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4ae960aae4400a4540a200af70eb66914403957
--- /dev/null
+++ b/.history/app_20251026141819.py
@@ -0,0 +1,189 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {"0": "O", "1": "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+ tag_values = [model.config.id2label[str(i)] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ # Tangani error jika folder model tidak ditemukan
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
diff --git a/.history/app_20251026141833.py b/.history/app_20251026141833.py
new file mode 100644
index 0000000000000000000000000000000000000000..415948062b5bc3dd962771899a62a6c4557a72d5
--- /dev/null
+++ b/.history/app_20251026141833.py
@@ -0,0 +1,198 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026141844.py b/.history/app_20251026141844.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca00d568bad4289973827ba8c977113b1b145bca
--- /dev/null
+++ b/.history/app_20251026141844.py
@@ -0,0 +1,198 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026142055.py b/.history/app_20251026142055.py
new file mode 100644
index 0000000000000000000000000000000000000000..50e6da8fefa5141bdbf8df2bf8f9164c8843758e
--- /dev/null
+++ b/.history/app_20251026142055.py
@@ -0,0 +1,201 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # --- INI ADALAH PERBAIKANNYA ---
+ # Tambahkan unsafe_allow_html=True agar Streamlit merender HTML-nya
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026142100.py b/.history/app_20251026142100.py
new file mode 100644
index 0000000000000000000000000000000000000000..0034c082f6c6e1a986ded7e738a9822a3abc72dc
--- /dev/null
+++ b/.history/app_20251026142100.py
@@ -0,0 +1,201 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # --- INI ADALAH PERBAIKANNYA ---
+ # Tambahkan unsafe_allow_html=True agar Streamlit merender HTML-nya
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026142115.py b/.history/app_20251026142115.py
new file mode 100644
index 0000000000000000000000000000000000000000..0034c082f6c6e1a986ded7e738a9822a3abc72dc
--- /dev/null
+++ b/.history/app_20251026142115.py
@@ -0,0 +1,201 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # --- INI ADALAH PERBAIKANNYA ---
+ # Tambahkan unsafe_allow_html=True agar Streamlit merender HTML-nya
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026142116.py b/.history/app_20251026142116.py
new file mode 100644
index 0000000000000000000000000000000000000000..0034c082f6c6e1a986ded7e738a9822a3abc72dc
--- /dev/null
+++ b/.history/app_20251026142116.py
@@ -0,0 +1,201 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # --- INI ADALAH PERBAIKANNYA ---
+ # Tambahkan unsafe_allow_html=True agar Streamlit merender HTML-nya
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026142654.py b/.history/app_20251026142654.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ff6b18cce0966ee3abe456df6bcadf2fcff3edd
--- /dev/null
+++ b/.history/app_20251026142654.py
@@ -0,0 +1,203 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # --- INI ADALAH PERBAIKANNYA ---
+ # Tambahkan unsafe_allow_html=True agar Streamlit merender HTML-nya
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143025.py b/.history/app_20251026143025.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d384819ed0860dc431591a72e4a48146dac9722
--- /dev/null
+++ b/.history/app_20251026143025.py
@@ -0,0 +1,127 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI SIMPLE) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL ---
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # Mengubah {0: "O", 1: "B-PER", ...} menjadi ["O", "B-PER", ...]
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval()
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ with st.spinner("Memuat model..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ st.header("Analisis Teks Anda")
+
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Tabel Data)")
+
+ # --- TAMPILAN BARU ---
+ # Kita ganti tampilan HTML dengan tabel data yang pasti berfungsi
+
+ # Buat DataFrame dari SEMUA token
+ df = pd.DataFrame(results, columns=["Token", "Tag"])
+
+ # Tampilkan tabel
+ st.dataframe(df, use_container_width=True)
+
+ # Tampilkan hanya entitas yang ditemukan (sebagai tambahan)
+ with st.expander("Lihat Entitas yang Ditemukan Saja"):
+ entities_only = df[df["Tag"] != 'O']
+ if not entities_only.empty:
+ st.dataframe(entities_only, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143113.py b/.history/app_20251026143113.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ff6b18cce0966ee3abe456df6bcadf2fcff3edd
--- /dev/null
+++ b/.history/app_20251026143113.py
@@ -0,0 +1,203 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil sebagai teks yang di-highlight (UI Menarik)
+ """
+ # Definisikan warna untuk entitas.
+ # Anda bisa tambahkan jika punya banyak tipe entitas.
+ # Di sini kita buat simpel: semua entitas akan berwarna biru muda.
+ STYLE = """
+
+ {token}
+
+ {label}
+
+
+ """
+
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ # Jika 'O' (Outside), tampilkan sebagai teks biasa
+ html_output += f" {token}"
+ else:
+ # Jika entitas, tampilkan dengan highlight
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # --- INI ADALAH PERBAIKANNYA ---
+ # Tambahkan unsafe_allow_html=True agar Streamlit merender HTML-nya
+ st.markdown(html_output, unsafe_allow_html=True)
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143134.py b/.history/app_20251026143134.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143134.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143200.py b/.history/app_20251026143200.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143200.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143201.py b/.history/app_20251026143201.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143201.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143305.py b/.history/app_20251026143305.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143305.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143306.py b/.history/app_20251026143306.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143306.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143307.py b/.history/app_20251026143307.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143307.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143308.py b/.history/app_20251026143308.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143308.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143309.py b/.history/app_20251026143309.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143309.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143312.py b/.history/app_20251026143312.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143312.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143314.py b/.history/app_20251026143314.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143314.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143406.py b/.history/app_20251026143406.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d384819ed0860dc431591a72e4a48146dac9722
--- /dev/null
+++ b/.history/app_20251026143406.py
@@ -0,0 +1,127 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI SIMPLE) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL ---
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # Mengubah {0: "O", 1: "B-PER", ...} menjadi ["O", "B-PER", ...]
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval()
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ with st.spinner("Memuat model..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ st.header("Analisis Teks Anda")
+
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Tabel Data)")
+
+ # --- TAMPILAN BARU ---
+ # Kita ganti tampilan HTML dengan tabel data yang pasti berfungsi
+
+ # Buat DataFrame dari SEMUA token
+ df = pd.DataFrame(results, columns=["Token", "Tag"])
+
+ # Tampilkan tabel
+ st.dataframe(df, use_container_width=True)
+
+ # Tampilkan hanya entitas yang ditemukan (sebagai tambahan)
+ with st.expander("Lihat Entitas yang Ditemukan Saja"):
+ entities_only = df[df["Tag"] != 'O']
+ if not entities_only.empty:
+ st.dataframe(entities_only, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143409.py b/.history/app_20251026143409.py
new file mode 100644
index 0000000000000000000000000000000000000000..304c66dac621cb050b773eccc71c8381f3f21a0e
--- /dev/null
+++ b/.history/app_20251026143409.py
@@ -0,0 +1,126 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI SIMPLE) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL ---
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # Mengubah {0: "O", 1: "B-PER", ...} menjadi ["O", "B-PER", ...]
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval()
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ with st.spinner("Memuat model..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ st.header("Analisis Teks Anda")
+
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Tabel Data)")
+
+ # --- TAMPILAN BARU ---
+ # Kita ganti tampilan HTML dengan tabel data yang pasti berfungsi
+
+ # Buat DataFrame dari SEMUA token
+ df = pd.DataFrame(results, columns=["Token", "Tag"])
+
+ # Tampilkan tabel
+ st.dataframe(df, use_container_width=True)
+
+ # Tampilkan hanya entitas yang ditemukan (sebagai tambahan)
+ with st.expander("Lihat Entitas yang Ditemukan Saja"):
+ entities_only = df[df["Tag"] != 'O']
+ if not entities_only.empty:
+ st.dataframe(entities_only, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/.history/app_20251026143416.py b/.history/app_20251026143416.py
new file mode 100644
index 0000000000000000000000000000000000000000..2009d3d5012af8372bd144aec813202affd2b38b
--- /dev/null
+++ b/.history/app_20251026143416.py
@@ -0,0 +1,187 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+st.write("--- SERVER SUDAH RESTART (VERSI BARU) ---") # <-- TANDA BAHWA FILE BARU BERJALAN
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143431.py b/.history/app_20251026143431.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c795b23aa75b7cf3f47ef9088ff05aea121bea3
--- /dev/null
+++ b/.history/app_20251026143431.py
@@ -0,0 +1,185 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143432.py b/.history/app_20251026143432.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c795b23aa75b7cf3f47ef9088ff05aea121bea3
--- /dev/null
+++ b/.history/app_20251026143432.py
@@ -0,0 +1,185 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026143433.py b/.history/app_20251026143433.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c795b23aa75b7cf3f47ef9088ff05aea121bea3
--- /dev/null
+++ b/.history/app_20251026143433.py
@@ -0,0 +1,185 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os
+import pandas as pd # Kita tambahkan pandas untuk menampilkan tabel
+
+# --- KONFIGURASI ---
+# Pastikan nama folder ini SAMA PERSIS dengan folder model Anda
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL (VERSI PERBAIKAN) ---
+# @st.cache_resource akan menyimpan model di cache agar tidak di-load ulang
+# Ini adalah fungsi yang sudah diperbaiki untuk membaca 'id2label' dari config
+@st.cache_resource
+def load_model_and_tokenizer(model_dir):
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # Muat model dan tokenizer
+ model = BertForTokenClassification.from_pretrained(model_dir)
+ tokenizer = BertTokenizer.from_pretrained(model_dir)
+
+ # --- PERBAIKAN DARI ERROR SEBELUMNYA ---
+ # Kita tidak lagi mencari 'tag_values.json'.
+ # Sebagai gantinya, kita membaca 'id2label' dari file config.json model.
+ # Ini dimuat secara otomatis ke dalam 'model.config'
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ # model.config.id2label adalah dictionary: {0: "O", 1: "B-indications", ...}
+ # Kita ubah menjadi list: ["O", "B-indications", ...]
+ # Ini penting agar kita bisa mapping output (angka) kembali ke label (teks)
+
+ # --- INI ADALAH PERBAIKAN UNTUK KeyError: '0' ---
+ # Mengubah str(i) menjadi i, karena keys-nya adalah integer
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ # Tentukan device (GPU jika ada, jika tidak CPU)
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval() # Set model ke mode evaluasi (penting untuk prediksi)
+
+ return model, tokenizer, tag_values, device
+
+ except KeyError as e:
+ # Tangani KeyError secara spesifik
+ st.error(f"Error saat memuat model (KeyError): {e}")
+ st.error("Ini biasanya terjadi jika 'id2label' di config.json tidak dimulai dari 0 atau key-nya bukan integer.")
+ return None, None, None, None
+ except Exception as e:
+ # Tangani error umum lainnya
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ # Tokenisasi teks input
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ # Lakukan prediksi
+ with torch.no_grad():
+ output = model(input_ids)
+
+ # Ambil label dengan skor tertinggi (argmax)
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+
+ # Ubah ID token kembali menjadi token (kata)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ # --- Logika dari Notebook (menggabungkan token '##') ---
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ # Abaikan token spesial [CLS] dan [SEP]
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ # Jika token adalah BPE (sub-word), gabungkan dengan token sebelumnya
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ # Jika token utuh, tambahkan token dan labelnya
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UNTUK TAMPILAN UI ---
+def display_highlighted_text(results):
+ """
+ Menampilkan hasil NER dengan highlight warna untuk entitas.
+ """
+ # Template HTML satu baris agar tidak terbaca sebagai teks mentah
+ STYLE = (
+ '{token}'
+ ''
+ '{label}'
+ )
+
+ # Mulai HTML container
+ html_output = ''
+
+ for token, label in results:
+ if label == "O":
+ html_output += f" {token}"
+ else:
+ html_output += STYLE.format(token=token, label=label)
+
+ html_output += "
"
+
+ # Render HTML di Streamlit
+ st.markdown(html_output, unsafe_allow_html=True)
+
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ # Konfigurasi halaman
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ # --- Header ---
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis (berdasarkan notebook Anda).")
+
+ # --- Memuat Model ---
+ # Gunakan st.spinner agar terlihat loading saat model dimuat
+ with st.spinner("Memuat model... Ini mungkin perlu beberapa saat..."):
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ # Hanya lanjutkan jika model berhasil dimuat
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ # --- Area Input ---
+ st.header("Analisis Teks Anda")
+
+ # Contoh teks diambil dari notebook Anda
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ # Tampilkan spinner saat proses analisis
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Teks dengan Highlight)")
+ display_highlighted_text(results)
+
+ # --- Tampilkan Data Mentah di dalam Expander ---
+ with st.expander("Lihat Data Mentah (Token & Tag)"):
+ # Filter hanya token yang BUKAN 'O' untuk data mentah
+ entities_only = [res for res in results if res[1] != 'O']
+ if entities_only:
+ df = pd.DataFrame(entities_only, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/app_20251026150046.py b/.history/app_20251026150046.py
new file mode 100644
index 0000000000000000000000000000000000000000..489f580b8b0254789b85739c321c7750eebb0133
--- /dev/null
+++ b/.history/app_20251026150046.py
@@ -0,0 +1,126 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os # <-- Pastikan 'os' di-import
+import pandas as pd
+
+# --- KONFIGURASI ---
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL ---
+@st.cache_resource
+def load_model_and_tokenizer(model_dir_relative): # <-- ganti nama argumen
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # --- PERBAIKAN UNTUK STREAMLIT CLOUD ---
+ # Ubah path relatif (misal: "./fine_tuned_bert_ner")
+ # menjadi path absolut (misal: "/mount/src/.../fine_tuned_bert_ner")
+ # Ini mencegah transformers salah mengira path lokal sebagai ID repo Hugging Face
+ model_dir_absolute = os.path.abspath(model_dir_relative)
+
+ # Muat model & tokenizer dari path absolut
+ model = BertForTokenClassification.from_pretrained(model_dir_absolute)
+ tokenizer = BertTokenizer.from_pretrained(model_dir_absolute)
+ # --- AKHIR PERBAIKAN ---
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval()
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir_relative}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ with st.spinner("Memuat model..."):
+ # Panggil fungsi dengan MODEL_DIR global
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ st.header("Analisis Teks Anda")
+
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Tabel Data)")
+
+ df = pd.DataFrame(results, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+
+ with st.expander("Lihat Entitas yang Ditemukan Saja"):
+ entities_only = df[df["Tag"] != 'O']
+ if not entities_only.empty:
+ st.dataframe(entities_only, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/.history/requirements_20251026144419.txt b/.history/requirements_20251026144419.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.history/requirements_20251026144423.txt b/.history/requirements_20251026144423.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9b2414aef217bf35278a636d88ac01a0203c18a0
--- /dev/null
+++ b/.history/requirements_20251026144423.txt
@@ -0,0 +1,5 @@
+streamlit==1.39.0
+torch==2.3.1
+transformers==4.44.2
+pandas==2.2.2
+numpy==1.26.4
diff --git a/.history/requirements_20251026144654.txt b/.history/requirements_20251026144654.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9b2414aef217bf35278a636d88ac01a0203c18a0
--- /dev/null
+++ b/.history/requirements_20251026144654.txt
@@ -0,0 +1,5 @@
+streamlit==1.39.0
+torch==2.3.1
+transformers==4.44.2
+pandas==2.2.2
+numpy==1.26.4
diff --git a/.history/requirements_20251026144655.txt b/.history/requirements_20251026144655.txt
new file mode 100644
index 0000000000000000000000000000000000000000..80027b7eae749537626ecef67ee52332fd5f2ba3
--- /dev/null
+++ b/.history/requirements_20251026144655.txt
@@ -0,0 +1,5 @@
+streamlit==1.39.0
+torch==2.3.1
+transformers==4.44.2
+pandas==2.2.2
+numpy==1.26.4
\ No newline at end of file
diff --git a/.history/requirements_20251026144821.txt b/.history/requirements_20251026144821.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9b2414aef217bf35278a636d88ac01a0203c18a0
--- /dev/null
+++ b/.history/requirements_20251026144821.txt
@@ -0,0 +1,5 @@
+streamlit==1.39.0
+torch==2.3.1
+transformers==4.44.2
+pandas==2.2.2
+numpy==1.26.4
diff --git a/.history/requirements_20251026145020.txt b/.history/requirements_20251026145020.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ca71b75817a29e9fdd472d6f34880d48b58a53a
--- /dev/null
+++ b/.history/requirements_20251026145020.txt
@@ -0,0 +1,4 @@
+streamlit
+torch
+transformers
+pandas
\ No newline at end of file
diff --git a/.history/requirements_20251026145022.txt b/.history/requirements_20251026145022.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9b2414aef217bf35278a636d88ac01a0203c18a0
--- /dev/null
+++ b/.history/requirements_20251026145022.txt
@@ -0,0 +1,5 @@
+streamlit==1.39.0
+torch==2.3.1
+transformers==4.44.2
+pandas==2.2.2
+numpy==1.26.4
diff --git a/.history/requirements_20251026145024.txt b/.history/requirements_20251026145024.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ca71b75817a29e9fdd472d6f34880d48b58a53a
--- /dev/null
+++ b/.history/requirements_20251026145024.txt
@@ -0,0 +1,4 @@
+streamlit
+torch
+transformers
+pandas
\ No newline at end of file
diff --git a/.history/requirements_20251026145026.txt b/.history/requirements_20251026145026.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ca71b75817a29e9fdd472d6f34880d48b58a53a
--- /dev/null
+++ b/.history/requirements_20251026145026.txt
@@ -0,0 +1,4 @@
+streamlit
+torch
+transformers
+pandas
\ No newline at end of file
diff --git a/.history/requirements_20251026145029.txt b/.history/requirements_20251026145029.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd21f5a5c87964d5173e03f62442ef542d14a4
--- /dev/null
+++ b/.history/requirements_20251026145029.txt
@@ -0,0 +1,5 @@
+streamlit
+torch
+transformers
+pandas
+numpy
\ No newline at end of file
diff --git a/.history/requirements_20251026145030.txt b/.history/requirements_20251026145030.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd21f5a5c87964d5173e03f62442ef542d14a4
--- /dev/null
+++ b/.history/requirements_20251026145030.txt
@@ -0,0 +1,5 @@
+streamlit
+torch
+transformers
+pandas
+numpy
\ No newline at end of file
diff --git a/.history/runtime_20251026144640.txt b/.history/runtime_20251026144640.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.history/runtime_20251026144646.txt b/.history/runtime_20251026144646.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5fbbbc850a78764532a54175e9b3df9060b468a8
--- /dev/null
+++ b/.history/runtime_20251026144646.txt
@@ -0,0 +1 @@
+python-3.10
diff --git a/.history/runtime_20251026144647.txt b/.history/runtime_20251026144647.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f31904fcb9165e71f5777bee4b1b5dc1cd22b4b7
--- /dev/null
+++ b/.history/runtime_20251026144647.txt
@@ -0,0 +1 @@
+python-3.10
\ No newline at end of file
diff --git a/.history/runtime_20251026144816.txt b/.history/runtime_20251026144816.txt
new file mode 100644
index 0000000000000000000000000000000000000000..11884c84ef7b051f80a9cbb85cb24c93121bc06d
--- /dev/null
+++ b/.history/runtime_20251026144816.txt
@@ -0,0 +1 @@
+python-3.11
diff --git a/.history/simpan_20251026125713.py b/.history/simpan_20251026125713.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/.history/simpan_20251026125720.py b/.history/simpan_20251026125720.py
new file mode 100644
index 0000000000000000000000000000000000000000..333341307ce3cfda628ba21e1dcaca226d005d1c
--- /dev/null
+++ b/.history/simpan_20251026125720.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./ner_bert_model"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026132828.py b/.history/simpan_20251026132828.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8fd29f98449dbd11d57adfd3afb44d8dc5edba0
--- /dev/null
+++ b/.history/simpan_20251026132828.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026132833.py b/.history/simpan_20251026132833.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4b8aa9822fe54f3a736649dcdb95452dab20d3a
--- /dev/null
+++ b/.history/simpan_20251026132833.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./fine"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026132834.py b/.history/simpan_20251026132834.py
new file mode 100644
index 0000000000000000000000000000000000000000..18d777871fbcf348cc844fd3329000f9c0a41b80
--- /dev/null
+++ b/.history/simpan_20251026132834.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./fine_tuned_bert_ner"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026140958.py b/.history/simpan_20251026140958.py
new file mode 100644
index 0000000000000000000000000000000000000000..18d777871fbcf348cc844fd3329000f9c0a41b80
--- /dev/null
+++ b/.history/simpan_20251026140958.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./fine_tuned_bert_ner"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026141007.py b/.history/simpan_20251026141007.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b1b599838b0b958a4673b7a0bad72b3d3e52a80
--- /dev/null
+++ b/.history/simpan_20251026141007.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./C:\Users\user\Documents\Project\Indonesian-NER-using-BERT\fine_tuned_bert_ner"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026141013.py b/.history/simpan_20251026141013.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c9e70280b56a0f13e5a3691102054506519b4c7
--- /dev/null
+++ b/.history/simpan_20251026141013.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./Cfine_tuned_bert_ner"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026141018.py b/.history/simpan_20251026141018.py
new file mode 100644
index 0000000000000000000000000000000000000000..18d777871fbcf348cc844fd3329000f9c0a41b80
--- /dev/null
+++ b/.history/simpan_20251026141018.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./fine_tuned_bert_ner"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/.history/simpan_20251026141023.py b/.history/simpan_20251026141023.py
new file mode 100644
index 0000000000000000000000000000000000000000..18d777871fbcf348cc844fd3329000f9c0a41b80
--- /dev/null
+++ b/.history/simpan_20251026141023.py
@@ -0,0 +1,26 @@
+import os
+import json
+
+# Tentukan direktori untuk menyimpan model
+OUTPUT_DIR = "./fine_tuned_bert_ner"
+
+# Buat direktori jika belum ada
+if not os.path.exists(OUTPUT_DIR):
+ os.makedirs(OUTPUT_DIR)
+
+print(f"Menyimpan model ke {OUTPUT_DIR}")
+
+# Simpan model yang sudah di-fine-tune
+model.save_pretrained(OUTPUT_DIR)
+
+# Simpan tokenizer
+tokenizer.save_pretrained(OUTPUT_DIR)
+
+# Simpan tag2idx (penting untuk mapping output)
+# (Ambil 'tag2idx' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag2idx.json'), 'w') as f:
+ json.dump(tag2idx, f)
+
+# (Ambil 'tag_values' dari sel 8)
+with open(os.path.join(OUTPUT_DIR, 'tag_values.json'), 'w') as f:
+ json.dump(tag_values, f)
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..88b0cccbc3da7f7527d502b0803dec2b928bea13
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+
+ # Indonesia Named Entity Recognition (NER) using BERT
+
+Aplikasi berbasis **Streamlit** untuk mendeteksi entitas bernama (*Named Entity Recognition / NER*) pada teks berbahasa Indonesia menggunakan **BERT (Bidirectional Encoder Representations from Transformers)** yang telah di-*fine-tune*.
+Proyek ini dirancang untuk kebutuhan analisis teks domain medis, namun dapat dikembangkan untuk domain lain seperti berita, hukum, atau sosial media.
+
+
+---
+
+## ๐ **Fitur Utama**
+- ๐ **Prediksi otomatis entitas** (mis. nama penyakit, spesies, lokasi, dsb.) dari teks input.
+- ๐จ **Highlight visual interaktif** untuk setiap entitas yang terdeteksi.
+- โ๏ธ **Berbasis model BERT yang telah di-fine-tune** untuk tugas token classification.
+- ๐ **Tabel hasil entitas** yang dapat diperluas (expandable).
+- ๐ป **Aplikasi berbasis web (Streamlit)** โ berjalan lokal maupun di-deploy ke cloud.
+
+---
+
+## ๐ง **Model yang Digunakan**
+Model menggunakan arsitektur **BERT (Bidirectional Encoder Representations from Transformers)** yang telah di-*fine-tune* pada dataset NER Bahasa Indonesia.
+Struktur folder model:
+
+fine_tuned_bert_ner/
+โ
+โโโ config.json
+
+โโโ pytorch_model.bin
+
+โโโ tokenizer_config.json
+
+โโโ vocab.txt
+
+โโโ special_tokens_map.json
+
+
+Pastikan folder ini berada **satu direktori** dengan file `app.py`.
+
+---
+
+## ๐ ๏ธ **Cara Menjalankan Proyek**
+
+### 1๏ธโฃ Clone Repository
+```bash
+git clone https://github.com/decoderr24/Indonesian-NER-using-BERT.git
+cd Indonesian-NER-using-BERT
+```
+2๏ธโฃ Install Dependencies
+
+Gunakan Python 3.8+ dan jalankan:
+
+```bash
+pip install -r requirements.txt
+
+```
+
+
+Atau manual:
+```bash
+pip install streamlit torch transformers pandas
+```
+3๏ธโฃ Jalankan Aplikasi
+```bash
+streamlit run app.py
+```
+Kemudian buka browser di:
+
+
+http://localhost:8501
+
+
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..489f580b8b0254789b85739c321c7750eebb0133
--- /dev/null
+++ b/app.py
@@ -0,0 +1,126 @@
+import streamlit as st
+import torch
+import numpy as np
+from transformers import BertTokenizer, BertForTokenClassification
+import json
+import os # <-- Pastikan 'os' di-import
+import pandas as pd
+
+# --- KONFIGURASI ---
+MODEL_DIR = "./fine_tuned_bert_ner"
+
+# --- FUNGSI UNTUK MEMUAT MODEL ---
+@st.cache_resource
+def load_model_and_tokenizer(model_dir_relative): # <-- ganti nama argumen
+ """
+ Memuat model, tokenizer, dan daftar tag dari direktori yang disimpan.
+ """
+ try:
+ # --- PERBAIKAN UNTUK STREAMLIT CLOUD ---
+ # Ubah path relatif (misal: "./fine_tuned_bert_ner")
+ # menjadi path absolut (misal: "/mount/src/.../fine_tuned_bert_ner")
+ # Ini mencegah transformers salah mengira path lokal sebagai ID repo Hugging Face
+ model_dir_absolute = os.path.abspath(model_dir_relative)
+
+ # Muat model & tokenizer dari path absolut
+ model = BertForTokenClassification.from_pretrained(model_dir_absolute)
+ tokenizer = BertTokenizer.from_pretrained(model_dir_absolute)
+ # --- AKHIR PERBAIKAN ---
+
+ if not hasattr(model.config, 'id2label'):
+ st.error("Error: 'id2label' tidak ditemukan di dalam config.json model.")
+ return None, None, None, None
+
+ tag_values = [model.config.id2label[i] for i in range(len(model.config.id2label))]
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ model.to(device)
+ model.eval()
+
+ return model, tokenizer, tag_values, device
+
+ except Exception as e:
+ st.error(f"Error saat memuat model: {e}")
+ st.error(f"Pastikan folder '{model_dir_relative}' ada di direktori yang sama dengan app.py")
+ return None, None, None, None
+
+# --- FUNGSI UNTUK PREDIKSI ---
+def predict(text, model, tokenizer, tag_values, device):
+ """
+ Melakukan prediksi NER pada teks input.
+ """
+ tokenized_sentence = tokenizer.encode(text, truncation=True, max_length=512)
+ input_ids = torch.tensor([tokenized_sentence]).to(device)
+
+ with torch.no_grad():
+ output = model(input_ids)
+
+ label_indices = np.argmax(output[0].to('cpu').numpy(), axis=2)
+ tokens = tokenizer.convert_ids_to_tokens(input_ids.to('cpu').numpy()[0])
+
+ new_tokens, new_labels = [], []
+ for token, label_idx in zip(tokens, label_indices[0]):
+ if token in ['[CLS]', '[SEP]']:
+ continue
+
+ if token.startswith("##"):
+ if new_tokens:
+ new_tokens[-1] = new_tokens[-1] + token[2:]
+ else:
+ new_labels.append(tag_values[label_idx])
+ new_tokens.append(token)
+
+ return list(zip(new_tokens, new_labels))
+
+# --- FUNGSI UTAMA APLIKASI ---
+def main():
+ st.set_page_config(
+ page_title="Aplikasi NER Medis",
+ page_icon="๐งช",
+ layout="wide"
+ )
+
+ st.title("๐งช Aplikasi Named Entity Recognition (NER) dengan BERT")
+ st.markdown("Aplikasi ini menggunakan model BERT yang di-fine-tune untuk mengenali entitas dari teks medis.")
+
+ with st.spinner("Memuat model..."):
+ # Panggil fungsi dengan MODEL_DIR global
+ model, tokenizer, tag_values, device = load_model_and_tokenizer(MODEL_DIR)
+
+ if model and tokenizer and tag_values and device:
+
+ st.success("Model berhasil dimuat!")
+
+ st.header("Analisis Teks Anda")
+
+ default_text = (
+ "Pasteurellosis in japanese quail (Coturnix coturnix japonica) caused by Pasteurella multocida multocida A:4. \n\n"
+ "Evaluation of transdermal penetration enhancers using a novel skin alternative. \n\n"
+ "A novel alternative to animal skin models was developed in order to aid in the screening of transdermal penetration enhancer."
+ )
+
+ user_input = st.text_area("Masukkan teks untuk dianalisis di sini:", default_text, height=150)
+
+ if st.button("๐ Analisis Teks", type="primary"):
+ if user_input:
+ with st.spinner("Menganalisis teks..."):
+ results = predict(user_input, model, tokenizer, tag_values, device)
+
+ st.subheader("Hasil Analisis (Tabel Data)")
+
+ df = pd.DataFrame(results, columns=["Token", "Tag"])
+ st.dataframe(df, use_container_width=True)
+
+ with st.expander("Lihat Entitas yang Ditemukan Saja"):
+ entities_only = df[df["Tag"] != 'O']
+ if not entities_only.empty:
+ st.dataframe(entities_only, use_container_width=True)
+ else:
+ st.info("Tidak ada entitas yang ditemukan.")
+ else:
+ st.warning("Silakan masukkan teks terlebih dahulu.")
+
+# Menjalankan aplikasi
+if __name__ == "__main__":
+ main()
+
diff --git a/id_gsd-ud-dev.conllu b/id_gsd-ud-dev.conllu
new file mode 100644
index 0000000000000000000000000000000000000000..2170bb3f688d8cfb434e2aee253bce3cb0e6ca0d
--- /dev/null
+++ b/id_gsd-ud-dev.conllu
@@ -0,0 +1,15135 @@
+# sent_id = dev-s1
+# text = Ahli rekayasa optik mendesain komponen dari instrumen optik seperti lensa, mikroskop, teleskop, dan peralatan lainnya yang mendukung sifat cahaya.
+# text_en = Optical engineers design components of optical instruments such as lenses, microscopes, telescopes, and other equipment that support the properties of light.
+1 Ahli ahli PROPN NSD _ 4 nsubj _ Morf=^ahli_NSD$
+2 rekayasa rekayasa NOUN NSD Number=Sing 1 compound _ Morf=^rekayasa_NSD$
+3 optik optik NOUN NSD Number=Sing 2 compound _ Morf=^optik_NSD$
+4 mendesain desain VERB VSA Mood=Ind|Voice=Act 0 root _ Morf=^meN+desain_VSA$
+5 komponen komponen NOUN NSD Number=Sing 4 obj _ Morf=^komponen_NSD$
+6 dari dari ADP R-- _ 7 case _ Morf=^dari_R--$
+7 instrumen instrumen NOUN NSD Number=Sing 4 obl _ Morf=^instrumen_NSD$
+8 optik optik ADJ NSD _ 7 amod _ Morf=^optik_NSD$
+9 seperti seperti ADP R-- _ 10 case _ Morf=^seperti_R--$
+10 lensa lensa NOUN NSD Number=Sing 4 obl _ SpaceAfter=No|Morf=^lensa_NSD$
+11 , , PUNCT Z-- _ 12 punct _ Morf=^,_Z--$
+12 mikroskop mikroskop NOUN NSD Number=Sing 10 conj _ SpaceAfter=No|Morf=^mikroskop_NSD$
+13 , , PUNCT Z-- _ 14 punct _ Morf=^,_Z--$
+14 teleskop teleskop NOUN NSD Number=Sing 10 conj _ SpaceAfter=No|Morf=^teleskop_NSD$
+15 , , PUNCT Z-- _ 17 punct _ Morf=^,_Z--$
+16 dan dan CCONJ H-- _ 17 cc _ Morf=^dan_H--$
+17 peralatan alat NOUN NSD Number=Sing 10 conj _ Morf=^per+alat+an_NSD$
+18 lainnya lainnya ADJ D-- _ 17 amod _ Morf=^lainnya_D--$
+19 yang yang PRON S-- PronType=Rel 20 nsubj _ Morf=^yang_S--$
+20 mendukung dukung VERB VSA Mood=Ind|Voice=Act 17 acl:relcl _ Morf=^meN+dukung_VSA$
+21 sifat sifat NOUN NSD Number=Sing 20 obj _ Morf=^sifat_NSD$
+22 cahaya cahaya NOUN NSD Number=Sing 21 compound _ SpaceAfter=No|Morf=^cahaya_NSD$
+23 . . PUNCT Z-- _ 4 punct _ Morf=^._Z--$
+
+# sent_id = dev-s2
+# text = Wisma Kalla terletak di kota makassar, tak jauh dari pantai Losari Makassar, dengat dengan lapangan Karebosi Makassar, dekat dengan Mal Ratu Indah, dan dekat juga dengan kawasan Trans Studio Makassar.
+# text_en = Wisma Kalla is located in the city of Makassar, not far from Losari Makassar beach, with Karebosi Makassar field, close to Ratu Indah Mall, and also close to the Trans Studio Makassar area.
+1 Wisma wisma PROPN NSD _ 3 nsubj _ Morf=^wisma_NSD$
+2 Kalla kalla PROPN F-- _ 1 flat:name _ Morf=^kalla_F--$
+3 terletak letak VERB VSP Mood=Ind|Voice=Pass 0 root _ Morf=^ter+letak_VSP$
+4 di di ADP R-- _ 5 case _ Morf=^di_R--$
+5 kota kota NOUN NSD Number=Sing 3 obl _ Morf=^kota_NSD$
+6 makassar makassar ADJ F-- _ 5 amod _ SpaceAfter=No|Morf=^makassar_F--$
+7 , , PUNCT Z-- _ 9 punct _ Morf=^,_Z--$
+8 tak tak PART G-- Polarity=Neg 9 advmod _ Morf=^tak_G--$
+9 jauh jauh ADJ ASP _ 3 amod _ Morf=^jauh_ASP$
+10 dari dari ADP R-- _ 11 case _ Morf=^dari_R--$
+11 pantai pantai NOUN NSD Number=Sing 9 nmod _ Morf=^pantai_NSD$
+12 Losari losari PROPN X-- _ 11 nmod _ Morf=^losari_X--$
+13 Makassar makassar PROPN F-- _ 12 flat:name _ SpaceAfter=No|Morf=^makassar_F--$
+14 , , PUNCT Z-- _ 15 punct _ Morf=^,_Z--$
+15 dengat dengat ADJ X-- _ 3 amod _ Morf=^dengat_X--$
+16 dengan dengan ADP R-- _ 17 case _ Morf=^dengan_R--$
+17 lapangan lapang NOUN NSD Number=Sing 15 nmod _ Morf=^lapang+an_NSD$
+18 Karebosi karebosi PROPN X-- _ 17 nmod _ Morf=^karebosi_X--$
+19 Makassar makassar PROPN F-- _ 18 flat:name _ SpaceAfter=No|Morf=^makassar_F--$
+20 , , PUNCT Z-- _ 21 punct _ Morf=^,_Z--$
+21 dekat dekat ADJ ASP _ 3 amod _ Morf=^dekat_ASP$
+22 dengan dengan ADP R-- _ 23 case _ Morf=^dengan_R--$
+23 Mal mal PROPN F-- _ 21 nmod _ Morf=^mal_F--$
+24 Ratu ratu PROPN NSD _ 23 flat:name _ Morf=^ratu_NSD$
+25 Indah indah PROPN ASP _ 24 flat:name _ SpaceAfter=No|Morf=^indah_ASP$
+26 , , PUNCT Z-- _ 28 punct _ Morf=^,_Z--$
+27 dan dan CCONJ H-- _ 28 cc _ Morf=^dan_H--$
+28 dekat dekat ADJ ASP _ 3 conj _ Morf=^dekat_ASP$
+29 juga juga ADV D-- _ 28 advmod _ Morf=^juga_D--$
+30 dengan dengan ADP R-- _ 31 case _ Morf=^dengan_R--$
+31 kawasan kawasan NOUN NSD Number=Sing 29 nmod _ Morf=^kawasan_NSD$
+32 Trans trans PROPN F-- _ 31 nmod _ Morf=^trans_F--$
+33 Studio studio PROPN NSD _ 32 flat:name _ Morf=^studio_NSD$
+34 Makassar makassar PROPN F-- _ 33 flat:name _ SpaceAfter=No|Morf=^makassar_F--$
+35 . . PUNCT Z-- _ 3 punct _ Morf=^._Z--$
+
+# sent_id = dev-s3
+# text = Hal tersebut dapat dilihat dalam beberapa contoh karya mereka, seperti โRietvield Schroeder House serta Red Blue Chairโ. Gaya de Stijl seperti yang telah disebutkan di awal, telah memengaruhi gaya dari Bauhaus serta gaya arsitektur Internasional, dan juga gaya desain interior dan fashion desain dunia.
+# text_en = This can be seen in several examples of their work, such as โRietvield Schroeder House and Red Blue Chairโ. The style of de Stijl as mentioned earlier, has influenced the styles of the Bauhaus and International architectural styles, as well as interior design styles and world design fashion.
+1 Hal hal NOUN NSD Number=Sing 4 nsubj:pass _ Morf=^hal_NSD$
+2 tersebut tersebut DET B-- PronType=Dem 1 det _ Morf=^tersebut_B--$
+3 dapat dapat AUX M-- _ 4 aux _ Morf=^dapat_M--$
+4 dilihat lihat VERB VSP Mood=Ind|Voice=Pass 0 root _ Morf=^di+lihat_VSP$
+5 dalam dalam ADP ASP _ 7 case _ Morf=^dalam_ASP$
+6 beberapa beberapa DET B-- PronType=Ind 7 det _ Morf=^beberapa_B--$
+7 contoh contoh NOUN NSD Number=Sing 4 obl _ Morf=^contoh_NSD$
+8 karya karya NOUN NSD Number=Sing 7 compound _ Morf=^karya_NSD$
+9 mereka mereka PRON PP3 Number=Plur|Person=3|PronType=Prs 8 nmod:poss _ SpaceAfter=No|Morf=^mereka_PP3$
+10 , , PUNCT Z-- _ 13 punct _ Morf=^,_Z--$
+11 seperti seperti ADP R-- _ 13 case _ Morf=^seperti_R--$
+12 โ โ PUNCT Z-- _ 13 punct _ SpaceAfter=No|Morf=^โ_Z--$
+13 Rietvield rietvield PROPN X-- _ 4 obl _ Morf=^rietvield_X--$
+14 Schroeder schroeder PROPN F-- _ 13 flat:name _ Morf=^schroeder_F--$
+15 House house PROPN F-- _ 14 flat:name _ Morf=^house_F--$
+16 serta serta CCONJ H-- _ 17 cc _ Morf=^serta_H--$
+17 Red red PROPN F-- _ 13 conj _ Morf=^red_F--$
+18 Blue blue PROPN F-- _ 17 flat:name _ Morf=^blue_F--$
+19 Chair chair PROPN X-- _ 18 flat:name _ SpaceAfter=No|Morf=^chair_X--$
+20 โ โ PUNCT Z-- _ 13 punct _ SpaceAfter=No|Morf=^โ_Z--$
+21 . . PUNCT Z-- _ 4 punct _ Morf=^._Z--$
+22 Gaya gaya NOUN NSD Number=Sing 33 nsubj _ Morf=^gaya_NSD$
+23 de de PROPN F-- _ 22 nmod _ Morf=^de_F--$
+24 Stijl stijl PROPN X-- _ 23 flat:name _ Morf=^stijl_X--$
+25 seperti seperti ADP R-- _ 22 case _ Morf=^seperti_R--$
+26 yang yang PRON S-- PronType=Rel 28 nsubj:pass _ Morf=^yang_S--$
+27 telah telah AUX D-- _ 28 aux _ Morf=^telah_D--$
+28 disebutkan sebut VERB VSP Mood=Ind|Voice=Pass 22 acl:relcl _ Morf=^di+sebut+kan_VSP$
+29 di di ADP R-- _ 30 case _ Morf=^di_R--$
+30 awal awal NOUN NSD Number=Sing 28 obl _ SpaceAfter=No|Morf=^awal_NSD$
+31 , , PUNCT Z-- _ 22 punct _ Morf=^,_Z--$
+32 telah telah AUX D-- _ 33 aux _ Morf=^telah_D--$
+33 memengaruhi memengaruhi VERB X-- _ 4 dep _ Morf=^memengaruhi_X--$
+34 gaya gaya NOUN NSD Number=Sing 33 obj _ Morf=^gaya_NSD$
+35 dari dari ADP R-- _ 36 case _ Morf=^dari_R--$
+36 Bauhaus bauhaus PROPN X-- _ 34 nmod _ Morf=^bauhaus_X--$
+37 serta serta CCONJ H-- _ 38 cc _ Morf=^serta_H--$
+38 gaya gaya NOUN NSD Number=Sing 34 conj _ Morf=^gaya_NSD$
+39 arsitektur arsitektur NOUN NSD Number=Sing 38 compound _ Morf=^arsitektur_NSD$
+40 Internasional internasional PROPN ASP _ 39 nmod _ SpaceAfter=No|Morf=^internasional_ASP$
+41 , , PUNCT Z-- _ 44 punct _ Morf=^,_Z--$
+42 dan dan CCONJ H-- _ 44 cc _ Morf=^dan_H--$
+43 juga juga ADV D-- _ 44 advmod _ Morf=^juga_D--$
+44 gaya gaya NOUN NSD Number=Sing 34 conj _ Morf=^gaya_NSD$
+45 desain desain NOUN NSD Number=Sing 44 compound _ Morf=^desain_NSD$
+46 interior interior NOUN NSD Number=Sing 45 compound _ Morf=^interior_NSD$
+47 dan dan CCONJ H-- _ 48 cc _ Morf=^dan_H--$
+48 fashion fashion PROPN F-- _ 45 conj _ Morf=^fashion_F--$
+49 desain desain NOUN NSD Number=Sing 48 compound _ Morf=^desain_NSD$
+50 dunia dunia NOUN NSD Number=Sing 49 compound _ SpaceAfter=No|Morf=^dunia_NSD$
+51 . . PUNCT Z-- _ 33 punct _ Morf=^._Z--$
+
+# sent_id = dev-s4
+# text = Sedangkan Permintaan adalah keinginan akan sesuatu yang didukung dengan kemampuan serta kesediaan membelinya.
+# text_en = While demand is a desire for something that is supported by the ability and willingness to buy it.
+1 Sedangkan sedangkan SCONJ S-- _ 4 mark _ Morf=^sedangkan_S--$
+2 Permintaan permintaan PROPN NSD _ 4 nsubj _ Morf=^per+minta+an_NSD$
+3 adalah adalah AUX O-- _ 4 cop _ Morf=^adalah_O--$
+4 keinginan ingin NOUN NSD Number=Sing 0 root _ Morf=^ke+ingin+an_NSD$
+5 akan akan ADP M-- _ 6 case _ Morf=^akan_M--$
+6 sesuatu suatu PRON NSD Number=Sing|PronType=Ind 4 compound _ Morf=^sesuatu_NSD$
+7 yang yang PRON S-- PronType=Rel 8 nsubj:pass _ Morf=^yang_S--$
+8 didukung dukung VERB VSP Mood=Ind|Voice=Pass 4 acl:relcl _ Morf=^di+dukung_VSP$
+9 dengan dengan ADP R-- _ 10 case _ Morf=^dengan_R--$
+10 kemampuan mampu NOUN NSD Number=Sing 8 obl _ Morf=^ke+mampu+an_NSD$
+11 serta serta CCONJ H-- _ 12 cc _ Morf=^serta_H--$
+12 kesediaan sedia NOUN NSD Number=Sing 10 conj _ Morf=^ke+sedia+an_NSD$
+13-14 membelinya _ _ _ _ _ _ _ Morf=^meN+beli_VSA+dia_PS3$|SpaceAfter=No
+13 membeli beli VERB VSA Mood=Ind|Voice=Act 12 acl _ Morf=^meN+beli_VSA$
+14 nya dia PRON PS3 Number=Sing|Person=3|PronType=Prs 13 obj _ Morf=^dia_PS3$
+15 . . PUNCT Z-- _ 4 punct _ Morf=^._Z--$
+
+# sent_id = dev-s5
+# text = Let It Shine adalah film televisi 2012.
+# text_en = Let It Shine is a 2012 television film.
+1 Let let PROPN F-- _ 5 nsubj _ Morf=^let_F--$
+2 It it PROPN F-- _ 1 flat:name _ Morf=^it_F--$
+3 Shine shine PROPN X-- _ 2 flat:name _ Morf=^shine_X--$
+4 adalah adalah AUX O-- _ 5 cop _ Morf=^adalah_O--$
+5 film film NOUN NSD Number=Sing 0 root _ Morf=^film_NSD$
+6 televisi televisi NOUN NSD Number=Sing 5 compound _ Morf=^televisi_NSD$
+7 2012 2012 NUM CC- NumType=Card 6 nummod _ SpaceAfter=No|Morf=^2012_CC-$
+8 . . PUNCT Z-- _ 5 punct _ Morf=^._Z--$
+
+# sent_id = dev-s6
+# text = Delapn tim bersaing pada putaran pertama musim; tujuh tim dari Sydney dan satu tim dari Newcastle.
+# text_en = Eight teams compete in the first round of the season; seven teams from Sydney and one team from Newcastle.
+1 Delapn delapan NUM X-- NumType=Card|Typo=Yes 2 nummod _ CorrectForm=Delapan|Morf=^delapn_X--$
+2 tim tim NOUN NSD Number=Sing 3 nsubj _ Morf=^tim_NSD$
+3 bersaing saing VERB VSA Mood=Ind|Voice=Act 0 root _ Morf=^ber+saing_VSA$
+4 pada pada ADP R-- _ 5 case _ Morf=^pada_R--$
+5 putaran putar NOUN NSD Number=Sing 3 obl _ Morf=^putar+an_NSD$
+6 pertama pertama ADJ CO- NumType=Ord 5 amod _ Morf=^pertama_CO-$
+7 musim musim NOUN NSD Number=Sing 5 nmod _ SpaceAfter=No|Morf=^musim_NSD$
+8 ; ; PUNCT Z-- _ 10 punct _ Morf=^;_Z--$
+9 tujuh tujuh NUM CC- NumType=Card 10 nummod _ Morf=^tujuh_CC-$
+10 tim tim NOUN NSD Number=Sing 3 appos _ Morf=^tim_NSD$
+11 dari dari ADP R-- _ 12 case _ Morf=^dari_R--$
+12 Sydney sydney PROPN F-- _ 10 nmod _ Morf=^sydney_F--$
+13 dan dan CCONJ H-- _ 15 cc _ Morf=^dan_H--$
+14 satu satu NUM CC- NumType=Card 15 nummod _ Morf=^satu_CC-$
+15 tim tim NOUN NSD Number=Sing 10 conj _ Morf=^tim_NSD$
+16 dari dari ADP R-- _ 17 case _ Morf=^dari_R--$
+17 Newcastle newcastle PROPN F-- _ 15 nmod _ SpaceAfter=No|Morf=^newcastle_F--$
+18 . . PUNCT Z-- _ 3 punct _ Morf=^._Z--$
+
+# sent_id = dev-s7
+# text = Struktur Organisasi program Studi susun untuk mengakomodasi pendelegasian wewenang, pembagian tugas, dan fleksibilitas aktifitas untuk pengembangan program studi.
+# text_en = The study program's organizational structure is structured to accommodate delegation of authority, division of tasks, and flexibility of activities for the development of study programs.
+1 Struktur struktur PROPN NSD _ 5 nsubj _ Morf=^struktur_NSD$
+2 Organisasi organisasi PROPN NSD _ 1 flat:name _ Morf=^organisasi_NSD$
+3 program program NOUN NSD Number=Sing 1 compound _ Morf=^program_NSD$
+4 Studi studi PROPN NSD _ 3 nmod _ Morf=^studi_NSD$
+5 susun susun VERB VSA Mood=Ind|Voice=Act 0 root _ Morf=^susun_VSA$
+6 untuk untuk SCONJ R-- _ 7 mark _ Morf=^untuk_R--$
+7 mengakomodasi akomodasi VERB VSA Mood=Ind|Voice=Act 5 xcomp _ Morf=^meN+akomodasi_VSA$
+8 pendelegasian delegasi NOUN NSD Number=Sing 7 obj _ Morf=^peN+delegasi+an_NSD$
+9 wewenang wewenang NOUN NSD Number=Sing 8 compound _ SpaceAfter=No|Morf=^wewenang_NSD$
+10 , , PUNCT Z-- _ 11 punct _ Morf=^,_Z--$
+11 pembagian bagi NOUN NSD Number=Sing 8 conj _ Morf=^peN+bagi+an_NSD$
+12 tugas tugas NOUN NSD Number=Sing 11 compound _ SpaceAfter=No|Morf=^tugas_NSD$
+13 , , PUNCT Z-- _ 15 punct _ Morf=^,_Z--$
+14 dan dan CCONJ H-- _ 15 cc _ Morf=^dan_H--$
+15 fleksibilitas fleksibilitas NOUN NSD Number=Sing 8 conj _ Morf=^fleksibilitas_NSD$
+16 aktifitas aktifitas NOUN NSD Number=Sing 15 compound _ Morf=^aktifitas_NSD$
+17 untuk untuk ADP R-- _ 18 case _ Morf=^untuk_R--$
+18 pengembangan kembang NOUN NSD Number=Sing 7 obl _ Morf=^peN+kembang+an_NSD$
+19 program program NOUN NSD Number=Sing 18 compound _ Morf=^program_NSD$
+20 studi studi NOUN NSD Number=Sing 19 compound _ SpaceAfter=No|Morf=^studi_NSD$
+21 . . PUNCT Z-- _ 5 punct _ Morf=^._Z--$
+
+# sent_id = dev-s8
+# text = Masa pendidikan hampir sama dengan SMK pada umumnya yaitu jenjang pendidikan kelas 10, 11 dan 12 dengan sistem pendidikan serupa, dengan praktik kerja industri untuk memperoleh pengalaman kerja dilaksanakan pada tahun keempat.
+# text_en = The period of education is almost the same as that of vocational schools in general, namely grades 10, 11 and 12 with a similar education system, with industrial work practices to gain work experience carried out in the fourth year.
+1 Masa masa NOUN NSD Number=Sing 4 nsubj _ Morf=^masa_NSD$
+2 pendidikan didik NOUN NSD Number=Sing 1 compound _ Morf=^peN+didik+an_NSD$
+3 hampir hampir ADV D-- _ 4 advmod _ Morf=^hampir_D--$
+4 sama sama ADJ ASP _ 0 root _ Morf=^sama_ASP$
+5 dengan dengan ADP R-- _ 6 case _ Morf=^dengan_R--$
+6 SMK smk PROPN X-- _ 4 nmod _ Morf=^smk_X--$
+7 pada pada ADP R-- _ 8 case _ Morf=^pada_R--$
+8 umumnya umumnya ADV B-- _ 6 acl _ Morf=^umumnya_B--$
+9 yaitu yaitu ADP O-- _ 10 case _ Morf=^yaitu_O--$
+10 jenjang jenjang NOUN NSD Number=Sing 4 obl _ Morf=^jenjang_NSD$
+11 pendidikan didik NOUN NSD Number=Sing 10 compound _ Morf=^peN+didik+an_NSD$
+12 kelas kelas NOUN NSD Number=Sing 11 compound _ Morf=^kelas_NSD$
+13 10 10 NUM CC- NumType=Card 12 nummod _ SpaceAfter=No|Morf=^10_CC-$
+14 , , PUNCT Z-- _ 15 punct _ Morf=^,_Z--$
+15 11 11 NUM CC- NumType=Card 13 nummod _ Morf=^11_CC-$
+16 dan dan CCONJ H-- _ 17 cc _ Morf=^dan_H--$
+17 12 12 NUM CC- NumType=Card 13 conj _ Morf=^12_CC-$
+18 dengan dengan ADP R-- _ 19 case _ Morf=^dengan_R--$
+19 sistem sistem NOUN NSD Number=Sing 4 nmod _ Morf=^sistem_NSD$
+20 pendidikan didik NOUN NSD Number=Sing 19 compound _ Morf=^peN+didik+an_NSD$
+21 serupa serupa ADJ ASP _ 19 amod _ SpaceAfter=No|Morf=^serupa_ASP$
+22 , , PUNCT Z-- _ 24 punct _ Morf=^,_Z--$
+23 dengan dengan ADP R-- _ 24 case _ Morf=^dengan_R--$
+24 praktik praktik NOUN NSD Number=Sing 4 nmod _ Morf=^praktik_NSD$
+25 kerja kerja VERB NSD _ 24 compound _ Morf=^kerja_NSD$
+26 industri industri ADJ NSD _ 25 amod _ Morf=^industri_NSD$
+27 untuk untuk SCONJ R-- _ 28 mark _ Morf=^untuk_R--$
+28 memperoleh peroleh VERB VSA Mood=Ind|Voice=Act 24 acl _ Morf=^meN+peroleh_VSA$
+29 pengalaman pengalaman NOUN NSD Number=Sing 28 obj _ Morf=^pengalaman_NSD$
+30 kerja kerja NOUN NSD Number=Sing 29 compound _ Morf=^kerja_NSD$
+31 dilaksanakan laksana VERB VSP Mood=Ind|Voice=Pass 29 ccomp _ Morf=^di+laksana+kan_VSP$
+32 pada pada ADP R-- _ 33 case _ Morf=^pada_R--$
+33 tahun tahun NOUN NSD Number=Sing 31 obl:tmod _ Morf=^tahun_NSD$
+34 keempat keempat ADJ CO- NumType=Ord 33 amod _ SpaceAfter=No|Morf=^ke+empat_CO-$
+35 . . PUNCT Z-- _ 4 punct _ Morf=^._Z--$
+
+# sent_id = dev-s9
+# text = Sutradara film ini Lasse Hallstrรถm dari novel Nicholas Sparks dengan judul yang sama.
+# text_en = The director of the film is Lasse Hallstrรถm from Nicholas Sparks' novel of the same name.
+1 Sutradara sutradara PROPN NSD _ 0 root _ Morf=^sutradara_NSD$
+2 film film NOUN NSD Number=Sing 1 compound _ Morf=^film_NSD$
+3 ini ini DET B-- PronType=Dem 2 det _ Morf=^ini_B--$
+4 Lasse lasse PROPN X-- _ 2 nmod _ Morf=^lasse_X--$
+5 Hallstrรถm hallstrรถm PROPN X-- _ 4 flat:name _ Morf=^hallstrรถm_X--$
+6 dari dari ADP R-- _ 7 case _ Morf=^dari_R--$
+7 novel novel NOUN F-- _ 2 nmod _ Morf=^novel_F--$
+8 Nicholas nicholas PROPN F-- _ 7 nmod _ Morf=^nicholas_F--$
+9 Sparks sparks PROPN X-- _ 8 flat:name _ Morf=^sparks_X--$
+10 dengan dengan ADP R-- _ 11 case _ Morf=^dengan_R--$
+11 judul judul NOUN NSD Number=Sing 7 nmod _ Morf=^judul_NSD$
+12 yang yang PRON S-- PronType=Rel 13 nsubj _ Morf=^yang_S--$
+13 sama sama ADJ ASP _ 11 acl:relcl _ SpaceAfter=No|Morf=^sama_ASP$
+14 . . PUNCT Z-- _ 1 punct _ Morf=^._Z--$
+
+# sent_id = dev-s10
+# text = Bandara ini juga menjadi home - base bagi Skuadron Udara 12 TNI AU. Bandar udara Sultan Syarif Kasim II (SSK. II) Pekanbaru adalah bandara peninggalan Sejarah dari zaman kemerdekaan melawan penjajah Belanda dan Jepang.
+# text_en = This airport is also the home - base for the 12th Air Squadron of the Indonesian Air Force. Sultan Syarif Kasim II Airport (SSK. II) Pekanbaru is a historical heritage airport from the independence era against the Dutch and Japanese colonialists.
+1 Bandara bandara NOUN NSD Number=Sing 4 nsubj _ Morf=^bandara_NSD$
+2 ini ini DET B-- PronType=Dem 1 det _ Morf=^ini_B--$
+3 juga juga ADV D-- _ 4 advmod _ Morf=^juga_D--$
+4 menjadi jadi VERB VSA Mood=Ind|Voice=Act 0 root _ Morf=^meN+jadi_VSA$
+5 home home NOUN F-- _ 4 obj _ Morf=^home_F--$
+6 - - PUNCT Z-- _ 7 punct _ Morf=^-_Z--$
+7 base base NOUN F-- _ 5 compound _ Morf=^base_F--$
+8 bagi bagi ADP R-- _ 9 case _ Morf=^bagi_R--$
+9 Skuadron skuadron PROPN X-- _ 5 nmod _ Morf=^skuadron_X--$
+10 Udara udara PROPN NSD _ 9 flat:name _ Morf=^udara_NSD$
+11 12 12 NUM CC- NumType=Card 12 nummod _ Morf=^12_CC-$
+12 TNI tni NOUN VSA _ 9 compound _ Morf=^tn+i_VSA$
+13 AU au PROPN F-- _ 12 nmod _ SpaceAfter=No|Morf=^au_F--$
+14 . . PUNCT Z-- _ 4 punct _ Morf=^._Z--$
+15 Bandar bandar PROPN NSD _ 28 nsubj _ Morf=^bandar_NSD$
+16 udara udara NOUN NSD Number=Sing 15 compound _ Morf=^udara_NSD$
+17 Sultan sultan PROPN F-- _ 16 nmod _ Morf=^sultan_F--$
+18 Syarif syarif PROPN X-- _ 17 flat:name _ Morf=^syarif_X--$
+19 Kasim kasim NOUN X-- _ 18 compound _ Morf=^kasim_X--$
+20 II ii NUM F-- NumType=Card 19 nummod _ Morf=^ii_F--$
+21 ( ( PUNCT Z-- _ 22 punct _ SpaceAfter=No|Morf=^(_Z--$
+22 SSK ssk PROPN X-- _ 15 appos _ SpaceAfter=No|Morf=^ssk_X--$
+23 . . PUNCT Z-- _ 22 punct _ Morf=^._Z--$
+24 II ii NUM F-- NumType=Card 22 nummod _ SpaceAfter=No|Morf=^ii_F--$
+25 ) ) PUNCT Z-- _ 22 punct _ Morf=^)_Z--$
+26 Pekanbaru pekanbaru PROPN X-- _ 22 flat:name _ Morf=^pekanbaru_X--$
+27 adalah adalah AUX O-- _ 28 cop _ Morf=^adalah_O--$
+28 bandara bandara NOUN NSD Number=Sing 4 dep _ Morf=^bandara_NSD$
+29 peninggalan pentinggalan PROPN NSD _ 28 nmod _ Morf=^peN+tinggal+an_NSD$
+30 Sejarah sejarah PROPN NSD _ 29 flat:name _ Morf=^sejarah_NSD$
+31 dari dari ADP R-- _ 32 case _ Morf=^dari_R--$
+32 zaman zaman NOUN NSD Number=Sing 28 nmod _ Morf=^zaman