參考了好多大佬的博客、知乎、視頻,鏈接列在文章末尾。
一、什麼是迴歸
與迴歸算法相對應的是分類算法,簡而言之,迴歸就是預測一系列連續的值,分類就是預測一系列離散的值。
那麼如何把機器學習中的迴歸算法應用於日常生活呢?以下用一個簡單的例子來說明:
通過市場調查,我們得到一些房屋面積和價格的相關數據。我們想知道,如果給一個新的房屋面積 ,能否根據已知的數據來預測其對應價格是多少呢?如圖:
爲了解決這個問題,我們引入線性迴歸模型。
二、一元線性迴歸
首先,我們畫出已知數據的散點圖1:
其次,我們模擬出一條直線,讓已知的數據點儘量落在直線上或直線周圍。如散點圖2:
最後,我們求出這條直線模型對應的函數公式,然後代入x=130,即可求得其預測價格f(x)。
而線性模型公式在這個例子中就是一條直線:。其中,w爲係數,b爲截距。
我們現在知道,線性迴歸就是要找一條直線,並且讓這條直線儘可能地擬合圖中的數據點。
那麼如何得到w和b從而構造這個公式呢?估計如果讓1000個人來畫這條線就會有1000種畫法,比如說散點圖3和散點圖4:
所以,我們需要一個評判標準,來評判哪條直線纔是最好的。
由此,我們引入損失函數來作爲評判標準。
三、損失函數
接上面的散點圖3 和散點圖4 兩種擬合情況。對於散點圖3的擬合直線,以及散點圖4的擬合直線,到底哪一條直線才最“合適”呢?
由此我們引入殘差,說白了就是真實值和預測值間的差值(也可以理解爲差距、距離)。即算一下實際房價和根據擬合直線的預測房價之間的差距(距離)就行了。
當把所有實際房價和預測房價的差距(距離)算出來然後做個加和,我們就能量化預測房價和實際房價之間的殘差。
例如下圖散點圖5中有很多紅色小豎線,每一條就是實際房價和預測房價的差距(距離)。
殘差公式:
其中,f(x)是預測房價,y是真實房價。
損失函數/殘差平方和/均方誤差(MSE)/歐氏距離之和:
其中,J是損失函數,m表示樣本個數,是預測值,是真實值。
總結,損失函數是衡量回歸模型誤差的函數,也就是我們要的“直線”的評價標準。
這個函數的值越小,說明直線越能擬合我們的數據。
好了,到這裏,我們通過損失函數公式,結合散點圖3參數 w=0.7925, b=15.353 ,和散點圖4參數 w=1.2452, b=-25 。算得散點圖3的損失函數J要小於散點圖4的損失函數J。所以,可以說明,散點圖3擬合的直線要比散點圖4擬合的直線更“合適”。
但是,我們不應該止步於此,我們要找的不是兩者之間的更優解,而應該是所有擬合直線中的最“合適”。
由此,我們引出最小二乘法。
四、最小二乘“參數估計”
在線性迴歸中,最小二乘法就是試圖找到一條直線,使所有樣本到直線上的歐式距離之和最小。
這套路,不就是已知函數,它有兩個自變量w和b,我們要求解w和b,使得這個函數的值最小。求解w和b的過程,美名其曰線性迴歸模型的最小二乘“參數估計”。
其求解過程無非就是微積分中,將J(w,b)分別對w和b求導,然後令其導數爲0,便可得到w和b的最優解。此處過程略去,得到:
其中,爲x的均值。
把這邊解得的w和b代入預測公式,即成功得到最合適的一元線性迴歸模型了。
五、多元線性迴歸
敲醒,線性迴歸哪有那麼簡單。上面都是拿一元線性迴歸舉的例子。可是我們生活中更常見的是多元問題。
就比如還拿那個房價預測舉例,常識可知,房價怎麼可能單單看房屋面積就決定,肯定還要考慮很多其他屬性,如房間數量、樓間距、離學校距離等等等等。
所以現在依舊是m個樣本做預測,但每個樣本不止一個屬性,而是由d個屬性描述。由此,我們引入多元線性迴歸。
仿照一元線性迴歸公式,我們得到多元線性迴歸公式:
其中,表示d個屬性每個的參數(權重);表示第i個樣本,每個屬性(d個)的取值,i的範圍爲1到m。
我們把它改寫成更高大上的向量形式:
其中,, ,
六、多元線性迴歸損失函數
同理,仿照一元線性迴歸的損失函數,我們得到多元線性迴歸的損失函數(向量表示):
下面來解釋這個公式:
已知: , ,。
其中:把w和b吸入向量形式。
其中:把數據集表示爲一個大小的矩陣,其中每行對應一個樣本,該行前d個元素對應於樣本的d個屬性值,最後一個元素恆置爲1。
其中:。
所以,
所以的平方在矩陣中即爲,即得到損失函數/歐氏距離之和:。
七、多元線性迴歸最小二乘法
用損失函數對求導,並等於0。
這次的解方程並沒有像一元線性迴歸那麼簡單,我們需要分情況討論:
第一種情況:當爲滿秩矩陣或正定矩陣時
得到的最優解:
其中,是的逆矩陣。
令,得到最終學得的最合適的多元線性迴歸模型爲。
第二種情況:當不爲滿秩矩陣時
現實任務中我們會遇到大量變量(對應大量待算屬性,大量未知的w),其數目甚至超過了樣本數目,導致的列數多於行數,顯然不滿秩。
此時可以解出多個,它們都能使均方誤差最小化。到底選擇哪一個解作爲輸出呢?將由學習算法的歸納偏好決定,常見的做法是引入正則化項(說白了就是損失函數公式最後加一個項,不詳細展開了)。
八、對數線性迴歸模型
以上部分介紹了一元線性迴歸模型和多元線性迴歸模型。
我們現在把模型公式來做個總結,把線性迴歸模型簡寫爲:。
其通過訓練樣本得出最優的w和b,從而對給定的新的樣本x進行值y的預測,其實際上是在試圖讓模型預測值不斷逼近真實值。
那我們是否可令模型預測值不斷逼近真實值的“衍生物”呢?比如說, 現在真實值擴大爲指數族中的任一分佈。
那就可將預測值的對數作爲線性模型逼近的目標,這樣得到的模型稱爲“對數線性迴歸”:。它實際上是在試圖讓模型預測值不斷逼近真實值。
它形式上仍是線性迴歸,但實質上已是在求取輸入空間到輸出空間的非線性函數映射。這裏的對數起到了將線性迴歸模型的預測值與真實標記聯繫起來的作用。
九、廣義線性模型
我們可以變換上面的“對數線性迴歸”公式爲。
但若真實值 y 不爲指數族中的分佈,而變爲更一般的分佈呢?
這樣得到的模型,我們稱爲“廣義線性模型”: 。
其中,函數稱爲“聯繫函數”,其單調可微。
很顯然,對數線性迴歸是廣義線性模型在時的特例。
十、pytorch實現一元線性迴歸
1. 網絡結構
繼承pytorch提供的nn.Module()
類。通過把nn.Linear()
綁定到類實例屬性,以及實現forward()
方法實現前向傳播:
class LinearRegression(torch.nn.Module):
"""
Linear Regressoin Module, the input features and output
features are defaults both 1
"""
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(1,1)
def forward(self,x):
out = self.linear(x)
return out
2. 優化算法選擇SGD優化
self.optimizer = torch.optim.SGD(self.model.parameters(), lr=self.learning_rate)
3. 損失函數選擇爲MSE
self.loss_function = torch.nn.MSELoss()
4. 構建數據
構建一個類似於線性函數的數據集,即y=kx+b,並且添加一個擾動噪聲:
import torch
import matplotlib.pyplot as plt
def create_linear_data(nums_data, if_plot=False):
"""
Create data for linear model
Args:
nums_data: how many data points that wanted
Returns:
x with shape (nums_data, 1)
"""
x = torch.linspace(0, 1, nums_data)
x = torch.unsqueeze(x, dim=1)
k = 2
y = k * x + torch.rand(x.size())
if if_plot:
plt.scatter(x.numpy(), y.numpy(), c='b')
plt.show()
data = {"x": x, "y": y}
return data
訓練測試集(100個樣本)
測試數據集(20個樣本)
5. 網絡訓練
訓練網絡的順序爲:讀取數據—數據送入網絡—得到網絡輸出—用輸出與標籤計算損失—最小化損失—更新梯度。
下列代碼在訓練網絡過程中,同時動態繪出了每隔500個epoch的線性模型函數擬合情況:
def train(self, data, model_save_path="model.pth"):
"""
Train the model and save the parameters
Args:
model_save_path: saved name of model
data: (x, y) = data, and y = kx + b
Returns:
None
"""
x = data["x"]
y = data["y"]
fig = plt.figure(figsize=(10, 10))
current_fig = 0
for epoch in range(self.epoches):
prediction = self.model(x)
loss = self.loss_function(prediction, y)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
if epoch % 500 == 0:
print("epoch: {}, loss is: {}".format(epoch, loss.item()))
current_fig += 1
plt.subplot(4, 5, current_fig)
plt.scatter(x.numpy(), y.numpy(), c='b')
plt.plot(x.numpy(), prediction.detach().numpy(), color="r")
plt.show()
torch.save(self.model.state_dict(), "linear.pth")
代碼最後一行torch.save()保存了模型的參數,用於測試階段使用。
訓練逐漸擬合線性迴歸函數的過程:
訓練擬合過程
6. 模型測試
模型測試階段需要讀取訓練階段保存的參數,並重新賦值給網絡:
def test(self, data, model_path="linear.pth"):
"""
Reload and test the model, plot the prediction
Args:
model_path: the model's path and name
data: (x, y) = data, and y = kx + b
Returns:
None
"""
x = data["x"]
y = data["y"]
self.model.load_state_dict(torch.load(model_path))
prediction = self.model(x)
loss = self.loss_function(prediction, y)
print("loss of test is: {}".format(loss.item()))
plt.scatter(x.numpy(), y.numpy(), c='b', marker='x')
plt.plot(x.numpy(), prediction.detach().numpy(), color="r")
plt.show()
測試結果
並且得到損失值如下:
7. 完整代碼
import torch
import matplotlib.pyplot as plt
def create_linear_data(nums_data, if_plot=False):
"""
Create data for linear model
Args:
nums_data: how many data points that wanted
Returns:
x with shape (nums_data, 1)
"""
x = torch.linspace(0, 1, nums_data)
x = torch.unsqueeze(x, dim=1)
k = 2
y = k * x + torch.rand(x.size())
if if_plot:
plt.scatter(x.numpy(), y.numpy(), c='b')
plt.show()
data = {"x": x, "y": y}
return data
class LinearRegression(torch.nn.Module):
"""
Linear Regressoin Module, the input features and output
features are defaults both 1
"""
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
out = self.linear(x)
return out
class Linear_Model():
def __init__(self):
"""
Initialize the Linear Model
"""
self.learning_rate = 0.001
self.epoches = 10000
self.loss_function = torch.nn.MSELoss()
self.create_model()
def create_model(self):
self.model = LinearRegression()
self.optimizer = torch.optim.SGD(self.model.parameters(), lr=self.learning_rate)
def train(self, data, model_save_path="model.pth"):
"""
Train the model and save the parameters
Args:
model_save_path: saved name of model
data: (x, y) = data, and y = kx + b
Returns:
None
"""
x = data["x"]
y = data["y"]
fig = plt.figure(figsize=(10, 10))
current_fig = 0
for epoch in range(self.epoches):
prediction = self.model(x)
loss = self.loss_function(prediction, y)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
if epoch % 500 == 0:
print("epoch: {}, loss is: {}".format(epoch, loss.item()))
current_fig += 1
plt.subplot(4, 5, current_fig)
plt.scatter(x.numpy(), y.numpy(), c='b')
plt.plot(x.numpy(), prediction.detach().numpy(), color="r")
plt.show()
torch.save(self.model.state_dict(), "linear.pth")
def test(self, data, model_path="linear.pth"):
"""
Reload and test the model, plot the prediction
Args:
model_path: the model's path and name
data: (x, y) = data, and y = kx + b
Returns:
None
"""
x = data["x"]
y = data["y"]
self.model.load_state_dict(torch.load(model_path))
prediction = self.model(x)
loss = self.loss_function(prediction, y)
print("loss of test is: {}".format(loss.item()))
plt.scatter(x.numpy(), y.numpy(), c='b', marker='x')
plt.plot(x.numpy(), prediction.detach().numpy(), color="r")
plt.show()
if __name__ == '__main__':
linear = Linear_Model()
data_train = create_linear_data(100, True)
linear.train(data_train)
data = create_linear_data(20, True)
linear.test(data)
十一、sklearn實現多元線性迴歸
案例:波士頓房價預測
網上關於個這個的資料和代碼實在是太多了,這邊列出幾個網址,這篇文章裏就不詳細講了,套路都是一樣的。
- 【機器學習】線性迴歸sklearn實現 - AI_developer - 博客園
- https://blog.csdn.net/qq_28827635/article/details/84481414
- https://my.oschina.net/u/2245781/blog/1855834
- https://github.com/TalkEveryX/Linear_Model/blob/master/boston_housing.py
十二、參考資料
- https://blog.csdn.net/alw_123/article/details/82193535
- https://blog.csdn.net/alw_123/article/details/82825785
- 化簡可得:用人話講明白線性迴歸LinearRegression
- 人人都會機器學習:線性迴歸模型原理及推導
- 王偉同學:線性迴歸:這可能是機器學習中最簡單的一個模型了
- 線性迴歸(Linear Regression)
- https://blog.csdn.net/pxhdky/article/details/82388964
- 周志華《機器學習》
- 機器學習入坑者:線性迴歸模型與pytorch實現
- https://zhuanlan.zhihu.com/p/59401172