《機器學習系統設計》(1)

來自書籍《Building Machine Learning Systems with Python 》

        本書主要在於如何實際的教用戶來學習ml,其中簡單的介紹了ml的原理,重點還是放在使用python和numpy、scipy、scikit-learn等包的使用上。通過簡單的實例來講解,還算是有趣。正如豆瓣上說的:

機器學習理論的經典教材很多,但講經典的理論如何實現的好書就不那麼多了。用python做機器學習的書,《集體智慧編程》《機器學習實戰》算是佼佼者,但這些書都是講的怎麼自己造輪子。而造出來的輪子在實際工程中,幾乎是沒有實用價值的。實際做機器學習項目時,用的往往都是現成的高效模型,或在這些模型基礎上做一些改進。如用python做機器學習,常會用到scikit-learn、numpy、scipy、matplotlib這些庫,但除了官方文檔,幾乎沒有書系統的闡述這些東東的工程級應用。這本書的出現,填補了這一空白。 這本書是給工程師看的,典型的快餐式書。 看了太多細火慢燉的東西,喫頓快餐,能迅速止餓
  ================================================== 
  優點:這本書告訴了你,python機器學習工業級的應用 
  缺點:憑空用了許多函數,卻沒有告訴你函數參數的意義

所以本書的真實意義就在於止餓,看過了就行了。希望借博客形式將裏面有價值的一次提取,以後就不需要翻看那些冗餘的部分了。

第一章引言

        這一章主要介紹的就是本書的開始,最開始簡單介紹了下numpy和scipy這兩個python包,書中也說了,對於python來說,適合來做算法的建模和預測,等到了需要的時候,再將算法重寫成c 或者cpp,不過隨着python的一些代碼包被重寫成c形式,在速度上還是能夠滿足的。還介紹了ML的流程:

     1、獲取數據,然後洗數據;

     2、探索和理解輸入數據;

     3、分析如何最好的將數據表達在學習算法中;

     4、選擇正確的模型和學習算法;

     5、正確的測量模型的效果。

不過對於學習算法和模型的選擇,並不是那麼容易簡單的,需要考慮的部分有很多,具體的就不說了,用到的時候自然會更加明白。並且介紹了幾個查資料的好去處:

    1、http://metaoptimize.com/qa – 該站點上幾乎每一個問題,都是機器學習專家差不多認同的答案。即使你沒有任何問題,也還是能夠從中學到不少知識的。
    2、 http://stats.stackexchange.com – 該問題查詢站點,也叫做交叉驗證( Cross Validated),類似於元優化(MetaOptimized),上面的問題都主要偏向於統計學的問題。
    3、http://stackoverflow.com –該站點和上面那個差不多,不過更多關注編程方面。
    4、  Freenode上的#machinelearning頻道 – 很小不過卻很活躍,有不少ML社區的專家。(個人:玩不來)
    5、http://www.TwoToReal.com – 這是本書作者們建立的站點。

    6、書中推薦的博客http://blog.kaggle.com,強烈推薦的,上面有着比賽,還有很多冠軍對自己算法的講解,如果其他地方不去的話,這個地方強烈推薦去看看。

     不過書中說到了,因爲Numpy和scipy中一些算法是通過c重寫的,所以很快,而python自帶的一些操作很慢,所以在使用之前,最好先去scipy查查有沒有對應的算法,直接使用。比如scipy是支持矩陣操作、線性代數、優化、聚類、空間操作、快速傅里葉變換等等。而且scipy是基於numpy的,所以下面的語句是正確的:

>>> scipy.dot is numpy.dot
True
不同的算法對應着有不同的工具箱:

本章的例子是假設網站新成立,需要通過每個小時的訪問人數來決定什麼時候更換更大的服務器,而較早更換太花錢,較晚更換又會使得網站癱瘓;所以如何很好的預測服務器的負載上限時間,還是很有意義的;原始數據顯示結果爲:


