Python {Article062}

ようこそ「Python」へ...

タイタニックで機械学習のGridSearchCVを学ぶには【sklearn GridSearchCV】

タイタニックのサバイバルデータで機械学習(Machine Learning)シリーズは、次の8つの記事から構成されています。 機械学習に興味のある方は、以下に掲載されている記事を順番に読むことをおすすめします。

この記事では機械学習(ML: Machine Learning)でGridSearchCV(Grid Search Cross-Validation)を使用してモデルを検証する方法を解説します。 Cross-Validationを使用してモデル(SVC)を評価するには、SVCのさまざまなパラメータを組み合わせて行う必要があります。 行4-7ではCross-ValidationでSVCのパラメータ「kernel, gamma」の値を切り替えて評価しています。

from sklearn import svm, datasets
from sklearn.model_selection import cross_val_score
iris = datasets.load_iris()
cross_val_score(svm.SVC(kernel='rbf', C=10, gamma='auto'), iris.data, iris.target, cv=5)
cross_val_score(svm.SVC(kernel='rbf', C=20, gamma='auto'), iris.data, iris.target, cv=5)
cross_val_score(svm.SVC(kernel='linear', C=10, gamma='auto'), iris.data, iris.target, cv=5)
cross_val_score(svm.SVC(kernel='linear', C=20, gamma='auto'), iris.data, iris.target, cv=5)
SVCのパラメータの全ての組み合わせてを検証するには、行5-8のようにforループをネストさせる必要があります。 ここではSVCの2個のパラメータ「kernel, C」の組み合わせしか検証していませんが、パラメータが多くなれば複雑なforループとなります。
import numpy as np
kernels = ['rbf', 'linear']
C = [10, 20]
avg_scores = {}
for kval in kernels:
    for cval in C:
        cv_scores = cross_val_score(svm.SVC(kernel=kval, C=cval, gamma='auto'), iris.data, iris.target, cv=5)
        avg_scores[kval + '_' + str(cval)] = np.average(cv_scores)
avg_scores
Sklearnはこのような不都合を回避するために、GridSearchCVをサポートしています。 GridSearchCVを使用すると前出のforループの処理を1行で組み込むことができます (行2-4ではGridSearchCVの記述を見やすくするために改行しています)。
from sklearn.model_selection import GridSearchCV
clf = GridSearchCV(svm.SVC(gamma='auto'), 
    {'C': [10,20], 'kernel': ['rbf','linear']}, 
    cv=5, return_train_score=False)
clf.fit(iris.data, iris.target)
clf.cv_results_

最近よくAI(Artificial Intelligence)、ML(Machine Learning)、DL(Deep Learning)という言葉を聞きますが、 AIはMLとDLを含んだ総称です。 そしてMLはDLを含んでいます。 そしてDLはNeural Networks(ニューラルネットワーク)を使用しています。 MLとDLはAIのサブセットということになります。 DL(Deep Learning)については「記事(Article028)」で詳しく解説しています。

ML(Machine Learning)は、Supervised Learning, UnSupervised Learning, Reinforcement Learningの3つに分類されています。 それぞれのタイプの概念は図(B, C, D)を参照してください。 そしてSupervised LearningはアルゴリズムによりReguression, Classfication, Clusteringの3つに分類されています。 それぞれのアルゴリズムの種類は図(E)を参照してください。

今回予測するのは、 タイタニック号の乗船客が「生存するか」「死亡するか」の2択ですから、 MLのClassficationのアルゴリズムを利用することになります。 ここではClassficationの8種類のアルゴリズム (KNeighborsClassifier, DecisionTreeClassifier, RandomForestClassifier, GaussianNB, SVC, ExtraTreeClassifier, GradientBoostingClassifier, AdaBoostClassifier) を使用して乗船客の生死を予測します。

