1. 程式人生 > 其它 >機器學習:Python測試線性可分性的方法

機器學習:Python測試線性可分性的方法

線性和非線性分類 兩個子集是線性可分的,如果存在一個超平面將每組的元素的所有元素的一組駐留在另一側的超平面其他設定。我們可以描述它在2D繪圖中通過分離線,並且在3D繪圖通過一個超平面。

根據定義,線性可分性定義為:如果存在

,

,那麼兩組集合

是線性可分的。

簡而言之,如果存在一個超平面完全分離H元素和M元素,那麼上面的表示式表示H和M是線

性可分的。

圖片來源:Sebastian Raschka 2

在上圖中,A顯示了一個線性分類問題,B顯示了一個非線性的分類問題。在A中,我們的決策邊界是一個線性的,它將藍色的點和綠色的點完全分開。在這個場景中,可以實現幾個線性分類器。

在B中,我們的決策邊界是非線性的,我們將使用非線性的核函式和其他非線性的分類演算法和技術。

一般來說,在機器學習中,在執行任何型別的分類器之前,理解我們要處理的資料是很重要的,以確定應該從哪一種演算法開始,以及我們需要調整哪些引數來適應任務。如果我們的問題是線性的或者非線性的,這就會給我們帶來線性可分性和理解的問題。

如上所述,有幾種分類演算法是通過構造一個線性決策邊界(超平面)分類來分離這些資料的,而這樣做的假設是:資料是線性可分的。然而,在實際操作中,事情並非那麼簡單,許多情況下的資料可能不是線性可分的,因此應用了非線性技術。線性和非線性技術的決策是基於資料科學家所知道的最終目標,他們願意接受的錯誤,平衡模型複雜性和泛化,偏見方差權衡等等。

這篇文章的靈感來自於線性可分性問題的研究論文,論文地址如下:

1.《線性可分性問題:一些測試方法》: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.121.6481&rep=rep1&type=pdf

2.《線性可分性測試的一個簡單演算法》:http://mllab.csa.iisc.ernet.in/downloads/Labtalk/talk30_01_08/lin_sep_test.pdf

本文的目標是在Python中應用和測試少數技術,並演示如何實現它們。測試線性可分性的一些技術是:

  • 領域和專業知識
  • 資料視覺化
  • 計算幾何學(凸包)
  • 機器學習:
    • 感知器
    • 支援向量機

領域和專業知識

這應該是顯而易見的,第一步應該是尋求分析師和其他已經熟悉資料的資料科學家的見解。在開始任何資料發現之旅前學會先問問題,以便更好地理解任務的目的(你的目標),並在早期獲得來自領域專家的見解。

獲得資料 對於上面列出的其他三種方法,我們將使用傳統的Iris資料集(鳶尾花資料集)來探索這些概念,並使用Python實現線性可分測試的一些理論。

  • Iris資料集:https://archive.ics.uci.edu/ml/datasets/iris

因為這是一個眾所周知的資料集,我們預先知道哪些類是線性可分的。對於我們的分析,我們將使用這些已知的知識來證實我們的發現。

Iris以鳶尾花的特徵作為資料來源,資料集包含150個數據集,分為3類,每類50個數據,每個資料包含4個屬性,是在資料探勘、資料分類中非常常用的測試集、訓練集。

首先,匯入必要的庫並載入資料。

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt 

from sklearn import datasets 
data = datasets.load_iris() 

#create a DataFrame 
df = pd.DataFrame(data.data, columns=data.feature_names) 
df['Target'] = pd.DataFrame(data.target) 
df.head()

資料視覺化

最簡單和最快速的方法是視覺化資料。如果特性(feature)的數量很大,那麼這種方法可能是不可行的,或者說太過直接了,因此很難在2D中繪圖。在這種情況下,我們可以使用Pair Plot方法,並且Pandas庫為我們使用scatter_matrix提供了一個很好的選項:

from pandas.tools.plotting import scatter_matrix
scatter_matrix(df.iloc[:,0:4], figsize=(15,11))

上面的散佈矩陣是資料集中所有特性的一個成對的散點圖(我們有4個特徵,所以我們得到一個4×4矩陣)。散佈矩陣提供了關於這些變數是如何相互關聯的見解。讓我們通過建立一個散佈矩陣中花瓣的長度(Petal Length)和花瓣寬度(Petal Width)的散點圖來展開這一擴充套件。

plt.clf()
plt.figure(figsize=(10,6))
plt.scatter(df.iloc[:,2], df.iloc[:,3])
plt.title('Petal Width vs Petal Length')
plt.xlabel(data.feature_names[2])
plt.ylabel(data.feature_names[3])
plt.show()

