mlforgex 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.
mlforge/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ __version__ = "1.0.0"
2
+
3
+ from .train import train_model
4
+ from .predict import predict
mlforge/predict.py ADDED
@@ -0,0 +1,23 @@
1
+ import pickle;
2
+ import pandas as pd;
3
+ def predict(model_path,preprocessor_path,input_data, encoder_path=None):
4
+ print("Loading the pickled model and preprocessor...")
5
+ model = pickle.load(open(model_path, 'rb'))
6
+ preprocessor = pickle.load(open(preprocessor_path, 'rb'))
7
+ encoder = pickle.load(open(encoder_path, 'rb')) if encoder_path else None
8
+ df= pd.read_csv(input_data)
9
+ X = preprocessor.transform(df)
10
+ predictions = model.predict(X)
11
+ if encoder_path:
12
+ predictions = encoder.inverse_transform(predictions)
13
+ return {"prediction": predictions.tolist()}
14
+ def main():
15
+ import argparse
16
+ parser = argparse.ArgumentParser()
17
+ parser.add_argument("--model", required=True)
18
+ parser.add_argument("--input", required=True)
19
+ parser.add_argument("--preprocessor", required=True)
20
+ parser.add_argument("--encoder", required=False)
21
+ args = parser.parse_args()
22
+ print(predict(args.model, args.preprocessor, args.input, args.encoder))
23
+
mlforge/train.py ADDED
@@ -0,0 +1,879 @@
1
+ import pickle
2
+ import pandas as pd
3
+ import pickle
4
+ import os
5
+ import numpy as np
6
+ import seaborn as sns
7
+ import matplotlib
8
+ import warnings
9
+ warnings.filterwarnings("ignore", category=UserWarning)
10
+ warnings.filterwarnings("ignore", category=FutureWarning)
11
+ matplotlib.use('Agg') # Set backend to Agg to prevent GUI window
12
+ import matplotlib.pyplot as plt
13
+ from sklearn.metrics import (
14
+ confusion_matrix,
15
+ ConfusionMatrixDisplay,
16
+ roc_curve,
17
+ precision_recall_curve,
18
+ RocCurveDisplay,
19
+ PrecisionRecallDisplay,
20
+ mean_squared_error,
21
+ r2_score,
22
+ accuracy_score,
23
+ )
24
+ from sklearn.inspection import permutation_importance
25
+ from sklearn.model_selection import learning_curve
26
+ import warnings
27
+ warnings.filterwarnings("ignore")
28
+ artifacts_path=os.path.join(os.path.dirname(__file__),"artifacts")
29
+ plot_path=os.path.join(artifacts_path,"Plots")
30
+ def train_model(data_path, dependent_feature,rmse_prob,f1_prob,n_jobs=-1,n_iter=100,cv=3):
31
+ os.makedirs(artifacts_path,exist_ok=True)
32
+ print("Getting data from:",data_path)
33
+ data=pd.read_csv(data_path)
34
+ df=pd.DataFrame(data)
35
+ # os.remove(data_path)
36
+ df.head()
37
+ print("Data loaded successfully")
38
+
39
+ for i in df.columns:
40
+ if df[i].dtype=="object":
41
+ df[i] = df[i].fillna(df[i].mode()[0])
42
+ else:
43
+ df[i] = df[i].fillna(df[i].median())
44
+
45
+ mild=False
46
+ moderate=False
47
+ majority=max(df[dependent_feature].value_counts())
48
+ minority=min(df[dependent_feature].value_counts())
49
+ IR = majority/minority
50
+ # print(f"Imbalance Ratio: {IR}")
51
+ if IR <= 3:
52
+ # print("Mild Imbalance")
53
+ mild=True
54
+ elif 3 < IR <= 20:
55
+ # print("Moderate Imbalance")
56
+ moderate=True
57
+
58
+ # feature selection
59
+ # Replace < Dependent feture > and < Independent feature > with actual column names
60
+ x=df.drop(columns=[dependent_feature])
61
+ y=df[dependent_feature]
62
+ # print("Independent Feature:", x)
63
+ # print("Dependent Feature:", y)
64
+ regressor=False
65
+ classification=False
66
+ print("Finding the type of problem...")
67
+ if df[dependent_feature].dtype=="object":
68
+ classification=True
69
+ print("Classification Problem")
70
+ else:
71
+ regressor=True
72
+ print("Regression Problem")
73
+
74
+ from sklearn.model_selection import train_test_split
75
+ # Splitting the dataset into training and testing sets
76
+ print("Splitting the dataset into training and testing sets...")
77
+ if classification:
78
+ x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42,stratify=y)
79
+ else:
80
+ x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)
81
+
82
+ print("Dataset split successfully")
83
+
84
+ cat_features=[i for i in x_train.columns if x_train[i].dtype=="object" and i!=dependent_feature]
85
+ num_features=[i for i in x_train.columns if x_train[i].dtype!="object" and i!=dependent_feature]
86
+
87
+ plt.figure(figsize=(12, 8))
88
+ sns.heatmap(
89
+ x[num_features].corr(),
90
+ annot=True,
91
+ fmt=".2f",
92
+ cmap="coolwarm",
93
+ annot_kws={"size": 14}
94
+ )
95
+ plt.title("Correlation Heatmap", fontsize=18)
96
+ os.makedirs(plot_path, exist_ok=True)
97
+ plt.savefig(os.path.join(plot_path,"correlation_heatmap.png"), bbox_inches='tight')
98
+
99
+ plt.close()
100
+ def correlation(dataset,threshold):
101
+ corr_thresh=set()
102
+ for i in range(len(dataset.columns)):
103
+ for j in range(i):
104
+ colname=dataset.columns[i]
105
+ if abs(dataset.corr().iloc[i,j])>threshold :
106
+ corr_thresh.add(colname)
107
+ return corr_thresh
108
+
109
+ dropcorr = correlation(x_train[num_features], 0.85)
110
+ x_train.drop([col for col in dropcorr if col in x_train.columns], axis=1, inplace=True)
111
+ x_test.drop([col for col in dropcorr if col in x_test.columns], axis=1, inplace=True)
112
+ cat_features = [i for i in x_train.columns if x_train[i].dtype == "object" and i != dependent_feature]
113
+ num_features = [i for i in x_train.columns if x_train[i].dtype != "object" and i != dependent_feature]
114
+ print("Categorical features : ",cat_features)
115
+ print("Numerical features : ",num_features)
116
+ print("Balancing the dataset...")
117
+ if mild:
118
+ from imblearn.under_sampling import RandomUnderSampler
119
+ undersampler = RandomUnderSampler(random_state=42)
120
+ x_train, y_train = undersampler.fit_resample(x_train, y_train)
121
+ print("Mild Imbalance Resolved")
122
+ if moderate:
123
+ from imblearn.over_sampling import SMOTETomek
124
+ smote_tomek = SMOTETomek(random_state=42)
125
+ x_train, y_train = smote_tomek.fit_resample(x_train, y_train)
126
+ print("Moderate Imbalace Resolved")
127
+
128
+
129
+
130
+ print("Preprocessing the data...")
131
+ from sklearn.preprocessing import StandardScaler, OneHotEncoder,OrdinalEncoder,LabelEncoder
132
+ from sklearn.compose import ColumnTransformer
133
+ scaler=StandardScaler()
134
+ ohe=OneHotEncoder(drop="first")
135
+ oe=OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1)
136
+ ohe_data=[]
137
+ oe_data=[]
138
+ for i in cat_features:
139
+ if len(x_train[i].unique())>10:
140
+ oe_data.append(i)
141
+ else:
142
+ ohe_data.append(i)
143
+ # print("One Hot Encoding Features:", ohe_data)
144
+ # print("Ordinal Encoding Features:", oe_data)
145
+ preprocessor=ColumnTransformer(
146
+ [("OneHotEncoder",ohe,ohe_data),
147
+ ("OrdinalEncoder",oe,oe_data),
148
+ ("StandardScaler",scaler,num_features)]
149
+ )
150
+ feature_names=list(x_train.columns)
151
+ x_train=preprocessor.fit_transform(x_train)
152
+ x_test=preprocessor.transform(x_test)
153
+ if classification:
154
+ le=LabelEncoder()
155
+ y_train=le.fit_transform(y_train)
156
+ y_test=le.transform(y_test)
157
+ model_dict=[]
158
+ print("Training the models...")
159
+ if regressor:
160
+ from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor,AdaBoostRegressor
161
+ from sklearn.linear_model import LinearRegression,Ridge, Lasso,ElasticNet
162
+ from xgboost import XGBRegressor
163
+ from sklearn.neighbors import KNeighborsRegressor
164
+ from sklearn.tree import DecisionTreeRegressor
165
+ from sklearn.svm import SVR
166
+ from sklearn.metrics import mean_absolute_error,mean_squared_error,r2_score
167
+ models={
168
+ "RandomForestRegressor":RandomForestRegressor(),
169
+ "GradientBoostingRegressor":GradientBoostingRegressor(),
170
+ "AdaBoostRegressor":AdaBoostRegressor(),
171
+ "XGBRegressor":XGBRegressor(),
172
+ "KNeighborsRegressor":KNeighborsRegressor(),
173
+ "LinearRegression":LinearRegression(),
174
+ "Ridge":Ridge(),
175
+ "Lasso":Lasso(),
176
+ "ElasticNet":ElasticNet(),
177
+ "DecisionTreeRegressor":DecisionTreeRegressor(),
178
+ "SVR":SVR()
179
+ }
180
+
181
+ for i in range(len(list(models))):
182
+ model = list(models.values())[i]
183
+ model.fit(x_train, y_train) # Train model
184
+
185
+ # Make predictions
186
+ y_train_pred = model.predict(x_train)
187
+ y_test_pred = model.predict(x_test)
188
+
189
+ model_train_mae = mean_absolute_error(y_train, y_train_pred)
190
+ model_train_mse = mean_squared_error(y_train, y_train_pred)
191
+ model_train_rmse = np.sqrt(model_train_mse)
192
+ model_train_r2 = r2_score(y_train, y_train_pred)
193
+
194
+ model_test_mae = mean_absolute_error(y_test, y_test_pred)
195
+ model_test_mse = mean_squared_error(y_test, y_test_pred)
196
+ model_test_rmse = np.sqrt(model_test_mse)
197
+ model_test_r2 = r2_score(y_test, y_test_pred)
198
+ model_dict.append({
199
+ "model":list(models.keys())[i],
200
+ "train_rmse": model_train_rmse,
201
+ "train_r2": model_train_r2,
202
+ "test_rmse": model_test_rmse,
203
+ "test_r2": model_test_r2,
204
+ "tuned":False
205
+ })
206
+
207
+ # print(list(models.keys())[i])
208
+
209
+ # print('Model performance for Training set')
210
+ # print("- mae: {:.4f}".format(model_train_mae))
211
+ # print('-mse: {:.4f}'.format(model_train_mse))
212
+ # print('- rmse: {:.4f}'.format(model_train_rmse))
213
+ # print('- r2: {:.4f}'.format(model_train_r2))
214
+
215
+
216
+
217
+ # print('----------------------------------')
218
+
219
+ # print('Model performance for Test set')
220
+ # print('- mae: {:.4f}'.format(model_test_mae))
221
+ # print('- mse: {:.4f}'.format(model_test_mse))
222
+ # print('- rmse: {:.4f}'.format(model_test_rmse))
223
+ # print('- r2: {:.4f}'.format(model_test_r2))
224
+
225
+
226
+ # print('='*35)
227
+ # print('\n')
228
+ else:
229
+ from sklearn.ensemble import RandomForestClassifier,GradientBoostingClassifier,AdaBoostClassifier
230
+ from xgboost import XGBClassifier
231
+ from sklearn.neighbors import KNeighborsClassifier
232
+ from sklearn.svm import SVC
233
+ from sklearn.tree import DecisionTreeClassifier
234
+ from sklearn.linear_model import LogisticRegression
235
+ from sklearn.metrics import accuracy_score,confusion_matrix,classification_report,f1_score,roc_auc_score,roc_curve,precision_score,recall_score
236
+ models={
237
+ "LogisticRegression":LogisticRegression(),
238
+ "DecisionTreeClassifier":DecisionTreeClassifier(),
239
+ "RandomForestClassifier":RandomForestClassifier(),
240
+ "GradientBoostingClassifier":GradientBoostingClassifier(),
241
+ "AdaBoostClassifier":AdaBoostClassifier(),
242
+ "XGBClassifier":XGBClassifier(),
243
+ "KNeighborsClassifier":KNeighborsClassifier(),
244
+ "SVC":SVC()
245
+ }
246
+
247
+ for i in range(len(list(models))):
248
+ model = list(models.values())[i]
249
+ model.fit(x_train, y_train) # Train model
250
+
251
+ # Make predictions
252
+ y_train_pred = model.predict(x_train)
253
+ y_test_pred = model.predict(x_test)
254
+
255
+ # Training set performance
256
+ model_train_accuracy = accuracy_score(y_train, y_train_pred) # Calculate Accuracy
257
+ model_train_f1 = f1_score(y_train, y_train_pred, average='weighted') # Calculate F1-score
258
+ model_train_precision = precision_score(y_train, y_train_pred) # Calculate Precision
259
+ model_train_recall = recall_score(y_train, y_train_pred) # Calculate Recall
260
+ model_train_rocauc_score = roc_auc_score(y_train, y_train_pred)
261
+ # Test set performance
262
+ model_test_accuracy = accuracy_score(y_test, y_test_pred) # Calculate Accuracy
263
+ model_test_f1 = f1_score(y_test, y_test_pred, average='weighted') # Calculate F1-score
264
+ model_test_precision = precision_score(y_test, y_test_pred) # Calculate Precision
265
+ model_test_recall = recall_score(y_test, y_test_pred) # Calculate Recal
266
+
267
+ model_dict.append({
268
+ "model":list(models.keys())[i],
269
+ "train_accuracy": model_train_accuracy,
270
+ "train_f1": model_train_f1,
271
+ "test_accuracy": model_test_accuracy,
272
+ "test_f1": model_test_f1,
273
+ "tuned":False
274
+ })
275
+ model_test_rocauc_score = roc_auc_score(y_test, y_test_pred) #Calculate Roc
276
+
277
+
278
+ # print(list(models.keys())[i])
279
+
280
+ # print('Model performance for Training set')
281
+ # print("- Accuracy: {:.4f}".format(model_train_accuracy))
282
+ # print('- F1 score: {:.4f}'.format(model_train_f1))
283
+
284
+ # print('- Precision: {:.4f}'.format(model_train_precision))
285
+ # print('- Recall: {:.4f}'.format(model_train_recall))
286
+ # print('- Roc Auc Score: {:.4f}'.format(model_train_rocauc_score))
287
+
288
+
289
+
290
+ # print('----------------------------------')
291
+
292
+ # print('Model performance for Test set')
293
+ # print('- Accuracy: {:.4f}'.format(model_test_accuracy))
294
+ # print('- F1 score: {:.4f}'.format(model_test_f1))
295
+ # print('- Precision: {:.4f}'.format(model_test_precision))
296
+ # print('- Recall: {:.4f}'.format(model_test_recall))
297
+ # print('- Roc Auc Score: {:.4f}'.format(model_test_rocauc_score))
298
+
299
+
300
+ # print('='*35)
301
+ # print('\n')
302
+ print("Finding the best model...")
303
+ if regressor:
304
+ comparison_df=pd.DataFrame(model_dict)
305
+ comparison_df["total_rmse"]=comparison_df["train_rmse"]+comparison_df["test_rmse"]
306
+ comparison_df["total_r2"]=comparison_df["train_r2"]+comparison_df["test_r2"]
307
+ # print(comparison_df)
308
+ comparison_df['Norm RMSE'] = comparison_df["total_rmse"] / comparison_df['total_rmse'].max()
309
+ comparison_df['Norm R2'] = 1 - (comparison_df["total_r2"] / comparison_df['total_r2'].max())
310
+ comparison_df['Combined Score'] = rmse_prob * comparison_df['Norm RMSE'] + (1-rmse_prob)* comparison_df['Norm R2']
311
+ combined_score_ranking = comparison_df.sort_values('Combined Score')
312
+ # print(combined_score_ranking[["train_rmse", "train_r2", "test_rmse", "test_r2","total_rmse","total_r2","Combined Score"]])
313
+ # print("===="*35)
314
+ best_models = comparison_df.nsmallest(3, 'Combined Score')
315
+
316
+ if classification:
317
+ comparison_df=pd.DataFrame(model_dict)
318
+ comparison_df["total_accuracy"]=comparison_df["train_accuracy"]+comparison_df["test_accuracy"]
319
+ comparison_df["total_f1"]=comparison_df["train_f1"]+comparison_df["test_f1"]
320
+ # print(comparison_df)
321
+ comparison_df['Norm F1'] = comparison_df["total_f1"] / comparison_df['total_f1'].max()
322
+ comparison_df['Norm Accuracy'] = comparison_df["total_accuracy"] / comparison_df['total_accuracy'].max()
323
+ comparison_df['Combined Score'] = f1_prob * comparison_df['Norm F1'] + (1-f1_prob)* comparison_df['Norm Accuracy']
324
+ combined_score_ranking = comparison_df.sort_values('Combined Score',ascending=False)
325
+ combined_score_ranking=combined_score_ranking[combined_score_ranking["train_f1"]-combined_score_ranking["test_f1"]<0.15]
326
+ # print(combined_score_ranking[["model","train_accuracy", "train_f1", "test_accuracy", "test_f1","total_accuracy","total_f1","Combined Score"]])
327
+ # print("===="*35)
328
+ best_models = combined_score_ranking.nlargest(3, 'Combined Score')
329
+
330
+ # print("Best Models based on Combined Score:", best_models[["model"]])
331
+ # print("===="*35)
332
+ # print(best_models)
333
+ # print("===="*35)
334
+ top_model=[i for i in best_models["model"] ]
335
+ print("Top Model:",top_model)
336
+ print("Training the best models with enhanced parameters...")
337
+ randomcv_model = []
338
+ if regressor:
339
+ reg_params = [
340
+ ["RandomForestRegressor", {
341
+ 'n_estimators': [100, 200,300,400],
342
+ 'max_depth': [None, 5, 10, 15, 20, 30, 50],
343
+ 'min_samples_split': [2, 5, 10, 15],
344
+ 'min_samples_leaf': [1, 2, 4, 6],
345
+ 'max_features': ['sqrt', 'log2', None, 0.5, 0.7],
346
+ 'bootstrap': [True, False],
347
+ 'random_state': [42]
348
+ }],
349
+
350
+ ["GradientBoostingRegressor", {
351
+ 'n_estimators': [100, 200,300,400],
352
+ 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.2],
353
+ 'max_depth': [3, 4, 5, 6, 7, 8],
354
+ 'min_samples_split': [2, 5, 10],
355
+ 'min_samples_leaf': [1, 2, 4],
356
+ 'subsample': [0.7, 0.8, 0.9, 1.0],
357
+ 'max_features': ['sqrt', 'log2', None],
358
+ 'loss': ['squared_error', 'absolute_error', 'huber', 'quantile'],
359
+ 'random_state': [42]
360
+ }],
361
+
362
+ ["AdaBoostRegressor", {
363
+ 'n_estimators': [50, 100, 150, 200, 300],
364
+ 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.5, 1.0],
365
+ 'loss': ['linear', 'square', 'exponential'],
366
+ 'random_state': [42]
367
+ }],
368
+
369
+ ["XGBRegressor", {
370
+ 'n_estimators': [100, 200,300,400],
371
+ 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.2],
372
+ 'max_depth': [3, 4, 5, 6, 7, 8, 9],
373
+ 'min_child_weight': [1, 2, 3, 4],
374
+ 'gamma': [0, 0.1, 0.2, 0.3, 0.4],
375
+ 'subsample': [0.6, 0.7, 0.8, 0.9, 1.0],
376
+ 'colsample_bytree': [0.6, 0.7, 0.8, 0.9, 1.0],
377
+ 'reg_alpha': [0, 0.1, 0.5, 1],
378
+ 'reg_lambda': [0, 0.1, 1, 10],
379
+ 'random_state': [42]
380
+ }],
381
+
382
+ ["KNeighborsRegressor", {
383
+ 'n_neighbors': [3, 5, 7, 10, 15, 20],
384
+ 'weights': ['uniform', 'distance'],
385
+ 'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute'],
386
+ 'leaf_size': [10, 20, 30, 40, 50],
387
+ 'p': [1, 2, 3]
388
+ }],
389
+
390
+ ["LinearRegression", {
391
+ 'fit_intercept': [True, False],
392
+ 'positive': [True, False],
393
+ 'copy_X': [True, False]
394
+ }],
395
+
396
+ ["Ridge", {
397
+ 'alpha': [0.001, 0.01, 0.1, 1.0, 10.0, 100.0],
398
+ 'solver': ['auto', 'svd', 'cholesky', 'lsqr', 'sparse_cg', 'sag', 'saga'],
399
+ 'fit_intercept': [True, False],
400
+ 'random_state': [42]
401
+ }],
402
+
403
+ ["Lasso", {
404
+ 'alpha': [0.0001, 0.001, 0.01, 0.1, 1.0],
405
+ 'max_iter': [1000, 2000, 5000, 10000],
406
+ 'selection': ['cyclic', 'random'],
407
+ 'random_state': [42]
408
+ }],
409
+
410
+ ["ElasticNet", {
411
+ 'alpha': [0.0001, 0.001, 0.01, 0.1, 1.0],
412
+ 'l1_ratio': [0.1, 0.3, 0.5, 0.7, 0.9],
413
+ 'max_iter': [1000, 5000],
414
+ 'selection': ['cyclic', 'random'],
415
+ 'random_state': [42]
416
+ }],
417
+
418
+ ["DecisionTreeRegressor", {
419
+ 'max_depth': [None, 5, 10, 15, 20, 30],
420
+ 'min_samples_split': [2, 5, 10, 15],
421
+ 'min_samples_leaf': [1, 2, 4, 6, 8],
422
+ 'max_features': ['sqrt', 'log2', None, 0.5, 0.7],
423
+ 'criterion': ['squared_error', 'friedman_mse', 'absolute_error', 'poisson'],
424
+ 'random_state': [42]
425
+ }],
426
+
427
+ ["SVR", {
428
+ 'kernel': ['rbf', 'linear', 'poly', 'sigmoid'],
429
+ 'C': [0.1, 0.5, 1, 5, 10, 50, 100],
430
+ 'epsilon': [0.01, 0.1, 0.2, 0.5, 1.0],
431
+ 'gamma': ['scale', 'auto'] + [0.001, 0.01, 0.1, 1, 10],
432
+ 'degree': [2, 3, 4] # Only for poly kernel
433
+ }]
434
+ ]
435
+
436
+ for i in reg_params:
437
+ if i[0] in top_model:
438
+ randomcv_model.append((i[0], models[i[0]], i[1]))
439
+
440
+ # print("Enhanced Random CV Model Parameters:", randomcv_model)
441
+ if classification:
442
+ class_params = [
443
+ ["LogisticRegression", {
444
+ 'penalty': ['l1', 'l2', 'elasticnet', 'none'],
445
+ 'C': np.logspace(-4, 4, 20),
446
+ 'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'],
447
+ 'max_iter': [100, 200, 500, 1000],
448
+ 'class_weight': [None, 'balanced'],
449
+ 'l1_ratio': [0, 0.25, 0.5, 0.75, 1] # For elasticnet
450
+ }],
451
+
452
+ ["DecisionTreeClassifier", {
453
+ 'criterion': ['gini', 'entropy', 'log_loss'],
454
+ 'splitter': ['best', 'random'],
455
+ 'max_depth': [None, 5, 10, 15, 20, 30, 50],
456
+ 'min_samples_split': [2, 5, 10, 15, 20],
457
+ 'min_samples_leaf': [1, 2, 4, 6, 8],
458
+ 'max_features': ['sqrt', 'log2', None, 0.3, 0.5, 0.7],
459
+ 'class_weight': [None, 'balanced'],
460
+ 'random_state': [42]
461
+ }],
462
+
463
+ ["RandomForestClassifier", {
464
+ 'n_estimators': [100, 200,300,400],
465
+ 'criterion': ['gini', 'entropy', 'log_loss'],
466
+ 'max_depth': [None, 5, 10, 15, 20, 30, 50],
467
+ 'min_samples_split': [2, 5, 10, 15],
468
+ 'min_samples_leaf': [1, 2, 4, 6],
469
+ 'max_features': ['sqrt', 'log2', None, 0.5, 0.7],
470
+ 'bootstrap': [True, False],
471
+ 'class_weight': [None, 'balanced', 'balanced_subsample'],
472
+ 'random_state': [42]
473
+ }],
474
+
475
+ ["GradientBoostingClassifier", {
476
+ 'n_estimators': [100, 200,300,400],
477
+ 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.2],
478
+ 'loss': ['log_loss', 'exponential'],
479
+ 'max_depth': [3, 4, 5, 6, 7, 8],
480
+ 'min_samples_split': [2, 5, 10],
481
+ 'min_samples_leaf': [1, 2, 4],
482
+ 'subsample': [0.7, 0.8, 0.9, 1.0],
483
+ 'max_features': ['sqrt', 'log2', None],
484
+ 'random_state': [42]
485
+ }],
486
+
487
+ ["AdaBoostClassifier", {
488
+ 'n_estimators': [50, 100, 150, 200,300,400],
489
+ 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.5, 1.0],
490
+ 'random_state': [42]
491
+ }],
492
+
493
+ ["XGBClassifier", {
494
+ 'n_estimators': [100, 200,300 ,400],
495
+ 'learning_rate': [0.001, 0.01, 0.05, 0.1, 0.2],
496
+ 'max_depth': [3, 4, 5, 6, 7, 8, 9],
497
+ 'min_child_weight': [1, 2, 3, 4],
498
+ 'gamma': [0, 0.1, 0.2, 0.3, 0.4],
499
+ 'subsample': [0.6, 0.7, 0.8, 0.9, 1.0],
500
+ 'colsample_bytree': [0.6, 0.7, 0.8, 0.9, 1.0],
501
+ 'reg_alpha': [0, 0.1, 0.5, 1],
502
+ 'reg_lambda': [0, 0.1, 1, 10],
503
+ 'scale_pos_weight': [1, 5, 10], # For imbalanced classes
504
+ 'random_state': [42],
505
+ 'use_label_encoder': [False],
506
+ 'eval_metric': ['logloss', 'aucpr', 'auc']
507
+ }],
508
+
509
+
510
+
511
+ ["KNeighborsClassifier", {
512
+ 'n_neighbors': [3, 5, 7, 10, 15, 20, 25],
513
+ 'weights': ['uniform', 'distance'],
514
+ 'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute'],
515
+ 'leaf_size': [10, 20, 30, 40, 50],
516
+ 'p': [1, 2, 3],
517
+ 'metric': ['minkowski', 'euclidean', 'manhattan']
518
+ }],
519
+
520
+ ["SVC", {
521
+ 'kernel': ['linear', 'rbf', 'poly', 'sigmoid'],
522
+ 'C': [0.001, 0.01, 0.1, 1, 5, 10, 50, 100],
523
+ 'gamma': ['scale', 'auto'] + [0.001, 0.01, 0.1, 1, 10],
524
+ 'degree': [2, 3, 4, 5], # For poly kernel
525
+ 'class_weight': [None, 'balanced'],
526
+ 'probability': [True], # If you need predict_proba
527
+ 'random_state': [42]
528
+ }],
529
+
530
+
531
+ ]
532
+
533
+ for i in class_params:
534
+ if i[0] in top_model:
535
+ randomcv_model.append((i[0], models[i[0]], i[1]))
536
+
537
+ # print("Enhanced Classification Model Parameters:", randomcv_model)
538
+
539
+ from sklearn.model_selection import RandomizedSearchCV
540
+
541
+ model_param = {}
542
+ for name, model, params in randomcv_model:
543
+ random = RandomizedSearchCV(estimator=model,
544
+ param_distributions=params,
545
+ n_iter=n_iter,
546
+ cv=cv,
547
+ n_jobs=n_jobs)
548
+ random.fit(x_train, y_train)
549
+ model_param[name] = random.best_params_
550
+
551
+ # for model_name in model_param:
552
+ # print(f"---------------- Best Params for {model_name} -------------------")
553
+ # print(model_param[model_name])
554
+ best_params=[]
555
+ if regressor:
556
+ model_cls={
557
+ "RandomForestRegressor":RandomForestRegressor,
558
+ "GradientBoostingRegressor":GradientBoostingRegressor,
559
+ "AdaBoostRegressor":AdaBoostRegressor,
560
+ "XGBRegressor":XGBRegressor,
561
+ "KNeighborsRegressor":KNeighborsRegressor,
562
+ "LinearRegression":LinearRegression,
563
+ "Ridge":Ridge,
564
+ "Lasso":Lasso,
565
+ "ElasticNet":ElasticNet,
566
+ "DecisionTreeRegressor":DecisionTreeRegressor,
567
+ "SVR":SVR
568
+ }
569
+ if classification:
570
+ model_cls={
571
+ "LogisticRegression":LogisticRegression,
572
+ "DecisionTreeClassifier":DecisionTreeClassifier,
573
+ "RandomForestClassifier":RandomForestClassifier,
574
+ "GradientBoostingClassifier":GradientBoostingClassifier,
575
+ "AdaBoostClassifier":AdaBoostClassifier,
576
+ "XGBClassifier":XGBClassifier,
577
+ "KNeighborsClassifier":KNeighborsClassifier,
578
+ "SVC":SVC
579
+ }
580
+ for i in model_param:
581
+ best_params.append([i,model_param[i]])
582
+ # print("Best Params:", best_params)
583
+
584
+ best_models_copy=best_models.copy()
585
+ if regressor:
586
+ best_models_copy.drop(columns=['total_rmse' ,'total_r2' ,'Norm RMSE','Norm R2',"Combined Score"], inplace=True)
587
+ if classification:
588
+ best_models_copy.drop(columns=['total_accuracy' ,'total_f1' ,'Norm F1','Norm Accuracy',"Combined Score"], inplace=True)
589
+ models={}
590
+ for i in best_params:
591
+ models[i[0]]=model_cls[i[0]](**i[1])
592
+ if regressor:
593
+ for i in range(len(list(models))):
594
+ model = list(models.values())[i]
595
+ model.fit(x_train, y_train) # Train model
596
+
597
+ # Make predictions
598
+ y_train_pred = model.predict(x_train)
599
+ y_test_pred = model.predict(x_test)
600
+
601
+ model_train_mae = mean_absolute_error(y_train, y_train_pred)
602
+ model_train_mse = mean_squared_error(y_train, y_train_pred)
603
+ model_train_rmse = np.sqrt(model_train_mse)
604
+ model_train_r2 = r2_score(y_train, y_train_pred)
605
+
606
+ model_test_mae = mean_absolute_error(y_test, y_test_pred)
607
+ model_test_mse = mean_squared_error(y_test, y_test_pred)
608
+ model_test_rmse = np.sqrt(model_test_mse)
609
+ model_test_r2 = r2_score(y_test, y_test_pred)
610
+
611
+ # print(list(models.keys())[i])
612
+ model_dict={
613
+ "model":list(models.keys())[i],
614
+ "train_rmse": model_train_rmse,
615
+ "train_r2": model_train_r2,
616
+ "test_rmse": model_test_rmse,
617
+ "test_r2": model_test_r2,
618
+ "tuned":True}
619
+ best_models_copy = pd.concat(
620
+ [best_models_copy, pd.DataFrame([model_dict])],
621
+ ignore_index=True
622
+ )
623
+
624
+
625
+ # print('Model performance for Training set')
626
+ # print("- mae: {:.4f}".format(model_train_mae))
627
+ # print('-mse: {:.4f}'.format(model_train_mse))
628
+ # print('- rmse: {:.4f}'.format(model_train_rmse))
629
+ # print('- r2: {:.4f}'.format(model_train_r2))
630
+
631
+
632
+
633
+ # print('----------------------------------')
634
+
635
+ # print('Model performance for Test set')
636
+ # print('- mae: {:.4f}'.format(model_test_mae))
637
+ # print('- mse: {:.4f}'.format(model_test_mse))
638
+ # print('- rmse: {:.4f}'.format(model_test_rmse))
639
+ # print('- r2: {:.4f}'.format(model_test_r2))
640
+
641
+
642
+ # print('='*35)
643
+ # print('\n')
644
+
645
+ if classification:
646
+
647
+ for i in range(len(list(models))):
648
+ model = list(models.values())[i]
649
+ model.fit(x_train, y_train) # Train model
650
+
651
+ # Make predictions
652
+ y_train_pred = model.predict(x_train)
653
+ y_test_pred = model.predict(x_test)
654
+
655
+ # Training set performance
656
+ model_train_accuracy = accuracy_score(y_train, y_train_pred) # Calculate Accuracy
657
+ model_train_f1 = f1_score(y_train, y_train_pred, average='weighted') # Calculate F1-score
658
+ model_train_precision = precision_score(y_train, y_train_pred) # Calculate Precision
659
+ model_train_recall = recall_score(y_train, y_train_pred) # Calculate Recall
660
+ model_train_rocauc_score = roc_auc_score(y_train, y_train_pred)
661
+ # Test set performance
662
+ model_test_accuracy = accuracy_score(y_test, y_test_pred) # Calculate Accuracy
663
+ model_test_f1 = f1_score(y_test, y_test_pred, average='weighted') # Calculate F1-score
664
+ model_test_precision = precision_score(y_test, y_test_pred) # Calculate Precision
665
+ model_test_recall = recall_score(y_test, y_test_pred) # Calculate Recal
666
+
667
+ model_dict={
668
+ "model":list(models.keys())[i],
669
+ "train_accuracy": model_train_accuracy,
670
+ "train_f1": model_train_f1,
671
+ "test_accuracy": model_test_accuracy,
672
+ "test_f1": model_test_f1,
673
+ "tuned":True
674
+ }
675
+ best_models_copy = pd.concat(
676
+ [best_models_copy, pd.DataFrame([model_dict])],
677
+ ignore_index=True
678
+ )
679
+
680
+
681
+
682
+ # print(list(models.keys())[i])
683
+
684
+ # print('Model performance for Training set')
685
+ # print("- Accuracy: {:.4f}".format(model_train_accuracy))
686
+ # print('- F1 score: {:.4f}'.format(model_train_f1))
687
+
688
+ # print('- Precision: {:.4f}'.format(model_train_precision))
689
+ # print('- Recall: {:.4f}'.format(model_train_recall))
690
+ # print('- Roc Auc Score: {:.4f}'.format(model_train_rocauc_score))
691
+
692
+
693
+
694
+ # print('----------------------------------')
695
+
696
+ # print('Model performance for Test set')
697
+ # print('- Accuracy: {:.4f}'.format(model_test_accuracy))
698
+ # print('- F1 score: {:.4f}'.format(model_test_f1))
699
+ # print('- Precision: {:.4f}'.format(model_test_precision))
700
+ # print('- Recall: {:.4f}'.format(model_test_recall))
701
+ # print('- Roc Auc Score: {:.4f}'.format(model_test_rocauc_score))
702
+
703
+
704
+ # print('='*35)
705
+ # print('\n')
706
+ best_models_copy
707
+ if regressor:
708
+ best_models_copy["total_rmse"]=best_models_copy["train_rmse"]+best_models_copy["test_rmse"]
709
+ best_models_copy["total_r2"]=best_models_copy["train_r2"]+best_models_copy["test_r2"]
710
+ best_models_copy['Norm RMSE'] = best_models_copy["total_rmse"] / best_models_copy['total_rmse'].max()
711
+ best_models_copy['Norm R2'] = 1 - (best_models_copy["total_r2"] / best_models_copy['total_r2'].max())
712
+ best_models_copy['Combined Score'] = rmse_prob * best_models_copy['Norm RMSE'] + (1-rmse_prob)* best_models_copy['Norm R2']
713
+ combined_score_ranking = best_models_copy.sort_values('Combined Score').reset_index(drop=True)
714
+ if classification:
715
+ best_models_copy["total_accuracy"]=best_models_copy["train_accuracy"]+best_models_copy["test_accuracy"]
716
+ best_models_copy["total_f1"]=best_models_copy["train_f1"]+best_models_copy["test_f1"]
717
+ best_models_copy['Norm F1'] = best_models_copy["total_f1"] / best_models_copy['total_f1'].max()
718
+ best_models_copy['Norm Accuracy'] = best_models_copy["total_accuracy"] / best_models_copy['total_accuracy'].max()
719
+ best_models_copy['Combined Score'] = f1_prob * best_models_copy['Norm F1'] + (1-f1_prob)* best_models_copy['Norm Accuracy']
720
+ combined_score_ranking = best_models_copy.sort_values('Combined Score',ascending=False).reset_index(drop=True)
721
+ # print(combined_score_ranking.iloc[0:1,:]["model"][0])
722
+ # print(combined_score_ranking)
723
+ if classification:
724
+ combined_score_ranking=combined_score_ranking[combined_score_ranking["train_f1"]-combined_score_ranking["test_f1"]<0.15]
725
+ # print("====="*35)
726
+ print(combined_score_ranking.iloc[0:1,:])
727
+ best_model_name = combined_score_ranking.iloc[0]["model"]
728
+ best_param_dict = None
729
+ for name, params in best_params:
730
+ if name == best_model_name:
731
+ best_param_dict = params
732
+ break
733
+ model = model_cls[best_model_name](**best_param_dict)
734
+ # print("Best Model Name:", best_model_name)
735
+ # print("Best Model Parameters:", best_param_dict)
736
+ model.fit(x_train, y_train)
737
+ print("Best Model Trained Successfully")
738
+ print("Saving the model and preprocessor...")
739
+ model_path = os.path.join(artifacts_path, "model.pkl")
740
+ preprocessor_path = os.path.join(artifacts_path, "preprocessor.pkl")
741
+ encoder_path = os.path.join(artifacts_path, "encoder.pkl")
742
+
743
+ with open(model_path, "wb") as f:
744
+ pickle.dump(model, f)
745
+
746
+ with open(preprocessor_path, "wb") as f:
747
+ pickle.dump(preprocessor, f)
748
+
749
+ if classification:
750
+ with open(encoder_path, "wb") as f:
751
+ pickle.dump(le, f)
752
+ response = {
753
+ "message": "Training completed successfully",
754
+ "Problem_type":"Classification",
755
+ "model_metrics": {
756
+ "Model": combined_score_ranking.iloc[0]["model"],
757
+ "Train_accuracy": float(combined_score_ranking.iloc[0]["train_accuracy"]),
758
+ "Train_f1": float(combined_score_ranking.iloc[0]["train_f1"]),
759
+ "Test_accuracy": float(combined_score_ranking.iloc[0]["test_accuracy"]),
760
+ "Test_f1": float(combined_score_ranking.iloc[0]["test_f1"]),
761
+ "Hyper_tuned": bool(combined_score_ranking.iloc[0]["tuned"]),
762
+ "Dropped_Columns":list(dropcorr)
763
+ }
764
+ }
765
+ plot_classification_metrics(model,x_train, y_train, x_test, y_test, class_names=['No', 'Yes'])
766
+ else:
767
+ response={
768
+ "message": "Training completed successfully",
769
+ "Problem_type":"Regression",
770
+ "model_metrics": {
771
+ "Model": combined_score_ranking.iloc[0]["model"],
772
+ "Train_R2": float(combined_score_ranking.iloc[0]["train_r2"]),
773
+ "Train_RMSE": float(combined_score_ranking.iloc[0]["train_rmse"]),
774
+ "Test_R2": float(combined_score_ranking.iloc[0]["test_r2"]),
775
+ "Test_RMSE": float(combined_score_ranking.iloc[0]["test_rmse"]),
776
+ "Hyper_tuned": bool(combined_score_ranking.iloc[0]["tuned"]),
777
+ "Dropped_Columns":list(dropcorr)
778
+ }
779
+ }
780
+ plot_regression_metrics(model, x_train, y_train, x_test, y_test,feature_names)
781
+ print(response)
782
+ return {"status": "success", "model": "trained_model.pkl"}
783
+
784
+ def plot_classification_metrics(model, X_train, y_train, X_test, y_test, class_names=None):
785
+ # Predict
786
+ y_pred = model.predict(X_test)
787
+ y_proba = model.predict_proba(X_test)[:, 1] if hasattr(model, "predict_proba") else None
788
+
789
+ # Confusion Matrix
790
+ cm = confusion_matrix(y_test, y_pred)
791
+ disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names)
792
+ disp.plot(cmap="Blues")
793
+ plt.title("Confusion Matrix")
794
+ plt.savefig(os.path.join(plot_path,"confusion_matrix.png"), bbox_inches='tight')
795
+ plt.close()
796
+ # ROC Curve
797
+ if y_proba is not None and len(np.unique(y_test)) == 2:
798
+ fpr, tpr, _ = roc_curve(y_test, y_proba)
799
+ RocCurveDisplay(fpr=fpr, tpr=tpr).plot()
800
+ plt.title("ROC Curve")
801
+ plt.savefig(os.path.join(plot_path,"roc_curve.png"), bbox_inches='tight')
802
+ plt.close()
803
+ # Precision-Recall Curve
804
+ if y_proba is not None:
805
+ precision, recall, _ = precision_recall_curve(y_test, y_proba)
806
+ PrecisionRecallDisplay(precision=precision, recall=recall).plot()
807
+ plt.title("Precision-Recall Curve")
808
+ plt.savefig(os.path.join(plot_path,"precision_recall_curve.png"), bbox_inches='tight')
809
+ plt.close()
810
+ # Learning Curve
811
+ train_sizes, train_scores, val_scores = learning_curve(model, X_train, y_train, cv=5, scoring='accuracy')
812
+ plt.plot(train_sizes, np.mean(train_scores, axis=1), label="Train")
813
+ plt.plot(train_sizes, np.mean(val_scores, axis=1), label="Validation")
814
+ plt.title("Learning Curve (Accuracy)")
815
+ plt.xlabel("Training Set Size")
816
+ plt.ylabel("Accuracy")
817
+ plt.legend()
818
+ plt.savefig(os.path.join(plot_path,"Accuracy_curve.png"), bbox_inches='tight')
819
+ plt.close()
820
+
821
+ # Class Distribution
822
+ fig, ax = plt.subplots(1, 2, figsize=(10, 4))
823
+ sns.countplot(x=y_train, ax=ax[0])
824
+ ax[0].set_title("Training Class Distribution")
825
+ sns.countplot(x=y_test, ax=ax[1])
826
+ ax[1].set_title("Testing Class Distribution")
827
+ plt.savefig(os.path.join(plot_path,"class_distribution.png"), bbox_inches='tight')
828
+ plt.close()
829
+ def plot_regression_metrics(model, X_train, y_train, X_test, y_test,feature_names):
830
+ y_pred = model.predict(X_test)
831
+ residuals = y_test - y_pred
832
+
833
+ # Actual vs. Predicted
834
+ plt.scatter(y_test, y_pred, alpha=0.7)
835
+ plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], '--r')
836
+ plt.xlabel("Actual")
837
+ plt.ylabel("Predicted")
838
+ plt.title("Actual vs. Predicted")
839
+ plt.savefig(os.path.join(plot_path,"actual_vs_predictes.png"), bbox_inches='tight')
840
+ plt.close()
841
+ # Residual Plot
842
+ plt.scatter(y_pred, residuals, alpha=0.7)
843
+ plt.axhline(0, color='red', linestyle='--')
844
+ plt.xlabel("Predicted")
845
+ plt.ylabel("Residuals")
846
+ plt.title("Residual Plot")
847
+ plt.savefig(os.path.join(plot_path,"residual.png"), bbox_inches='tight')
848
+ plt.close()
849
+ # Distribution of Residuals
850
+ sns.histplot(residuals, kde=True)
851
+ plt.title("Distribution of Residuals")
852
+ plt.xlabel("Residual")
853
+ plt.savefig(os.path.join(plot_path,"residual_distribution.png"), bbox_inches='tight')
854
+ plt.close()
855
+ # Learning Curve (R² or MSE)
856
+ train_sizes, train_scores, val_scores = learning_curve(model, X_train, y_train, cv=5, scoring='r2')
857
+ plt.plot(train_sizes, np.mean(train_scores, axis=1), label="Train")
858
+ plt.plot(train_sizes, np.mean(val_scores, axis=1), label="Validation")
859
+ plt.title("Learning Curve (R² Score)")
860
+ plt.xlabel("Training Set Size")
861
+ plt.ylabel("R² Score")
862
+ plt.savefig(os.path.join(plot_path,"r2_score.png"), bbox_inches='tight')
863
+ plt.legend()
864
+ plt.close()
865
+
866
+
867
+ def main():
868
+ import argparse
869
+ parser = argparse.ArgumentParser()
870
+ parser.add_argument("--data", required=True)
871
+ parser.add_argument("--target", required=True)
872
+ parser.add_argument("--rmse", required=True)
873
+ parser.add_argument("--f1", required=True)
874
+ parser.add_argument("--n_jobs", default=-1, type=int)
875
+ parser.add_argument("--n_iter", default=100, type=int)
876
+ parser.add_argument("--cv", default=3, type=int)
877
+ args = parser.parse_args()
878
+ print(train_model(args.data, args.target, rmse_prob=args.rmse, f1_prob=args.f1, n_jobs=args.n_jobs, n_iter=args.n_iter, cv=args.cv))
879
+
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: mlforgex
3
+ Version: 1.0.0
4
+ Summary: MLForge: Train and evaluate ML models easily
5
+ Home-page: https://github.com/yourusername/mlforge
6
+ Author: Priyanshu Mathur
7
+ Author-email: mathurpriyanshu2006@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.8
11
+ License-File: LICENSE
12
+ Requires-Dist: pandas
13
+ Requires-Dist: numpy
14
+ Requires-Dist: scikit-learn
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: home-page
19
+ Dynamic: license-file
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
@@ -0,0 +1,9 @@
1
+ mlforge/__init__.py,sha256=KF9-OgHhD_9tvdgekjebdIehH34eCbd7FSb_9YcN0t4,87
2
+ mlforge/predict.py,sha256=zxu6rR-94F1h4k4qasrVSpNVkcSXKcvVTRe4loUibJ0,1040
3
+ mlforge/train.py,sha256=rhzIkJJRfjY_jNbUtUlKMnAIOTq8ZqjTWDoKw82Wwco,39526
4
+ mlforgex-1.0.0.dist-info/licenses/LICENSE,sha256=ajw85KQBQVyhuA7N0vPG92aXWWUanH6aq2wOo6hQNc8,1092
5
+ mlforgex-1.0.0.dist-info/METADATA,sha256=e4xNgkhuT0NivsYw3Xutjw7CSqBh6LXTRrf9CRkGdqw,623
6
+ mlforgex-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ mlforgex-1.0.0.dist-info/entry_points.txt,sha256=HxvVbd88ye_YRcZw_Kp_Yn-d-ccyGlfLdqrXcZJnJAE,92
8
+ mlforgex-1.0.0.dist-info/top_level.txt,sha256=Deisw-We6Xoaj6mV5d7DdeuiA7sYl_RXBJh2tAaTdW4,8
9
+ mlforgex-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ mlforge-predict = mlforge.predict:main
3
+ mlforge-train = mlforge.train:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Priyanshu_Mathur
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mlforge