下面就是能夠說明本章的代碼完整版:

import os
import scipy as sp
import matplotlib.pyplot as plt

data_dir = os.path.join(
    os.path.dirname(os.path.realpath(__file__)), "..", "data")
data = sp.genfromtxt(os.path.join(data_dir, "web_traffic.tsv"), delimiter="\t")#這裏的tsv是個txt格式文檔,其中是個743*2的矩陣,其中第一列爲1:1:743;第二列爲每個小時的網站訪問次數。該函數第一個爲文件的路徑及文件;第二個以‘\t'作爲分隔符。
print(data[:10])

# all examples will have three classes in this file
colors = ['g', 'k', 'b', 'm', 'r']
linestyles = ['-', '-.', '--', ':', '-']

x = data[:, 0]#讀取第一列
y = data[:, 1]#讀取第二列
print("Number of invalid entries:", sp.sum(sp.isnan(y)))#其中isnan函數返回參數代表的數組中是否是非數值的bool矩陣
x = x[~sp.isnan(y)]#前面加~將返回的bool索引數組取反,這樣就能將那些數值的元素給索引出來
y = y[~sp.isnan(y)]

# plot input data

#在python中可以通過matplotlib來模仿matlab中的figure的畫圖功能,下面是定義一個畫數據的函數,具體細節可參閱matplotlib官網說明
def plot_models(x, y, models, fname, mx=None, ymax=None, xmin=None):
    plt.clf()
    plt.scatter(x, y, s=10)
    plt.title("Web traffic over the last month")#圖的標題
    plt.xlabel("Time")
    plt.ylabel("Hits/hour")
    plt.xticks(
        [w * 7 * 24 for w in range(10)], ['week %i' % w for w in range(10)])#x軸的刻度位置:標籤

    if models:
        if mx is None:
            mx = sp.linspace(0, x[-1], 1000)
        for model, style, color in zip(models, linestyles, colors):
            # print "Model:",model
            # print "Coeffs:",model.coeffs
            plt.plot(mx, model(mx), linestyle=style, linewidth=2, c=color)

        plt.legend(["d=%i" % m.order for m in models], loc="upper left")

    plt.autoscale(tight=True)
    plt.ylim(ymin=0)
    if ymax:
        plt.ylim(ymax=ymax)
    if xmin:
        plt.xlim(xmin=xmin)
    plt.grid(True, linestyle='-', color='0.75')
    plt.savefig(fname)

# 第一次看到數據的時候採用不同階數的多項式進行擬合
plot_models(x, y, None, os.path.join("..", "1400_01_01.png"))

# create and plot models
fp1, res, rank, sv, rcond = sp.polyfit(x, y, 1, full=True)#多項式擬合函數,返回的fp1爲按照第三個參數(多項式的階數)指定的多項式的各個係數
print("Model parameters: %s" % fp1)
print("Error of the model:", res)##輸出擬合後模型的殘差
f1 = sp.poly1d(fp1)#爲y = kx+b
f2 = sp.poly1d(sp.polyfit(x, y, 2))#擬合的爲y =ax^2+bx+c 
f3 = sp.poly1d(sp.polyfit(x, y, 3))
f10 = sp.poly1d(sp.polyfit(x, y, 10))
f100 = sp.poly1d(sp.polyfit(x, y, 100))

plot_models(x, y, [f1], os.path.join("..", "1400_01_02.png"))
plot_models(x, y, [f1, f2], os.path.join("..", "1400_01_03.png"))
plot_models(
    x, y, [f1, f2, f3, f10, f100], os.path.join("..", "1400_01_04.png"))

#通過分析數據的走勢,然後在決定使用兩段直線來擬合,在發現week3,5的時候前後的數據呈現一個不同的走勢
inflection = 3.5 * 7 * 24 #x軸的分段點
xa = x[:inflection]
ya = y[:inflection]
xb = x[inflection:]
yb = y[inflection:]

