This commit is contained in:
2026-04-25 16:51:07 +09:00
parent 4f6ce49704
commit 956c53ba23
24 changed files with 312 additions and 99952 deletions

View File

@@ -7,6 +7,9 @@ import random
import joblib
import numpy as np
import lightgbm as lgb
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error
@@ -82,7 +85,7 @@ def train_model(working_dir: str, data_dir: str):
key = (songno, diff)
if key in feature_map:
features = [safe_float(feature_map[key].get(k, 0)) for k in feature_names]
dataset.append((features, measure))
dataset.append((features, measure, songno, diff))
random.shuffle(dataset)
if len(dataset) < (TRAIN_SIZE + VALID_SIZE):
@@ -91,10 +94,11 @@ def train_model(working_dir: str, data_dir: str):
train_dataset = dataset[:TRAIN_SIZE]
valid_dataset = dataset[TRAIN_SIZE:TRAIN_SIZE + VALID_SIZE]
X_train = np.array([x for x, _ in train_dataset], dtype=np.float32)
y_train = np.array([y for _, y in train_dataset], dtype=np.float32)
X_valid = np.array([x for x, _ in valid_dataset], dtype=np.float32)
y_valid = np.array([y for _, y in valid_dataset], dtype=np.float32)
X_train = np.array([x for x, _, _, _ in train_dataset], dtype=np.float32)
y_train = np.array([y for _, y, _, _ in train_dataset], dtype=np.float32)
X_valid = np.array([x for x, _, _, _ in valid_dataset], dtype=np.float32)
y_valid = np.array([y for _, y, _, _ in valid_dataset], dtype=np.float32)
valid_info = [(s, d) for _, _, s, d in valid_dataset]
print(f"Train Size: {len(X_train)} | Valid Size: {len(X_valid)} | Features: {len(feature_names)}")
@@ -133,6 +137,66 @@ def train_model(working_dir: str, data_dir: str):
print(f"\nMAE: {mae:.4f} | Accuracy (±{ERROR_TOLERANCE}): {accuracy:.4f}")
# =====================================================
# save validate.json
# =====================================================
validate_details = []
for i in range(len(y_valid)):
actual = float(y_valid[i])
predicted = float(pred[i])
songno, diff = valid_info[i]
validate_details.append({
"songno": songno,
"diff": diff,
"actual": actual,
"predicted": predicted,
"error": actual - predicted
})
validate_details.sort(key=lambda x: abs(x["error"]), reverse=True)
validate_result = {
"summary": {
"total_compared": len(y_valid),
"average_absolute_error": float(mae),
"accuracy": float(accuracy),
"timestamp": "now",
"script_used": "train/train_lightgbm.py"
},
"details": validate_details
}
validate_path = os.path.join(working_dir, "validate.json")
with open(validate_path, "w", encoding="utf-8") as f:
json.dump(validate_result, f, indent=2, ensure_ascii=False)
print(f"Validation result saved: {validate_path}")
# =====================================================
# save validate.png
# =====================================================
try:
plt.switch_backend('Agg')
df_plot = pd.DataFrame(validate_details)
df_plot['abs_error'] = df_plot['error'].abs()
df_plot = df_plot.sort_values('abs_error', ascending=False).reset_index(drop=True)
plt.figure(figsize=(12, 6))
sns.scatterplot(data=df_plot, x=df_plot.index, y='abs_error', alpha=0.6, s=20, color='darkorange')
plt.axhline(0.2, color='green', linestyle='--', linewidth=0.8, alpha=0.5, label='Target (0.2)')
plt.axhline(0.5, color='blue', linestyle='--', linewidth=0.8, alpha=0.5)
plt.axhline(1.0, color='red', linestyle='--', linewidth=0.8, alpha=0.5)
plt.ylim(0, max(3.5, df_plot['abs_error'].max() + 0.5))
plt.title(f'Validation Absolute Error - {os.path.basename(working_dir)}', fontsize=14)
plt.xlabel('Samples (Sorted by Error Magnitude)', fontsize=12)
plt.ylabel('Absolute Error', fontsize=12)
plt.grid(True, axis='y', alpha=0.3)
plot_path = os.path.join(working_dir, "validate.png")
plt.savefig(plot_path)
plt.close()
print(f"Validation plot saved: {plot_path}")
except Exception as e:
print(f"[WARN] Failed to create validation plot: {e}")
joblib.dump(model, model_path)
joblib.dump(scaler, scaler_path)
with open(feature_names_path, "w", encoding="utf-8") as f:

View File

@@ -6,6 +6,9 @@ import os
import random
import joblib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from xgboost import XGBRegressor
from sklearn.preprocessing import StandardScaler
@@ -177,7 +180,9 @@ def train_model(
dataset.append((
features,
measure
measure,
songno,
diff
))
# =====================================================
@@ -205,25 +210,29 @@ def train_model(
]
X_train = np.array(
[x for x, _ in train_dataset],
[x for x, _, _, _ in train_dataset],
dtype=np.float32
)
y_train = np.array(
[y for _, y in train_dataset],
[y for _, y, _, _ in train_dataset],
dtype=np.float32
)
X_valid = np.array(
[x for x, _ in valid_dataset],
[x for x, _, _, _ in valid_dataset],
dtype=np.float32
)
y_valid = np.array(
[y for _, y in valid_dataset],
[y for _, y, _, _ in valid_dataset],
dtype=np.float32
)
valid_info = [
(s, d) for _, _, s, d in valid_dataset
]
print(f"Train Size: {len(X_train)}")
print(f"Valid Size: {len(X_valid)}")
print(f"Feature Count: {len(feature_names)}")
@@ -314,6 +323,73 @@ def train_model(
for name, score in pairs:
print(f"{name:25} {score:.6f}")
# =====================================================
# save validate.json
# =====================================================
validate_details = []
for i in range(len(y_valid)):
actual = float(y_valid[i])
predicted = float(pred[i])
songno, diff = valid_info[i]
validate_details.append({
"songno": songno,
"diff": diff,
"actual": actual,
"predicted": predicted,
"error": actual - predicted
})
# 에러 절댓값 기준 정렬
validate_details.sort(key=lambda x: abs(x["error"]), reverse=True)
validate_result = {
"summary": {
"total_compared": len(y_valid),
"average_absolute_error": float(mae),
"accuracy": float(accuracy),
"timestamp": "now",
"script_used": "train/train_xgboost.py"
},
"details": validate_details
}
validate_path = os.path.join(working_dir, "validate.json")
with open(validate_path, "w", encoding="utf-8") as f:
json.dump(validate_result, f, indent=2, ensure_ascii=False)
print(f"Validation result saved: {validate_path}")
# =====================================================
# save validate.png
# =====================================================
try:
plt.switch_backend('Agg') # GUI 없는 환경 대응
df_plot = pd.DataFrame(validate_details)
df_plot['abs_error'] = df_plot['error'].abs()
df_plot = df_plot.sort_values('abs_error', ascending=False).reset_index(drop=True)
plt.figure(figsize=(12, 6))
sns.scatterplot(data=df_plot, x=df_plot.index, y='abs_error', alpha=0.6, s=20, color='darkorange')
plt.axhline(0.2, color='green', linestyle='--', linewidth=0.8, alpha=0.5, label='Target (0.2)')
plt.axhline(0.5, color='blue', linestyle='--', linewidth=0.8, alpha=0.5)
plt.axhline(1.0, color='red', linestyle='--', linewidth=0.8, alpha=0.5)
plt.ylim(0, max(3.5, df_plot['abs_error'].max() + 0.5))
plt.title(f'Validation Absolute Error - {os.path.basename(working_dir)}', fontsize=14)
plt.xlabel('Samples (Sorted by Error Magnitude)', fontsize=12)
plt.ylabel('Absolute Error', fontsize=12)
plt.grid(True, axis='y', alpha=0.3)
plot_path = os.path.join(working_dir, "validate.png")
plt.savefig(plot_path)
plt.close()
print(f"Validation plot saved: {plot_path}")
except Exception as e:
print(f"[WARN] Failed to create validation plot: {e}")
# =====================================================
# save
# =====================================================
@@ -366,4 +442,4 @@ if __name__ == "__main__":
train_model(
args.workingDir,
args.dataDir
)
)