ML(Macine Learning)を使用して予測するとき、予測するデータの属性によりアルゴリズム(モデル)の評価方法が異なります。 「記事(Article056)」で解説した売上データからお客さんが商品を「買う、買わない」といった予測では、 accuracy_score(正解率)で予測値を評価してもとくに問題はありません。 ところが、今回のタイタニックのような人間の「生死」を予測するケースでは、accuracy_score(正解率)だけで予測を評価することはできません。 たとえば、モデルが「死亡する」と予測して、その予測が外れてもとくに問題にはなりません。 ところが、モデルが「生存する」と予測して、その予測が外れると「死亡」するということになるので問題になります。 このような場合は、モデルの予測値を4パターンに分けて評価する必要があります。 MLはこれら4パターンの評価情報を取得する方法としてclassification_report()とconfusion_matrix()メソッドを用意しています。

「初級」編では、データの取り込み、データの分析(可視化)、学習、予測、予測評価の順番に説明します。 「予測評価」では、予測を評価するための情報を取得する3種類の方法(メソッド)を解説します。 さらに、なぜclassification_report(), confusion_matrix(), accuracy_score()の3種類のメソッドが用意されているのか、 そして、これらのメソッドで取得した評価情報をどのように活用するのかについても説明しています。

「中級」編では、Pandas、Matplotlibを使用したデータ分析を詳しく解説しています。 データを分析するには可視化することが重要ですが、Pandasのplot()メソッドを使用する簡単にデータを可視化することができます。 さらにMatplotlibを使用するとグラフを見栄えよくする、見やすくする、グラフにさまざまな補足情報を表示するといったことが可能になります。

「上級」編では、複数のアルゴリズム(モデル)を使用して実際に予測して、予測を評価する方法について解説しています。 予測値を調整するには、モデルにさまざまなパラメータを追加して、さらにパラメータの値(範囲)も同時に調整する必要があります。 これらを効率的に行う方法としてPipelineを使用したGridSearchCV()、RandomizedSearchCV()メソッドについて解説しています。 RandomizedSearchCV()を使用すると、モデルにどのようなパラメータを追加すると予測が改善するかを効率的に行うことができます。 さらに、GridSerachCV()を使用すると、モデルのパラメータの値(範囲)の調整を効率的に行うことができます。

ここではVisula Studio Code(VSC)の「Python Interactive window」 を使用してJupter Notebookのような環境で説明します。 VSCを通常の環境からインタラクティブな環境に切り換えるにはコードを記述するときコメント「# %%」を入力します。 詳しい、操作手順については「ここ」 を参照してください。 インタラクティブな環境では、Pythonの「print(), plt.show()」などを使う必要がないので掲載しているコードでは省略しています。 VSCで通常の環境で使用するときは、必要に応じて「print(), plt.show()」等を追加してください。

この記事では、Pandas、Matplotlibのライブラリを使用しますので 「記事(Article001) | 記事(Article002) | 記事(Article003) | 記事(Article004)」 を参照して事前にインストールしておいてください。 Pythonのコードを入力するときにMicrosoftのVisula Studio Codeを使用します。 まだ、インストールしていないときは「記事(Article001)」を参照してインストールしておいてください。

説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。

click image to zoom!
図A: AI/ML/DL
click image to zoom!
図B: Supervised
click image to zoom!
図C: UnSupervised
click image to zoom!
図D: Reinforcement
click image to zoom!
図E: Algorithms
click image to zoom!
図F: Cross-Validation
click image to zoom!
GridSearchCV[1]
click image to zoom!
GridSearchCV[2]