fa = sp.poly1d(sp.polyfit(xa, ya, 1))#直線擬合
fb = sp.poly1d(sp.polyfit(xb, yb, 1))#直線擬合

plot_models(x, y, [fa, fb], os.path.join("..", "1400_01_05.png"))

#定義計算殘差的函數
def error(f, x, y):
    return sp.sum((f(x) - y) ** 2)

print("Errors for the complete data set:")
for f in [f1, f2, f3, f10, f100]:
    print("Error d=%i: %f" % (f.order, error(f, x, y)))

print("Errors for only the time after inflection point")
for f in [f1, f2, f3, f10, f100]:
    print("Error d=%i: %f" % (f.order, error(f, xb, yb)))

print("Error inflection=%f" % (error(fa, xa, ya) + error(fb, xb, yb)))


# extrapolating into the future
plot_models(
    x, y, [f1, f2, f3, f10, f100], os.path.join("..", "1400_01_06.png"),
    mx=sp.linspace(0 * 7 * 24, 6 * 7 * 24, 100),
    ymax=10000, xmin=0 * 7 * 24)

print("Trained only on data after inflection point")
fb1 = fb
fb2 = sp.poly1d(sp.polyfit(xb, yb, 2))
fb3 = sp.poly1d(sp.polyfit(xb, yb, 3))
fb10 = sp.poly1d(sp.polyfit(xb, yb, 10))
fb100 = sp.poly1d(sp.polyfit(xb, yb, 100))

print("Errors for only the time after inflection point")
for f in [fb1, fb2, fb3, fb10, fb100]:
    print("Error d=%i: %f" % (f.order, error(f, xb, yb)))

plot_models(
    x, y, [fb1, fb2, fb3, fb10, fb100], os.path.join("..", "1400_01_07.png"),
    mx=sp.linspace(0 * 7 * 24, 6 * 7 * 24, 100),
    ymax=10000, xmin=0 * 7 * 24)

# 通過將所有數據劃分成訓練集合測試集來選擇多項式擬合的階數
frac = 0.3
split_idx = int(frac * len(xb))
shuffled = sp.random.permutation(list(range(len(xb))))
test = sorted(shuffled[:split_idx])
train = sorted(shuffled[split_idx:])
fbt1 = sp.poly1d(sp.polyfit(xb[train], yb[train], 1))#一階多項式擬合後得到的多項式形式
fbt2 = sp.poly1d(sp.polyfit(xb[train], yb[train], 2))#2階多項式擬合後得到的多項式形式
fbt3 = sp.poly1d(sp.polyfit(xb[train], yb[train], 3))#3階多項式
fbt10 = sp.poly1d(sp.polyfit(xb[train], yb[train], 10))
fbt100 = sp.poly1d(sp.polyfit(xb[train], yb[train], 100))

print("Test errors for only the time after inflection point")
for f in [fbt1, fbt2, fbt3, fbt10, fbt100]:
    print("Error d=%i: %f" % (f.order, error(f, xb[test], yb[test])))

plot_models(
    x, y, [fbt1, fbt2, fbt3, fbt10, fbt100], os.path.join("..",
                                                          "1400_01_08.png"),
    mx=sp.linspace(0 * 7 * 24, 6 * 7 * 24, 100),
    ymax=10000, xmin=0 * 7 * 24)

