Spaces:
Sleeping
Sleeping
rynfkn commited on
Commit ·
c4814cc
1
Parent(s): c54d9bb
add processor
Browse files
processor/__pycache__/image_processor.cpython-312.pyc
ADDED
|
Binary file (2.56 kB). View file
|
|
|
processor/__pycache__/tabular_processor.cpython-312.pyc
ADDED
|
Binary file (13.8 kB). View file
|
|
|
processor/__pycache__/text_processor.cpython-312.pyc
ADDED
|
Binary file (4.17 kB). View file
|
|
|
processor/image_processor.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from transformers import AutoImageProcessor, AutoModel
|
| 6 |
+
|
| 7 |
+
class ImageProcessor:
|
| 8 |
+
"""Processes image data and extracts embeddings"""
|
| 9 |
+
def __init__(self, model_name):
|
| 10 |
+
self.image_processor = AutoImageProcessor.from_pretrained(model_name)
|
| 11 |
+
self.model = AutoModel.from_pretrained(model_name)
|
| 12 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 13 |
+
self.model.to(self.device)
|
| 14 |
+
self.model.eval()
|
| 15 |
+
|
| 16 |
+
def process(self, image_path):
|
| 17 |
+
"""Extract image embeddings"""
|
| 18 |
+
# Load and process the image
|
| 19 |
+
image = Image.open(image_path).convert('RGB')
|
| 20 |
+
inputs = self.image_processor(image, return_tensors="pt")
|
| 21 |
+
inputs = {k: v.to(self.device) for k, v in inputs.items()}
|
| 22 |
+
|
| 23 |
+
# Extract embeddings
|
| 24 |
+
with torch.no_grad():
|
| 25 |
+
outputs = self.model(**inputs)
|
| 26 |
+
# DINOv2 uses CLS token embedding
|
| 27 |
+
embedding = outputs.last_hidden_state[:, 0, :].cpu().numpy()
|
| 28 |
+
|
| 29 |
+
# Create feature names and dataframe
|
| 30 |
+
columns = [f'img_emb_{i}' for i in range(embedding.shape[1])]
|
| 31 |
+
return pd.DataFrame(embedding, columns=columns)
|
processor/tabular_processor.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import pickle
|
| 3 |
+
from config import SCALER_PATH
|
| 4 |
+
|
| 5 |
+
# Data processing modules
|
| 6 |
+
class TabularProcessor:
|
| 7 |
+
"""Processes tabular data with comprehensive manual encoding"""
|
| 8 |
+
def __init__(self, scaler_path=SCALER_PATH):
|
| 9 |
+
# Load the fitted scaler
|
| 10 |
+
with open(scaler_path, 'rb') as f:
|
| 11 |
+
self.scaler = pickle.load(f)
|
| 12 |
+
|
| 13 |
+
# Define complete manual encoding mappings
|
| 14 |
+
self.encoding_mappings = {
|
| 15 |
+
'Sertifikat': {
|
| 16 |
+
'HGB': 0, 'Hak Pakai': 1, 'Lain-lain': 2,
|
| 17 |
+
'SHM': 3, 'Strata Title': 4
|
| 18 |
+
},
|
| 19 |
+
'Interior': {
|
| 20 |
+
'Full Furnished': 0, 'Semi Furnished': 1,
|
| 21 |
+
'Unfurnished': 2, 'Unknown': 3
|
| 22 |
+
},
|
| 23 |
+
'Orientasi Bangunan': {
|
| 24 |
+
'Barat': 0, 'Barat Daya': 1, 'Barat Laut': 2,
|
| 25 |
+
'Hook': 3, 'Selatan': 4, 'Timur': 5,
|
| 26 |
+
'Timur Laut': 6, 'Unknown': 7, 'Utara': 8
|
| 27 |
+
},
|
| 28 |
+
'City': {
|
| 29 |
+
'Malang': 0, 'Sidoarjo': 1, 'Surabaya': 2
|
| 30 |
+
},
|
| 31 |
+
'District': {
|
| 32 |
+
'Blimbing': 0, 'Dau': 1, 'Dinoyo': 2, 'Dukuh Pakis': 3,
|
| 33 |
+
'Gedangan': 4, 'Griya Shanta': 5, 'Gubeng': 6, 'Jatimulyo': 7,
|
| 34 |
+
'Kedungkandang': 8, 'Kenjeran': 9, 'Lowokwaru': 10,
|
| 35 |
+
'Malang Kota': 11, 'Merjosari': 12, 'Mojolangu': 13,
|
| 36 |
+
'Mulyorejo': 14, 'Pakis': 15, 'Pakuwon City': 16,
|
| 37 |
+
'Pakuwon Indah': 17, 'Permata Jingga': 18, 'Rungkut': 19,
|
| 38 |
+
'Sidoarjo': 20, 'Soekarno Hatta': 21, 'Sukolilo': 22,
|
| 39 |
+
'Sukun': 23, 'Surabaya Kota': 24, 'Tenggilis Mejoyo': 25,
|
| 40 |
+
'Tlogomas': 26, 'Wagir': 27, 'Waru': 28
|
| 41 |
+
},
|
| 42 |
+
'Nama Perumahan': {
|
| 43 |
+
'-': 0, 'Alana Regency Cemandi': 1, 'Amesta living': 2,
|
| 44 |
+
'Anggrek Residence': 3, 'Atrani Residence': 4, 'Babatan Pantai': 5,
|
| 45 |
+
'Barata Jaya': 6, 'Baruk Utara': 7, 'Bratang Binangun': 8,
|
| 46 |
+
'Citra Garden': 9, 'Citra Garden Sidoarjo': 10, 'Citra Harmoni': 11,
|
| 47 |
+
'Citra garden': 12, "D'gardenia City": 13, 'DE IMPERIAL ESTATE': 14,
|
| 48 |
+
'Darmo Permai': 15, 'Dekat Taman Pinang Indah Sidoarjo': 16,
|
| 49 |
+
'Delta Mandala': 17, 'Deltasari Indah': 18, 'Dharma Husada Mas': 19,
|
| 50 |
+
'Dharmahusada Mas': 20, 'Dinoyo': 21, 'Dream Park Regency': 22,
|
| 51 |
+
'Dukuh Kupang': 23, 'Dukuh Kupang Timur soho': 24,
|
| 52 |
+
'Galaxy Bumi Permai': 25, 'Graha Sukolilo Regency': 26,
|
| 53 |
+
'Grand Delta Sari': 27, 'Grand Nature Residence': 28,
|
| 54 |
+
'Green Lake Natural Living': 29, 'Green Orchid Residence': 30,
|
| 55 |
+
'Griya Permata Gedangan': 31, 'Gunung Anyar Baru': 32,
|
| 56 |
+
'Gunung Anyar Utara': 33, 'Istana Mentari': 34, 'Jaya Maspion Permata': 35,
|
| 57 |
+
'Jemursari Regency': 36, 'Jl. Ngagel': 37, 'Joyo Grand': 38,
|
| 58 |
+
'KECIPIR REGENCY': 39, 'Kahuripan Nirwana': 40, 'Kalijudan': 41,
|
| 59 |
+
'Kendalsari': 42, 'Klampis Semolo': 43, 'Koala Regency': 44,
|
| 60 |
+
'Kosagrha': 45, 'Kresna Asri': 46, 'Kupang Baru': 47,
|
| 61 |
+
'Kutisari Indah Utara': 48, 'Kutisari Selatan': 49,
|
| 62 |
+
'Kutisari Selatan XIII': 50, 'Kutisri Indah': 51,
|
| 63 |
+
'Lebak Indah Town House': 52, 'Manyar': 53, 'Manyar Jaya': 54,
|
| 64 |
+
'Manyar Kertoadi': 55, 'Manyar Tirtoyoso': 56, 'Medayu': 57,
|
| 65 |
+
'Medayu Utara': 58, 'Medokan Ayu Tambak': 59,
|
| 66 |
+
'Medokan Sawah Timur Rungkut Surabaya': 60, 'Mojoklangru Kidul': 61,
|
| 67 |
+
'Mulyosari': 62, 'Mulyosari Utara': 63, 'Mutiara City': 64,
|
| 68 |
+
'Ngagel Wasana': 65, 'Nginden': 66, 'Nginden Intan Timur': 67,
|
| 69 |
+
'Nirwana Eksekutif': 68, 'Nirwana Eskekutif': 69, 'Omah View': 70,
|
| 70 |
+
'Pakisjajar': 71, 'Pakuwon City': 72, 'Pantai Mentari': 73,
|
| 71 |
+
'Park Regency Keputih': 74, 'Penjaringan Asri': 75,
|
| 72 |
+
'Penjaringan Sari': 76, 'Pepelegi Indah': 77,
|
| 73 |
+
'Perum Puri Surya Jaya Cluster Valencia Residence Gedangan Sidoarjo': 78,
|
| 74 |
+
'Perum perlian kencana sari': 79, 'Perumahan': 80,
|
| 75 |
+
'Perumahan Bluru Permai Blok C no 11': 81,
|
| 76 |
+
'Perumahan Dharma Husada Indah': 82, 'Perumahan Green Lake': 83,
|
| 77 |
+
'Perumahan Griya Amerta': 84, 'Perumahan Griya Galaxy': 85,
|
| 78 |
+
'Perumahan Griya Pesona Asri': 86, 'Perumahan Istana Safira DAU': 87,
|
| 79 |
+
'Perumahan Medokan Ayu Surabaya': 88, 'Perumahan Mutiara Regency': 89,
|
| 80 |
+
'Perumahan Puri Indah Sidoarjo Kota': 90, 'Perumahan Putra Bangsa': 91,
|
| 81 |
+
'Perumahan Royal Park Regency Rungkut': 92, 'Perumahan Rungkut Mapan': 93,
|
| 82 |
+
'Perumahan Rungkut Menanggal Harapan Surabaya': 94,
|
| 83 |
+
'Perumahan Semolowaru Elok': 95, 'Perumahan Semolowaru Indah 2': 96,
|
| 84 |
+
'Perumahan Taman Pinang Indah': 97,
|
| 85 |
+
'Perumahan Taman Puspa Anggaswangi': 98, 'Perumahan YKP': 99,
|
| 86 |
+
'Perumahan river viuw': 100, 'Pondok Candra': 101,
|
| 87 |
+
'Pondok Candra Indah Waru Rungkut Sidoarjo': 102,
|
| 88 |
+
'Pondok Chandra': 103, 'Pondok Jati': 104, 'Pondok Mutiara': 105,
|
| 89 |
+
'Pondok Tjandra': 106, 'Pondok Tjandra Indah': 107,
|
| 90 |
+
'Prapen indah Kec. Tenggilis Mejoyo Surabaya': 108,
|
| 91 |
+
'Puri Asri Regency': 109, 'Puri Gunung Anyar': 110,
|
| 92 |
+
'Puri Indah Lestari': 111, 'Puri Surya Jaya': 112,
|
| 93 |
+
'Putra Bangsa': 113, 'Rifera Townhouse': 114, 'Rungkut Asri': 115,
|
| 94 |
+
'Rungkut Asri Utara': 116, 'Rungkut Harapan': 117,
|
| 95 |
+
'Rungkut Mapan': 118, 'Rungkut Menanggal': 119,
|
| 96 |
+
'Saphire residence': 120, 'Semampir Tengah': 121,
|
| 97 |
+
'Semolowaru Elok': 122, 'Sentra Point': 123,
|
| 98 |
+
'Simpang Darmo Permai Selatan': 124, 'Springville Residence': 125,
|
| 99 |
+
'Sukolilo Dian Regency 2': 126, 'Sunan Kalijaga': 127,
|
| 100 |
+
'Sutorejo Prima': 128, 'Sutorejo Prima Indah': 129,
|
| 101 |
+
'Sutorejo Timur': 130, 'Sutorejo Utara': 131,
|
| 102 |
+
'TENGGILIS MEJOYO SELATAN': 132, 'Taman Pondok Legi 4': 133,
|
| 103 |
+
'Taman Rivera Regency': 134, 'Taman pondok indah': 135,
|
| 104 |
+
'Taman pondok legi': 136, 'Tambak Medokan Ayu': 137,
|
| 105 |
+
'Tambak medokan ayu VI C': 138, 'Tenggilis Mejoyo': 139,
|
| 106 |
+
'Unknown': 140, 'Valencia garden , gedangan': 141, 'Vbt': 142,
|
| 107 |
+
'Vila bukit tidar': 143, 'Villa Kalijudan Indah': 144,
|
| 108 |
+
'Wellington Park Residence': 145, 'Wisata Semanggi Mangrove': 146,
|
| 109 |
+
'Wisma Mukti': 147, 'Wisma Permai': 148, 'Wisma Permai Waru': 149,
|
| 110 |
+
'Wisma Tropodo': 150, 'Wisma permai tengah': 151,
|
| 111 |
+
'YKP Medokan Asri Rungkut Surabaya Timur': 152,
|
| 112 |
+
'klampis semolo': 153, 'kompleks mojoarum': 154,
|
| 113 |
+
'perum jaya maspion permata': 155,
|
| 114 |
+
'perum taman pinang indah sidoarjo': 156,
|
| 115 |
+
'regency one east point': 157, 'sukolilo Dian Regency': 158
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
# Default values for unknown categories
|
| 120 |
+
self.default_values = {
|
| 121 |
+
'Sertifikat': 3, # 'Lain-lain'
|
| 122 |
+
'Interior': 3, # 'Unknown'
|
| 123 |
+
'Orientasi Bangunan': 7, # 'Unknown'
|
| 124 |
+
'City': 0, # 'Malang'
|
| 125 |
+
'District': 0, # 'Blimbing'
|
| 126 |
+
'Nama Perumahan': 140 # 'Unknown'
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
# Median/mode values for imputation
|
| 130 |
+
self.imputation_values = {
|
| 131 |
+
'numerical': {
|
| 132 |
+
'Luas Bangunan': 138.0, # Will be set during training
|
| 133 |
+
'Luas Tanah': 120.0,
|
| 134 |
+
'Daya Listrik': 2200.0,
|
| 135 |
+
'Tahun Dibangun': 2021,
|
| 136 |
+
'Latitude' : -7.294603,
|
| 137 |
+
'Longitude' : 112.769703
|
| 138 |
+
|
| 139 |
+
},
|
| 140 |
+
'categorical': {
|
| 141 |
+
'Kamar Tidur': 3,
|
| 142 |
+
'Kamar Mandi': 2,
|
| 143 |
+
'Carpots': 1,
|
| 144 |
+
'Garasi': 1,
|
| 145 |
+
'Jumlah Lantai': 2
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
def process(self, data):
|
| 150 |
+
"""Process tabular data with comprehensive preprocessing"""
|
| 151 |
+
# Convert to DataFrame if needed
|
| 152 |
+
if not isinstance(data, pd.DataFrame):
|
| 153 |
+
data = pd.DataFrame([data])
|
| 154 |
+
|
| 155 |
+
# Ensure all expected columns are present
|
| 156 |
+
expected_cols = [
|
| 157 |
+
'District', 'City', 'Longitude', 'Latitude', 'Garasi',
|
| 158 |
+
'Tahun Dibangun', 'Orientasi Bangunan', 'Jumlah Lantai',
|
| 159 |
+
'Interior', 'Daya Listrik', 'Carpots', 'Sertifikat',
|
| 160 |
+
'Nama Perumahan', 'Luas Bangunan', 'Luas Tanah',
|
| 161 |
+
'Kamar Mandi', 'Kamar Tidur'
|
| 162 |
+
]
|
| 163 |
+
|
| 164 |
+
for col in expected_cols:
|
| 165 |
+
if col not in data.columns:
|
| 166 |
+
raise ValueError(f"Missing required column: {col}")
|
| 167 |
+
|
| 168 |
+
# Apply preprocessing steps
|
| 169 |
+
self._handle_missing_values(data)
|
| 170 |
+
self._apply_manual_encoding(data)
|
| 171 |
+
self._handle_special_cases(data)
|
| 172 |
+
|
| 173 |
+
# Apply the scaler
|
| 174 |
+
scaled_data = self.scaler.transform(data)
|
| 175 |
+
return pd.DataFrame(scaled_data, columns=data.columns)
|
| 176 |
+
|
| 177 |
+
def _handle_missing_values(self, data):
|
| 178 |
+
"""Handle missing values according to training logic"""
|
| 179 |
+
# Numerical features
|
| 180 |
+
for col in self.imputation_values['numerical']:
|
| 181 |
+
if col in data.columns and data[col].isna().any():
|
| 182 |
+
data[col].fillna(self.imputation_values['numerical'][col], inplace=True)
|
| 183 |
+
|
| 184 |
+
# Categorical features
|
| 185 |
+
for col in self.imputation_values['categorical']:
|
| 186 |
+
if col in data.columns and data[col].isna().any():
|
| 187 |
+
data[col].fillna(self.imputation_values['categorical'][col], inplace=True)
|
| 188 |
+
|
| 189 |
+
# Special cases
|
| 190 |
+
for col in ['Interior', 'Orientasi Bangunan', 'Nama Perumahan']:
|
| 191 |
+
if col in data.columns and data[col].isna().any():
|
| 192 |
+
data[col].fillna("Unknown", inplace=True)
|
| 193 |
+
|
| 194 |
+
def _apply_manual_encoding(self, data):
|
| 195 |
+
"""Apply comprehensive manual encoding"""
|
| 196 |
+
for col, mapping in self.encoding_mappings.items():
|
| 197 |
+
if col in data.columns:
|
| 198 |
+
# Convert to string type for consistent mapping
|
| 199 |
+
data[col] = data[col].astype(str)
|
| 200 |
+
# Handle unknown categories
|
| 201 |
+
data[col] = data[col].apply(
|
| 202 |
+
lambda x: mapping.get(x, self.default_values[col]))
|
| 203 |
+
|
| 204 |
+
def _handle_special_cases(self, data):
|
| 205 |
+
"""Handle special cases in the data"""
|
| 206 |
+
# Orientation special values
|
| 207 |
+
if 'Orientasi Bangunan' in data.columns:
|
| 208 |
+
orientation_replace = {'1': 'Unknown', '2': 'Unknown',
|
| 209 |
+
'3': 'Unknown', '4': 'Unknown'}
|
| 210 |
+
data['Orientasi Bangunan'] = data['Orientasi Bangunan'].replace(
|
| 211 |
+
orientation_replace)
|
| 212 |
+
data['Orientasi Bangunan'] = data['Orientasi Bangunan'].map(
|
| 213 |
+
self.encoding_mappings['Orientasi Bangunan'])
|
| 214 |
+
|
| 215 |
+
# Fill any remaining NA values
|
| 216 |
+
for col in data.columns:
|
| 217 |
+
if data[col].isna().any():
|
| 218 |
+
if col in self.default_values:
|
| 219 |
+
data[col].fillna(self.default_values[col], inplace=True)
|
| 220 |
+
elif data[col].dtype.kind in 'biufc': # numeric
|
| 221 |
+
data[col].fillna(0, inplace=True)
|
| 222 |
+
else:
|
| 223 |
+
data[col].fillna("Unknown", inplace=True)
|
processor/text_processor.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import nltk
|
| 6 |
+
from nltk.tokenize import word_tokenize
|
| 7 |
+
from nltk.corpus import stopwords
|
| 8 |
+
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
|
| 9 |
+
from transformers import AutoTokenizer, AutoModel
|
| 10 |
+
nltk.download('punkt')
|
| 11 |
+
nltk.download('stopwords')
|
| 12 |
+
|
| 13 |
+
class TextProcessor:
|
| 14 |
+
"""Processes text data and extracts embeddings"""
|
| 15 |
+
def __init__(self, model_name):
|
| 16 |
+
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 17 |
+
self.model = AutoModel.from_pretrained(model_name)
|
| 18 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 19 |
+
self.model.to(self.device)
|
| 20 |
+
self.model.eval()
|
| 21 |
+
|
| 22 |
+
# Initialize Sastrawi stemmer
|
| 23 |
+
factory = StemmerFactory()
|
| 24 |
+
self.stemmer = factory.create_stemmer()
|
| 25 |
+
|
| 26 |
+
def preprocess_text(self, text):
|
| 27 |
+
"""Apply text preprocessing"""
|
| 28 |
+
text = text.lower()
|
| 29 |
+
text = re.sub(r'\d+', '', text)
|
| 30 |
+
text = re.sub(r'[^\w\s]', ' ', text)
|
| 31 |
+
text = re.sub(r'\s+', ' ', text).strip()
|
| 32 |
+
|
| 33 |
+
# Tokenize and remove stopwords
|
| 34 |
+
tokens = word_tokenize(text)
|
| 35 |
+
indonesian_stopwords = set(stopwords.words('indonesian'))
|
| 36 |
+
tokens = [word for word in tokens if word not in indonesian_stopwords]
|
| 37 |
+
|
| 38 |
+
# Apply stemming
|
| 39 |
+
tokens = [self.stemmer.stem(word) for word in tokens]
|
| 40 |
+
return ' '.join(tokens)
|
| 41 |
+
|
| 42 |
+
def process(self, texts):
|
| 43 |
+
"""Extract text embeddings"""
|
| 44 |
+
if isinstance(texts, str):
|
| 45 |
+
texts = [texts]
|
| 46 |
+
|
| 47 |
+
# Preprocess texts
|
| 48 |
+
processed_texts = [self.preprocess_text(text) for text in texts]
|
| 49 |
+
|
| 50 |
+
# Add E5 prefix
|
| 51 |
+
batch = ["passage: " + text for text in processed_texts]
|
| 52 |
+
|
| 53 |
+
# Tokenize and get embeddings
|
| 54 |
+
inputs = self.tokenizer(
|
| 55 |
+
batch,
|
| 56 |
+
return_tensors="pt",
|
| 57 |
+
truncation=True,
|
| 58 |
+
padding=True,
|
| 59 |
+
max_length=512
|
| 60 |
+
).to(self.device)
|
| 61 |
+
|
| 62 |
+
with torch.no_grad():
|
| 63 |
+
outputs = self.model(**inputs)
|
| 64 |
+
# E5 uses CLS token embedding
|
| 65 |
+
embeddings = outputs.last_hidden_state[:, 0].cpu().numpy()
|
| 66 |
+
|
| 67 |
+
# Normalize embeddings
|
| 68 |
+
embeddings = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
|
| 69 |
+
|
| 70 |
+
# Create feature names and dataframe
|
| 71 |
+
columns = [f'e5_{i}' for i in range(embeddings.shape[1])]
|
| 72 |
+
return pd.DataFrame(embeddings, columns=columns)
|