Python 中的機器學(xué)習(xí)簡介:簡單線性回歸
一、說明
二、技術(shù)背景
????????這是涵蓋回歸、梯度下降、分類和機器學(xué)習(xí)的其他基本方面的系列文章的第一篇文章。本文重點介紹簡單的線性回歸,它確定了一組點的最佳擬合線,以便進行將來的預(yù)測。
2.1 最佳擬合線
????????最佳擬合線是最準(zhǔn)確地表示一組點的方程。對于給定的輸入,公式的輸出應(yīng)盡可能接近預(yù)期輸出。
????????在上圖中,很明顯,中間線比左線或右線更適合藍(lán)點。但是,它是最適合的路線嗎?有沒有第四條線更適合這些觀點?這條線當(dāng)然可以向上或向下移動,以確保相同數(shù)量的點落在其上方和下方。但是,可能有十幾行符合此確切標(biāo)準(zhǔn)。是什么讓其中任何一個成為最好的?
????????值得慶幸的是,有一種方法可以使用回歸在數(shù)學(xué)上確定一組點的最佳擬合線。
2.2 回歸
????????回歸有助于識別兩個或多個變量之間的關(guān)系,它采用多種形式,包括簡單線性、多重線性、多項式等。為了證明這種方法的有用性,將使用簡單的線性回歸。
????????簡單線性回歸嘗試找到一組點的最佳擬合線。更具體地說,它標(biāo)識自變量和因變量之間的關(guān)系。最佳擬合線的形式為?y = mx + b。
- x?是輸入或自變量
- m?是直線的斜率或陡度
- b?是?y?截距
- y?是輸出或因變量
????????簡單線性回歸的目標(biāo)是確定?m?和?b?的值,當(dāng)給定?x?時,這些值將生成最準(zhǔn)確的?y?值。這個方程,也稱為模型,也可以用機器學(xué)習(xí)術(shù)語進行評估。在等式中,w 表示“重量”:? = Xw?+ w?
- X?是輸入或特征
- w??是斜率
- w??是偏置或?y?截距
- ??是預(yù)測,發(fā)音為“y-hat”
????????雖然這很有用,但需要評估方程的準(zhǔn)確性。如果它的預(yù)測很差,它就不是很有用。為此,使用成本或損失函數(shù)。
2.3 成本或損失函數(shù)
????????回歸需要某種方法來跟蹤模型預(yù)測的準(zhǔn)確性。給定輸入,方程的輸出是否盡可能接近預(yù)期輸出?成本函數(shù),也稱為損失函數(shù),用于確定方程的精度。
????????例如,如果預(yù)期輸出為 5,而方程輸出為 18,則損失函數(shù)應(yīng)表示此差值。一個簡單的損失函數(shù)可以輸出 13,這是這些值之間的差值。這表明模型的性能很差。另一方面,如果預(yù)期輸出為 5,模型預(yù)測為 5,則損失函數(shù)應(yīng)輸出 0,這表明模型的性能非常出色。
執(zhí)行此操作的常用損失函數(shù)是均方誤差 (MSE):
????????此函數(shù)用于查找模型的預(yù)測 (?) 和預(yù)期輸出 (Y) 之間的差異。然后對差值進行平方,以確保輸出始終為正。它跨一組大小為?n?的點執(zhí)行此操作。通過將所有這些點的平方差相加并除以?n,輸出是均方差(誤差)。這是一種同時評估模型在所有點上的性能的簡單方法。下面可以看到一個簡單的例子:
?
????????雖然還有無數(shù)其他損失函數(shù)同樣適用于這種情況,但由于其簡單性,這是機器學(xué)習(xí)中回歸中最受歡迎的損失函數(shù)之一,尤其是在梯度下降方面,這將在后面解釋。
為了最好地理解梯度下降的位置,可以評估一個示例。
三、預(yù)測最佳擬合線
????????若要顯示操作中的簡單線性回歸,需要數(shù)據(jù)來訓(xùn)練模型。這以?X?數(shù)組和?Y?數(shù)組的形式出現(xiàn)。對于此示例,可以手動生成數(shù)據(jù)。它可以從“藍(lán)圖”函數(shù)創(chuàng)建。隨機性可以添加到藍(lán)圖中,模型將被強制學(xué)習(xí)底層函數(shù)。PyTorch,一個標(biāo)準(zhǔn)的機器學(xué)習(xí)庫,用于實現(xiàn)回歸。
3.1 生成數(shù)據(jù)
????????首先,下面的代碼使用隨機整數(shù)生成器生成一個輸入值數(shù)組。X?當(dāng)前具有?(n 個樣本,num 特征)的形狀。請記住,特征是一個自變量,簡單線性回歸有 1。在本例中,n?將為 20。
import torch
torch.manual_seed(5)
torch.set_printoptions(precision=2)
# (n samples, features)
X = torch.randint(low=0, high=11, size=(20, 1))
tensor([[ 9],
[10],
[ 0],
[ 3],
[ 8],
[ 8],
[ 0],
[ 4],
[ 1],
[ 0],
[ 7],
[ 9],
[ 3],
[ 7],
[ 9],
[ 7],
[ 3],
[10],
[10],
[ 4]])
????????然后可以通過?Y = 1.5X + 2?傳遞這些值以生成輸出值,并且可以使用平均值為 0 且標(biāo)準(zhǔn)差為 1 的正態(tài)分布將這些值添加一些隨機性。Y?的形狀為 (n 個樣本,1)。
????????下面的代碼顯示了具有相同形狀的隨機值。
torch.manual_seed(5)
# normal distribution with a mean of 0 and std of 1
normal = torch.distributions.Normal(loc=0, scale=1)
normal.sample(X.shape)
tensor([[ 1.84],
[ 0.52],
[-1.71],
[-1.70],
[-0.13],
[-0.60],
[ 0.14],
[-0.15],
[ 2.61],
[-0.43],
[ 0.35],
[-0.06],
[ 1.48],
[ 0.49],
[ 0.25],
[ 1.75],
[ 0.74],
[ 0.03],
[-1.17],
[-1.51]])
????????最后,可以使用下面的代碼計算Y。
Y = (1.5*X + 2) + normal.sample(X.shape)
Y
tensor([[15.00],
[15.00],
[-0.36],
[ 6.75],
[13.59],
[15.16],
[ 2.33],
[ 8.72],
[ 2.67],
[ 1.81],
[13.74],
[14.06],
[ 7.15],
[12.81],
[15.91],
[13.15],
[ 6.76],
[18.05],
[18.71],
[ 6.80]])
它們也可以與 matplotlib 一起繪制,以便更好地理解它們的關(guān)系:
import matplotlib.pyplot as plt
plt.scatter(X,Y)
plt.xlim(-1,11)
plt.ylim(0,20)
plt.xlabel("$X$")
plt.ylabel("$Y$")
plt.show()
?
????????雖然為示例生成數(shù)據(jù)似乎違反直覺,但它是演示回歸如何工作的好方法。該模型(如下所示)將僅提供?X?和?Y,并且需要將 w??標(biāo)識為 1.5,將 w??標(biāo)識為 2。
????????
????????權(quán)重可以存儲在數(shù)組?w 中。?這個數(shù)組中有兩個權(quán)重,一個用于偏差,一個用于特征的數(shù)量。它的形狀為 (數(shù)字特征 + 1 個偏差,1)。對于此示例,數(shù)組的形狀為 (2, 1)。
torch.manual_seed(5)
w = torch.rand(size=(2, 1))
w
tensor([[0.83],
[0.13]])
????????生成這些值后,可以創(chuàng)建模型。
3.2 創(chuàng)建模型
????????模型的第一步是為最佳擬合線定義一個函數(shù),為 MSE 定義另一個函數(shù)。
????????如前所述,該模型的方程為?? = Xw?+ w?。?截至目前,偏差已添加到每個樣本中。這相當(dāng)于將偏差廣播為與?X?大小相同并將數(shù)組相加。輸出如下所示。
w[1]*X + w[0]
tensor([[1.97],
[2.09],
[0.83],
[1.21],
[1.84],
[1.84],
[0.83],
[1.33],
[0.96],
[0.83],
[1.71],
[1.97],
[1.21],
[1.71],
[1.97],
[1.71],
[1.21],
[2.09],
[2.09],
[1.33]])
????????下面的函數(shù)計算輸出。
# line of best fit
def model(w, X):
"""
Inputs:
w: array of weights | (num features + 1 bias, 1)
X: array of inputs | (n samples, num features + 1 bias)
Output:
returns the predictions | (n samples, 1)
"""
return w[1]*X + w[0]
????????MSE 的功能非常簡單:
# mean squared error (MSE)
def MSE(Yhat, Y):
"""
Inputs:
Yhat: array of predictions | (n samples, 1)
Y: array of expected outputs | (n samples, 1)
Output:
returns the loss of the model, which is a scalar
"""
return torch.mean((Yhat-Y)**2) # mean((error)^2)
3.3 預(yù)覽最佳擬合線
????????創(chuàng)建函數(shù)后,可以使用繪圖預(yù)覽最佳擬合線,并且可以創(chuàng)建標(biāo)準(zhǔn)函數(shù)以供將來使用。它將以紅色顯示最佳擬合線,以橙色顯示每個輸入的預(yù)測,以藍(lán)色顯示預(yù)期輸出。
def plot_lbf():
"""
Output:
plots the line of best fit in comparison to the training data
"""
# plot the points
plt.scatter(X,Y)
# predictions for the line of best fit
Yhat = model(w, X)
plt.scatter(X, Yhat, zorder=3) # plot the predictions
# plot the line of best fit
X_plot = torch.arange(-1,11+0.1,.1) # generate values with a step of .1
plt.plot(X_plot, model(w, X_plot), color="red", zorder=0)
plt.xlim(-1, 11)
plt.xlabel("$X$")
plt.ylabel("$Y$")
plt.title(f"MSE: {MSE(Yhat, Y):.2f}")
plt.show()
plot_lbf()
?
????????具有當(dāng)前權(quán)重的輸出并不理想,因為MSE為105.29。為了獲得更好的MSE,需要選擇不同的權(quán)重。它們可以再次隨機化,但獲得完美線的機會很小。這是梯度下降算法可用于以定義的方式更改權(quán)重值的地方。
3.4 梯度下降
????????梯度下降算法的解釋可以在這里找到:梯度下降的簡單介紹。在繼續(xù)之前應(yīng)閱讀本文以避免混淆。
????????總結(jié)一下這篇文章,梯度下降使用成本函數(shù)的梯度來揭示每個權(quán)重對其的方向和影響。通過使用學(xué)習(xí)率縮放梯度并從每個權(quán)重的當(dāng)前值中減去梯度,成本函數(shù)最小化,迫使模型的預(yù)測盡可能接近預(yù)期輸出。
對于簡單的線性回歸,f?將是 MSE。Python 實現(xiàn)可以在下面看到。請記住,每個權(quán)重都有自己的偏導(dǎo)數(shù)用于公式,如上所示。
# optimizer
def gradient_descent(w):
"""
Inputs:
w: array of weights | (num features + 1 bias, 1)
Global Variables / Constants:
X: array of inputs | (n samples, num features + 1 bias)
Y: array of expected outputs | (n samples, 1)
lr: learning rate to scale the gradient
Output:
returns the updated weights
"""
n = len(X)
# update the bias
w[0] = w[0] - lr*2/n * torch.sum(model(w,X) - Y)
# update the weight
w[1] = w[1] - lr*2/n * torch.sum(X*(model(w,X) - Y))
return w
????????現(xiàn)在,該函數(shù)可用于更新權(quán)重。學(xué)習(xí)率是根據(jù)經(jīng)驗選擇的,但它通常是一個較小的值。還可以繪制最佳擬合的新線。
lr = 0.01
print("weights before:", w.flatten())
print("MSE before:", MSE(model(w,X), Y))
# update the weights
w = gradient_descent(w)
print("weights after:", w.flatten())
print("MSE after:", MSE(model(w,X), Y))
plot_lbf()
weights before: tensor([0.83, 0.13])
MSE before: tensor(105.29)
weights after: tensor([1.01, 1.46])
MSE after: tensor(2.99)
?
????????MSE 在第一次嘗試時下降了 100 多分,但這條線仍然不能完全符合點數(shù)。請記住,目標(biāo)是將?w??設(shè)為 2,將 w??設(shè)為 1.5。為了加快學(xué)習(xí)過程,可以再執(zhí)行500次梯度下降,并且可以檢查新結(jié)果。
# update the weights
for i in range(0, 500):
# update the weights
w = gradient_descent(w)
# print the new values every 10 iterations
if (i+1) % 100 == 0:
print("epoch:", i+1)
print("weights:", w.flatten())
print("MSE:", MSE(model(w,X), Y))
print("="*10)
plot_lbf()
epoch: 100
weights: tensor([1.44, 1.59])
MSE: tensor(1.31)
==========
epoch: 200
weights: tensor([1.67, 1.56])
MSE: tensor(1.25)
==========
epoch: 300
weights: tensor([1.80, 1.54])
MSE: tensor(1.24)
==========
epoch: 400
weights: tensor([1.87, 1.53])
MSE: tensor(1.23)
==========
epoch: 500
weights: tensor([1.91, 1.52])
MSE: tensor(1.23)
==========
?
????????500 個時期后,MSE 為 1.23。w??為 1.91,w??為 1.52。這意味著模型成功識別了最佳擬合線??梢詧?zhí)行其他更新,但添加到輸出值的隨機性可能會阻止模型實現(xiàn)完美的預(yù)測。
????????為了建立關(guān)于梯度下降如何工作的額外直覺,可以通過將它們與其輸出 MSE 繪制來檢查 w??和?w??的影響。繪制梯度下降的函數(shù)可以在附錄中檢查,輸出可以在下面檢查:
torch.manual_seed(5)
w = torch.rand(size=(2, 1))
w0s, w1s, losses = list(),list(),list()
# update the weights
for i in range(0, 500):
if i == 0 or (i+1) % 10 == 0:
w0s.append(float(w[0]))
w1s.append(float(w[1]))
losses.append(MSE(model(w,X), Y))
# update the weights
w = gradient_descent(w)
plot_GD([-2, 5.2], [-2, 5.2])
?
????????每個橙色點表示權(quán)重的更新,紅線表示從一個迭代到下一個迭代的變化。最大的更新是從第一次迭代到第二次迭代,即紅線。其他橙色點靠得很近,因為它們的衍生物很小,因此更新更小。該圖顯示了權(quán)重如何更新,直到獲得最佳 MSE。
????????雖然這種方法很有用,但可以通過幾種方式簡化。首先,它沒有利用矩陣乘法,這將簡化模型的方程。其次,梯度下降不是回歸的閉式解,因為每個問題的 epoch 數(shù)和學(xué)習(xí)率都不同,并且解是近似的。本文的最后一部分將解決第一個問題,下一篇文章將解決第二個問題。
四、另一種方法
????????雖然這種方法很有用,但它并不像它可能的那么簡單。它不利用矩陣。截至目前,整個方程?? = Xw?+ w??用于模型的函數(shù),并且必須單獨計算每個權(quán)重的偏導(dǎo)數(shù)以進行梯度下降。通過使用矩陣運算和微積分,這兩個函數(shù)都簡化了。
????????首先,X?的形狀為 (n 個樣本,num 特征),w?的形狀為?(num 特征?+ 1?個偏差,1)。通過在?X?中添加額外的列,可以使用矩陣乘法,因為它將具有?(n 個樣本、num 特征 + 1 個偏差)的新形狀。這可以是一列將乘以偏差的列,這將縮放向量。這相當(dāng)于廣播偏差,這是先前計算預(yù)測的方式。
X = torch.hstack((torch.ones(X.shape),X))
X
tensor([[ 1., 9.],
[ 1., 10.],
[ 1., 0.],
[ 1., 3.],
[ 1., 8.],
[ 1., 8.],
[ 1., 0.],
[ 1., 4.],
[ 1., 1.],
[ 1., 0.],
[ 1., 7.],
[ 1., 9.],
[ 1., 3.],
[ 1., 7.],
[ 1., 9.],
[ 1., 7.],
[ 1., 3.],
[ 1., 10.],
[ 1., 10.],
[ 1., 4.]])
????????這會將等式更改為?? = X?w?+ X?w?。展望未來,偏差可以被視為一個特征,因此?num 特征可以表示自變量和偏差,并且可以省略?+ 1 偏差。因此,X?的大小為 (n 個樣本,num?特征),w?的大小為?(num features,1)。當(dāng)它們相互相乘時,輸出是預(yù)測向量,其大小為?(n 個樣本,1)。矩陣乘法的輸出與 相同。w[1]*X + w[0]
torch.manual_seed(5)
w = torch.rand(size=(2, 1))
torch.matmul(X, w)
tensor([[1.97],
[2.09],
[0.83],
[1.21],
[1.84],
[1.84],
[0.83],
[1.33],
[0.96],
[0.83],
[1.71],
[1.97],
[1.21],
[1.71],
[1.97],
[1.71],
[1.21],
[2.09],
[2.09],
[1.33]])
考慮到這一點,可以更新模型的功能:
# line of best fit
def model(w, X):
"""
Inputs:
w: array of weights | (num features, 1)
X: array of inputs | (n samples, num features)
Output:
returns the output of X@w | (n samples, 1)
"""
return torch.matmul(X, w)
????????由于不再將每個權(quán)重視為單個分量,因此梯度下降算法也可以更新?;谔荻认陆档暮唵谓榻B,矩陣的梯度下降算法如下:
????????這可以通過 PyTorch 輕松實現(xiàn)。由于?w?在文章開頭被重塑,因此導(dǎo)數(shù)的輸出需要重新整形以進行減法。
# optimizer
def gradient_descent(w):
"""
Inputs:
w: array of weights | (num features, 1)
Global Variables / Constants:
X: array of inputs | (n samples, num features)
Y: array of expected outputs | (n samples, 1)
lr: learning rate to scale the gradient
Output:
returns the updated weights | (num features, 1)
"""
n = X.shape[0]
return w - (lr * 2/n) * (torch.matmul(-Y.T, X) + torch.matmul(torch.matmul(w.T, X.T), X)).reshape(w.shape)
????????使用 500 個 epoch,可以生成與以前相同的輸出:
lr = 0.01
# update the weights
for i in range(0, 501):
# update the weights
w = gradient_descent(w)
# print the new values every 10 iterations
if (i+1) % 100 == 0:
print("epoch:", i+1)
print("weights:", w.flatten())
print("MSE:", MSE(model(w,X), Y))
print("="*10)
epoch: 100
weights: tensor([1.43, 1.59])
MSE: tensor(1.31)
==========
epoch: 200
weights: tensor([1.66, 1.56])
MSE: tensor(1.25)
==========
epoch: 300
weights: tensor([1.79, 1.54])
MSE: tensor(1.24)
==========
epoch: 400
weights: tensor([1.87, 1.53])
MSE: tensor(1.23)
==========
epoch: 500
weights: tensor([1.91, 1.53])
MSE: tensor(1.23)
==========
????????由于這些函數(shù)不需要為每個要素手動添加其他變量,因此可用于多元線性回歸和多項式回歸。
五、結(jié)論
????????下一篇文章將討論不近似權(quán)重的回歸閉式解決方案。相反,最小化的值將使用 Python?中的機器學(xué)習(xí)簡介:Python 中回歸的正態(tài)方程直接計算。
請不要忘記點贊和關(guān)注!:)文章來源:http://www.zghlxwxcb.cn/news/detail-674832.html
六、附錄
繪制梯度下降:此函數(shù)利用 Plotly 在三維空間中顯示梯度下降。文章來源地址http://www.zghlxwxcb.cn/news/detail-674832.html
import plotly.graph_objects as go
import plotly
import plotly.express as px
def plot_GD(w0_range, w1_range):
"""
Inputs:
w0_range: weight range [w0_low, w0_high]
w1_range: weight range [w1_low, w1_high]
Global Variables:
X: array of inputs | (n samples, num features + 1 bias)
Y: array of expected outputs | (n samples, 1)
lr: learning rate to scale the gradient
Output:
prints gradient descent
"""
# generate all the possible weight combinations (w0, w1)
w0_plot, w1_plot = torch.meshgrid(torch.arange(w0_range[0],
w0_range[1],
0.1),
torch.arange(w1_range[0],
w1_range[1],
0.1))
# rearrange into coordinate pairs
w_plot = torch.hstack((w0_plot.reshape(-1,1), w1_plot.reshape(-1,1)))
# calculate the MSE for each pair
mse_plot = [MSE(model(w, X), Y) for w in w_plot]
# plot the data
fig = go.Figure(data=[go.Mesh3d(x=w_plot[:,0],
y=w_plot[:,1],
z=mse_plot,)])
# plot gradient descent on loss function
fig.add_scatter3d(x=w0s,
y=w1s,
z=losses,
marker=dict(size=3,color="orange"),
line=dict(color="red",width=5))
# prepare ranges for plotting
xaxis_range = [w0 + 0.01 if w0 < 0 else w0 - 0.01 for w0 in w0_range]
yaxis_range = [w1 + 0.01 if w1 < 0 else w1 - 0.01 for w1 in w1_range]
fig.update_layout(scene = dict(xaxis_title='w<sub>0</sub>',
yaxis_title='w<sub>1</sub>',
zaxis_title='MSE',
xaxis_range=xaxis_range,
yaxis_range=yaxis_range))
fig.show()
?七、參考和引用
- plot 3D 繪圖
- 亨特·菲利普斯
到了這里,關(guān)于機器學(xué)習(xí)簡介[01/2]:簡單線性回歸的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!