原理
核心思想:相似的輸入必會產生相似的輸出。
原理:首先從訓練樣本矩陣中選擇第一個特征進行划分,使每個子表中該特征的值全部相同(比如第一個特征是男女,則可以划分出兩個子表,男表和女表),然后再在每個子表中選擇下一個特征按照同樣的規則繼續划分更小的子表(比如第二個特征是年齡,我可以划分成三個子表(當然根據情況的不同而不同),小於18,大於18小於60,大於60,則在男女表中分別又有三個子表,每個子表下的特征值都相同),不斷重復直到所有的特征全部使用完為止,此時便得到葉級子表,其中所有樣本的特征值全部相同。
解釋:決策樹是一種分類方法,用於對樣本的特征分類。而分類完成之后,得到的結果是同一類(或者稱為表)的所有特征基本相同,然后根據某一類的所有樣本通過平均(回歸)或者投票(分類)得到一個輸出。那么,當有新的待預測樣本需要預測輸出時,我只需知道樣本屬於哪個類(表)。
工程優化(剪枝):不必用盡所有的特征,葉級子表中允許混雜不同的特征值,以此降低決策樹的層數,在精度犧牲可接受的前提下,提高模型的性能。通常情況下,可以優先選擇使信息熵減少量最大的特征作為划分子表的依據。(通俗的講就是有些特征值並不區分,比如第一個特征是男女,我並不分成兩個表,而是放在一個表里,這種情況一般是男女這個特征對輸出的影響不大),如何區分有用特征和無用特征或者說影響不大的特征呢?通過信息熵或基尼指數來區分。也可以用PCA和ICA等方法對特征先進行降維操作。
sklearn api
class sklearn.tree.DecisionTreeClassifier()
參數
- criterion:選值{“gini”, “entropy”},即基尼指數和信息熵,默認'gini'
- splitter: 選值{'best', 'random'}, 默認'best',random是為了防止過擬合
- max_depth: 樹的最大深度,如果不給定則會用進所有特征構建樹,或者滿足參數min_samples_split時停止
- min_samples_split:節點拆分的最小樣本數,可以是int和float,float表示 ceil(min_samples_split * n_samples), 即該小數為總樣本的占比
- min_samples_leaf:每個節點的最小樣本數,可以是int和float
- min_weight_fraction_leaf:float,默認值= 0.0,在所有葉節點處(所有輸入樣本)的權重總和中的最小加權分數。如果未提供sample_weight,則樣本的權重相等
- max_features :考慮的最大特征數,int,float或{“ auto”,“ sqrt”,“ log2”}
1. 如果為int,則max_features在每個分割處考慮特征。
2. 如果為float,max_features則為小數,並 在每次拆分時考慮要素。int(max_features * n_features)
3. 如果是"auto",則max_features=sqrt(n_features)。
4. 如果是"sqrt",則max_features=sqrt(n_features)。
5. 如果為"log2",則為max_features=log2(n_features)。
6. 如果是None,則max_features=n_features。 - random_state : 隨機種子,int或RandomState。為了防止過擬合,原理不知道
- max_leaf_nodes:最大的葉子節點數,具體取值依情況調試
- min_impurity_decrease : 限制信息增益的大小,信息增益小於設定數值的分枝不會發生。
- min_impurity_split: 在0.19前使用,現由min_impurity_decrease代替
- class_weight :樣本權重
- ccp_alpha:看不懂
屬性 - classes_ :標簽數組
- feature_importances_:特征重要性(基於基尼指數和信息熵)
- max_features_ :模型使用的最大特征數的推斷值
- n_classes_ :樣本數
- n_features_ : 特征數
- n_outputs_:
- tree_:tree對象
方法 - apply(X[, check_input]):返回X被預測的葉子索引
- cost_complexity_pruning_path(X, y[, …]):沒看懂
- decision_path(X[, check_input]):返回樹中的決策路徑
- fit(X, y[, sample_weight, …]):訓練
- get_depth():獲取模型深度
- get_n_leaves():獲取模型葉子數
- get_params([deep]):獲取模型參數
- predict(X[, check_input]):預測
- predict_log_proba(X):預測X的對數概率
- predict_proba(X[, check_input]):預測X的概率
- score(X, y[, sample_weight]):返回預測y和輸出y的正確率占比
- set_params(params):設置模型參數
驗證碼識別
前面使用的驗證碼特征和類別對應過於明顯,所以我們選擇接口的另一種驗證碼,即70x25大小的,如下:
雖然同樣很簡單,但是加入了字符。
至於預處理和數字驗證碼一樣,正常驗證碼->灰度圖->二值化->切割->標注。不過經過測試發現,無論我如何調參,准確率都比較低。看了所有的字符才發現,圖片的字符雖然沒有傾斜變形但有粗體和細體的區別,而我在標注的時候並沒有嚴格讓粗體和細體的樣本數一樣。而且字符的位置不在圖片的中間,字符大小也不一樣,有的偏上,有的偏下,有的偏小,有的又偏大。即使重新標注的准確率還是難達到我要的標准。
對於這種分割線和字符邊緣明顯的驗證碼來說,我們可以將字符從切割后的圖片中提取出來,也就是去掉邊緣外的空白,然后都調整到一樣的大小。這樣就去掉了字符位置和大小對算法的干擾,至於粗體和細體,只要保證這兩個的訓練樣本數量相同就可以了。代碼如下:
def img_preprocess(file):
img1 = Image.open(file)
pix = np.array(img1)
pix = (pix > 180) * 255
width, height = pix.shape
for i in range(width):
if np.sum(pix[i]==0):
xstart = i
break
for i in range(width-1, 0, -1):
if np.sum(pix[i]==0):
xend = i + 1
break
for i in range(height):
if np.sum(pix[:,i]==0):
ystart = i
break
for i in range(height-1, 0, -1):
if np.sum(pix[:,i]==0):
yend = i + 1
break
new_pix = pix[xstart:xend, ystart:yend]
img = Image.fromarray(new_pix).convert('L')
if new_pix.size != (8, 10):
img = img.resize((8, 10), resample=Image.NEAREST)
img.save(file)
接着我們使用決策樹重新訓練樣本並調整參數,我們先看max_depth這個參數,代碼如下:
from sklearn.tree import DecisionTreeClassifier
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as mp
def func(k):
x = []
y = []
for label in os.listdir('train'):
for file in os.listdir(f'train/{label}'):
im = Image.open(f'train/{label}/{file}')
pix = np.array(im)
pix = (pix > 180) * 1
pix = pix.ravel()
x.append(list(pix))
y.append(label)
train_x = np.array(x)
train_y = np.array(y)
model = DecisionTreeClassifier(max_depth=k)
model.fit(train_x, train_y)
x = []
y = []
for label in os.listdir('test'):
for file in os.listdir(f'test/{label}'):
im = Image.open(f'test/{label}/{file}')
pix = np.array(im)
pix = (pix > 180) * 1
pix = pix.ravel()
x.append(list(pix))
y.append(label)
test_x = np.array(x)
test_y = np.array(y)
score = model.score(test_x, test_y)
return score
if __name__ == "__main__":
os.chdir('G:\\knn\\字符驗證碼\\')
x = list(range(1, 15))
y = [func(i) for i in x]
mp.scatter(x, y)
mp.show()
運行結果:
可以看到當max_depth=8的時候,准確率已經很接近1了,所以我們直接將max_depth取8就行了。既然識別的准確率已經接近1,其他的參數調不調整好像並不重要了,不過因為這是驗證碼的識別,不容易出現過擬合的情況,在其他情況下,如果准確率接近1就更要去調整隨機參數(random_state和splitter)和剪枝參數(min_samples_leaf等)來防止過擬合。我后面也試着調整了一下其他參數,發現模型的准確率變化不大,默認即可。
訓練測試數據集:https://www.lanzous.com/i8joo0f
最后,我正在學習一些機器學習的算法,對於一些我需要記錄的內容我都會分享到博客和微信公眾號(python成長路),歡迎關注。平時的話一般分享一些爬蟲或者Python的內容。