手寫演算法-python代碼實作Lasso回歸
- Lasso回歸簡介
- Lasso回歸分析與python代碼實作
- 1、python實作坐標軸下降法求解Lasso
- 呼叫sklearn的Lasso回歸對比
- 2、近似梯度下降法python代碼實作Lasso
Lasso回歸簡介
上一篇文章我們詳細介紹了過擬合和L1、L2正則化,Lasso就是基于L1正則化,它可以使得引數稀疏,防止過擬合,其中的原理都講的很清楚,詳情可以看我的這篇文章,
鏈接: 原理決議-過擬合與正則化
本文主要實作python代碼的Lasso回歸,并用實體佐證原理,
Lasso回歸分析與python代碼實作
我們先生成資料集,還是用sklearn生成,
import numpy as np
from matplotlib import pyplot as plt
import sklearn.datasets
#生成100個一元回歸資料集
x,y = sklearn.datasets.make_regression(n_features=1,noise=5,random_state=2020)
plt.scatter(x,y)
plt.show()

如上所示,生成了一個一元回歸資料集,如果資料中混入了噪聲,如:(手動添加5個噪聲資料)
#加5個例外資料,為什么這么加,大家自己看一下生成的x,y的樣子
a = np.linspace(1,2,5).reshape(-1,1)
b = np.array([350,380,410,430,480])
#生成新的資料集
x_1 = np.r_[x,a]
y_1 = np.r_[y,b]
plt.scatter(x_1,y_1)
plt.show()

這個時候,資料表現為這個樣子,由于這幾個資料是例外資料,所以我們的線性回歸模型應該擬合下面的樣本點,即最終的引數應該比較小,不應該因為加入了幾個很例外的資料,導致引數發生很大的偏移,以這個圖為例,就是不應該變得很大,
,下面用我們之前寫好的線性回歸類(python代碼實作),來展示效果:
class normal():
def __init__(self):
pass
def fit(self,x,y):
m=x.shape[0]
X = np.concatenate((np.ones((m,1)),x),axis=1)
xMat=np.mat(X)
yMat =np.mat(y.reshape(-1,1))
xTx=xMat.T*xMat
#xTx.I為xTx的逆矩陣
ws=xTx.I*xMat.T*yMat
#回傳引數
return ws
plt.rcParams['font.sans-serif']=['SimHei'] #用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus']=False #用來正常顯示負號
clf1 =normal()
#擬合原始資料
w1 = clf1.fit(x,y)
#預測資料
y_pred = x * w1[1] + w1[0]
#擬合新資料
w2 = clf1.fit(x_1,y_1)
#預測資料
y_1_pred = x_1 * w2[1] + w2[0]
print('原始樣本擬合引數:\n',w1)
print('\n')
print('新樣本擬合引數:\n',w2)
ax1= plt.subplot()
ax1.scatter(x_1,y_1,label='樣本分布')
ax1.plot(x,y_pred,c='y',label='原始樣本擬合')
ax1.plot(x_1,y_1_pred,c='r',label='新樣本擬合')
ax1.legend(prop = {'size':15}) #此引數改變標簽字號的大小
plt.show()

W的第一個引數是截距,第二個引數是斜率,也就是系數,可以看到系數變大了很多,僅因為加入了幾個噪聲,模型的魯棒性很差,泛化能力也差,出現了一定程度的過擬合,
我們再來看Lasso的運算式:

= 線性回歸損失函式 + L1正則項,上一篇文章我們有分析過L1正則項的特點(本文前面有鏈接),引數λ是正則項系數,正則項對引數θ不是連續可導,一般情況下,有以下兩種方式來求Lasso的引數,
1、坐標軸下降法
2、用最小角回歸法
這里推薦一篇劉建平博士的博客,寫得很清楚,
鏈接: Lasso回歸演算法: 坐標軸下降法與最小角回歸法小結

1、python實作坐標軸下降法求解Lasso
我們采用坐標軸下降法來求引數:python代碼實作如下:
#臨時寫的函式,要在引入一個copy包,進行深度拷貝
#大家寫一份代碼,把要引入的包全放在最前面
import copy
def CoordinateDescent(x, y,epochs,learning_rate,Lambda):
m=x.shape[0]
X = np.concatenate((np.ones((m,1)),x),axis=1)
xMat=np.mat(X)
yMat =np.mat(y.reshape(-1,1))
w = np.ones(X.shape[1]).reshape(-1,1)
for n in range(epochs):
out_w = copy.copy(w)
for i,item in enumerate(w):
#在每一個W值上找到使損失函式收斂的點
for j in range(epochs):
h = xMat * w
gradient = xMat[:,i].T * (h - yMat)/m + Lambda * np.sign(w[i])
w[i] = w[i] - gradient* learning_rate
if abs(gradient)<1e-3:
break
out_w = np.array(list(map(lambda x:abs(x)<1e-3, out_w-w)))
if out_w.all():
break
return w
CoordinateDescent()函式來實作我們的Lasso回歸,示例:
當Lambda引數為0時,也就是不加L1正則項時,就是普通的線性回歸,引數輸出都是一樣的,也是47點多
#Lambda=0時;
w = CoordinateDescent(x_1,y_1,epochs=250,learning_rate=0.001,Lambda=0)
print(w)
#計算新的擬合值
y_1_pred = x_1 * w[1] + w[0]
ax1= plt.subplot()
ax1.scatter(x_1,y_1,label='樣本分布')
ax1.plot(x,y_pred,c='y',label='原始樣本擬合')
ax1.plot(x_1,y_1_pred,c='r',label='新樣本擬合')
ax1.legend(prop = {'size':15}) #此引數改變標簽字號的大小
plt.show()

