TIF_E41253316/machineLearning/app.py

264 lines
10 KiB
Python

from flask import Flask, request, jsonify
import pandas as pd
import numpy as np
import os
import joblib
import warnings
from sklearn.exceptions import InconsistentVersionWarning
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
warnings.filterwarnings("ignore", category=InconsistentVersionWarning)
MODEL_PATH = 'rekomendasi_rf_model.joblib'
ENCODER_PATH = 'rekomendasi_labelencoders.joblib'
TARGET_ENCODER_PATH = 'rekomendasi_targetencoder.joblib'
DATASET_PATH = 'dataset/data1.csv'
app = Flask(__name__)
model = None
le_dict = None
target_encoder = None
df = None
fitur_cols = [
"Intensitas Cahaya (Kategori)",
"Intensitas Air (Kategori)",
"Cara Penyiraman (Frekuensi)",
"Konsep Utama Taman",
"Ukuran Taman yang Cocok (Kategori)",
"Lokasi Penanaman (Indoor/Outdoor)"
]
target_col = "Tipe Utama Tanaman"
nama_tanaman_col = "Nama Tanaman"
def load_model_data():
global model, le_dict, target_encoder, df
model = joblib.load(MODEL_PATH)
le_dict = joblib.load(ENCODER_PATH)
target_encoder = joblib.load(TARGET_ENCODER_PATH)
df = pd.read_csv(DATASET_PATH, sep=';')
for col in df.columns:
df[col] = df[col].astype(str).str.strip()
print("Model dan dataset berhasil dimuat ke memory.")
try:
load_model_data()
except Exception as e:
print(f"Gagal memuat model awal: {e}")
@app.route('/retrain', methods=['POST'])
def retrain_model():
global model, le_dict, target_encoder, df
try:
if 'file' not in request.files:
return jsonify({"success": False, "error": "File CSV tidak ditemukan."}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"success": False, "error": "File CSV kosong."}), 400
os.makedirs('dataset', exist_ok=True)
try:
new_data = pd.read_csv(file, sep=';')
except Exception as e:
return jsonify({"success": False, "error": f"Gagal membaca file CSV: {str(e)}"}), 400
for col in new_data.columns:
new_data[col] = new_data[col].astype(str).str.strip()
print("Dataset baru diterima.")
print(f"Jumlah data baru: {len(new_data)}")
# Sertakan nama_tanaman_col ke dalam kolom yang wajib ada
required_columns = fitur_cols + [target_col, nama_tanaman_col]
missing_columns = [col for col in required_columns if col not in new_data.columns]
if missing_columns:
return jsonify({"success": False, "error": "Kolom dataset tidak lengkap.", "kolom_hilang": missing_columns}), 400
new_data = new_data[required_columns].copy()
new_data = new_data.dropna(subset=required_columns).copy()
if len(new_data) < 1:
return jsonify({"success": False, "error": "Dataset baru tidak memiliki data yang valid."}), 400
if os.path.exists(DATASET_PATH):
try:
old_data = pd.read_csv(DATASET_PATH, sep=';')
for col in old_data.columns:
old_data[col] = old_data[col].astype(str).str.strip()
missing_old_columns = [col for col in required_columns if col not in old_data.columns]
if missing_old_columns:
return jsonify({"success": False, "error": "Dataset lama tidak memiliki kolom yang diperlukan.", "kolom_hilang": missing_old_columns}), 500
old_data = old_data[required_columns].copy()
print(f"Jumlah data lama: {len(old_data)}")
except Exception as e:
return jsonify({"success": False, "error": f"Gagal membaca dataset lama: {str(e)}"}), 500
else:
old_data = pd.DataFrame(columns=required_columns)
print("Dataset lama tidak ditemukan.")
combined_df = pd.concat([old_data, new_data], ignore_index=True)
before_duplicate = len(combined_df)
combined_df = combined_df.drop_duplicates(subset=required_columns, keep='first').reset_index(drop=True)
after_duplicate = len(combined_df)
duplicate_count = before_duplicate - after_duplicate
if len(combined_df) < 2:
return jsonify({"success": False, "error": "Dataset minimal harus memiliki 2 data."}), 400
training_df = combined_df.copy()
X = training_df[fitur_cols].copy()
y = training_df[target_col].copy()
new_le_dict = {}
for col in fitur_cols:
le = LabelEncoder()
X[col] = le.fit_transform(X[col].astype(str))
new_le_dict[col] = le
new_target_encoder = LabelEncoder()
y_encoded = new_target_encoder.fit_transform(y.astype(str))
jumlah_tipe = len(new_target_encoder.classes_)
class_counts = pd.Series(y_encoded).value_counts()
can_stratify = len(y_encoded) >= 10 and class_counts.min() >= 2 and int(np.ceil(len(y_encoded) * 0.2)) >= jumlah_tipe
if can_stratify:
X_train, X_test, y_train, y_test = train_test_split(X, y_encoded, test_size=0.2, random_state=42, stratify=y_encoded)
mode_training = "training_testing"
else:
X_train = X
y_train = y_encoded
X_test = pd.DataFrame()
y_test = np.array([])
mode_training = "seluruh_data"
new_model = RandomForestClassifier(n_estimators=100, max_depth=10, min_samples_split=2, random_state=42)
new_model.fit(X_train, y_train)
accuracy_percent = None
if mode_training == "training_testing":
y_pred = new_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
accuracy_percent = round(accuracy * 100, 2)
model_path_temp = MODEL_PATH + '.tmp'
encoder_path_temp = ENCODER_PATH + '.tmp'
target_encoder_path_temp = TARGET_ENCODER_PATH + '.tmp'
dataset_path_temp = DATASET_PATH + '.tmp'
joblib.dump(new_model, model_path_temp)
joblib.dump(new_le_dict, encoder_path_temp)
joblib.dump(new_target_encoder, target_encoder_path_temp)
training_df.to_csv(dataset_path_temp, sep=';', index=False)
os.replace(model_path_temp, MODEL_PATH)
os.replace(encoder_path_temp, ENCODER_PATH)
os.replace(target_encoder_path_temp, TARGET_ENCODER_PATH)
try:
os.replace(dataset_path_temp, DATASET_PATH)
except PermissionError:
if os.path.exists(dataset_path_temp):
try:
os.remove(dataset_path_temp)
except Exception:
pass
return jsonify({"success": False, "error": f"Dataset berhasil diproses, tetapi {DATASET_PATH} tidak dapat diganti karena sedang digunakan."}), 500
model = new_model
le_dict = new_le_dict
target_encoder = new_target_encoder
df = training_df
return jsonify({
"success": True,
"message": "Dataset lama + baru berhasil digabung dan model berhasil dilatih ulang.",
"data_lama": int(len(old_data)),
"data_baru": int(len(new_data)),
"data_duplikat": int(duplicate_count),
"total_data": int(len(training_df)),
"data_training": int(len(X_train)),
"data_testing": int(len(X_test)),
"jumlah_tipe": int(jumlah_tipe),
"akurasi": accuracy_percent,
"mode_training": mode_training
}), 200
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/predict', methods=['POST'])
def predict():
global model, le_dict, target_encoder, df
if model is None:
return jsonify({"error": "Model belum tersedia."}), 500
data = request.get_json(silent=True)
if not data:
return jsonify({"error": "Format request harus berupa JSON."}), 400
input_user_text = {}
for col in fitur_cols:
value = data.get(col, "")
input_user_text[col] = str(value).strip()
for col in fitur_cols:
if input_user_text[col] == "":
return jsonify({"error": f"Input untuk '{col}' tidak boleh kosong."}), 400
if input_user_text[col] not in le_dict[col].classes_:
return jsonify({"error": f"Input '{input_user_text[col]}' untuk '{col}' tidak valid.", "pilihan": list(le_dict[col].classes_)}), 400
input_encoded = {}
try:
for col in fitur_cols:
input_encoded[col] = le_dict[col].transform([input_user_text[col]])[0]
except Exception as e:
return jsonify({"error": f"Gagal encoding: {str(e)}"}), 400
input_df = pd.DataFrame([input_encoded])
try:
proba = model.predict_proba(input_df)[0]
except Exception as e:
return jsonify({"error": f"Gagal prediksi model: {str(e)}"}), 500
jumlah_rekomendasi = min(3, len(proba))
top3_index = proba.argsort()[-jumlah_rekomendasi:][::-1]
top_types = []
for idx in top3_index:
tipe = target_encoder.inverse_transform([idx])[0]
persen = round(float(proba[idx]) * 100, 2)
top_types.append({"nama": tipe, "persen": persen})
rekomendasi = []
for tipe_info in top_types:
tipe = tipe_info["nama"]
tanaman_df = df[df[target_col] == tipe].copy()
skor = []
for _, row in tanaman_df.iterrows():
cocok = sum(1 for col in fitur_cols if row[col] == input_user_text[col])
skor.append(cocok)
tanaman_df["Skor"] = skor
tanaman_df = tanaman_df.sort_values(by="Skor", ascending=False)
top5 = tanaman_df.head(5)
daftar_tanaman = []
for _, row in top5.iterrows():
daftar_tanaman.append({"nama": row[nama_tanaman_col], "skor": int(row["Skor"])})
rekomendasi.append({"tipe": tipe, "persen": tipe_info["persen"], "tanaman": daftar_tanaman})
return jsonify({"rekomendasi": rekomendasi})
if __name__ == '__main__':
port = int(os.environ.get("PORT", 5000))
app.run(host='0.0.0.0', port=port)