基於決策樹的簡單驗證碼識別

原理

核心思想:類似的輸入必會產生類似的輸出。node

原理:首先從訓練樣本矩陣中選擇第一個特徵進行劃分,使每一個子表中該特徵的值所有相同(好比第一個特徵是男女,則能夠劃分出兩個子表,男表和女表),而後再在每一個子表中選擇下一個特徵按照一樣的規則繼續劃分更小的子表(好比第二個特徵是年齡,我能夠劃分紅三個子表(固然根據狀況的不一樣而不一樣),小於18,大於18小於60,大於60,則在男女表中分別又有三個子表,每一個子表下的特徵值都相同),不斷重複直到全部的特徵所有使用完爲止,此時便獲得葉級子表,其中全部樣本的特徵值所有相同。python

解釋:決策樹是一種分類方法,用於對樣本的特徵分類。而分類完成以後,獲得的結果是同一類(或者稱爲表)的全部特徵基本相同,而後根據某一類的全部樣本經過平均(迴歸)或者投票(分類)獲得一個輸出。那麼,當有新的待預測樣本須要預測輸出時,我只需知道樣本屬於哪一個類(表)。算法

工程優化(剪枝):沒必要用盡全部的特徵,葉級子表中容許混雜不一樣的特徵值,以此下降決策樹的層數,在精度犧牲可接受的前提下,提升模型的性能。一般狀況下,能夠優先選擇使信息熵減小量最大的特徵做爲劃分子表的依據。(通俗的講就是有些特徵值並不區分,好比第一個特徵是男女,我並不分紅兩個表,而是放在一個表裏,這種狀況通常是男女這個特徵對輸出的影響不大),如何區分有用特徵和無用特徵或者說影響不大的特徵呢?經過信息熵或基尼指數來區分。也能夠用PCA和ICA等方法對特徵先進行降維操做。api

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大小的,以下:
在這裏插入圖片描述
雖然一樣很簡單,可是加入了字符。
至於預處理和數字驗證碼同樣,正常驗證碼->灰度圖->二值化->切割->標註。不過通過測試發現,不管我如何調參,準確率都比較低。看了全部的字符才發現,圖片的字符雖然沒有傾斜變形但有粗體和細體的區別,而我在標註的時候並無嚴格讓粗體和細體的樣本數同樣。並且字符的位置不在圖片的中間,字符大小也不同,有的偏上,有的偏下,有的偏小,有的又偏大。即便從新標註的準確率仍是難達到我要的標準。微信

對於這種分割線和字符邊緣明顯的驗證碼來講,咱們能夠將字符從切割後的圖片中提取出來,也就是去掉邊緣外的空白,而後都調整到同樣的大小。這樣就去掉了字符位置和大小對算法的干擾,至於粗體和細體,只要保證這兩個的訓練樣本數量相同就能夠了。代碼以下:app

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這個參數,代碼以下:dom

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的內容。
lUE1wd.jpg

相關文章
相關標籤/搜索