選自machinelearningmastery
作者:Jason Brownlee
參與:linjing、吳攀
許多機器學習算法的核心是優化。優化算法用於在機器學習中為給定訓練集找出合理的模型參數設置。機器學習最常見的優化算法是隨機梯度下降(SGD:stochastic gradient descent)。
本教程將指導大家用 Python 實現隨機梯度下降對線性回歸算法的優化。通過本教程的學習,你將了解到:
如何用隨機梯度下降估計線性回歸係數
如何對多元線性回歸做預測
如何用帶隨機梯度下降的線性回歸算法對新數據做預測
說明
本文將對線性回歸、隨即梯度下降方法以及本教程所使用的葡萄酒品質數據集做一個集中闡釋。
多元線性回歸
線性回歸是一種用於預測真實值的方法。讓人困惑的是,這些需要預測真實值的問題被稱為回歸問題(regression problems)。
線性回歸是一種用直線對輸入輸出值進行建模的方法。在超過二維的空間裡,這條直線被想像成一個平面或者超平面(hyperplane)。預測即是通過對輸入值的組合對輸出值進行預判。
y = b0 + b1 * x1 + b2 * x2 + ...
係數 (b) 用於對每個輸入屬性 (x) 進行加權,而學習算法的目的正是尋找一組能導出好的預測值 (y) 的係數。這些係數可以使用隨機梯度下降的方法找到。
隨機梯度下降
梯度下降(Gradient Descent)是遵循成本函數的梯度來最小化一個函數的過程。這個過程涉及到對成本形式以及其衍生形式的認知,使得我們可以從已知的給定點朝既定方向移動。比如向下朝最小值移動。
在機器學習中,我們可以利用隨機梯度下降的方法來最小化訓練模型中的誤差,即每次迭代時完成一次評估和更新。
這種優化算法的工作原理是模型每看到一個訓練實例,就對其作出預測,並重複迭代該過程到一定的次數。這個流程可以用於找出能導致訓練數據最小誤差的模型的係數。用機器學習的術語來講,就是每次迭代過程都用如下等式更新係數(b)。
b = b - learning_rate * error * x
其中 b 是係數或者被優化的權重,learing_rate 需手動設定(如 0.01),error 是取決於權重的訓練數據模型的預測誤差,x 是輸入值。
葡萄酒品質數據集
開發了具有梯度下降的線性回歸算法之後,我們可以將其運用到一個關於葡萄酒品質的數據集當中。這個數據集囊括了 4898 種白葡萄酒的測量標準,包括酸度和 ph 值。目的是用這些客觀標準來預測葡萄酒的品質,分為 0 到 10 級。
下表給出了 5 個數據樣本。
7,0.27,0.36,20.7,0.045,45,170,1.001,3,0.45,8.8,6
6.3,0.3,0.34,1.6,0.049,14,132,0.994,3.3,0.49,9.5,6
8.1,0.28,0.4,6.9,0.05,30,97,0.9951,3.26,0.44,10.1,6
7.2,0.23,0.32,8.5,0.058,47,186,0.9956,3.19,0.4,9.9,6
7.2,0.23,0.32,8.5,0.058,47,186,0.9956,3.19,0.4,9.9,6
所有數據需歸一化為 0-1 之間的值。每種屬性標準單位不同,因而有不同的縮放尺度。通過預測該歸一化數據集的平均值(零規則算法),達到了 0.148 的基準方均根差(RMSE)。
該數據集詳情請參閱 UCI Machine Learning Repository:http://archive.ics.uci.edu/ml/datasets/Wine+Quality
下載該數據集並將其保存到當前工作目錄,文件名為 winequality-white.csv。(注意:文件開頭的頭信息需去除,用作分隔符的 『;』 需改為符合 CSV 格式的 『,』。)
教程
本教程分為三個部分:
1. 預測
2. 估計係數
3. 葡萄酒品質預測
這將能讓你了解在你自己的預測建模問題上實現和應用帶有隨機梯度下降的線性回歸的基礎。
1. 預測
首先建立一個用於預測的函數。這將用於對隨機梯度下降的候選係數的評估,且模型確定之後也需要這個函數。我們會在測試集或者新的數據上用該函數來進行預測。
函數 predict() 如下所示,用於預測給定了一組係數的行的輸出值。
第一個係數始終為截距,也稱為偏差或 b0,因其相對獨立且不與特定的輸入值相關。
我們可以用一個小的數據集對這個函數進行測試。
x, y
1, 1
2, 3
4, 3
3, 2
5, 5
下圖是一小部分數據:
線性回歸的部分轉換數據
我們也可用之前準備好的係數為這個數據集做預測。predict() 函數測試如下。
單個輸入值 (x) 和兩個係數(b0 和 b1)。用於建模該問題的預測方程為:
y = b0 + b1 * x
或者,手動選擇特定係數:
y = 0.4 + 0.8 * x
運行此函數,我們將得到一個相當接近預測值的輸出值(y)。
Expected=1.000, Predicted=1.200
Expected=3.000, Predicted=2.000
Expected=3.000, Predicted=3.600
Expected=2.000, Predicted=2.800
Expected=5.000, Predicted=4.400
現在我們可以用隨機梯度下降來優化我們的係數值了。
2. 估計係數
我們可以使用隨機梯度下降來為我們的訓練數據估計係數值。隨機階梯下降需要兩個設定參數:
這兩個值和數據集都是函數的參數。我們的這個函數將執行三個遍歷循環:
1. 單次 epoch 循環
2. 單次 epoch 中訓練集中的每行循環
3. 單次 epoch 中每個係數循環並為每一行更新它
可以看到,每次 epoch,我們都會更新數據集裡每行的係數。係數的更新是基於模型生成的誤差。該誤差被算作候選係數的預測值和預期輸出值之間的差。
error = prediction - expected
有一個係數用於加權每一個輸入屬性,這些屬性將以連續的方式進行更新,比如
b1(t+1) = b1(t) - learning_rate * error(t) * x1(t)
列表開始的特殊係數,也被稱為截距(intercept)或偏差(bias),也以類似的方式更新,但因其不與特定輸入值相關,所以無輸入值。
b0(t+1) = b0(t) - learning_rate * error(t)
現在我們把所有東西組合在一起。coefficients_sgd() 函數正是用隨機梯度下降來計算一個訓練集的係數值,下面即是該函數:
此外,我們追蹤每個 epoch 的方差(正值)總和從而在循環之後得到一個好的結果。
我們用 0.001 的學習速率訓練該模型 50 次,即把整個訓練數據集的係數曝光 50 次。運行一個 epoch 系統就將該次循環中的和方差(sum squared error)和以及最終係數集合 print 一次:
>epoch=45, lrate=0.001, error=2.650
>epoch=46, lrate=0.001, error=2.627
>epoch=47, lrate=0.001, error=2.607
>epoch=48, lrate=0.001, error=2.589
>epoch=49, lrate=0.001, error=2.573
[0.22998234937311363, 0.8017220304137576]
可以看到誤差是如何在歷次 epoch 中持續降低的。或許我們可以增加訓練次數(epoch)或者每個 epoch 中的係數總量(調高學習速率)。
嘗試一下看你能得到什麼結果。
現在,我們將這個算法用到實際的數據當中。
3. 葡萄酒品質預測
我們將使用隨機階梯下降的方法為葡萄酒品質數據集訓練一個線性回歸模型。本示例假定一個名為 winequality—white.csv 的 csv 文件副本已經存在於當前工作目錄。
首先加載該數據集,將字符串轉換成數字,並將輸出列從字符串轉換成數值 0 和 1. 這個過程是通過輔助函數 load_csv()、str_column_to_float() 以及 dataset_minmax() 和 normalize_dataset() 來分別實現的。
我們將通過 K 次交叉驗證來預估得到的學習模型在未知數據上的表現。這就意味著我們將創建並評估 K 個模型並預估這 K 個模型的平均誤差。輔助函數 cross_validation_split()、rmse_metric() 和 evaluate_algorithm() 用於求導根均方差以及評估每一個生成的模型。
我們用之前創建的函數 predict()、coefficients_sgd() 以及 linear_regression_sgd() 來訓練模型。完整代碼如下:
# Linear Regression With Stochastic Gradient Descent for Wine Quality
from random import seed
from random import randrange
from csv import reader
from math import sqrt
# Load a CSV file
def load_csv(filename):
dataset = list()
with open(filename, 'r') as file:
csv_reader = reader(file)
for row in csv_reader:
if not row:
continue
dataset.append(row)
return dataset
# Convert string column to float
def str_column_to_float(dataset, column):
for row in dataset:
row[column] = float(row[column].strip())
# Find the min and max values for each column
def dataset_minmax(dataset):
minmax = list()
for i in range(len(dataset[0])):
col_values = [row[i] for row in dataset]
value_min = min(col_values)
value_max = max(col_values)
minmax.append([value_min, value_max])
return minmax
# Rescale dataset columns to the range 0-1
def normalize_dataset(dataset, minmax):
for row in dataset:
for i in range(len(row)):
row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0])
# Split a dataset into k folds
def cross_validation_split(dataset, n_folds):
dataset_split = list()
dataset_copy = list(dataset)
fold_size = len(dataset) / n_folds
for i in range(n_folds):
fold = list()
while len(fold) < fold_size:
index = randrange(len(dataset_copy))
fold.append(dataset_copy.pop(index))
dataset_split.append(fold)
return dataset_split
# Calculate root mean squared error
def rmse_metric(actual, predicted):
sum_error = 0.0
for i in range(len(actual)):
prediction_error = predicted[i] - actual[i]
sum_error += (prediction_error ** 2)
mean_error = sum_error / float(len(actual))
return sqrt(mean_error)
# Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):
folds = cross_validation_split(dataset, n_folds)
scores = list()
for fold in folds:
train_set = list(folds)
train_set.remove(fold)
train_set = sum(train_set, [])
test_set = list()
for row in fold:
row_copy = list(row)
test_set.append(row_copy)
row_copy[-1] = None
predicted = algorithm(train_set, test_set, *args)
actual = [row[-1] for row in fold]
rmse = rmse_metric(actual, predicted)
scores.append(rmse)
return scores
# Make a prediction with coefficients
def predict(row, coefficients):
yhat = coefficients[0]
for i in range(len(row)-1):
yhat += coefficients[i + 1] * row[i]
return yhat
# Estimate linear regression coefficients using stochastic gradient descent
def coefficients_sgd(train, l_rate, n_epoch):
coef = [0.0 for i in range(len(train[0]))]
for epoch in range(n_epoch):
for row in train:
yhat = predict(row, coef)
error = yhat - row[-1]
coef[0] = coef[0] - l_rate * error
for i in range(len(row)-1):
coef[i + 1] = coef[i + 1] - l_rate * error * row[i]
# print(l_rate, n_epoch, error)
return coef
# Linear Regression Algorithm With Stochastic Gradient Descent
def linear_regression_sgd(train, test, l_rate, n_epoch):
predictions = list()
coef = coefficients_sgd(train, l_rate, n_epoch)
for row in test:
yhat = predict(row, coef)
predictions.append(yhat)
return(predictions)
# Linear Regression on wine quality dataset
seed(1)
# load and prepare data
filename = 'winequality-white.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])):
str_column_to_float(dataset, i)
# normalize
minmax = dataset_minmax(dataset)
normalize_dataset(dataset, minmax)
# evaluate algorithm
n_folds = 5
l_rate = 0.01
n_epoch = 50
scores = evaluate_algorithm(dataset, linear_regression_sgd, n_folds, l_rate, n_epoch)
print('Scores: %s' % scores)
print('Mean RMSE: %.3f' % (sum(scores)/float(len(scores))))
一個等於 5 的 k 值被用於交叉驗證,給每次迭代 4898/5 = 979.6(低於 1000 都行)條記錄來進行評估。對一個小實驗選擇了 0.01 的學習率和 50 訓練 epoch.
你可以嘗試你自己的配置,看你能否超過我的分數。
運行這個樣本,為 5 次交叉驗證的每一次 print 一個分數,然後 print 平均均方根誤差(RMSE)。我們可以看到(在歸一化的數據集上)該 RMSE 為 0.126。如果我們只是預測平均值的話(使用 Zero Rule Algorithm),那麼這個結果就低於基準值 0.148。
Scores: [0.12259834231519767, 0.12733924130891316, 0.12610773846663892, 0.1289950071681572, 0.1272180783291014]
Mean RMSE: 0.126
擴展
這裡給出了一些擴展練習,你可以思考並嘗試解決它們:
調整該實例。調整其學習率、epoch 的數量甚至原始數據處理和準備的方法,以期能提高終結果。
批量進行隨機梯度下降。改變隨機梯度下降算法使其在每個 epoch 上累積更新,且僅在 epoch 結束時批量更新係數。
額外的回歸問題。應用該技術來解決 UCI 機器學習庫中的其它回歸問題。
你會探索這些擴展任務嗎?
回顧總結
本教程介紹了如何用 Python 實現帶有隨機梯度下降的多元線性回歸算法。其中包括:
如何對多元線性回歸問題做預測
如何優化用於隨機梯度下降的係數設置
如何將該方法用於實際的回歸預測模型問題
©本文為機器之心編譯,轉載請聯繫本公眾號獲得授權。
✄---
加入機器之心(全職記者/實習生):hr@almosthuman.cn
投稿或尋求報導:editor@almosthuman.cn
廣告&商務合作:bd@almosthuman.cn