線性迴歸的推導
什麼是線性迴歸?
迴歸算法是一種有監督算法
迴歸算法是一種比較常用的機器學習算法,用於構建一個模型來做特徵向量到標籤的映射。,用來建立“解釋”變量(自變量X)和觀測值(因變量Y)之間的關係。在算法的學習過程中,試圖尋找一個模型,最大程度擬合訓練數據。
迴歸算法在使用時,接收一個n維度特徵向量,輸出一個連續的數據值。
下面通過一則案例引出線性迴歸:
房屋面積(m^2) | 租賃價格(1000¥) |
---|---|
10 | 0.8 |
15 | 1 |
20 | 1.8 |
30 | 2 |
50 | 3.2 |
60 | 3 |
60 | 3.1 |
70 | 3.5 |
目的:假如有一個新的房屋面積比如35平方米,那我們應該怎麼預測價格?
假設
輸入:x特徵向量
輸出:hθ(x)即預測值
假設擬合函數:
解決方案:
最小二乘法(又稱最小平方法)是一種數學優化技術。它由兩部分組成:
一、計算所有樣本誤差的平均(代價函數)
二、使用最優化方法尋找數據的最佳函數匹配(抽象的)
最小二乘法是抽象的,具體的最優化方法有很多,比如正規方程法、梯度下降法、牛頓法、擬牛頓法等等。
線性迴歸的表達式
選擇最優的θ值構成算法公式,並最終要求是計算出θ的值:
預備知識
大數定理
意義
隨着樣本容量n的增加,樣本平均數將接近於總體平均數(期望μ),所以在統計推斷中,一般都會使用樣本平均數估計總體平均數的值。也就是我們會使用一部分樣本的平均值來代替整體樣本的期望/均值,出現偏差的可能是存在的,但是當n足夠大的時候,偏差的可能性是非常小的,當n無限大的時候,這種可能性的概率基本爲0。
作用
就是爲使用頻率來估計概率提供了理論支持;爲使用部分數據來近似的模擬構建全部數據的特徵提供了理論支持。
如下圖的例子:
從1到10中進行隨機抽取數字,經過非常多次循環後,我們發現均值趨向5
import matplotlib.pyplot as plt
import random
import numpy as np
random.seed(28)
def generate_random_int(n):
return [random.randint(1, 9) for i in range(n)]
if __name__ == '__main__':
number = 8000
x = [i for i in range(number + 1) if i != 0]
total_random_int = generate_random_int(number)
y = [np.mean(total_random_int[0:i + 1]) for i in range(number)]
plt.plot(x, y, "b-")
plt.xlim(0, number)
plt.grid(True)
plt.show()
中心極限定理
意義
中心極限定理就是一般在同分布的情況下,抽樣樣本值的規範和在總體數量趨於無窮時的極限分佈近似於正態分佈。
作用
假設{Xn}爲獨立同分布的隨機變量序列,並具有相同的期望μ和方差爲σ2,則{Xn}服從中心極限定理,且Zn爲隨機序列{Xn}的規範和:
隨機的拋六面的骰子,計算三次的點數的和, 三次點數的和其實就是一個事件A,現在問事件A發生的概率以及事件A所屬的分佈是什麼?
import matplotlib.pyplot as plt
import random
import numpy as np
random.seed(28)
def generate_random_int():
# 模擬產生篩子
return random.randint(1,6)
def generate_mean(n):
# 隨機選擇三個篩子的和
return np.sum([generate_random_int() for i in range(n)])
if __name__ == '__main__':
number1 = 1000000
number2 = 3
keys = [generate_mean(number2) for i in range(number1)]
result = {}
for key in keys:
count = 1
if key in result:
count+=result[key]
result[key] = count
x = sorted(np.unique(list(result.keys())))
y = []
for key in x:
y.append(result[key]/number1)
plt.plot(x,y,"b-")
plt.xlim(x[0]-1,x[-1]+1)
plt.grid(True)
plt.show()
最大似然函數
最大概似估計、極大似然估計,是一種具有理論性的參數估計方法。基本思想是:當從模型總體隨機抽取n組樣本觀測值後,最合理的參數估計量應該使得從模型中抽取該n組樣本觀測值的概率最大;一般步驟如下:
- 寫出似然函數;
- 對似然函數取對數,並整理;
- 求導數;
- 解似然方程
梯度下降
優化思想
用當前位置負梯度方向作爲搜索方向,因爲該方向爲當前位置的最快下降方向,所以梯度下降法也被稱爲“最速下降法”。梯度下降法中越接近目標值,變量變化越小。計算公式如下:
α被稱爲步長或者學習率(learningrate),表示自變量θ每次迭代變化的大小。
收斂條件:當目標函數的函數值變化非常小的時候或者達到最大迭代次數的時候,就結束循環。
分類
批量梯度下降法(BatchGradient Descent)
•有N個樣本,求梯度的時候就用了N個樣本的梯度數據
•優點:準確 缺點:速度慢
隨機梯度下降
•和批量梯度下降法原理類似,區別在於求梯度時沒有用所有的N個樣本的數據,而是僅僅選取一個樣本來求梯度
•優點:速度快 缺點:準確度低
小批量梯度下降法(Mini-batchGradient Descent)
•批量梯度下降法和隨機梯度下降法的折衷,比如N=100
•spark中使用的此方法
例子
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return 0.5*(x-0.25)**2
def h(x):
return x-1/4
X = []
Y = []
x =2
# 此時的學習率,是由一定區間的,過小會導致性能變慢,過大可能發散
step = 0.8
f_change = f(x)
f_current = f(x)
X.append(x)
Y.append(f_current)
# 當變化值小於1e-10的時候停止,也就是梯度
while f_change > 1e-10 and len(X)<100:
# 這裏是梯度下降的變化程度x = x - k*f(x)'
x = x - step * h(x)
tmp = f(x)
f_change = np.abs(f_current - tmp)
f_current = tmp
X.append(x)
Y.append(f_current)
fig = plt.figure()
X2 = np.arange(-2.1,2.65,0.05)
Y2 = 0.5*(X2-0.25)**2
plt.plot(X2,Y2,'-',color="#666666",linewidth = 2)
plt.plot(X,Y,'bo--')
plt.title("$y= 0.5*(x-0.25)^2$通過梯度下降法得到目標值x=%.2f,y=%.2f迭代次數%d"%(x,f_current,len(X)))
plt.show()
三維數據中的梯度下降
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def f(x, y):
return x ** 2 + y ** 2
def h(t):
return 2 * t
X = []
Y = []
Z = []
x = 2
y = 2
f_change = x ** 2 + y ** 2
f_current = f(x, y)
step = 0.1
X.append(x)
Y.append(y)
Z.append(f_current)
while f_change > 1e-10:
# 對於各自未知數求偏導
x = x - step * h(x)
y = y - step * h(y)
f_change = np.abs(f_current - f(x,y))
f_current = f(x,y)
X.append(x)
Y.append(y)
Z.append(f_current)
fig = plt.figure()
ax = Axes3D(fig)
X2 = np.arange(-2,2,0.2)
Y2 = np.arange(-2,2,0.2)
X2,Y2 = np.meshgrid(X2,Y2)
Z2 = X2**2 + Y2**2
ax.plot_surface(X2,Y2,Z2,rstride=1,cstride=1,cmap='rainbow')
ax.plot(X,Y,Z,'ro--')
ax.set_title("")
plt.show()
推導線性迴歸
確定目標函數:
首先我們找到的數據,或者在測量的過程中肯定會產生或多或少的數據,所以這裏就利用中心極限定理,我們可以將誤差epsilon認爲是獨立分佈的服從均值爲0方差爲σ^2的高斯分佈。
利用最大似然函數來求解:
目標函數爲:
因爲累乘求導比較複雜,這裏利用對數化,形成對數似然函數:
這裏我們主要是求似然函數的最大值,前面是常量忽略不計,所以主要是後面的內容。
這裏的目標函數是平方和損失函數,除了它還有0-1損失函數,感知損失函數,絕對值損失函數,對數損失函數。
利用梯度下降對目標函數θ求解
初始化θ(隨機初始化,可以初始爲0)
沿着負梯度方向迭代,更新後的θ使J(θ)更小
對損失函數進行求導:
實現代碼:
批量下降線性迴歸:
import numpy as np
# 構造訓練數據集
x_train = np.array([[2, 0., 3], [3, 1., 3], [0, 2., 3], [4, 3., 2], [1, 4., 4]])
m = len(x_train)
print(m)
x0 = np.full((m, 1), 1)
print(x0)
# 構造一個每個數據第一維特徵都是1的矩陣
input_data = np.hstack([x0, x_train])
m, n = input_data.shape
theta1 = np.array([[2, 3, 4]]).T
# 構建標籤數據集,後面的np.random.randn是將數據加一點噪聲,以便模擬數據集。
# y_train = (input_data.dot(np.array([1, 2, 3, 4]).T)).T
y_train = x_train.dot(theta1) + np.array([[2], [2], [2], [2], [2]])
# 設置兩個終止條件
loop_max = 1000000
epsilon = 1e-5
# 初始theta
np.random.seed(0) # 設置隨機種子
theta = np.random.randn(n, 1) # 隨機取一個1維列向量初始化theta
# 初始化步長/學習率
alpha = 0.00001
# 初始化誤差,每個維度的theta都應該有一個誤差,所以誤差是一個4維。
error = np.zeros((n, 1)) # 列向量
# 初始化偏導數
diff = np.zeros((input_data.shape[1], 1))
# 初始化循環次數
count = 0
while count < loop_max:
count += 1
sum_m = np.zeros((n, 1))
for i in range(m):
for j in range(input_data.shape[1]):
# 計算每個維度的theta
diff[j] = (input_data[i].dot(theta) - y_train[i]) * input_data[i, j]
# 求每個維度的梯度的累加和
sum_m = sum_m + diff
# 利用這個累加和更新梯度
theta = theta - alpha * sum_m
# else中將前一個theta賦值給error,theta - error便表示前後兩個梯度的變化,當梯度
# 變化很小(在接收的範圍內)時,便停止迭代。
if np.linalg.norm(theta - error) < epsilon:
break
else:
error = theta
print(theta)
隨機梯度線性迴歸:
# -*- coding: utf-8 -*-
"""
Created on Wed Nov 28 14:02:17 2018
@author: Administrator
"""
import numpy as np
# 構造訓練數據集
x_train = np.array([[2, 0., 3], [3, 1., 3], [0, 2., 3], [4, 3., 2], [1, 4., 4]])
# 構建一個權重作爲數據集的真正的權重,theta1主要是用來構建y_train,然後通過模型計算
# 擬合的theta,這樣可以比較兩者之間的差異,驗證模型。
theta1 = np.array([[2 ,3, 4]]).T
# 構建標籤數據集,y=t1*x1+t2*x2+t3*x3+b即y=向量x_train乘向量theta+b, 這裏b=2
y_train = (x_train.dot(theta1) + np.array([[2],[2],[2],[2],[2]])).ravel()
# 構建一個5行1列的單位矩陣x0,然它和x_train組合,形成[x0, x1, x2, x3],x0=1的數據形式,
# 這樣可以將y=t1*x1+t2*x2+t3*x3+b寫爲y=b*x0+t1*x1+t2*x2+t3*x3即y=向量x_train乘向
# 量theta其中theta應該爲[b, *, * , *],則要擬合的theta應該是[2,2,3,4],這個值可以
# 和算出來的theta相比較,看模型的是否達到預期
x0 = np.ones((5, 1))
input_data = np.hstack([x0, x_train])
m, n = input_data.shape
# 設置兩個終止條件
loop_max = 10000000
epsilon = 1e-6
# 初始化theta(權重)
np.random.seed(0)
theta = np.random.rand(n).T # 隨機生成10以內的,n維1列的矩陣
# 初始化步長/學習率
alpha = 0.000001
# 初始化迭代誤差(用於計算梯度兩次迭代的差)
error = np.zeros(n)
# 初始化偏導數矩陣
diff = np.zeros(n)
# 初始化循環次數
count = 0
while count < loop_max:
count += 1 # 沒運行一次count加1,以此來總共記錄運行的次數
# 計算梯度
for i in range(m):
# 計算每個維度theta的梯度,並運用一個梯度去更新它
diff = input_data[i].dot(theta)-y_train[i]
theta = theta - alpha * diff*(input_data[i])
# else中將前一個theta賦值給error,theta - error便表示前後兩個梯度的變化,當梯度
#變化很小(在接收的範圍內)時,便停止迭代。
if np.linalg.norm(theta - error) < epsilon: # 判斷theta與零向量的距離是否在誤差內
break
else:
error = theta
print(theta)
小批量梯度下降
import numpy as np
# 構造訓練數據集
x_train = np.array([[2, 0., 3], [3, 1., 3], [0, 2., 3], [4, 3., 2], [1, 4., 4]])
m = len(x_train)
x0 = np.full((m, 1), 1)
# 構造一個每個數據第一維特徵都是1的矩陣
input_data = np.hstack([x0, x_train])
m, n = input_data.shape
theta1 = np.array([[2, 3, 4]]).T
# 構建標籤數據集,後面的np.random.randn是將數據加一點噪聲,以便模擬數據集。
# y_train = (input_data.dot(np.array([1, 2, 3, 4]).T)).T
y_train = x_train.dot(theta1) + np.array([[2], [2], [2], [2], [2]])
# 設置兩個終止條件
loop_max = 1000000
epsilon = 1e-5
# 初始theta
np.random.seed(0) # 設置隨機種子
theta = np.random.randn(n, 1) # 隨機取一個1維列向量初始化theta
# 初始化步長/學習率
alpha = 0.00001
# 初始化誤差,每個維度的theta都應該有一個誤差,所以誤差是一個4維。
error = np.zeros((n, 1)) # 列向量
# 初始化偏導數
diff = np.zeros((input_data.shape[1], 1))
# 初始化循環次數
count = 0
# 設置小批量的樣本數
minibatch_size = 2
while count < loop_max:
count += 1
sum_m = np.zeros((n, 1))
for i in range(1, m, minibatch_size):
# for j in range(i - 1, i + minibatch_size - 1, 1):
# # 計算每個維度的theta
# diff[j] = (input_data[i].dot(theta) - y_train[i]) * input_data[i, j]
diff = (1/minibatch_size)*input_data[i:i+minibatch_size,:].T.dot(input_data[i:i+minibatch_size,:].dot(theta) - y_train[i:i+minibatch_size,:])
# 求每個維度的梯度的累加和
sum_m = sum_m + diff
# 利用這個累加和更新梯度
theta = theta - alpha * (1.0 / minibatch_size) * sum_m
# else中將前一個theta賦值給error,theta - error便表示前後兩個梯度的變化,當梯度
# 變化很小(在接收的範圍內)時,便停止迭代。
if np.linalg.norm(theta - error) < epsilon:
break
else:
error = theta
# 和sklearn庫相對比
# from sklearn.linear_model import LinearRegression
# model = LinearRegression(fit_intercept=False)
# model.fit(input_data,y_train)
# print(model.coef_)
print(theta)
線性迴歸和正則化
向量範數
1-範數:即向量元素絕對值之和,調用函數np.linalg.norm(x, 1)
2-範數:Euclid範數(歐幾里得範數,常用計算向量長度),即向量元素絕對值的平方 和再開方,調用函數np.linalg.norm(x) (默認爲2)
無窮大範數:即所有向量元素絕對值中的最大值,調用函數np.linalg.norm (x, np.inf)
無窮小范數:即所有向量元素絕對值中的最小值,調用函數np.linalg.norm (x, -np.inf)
p-範數即向量元素絕對值的p次方和的1/p次冪,調用函數np.linalg.norm (x, p)
下圖表示了p從無窮到0變化時,三維空間中到原點的距離(範數)爲1的點構成的圖形的變化情況。
經驗風險和結構風險(可跳過)
期望風險表示的是全局的概念,表示的是決策函數對所有的樣本(X,Y)預測能力的大小,而經驗風險則是局部的概念,僅僅表示決策函數對訓練數據集裏樣本的預測能力。理想的模型(決策)函數應該是讓所有的樣本的損失函數最小的(也即期望風險最小化),但是期望風險函數往往是不可得到的,即上式中,X與Y的聯合分佈函數不容易得到。現在我們已經清楚了期望風險是全局的,理想情況下應該是讓期望風險最小化,但是呢,期望風險函數又不是那麼容易得到的。怎麼辦呢?那就用局部最優的代替全局最優這個思想吧。這就是經驗風險最小化的理論基礎。
通過上面的分析可以知道,經驗風險與期望風險之間的聯繫與區別。現在再總結一下:
經驗風險是局部的,基於訓練集所有樣本點損失函數最小化的。
期望風險是全局的,是基於所有樣本點的損失函數最小化的。
經驗風險函數是現實的,可求的;
期望風險函數是理想化的,不可求的;
只考慮經驗風險的話,會出現過擬合的現象,過擬合的極端情況便是模型f(x)對訓練集中所有的樣本點都有最好的預測能力,但是對於非訓練集中的樣本數據,模型的預測能力非常不好。怎麼辦呢?這個時候就引出了結構風險。結構風險是對經驗風險和期望風險的折中。在經驗風險函數後面加一個正則化項(懲罰項)便是結構風險了。如下式:
相比於經驗風險,結構風險多了一個懲罰項,其中是一個l是一個大於0的係數。J(f)表示的是模型f的複雜度。結構風險可以這麼理解:
經驗風險越小,模型決策函數越複雜,其包含的參數越多,當經驗風險函數小到一定程度就出現了過擬合現象。也可以理解爲模型決策函數的複雜程度是過擬合的必要條件,那麼我們要想防止過擬合現象的發生,就要破壞這個必要條件,即降低決策函數的複雜度。也即,讓懲罰項J(f)最小化,現在出現兩個需要最小化的函數了。我們需要同時保證經驗風險函數和模型決策函數的複雜度都達到最小化,一個簡單的辦法把兩個式子融合成一個式子得到結構風險函數然後對這個結構風險函數進行最小化。
簡而言之,經驗風險最小化可以理解爲:最小化代價函數結構風險最小化可以理解爲:最小化目標函數(代價函數+正則化項)
爲什麼要引入正則項
爲了防止數據過擬合,也就是的θ值在樣本空間中不能過大/過小,可以在目標函數之上增加一個平方和損失:
Ridge迴歸
L2正則化:權值向量w中各個元素的平方和
上面我們一直使用θ的平方作爲正則項也就是L2正則,它的學名叫做Ridge迴歸或者嶺迴歸,
Lasso迴歸
L1正則化:權值向量w中各個元素的絕對值之和
L1正則化 VS L2正則化
L1正則化可以產生稀疏權值矩陣,即產生一個稀疏模型,可以用於特徵選擇。
L2正則化可以防止模型過擬合(overfitting)
爲什麼 L1 正則可以產生稀疏模型(很多參數=0),而L2 正則不會出現很多參數爲0的情況?
由圖可知左面式L1正則,右面是L2正則,這裏左面更容易和超平面在y軸上形成最優解,此時x=0也就是產生了稀疏解,這正是我們大多說時候想要的。
Elasitc Net
同時使用L1正則和L2正則的線性迴歸模型就稱爲Elasitc Net算法(彈性網絡算法)
到此線性迴歸以及優化已經全部總結,訓練結束後可以使用MSE,RMSE,R^2這些指標進行評估。