它仍然沒有那麼有用。讓我們給每個類著色,並新增一個圖例,這樣我們就能理解圖中每個類的資料分佈,並確定這些類是否可以被線性分離。

讓我們更新我們的程式碼:

plt.clf()
plt.figure(figsize = (10, 6))
names = data.target_names
colors = ['b','r','g']
label = (data.target).astype(np.int)
plt.title('Petal Width vs Petal Length')
plt.xlabel(data.feature_names[2])
plt.ylabel(data.feature_names[3])
for i in range(len(names)):
 bucket = df[df['Target'] == i]
 bucket = bucket.iloc[:,[2,3]].values
 plt.scatter(bucket[:, 0], bucket[:, 1], label=names[i]) 
plt.legend()
plt.show()

看上去好多了。我們只是繪製了整個資料集,所有150個點。每個類有50個數據點。是的,乍一看,我們可以看到藍色的點(Setosa類)可以很容易地通過畫一條線來分隔,並將其與其他類隔離開來。但是其他兩個類呢?

讓我們檢查另一種更確定的方法。

計算幾何學

在這種方法中,我們將使用凸包(Convex Hull)來檢查一個特定的類是否是線性可分的。簡而言之,凸包代表了一組資料點(類)的外邊界,這就是為什麼有時它被稱為凸包。

當測試線性可分性時使用凸包的邏輯是相當直接的,可以這樣說:

如果X和Y的凸包的交點是空的,那麼兩個類X和Y是線性可分的。

一種快速的方法來檢視它是如何工作的,就是將每個類的凸包的資料點視覺化。我們將繪製凸包邊界,以直觀地檢查交點。我們將使用Scipy庫來幫助我們計算凸包。更多資訊請參閱下方Scipy文件地址。

  • 地址:https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.ConvexHull.html

讓我們更新前面的程式碼,以包含凸包。

from scipy.spatial import ConvexHull
 
plt.clf()
plt.figure(figsize = (10, 6))
names = data.target_names
label = (data.target).astype(np.int)
colors = ['b','r','g']
plt.title('Petal Width vs Petal Length')
plt.xlabel(data.feature_names[2])
plt.ylabel(data.feature_names[3])
for i in range(len(names)):
 bucket = df[df['Target'] == i]
 bucket = bucket.iloc[:,[2,3]].values
 hull = ConvexHull(bucket)
 plt.scatter(bucket[:, 0], bucket[:, 1], label=names[i]) 
 for j in hull.simplices:
 plt.plot(bucket[j,0], bucket[j,1], colors[i])
plt.legend()
plt.show()

我們的輸出看起來像這樣:

至少從直觀上看,Setosa是一個線性可分的類。換句話說,我們可以很容易地畫出一條直線,將Setosa類與非Setosa類分開。Versicolor類和Versicolor類都不是線性可分的,因為我們可以看到它們兩個之間確實有一個交集。

機器學習

在本節中,我們將研究兩個分類器,用於測試線性可分性:感知器(最簡單的神經網路)和支援向量機(稱為核方法的一部分)。

單層感知器 感知器(perceptron)是一種用於二進位制分類的演算法,屬於一類線性分類器。在二進位制分類中,我們嘗試將資料分成兩個Bucket:要麼是在Buket A或 Bucket B中,或是更簡單的:要麼是在Bucket A中,要麼不在Bucket A中(假設我們只有兩個類),因此命名為二進位制分類。

只有當輸入向量是線性可分的時,一個單層感知器才會收斂。在這個狀態下,所有輸入向量都將被正確地分類,表示線性可分性。如果它們不是線性可分的,它就不會收斂。換句話說,如果資料集不是線性可分的,它就不能正確地分類。對於我們的測試目的,這正是我們所需要的。

我們將把它應用在整個資料上,而不是將它分割成測試/訓練,因為我們的目的是測試類之間的線性可分性,而不是為將來的預測建立模型。

我們將使用Scikit-Learn並選擇感知器作為我們的線性模型選擇。在此之前,讓我們做一些基本的資料預處理任務:

# Data Preprocessing
x = df.iloc[:, [2,3]].values
# we are picking Setosa to be 1 and all other classes will be 0
y = (data.target == 0).astype(np.int) 
 
#Perform feature scaling
from sklearn.preprocessing import StandardScaler
sc= StandardScaler()
x = sc.fit_transform(x)

現在,讓我們來構建我們的分類器:

from sklearn.linear_model import Perceptron
perceptron = Perceptron(random_state = 0)
perceptron.fit(x, y)
predicted = perceptron.predict(x)

為了更好地理解結果,我們將繪製混淆矩陣和決策邊界。

from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y, predicted)
 
