<jupyter_start><jupyter_text>NoteBook de modeloEn este notebook se explica a teoria detras del apilamiento de modelos, una forma de enssemble de hombre pobre. Es una técnica avanzada que mejora los resultados de modelos tradicionales.Para este ejemplo se definira una estrategia de validación, se definira los modelos base para hacerlos robustos a valores atipicos y se optimizara la rata de aprendizaje para optimizar sus resultados<jupyter_code>#Importando librerias nesesarias
from sklearn.linear_model import ElasticNet, Lasso,  BayesianRidge, LassoLarsIC
from sklearn.ensemble import RandomForestRegressor,  GradientBoostingRegressor
from sklearn.kernel_ridge import KernelRidge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import RobustScaler
from sklearn.base import BaseEstimator, TransformerMixin, RegressorMixin, clone
from sklearn.model_selection import KFold, cross_val_score, train_test_split
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import GridSearchCV
import xgboost as xgb

import warnings
warnings.filterwarnings("ignore")

import numpy as np
import pandas as pd<jupyter_output><empty_output><jupyter_text>Estrategia de validaciónLa estrategia de validación es fundamental para determinar la validez del ajuste de los algoritmos utilizados. Para este caso se usara cross validation, agregando una línea de codigo que garantize la mezcla de los datos para mejores resultados.Al ser un problema de regressión se usara como metrica de scoring NMSE (Negative Mean Squared Error), para los problemas de clasificación se puede utilizar r2 o BCE (Binary Cross Entropy)<jupyter_code>n_folds = 5

def rmsle_cv(model):
    kf = KFold(n_folds, shuffle=True, random_state=42).get_n_splits(train.values)
    rmse= np.sqrt(-cross_val_score(model, train.values, target, scoring="neg_mean_squared_error", cv = kf))
    
    return(rmse)

def rmsle(y, y_pred):
    return np.sqrt(mean_squared_error(y, y_pred))<jupyter_output><empty_output><jupyter_text>Importando datos con pandas<jupyter_code>train = pd.read_csv('../csv/clean_train.csv')
test = pd.read_csv('../csv/clean_test.csv')
target = pd.read_csv('../csv/target.csv')
print(train.shape)
print(test.shape)
print(target.shape)
test_id = test['Id']
test.drop('Id', axis=1, inplace=True)<jupyter_output><empty_output><jupyter_text>Modelos baseLos modelos base seran los cuales se apilaran, para este caso se puede trabajar solo con el modelo stacked o promediarlo con otros modelos tipo Boosting como XGB o LightGB. Para este notebook se promedia con XGB.----- LassoEste modelo es muy sensible a valores atipicos, así que para hacerlo más robusto se hara un pipeline con la librería RobustScaler() de Scikit Learn. Refrescando conceptos, un pipeline permite combinar dos metodos para lograr un solo resultado, es increiblemente util en ML dominar este concepto, más información aquí:https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.make_pipeline.html ElasticNetAl igual que Lasso este modelo es suseptible a valores atipicos así que se hace neseario hacer pipeline con RobustScaler() KernelRidgeRegressionEste metodo de por sí ya es robusto a valores atipicos, es una conbinación de Ridge con Kernel PCA. En este caso no se nesecita RobustScaler() Gradient Boost RegressorCuando se tiene valores atipicos, Gradient Boost Regressorse recomienda trabajar con una función de perdida tipo Huber. Modelos de apoyoEstos modelos le agregan un peso por si solos al modelo de ensamble, tienen voto por fuera del stacking. Para estecaso se usaran solo XGBoost, peor LightGB es otra buena opción.---- XGBoostUna variante de Gradient Boost Regresor, se enfoca en combinar diferentes arquitecturas de arboles de desiciones.<jupyter_code>class Models:
    def __init__(self):
        self.reg = {
            'ELASTIC_NET': ElasticNet(l1_ratio=.9, random_state=3),
            'GRADIENT': GradientBoostingRegressor(n_estimators=3000,
                                   max_depth=4, max_features='sqrt',
                                   min_samples_leaf=15, min_samples_split=10, 
                                   loss='huber', random_state =5),
            'LASSO': Lasso(random_state=1),
            'KERNEL_RIDGE': KernelRidge(kernel='polynomial', degree=2, coef0=2.5),
            'XGB': xgb.XGBRegressor(colsample_bytree=0.4603, gamma=0.0468, max_depth=3, 
                             min_child_weight=1.7817, n_estimators=2200,
                             reg_alpha=0.4640, reg_lambda=0.8571,
                             subsample=0.5213, silent=1,
                             random_state =7, nthread = -1)
        }

        self.params = {
            'ELASTIC_NET': {
                'alpha': [0.0005, 0.005, 1]
            }, 
            'GRADIENT': {
                'learning_rate': [0.01, 0.05, 0.1]
            },
            'LASSO': {
                'alpha': [0.0005, 0.005, 1]
            },
            'KERNEL_RIDGE': {
                'alpha': [0.1, 0.5, 0.6]
            },
            'XGB': {
                'learning_rate': [0.05, 0.06, 0.07]
            }
        }


    def grid_training(self, X, y, name):
        best_model = None

        reg_dic = self.reg[name]

        grid_reg = GridSearchCV(reg_dic, self.params[name], cv=3)
        grid_reg.fit(X, y.values.ravel())

        #Modelos base más robustos a valores atipicos, usando robust scaler: Lasso y ElasticNet. 
        if name == 'ELASTIC_NET' or name == 'LASSO': 
            best_model = make_pipeline(RobustScaler(), grid_reg.best_estimator_)
        else:
            best_model = grid_reg.best_estimator_

        return best_model
