本文通過一個虛擬的資料集,對之前的內容做一個回顧,
本文涉及的繪圖物件詳見:
散點圖繪制傳送門
條形圖、直方圖繪制傳送門
箱線圖繪制傳送門
文末多子圖、分類邊界的繪制詳見:
子圖繪制傳送門
等高面圖繪制傳送門
文章目錄
- 構造資料集
- 資料分布圖
- 散點圖
- 帶誤差棒的條形圖
- 箱線圖
- 直方圖
- 聯合分布直方圖
- 特征生成與選擇
- 長寬比
- 面積
- 周長
- 顯著性檢驗
- 聚類演算法訓練程序可視化
- 定義函式-計算距離
- 定義函式-訓練簇心
- 定義函式預測分類
- 開始訓練
- 繪制某一步的簇心及預測邊界
- 繪制訓練程序的簇心及分類邊界
本文的運行環境為 jupyter notebook
python版本為3.7
本文所用到的庫包括
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# 所有字體大小設定為15號
plt.rcParams['xtick.labelsize'] = 15
plt.rcParams['ytick.labelsize'] = 15
plt.rcParams['legend.fontsize'] = 15
plt.rcParams['axes.labelsize'] = 15
plt.rcParams['axes.titlesize'] = 15
構造資料集
如下圖所示,本文構建的資料集包含兩種水果的兩個特征,特征1為水果截面圖的長度,特征2為水果截面圖的寬度,
構建資料集代碼如下:
以8為中心值,0.6為標準偏差的正態分布資料構造香蕉長度特征,
以4為中心值,0.6為標準偏差的正態分布資料構造香蕉寬度特征,
相比香蕉而言,蘋果長寬比較為均衡,
以6為中心值,0.6為標準偏差的正態分布資料構造蘋果長度特征,
以6為中心值,0.6為標準偏差的正態分布資料構造蘋果寬度特征,
共生成3個pandas資料集,分別為香蕉(bananas),蘋果(apples)和包含了香蕉和蘋果的水果(fruits),包含香蕉和蘋果各1000個,每個資料集都包含一個分類列(class),香蕉的分類用整型數字 0 表示,蘋果用數字 1 表示,
np.random.seed(121)
N = 1000
std = 0.6
bananas = pd.DataFrame({
'length': np.random.normal(8, std, N),
'width': np.random.normal(4, std, N),
'class': np.zeros(N, dtype='int')}
)
apples = pd.DataFrame({
'length': np.random.normal(6, std, N),
'width': np.random.normal(6, std, N),
'class': np.ones(N, dtype='int')}
)
fruits = pd.concat([bananas, apples])
資料分布圖
散點圖
將長、寬特征映射到散點圖觀察兩類水果資料分布情況,(代碼注釋部分傳送門未涉及,對于資料框類資料集的賦值,建議注釋部分的傳參方式,以增加可讀性)
plt.scatter(x=apples['length'], y=apples['width'], label='apples')
plt.scatter(x=bananas['length'], y=bananas['width'], label='bananas')
plt.legend()
plt.xlabel('Length')
plt.ylabel('Width')
######### 以上代碼等價以下代碼,對于資料框(pandas物件)可通過列名直接賦值 #########
# plt.scatter(x='length', y='width', data=apples, label='apples')
# plt.scatter(x='length', y='width', data=bananas, label='bananas')
# plt.legend()
帶誤差棒的條形圖
以2倍標準偏差繪制誤差線,分別比較兩類水果的長度和寬度特征,顯而易見的,均值上,香蕉比蘋果長,但是蘋果比香蕉寬,
labels = ['bananas', 'apples', 'bananas', 'apples']
x = [1, 1.5, 2.5, 3.0]
height = [fruits.loc[fruits['class'] == 0, 'length'].mean(),
fruits.loc[fruits['class'] == 1, 'length'].mean(),
fruits.loc[fruits['class'] == 0, 'width'].mean(),
fruits.loc[fruits['class'] == 1, 'width'].mean()]
yerr = [2*fruits.loc[fruits['class'] == 0, 'length'].std(),
2*fruits.loc[fruits['class'] == 1, 'length'].std(),
2*fruits.loc[fruits['class'] == 0, 'width'].std(),
2*fruits.loc[fruits['class'] == 1, 'width'].std()]
color = ['tab:blue', 'tab:blue', 'tab:orange', 'tab:orange']
plt.bar(x=x,
height=height,
color=color,
width=0.4,
error_kw={'lw': 4},
yerr=yerr)
plt.xticks(ticks=x, labels=labels)
plt.title('Fruit length VS width')
plt.grid()
箱線圖
從箱線圖可以看出,兩類水果在長度特征上,分布差異明顯,
fea = 'length'
plt.boxplot(x=fea, data=fruits.loc[fruits['class'] == 0],
patch_artist=True, boxprops={'facecolor': 'tab:blue'},
positions=[0], labels=['bananas'], widths=[0.15])
plt.boxplot(x=fea, data=fruits.loc[fruits['class'] == 1],
patch_artist=True, boxprops={'facecolor': 'tab:orange'},
positions=[0.3], labels=['apples'], widths=[0.15])
plt.ylabel('length')
plt.xlim(-0.2, 0.5)
plt.title('Length Distribution ')
直方圖
另一種有效展示特征分布的圖形為直方圖,從下圖可以看出,兩類水果寬度呈現兩種不同分布,但有部磁區域存在重疊,(大香蕉和小蘋果)
fea = 'width'
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 0],
bins=30, density=True, color='tab:blue', label='bananas',)
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 1],
bins=30, density=True, color='tab:orange', label='apples', alpha=0.8)
plt.legend(frameon=False)
plt.title('Width Distribution ')
plt.xlabel('Width')
聯合分布直方圖
plt.hist2d(ax.hist2d)介面可以更形象地描述兩個特征的聯合分布情況,黃色部分表示該區域的樣本密度大,黑色部分表示該區域的樣本密度小,
fig, (ax, cax) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [0.9, 0.1]})
_, _, _, mappable = ax.hist2d(
x='length', y='width', bins=50, data=fruits, cmap=plt.cm.hot)
ax.set_xlabel('length')
ax.set_ylabel('width')
plt.colorbar(mappable=mappable, cax=cax, ax=ax)
想象這樣一個場景:香蕉和蘋果一個個地以固定時間間隔經過傳送帶,傳送帶上有一個工業攝像頭,該攝像頭經影像分析演算法可以獲得前文所述的水果截面長度和寬度的資料,那么,如果通過一個更好的特征識別出該水果是香蕉,還是蘋果? 完成識別后到下一個節點,可通過萬向輪將不同種類的水果傳輸到不同的收集框,
特征生成與選擇
長寬比
將水果的長度除以水果的寬度,可以獲得水果的長寬比,兩類水果的長寬比分布如下:
fea = 'L/W'
fruits[fea] = fruits['length']/fruits['width']
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 0],
bins=30, density=True, color='tab:blue', label='bananas',)
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 1],
bins=30, density=True, color='tab:orange', label='apples',)
plt.legend()
plt.title('L/W Distribution ')
plt.xlabel('L/W ratio')
從圖中可以看出,長寬比,這個特征很好地將香蕉和水果的分布分離開,重疊面積很小,
畢竟,經驗上我們知道香蕉是瘦長的而蘋果是圓的,該特征較高程度地提煉出兩種水果的主要差異,
面積
將水果的長度乘以水果的寬度,可以獲得水果的面積,兩類水果的面積分布如下:
fea = 'area'
fruits[fea] = fruits['length']*fruits['width']
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 0], histtype='step', lw=4,
bins=30, density=True, color='tab:blue', label='bananas')
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 1], histtype='step', lw=4,
bins=30, density=True, color='tab:orange', label='apples')
plt.title('Area Distribution ')
plt.legend()
plt.xlabel('Area')
周長
將水果的長度加上水果的寬度,再乘以2,可以獲得水果的周長,兩類水果的周長分布如下:
fea = 'circumference'
fruits[fea] = 2*(fruits['length']+fruits['width'])
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 0], histtype='step', lw=4,
bins=30, density=True, color='tab:blue', label='bananas')
plt.hist(x=fea, data=fruits.loc[fruits['class'] == 1], histtype='step', lw=4,
bins=30, density=True, color='tab:orange', label='apples')
plt.title('circumference Distribution ')
plt.legend()
plt.xlabel('Circumference')
從面積、周長分布圖可以看出,兩類水果的重疊面積非常大,說明用這兩個特征分類香蕉和蘋果大概率會錯分,
顯著性檢驗
可以用scipy庫的stats.ttest_ind介面對兩類水果在長寬比、面積、周長三個特征上做獨立t檢驗,t檢驗拒絕了長寬比、面積上兩類水果中心值相同的原假設,
from scipy import stats
fea = 'L/W'
fruits[fea] = fruits['length']/fruits['width']
stats.ttest_ind(fruits.loc[fruits['class'] == 0, fea],
fruits.loc[fruits['class'] == 1, fea])
輸出
Ttest_indResult(statistic=82.438425661083, pvalue=0.0)
fea = 'area'
fruits[fea] = fruits['length']*fruits['width']
stats.ttest_ind(fruits.loc[fruits['class'] == 0, fea],
fruits.loc[fruits['class'] == 1, fea])
輸出
Ttest_indResult(statistic=-18.33979554877528, pvalue=1.4737736564093967e-69)
fea = 'circumference'
fruits[fea] = 2*(fruits['length']+fruits['width'])
stats.ttest_ind(fruits.loc[fruits['class'] == 0, fea],
fruits.loc[fruits['class'] == 1, fea])
輸出
Ttest_indResult(statistic=-1.3170522756675043, pvalue=0.1879721312693096)
t檢驗結果匯總表
| 特征 | 特征 | t-統計量 | p-value | reject |
|---|---|---|---|---|
| 長寬比 | L/W | 82.44 | 0.0 | True |
| 面積 | Area | -18.34 | 1.47e-69 | True |
| 周長 | Circumference | -1.32 | 0.19 (>0.05) | False |
聚類演算法訓練程序可視化
聚類分析又稱群分析,是機器學習的一個重要演算法,以下程序演示了用K-MEANS演算法對兩類水果聚類的程序,
定義函式-計算距離
各樣本到簇心的距離,采用歐式距離,distance_L函式實作了各樣本與簇心距離的計算,
def distance_L(fea_1, fea_2, center_x, center_y, data):
x_x = np.power(data[fea_1]-center_x, 2) # (x-cx)^2
y_y = np.power(data[fea_2]-center_y, 2) # (y-cy)^2
return np.array(np.sqrt(x_x+y_y))
定義函式-訓練簇心
train_centers函式定義了模型訓練程序,通過傳入上一次得到的簇心,計算下一次的簇心,
def train_centers(fea_1, fea_2, centers, data):
# 上一個迭代次獲得的簇心
(center1_x, center1_y), (center2_x, center2_y) = centers
# 分別計算與簇心1,和簇心2的歐式距離
distance_1 = distance_L(fea_1, fea_2, center1_x, center1_y, data)
distance_2 = distance_L(fea_1, fea_2, center2_x, center2_y, data)
# 與 center1 歐氏距離較近的資料點,他們的均值為新簇心
center1_x = data.loc[distance_1 < distance_2, fea_1].mean()
center1_y = data.loc[distance_1 < distance_2, fea_2].mean()
# 與 center2 歐氏距離較近的資料點,他們的均值為新簇心
center2_x = data.loc[distance_1 > distance_2, fea_1].mean()
center2_y = data.loc[distance_1 > distance_2, fea_2].mean()
return (center1_x, center1_y), (center2_x, center2_y)
# 測驗
train_centers('length', 'width', centers=((3, 3), (8, 8)), data=fruits)
# ((3, 3), (8, 8)) 經一次訓練后變為 ((6.286898045335904, 4.2832890208506464), (7.082150034373463, 5.088522425327138))
輸出
((6.286898045335904, 4.2832890208506464),
(7.082150034373463, 5.088522425327138))
測驗代碼展示了簇心經訓練由(3, 3), (8, 8)變為((6.286898045335904, 4.2832890208506464), (7.082150034373463, 5.088522425327138)),
定義函式預測分類
pred_centers介面實作了由簇心預測傳入新樣本應屬于哪一類別,
def pred_centers(x, y, centers):
(center1_x, center1_y), (center2_x, center2_y) = centers
# 需要預測的x,y與簇心的距離
d1 = np.sqrt(np.power(x-center1_x, 2)+np.power(y-center1_y, 2))
d2 = np.sqrt(np.power(x-center2_x, 2)+np.power(y-center2_y, 2))
# 若距離center1簇心的距離小于center2,則為0,否則為1,此處將bool直接轉為int
pred_classes = np.array(d1 < d2, dtype='int')
return pred_classes
# 測驗
x = np.array([[1, 1, 1], [5, 5, 5], [10, 10, 10]])
y = np.array([[1, 5, 10], [1, 5, 10], [1, 5, 10]])
pred_centers(x, y, [[3, 6], [9, 5]])
輸出
array([[1, 1, 1],
[1, 1, 1],
[0, 0, 0]])
開始訓練
訓練開始時,先隨機生成兩個簇心(因為有兩類水果),隨后訓練5次,每次訓練得到的簇心均保存至centers_all串列,輸出部分列印了初始簇心及訓練第一次得到的簇心,
np.random.seed(122)
fea_1, fea_2 = 'length', 'width'
epochs = 5
centers = np.random.randint(low=3, high=10, size=(2, 2)) # 隨機初始化香蕉和蘋果的簇心
centers_all = [centers] # 保存所有迭代次數的簇心
for i in range(epochs):
centers = train_centers(fea_1, fea_2, centers, data=fruits)
centers_all.append(centers)
centers_all[:2]
輸出
[array([[5, 5],
[9, 7]]),
((6.5143691190390625, 5.177757867221544),
(8.199241667306879, 4.528661649079994))]
繪制某一步的簇心及預測邊界
將訓練1次的簇心及其分類效果可視化如下,簇心已從初始的隨機值移動至較接近兩類水果資料集的中心位置,
epoch = 1
# 繪制水果的原始資料圖
plt.scatter(x=apples['length'], y=apples['width'], label='apples', s=20)
plt.scatter(x=bananas['length'], y=bananas['width'], label='bananas', s=20)
# 繪制簇心
(center1_x, center1_y), (center2_x, center2_y) = centers_all[epoch]
plt.scatter(center1_x, center1_y, marker='^', s=300, c='r')
plt.scatter(center2_x, center2_y, marker='v', s=300, c='r')
plt.xlim(4, 10)
plt.ylim(1, 9)
# 繪制分類邊界
x = np.linspace(4, 10, 100)
y = np.linspace(1, 9, 100)
X, Y = np.meshgrid(x, y)
Z = pred_centers(X, Y, centers_all[epoch])
plt.contourf(X, Y, Z, cmap=plt.cm.hot, alpha=0.3)
plt.xlabel('Length')
plt.ylabel('Width')
# 標題圖例
plt.title('Cluster centers after train epoch: %d' % epoch)
plt.legend()
繪制訓練程序的簇心及分類邊界
以下程序繪制了各迭代次數下,簇心的移動及當前簇心的分類邊界,從圖中可以看出迭代至第3次,模型已能很好地將香蕉和水果分開,兩個簇心(5次訓練后得到的簇心((6.000, 6.001),
(7.973, 3.989)))與我們前文構造的((6,6),(8,4))較為一致,
# 將6次迭代程序分類邊界和簇心可視化
fig, axs = plt.subplots(2, 3, sharex=True, sharey=True, figsize=(10, 6))
fig.suptitle('Cluster centers train process',fontsize=20,va='top')
axs = axs.ravel()
for epoch, ax in enumerate(axs):
ax.scatter(x=apples['length'], y=apples['width'], label='apples', s=10)
ax.scatter(x=bananas['length'], y=bananas['width'], label='bananas', s=10)
(center1_x, center1_y), (center2_x, center2_y) = centers_all[epoch]
ax.scatter(center1_x, center1_y, marker='^', s=200, c='tab:red')
ax.scatter(center2_x, center2_y, marker='v', s=200, c='tab:red')
ax.set_xlim(4, 10)
ax.set_ylim(1, 9)
ax.set_title('CCs after train epoch: %d' % epoch)
x = np.linspace(4, 10, 100)
y = np.linspace(1, 9, 100)
X, Y = np.meshgrid(x, y)
Z = pred_centers(X, Y, centers_all[epoch])
ax.contourf(X, Y, Z, cmap=plt.cm.hot, alpha=0.2)
# 第一列左側添加y軸標題
if ax.is_first_col():
ax.set_ylabel('Width')
# 最后一行底部添加x軸標題
if ax.is_last_row():
ax.set_xlabel('Length')
plt.subplots_adjust(bottom=0.2,top=0.85,left=0.05,right=0.95)
于是,我們得到了一個分類香蕉和蘋果的模型,
香蕉和蘋果,因為形態上差異較大,所以只需要兩個簡單的特征和很少次數的迭代就能將其較好地分開,如果再加入龍蛇果、梨、桃子、黃瓜等形態上分別與他們相近的水果,可能就需要收集更多的特征來訓練模型,這種以提取特征為分類依據的方法被稱為模式識別,或特征工程,當然也可以用計算機視覺相關的技識訓得更優更穩健的模型,這些就不在本文的討論范圍之內了,
希望對你有所啟發和幫助!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/66683.html
標籤:其他
上一篇:開啟Scrapy爬蟲之路