當Lambda =10時,引數變為37點多;
#Lambda=10時;
w = CoordinateDescent(x_1,y_1,epochs=250,learning_rate=0.001,Lambda=10)
print(w)
#計算新的擬合值
y_1_pred = x_1 * w[1] + w[0]
ax1= plt.subplot()
ax1.scatter(x_1,y_1,label='樣本分布')
ax1.plot(x,y_pred,c='y',label='原始樣本擬合')
ax1.plot(x_1,y_1_pred,c='r',label='新樣本擬合')
ax1.legend(prop = {'size':15}) #此引數改變標簽字號的大小
plt.show()

當Lambda =30時,引數變為17點多,基本上已經和沒添加例外值的引數是一樣的了;
#Lambda=30時;
w = CoordinateDescent(x_1,y_1,epochs=250,learning_rate=0.001,Lambda=30)
print(w)
#計算新的擬合值
y_1_pred = x_1 * w[1] + w[0]
ax1= plt.subplot()
ax1.scatter(x_1,y_1,label='樣本分布')
ax1.plot(x,y_pred,c='y',label='原始樣本擬合')
ax1.plot(x_1,y_1_pred,c='r',label='新樣本擬合')
ax1.legend(prop = {'size':15}) #此引數改變標簽字號的大小
plt.show()

當Lambda =100時,引數基本上已經趨近于0,擬合線差不多就是一條水平線了;
#Lambda=100時;
w = CoordinateDescent(x_1,y_1,epochs=250,learning_rate=0.001,Lambda=100)
print(w)
#計算新的擬合值
y_1_pred = x_1 * w[1] + w[0]
ax1= plt.subplot()
ax1.scatter(x_1,y_1,label='樣本分布')
ax1.plot(x,y_pred,c='y',label='原始樣本擬合')
ax1.plot(x_1,y_1_pred,c='r',label='新樣本擬合')
ax1.legend(prop = {'size':15}) #此引數改變標簽字號的大小
plt.show()

正則項引數過大、過小都不好,
過小起不到懲罰效果,模型任然過擬合;
過大懲罰太大,會使得模型欠擬合,達不到要求;
我們選擇引數的標準:模型在訓練集、驗證集、測驗集上,評估效果接近時,這個正則項引數較好,
呼叫sklearn的Lasso回歸對比
同樣的,可以呼叫sklearn的Lasso回歸來測驗代碼的正確性;
(只看引數的值,圖就不畫了)
from sklearn.linear_model import Lasso
lr=Lasso(alpha=0)
lr.fit(x_1,y_1)
print('alpha=0時',lr.coef_,'\n')
lr=Lasso(alpha=10)
lr.fit(x_1,y_1)
print('alpha=10時',lr.coef_,'\n')
lr=Lasso(alpha=30)
lr.fit(x_1,y_1)
print('alpha=30時',lr.coef_,'\n')
lr=Lasso(alpha=100)
lr.fit(x_1,y_1)
print('alpha=100時',lr.coef_)

基本上和我們的python代碼實作的系數差不多,
2、近似梯度下降法python代碼實作Lasso
只是在我們梯度下降法代碼基礎上,改了梯度的計算,加了sign(w),也就是加上L1正則項的導數);
class lasso():
def __init__(self):
pass
#梯度下降法迭代訓練模型引數,x為特征資料,y為標簽資料,a為學習率,epochs為迭代次數
def fit(self,x,y,a,epochs,Lambda):
#計算總資料量
m=x.shape[0]
#給x添加偏置項
X = np.concatenate((np.ones((m,1)),x),axis=1)
#計算總特征數
n = X.shape[1]
#初始化W的值,要變成矩陣形式
W=np.mat(np.ones((n,1)))
#X轉為矩陣形式
xMat = np.mat(X)
#y轉為矩陣形式,這步非常重要,且要是m x 1的維度格式
yMat =np.mat(y.reshape(-1,1))
#回圈epochs次
for i in range(epochs):
gradient = xMat.T*(xMat*W-yMat)/m + Lambda * np.sign(W)
W=W-a * gradient
return W
def predict(self,x,w): #這里的x也要加偏置,訓練時x是什么維度的資料,預測也應該保持一樣
return np.dot(x,w)
下面是運行的結果:

sklearn展示Lasso:
1、隨著alpha值的增大,也就是正則項系數增大,系數變得越來越稀疏,更多的系數變為0,
#波士頓房價回歸資料集
data = sklearn.datasets.load_boston()
x =data['data']
y= data['target']
from sklearn.linear_model import Lasso
lr=Lasso(alpha= 1)
lr.fit(x,y)
print('當alpha=1時:\n',lr.coef_)
lr=Lasso(alpha= 5)
lr.fit(x,y)
print('當alpha=5時:\n',lr.coef_)
lr=Lasso(alpha= 10)
lr.fit(x,y)
print('當alpha=10時:\n',lr.coef_)

下一篇我們介紹Ridge回歸,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231001.html
標籤:python
