準確預測Fitbit的睡眠得分
在本文的前兩部分中,我獲取了Fitbit的睡眠數據並對其進行預處理,將這些數據分為訓練集、驗證集和測試集,除此之外,我還訓練了三種不同的機器學習模型並比較了它們的性能。
在第2部分中,我們看到使用隨機森林和xgboost默認超參數,並在驗證集上評估模型性能會導致多元線性回歸表現最佳,而隨機森林和xgboost回歸的表現稍差一些。
在本文的這一部分中,我將討論只使用一個驗證集的缺點。除此之外,我們還會談到如何解決這些缺點以及如何調優模型超參數以提高性能。就讓我們一探究竟吧。
交叉驗證
簡單訓練、驗證和測試分割的缺點
在本文的第2部分中,我們將數據分為訓練、驗證和測試集,在訓練集上訓練我們的模型並在驗證集上對模型進行評估。我們還沒有接觸到測試集,因為它是保留集,它代表的是從未見過的數據,一旦我們覺得機器學習模型有能力進行最終測試,這些數據將用於評估它們的泛化程度。
因為我們只將數據分成了一組訓練數據和一組驗證數據,所以模型的性能指標高度依賴於這兩組數據。機器學習模型只進行一次訓練和評估,因此它的性能就取決於那一次評估。而且在對同一數據的不同子集進行訓練和評估時,學習模型的表現可能會非常不同,這僅僅是因為選取的子集不同。
如果我們把這個過程分解為多次訓練和驗證測試,每次訓練和評估我們的模型都是在不同的數據子集上,最後在多次評估中觀察模型的平均表現會怎麼樣呢?這就是K-fold交叉驗證背後的想法。
K-fold交叉驗證
在K-fold交叉驗證(CV)中,我們仍然要先從需要被處理的數據集中分離出一個測試/保留集,以用於模型的最終評估。剩下的數據,即除測試集之外的所有數據,將被分割成K個摺疊數(子集)。然後交叉驗證迭代這些摺疊,在每次迭代中使用一個K摺疊作為驗證集,同時使用所有剩餘的摺疊作為訓練集。重複這個過程,直到每個摺疊都被用作驗證集。以下是5倍交叉驗證的流程:
將模型在同一個訓練數據的不同子集進行K次訓練和測試,我們可以更準確地表示我們的模型在它以前沒有見過的數據上的表現。在K-fold CV中,我們在每次迭代後對模型進行評分,並計算所有評分的平均值。這樣就可以更好地表示該方法與只使用一個訓練和驗證集相比,模型的表現是怎樣的。
Python中的K-fold交叉驗證
因為Fitbit睡眠數據集相對較小,所以我將使用4倍交叉驗證,並將目前使用的多元線性回歸、隨機森林和xgboost回歸這三種模型進行比較。
請注意,四倍CV可以很好地與第2部分中分離出來的訓練數據和驗證數據進行比較,因為我們將數據分割為75%的訓練數據和25%的驗證數據。一個四倍CV本質上也是如此,只是四次,每次使用不同的子集。我創建了一個函數,它將我們想要比較的模型列表,特徵數據,目標變量數據以及我們想要創建的摺疊數作為輸入。該函數計算我們之前使用的性能度量並返回一個表格,其中包含所有模型的平均值以及每種度量類型的每一頁的得分,以備我們進一步研究。函數如下:
# Define a function that compares the CV perfromance of a set of predetrmined models def cv_comparison(models, X, y, cv): # Initiate a DataFrame for the averages and a list for all measures cv_accuracies = pd.DataFrame() maes = [] mses = [] r2s = [] accs = [] # Loop through the models, run a CV, add the average scores to the DataFrame and the scores of # all CVs to the list for model in models: mae = -np.round(cross_val_score(model, X, y, scoring='neg_mean_absolute_error', cv=cv), 4) maes.append(mae) mae_avg = round(mae.mean(), 4) mse = -np.round(cross_val_score(model, X, y, scoring='neg_mean_squared_error', cv=cv), 4) mses.append(mse) mse_avg = round(mse.mean(), 4) r2 = np.round(cross_val_score(model, X, y, scoring='r2', cv=cv), 4) r2s.append(r2) r2_avg = round(r2.mean(), 4) acc = np.round((100 - (100 * (mae * len(X))) / sum(y)), 4) accs.append(acc) acc_avg = round(acc.mean(), 4) cv_accuracies[str(model)] = [mae_avg, mse_avg, r2_avg, acc_avg] cv_accuracies.index = ['Mean Absolute Error', 'Mean Squared Error', 'R^2', 'Accuracy'] return cv_accuracies, maes, mses, r2s, accs
現在我們可以創建一個將要使用的模型列表,並通過4次交叉驗證調用上面的函數:
# Create the models to be tested mlr_reg = LinearRegression() rf_reg = RandomForestRegressor(random_state=42) xgb_reg = xgb_regressor = XGBRegressor(random_state=42) # Put the models in a list to be used for Cross-Validation models = [mlr_reg, rf_reg, xgb_reg] # Run the Cross-Validation comparison with the models used in this analysis comp, maes, mses, r2s, accs = cv_comparison(models, X_train_temp, y_train_temp, 4)
得到的結果對比表如下所示:
使用4倍CV,隨機森林回歸模型在所有性能指標上都優於其他兩個模型。但是在第2部分中,我們看到多元線性回歸具有最好的性能指標,為什麼會發生變化呢?
為了理解為什麼交叉驗證得到的分數與第2部分中簡單的訓練和驗證不同,我們需要仔細看看模型在每個摺疊上是如何執行的。上面的cv_compare()函數返回每個摺疊中每個不同模型的所有分數的列表。讓我們看看三種模型在每次摺疊時的r平方是如何比較的。為了得到表格格式的結果,讓我們也快速將其轉換為數據幀:
# Create DataFrame for all R^2s r2_comp = pd.DataFrame(r2s, index=comp.columns, columns=['1st Fold', '2nd Fold', '3rd Fold', '4th Fold']) # Add a column for the averages r2_comp['Average'] = np.round(r2_comp.mean(axis=1),4)
上表說明了4倍CV與訓練集和驗證集得分不同的原因。R-squared在不同的摺疊中差異很大,特別是在xgboost和多元線性回歸中。這也說明了為什麼使用交叉驗證如此重要,特別是對於小數據集,如果你只依賴於一個簡單的訓練集和驗證集,你的結果可能會有很大的不同,這個結果就取決於你最終得到的數據分割是什麼樣子的。
現在我們知道了交叉驗證是什麼以及它為什麼重要,讓我們看看是否可以通過調優超參數從我們的模型中獲得更多。
超參數調優
模型參數是在模型訓練時學習的,不能任意設置。與模型參數不同,超參數是用戶在訓練機器學習模型前可以設置的參數。隨機森林中超參數的例子有:森林中擁有的決策樹的數量、每次分割時需要考慮的最大特徵數量,或者樹的最大深度。
正如我前面提到的,沒有一種萬能的方法可以找到最優超參數。一組超參數可能在一個機器學習問題上表現良好,但在另一個機器學習問題上可能表現不佳。那麼我們怎麼得到最優超參數呢?
一種可能的方法是使用有根據的猜測作為起點,手動調整優超參數,更改一些超參數,然後訓練模型並評估該模型的性能。一直重複這些步驟,直到我們對性能滿意為止。這聽起來像是一個不必要的乏味的方法,但的確如此。
比較超參數調整和吉他調弦。你可以選擇用你的耳朵來給吉他調音,這種方式需要大量的練習和耐心,而且你可能永遠不會得到一個最佳的結果,特別是如果你是一個初學者。但幸運的是,有電子吉他調音器可以幫助你找到正確的音調,它可以解釋你吉他弦的聲波並顯示它所讀取的內容。雖然你仍然需要使用機器頭來調音琴弦,但過程會快得多,電動調音器會確保你的調音接近最佳狀態。那麼機器學習和電吉他調音師有什麼相同的地方呢?
隨機網格搜索交叉驗證
優化機器學習超參數最流行的方法之一是scikiti-learn中的RandomizedSearchCV()。讓我們仔細分析一下是什麼意思。
在隨機網格搜索交叉驗證中,我們首先創建一個超參數網格,我們想通過嘗試優化這些超參數的值,讓我們看一個隨機森林回歸器的超參數網格示例,並看看是如何設置它的:
# Number of trees in Random Forest rf_n_estimators = [int(x) for x in np.linspace(200, 1000, 5)] rf_n_estimators.append(1500) rf_n_estimators.append(2000) # Maximum number of levels in tree rf_max_depth = [int(x) for x in np.linspace(5, 55, 11)] # Add the default as a possible value rf_max_depth.append(None) # Number of features to consider at every split rf_max_features = ['auto', 'sqrt', 'log2'] # Criterion to split on rf_criterion = ['mse', 'mae'] # Minimum number of samples required to split a node rf_min_samples_split = [int(x) for x in np.linspace(2, 10, 9)] # Minimum decrease in impurity required for split to happen rf_min_impurity_decrease = [0.0, 0.05, 0.1] # Method of selecting samples for training each tree rf_bootstrap = [True, False] # Create the grid rf_grid = {'n_estimators': rf_n_estimators, 'max_depth': rf_max_depth, 'max_features': rf_max_features, 'criterion': rf_criterion, 'min_samples_split': rf_min_samples_split, 'min_impurity_decrease': rf_min_impurity_decrease, 'bootstrap': rf_bootstrap}
首先,我們為要優化的每個超參數創建一個可能的取值列表,然後使用上面所示的帶有鍵值對的字典來設置網格。為了找到和理解機器學習模型的超參數,你可以查閱模型的官方文檔。
生成的網格如下所示:
顧名思義,隨機網格搜索交叉驗證使用交叉驗證來評估模型性能。隨機搜索意味著算法不是嘗試所有可能的超參數組合(在我們的例子中是27216個組合),而是隨機從網格中為每個超參數選擇一個值,並使用這些超參數的隨機組合來評估模型。
用計算機將所有可能的組合都嘗試一遍是非常昂貴的,而且需要很長時間。隨機選擇超參數可以顯著地加快這個過程,並且通常為嘗試所有可能的組合提供了一個類似的好的解決方案。讓我們看看隨機網格搜索交叉驗證是如何使用的。
隨機森林的超參數整定
使用先前創建的網格,我們可以為我們的隨機森林回歸器找到最佳的超參數。因為數據集相對較小,我將使用3倍的CV並運行200個隨機組合。因此,隨機網格搜索CV總共將要訓練和評估600個模型(200個組合的3倍)。由於與其他機器學習模型(如xgboost)相比,隨機森林的計算速度較慢,運行這些模型需要幾分鐘時間。一旦這個過程完成,我們就可以得到最佳的超參數。
以下展示了如何使用RandomizedSearchCV():
# Create the model to be tuned rf_base = RandomForestRegressor() # Create the random search Random Forest rf_random = RandomizedSearchCV(estimator = rf_base, param_distributions = rf_grid, n_iter = 200, cv = 3, verbose = 2, random_state = 42, n_jobs = -1) # Fit the random search model rf_random.fit(X_train_temp, y_train_temp) # View the best parameters from the random search rf_random.best_params_
我們將在最終模型中使用這些超參數,並在測試集上對模型進行測試。
xgboost的超參數整定
對於我們的xgboost回歸,過程基本上與隨機森林相同。由於模型的性質,我們試圖優化的超參數有一些是相同的,有一些是不同的。您可以在這裡找到xgb回歸器超參數的完整列表和解釋。我們再次創建網格:
# Number of trees to be used xgb_n_estimators = [int(x) for x in np.linspace(200, 2000, 10)] # Maximum number of levels in tree xgb_max_depth = [int(x) for x in np.linspace(2, 20, 10)] # Minimum number of instaces needed in each node xgb_min_child_weight = [int(x) for x in np.linspace(1, 10, 10)] # Tree construction algorithm used in XGBoost xgb_tree_method = ['auto', 'exact', 'approx', 'hist', 'gpu_hist'] # Learning rate xgb_eta = [x for x in np.linspace(0.1, 0.6, 6)] # Minimum loss reduction required to make further partition xgb_gamma = [int(x) for x in np.linspace(0, 0.5, 6)] # Learning objective used xgb_objective = ['reg:squarederror', 'reg:squaredlogerror'] # Create the grid xgb_grid = {'n_estimators': xgb_n_estimators, 'max_depth': xgb_max_depth, 'min_child_weight': xgb_min_child_weight, 'tree_method': xgb_tree_method, 'eta': xgb_eta, 'gamma': xgb_gamma, 'objective': xgb_objective}
生成的網格如下所示:
為了使性能評估具有可比性,我還將使用具有200個組合的3倍CV來進行xgboost:
# Create the model to be tuned xgb_base = XGBRegressor() # Create the random search Random Forest xgb_random = RandomizedSearchCV(estimator = xgb_base, param_distributions = xgb_grid, n_iter = 200, cv = 3, verbose = 2, random_state = 420, n_jobs = -1) # Fit the random search model xgb_random.fit(X_train_temp, y_train_temp) # Get the optimal parameters xgb_random.best_params_
最優超參數如下:
同樣的,這些將在最終的模型中使用。
雖然對有些人來說這可能是顯而易見的,但我只是想在這裡提一下:我們為什麼不為多元線性回歸做超參數優化是因為模型中沒有超參數需要調整,它只是一個多元線性回歸。
現在我們已經獲得了最佳的超參數(至少在交叉驗證方面),我們終於可以在測試數據上評估我們的模型了,我們就可以根據我們從一開始就持有的測試數據來評估我們的模型了!
最終模型的評估
在評估了我們的機器學習模型的性能並找到了最佳超參數之後,是時候對模型進行最後的測試了。
我們對模型進行了訓練,這些數據是我們用於進行評估的數據的80%,即除了測試集之外的所有數據。我們使用上一部分中找到的超參數,然後比較模型在測試集上的表現。
讓我們創建和訓練我們的模型:
# Create the final Multiple Linear Regression mlr_final = LinearRegression() # Create the final Random Forest rf_final = RandomForestRegressor(n_estimators = 200, min_samples_split = 6, min_impurity_decrease = 0.0, max_features = 'sqrt', max_depth = 25, criterion = 'mae', bootstrap = True, random_state = 42) # Create the fnal Extreme Gradient Booster xgb_final = XGBRegressor(tree_method = 'exact', objective = 'reg:squarederror', n_estimators = 1600, min_child_weight = 6, max_depth = 8, gamma = 0, eta = 0.1, random_state = 42) # Train the models using 80% of the original data mlr_final.fit(X_train_temp, y_train_temp) rf_final.fit(X_train_temp, y_train_temp) xgb_final.fit(X_train_temp, y_train_temp)
我定義了一個函數,該函數對所有最終模型進行評分,並創建了一個更容易比較的數據幀:
# Define a function that compares all final models def final_comparison(models, test_features, test_labels): scores = pd.DataFrame() for model in models: predictions = model.predict(test_features) mae = round(mean_absolute_error(test_labels, predictions), 4) mse = round(mean_squared_error(test_labels, predictions), 4) r2 = round(r2_score(test_labels, predictions), 4) errors = abs(predictions - test_labels) mape = 100 * np.mean(errors / test_labels) accuracy = round(100 - mape, 4) scores[str(model)] = [mae, mse, r2, accuracy] scores.index = ['Mean Absolute Error', 'Mean Squared Error', 'R^2', 'Accuracy'] return scores
使用我們的三個最終模型調用該函數並調整列標題將會得到以下最終計算結果:
# Call the comparison function with the three final models final_scores = final_comparison([mlr_final, rf_final, xgb_final], X_test, y_test) # Adjust the column headers final_scores.columns = ['Linear Regression', 'Random Forest', 'Extreme Gradient Boosting']
獲勝者是:隨機森林回歸!
隨機森林的R-squared達到80%,測試集的準確率為97.6%,這意味著它的預測平均只有2.4%的偏差。這是個不錯的結果!
在此分析中,多元線性回歸的表現並不遜色,但xgboost並沒有達到其所宣傳的效果。
總結評論
整個分析過程和實際操作過程都很有趣。我一直在研究Fitbit是如何計算睡眠分數的,現在我很高興能更好地理解它。最重要的是,我建立了一個機器學習模型,可以非常準確地預測睡眠分數。話雖如此,我還是想強調幾件事:
正如我在第2部分中提到的,對多元線性回歸係數的解釋可能不準確,因為特徵之間存在高度的多重共線性。我用於分析的數據集相當小,因為它依賴於從Fitbit獲得的286個數據點。這限制了結果的可推廣性,需要更大的數據集才能訓練出更健壯的模型。該分析僅使用了Fitbit的一個人的睡眠數據,因此可能不能很好地推廣到其他睡眠模式、心率等不同的人。我希望你喜歡這篇關於如何使用機器學習來預測Fitbit睡眠分數的全面分析,並且了解了不同睡眠階段的重要性以及睡眠過程中所花費的時間。
感謝你的閱讀!
作者:Jonas Benner
deephub翻譯組:錢三一