GridSearchCVを使用してCross-Validationを行う

  1. まずはPythonのライブラリを取り込む

    Visual Studio Code(VSC)を起動したら新規ファイルを作成して行1-30をコピペします。 行2-28ではPythonのライブラリを取り込んでいます。 行30ではPythonの警告を抑止しています。 ライブラリをまだインストールしていないときは「pip install」で事前にインストールしておいてください。
    ### Import the libraries
    from functools import reduce
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from pandas.core.reshape.reshape import stack 
    import seaborn as sns
    
    # Importing Classifier Modules
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.tree import DecisionTreeClassifier, ExtraTreeClassifier
    from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, BaggingClassifier, AdaBoostClassifier, GradientBoostingClassifier
    from sklearn.naive_bayes import GaussianNB
    from sklearn.svm import SVC     
    
    # Cross Validation(k-fold)
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    
    # Pipeline and GridSearchCV
    from sklearn.preprocessing import StandardScaler
    from sklearn.model_selection import train_test_split
    from sklearn.pipeline import Pipeline
    from sklearn.model_selection import GridSearchCV
    from sklearn.metrics import precision_score, recall_score, accuracy_score, make_scorer, f1_score, roc_auc_score
    import sklearn.metrics as skm 
    
    import warnings
    
    warnings.simplefilter('ignore')
    click image to zoom!
    図1
    図1はVisual Studio Code(VSC)の画面です。
  2. PandasのDataFrameにタイタニックのサバイバルデータを取り込む

    行2ではCSVファイルのパスを定義しています。 当サイトからダウンロードするときは行3のコメント(#)を外してください。 行4ではPandasのread_csv()メソッドでCSVファイルをDataFrameに取り込んでいます。
    ### Load the data
    train_file = 'data/csv/titanic/train_cleaned.csv'
    #train_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/train_cleaned.csv'
    train = pd.read_csv(train_file)
    
    temp = train.copy()
    X = temp.drop('Survived', axis=1)    # Exclude Survived column
    y = temp['Survived']                 # Survived column only
    click image to zoom!
    図2-1
    図2-1にはDataFrame(X)の構造と内容が表示されています。 DataFrame(X)には891人の乗船客のデータが格納されています。
    click image to zoom!
    図2-2
    図2-2にはSeries(y)の件数と内容が表示されています。 Series(y)には乗船客の生死(0:Dead, 1:Survived)が格納されています。
  3. GridSearchCVで複数のモデルを評価する

    行2ではデータを75対25に分割しています。 行4-63では評価するモデルを定義しています。 GridSearchCV()を使用するときは、モデルのパラメータ値を連続ではなく不連続な飛び飛びの値を指定します。 なぜかと言えば、計測時間を節約するためです。 ここではPythonの「list(range())」を使用して不連続な値を生成しています。 ちなみにRandomizedSearchCV()を使用するときは連続したパラメータ値を指定します。

    行68-81ではGridSearchCV()とfit()で複数のモデルの学習・評価を行っています。 GridSearchCV()の引数に「cv=5」を指定しているので各モデルとも5回学習・評価を行います。 行93-121ではモデルの評価結果を表示しています。
    ### Modeling
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)  
    
    model_params = {
        'clf0': {
            'model': KNeighborsClassifier(),
            'params': {
                'n_neighbors': list(range(3, 15, 2)), 
                'weights': ['uniform', 'distance'],
                'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']                
            }       
        },     
        'clf1': {
            'model': DecisionTreeClassifier(),
            'params': {
                'criterion': ['gini','entropy'],
                'splitter': ['best','random']              
            }       
        },
        'clf2': {
            'model': RandomForestClassifier(),
            'params': {
                'n_estimators': list(range(10, 100, 10)),
                'criterion': ['gini','entropy']              
            }       
        },
        'clf3': {
            'model': GaussianNB(),
            'params': {}       
        }, 
        'clf4': {
            'model': SVC(),
            'params': {
                'kernel': ['linear', 'poly', 'rbf'], 
                'gamma': ['scale','auto'], 
                'decision_function_shape': ['ovr','ovo']              
            }       
        }, 
        'clf5': {
            'model': ExtraTreeClassifier(),
            'params': {   
                #'n_estimators': list(range(10, 100, 10)), 
                'criterion': ['gini','entropy'], 
                'max_features': ['auto','sqrt','log2'], 
                'class_weight': ['balanced', 'balanced_subsample']            
            }       
        }, 
        'clf6': {
            'model': GradientBoostingClassifier(),
            'params': {
                'n_estimators': list(range(10, 100, 10)),     
                'loss' : ['deviance','exponential'],
                'max_features':['auto','sqrt','log2']               
            }       
        }, 
        'clf7': {
            'model': AdaBoostClassifier(),
            'params': {
                'n_estimators': list(range(10, 100, 10)), 
                'algorithm': ['SAMME.R','SAMME']              
            }       
        }                                                                        
    }
    
    df = pd.DataFrame()   
    
    # Grid Search Cross-Validation
    for _, mp in model_params.items():                     
        grid = GridSearchCV(mp['model'], mp['params'], cv=5, verbose=0,
                    scoring={
                            'f1_score': make_scorer(f1_score),                        
                            'accuracy': make_scorer(accuracy_score),
                            'precision': make_scorer(precision_score),
                            'recall': make_scorer(recall_score),
                            'ROC_AUC': make_scorer(roc_auc_score)
                            },
                    n_jobs=-1,        
                    refit='accuracy', 
                    return_train_score=True)                                       
          
        grid.fit(X, y)  
        classifier_name = mp['model'].__class__.__name__
        grid_best_params = grid.best_params_ 
        grid_score = grid.score(X_test, y_test)
        results = grid.cv_results_
    
        results['classifier_name'] = classifier_name       
        results['grid_score'] = grid_score
        results['grid_best_params'] = grid_best_params     
        df = df.append(results, ignore_index=True)
    
    # Print results
    for ix, row in df.iterrows():
        x60 = '-'*60
        cls_name = row['classifier_name']
        print(f'{x60} {ix}: {cls_name}')
        grid_score = row['grid_score'] * 100
        print(f'Grid_Score: {grid_score:.2f}') 
    
        f1_score_min = row['mean_test_f1_score'].min() * 100
        f1_score_max = row['mean_test_f1_score'].max() * 100
        print(f'F1_Score: {f1_score_min:.2f} - {f1_score_max:.2f}')
    
        accuracy_min = row['mean_test_accuracy'].min() * 100
        accuracy_max = row['mean_test_accuracy'].max() * 100
        print(f'Accuracy: {accuracy_min:.2f} - {accuracy_max:.2f}')
    
        precision_min = row['mean_test_precision'].min() * 100
        precision_max = row['mean_test_precision'].max() * 100
        print(f'Precision: {precision_min:.2f} - {precision_max:.2f}')
    
        recall_min = row['mean_test_recall'].min() * 100
        recall_max = row['mean_test_recall'].max() * 100
        print(f'Recall: {recall_min:.2f} - {recall_max:.2f}')
    
        roc_auc_min = row['mean_test_ROC_AUC'].min() * 100
        roc_auc_max = row['mean_test_ROC_AUC'].max() * 100
        print(f'ROC_AUC: {roc_auc_min:.2f} - {roc_auc_max:.2f}')
    
        grid_best_params = row['grid_best_params']
        print('Grid_best_params:', grid_best_params)
    click image to zoom!
    図3-1
    図3-1は実行結果です。 各モデルのスコアとベスト・パラメータが表示されています。 もっとのスコアが高いのは「DecisionTreeClassifier」の「92.38」です。 そして「DecisionTreeClassifier」のベスト・パラメータは「{'criterion': 'gini', 'splitter': 'best'}」ということになります。

    F1-Score, Precision, Racall, Accuracy等の意味と使い方については「記事(Article048)」で詳細に解説しています。
    click image to zoom!
    図3-2
    図3-2は実行結果です。
  4. ここで解説したコードをまとめて掲載

    最後にここで解説したすべてのコードをまとめて掲載しましたので参考にしてください。
    
    ### Import the libraries
    from functools import reduce
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    from pandas.core.reshape.reshape import stack 
    import seaborn as sns
    
    # Importing Classifier Modules
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.tree import DecisionTreeClassifier, ExtraTreeClassifier
    from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier, BaggingClassifier, AdaBoostClassifier, GradientBoostingClassifier
    from sklearn.naive_bayes import GaussianNB
    from sklearn.svm import SVC     
    
    # Cross Validation(k-fold)
    from sklearn.model_selection import KFold
    from sklearn.model_selection import cross_val_score
    
    # Pipeline and GridSearchCV
    from sklearn.preprocessing import StandardScaler
    from sklearn.model_selection import train_test_split
    from sklearn.pipeline import Pipeline
    from sklearn.model_selection import GridSearchCV
    from sklearn.metrics import precision_score, recall_score, accuracy_score, make_scorer, f1_score, roc_auc_score
    import sklearn.metrics as skm 
    
    import warnings
    
    warnings.simplefilter('ignore')
    
    
    # %%
    
    ### Load the data
    train_file = 'data/csv/titanic/train_cleaned.csv'
    #train_file = 'https://money-or-ikigai.com/menu/python/article/data/titanic/train_cleaned.csv'
    train = pd.read_csv(train_file)
    
    temp = train.copy()
    X = temp.drop('Survived', axis=1)    # Exclude Survived column
    y = temp['Survived']                 # Survived column only
    
    
    # %%
    
    ### Modeling
    
    #clf0 = KNeighborsClassifier()                       # n_neighbors=[3,5,10,15], weights={'uniform', 'distance'}, algorithm={'auto', 'ball_tree', 'kd_tree', 'brute'}
    # KNeighborsClassifier(n_neighbors=5, *, weights={‘uniform’, ‘distance’} , algorithm={‘auto’, ‘ball_tree’, ‘kd_tree’, ‘brute’}, 
    # leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None)
    
    #clf1 = DecisionTreeClassifier()                     # criterion={'gini','entropy'}, splitter={'best','random'}
    # DecisionTreeClassifier(*, criterion={'gini','entropy'}, splitter={'best','random'}, max_depth=None, 
    # min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, 
    # max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, 
    # class_weight=None, ccp_alpha=0.0)
    
    #clf2 = RandomForestClassifier()                     # n_estimators=1-100, criterion={'gini','entropy'}
    # RandomForestClassifier(n_estimators=100, *, criterion={'gini','entropy'}, 
    # max_depth=None, min_samples_split=2, min_samples_leaf=1, 
    # min_weight_fraction_leaf=0.0, max_features='auto', max_leaf_nodes=None, 
    # min_impurity_decrease=0.0, bootstrap=True, oob_score=False, 
    # n_jobs=None, random_state=None, verbose=0, warm_start=False, 
    # class_weight=None, ccp_alpha=0.0, max_samples=None)
    
    #clf3 = GaussianNB()                                 # None
    # GaussianNB(*, priors=None, var_smoothing=1e-09)
    
    #clf4 = SVC()                                        # kernel={'linear', 'poly', 'rbf', 'sigmoid', 'precomputed'}, gamma={'scale','auto'}, decision_function_shape={'ovr','ovo'}    
    # SVC(*, C=1.0, kernel='rbf', degree=3, gamma='scale', coef0=0.0, 
    # shrinking=True, probability=False, tol=0.001, cache_size=200, 
    # class_weight=None, verbose=False, max_iter=- 1, 
    # decision_function_shape='ovr', break_ties=False, random_state=None)
    
    #clf5 = ExtraTreesClassifier()                       # n_estimators=10-100, criterion={'gini','entropy'}, max_features={'auto','sqrt','log2'}, class_weight={'balanced', 'balanced_subsample'}
    # ExtraTreesClassifier(n_estimators=100, *, criterion='gini', max_depth=None, 
    # min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, 
    # max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, 
    # bootstrap=False, oob_score=False, n_jobs=None, random_state=None, verbose=0, 
    # warm_start=False, class_weight=None, ccp_alpha=0.0, max_samples=None)
    
    #clf6 = GradientBoostingClassifier()                 # loss={'deviance','exponential'}, n_estimators=10-100
    # GradientBoostingClassifier(*, loss='deviance', learning_rate=0.1, n_estimators=100, 
    # subsample=1.0, criterion='friedman_mse', min_samples_split=2, min_samples_leaf=1, 
    # min_weight_fraction_leaf=0.0, max_depth=3, min_impurity_decrease=0.0, 
    # init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None, 
    # warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
    
    #clf7 = AdaBoostClassifier()                         # n_estimators=10-100, algorithm={'SAMME.R','SAMME'}
    # AdaBoostClassifier(base_estimator=None, *, n_estimators=50, learning_rate=1.0, algorithm='SAMME.R', random_state=None)
    
    # Train-test Split
    # sklearn.model_selection.train_test_split(# *arrays, test_size=None, train_size=None, 
    # random_state=None, shuffle=True, stratify=None)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)  
    
    model_params = {
        'clf0': {
            'model': KNeighborsClassifier(),
            'params': {
                'n_neighbors': list(range(3, 15, 2)), 
                'weights': ['uniform', 'distance'],
                'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute']                
            }       
        },     
        'clf1': {
            'model': DecisionTreeClassifier(),
            'params': {
                'criterion': ['gini','entropy'],
                'splitter': ['best','random']              
            }       
        },
        'clf2': {
            'model': RandomForestClassifier(),
            'params': {
                'n_estimators': list(range(10, 100, 10)),
                'criterion': ['gini','entropy']              
            }       
        },
        'clf3': {
            'model': GaussianNB(),
            'params': {}       
        }, 
        'clf4': {
            'model': SVC(),
            'params': {
                'kernel': ['linear', 'poly', 'rbf'], 
                'gamma': ['scale','auto'], 
                'decision_function_shape': ['ovr','ovo']              
            }       
        }, 
        'clf5': {
            'model': ExtraTreeClassifier(),
            'params': {   
                #'n_estimators': list(range(10, 100, 10)), 
                'criterion': ['gini','entropy'], 
                'max_features': ['auto','sqrt','log2'], 
                'class_weight': ['balanced', 'balanced_subsample']            
            }       
        }, 
        'clf6': {
            'model': GradientBoostingClassifier(),
            'params': {
                'n_estimators': list(range(10, 100, 10)),     
                'loss' : ['deviance','exponential'],
                'max_features':['auto','sqrt','log2']               
            }       
        }, 
        'clf7': {
            'model': AdaBoostClassifier(),
            'params': {
                'n_estimators': list(range(10, 100, 10)),
                'algorithm': ['SAMME.R','SAMME']              
            }       
        }                                                                        
    }
    
    df = pd.DataFrame()   
    
    # Grid Search Cross-Validation
    for _, mp in model_params.items():                     
        grid = GridSearchCV(mp['model'], mp['params'], cv=5, verbose=0,
                    scoring={
                            'f1_score': make_scorer(f1_score),                        
                            'accuracy': make_scorer(accuracy_score),
                            'precision': make_scorer(precision_score),
                            'recall': make_scorer(recall_score),
                            'ROC_AUC': make_scorer(roc_auc_score)
                            },
                    n_jobs=-1,        
                    refit='accuracy', 
                    return_train_score=True)                                       
            
        # GridSearchCV(estimator, param_grid, *, scoring=None, n_jobs=None, 
        # refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', 
        # error_score=nan, return_train_score=False)
    
        grid.fit(X, y)  # X_train, y_train
    
        classifier_name = mp['model'].__class__.__name__
        #print('+'*60, classifier_name)
    
        grid_best_params = grid.best_params_
        #print(f'best_params = {grid_best_params}')     
    
        grid_score = grid.score(X_test, y_test)
        #print(f'grid_score = {grid_score*100:.2f}')
    
        results = grid.cv_results_
        #print('type(results) =', type(results)) # dict type
    
        results['classifier_name'] = classifier_name       
        results['grid_score'] = grid_score
        results['grid_best_params'] = grid_best_params     
        df = df.append(results, ignore_index=True)
    
    # Print results
    for ix, row in df.iterrows():
        #print(ix, row)
        x60 = '-'*60
        cls_name = row['classifier_name']
        print(f'{x60} {ix}: {cls_name}')
        grid_score = row['grid_score'] * 100
        print(f'Grid_Score: {grid_score:.2f}') 
    
        f1_score_min = row['mean_test_f1_score'].min() * 100
        f1_score_max = row['mean_test_f1_score'].max() * 100
        print(f'F1_Score: {f1_score_min:.2f} - {f1_score_max:.2f}')
    
        accuracy_min = row['mean_test_accuracy'].min() * 100
        accuracy_max = row['mean_test_accuracy'].max() * 100
        print(f'Accuracy: {accuracy_min:.2f} - {accuracy_max:.2f}')
    
        precision_min = row['mean_test_precision'].min() * 100
        precision_max = row['mean_test_precision'].max() * 100
        print(f'Precision: {precision_min:.2f} - {precision_max:.2f}')
    
        recall_min = row['mean_test_recall'].min() * 100
        recall_max = row['mean_test_recall'].max() * 100
        print(f'Recall: {recall_min:.2f} - {recall_max:.2f}')
    
        roc_auc_min = row['mean_test_ROC_AUC'].min() * 100
        roc_auc_max = row['mean_test_ROC_AUC'].max() * 100
        print(f'ROC_AUC: {roc_auc_min:.2f} - {roc_auc_max:.2f}')
    
        grid_best_params = row['grid_best_params']
        print('Grid_best_params:', grid_best_params)