plt.clf() 
plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Wistia)
classNames = ['Negative','Positive']
plt.title('Perceptron Confusion Matrix - Entire Data')
plt.ylabel('True label')
plt.xlabel('Predicted label')
tick_marks = np.arange(len(classNames))
plt.xticks(tick_marks, classNames, rotation=45)
plt.yticks(tick_marks, classNames)
s = [['TN','FP'], ['FN', 'TP']]
 
for i in range(2):
 for j in range(2):
 plt.text(j,i, str(s[i][j])+" = "+str(cm[i][j]))
plt.show()

現在,讓我們繪製出決策邊界:

from matplotlib.colors import ListedColormap
plt.clf()
X_set, y_set = x, y
X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 
0].max() + 1, step = 0.01),
 np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step =
 0.01))
plt.contourf(X1, X2, perceptron.predict(np.array([X1.ravel(), X2.ravel()]).T).
 reshape(X1.shape),
 alpha = 0.75, cmap = ListedColormap(('navajowhite', 'darkkhaki')))
plt.xlim(X1.min(), X1.max())
plt.ylim(X2.min(), X2.max())
for i, j in enumerate(np.unique(y_set)):
 plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1],
 c = ListedColormap(('red', 'green'))(i), label = j)
plt.title('Perceptron Classifier (Decision boundary for Setosa vs the rest)')
plt.xlabel('Petal Length')
plt.ylabel('Petal Width')
plt.legend()
plt.show()

我們可以看到,我們的感知器確實收斂了,並能夠將Setosa從非Setosa中區分出來,因為資料是線性可分的。如果資料不是線性可分的,情況就不一樣了。讓我們在另一個類上試一下。

以下是Versicolor類(試圖將Versicolor與剩下的類分開):

支援向量機 現在,讓我們用一個核函式的支援向量機來檢查另一種方法。為了測試線性可分性,我們將選擇一個核函式的硬間隔(hard-margin)(最大距離,反義詞為soft-margin)支援向量機。現在,如果我們的目的是訓練一個模型,我們的選擇將會完全不同。但是,由於我們正在測試線性可分性,所以我們想要一個能夠失敗的嚴格的測試(或者如果不收斂的話就會產生錯誤的結果)來幫助我們更好地評估手頭的資料。

圖片來源:維基百科

現在,讓我們編碼:

from sklearn.svm import SVC
svm = SVC(C=1.0, kernel='linear', random_state=0)
svm.fit(x, y)
 
predicted = svm.predict(x)
 
cm = confusion_matrix(y, predicted)
 
plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Wistia)
classNames = ['Negative','Positive']
plt.title('SVM Linear Kernel Confusion Matrix - Setosa')
plt.ylabel('True label')
plt.xlabel('Predicted label')
tick_marks = np.arange(len(classNames))
plt.xticks(tick_marks, classNames, rotation=45)
plt.yticks(tick_marks, classNames)
s = [['TN','FP'], ['FN', 'TP']]
 
for i in range(2):
 for j in range(2):
 plt.text(j,i, str(s[i][j])+" = "+str(cm[i][j]))

下面是混淆矩陣和決策邊界的圖:

完美的分離/分類指示一個線性可分性。

現在,讓我們來測試一下,對Versicolor類進行測試,我們得到了下面的繪圖。有趣的是,我們沒有看到一個決策邊界,而混淆矩陣表示分類器的工作完成得並不好。

現在,為了好玩,並且可以演示出支援向量機的強大功能,讓我們應用一個非線性的核心。在這種情況下,我們將應用一個徑向基函式(RBF Kernel)對上面的程式碼稍微改動一下,得到了完全不同的結果:

x = df.iloc[:, [2,3]].values
y = (data.target == 1).astype(np.int) # we are picking Versicolor to be 1 
and all other classes will be 0
 
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
x = sc.fit_transform(x)
 
from sklearn.svm import SVC
svm = SVC(kernel='rbf', random_state=0)
svm.fit(x, y)
 
predicted = svm.predict(x)
 
cm = confusion_matrix(y, predicted)
 
plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Wistia)
classNames = ['Negative','Positive']
plt.title('SVM RBF Confusion Matrix - Versicolor')
plt.ylabel('True label')
plt.xlabel('Predicted label')
tick_marks = np.arange(len(classNames))
plt.xticks(tick_marks, classNames, rotation=45)
plt.yticks(tick_marks, classNames)
s = [['TN','FP'], ['FN', 'TP']]
 
for i in range(2):
 for j in range(2):
 plt.text(j,i, str(s[i][j])+" = "+str(cm[i][j]))
  • Python原始碼:https://github.com/tatwan/Linear-Separability/blob/master/linear_separability.py