models = ['ELASTIC_NET', 'GRADIENT', 'LASSO', 'KERNEL_RIDGE', 'XGB']
base_models = []
for model in models:
        base_model = Models().grid_training(train,target,model)
        base_models.append(base_model)<jupyter_output><empty_output><jupyter_text>Embedding con modelos apiladosEl paso a paso detras de esta técncia es la siguiente: * Dividir el trainig set en dos partes. Train y holdout. Para esto se clonan los modelos* Entrenar estos modelos en la primera parte* Probar estos modelos en la segunda parte* Usar la predicciones con la metodologia fold como las entradas y las respuestas correctas (La variable objetivo) como la salida de alto nivel![image.png](attachment:image.png)Imagen tomada de https://www.kaggle.com/getting-started/18153post103381Este metodo se puede hacer más robusto agregando meta modelos en el ultimo paso, agregando un loop que repita los ultimos tres de forma iterativa y luego hacer un promedio de los modelos base sobre la data de prueba y usar estas como meta features en los meta modelos. Dado que con la primera opción se obtiene un resultado del 90% de presición, no implatare el meta modelo en la solución, sin embargo dejare la estructura de la clase por si alguien está interesado en hacerlo.<jupyter_code>class AveragingModels(BaseEstimator, RegressorMixin, TransformerMixin):
    def __init__(self, models):
        self.models = models
        
    # Definiendo clones de los modelos base para entrenar la data
    def fit(self, X, y):
        self.models_ = [clone(x) for x in self.models]
        
        # Entrenando los modelos base clonados
        for model in self.models_:
            model.fit(X, y)

        return self
    
    #Prediciendo los modelos base y promediandolos
    def predict(self, X):
        predictions = np.column_stack([
            model.predict(X) for model in self.models_
        ])
        return np.mean(predictions, axis=1) 
averaged_models = AveragingModels(models = (base_models[0], 
                                            base_models[1], 
                                            base_models[2], 
                                            base_models[3]))
score = rmsle_cv(averaged_models)
print(" Averaged base models score: {:.4f} ({:.4f})\n".format(score.mean(), score.std()))
averaged_models.fit(train, target)
pred = averaged_models.predict(test)

xgb = base_models[4]
xgb.fit(train, target)
xgb_pred_train = xgb.predict(train)
xgb_pred = xgb.predict(test)
rmsle(target, xgb_pred_train)
enssemble = pred*0.6 + xgb_pred*0.3
sub = pd.DataFrame()
sub['Id'] = test_id
sub['SalePrice'] = np.expm1(enssemble)
print(sub.head())<jupyter_output>Id     SalePrice
0  1461  37096.338440
1  1462  49087.601088
2  1463  54685.353372
3  1464  57276.752902
4  1465  56916.214770<jupyter_text>Bonus: Clase con meta modelos<jupyter_code>class StackingAveragedModels(BaseEstimator, RegressorMixin, TransformerMixin):
    def __init__(self, base_models, meta_model, n_folds=5):
        self.base_models = base_models
        self.meta_model = meta_model
        self.n_folds = n_folds
   
    # Nuevamente entrenamos los clones
    def fit(self, X, y):
        self.base_models_ = [list() for x in self.base_models]
        self.meta_model_ = clone(self.meta_model)
        kfold = KFold(n_splits=self.n_folds, shuffle=True, random_state=156)
        
        # Entrenamos clones y creamos predicciones flod
        # que se nesecitan para entrenar los meta-modelos
        out_of_fold_predictions = np.zeros((X.shape[0], len(self.base_models)))
        for i, model in enumerate(self.base_models):
            for train_index, holdout_index in kfold.split(X, y):
                instance = clone(model)
                self.base_models_[i].append(instance)
                instance.fit(X[train_index], y[train_index])
                y_pred = instance.predict(X[holdout_index])
                out_of_fold_predictions[holdout_index, i] = y_pred
                
        # Ahora entrenamos los meta-modelos clonados usando prediciones out-of-fold como nueva caracteristica
        self.meta_model_.fit(out_of_fold_predictions, y)
        return self
   
    #Hacemos las predcciones de todos los modelos base con la data de prueba y usamos las predicciones promedio como 
    #meta-caracteristicas para la predicción final la cual es hecha por el meta-modelo
    def predict(self, X):
        meta_features = np.column_stack([
            np.column_stack([model.predict(X) for model in base_models]).mean(axis=1)
            for base_models in self.base_models_ ])
        return self.meta_model_.predict(meta_features)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07928488146132918, 'fraction_numerical': 0.02108433734939759, 'mean_word_length': 3.2063751532488762, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 24, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '528162', 'license': 'CC0-1.0', 'n_tokens_mistral': 3432, 'n_tokens_neox': 3259, 'n_words': 955, 'path': 'NoteBook/.ipynb_checkpoints/Model-checkpoint.ipynb', 'repo_name': 'p1gm1/ML-Regression-python', 'size': '10292'}