系列文章目錄:
演算法介紹
前面兩篇分別介紹了分類與迴歸問題中各自最簡單的演算法,有一點相同的是它們都是線性的,而實際工作中遇到的基本都是非線性問題,而能夠處理非線性問題是機器學習有實用價值的基礎;
首先,非線性問題在分類與迴歸中的表現是不同的,在迴歸問題中,通常指的是無法通過線性模型很好的擬合,而在分類問題中,非線性問題指的是無法通過超平面進行正確的分類;
對於非線性問題的處理方法:
- 從資料出發:由於非線性是基於當前的特徵空間,因此一般可以通過特徵轉換、升維等方式使得問題在新的特徵空間中轉為線性(可以推導只要維度足夠多,資料總是線性的);
- 從模型出發:使用能處理非線性的模型來處理問題,比如決策樹、神經網路等;
本篇主要從資料或者說特徵的角度來看如何處理分類和迴歸的非線性問題,這一類處理手段與具體的演算法無關,因此有更大的普適性,在機器學習中也被廣泛的使用;
PS:注意程式碼中用到的線性迴歸、感知機等模型都是自己實現的哈,不是sklearn的,所以可能引數、用法、結果並不完全一致;
非線性迴歸問題
典型的非線性迴歸問題資料分佈情況如下圖,注意x為輸入特徵,y為輸出目標:
可以看到,該資料集無法用一條直線來較好的擬合,而應該使用一條曲線,那麼問題就變成了如何使得只能擬合直線的線性迴歸能夠擬合出一條合適的曲線;
解決思路
由於線性迴歸只能擬合直線,而當前資料集視覺化後明顯不是直線,因此解決思路只能從資料上入手,即通過修改座標系(或者叫特徵轉換)來改變資料的分佈排列情況,使得其更接近於線性;
針對此處的資料集,通過觀察其分佈情況,考慮將\(x\)轉換為\(x^2\),當然,實際工作中不僅需要進行資料探索,同時也需要大量的嘗試才能找到最合適的特徵轉換方法;
程式碼實現
構建非線性資料集
X = np.array([-1+(1-(-1))*(i/10) for i in range(10)]).reshape(-1,1)
y = (X**2)+rnd.normal(scale=.1,size=X.shape)
直接跑線性迴歸模型
model = LR(X=X,y=y)
w,b = model.train()
print(w,b)
通過特徵轉換來改變資料分佈情況
X2 = X**2
在轉換後的資料集上執行線性迴歸
model = LR(X=X2,y=y)
w,b = model.train()
print(w,b)
在原始座標系下繪製線性迴歸的擬合線
x_min,x_max = min(X[:,0]),max(X[:,0])
line_x = [x_min+(x_max-x_min)*(i/100) for i in range(100)]
line_y = [model.predict(np.array([x**2])) for x in line_x]
完整程式碼
import numpy as np
import matplotlib.pyplot as plt
from 線性迴歸最小二乘法矩陣實現 import LinearRegression as LR
plt.figure(figsize=(18,4))
def pain(pos=141,xlabel='x',ylabel='y',title='',x=[],y=[],line_x=[],line_y=[]):
plt.subplot(pos)
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.scatter(x,y)
plt.plot(line_x,line_y)
rnd = np.random.RandomState(3) # 為了演示,採用固定的隨機
X = np.array([-1+(1-(-1))*(i/10) for i in range(10)]).reshape(-1,1)
y = (X**2)+rnd.normal(scale=.1,size=X.shape)
model = LR(X=X,y=y)
w,b = model.train()
print(w,b)
line_x = [min(X[:,0]),max(X[:,0])]
line_y = [model.predict(np.array([min(X[:,0])])),model.predict(np.array([max(X[:,0])]))]
pain(131,'x','y','degress=1',X[:,0],y[:,0],line_x,line_y)
X2 = X**2
model = LR(X=X2,y=y)
w,b = model.train()
print(w,b)
line_x = [min(X2[:,0]),max(X2[:,0])]
line_y = [model.predict(np.array([x**2])) for x in line_x]
pain(132,'x^2','y','translate coord & degress=2',X2[:,0],y[:,0],line_x,line_y)
x_min,x_max = min(X[:,0]),max(X[:,0])
line_x = [x_min+(x_max-x_min)*(i/100) for i in range(100)]
line_y = [model.predict(np.array([x**2])) for x in line_x]
pain(133,'x','y','degress=2',X[:,0],y[:,0],line_x,line_y)
plt.show()
非線性分類問題
線性不可分的情況在分類問題中比比皆是,簡單的不可分情況如下:
雖然問題型別不同,但是我們的解決思路是一致的,即通過特徵轉換將線性不可分問題轉為線性可分;
針對上述資料分佈,可以觀察到不同型別的點距離中心點的距離差異很明顯,圓點距離中心的距離很近,而叉叉距離中心的距離很遠,如果能夠構建一個表示該距離的特徵,那麼就可以基於該特徵進行分類;
基於上述分析,通過歐氏距離公式計算點到原點的距離有:\(\sqrt{(x1-0)^2+(x2-0)^2}\),由於我們只是期望獲得一個廣義上的距離,並不嚴格要求是歐氏距離,因此將公式中的根號去掉也不影響對資料分佈的改變,最後特徵轉換為將\(x_1\)轉換為\(x_1^2\),同樣的將\(x_2\)轉換為\(x_2^2\),轉換後的橫縱座標值之和就可以用於表示我們期望的距離;
程式碼實現
構建線性不可分資料集
X = np.array([[-1.8,0.6],[0.48,-1.36],[3.68,-3.64],[1.44,0.52],[3.42,3.5],[-4.18,1.68]])
y = np.array([1,1,-1,1,-1,-1])
直接執行感知機模型
model = Perceptron(X=X,y=y,epochs=100)
w,b = model.train()
特徵轉換及轉換後的特徵分佈
Z = X**2 # z1=x1^2,z2=x2^2
在轉換後的資料集上執行感知機
model = Perceptron(X=Z,y=y,epochs=100)
w,b = model.train()
在原始座標系下看感知機擬合的超平面
line_x = [(line_x[0]+(line_x[1]-line_x[0])*(i/100))**.5 for i in range(0,100)]
line_y = [((-b-w[0]*(x**2))/w[1])**.5 for x in line_x]
通過增加任意二階以及小於二階特徵來擬合任意二次曲線
Z2 = np.array([[x[0]**2,x[1]**2,x[0]*x[1],x[0],x[1]] for x in X])
model = Perceptron(X=Z2,y=y,w=np.array([0,0,0,0,0]),epochs=100)
w,b = model.train()
完整程式碼
import numpy as np
import matplotlib.pyplot as plt
from 感知機口袋演算法 import Perceptron
plt.figure(figsize=(18,6))
'''
通過座標轉換/特徵轉換將非線性問題轉為線性問題,再使用線性模型解決;
'''
def trans_z(X):
return X**2
def trans_z2(X):
return np.array([[x[0]**2,x[1]**2,x[0]*x[1],x[0],x[1]] for x in X])
def pain(pos=121,title='',xlabel='',ylabel='',resolution=0.05,model=None,X=[],y=[],line_x=[],line_y=[],transform=None):
plt.subplot(pos)
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
xy_min = min(min([x[0] for x in X]),min([x[1] for x in X]))
xy_max = max(max([x[0] for x in X]),max([x[1] for x in X]))
xx1, xx2 = np.mgrid[xy_min-1:xy_max+1.1:resolution, xy_min-1:xy_max+1.1:resolution]
grid = np.c_[xx1.ravel(), xx2.ravel()]
if transform:
grid = transform(grid)
y_pred = np.array([model.predict(np.array(x)) for x in grid]).reshape(xx1.shape)
plt.contourf(xx1, xx2, y_pred, 25, cmap="coolwarm", vmin=0, vmax=1, alpha=0.8)
plt.scatter([xi[0] for xi,yi in zip(X,y) if yi==1],[xi[1] for xi,yi in zip(X,y) if yi==1],c='black',marker='o')
plt.scatter([xi[0] for xi,yi in zip(X,y) if yi==-1],[xi[1] for xi,yi in zip(X,y) if yi==-1],c='black',marker='x')
# plt.plot(line_x,line_y,color='black')
## 不可分
X = np.array([[-1.8,0.6],[0.48,-1.36],[3.68,-3.64],[1.44,0.52],[3.42,3.5],[-4.18,1.68]])
y = np.array([1,1,-1,1,-1,-1])
model = Perceptron(X=X,y=y,epochs=100)
w,b = model.train()
# 注意繪製分割直線公式為:wx+b=0,因此給定x[0],計算對應的x[1]即可畫圖
# w[0]*x[0]+w[1]*x[1]+b=0 => x[1]=(-b-w[0]*x[0])/w[1]
line_x = [min([x[0] for x in X])-3,max([x[0] for x in X])+3]
line_y = [(-b-w[0]*line_x[0])/w[1],(-b-w[0]*line_x[1])/w[1]]
pain(141,'Before coordinate translate','x1','x2',model=model,X=X,y=y)
## 轉換座標為可分
Z = X**2 # z1=x1^2,z2=x2^2,相當於對原資料空間做座標系轉換,也可以理解為特徵轉換
model = Perceptron(X=Z,y=y,epochs=100)
w,b = model.train()
line_x = [min([x[0] for x in Z])-3,max([x[0] for x in Z])+3]
line_y = [(-b-w[0]*line_x[0])/w[1],(-b-w[0]*line_x[1])/w[1]]
pain(142,'After coordinate translate','z1=x1^2','z2=x2^2',model=model,X=Z,y=y)
## 轉換回原座標繪製分割線,此時為曲線
line_x = [(line_x[0]+(line_x[1]-line_x[0])*(i/100))**.5 for i in range(0,100)]
line_y = [((-b-w[0]*(x**2))/w[1])**.5 for x in line_x]
pain(143,'Back to original coordinate','x1','x2',model=model,X=X,y=y,transform=trans_z)
## 使用任意二次曲線轉換座標:所有可能的二元二次方程
Z2 = np.array([[x[0]**2,x[1]**2,x[0]*x[1],x[0],x[1]] for x in X])
model = Perceptron(X=Z2,y=y,w=np.array([0,0,0,0,0]),epochs=100)
w,b = model.train()
# w0*x0^2+w1*x1^2+w2*x0*x1+w3*x0+w4*x1+b=0 => x1=-b-w3*x0-w0*x0^2
# line_x = [(line_x[0]+(line_x[1]-line_x[0])*(i/100))**.5 for i in range(0,100)]
# line_y = [((-b-w[0]*(x**2))/w[1])**.5 for x in line_x]
pain(144,'Back to original coordinate','x1','x2',model=model,X=X,y=y,transform=trans_z2)
plt.show()
最後
對於特徵轉換,可以應用的方法很多,本篇主要是以最簡單的二次多項式進行轉換,實際上對於更復雜的資料,需要進行更高階的轉換,當然也可以基於業務進行特徵轉換等等,通常這也是ML中非常消耗時間成本的一個步驟,也是對於最終結果影響最大的一步;