from scipy.optimize import fsolve#通過fsolve函數來尋找達到最大值時候的解,即得到什麼時候是服務器負載上限的日子
print(fbt2)
print(fbt2 - 100000)
reached_max = fsolve(fbt2 - 100000, 800) / (7 * 24)#參數:函數形式;自變量初始值
print("100,000 hits/hour expected at week %f" % reached_max[0])
在上面的代碼中,第一章的有價值部分是:

    1、sp.ployfit()函數,該函數爲scipy提供的多項式擬合函數:參數形式爲:x,y,階數,是否完整輸出模型參數。

     2、拿到數據之後最好先了解下,正如上面代碼的第二部分,在觀察數據的走勢發現,使用分段形式,在3.5周前後分別使用直線擬合,最後得到的效果最好(這是在第四周之後的事情),即在真實世界中的影響因素很多,雖然採用多項式擬合100階時候過擬合,1階欠擬合,而且分段形式的error還大,但是根據作者後期對網站的數據觀察,分段的效果最好,當然這不是說什麼都是分段,只是在做數據預測的時候,最好多個模型都做評估,而不只要單單評估一個模型,這樣在商業上,後期纔能有很好的方案遇見,畢竟機器學習也只是基於當前的數據的預測。


參考資料:

1、python使用matplotlib繪圖:http://www.cnblogs.com/qianlifeng/archive/2012/02/13/2350086.html

2、科學計算:最優化問題:http://blog.sina.com.cn/s/blog_5f234d4701013ln6.html

