simml 0.1.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.
Potentially problematic release.
This version of simml might be problematic. Click here for more details.
- simml/surrogate/__init__.py +3 -0
- simml/surrogate/models.py +9 -0
- simml/surrogate/multi_trainer.py +55 -0
- simml/surrogate/trainer.py +50 -0
- simml-0.1.0.dist-info/METADATA +22 -0
- simml-0.1.0.dist-info/RECORD +7 -0
- simml-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
_C='neural_network'
|
|
3
|
+
_B='gaussian_process'
|
|
4
|
+
_A='linear'
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
class LinearModelConfig(BaseModel):type:Literal[_A]=_A
|
|
8
|
+
class GaussianProcessModelConfig(BaseModel):type:Literal[_B]=_B;n_restarts_optimizer:int=5;normalize_y:bool=True;random_seed:int=42
|
|
9
|
+
class NeuralNetworkModelConfig(BaseModel):type:Literal[_C]=_C;hidden_layer_sizes:list[int]=[64,64];activation:str='relu';solver:str='adam';max_iter:int=2000;early_stopping:bool=True;random_seed:int=42
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
_B='target_names'
|
|
3
|
+
_A='pipeline'
|
|
4
|
+
import warnings
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
import joblib,numpy as np,pandas as pd
|
|
9
|
+
from sklearn.base import clone
|
|
10
|
+
from sklearn.gaussian_process import GaussianProcessRegressor
|
|
11
|
+
from sklearn.metrics import mean_absolute_error,r2_score
|
|
12
|
+
from sklearn.model_selection import KFold,LeaveOneOut,cross_val_predict
|
|
13
|
+
from sklearn.multioutput import MultiOutputRegressor
|
|
14
|
+
from sklearn.pipeline import Pipeline
|
|
15
|
+
from sklearn.preprocessing import StandardScaler
|
|
16
|
+
from.models import GaussianProcessModelConfig
|
|
17
|
+
from.trainer import CVResult,build_pipeline,estimator_from_config
|
|
18
|
+
def build_multi_pipeline(estimator):
|
|
19
|
+
A=estimator
|
|
20
|
+
if isinstance(A,GaussianProcessRegressor):B=MultiOutputRegressor(clone(A),n_jobs=-1)
|
|
21
|
+
else:B=clone(A)
|
|
22
|
+
return Pipeline([('scaler',StandardScaler()),('model',B)])
|
|
23
|
+
@dataclass
|
|
24
|
+
class MultiCVResult:
|
|
25
|
+
model_name:str;target_results:dict[str,CVResult]
|
|
26
|
+
@property
|
|
27
|
+
def mean_q2(self):A=[A.q2 for A in self.target_results.values()if not np.isnan(A.q2)];return float(np.mean(A))if A else float('nan')
|
|
28
|
+
@property
|
|
29
|
+
def mean_r2(self):return float(np.mean([A.r2 for A in self.target_results.values()]))
|
|
30
|
+
def summary(B):
|
|
31
|
+
C=[f"\n ┌─ {B.model_name} (multi-output) [Q² moyen={B.mean_q2:+.4f} R² moyen={B.mean_r2:+.4f}]"]
|
|
32
|
+
for(D,A)in B.target_results.items():E='-'*int(max(0,A.q2)*20);C.append(f" │ {D:<26} R²={A.r2:+.4f} Q²={A.q2:+.4f} RMSE={A.rmse:.3f} LOOCV={A.loocv_rmse:.3f} {E}")
|
|
33
|
+
C.append(f" └{'─'*62}");return'\n'.join(C)
|
|
34
|
+
@dataclass
|
|
35
|
+
class MultiOutputSurrogateTrainer:
|
|
36
|
+
feature_names:list[str];target_names:list[str];model_configs:list[Any];n_folds:int=5;random_seed:int=42;models_dir:Path=Path('models_saved_multi')
|
|
37
|
+
def _estimators(A):return[estimator_from_config(A)for A in A.model_configs]
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _press(y,y_loo):A=y-y_loo;B=float(np.sum(A**2));C=float(np.sum((y-np.mean(y))**2));D=1.-B/C if C>0 else float('nan');E=float(np.sqrt(np.mean(A**2)));return B,D,E
|
|
40
|
+
def run(A,csv_path):
|
|
41
|
+
J='ignore';I=True;E=pd.read_csv(csv_path).dropna(subset=A.target_names);F=E[A.feature_names].values;B=E[A.target_names].values;S=KFold(n_splits=A.n_folds,shuffle=I,random_state=A.random_seed);T=LeaveOneOut();print(f" Dataset : {len(E)} lignes │ {len(A.feature_names)} features │ {len(A.target_names)} cibles (multi-output)");K=[]
|
|
42
|
+
for(C,L)in A._estimators():
|
|
43
|
+
print(f"\n ══ {C} (multi-output) ══");M=build_multi_pipeline(L)
|
|
44
|
+
with warnings.catch_warnings():warnings.simplefilter(J);U=cross_val_predict(M,F,B,cv=S)
|
|
45
|
+
with warnings.catch_warnings():warnings.simplefilter(J);V=cross_val_predict(M,F,B,cv=T)
|
|
46
|
+
N={}
|
|
47
|
+
for(G,O)in enumerate(A.target_names):D,H,W=B[:,G],U[:,G],V[:,G];X,Y,Z=A._press(D,W);N[O]=CVResult(model_name=C,target_name=O,r2=r2_score(D,H),mae=mean_absolute_error(D,H),rmse=float(np.sqrt(np.mean((D-H)**2))),press=X,q2=Y,loocv_rmse=Z)
|
|
48
|
+
P=MultiCVResult(model_name=C,target_results=N);K.append(P);print(P.summary());Q=build_multi_pipeline(L)
|
|
49
|
+
with warnings.catch_warnings():warnings.simplefilter(J);Q.fit(F,B)
|
|
50
|
+
A.models_dir.mkdir(parents=I,exist_ok=I);a=C.replace(' ','_').replace('(','').replace(')','');R=A.models_dir/f"{a}_multi.joblib";joblib.dump({_A:Q,_B:A.target_names},R);print(f" Sauvegardé : {R}")
|
|
51
|
+
return K
|
|
52
|
+
@staticmethod
|
|
53
|
+
def print_best(results):A='═';print(f"\n{A*80}");print(' MEILLEUR MODÈLE MULTI-OUTPUT (Q² moyen)');print(f"{A*80}");B=max(results,key=lambda r:r.mean_q2);print(B.summary());print(f"{A*80}")
|
|
54
|
+
@staticmethod
|
|
55
|
+
def load(path):A=joblib.load(path);return A[_A],A[_B]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import warnings
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
import joblib,numpy as np,pandas as pd
|
|
7
|
+
from sklearn.base import clone
|
|
8
|
+
from sklearn.gaussian_process import GaussianProcessRegressor
|
|
9
|
+
from sklearn.gaussian_process.kernels import RBF,ConstantKernel,WhiteKernel
|
|
10
|
+
from sklearn.linear_model import LinearRegression
|
|
11
|
+
from sklearn.metrics import mean_absolute_error,r2_score
|
|
12
|
+
from sklearn.model_selection import KFold,LeaveOneOut,cross_val_predict
|
|
13
|
+
from sklearn.neural_network import MLPRegressor
|
|
14
|
+
from sklearn.pipeline import Pipeline
|
|
15
|
+
from sklearn.preprocessing import StandardScaler
|
|
16
|
+
from.models import GaussianProcessModelConfig,LinearModelConfig,NeuralNetworkModelConfig
|
|
17
|
+
def build_pipeline(estimator):return Pipeline([('scaler',StandardScaler()),('model',clone(estimator))])
|
|
18
|
+
def estimator_from_config(cfg):
|
|
19
|
+
A=cfg
|
|
20
|
+
if isinstance(A,LinearModelConfig):return'Regression Lineaire',LinearRegression()
|
|
21
|
+
if isinstance(A,GaussianProcessModelConfig):return'Gaussian Process',GaussianProcessRegressor(kernel=ConstantKernel(1.)*RBF(1.)+WhiteKernel(.001),n_restarts_optimizer=A.n_restarts_optimizer,normalize_y=A.normalize_y,random_state=A.random_seed)
|
|
22
|
+
if isinstance(A,NeuralNetworkModelConfig):return'Neural Network (MLP)',MLPRegressor(hidden_layer_sizes=tuple(A.hidden_layer_sizes),activation=A.activation,solver=A.solver,max_iter=A.max_iter,early_stopping=A.early_stopping,random_state=A.random_seed)
|
|
23
|
+
raise ValueError(f"Config modèle non reconnue : {type(A)}")
|
|
24
|
+
@dataclass
|
|
25
|
+
class CVResult:
|
|
26
|
+
model_name:str;target_name:str;r2:float;mae:float;rmse:float;press:float;q2:float;loocv_rmse:float
|
|
27
|
+
def line(A):B='-'*int(max(0,A.q2)*20);return f" {A.model_name:<28} │ {A.target_name:<26} │ R²={A.r2:+.4f} Q²={A.q2:+.4f} MAE={A.mae:.3f} RMSE={A.rmse:.3f} LOOCV={A.loocv_rmse:.3f} PRESS={A.press:.3f} {B}"
|
|
28
|
+
@dataclass
|
|
29
|
+
class SurrogateTrainer:
|
|
30
|
+
feature_names:list[str];target_names:list[str];model_configs:list[Any];n_folds:int=5;random_seed:int=42;models_dir:Path=Path('models_saved')
|
|
31
|
+
def _estimators(A):return[estimator_from_config(A)for A in A.model_configs]
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _compute_press(y_true,y_pred):A=y_true;B=A-y_pred;C=float(np.sum(B**2));D=float(np.sum((A-np.mean(A))**2));E=1.-C/D if D>0 else np.nan;F=float(np.sqrt(np.mean(B**2)));return C,E,F
|
|
34
|
+
def run(A,csv_path):
|
|
35
|
+
H='ignore';G=True;D=pd.read_csv(csv_path).dropna(subset=A.target_names);E=D[A.feature_names].values;P=KFold(n_splits=A.n_folds,shuffle=G,random_state=A.random_seed);Q=LeaveOneOut();R=A._estimators();I=[];print(f" Dataset : {len(D)} lignes │ {len(A.feature_names)} features │ {len(A.target_names)} cibles")
|
|
36
|
+
for C in A.target_names:
|
|
37
|
+
B=D[C].values;print(f"\n ── {C} ──")
|
|
38
|
+
for(J,K)in R:
|
|
39
|
+
L=build_pipeline(K)
|
|
40
|
+
with warnings.catch_warnings():warnings.simplefilter(H);F=cross_val_predict(L,E,B,cv=P)
|
|
41
|
+
with warnings.catch_warnings():warnings.simplefilter(H);S=cross_val_predict(L,E,B,cv=Q)
|
|
42
|
+
T,U,V=A._compute_press(y_true=B,y_pred=S);M=CVResult(model_name=J,target_name=C,r2=r2_score(B,F),mae=mean_absolute_error(B,F),rmse=float(np.sqrt(np.mean((B-F)**2))),press=T,q2=U,loocv_rmse=V);I.append(M);print(M.line());N=build_pipeline(K)
|
|
43
|
+
with warnings.catch_warnings():warnings.simplefilter(H);N.fit(E,B)
|
|
44
|
+
O=A.models_dir/C;O.mkdir(parents=G,exist_ok=G);W=J.replace(' ','_').replace('(','').replace(')','');joblib.dump(N,O/f"{W}.joblib")
|
|
45
|
+
return I
|
|
46
|
+
@staticmethod
|
|
47
|
+
def print_best(results,target_names):
|
|
48
|
+
A='═';print(f"\n{A*100}");print(' MEILLEUR MODÈLE PAR CIBLE (Q²)');print(f"{A*100}")
|
|
49
|
+
for B in target_names:C=max((A for A in results if A.target_name==B),key=lambda r:r.q2);print(C.line())
|
|
50
|
+
print(f"{A*100}")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simml
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A framework for scientific simulation surrogate models.
|
|
5
|
+
Author-email: PinkSalmon <dev@pinksalmon.cloud>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: joblib
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: pydantic
|
|
14
|
+
Requires-Dist: scikit-learn
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: mkdocstrings-python; extra == 'docs'
|
|
22
|
+
Requires-Dist: zensical; extra == 'docs'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
simml/surrogate/__init__.py,sha256=estmhsSPMzyGsAjK8jLQQ0dcScgjxJvTorQueOys7dU,250
|
|
2
|
+
simml/surrogate/models.py,sha256=MfiMRLjt8bHpaZSk6lNSzLkg4M9eGP4YLEQhv4VlhKo,534
|
|
3
|
+
simml/surrogate/multi_trainer.py,sha256=QUwc-7WwFZFPUfWwrnPxvNlF513G6BCdoc8hfSmflfA,3589
|
|
4
|
+
simml/surrogate/trainer.py,sha256=gU6v1xy8OTmoBiUqNiDThzaDyGT_5vsXewGwcySvbvo,3701
|
|
5
|
+
simml-0.1.0.dist-info/METADATA,sha256=biedC7J0cPFLmPFcLx1lESltvIBUalBOI67uhZSpOgs,789
|
|
6
|
+
simml-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
simml-0.1.0.dist-info/RECORD,,
|