手擼機器學習算法 - 非線性問題

系列文章目錄:

算法介紹

前面兩篇分別介紹了分類迴歸問題中各自最簡單的算法,有一點相同的是它們都是線性的,而實際工作中遇到的基本都是非線性問題,而能夠處理非線性問題是機器學習有實用價值的基礎;
首先,非線性問題在分類與迴歸中的表現是不同的,在迴歸問題中,通常指的是無法通過線性模型很好的擬合,而在分類問題中,非線性問題指的是無法通過超平面進行正確的分類;

對於非線性問題的處理方法:

  1. 數據出發:由於非線性是基於當前的特徵空間,因此一般可以通過特徵轉換、升維等方式使得問題在新的特徵空間中轉爲線性(可以推導只要維度足夠多,數據總是線性的);
  2. 模型出發:使用能處理非線性的模型來處理問題,比如決策樹、神經網絡等;

本篇主要從數據或者說特徵的角度來看如何處理分類和迴歸的非線性問題,這一類處理手段與具體的算法無關,因此有更大的普適性,在機器學習中也被廣泛的使用;

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中非常消耗時間成本的一個步驟,也是對於最終結果影響最大的一步;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章