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


系列文章目錄:

算法介紹

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM