<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 This notebook provides an example on how to use a custom class within Flexcode. <br>
In order to be compatible, a regression method needs to have a fit and predict method implemented - i.e. 
model.fit() and model.predict() need to be the functions used for training and predicting respectively.
We provide here an example with artifical data. <br>
We compare the FlexZBoost (Flexcode with builtin XGBoost) with the custom class of FLexcode when passing
XGBoost Regressor. The two should give basically identical results.
Step1:  Data Creation
Step2:  FlexZBoost
Step3:  Custom Model
Our custom model in this case is going to be XGBRegressor. <br>
The only difference with the above is that we are going to use the CustomModel class and we are going to pass
XGBRegressor as custom_model.
After that, everything is exactly as above. <br>
Parameters can be passed also in the same way as above.
Step4:  The two conditional density estimates should be the same across the board. <br>
We check the maximum difference in absolute value between the two.
<ASSISTANT_TASK:>
Python Code:
import flexcode
import numpy as np
import xgboost as xgb
from flexcode.regression_models import XGBoost, CustomModel
Explanation: This notebook provides an example on how to use a custom class within Flexcode. <br>
In order to be compatible, a regression method needs to have a fit and predict method implemented - i.e. 
model.fit() and model.predict() need to be the functions used for training and predicting respectively.
We provide here an example with artifical data. <br>
We compare the FlexZBoost (Flexcode with builtin XGBoost) with the custom class of FLexcode when passing
XGBoost Regressor. The two should give basically identical results.
End of explanation
def generate_data(n_draws):
    x = np.random.normal(0, 1, n_draws)
    z = np.random.normal(x, 1, n_draws)
    return x, z
x_train, z_train = generate_data(5000)
x_validation, z_validation = generate_data(5000)
x_test, z_test = generate_data(5000)
Explanation: Data Creation
End of explanation
# Parameterize model
model = flexcode.FlexCodeModel(XGBoost, max_basis=31, basis_system="cosine",
                             regression_params={'max_depth': 3, 'learning_rate': 0.5, 'objective': 'reg:linear'})
# Fit and tune model
model.fit(x_train, z_train)
cdes_predict_xgb, z_grid = model.predict(x_test, n_grid=200)
model.__dict__
import pickle
pickle.dump(file=open('example.pkl', 'wb'), obj=model, 
            protocol=pickle.HIGHEST_PROTOCOL)
model = pickle.load(open('example.pkl', 'rb'))
model.__dict__
cdes_predict_xgb, z_grid = model.predict(x_test, n_grid=200)
Explanation: FlexZBoost
End of explanation
# Parameterize model
my_model = xgb.XGBRegressor
model_c = flexcode.FlexCodeModel(CustomModel, max_basis=31, basis_system="cosine",
                                 regression_params={'max_depth': 3, 'learning_rate': 0.5, 'objective': 'reg:linear'},
                                 custom_model=my_model)
# Fit and tune model
model_c.fit(x_train, z_train)
cdes_predict_custom, z_grid = model_c.predict(x_test, n_grid=200)
Explanation: Custom Model
Our custom model in this case is going to be XGBRegressor. <br>
The only difference with the above is that we are going to use the CustomModel class and we are going to pass
XGBRegressor as custom_model.
After that, everything is exactly as above. <br>
Parameters can be passed also in the same way as above.
End of explanation
np.max(np.abs(cdes_predict_custom - cdes_predict_xgb))
Explanation: The two conditional density estimates should be the same across the board. <br>
We check the maximum difference in absolute value between the two.
End of explanation
<END_TASK>