knnopt 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- knnopt/__init__.py +0 -0
- knnopt/cli_predict.py +104 -0
- knnopt/cli_train.py +64 -0
- knnopt/cli_validate.py +203 -0
- knnopt/knnopt.py +461 -0
- knnopt/utils/__init__.py +0 -0
- knnopt/utils/combine_csv.py +50 -0
- knnopt/utils/csv_diff.py +121 -0
- knnopt/utils/data_processing.py +179 -0
- knnopt/utils/get_dataset_cls.py +80 -0
- knnopt/utils/get_dataset_reg.py +88 -0
- knnopt/utils/knnopt_example.py +160 -0
- knnopt/utils/pearson_coeffs.py +66 -0
- knnopt/utils/split_csv.py +75 -0
- knnopt/utils/split_csv_by_row.py +59 -0
- knnopt/utils/train_validate_split.py +45 -0
- knnopt-1.0.0.dist-info/METADATA +22 -0
- knnopt-1.0.0.dist-info/RECORD +22 -0
- knnopt-1.0.0.dist-info/WHEEL +5 -0
- knnopt-1.0.0.dist-info/entry_points.txt +14 -0
- knnopt-1.0.0.dist-info/licenses/LICENSE +21 -0
- knnopt-1.0.0.dist-info/top_level.txt +1 -0
knnopt/__init__.py
ADDED
|
File without changes
|
knnopt/cli_predict.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# knnopt/cli_predict.py
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
import joblib
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
description="Predict using trained KNN Optimizer (Regression or Classification)"
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"model_type",
|
|
15
|
+
nargs="?",
|
|
16
|
+
default="reg",
|
|
17
|
+
choices=["reg", "cls"],
|
|
18
|
+
help="Model type: reg=regression (default), cls=classification"
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"model_dir_or_file",
|
|
22
|
+
nargs="?",
|
|
23
|
+
default=".",
|
|
24
|
+
help="Model directory or full input CSV path (default: current directory)"
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-o", "--output",
|
|
28
|
+
default="predictions.csv",
|
|
29
|
+
help="Output CSV file name (default: predictions.csv)"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
|
|
34
|
+
# Determine model_dir and input_csv
|
|
35
|
+
if args.model_dir_or_file.lower().endswith(".csv"):
|
|
36
|
+
input_csv = os.path.abspath(args.model_dir_or_file)
|
|
37
|
+
model_dir = os.path.dirname(input_csv)
|
|
38
|
+
else:
|
|
39
|
+
model_dir = os.path.abspath(args.model_dir_or_file)
|
|
40
|
+
input_csv = os.path.join(model_dir, "input.csv")
|
|
41
|
+
|
|
42
|
+
# Validate paths
|
|
43
|
+
if not os.path.exists(model_dir):
|
|
44
|
+
print(f"❌ Error: Model directory '{model_dir}' not found.")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
if not os.path.exists(input_csv):
|
|
47
|
+
print(f"❌ Error: Input CSV '{input_csv}' not found.")
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
# Load artifacts
|
|
51
|
+
scaler_path = os.path.join(model_dir, "scaler.pkl")
|
|
52
|
+
weights_path = os.path.join(model_dir, "weights.npy")
|
|
53
|
+
models_path = os.path.join(model_dir, "knn_models.pkl")
|
|
54
|
+
|
|
55
|
+
if not all(os.path.exists(p) for p in [scaler_path, weights_path, models_path]):
|
|
56
|
+
print("❌ Error: Missing model artifacts in model_dir.")
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
scaler = joblib.load(scaler_path)
|
|
60
|
+
W = np.load(weights_path)
|
|
61
|
+
models = joblib.load(models_path)
|
|
62
|
+
|
|
63
|
+
# Load input data
|
|
64
|
+
df = pd.read_csv(input_csv)
|
|
65
|
+
|
|
66
|
+
# Ensure all feature columns exist
|
|
67
|
+
features = scaler.feature_names_in_ if hasattr(scaler, "feature_names_in_") else list(df.columns)
|
|
68
|
+
missing_features = [f for f in features if f not in df.columns]
|
|
69
|
+
if missing_features:
|
|
70
|
+
print(f"❌ Error: Missing feature columns in input CSV: {missing_features}")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
# Scale features
|
|
74
|
+
X_scaled = scaler.transform(df[features])
|
|
75
|
+
|
|
76
|
+
# Predict for each target/class
|
|
77
|
+
predictions = {}
|
|
78
|
+
for idx, target_name in enumerate(models.keys()):
|
|
79
|
+
X_weighted = X_scaled * W[:, idx]
|
|
80
|
+
y_pred = models[target_name].predict(X_weighted)
|
|
81
|
+
|
|
82
|
+
if args.model_type == "reg":
|
|
83
|
+
predictions[target_name] = np.round(y_pred, 4)
|
|
84
|
+
else:
|
|
85
|
+
predictions[target_name] = y_pred # classification labels
|
|
86
|
+
|
|
87
|
+
# Save predictions.csv (just predictions)
|
|
88
|
+
pred_df = pd.DataFrame(predictions)
|
|
89
|
+
pred_csv_path = os.path.join(model_dir, args.output)
|
|
90
|
+
pred_df.to_csv(pred_csv_path, index=False, encoding="utf-8")
|
|
91
|
+
|
|
92
|
+
# Save predictions_full.csv (input + predictions)
|
|
93
|
+
pred_full_df = df.copy()
|
|
94
|
+
for col, vals in predictions.items():
|
|
95
|
+
pred_full_df[col + "_pred"] = vals
|
|
96
|
+
pred_full_csv_path = os.path.join(model_dir, "predictions_full.csv")
|
|
97
|
+
pred_full_df.to_csv(pred_full_csv_path, index=False, encoding="utf-8")
|
|
98
|
+
|
|
99
|
+
print(f"✅ Predictions saved to {pred_csv_path}")
|
|
100
|
+
print(f"✅ Full predictions saved to {pred_full_csv_path}")
|
|
101
|
+
print(pred_df)
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
knnopt/cli_train.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
from tabulate import tabulate
|
|
7
|
+
from knnopt.knnopt import KnnOpt
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
parser = argparse.ArgumentParser(description="Train KNN Optimizer (Regression or Classification)")
|
|
11
|
+
parser.add_argument("csv", help="Path to CSV dataset file")
|
|
12
|
+
parser.add_argument("-f", "--features", type=str, required=True,
|
|
13
|
+
help="Comma-separated feature column names")
|
|
14
|
+
parser.add_argument("-t", "--targets", type=str, required=True,
|
|
15
|
+
help="Comma-separated target column names")
|
|
16
|
+
parser.add_argument("--model-type", choices=["reg", "cls"], default="reg")
|
|
17
|
+
parser.add_argument("-n", "--n_neighbors", type=int, default=5)
|
|
18
|
+
parser.add_argument("-w", "--weights", choices=["uniform", "distance"], default="distance")
|
|
19
|
+
parser.add_argument("-m", "--metric", choices=["euclidean", "manhattan"], default="euclidean")
|
|
20
|
+
parser.add_argument("--optimizer", default="LN_COBYLA")
|
|
21
|
+
# ✅ Add test_size argument
|
|
22
|
+
parser.add_argument("--test-size", type=float, default=0.3,
|
|
23
|
+
help="Fraction of dataset to use for testing (default: 0.3)")
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
|
|
26
|
+
# Validate CSV file
|
|
27
|
+
if not args.csv.lower().endswith(".csv"):
|
|
28
|
+
print("Error: Must be a CSV file.")
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
if not os.path.exists(args.csv):
|
|
31
|
+
print(f"Error: File '{args.csv}' not found.")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
# Split features and targets into lists
|
|
35
|
+
features_list = [f.strip() for f in args.features.split(",") if f.strip()]
|
|
36
|
+
targets_list = [t.strip() for t in args.targets.split(",") if t.strip()]
|
|
37
|
+
print('features_list:', features_list)
|
|
38
|
+
|
|
39
|
+
# Load CSV
|
|
40
|
+
df = pd.read_csv(args.csv)
|
|
41
|
+
|
|
42
|
+
# Validate columns
|
|
43
|
+
for col in features_list + targets_list:
|
|
44
|
+
if col not in df.columns:
|
|
45
|
+
print(f"Error: Column '{col}' not found in CSV.")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
# Train model
|
|
49
|
+
model = KnnOpt(
|
|
50
|
+
features=features_list,
|
|
51
|
+
targets=targets_list,
|
|
52
|
+
model_type=args.model_type,
|
|
53
|
+
n_neighbors=args.n_neighbors,
|
|
54
|
+
weights=args.weights,
|
|
55
|
+
metric=args.metric,
|
|
56
|
+
optimizer=args.optimizer
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
result = model.train(args.csv, test_size=args.test_size)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
main()
|
knnopt/cli_validate.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import joblib
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(
|
|
10
|
+
description="Validate trained KNN Optimizer model with relative difference (%)"
|
|
11
|
+
)
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"model_type",
|
|
14
|
+
choices=["reg", "cls"],
|
|
15
|
+
default="reg",
|
|
16
|
+
help="Model type: reg=regression, cls=classification"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"model_dir",
|
|
20
|
+
help="Directory containing trained model artifacts"
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"input_csv",
|
|
24
|
+
help="CSV file containing features and target columns"
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-o", "--output",
|
|
28
|
+
default="validation_results.csv",
|
|
29
|
+
help="Output CSV file name for results (default: validation_results.csv)"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
|
|
34
|
+
# Validate paths
|
|
35
|
+
model_dir = os.path.abspath(args.model_dir)
|
|
36
|
+
input_csv = os.path.abspath(args.input_csv)
|
|
37
|
+
|
|
38
|
+
if not os.path.exists(model_dir):
|
|
39
|
+
print(f"❌ Error: Model directory '{model_dir}' not found.")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
if not os.path.exists(input_csv):
|
|
42
|
+
print(f"❌ Error: Input CSV '{input_csv}' not found.")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
# Load artifacts
|
|
46
|
+
scaler_path = os.path.join(model_dir, "scaler.pkl")
|
|
47
|
+
weights_path = os.path.join(model_dir, "weights.npy")
|
|
48
|
+
models_path = os.path.join(model_dir, "knn_models.pkl")
|
|
49
|
+
|
|
50
|
+
if not all(os.path.exists(p) for p in [scaler_path, weights_path, models_path]):
|
|
51
|
+
print("❌ Error: Missing model artifacts in model_dir.")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
scaler = joblib.load(scaler_path)
|
|
55
|
+
W = np.load(weights_path)
|
|
56
|
+
models = joblib.load(models_path)
|
|
57
|
+
|
|
58
|
+
# Load input data
|
|
59
|
+
df = pd.read_csv(input_csv)
|
|
60
|
+
|
|
61
|
+
# Ensure all feature columns exist
|
|
62
|
+
features = scaler.feature_names_in_ if hasattr(scaler, "feature_names_in_") else list(df.columns)
|
|
63
|
+
missing_features = [f for f in features if f not in df.columns]
|
|
64
|
+
if missing_features:
|
|
65
|
+
print(f"❌ Error: Missing feature columns in input CSV: {missing_features}")
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
# Scale features
|
|
69
|
+
X_scaled = scaler.transform(df[features])
|
|
70
|
+
|
|
71
|
+
# Predict for each target/class
|
|
72
|
+
predictions = {}
|
|
73
|
+
for idx, target_name in enumerate(models.keys()):
|
|
74
|
+
X_weighted = X_scaled * W[:, idx]
|
|
75
|
+
y_pred = models[target_name].predict(X_weighted)
|
|
76
|
+
|
|
77
|
+
if args.model_type == "reg":
|
|
78
|
+
predictions[target_name] = np.round(y_pred, 4)
|
|
79
|
+
else:
|
|
80
|
+
predictions[target_name] = y_pred # classification labels
|
|
81
|
+
|
|
82
|
+
# Compute relative difference for regression
|
|
83
|
+
results_data = []
|
|
84
|
+
if args.model_type == "reg":
|
|
85
|
+
for target_name in models.keys():
|
|
86
|
+
if target_name not in df.columns:
|
|
87
|
+
print(f"Error: Target column '{target_name}' not found in input CSV.")
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
|
|
90
|
+
y_true = df[target_name].values
|
|
91
|
+
y_pred = predictions[target_name]
|
|
92
|
+
|
|
93
|
+
# Mean and StdDev of true values
|
|
94
|
+
mean_true = np.mean(y_true)
|
|
95
|
+
std_true = np.std(y_true, ddof=0) # population stddev
|
|
96
|
+
|
|
97
|
+
# Average absolute difference
|
|
98
|
+
avg_diff = np.mean(np.abs(y_true - y_pred))
|
|
99
|
+
|
|
100
|
+
# Normalization choice: max(abs(mean), stddev)
|
|
101
|
+
norm_value = max(abs(mean_true), std_true)
|
|
102
|
+
norm_type = "mean" if abs(mean_true) >= std_true else "stddev"
|
|
103
|
+
|
|
104
|
+
# Relative difference in percentage
|
|
105
|
+
rel_diff_pct = (avg_diff / norm_value) * 100
|
|
106
|
+
|
|
107
|
+
results_data.append({
|
|
108
|
+
"Target": target_name,
|
|
109
|
+
"Mean": round(mean_true,4),
|
|
110
|
+
"StdDev": round(std_true),
|
|
111
|
+
"NormType": norm_type,
|
|
112
|
+
"NormValue": round(norm_value,4),
|
|
113
|
+
"AvgDiff": round(avg_diff,4),
|
|
114
|
+
"RelDiff%": round(rel_diff_pct,4)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
# Column-average of relative difference
|
|
118
|
+
avg_rel_diff_pct = np.mean([r["RelDiff%"] for r in results_data])
|
|
119
|
+
results_data.append({
|
|
120
|
+
"Target": "Average",
|
|
121
|
+
"Mean": np.nan,
|
|
122
|
+
"StdDev": np.nan,
|
|
123
|
+
"NormType": "",
|
|
124
|
+
"NormValue": np.nan,
|
|
125
|
+
"AvgDiff": np.nan,
|
|
126
|
+
"RelDiff%": avg_rel_diff_pct
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
# Save results to CSV
|
|
130
|
+
results_df = pd.DataFrame(results_data)
|
|
131
|
+
results_csv_path = os.path.join(model_dir, args.output)
|
|
132
|
+
results_df.to_csv(results_csv_path, index=False, encoding="utf-8")
|
|
133
|
+
|
|
134
|
+
print(f"✅ Validation complete. Relative difference results saved to '{results_csv_path}'.")
|
|
135
|
+
|
|
136
|
+
else:
|
|
137
|
+
# Ensure all feature columns exist
|
|
138
|
+
features = scaler.feature_names_in_ if hasattr(scaler, "feature_names_in_") else list(df.columns)
|
|
139
|
+
missing_features = [f for f in features if f not in df.columns]
|
|
140
|
+
missing_targets = [t for t in models.keys() if t not in df.columns]
|
|
141
|
+
if missing_features or missing_targets:
|
|
142
|
+
print(f"❌ Error: Missing columns: {missing_features + missing_targets}")
|
|
143
|
+
sys.exit(1)
|
|
144
|
+
|
|
145
|
+
# Prepare features
|
|
146
|
+
X_scaled = scaler.transform(df[features])
|
|
147
|
+
|
|
148
|
+
# Predict for each target/class
|
|
149
|
+
predictions = {}
|
|
150
|
+
for idx, target_name in enumerate(models.keys()):
|
|
151
|
+
X_weighted = X_scaled * W[:, idx]
|
|
152
|
+
y_pred = models[target_name].predict(X_weighted)
|
|
153
|
+
predictions[target_name] = y_pred.astype(int)
|
|
154
|
+
|
|
155
|
+
# Actual values
|
|
156
|
+
actual_arr = df[list(models.keys())].to_numpy(dtype=int)
|
|
157
|
+
pred_arr = pd.DataFrame(predictions)[list(models.keys())].to_numpy(dtype=int)
|
|
158
|
+
|
|
159
|
+
# Element-wise correctness (1 = correct, 0 = incorrect)
|
|
160
|
+
match_arr = (actual_arr == pred_arr).astype(int)
|
|
161
|
+
|
|
162
|
+
# Column-average accuracy in %
|
|
163
|
+
col_avg_acc = np.round(np.mean(match_arr, axis=0) * 100, 2)
|
|
164
|
+
|
|
165
|
+
# === Save element-wise correctness ===
|
|
166
|
+
accuracy_df = pd.DataFrame(match_arr, columns=list(models.keys()))
|
|
167
|
+
accuracy_csv_path = os.path.join(model_dir, "correct_prediction.csv")
|
|
168
|
+
# Insert caption row at top
|
|
169
|
+
caption_df = pd.DataFrame(
|
|
170
|
+
[["element-wise correct predictions (1=true, 0=false)"] + [""] * (len(models.keys()) - 1)],
|
|
171
|
+
columns=list(models.keys())
|
|
172
|
+
)
|
|
173
|
+
accuracy_df = pd.concat([caption_df, accuracy_df], ignore_index=True)
|
|
174
|
+
accuracy_df.to_csv(accuracy_csv_path, index=False, encoding="utf-8")
|
|
175
|
+
|
|
176
|
+
# === Save column-average accuracy ===
|
|
177
|
+
summary_df = pd.DataFrame([col_avg_acc], columns=list(models.keys()))
|
|
178
|
+
accuracy_summary_path = os.path.join(model_dir, "accuracy_summary.csv")
|
|
179
|
+
# Insert caption row at top
|
|
180
|
+
caption_summary_df = pd.DataFrame(
|
|
181
|
+
[["column-averaged prediction accuracy (%)"] + [""] * (len(models.keys()) - 1)],
|
|
182
|
+
columns=list(models.keys())
|
|
183
|
+
)
|
|
184
|
+
summary_df = pd.concat([caption_summary_df, summary_df], ignore_index=True)
|
|
185
|
+
summary_df.to_csv(accuracy_summary_path, index=False, encoding="utf-8")
|
|
186
|
+
|
|
187
|
+
print(f"✅ Classification validation complete.")
|
|
188
|
+
print(f" Element-wise correctness saved to '{accuracy_csv_path}'")
|
|
189
|
+
print(f" Column-average accuracy saved to '{accuracy_summary_path}'")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# Save predictions_full.csv (input + predictions)
|
|
193
|
+
pred_full_df = df.copy()
|
|
194
|
+
for col, vals in predictions.items():
|
|
195
|
+
pred_full_df[col + "_pred"] = vals
|
|
196
|
+
pred_full_csv_path = os.path.join(model_dir, "predictions_full.csv")
|
|
197
|
+
pred_full_df.to_csv(pred_full_csv_path, index=False, encoding="utf-8")
|
|
198
|
+
|
|
199
|
+
print(f"✅ Predictions saved to '{pred_full_csv_path}'.")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
main()
|