3、scipy 數值計算庫:http://www.tuicool.com/articles/rmauqun

  第二章

        首先介紹了一個1930年的花朵數據集,Iris(https://archive.ics.uci.edu/ml/datasets/Iris),其中包含150個樣本,每個樣本4個特徵(花萼長度,花萼寬度;花瓣長度,花瓣寬度)。第一步就是ML需要的可視化,因爲很多時候,我們是需要通過肉眼來觀察數據的內在走勢的,然後如第一章一樣挑選不同的模型。不過這個數據集被sklearn自帶了,不需要去官網上下載。     

import numpy as np
from sklearn.datasets import load_iris  #如果安裝的是anaconda的話會自帶
from matplotlib import pyplot as plt    #或者import matplotlib.pyplot as plt

data = load_iris()                      #直接裝載iris數據集
features = data['data']                 #或者features = data.data(這即可以作爲字典的鍵索取,也被定爲該變量的屬性)
feature_names = data['feature_names']
target = data['target']

#下面是畫出6個子圖的部分
pairs = [(0,1),(0,2),(0,3),(1,2),(1,3),(2,3)]
for i,(p0,p1) in enumerate(pairs):    #enumerate函數在《howto-functional》中有介紹
    plt.subplot(2,3,i+1)
    for t,marker,c in zip(range(3),">ox","rgb"):
        plt.scatter(features[target == t,p0], features[target == t,p1], marker=marker, c=c)
    plt.xlabel(feature_names[p0])
    plt.ylabel(feature_names[p1])
    plt.xticks([])
    plt.yticks([])
plt.savefig('../1400_02_01.png')
         1、enumerate(iter)函數會計算可迭代元素的個數,然後返回一個2-元組,分別爲:計數值;每個元素。 

在可視化之後,肉眼發現,iris數據集中setosa花類別明顯和其他兩個分離,所以直接通過那個特徵將該花分離出來,使得數據集變成了2分類形式:


import numpy as np
from sklearn.datasets import load_iris

data = load_iris()
features = data['data']                   #讀取特徵矩陣
labels = data['target_names'][data['target']]   #即name[target],兩個都是numpy.ndarray類型

plength = features[:,2]                   #肉眼觀察可以直接區分的特徵
is_setosa = (labels == 'setosa')          #生成標籤等於‘setosa’的bool索引矩陣
print('Maximum of setosa: {0}.'.format(plength[is_setosa].max()))              #輸出setosa的最大上限
print('Minimum of others: {0}.'.format(plength[~is_setosa].min()))             #輸出其他兩類的最小下限
只要滿足下面的就是setosa類
if features[:,2] <2 : print 'setosa'
然後選擇剩下的兩類數據:

from matplotlib import pyplot as plt
import numpy as np
from sklearn.datasets import load_iris
from threshold import learn_model, apply_model, accuracy #從閾值模塊中裝載模型學習,使用,測試函數

data = load_iris()                               #裝載數據
features = data['data']                          #讀取特徵部分
labels = data['target_names'][data['target']]    #將【0 1 2】標籤換成名稱


setosa = (labels == 'setosa')                   #生成bool索引矩陣
features = features[~setosa]                    #提取其餘兩類特徵
labels = labels[~setosa]                        #提取其餘兩類標籤
virginica = (labels == 'virginica')             #讀取兩類中爲virginica的標籤

testing = np.tile([True, False], 50)            #創建布爾矩陣,其中爲【true,false,true,false...】一共50對
training = ~testing                             #上面取反,這樣就是間隔的將一個做訓練數據,一個測試

model = learn_model(features[training], virginica[training])  #使用寫好的模型訓練函數
train_error = accuracy(features[training], virginica[training], model)  #模型測試訓練集的正確率
test_error = accuracy(features[testing], virginica[testing], model)

print('''\
Training error was {0:.1%}.
Testing error was {1:.1%} (N = {2}).
'''.format(train_error, test_error, testing.sum()))
上面的threshold模塊內容爲下面部分。該算法是隻計算某一維上特徵能否分類,只考慮單獨的一維特徵:

import numpy as np
def learn_model(features, labels):
    best_acc = -1.0
    for fi in range(features.shape[1]):  #特徵矩陣的第二維,即每個樣本特徵維度
        thresh = features[:,fi].copy()   #讀取某一維所有特徵
        thresh.sort()                    #排序這維特徵
        for t in thresh:                 #t爲這一維特徵的閾值
            pred = (features[:,fi] > t)  #生成大於閾值的bool索引
            acc = (pred == labels).mean()#讀取大於閾值的作爲正確類,生成bool索引,並計算準確度,這裏就是爲了找到最優分類線
            if acc > best_acc:           #如果當前準確度大於記錄的,就更新
                best_acc = acc
                best_fi = fi
                best_t = t
    return best_t, best_fi              #返回能夠最好分類的那一維及其閾值

def apply_model(features, model):
    t, fi = model
    return features[:,fi] > t           #將大於閾值的返回

def accuracy(features, labels, model):
    preds = apply_model(features, model)
    return np.mean(preds == labels)
       最開始的的準確度爲94%,相比來說有些過度優化了,因爲這裏是使用相同的數據來訓練和預測,在分了訓練集和測試集之後結果爲96%和90%(訓練50個,測試50個)。然而,這並沒有充分利用到數據集內的信息。所以又產生了新的訓練方法交叉驗證。而且在生成幾折的時候,記得要注意每一折的平衡,因爲如果測試集中都是來自同一類,那結果就沒有代表性了。交叉驗證的結果會低於訓練集的結果,不過這卻是更有說服力的結果。

from load import load_dataset
import numpy as np
from threshold import learn_model, apply_model, accuracy

features,labels = load_dataset('seeds')    #什麼數據集不重要,這裏是爲了說明原理
labels = labels == 'Canadian'

error = 0.0
for fold in range(10):   #10折交叉驗證
    training = np.ones(len(features), bool)   #建立標籤對應的布爾矩陣
    training[fold::10] = 0      #因爲數據是順序排序,所以通過跳10來提取不同的數據
    testing = ~training         #將訓練集取反作爲測試集
    model = learn_model(features[training], labels[training])   #模型訓練
    test_error = accuracy(features[testing], labels[testing], model)   #模型測試
    error += test_error          #累積誤差

error /= 10.0                    #計算10折交叉驗證上平均誤差,作爲該模型的誤差

print('Ten fold cross-validated error was {0:.1%}.'.format(error))

      然後本書接着以更爲複雜的例子種子,其中涉及到多個特徵,而且第三個特徵是依據前面的兩個特徵計算出來的。作者也認同一個觀點,一個簡單的算法在一個很好的特徵上的分類結果要好於一個複雜的算法在不好的特徵上的應用,而且好的特徵應該是基於某些容易區分的變量上可變的,而基於那些比如噪音變量來說是不變的,這纔是好特徵。所以在你收集數據之前,應該要有一定的判斷,什麼樣的特徵才應該被收集。然後將所有的特徵給機器來計算和得到最好的分類器。通常來說,有個問題就叫做特徵選擇,雖然有很多方法被提出就是爲了解決這個問題的,不過在實際操作中,還是簡單的想法最有效,因爲在實際工程上,還有個實時性需要考慮,所以很多時候是要在準確度和實時性上做權衡的。

     接下來就是最近鄰分類了,

from load import load_dataset
import numpy as np
from knn import learn_model, apply_model, accuracy

features,labels = load_dataset('seeds')   #裝載數據集的特徵和標籤

def cross_validate(features, labels):      #這裏使用的是10折交叉驗證
    error = 0.0
    for fold in range(10):
        training = np.ones(len(features), bool)
        training[fold::10] = 0
        testing = ~training
        model = learn_model(1, features[training], labels[training])  #訓練集進行模型訓練
        test_error = accuracy(features[testing], labels[testing], model)  #測試集測試結果
        error += test_error                                               #累加誤差

    return error/ 10.0

error = cross_validate(features, labels)    #調用上面的交叉驗證函數
print('Ten fold cross-validated error was {0:.1%}.'.format(error))
#下面是對特徵的每一維度進行歸一化,也就是減均值,除標準方差
features -= features.mean(0)
features /= features.std(0)
error = cross_validate(features, labels)    #調用上面的交叉驗證函數
print('Ten fold cross-validated error after z-scoring was {0:.1%}.'.format(error))
        其中爲什麼要做第二步的每一維歸一化,是因爲當某一維度很多大,其他維度很小的時候,所謂的最近鄰,或者說很多其他分類都是這個維度上的特徵在起作用,所以需要將每一維的特徵都進行歸一化,從而使得每一維度的特徵都有着相同的貢獻能力。
上面的knn模塊爲:

import numpy as np
def learn_model(k, features, labels):       #Knn,在本文中實例化的時候k =1,見上面的代碼,
    return k, features.copy(),labels.copy() #該函數只是複製下訓練集

def plurality(xs):                #該函數創建個字典,然後統計K箇中多少個投票爲同一類
    from collections import defaultdict
    counts = defaultdict(int)
    for x in xs:
        counts[x] += 1    #對同一類投票
    maxv = max(counts.values())    #找到最大可能的
    for k,v in counts.items():    #循環找到最大那個可能對應的類別標籤,然後返回
        if v == maxv:
            return k

def apply_model(features, model):
    k, train_feats, labels = model   #解析訓練集的K 、特徵、標籤
    results = []
    for f in features:               #測試集特徵一個一個的測試
        label_dist = []
        for t,ell in zip(train_feats, labels):    #訓練集的特徵和標籤
            label_dist.append( (np.linalg.norm(f-t), ell) )  #用到了numpy的範數函數norm(),默認爲2範數:平方開根號
        label_dist.sort(key=lambda d_ell: d_ell[0])     #將每個訓練樣本與當前測試樣本計算然後排序
        label_dist = label_dist[:k]                      #取前k個完美匹配的
        results.append(plurality([ell for _,ell in label_dist])) #提取前K個匹配的標籤放入plurality()函數
    return np.array(results)        #返回整個測試集的結果標籤

def accuracy(features, labels, model):  #計算準確率的函數
    preds = apply_model(features, model)   
    return np.mean(preds == labels)    #返回預測結果與標籤之間相等的均值,也就是正確率

   上面norm()函數官網地址:http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.norm.html#numpy.linalg.norm    

     所謂的KNN就是不只是針對一個最近的樣本來計算當前樣本的歸屬,而是通過周圍的K個樣本一起來計算當前樣本的歸屬,通常K可以爲5,或者對於很大的數據集來說,K可以更大。


2015/07/24,  第0次修改。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章