code: def grid_search(X, y, param_grid, n_folds=3):
      """Searches for the most optimal parameters in the grid
      Returns trained model and optimal parameters
      """
      best_params = {}
  
      if param_grid:  # Avoid corner case when param_grid is empty
          param_names, param_values_lists = zip(*param_grid.items())
          best_mse = 2.0**32
          for param_values in itertools.product(*param_values_lists):
              kwargs = dict(zip(param_names, param_values))
              linear = neoml.Linear.LinearRegressor(**kwargs)
              folds = 20  # dataset is very tiny
              avg_mse = 0.0
              # Calculate average MSE for K-folds
              for X_train, y_train, X_test, y_test in cv_iterator(
                  X, y, folds
              ):
                  model = linear.train(X_train, y_train)
                  avg_mse += mse(y_test, model.predict(X_test))
              # Update params if MSE is less
              if avg_mse < best_mse:
                  best_mse = avg_mse
                  best_params = kwargs
  
      best_linear = neoml.Linear.LinearRegressor(**best_params)
      return best_linear.train(X, y), best_params
  
imports: import neoml
  import numpy
  import itertools
  from sklearn.datasets import load_boston
label_desc: Use image node_id 3 for calling a global function with example usage: grid_search(X, y, param_grid, n_folds) and returns: best_linear,  best_params