線性迴歸是機器學習中的一個非常重要的算法,一般用來進行數據的擬合或者預測,同時還可以用來特徵重要性評估。
線性迴歸一般分爲一元線性迴歸(一個x,一個y)、多元線性迴歸(多個x,一個y):
例如 :y=a*x+b,這就是一元函數
y=a*x1+b*x2+c ,這就是多元線性迴歸
在這裏我只講述一元的線性迴歸,多元的類型,我會在後面提及
首先,我們這裏有一堆數據
所謂一元線性迴歸要做的事情,實際上是去找一條最優直線,能夠以最小的誤差去擬合這些點。
如上圖所示,橫座標表示x,縱座標表示y。我們要找的就是圖中的這條直線。我們要去找到這條直線,大家可以想象,我們肯定希望找到的那條線,距離每個點都很近,最好所有的點上都在這條線上,但是一條直線去擬合所有的點都在這條直線上肯定不現實,所以我們希望這些點儘量離這條直線近一點。即去找每個點和直線的距離 最小的那條線,一般我們有幾個確定參數的方法:
- 使得誤差的最大絕對值達到最小
- 使誤差絕對值的平均值達到最小
- 使誤差的平方和達到最小
第一種方法中,所確定的參數使得最大誤差達到最小,因而稱爲最大最小問題,也稱爲最優一致逼近問題。這類問題在求解上存在一定的困難
第二種方法中,由於函數關於參數不可微,因此不能用一般的極值方法求最優解
第三中方法中,所確定的參數使得均方差最小,這種逼近稱爲最小二乘逼近,此時成y=mx+b爲最小二乘擬合直線,並且由於函數關於參數連續可微,所以計算上易於處理。在這裏,我們採用第三種方法,那麼誤差(代價函數)可以表示爲,這裏i表示第i個數據,N表示總的樣本個數。一般我們還會把Loss求和平均,來當作最終的損失,即:
怎麼求極值呢?
令每個變量的偏導數爲零,求方程組的解唄,這個是很基礎的高數問題了。
我們可以得到下面的方程組
然後就是巴拉巴拉巴拉把m和b求出來,這樣就得到我們要的線性方程了。
梯度下降法
沒有梯度下降就沒有現在的深度學習,這是一個神奇的算法。
最小二乘法可以一步到位,直接算出m和b,但他是有前提的,具體我有點記不清了,好像是需要滿秩什麼的。梯度下降法和最小二乘不一樣,它通過一步一步的迭代,慢慢的去靠近到那條最優直線。
最小二乘法裏面我們提到了兩個偏導數,分別爲
我們要去找Loss這個方程的最小值,最小值怎麼求?按數學的求法就是最小二乘法唄,但是大家可以直觀的想一下,很多地方都會用一個碗來形容,那我也找個碗來解釋吧。
大家把這個Loss函數想象成這個碗,而我們要求的最小值就是碗底。假設我們現在不能用最小二乘法求極小值,但是我們的計算機的計算能量很強,我們可以用計算量換結果,不管我們位於這個碗的什麼位置,只要我們想去碗底,就要往下走。
往下走????????
這個下不就是往梯度方向走嗎,那我們沿着梯度一點一點滑下去唄,反正計算機不嫌累。梯度不就是上面那兩個公式唄。現在梯度有了,那每次滑多遠呢,一滑劃過頭了不久白算半天了嗎,所以還得定義步長,用來表示每次滑多長。這樣我們就能每次向下走一點點,再定義一個迭代值用來表示滑多少次,這樣我們就能慢慢的一點點的靠近最小值了,不出意外還是能距離最優值很近的。
順便把上面這個梯度下降法實現下
每次向下滑要慢慢滑,就是要個步長,我們定義爲learning_rate,往往很小的一個值。
向下滑動的次數,就是迭代的次數,我定義爲num_iter,相對learning_rate往往很大。
定義好這兩個,我們就可以一邊求梯度,一邊向下滑了。就是去更新m和b。
我這裏用了加號,很多人會誤以爲梯度下降就要減,但是其實梯度本身是有方向的,所以這裏直接加就可以。
整體代碼如下:
import numpy
import pylab
import math
#生成數據集
def createData():
dataSet=numpy.loadtxt('DataSet/Data.txt') #讀取Data.txt文件
dataSet=dataSet.astype(dtype=float) #轉化成float類型
return dataSet
def line_regression(dataSet): #目標函數設爲 y=ax+b
learn_rate=0.001 #學習速率
init_a=0 #a初始化爲0
init_b=0 #b初始化爲0
iter_count=1000 #迭代次數
a,b=optimization(dataSet,init_a,init_b,learn_rate,iter_count) #尋找最優的參數a、b,使得擬合效果最佳
print(a,b)
plot_data(dataSet, a, b)
def plot_data(data,a,b):
#plottting
x = data[:,0]
y = data[:,1]
y_predict = a*x+b
pylab.plot(x,y,'o')
#pylab.plot(x,y_predict,'k-')
pylab.show()
def optimization(dataSet,init_a,init_b,learn_rate,iter_count):
a=init_a
b=init_b
for i in numpy.arange(0,iter_count): #迭代次數
a,b=compute_gradient(a,b,dataSet,learn_rate)
if(i%100==0):
print('iter {0}:error={1}'.format(i, compute_error(a, b, dataSet)))
return (a,b)
def compute_error(a,b,dataSet):
sum=0
for i in numpy.arange(len(dataSet)):
x=dataSet[i,0]
y=dataSet[i,1]
y_predict=a*x+b
sum+=math.pow(y-y_predict,2)
return sum/len(dataSet)
def compute_gradient(a,b,dataSet,learn_rata):
a_gradient = 0 #a的偏導數
b_gradient = 0 #b的偏導數
N = float(len(dataSet))
# Two ways to implement this
# first way
for i in range(0,len(dataSet)): #循環每一對數據,求a,b的偏導數
x = dataSet[i,0]
y = dataSet[i,1]
#computing partial derivations of our error function
#b_gradient = -(2/N)*sum((y-(m*x+b))^2)
#m_gradient = -(2/N)*sum(x*(y-(m*x+b))^2)
a_gradient += -(2 / N) * x * (y - ((a * x) + b))
b_gradient += -(2/N)*(y-((a*x)+b))
# Vectorization implementation
# x = dataSet[:, 0]
# y = dataSet[:, 1]
# b_gradient = -(2 / N) * (y - m_current * x - b_current)
# b_gradient = np.sum(b_gradient, axis=0)
# m_gradient = -(2 / N) * x * (y - m_current * x - b_current)
# m_gradient = np.sum(m_gradient, axis=0)
# update our b and m values using out partial derivations
#print(a_gradient,b_gradient)
new_a = a - (learn_rata * a_gradient) #如果a_gradient>0,說明在a點是遞增的,所以按照梯度下降原理,我們要往相反方向走,所以要減
#如果a_gradient<0,說明在a點是遞減的,所以按照梯度下降原理,要按照這個方向走下去,所以減去一個
#負值,相當於加上一個正值
new_b = b - (learn_rata * b_gradient) #原理同上
return [new_a, new_b]
if __name__=="__main__":
dataSet=createData()
line_regression(dataSet)