Python機器學習實戰與kaggle實戰
阿新 • • 發佈:2019-01-10
https://mlnote.wordpress.com/2015/12/16/python%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E4%B8%8Ekaggle%E5%AE%9E%E6%88%98-machine-learning-for-kaggle-competition-in-python/
Author: Miao Fan (範淼), Ph.D. candidate on Computer Science.
Affiliation: Tsinghua University / New York University
宣告: 下面這些內容,都是學習《~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.1 線性分類器 使用Scikit-learn資料庫中預裝的牽牛花品種資料,進行線性分類實踐。線性分類器中,Logistic Regression比較常用,適合做概率估計,即對分配給每個類別一個估計概率。這個在Kaggle競賽裡經常需要。 【Source Code】 In [1]:
from sklearn.datasets import load_iris
from sklearn.cross_validation import train_test_split
from sklearn import preprocessing
# 讀取資料
iris = load_iris()
# 選取特徵與標籤
X_iris, y_iris = iris.data, iris.target
# 選擇前兩列資料作為特徵
X, y = X_iris[:, :2], y_iris
# 選取一部分,25%的訓練資料作為測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state = 33)
# 對原特徵資料進行標準化預處理,這個其實挺重要,但是經常被一些選手忽略
scaler = preprocessing.StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
from sklearn.linear_model import SGDClassifier
# 選擇使用SGD分類器,適合大規模資料,隨機梯度下降方法估計引數
clf = SGDClassifier()
clf.fit(X_train, y_train)
# 匯入評價包
from sklearn import metrics
y_train_predict = clf.predict(X_train)
# 內測,使用訓練樣本進行準確性能評估
print metrics.accuracy_score(y_train, y_train_predict)
# 標準外測,使用測試樣本進行準確性能評估
y_predict = clf.predict(X_test)
print metrics.accuracy_score(y_test, y_predict)
0.660714285714 0.684210526316In [2]:
# 如果需要更加詳細的效能報告,比如precision, recall, accuracy,可以使用如下的函式。 print metrics.classification_report(y_test, y_predict, target_names = iris.target_names)
precision recall f1-score support setosa 1.00 1.00 1.00 8 versicolor 0.43 0.27 0.33 11 virginica 0.65 0.79 0.71 19 avg / total 0.66 0.68 0.66 38In [4]:
# 如果想詳細探查SGDClassifier的分類效能,我們需要充分利用資料,因此需要把資料切分為N個部分,每個部分都用於測試一次模型效能。 from sklearn.cross_validation import cross_val_score, KFold from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler # 這裡使用Pipeline,便於精簡模型搭建,一般而言,模型在fit之前,對資料需要feature_extraction, preprocessing, 等必要步驟。 # 這裡我們使用預設的引數配置 clf = Pipeline([('scaler', StandardScaler()), ('sgd_classifier', SGDClassifier())]) # 5折交叉驗證整個資料集合 cv = KFold(X.shape[0], 5, shuffle=True, random_state = 33) scores = cross_val_score(clf, X, y, cv=cv) print scores # 計算一下模型綜合性能,平均精度和標準差 print
[ 0.56666667 0.73333333 0.83333333 0.76666667 0.8 ] 0.74 0.0928559218479 0.74 0.0464279609239總結一下:線性分類器有幾種, Logistic_regression在scikit-learn裡也有實現。比起SGD這個分類器而言,前者使用更加精確,但是更加耗時的解析解。SGD分類器可以大體代表這些線性分類器的效能,但是由於是近似估計的引數,因此模型效能結果不是很穩定,需要通過調節超引數獲得模型的效能上限。 ~~~~~~~~~~~~~~~~~~~~~~~~~ 1.2 SVM 分類器 這一部分,我們探究支援向量機,這是個強分類器,效能要比普通線性分類器強大一些,一般而言,基於的也是線性假設。但是由於可以引入一些核技巧(kernel trick),可以將特徵對映到更加高維度,甚至非線性的空間上,從而使資料空間變得更加可分。再加上SVM本身只是會選取少量的支援向量作為確定分類器超平面的證據,因此,即便資料變得高維度,非線性對映,也不會佔用太多的記憶體空間,只是計算這些支援向量的CPU代價比較高。另外,這個分類器適合於直接做分類,不適合做分類概率的估計。 【Source Code】
這裡我們使用 AT&T 400張人臉,這個經典資料集來介紹: In [1]:
from sklearn.datasets import fetch_olivetti_faces # 這部分資料沒有直接儲存在現有包中,都是通過這類函式線上下載 faces = fetch_olivetti_faces()In [2]:
# 這裡證明,資料是以Dict的形式儲存的,與多數實驗性資料的格式一致
faces.keys()
Out[2]:
['images', 'data', 'target', 'DESCR']In [3]:
# 使用shape屬性檢驗資料規模
print faces.data.shape
print faces.target.shape
(400L, 4096L) (400L,)In [4]:
from sklearn.cross_validation import train_test_split
from sklearn.svm import SVC
# 同樣是分割資料 25%用於測試
X_train, X_test, y_train, y_test = train_test_split(faces.data, faces.target, test_size=0.25, random_state=0)
In [5]:
from sklearn.cross_validation import cross_val_score, KFold
from scipy.stats import sem
# 構造一個便於交叉驗證模型效能的函式(模組)
def evaluate_cross_validation(clf, X, y, K):
# KFold 函式需要如下引數:資料量, 叉驗次數, 是否洗牌
cv = KFold(len(y), K, shuffle=True, random_state = 0)
# 採用上述的分隔方式進行交叉驗證,測試模型效能,對於分類問題,這些得分預設是accuracy,也可以修改為別的
scores = cross_val_score(clf, X, y, cv=cv)
print scores
print 'Mean score: %.3f (+/-%.3f)' % (scores.mean(), sem(scores))
# 使用線性核的SVC (後面會說到不同的核,結果可能大不相同)
svc_linear = SVC(kernel='linear')
# 五折交叉驗證 K = 5
evaluate_cross_validation(svc_linear, X_train, y_train, 5)
[ 0.93333333 0.86666667 0.91666667 0.93333333 0.91666667] Mean score: 0.913 (+/-0.012)~~~~~~~~~~~~~~~~~~~~~~~~~
1.3 樸素貝葉斯分類器(Naive Bayes) 這一部分我們探討樸素貝葉斯分類器,大量實驗證明,這個分類模型在對文字分類中能表現良好。究其原因,也許是對於郵件過濾這類任務,我們用於區分類別的文字特徵彼此獨立性較強,剛好模型的假設便是特徵獨立。 【Source Code】 In [1]:
from sklearn.datasets import fetch_20newsgroupsIn [2]:
# 與之前的人臉資料集一樣,20類新聞資料同樣需要臨時下載函式的幫忙 news = fetch_20newsgroups(subset='all')In [9]:
# 查驗資料,依然採用dict格式,共有18846條樣本
print len(news.data), len(news.target)
print news.target
18846 18846 [10 3 17 ..., 3 1 7]In [4]:
# 查驗一下新聞類別和種數 print news.target_names print news.target_names.__len__()
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware', 'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles', 'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space', 'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc', 'talk.religion.misc']
20
In [5]:
# 同樣,我們選取25%的資料用來測試模型效能
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(news.data, news.target, test_size=0.25)
In [6]:
print X_train.__len__() print y_train.__len__() print X_test.__len__()
14134 14134 4712In [13]:
# 許多原始資料無法直接被分類器所使用,影象可以直接使用pixel資訊,文字則需要進一步處理成數值化的資訊
from sklearn.feature_extraction.text import CountVectorizer, HashingVectorizer, TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.cross_validation import *
from scipy.stats import sem
# 我們在NB_Classifier的基礎上,對比幾種特徵抽取方法的效能。並且使用Pipline簡化構建訓練流程
clf_1 = Pipeline([('count_vec', CountVectorizer()), ('mnb', MultinomialNB())])
clf_2 = Pipeline([('hash_vec', HashingVectorizer(non_negative=True)), ('mnb', MultinomialNB())])
clf_3 = Pipeline([('tfidf_vec', TfidfVectorizer()), ('mnb', MultinomialNB())])
# 構造一個便於交叉驗證模型效能的函式(模組)
def evaluate_cross_validation(clf, X, y, K):
# KFold 函式需要如下引數,資料量, K,是否洗牌
cv = KFold(len(y), K, shuffle=True, random_state = 0)
# 採用上述的分隔方式進行交叉驗證,測試模型效能,對於分類問題,這些得分預設是accuracy,也可以修改為別的
scores = cross_val_score(clf, X, y, cv=cv)
print scores
print 'Mean score: %.3f (+/-%.3f)' % (scores.mean(), sem(scores))
In [14]:
clfs = [clf_1, clf_2, clf_3] for clf in clfs: evaluate_cross_validation(clf, X_train, y_train, 5)
[ 0.83516095 0.83374602 0.84471171 0.83622214 0.83227176] Mean score: 0.836 (+/-0.002) [ 0.76052352 0.72727273 0.77538026 0.74778918 0.75194621] Mean score: 0.753 (+/-0.008) [ 0.84435798 0.83409975 0.85496993 0.84082066 0.83227176] Mean score: 0.841 (+/-0.004)In [15]:
# 從上述結果中,我們發現常用的兩個特徵提取方法得到的效能相當。 讓我們選取其中之一,進一步靠特徵的精細篩選提升效能。 clf_4 = Pipeline([('tfidf_vec_adv', TfidfVectorizer(stop_words='english')), ('mnb', MultinomialNB())]) evaluate_cross_validation(clf_4, X_train, y_train, 5)
[ 0.87053414 0.86664308 0.887867 0.87371772 0.86553432] Mean score: 0.873 (+/-0.004)In [16]:
# 如果再嘗試修改貝葉斯分類器的平滑引數,也許效能會更上一層樓。 clf_5 = Pipeline([('tfidf_vec_adv', TfidfVectorizer(stop_words='english')), ('mnb', MultinomialNB(alpha=0.01))]) evaluate_cross_validation(clf_5, X_train, y_train, 5)
[ 0.90060134 0.89741776 0.91651928 0.90909091 0.90410474] Mean score: 0.906 (+/-0.003)~~~~~~~~~~~~~~~~~~~~~~~~~ 1.4 決策樹分類器(Decision Tree) / 整合分類器(Ensemble Tree) 之前的分類器大多有一下幾點缺陷: a)線性分類器對於特徵與類別直接的關係是“線性假設”,如果遇到非線性的關係,就很難辨識,比如Titanic資料中,如果假設“年齡”與“生存”是正相關的,那麼年齡越大,越有可能生存;但是事實證明,這個假設是錯誤的,不是正相關,而偏偏是老人與小孩更加容易獲得生存的機會。這種情況,線性假設不完全成立,因此,需要非線性的分類器。
b)即便使用類似SVM的分類器,我們很難得到明確分類“依據”的說明,無法“解釋”分類器是如何工作的,特別無法從人類邏輯的角度理解。高維度、不可解釋性等,這些都是弊端。
決策樹分類器解決了上述兩點問題。我們使用Titanic(泰坦尼克號的救援記錄)這個資料集來實踐一個預測某乘客是否獲救的分類器。 【Source Code】
In [1]:
# 這裡為了處理資料方便,我們引入一個新的工具包pandas
import pandas as pd
import numpy as np
titanic = pd.read_csv('http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt')
In [2]:
#瞧瞧資料,什麼資料特徵的都有,有數值型的、類別型的,字串,甚至還有缺失的資料等等。
titanic.head()
# 使用pandas,資料都轉入pandas獨有的dataframe格式(二維資料表格),直接使用info(),檢視資料的基本特徵
titanic.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1313 entries, 0 to 1312 Data columns (total 11 columns): row.names 1313 non-null int64 pclass 1313 non-null object survived 1313 non-null int64 name 1313 non-null object age 633 non-null float64 embarked 821 non-null object home.dest 754 non-null object room 77 non-null object ticket 69 non-null object boat 347 non-null object sex 1313 non-null object dtypes: float64(1), int64(2), object(8) memory usage: 123.1+ KBIn [4]:
# 這份調查資料是真實的泰坦尼克號乘客個人和登船資訊,有助於我們預測每位遇難乘客是否倖免。 # 一共1313條資料,有些特徵是完整的(比如 pclass, survived, name),有些是有缺失的;有些是數值型別的資訊(age: float64),有些則是字串。 # 機器學習有一個不太被初學者重視,並且耗時,但是十分重要的一環,特徵的選擇,這個需要基於一些背景知識。根據我們對這場事故的瞭解,sex, age, pclass這些都很有可能是決定倖免與否的關鍵因素。 # we keep pclass, age, sex. X = titanic[['pclass', 'age', 'sex']] y = titanic['survived']In [5]:
X.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1313 entries, 0 to 1312 Data columns (total 3 columns): pclass 1313 non-null object age 633 non-null float64 sex 1313 non-null object dtypes: float64(1), object(2) memory usage: 41.0+ KBIn [6]:
# 下面有幾個對資料處理的任務 # 1) age這個資料列,只有633個 # 2) sex 與 pclass兩個資料列的值都是類別型的,需要轉化為數值特徵,用0/1代替 # 首先我們補充age裡的資料,使用平均數或者中位數都是對模型偏離造成最小影響的策略 X['age'].fillna(X['age'].mean(), inplace=True)
C:\Anaconda2\lib\site-packages\pandas\core\generic.py:2748: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
self._update_inplace(new_data)
In [7]:
X.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1313 entries, 0 to 1312 Data columns (total 3 columns): pclass 1313 non-null object age 1313 non-null float64 sex 1313 non-null object dtypes: float64(1), object(2) memory usage: 41.0+ KBIn [8]:
from sklearn.cross_validation import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state = 33) # 我們使用scikit-learn中的feature_extraction from sklearn.feature_extraction import DictVectorizer vec = DictVectorizer(sparse=False) X_train = vec.fit_transform(X_train.to_dict(orient='record')) print vec.feature_names_ # 我們發現,凡是類別型的特徵都單獨剝離出來,獨成一列特徵,數值型的則保持不變
['age', 'pclass=1st', 'pclass=2nd', 'pclass=3rd', 'sex=female', 'sex=male']In [9]:
X_test = vec.transform(X_test.to_dict(orient='record'))
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier(criterion='entropy', max_depth=3, min_samples_leaf=5)
dtc.fit(X_train, y_train)
dtc.score(X_test, y_test)
Out[9]:
0.79331306990881456In [10]:
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(max_depth=3, min_samples_leaf=5)
rfc.fit(X_train, y_train)
rfc.score(X_test, y_test)
Out[10]:
0.77203647416413379In [11]:
from sklearn.ensemble import GradientBoostingClassifier
gbc = GradientBoostingClassifier(max_depth=3, min_samples_leaf=5)
gbc.fit(X_train, y_train)
gbc.score(X_test, y_test)
Out[11]:
0.79027355623100304In [13]:
from sklearn.metrics import classification_report y_predict = gbc.predict(X_test) print classification_report(y_predict, y_test) # 這裡的函式可以便於生成分類器效能報告(precision,recall)這些是在二分類背景下才有的指標。
precision recall f1-score support 0 0.93 0.78 0.84 241 1 0.57 0.83 0.68 88 avg / total 0.83 0.79 0.80 329~~~~~~~~~~~~~~~~~~~~~~~~~ 1.5 迴歸問題(Regressions) 迴歸問題和分類問題都同屬於監督學習範疇,唯一不同的是,迴歸問題的預測目標是在無限的連續實數域,比如預測房價、股票價格等等;分類問題則是對有限範圍的幾個類別(離散數)進行預測。當然兩者的界限不一定涇渭分明,也可以適度轉化。比如,有一個經典的對紅酒質量的預測,大體分為10等級,怎樣看待這個預測目標,都是可以的。預測的結果,可以在(0-10]區間連續(迴歸問題),也可以只預測10個等級的某個值(分類問題)。 這裡我們舉一個預測美國波士頓地區房價的問題,這是個經典的迴歸問題,我們一步步採用各種用於迴歸問題的訓練模型,一步步嘗試提升模型的迴歸效能。【Source Code】
In [1]:
# 首先預讀房價資料
from sklearn.datasets import load_boston
boston = load_boston()
# 查驗資料規模
print boston.data.shape
(506L, 13L)In [2]:
# 多多弄懂資料特徵的含義也是一個好習慣
print boston.feature_names
print boston.DESCR
['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
'B' 'LSTAT']
Boston House Prices dataset
Notes
------
Data Set Characteristics:
:Number of Instances: 506
:Number of Attributes: 13 numeric/categorical predictive
:Median Value (attribute 14) is usually the target
:Attribute Information (in order):
- CRIM per capita crime rate by town
- ZN proportion of residential land zoned for lots over 25,000 sq.ft.
- INDUS proportion of non-retail business acres per town
- CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
- NOX nitric oxides concentration (parts per 10 million)
- RM average number of rooms per dwelling
- AGE proportion of owner-occupied units built prior to 1940
- DIS weighted distances to five Boston employment centres
- RAD index of accessibility to radial highways
- TAX full-value property-tax rate per $10,000
- PTRATIO pupil-teacher ratio by town
- B 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
- LSTAT % lower status of the population
- MEDV Median value of owner-occupied homes in $1000's
:Missing Attribute Values: None
:Creator: Harrison, D. and Rubinfeld, D.L.
This is a copy of UCI ML housing dataset.
http://archive.ics.uci.edu/ml/datasets/Housing
This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.
The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
prices and the demand for clean air', J. Environ. Economics & Management,
vol.5, 81-102, 1978. Used in Belsley, Kuh & Welsch, 'Regression diagnostics
...', Wiley, 1980. N.B. Various transformations are used in the table on
pages 244-261 of the latter.
The Boston house-price data has been used in many machine learning papers that address regression
problems.
**References**
- Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
- Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.
- many more! (see http://archive.ics.uci.edu/ml/datasets/Housing)
In [3]:
# 這裡多一個步驟,查驗資料是否正規化,一般都是沒有的
import numpy as np
print np.max(boston.target)
print np.min(boston.target)
print np.mean(boston.target)
50.0 5.0 22.5328063241In [4]:
from sklearn.cross_validation import train_test_split
# 依然如故,我們對資料進行分割
X_train, X_test, y_train, y_test = train_test_split(boston.data, boston.target, test_size = 0.25, random_state=33)
from sklearn.preprocessing import StandardScaler
# 正規化的目的在於避免原始特徵值差異過大,導致訓練得到的引數權重不一
scalerX = StandardScaler().fit(X_train)
X_train = scalerX.transform(X_train)
X_test = scalerX.transform(X_test)
scalery = StandardScaler().fit(y_train)
y_train = scalery.transform(y_train)
y_test = scalery.transform(y_test)
In [5]:
# 先把評價模組寫好,依然是預設5折交叉驗證,只是這裡的評價指標不再是精度,而是另一個函式R2,大體上,這個得分多少代表有多大百分比的迴歸結果可以被訓練器覆蓋和解釋
from sklearn.cross_validation import *
def train_and_evaluate(clf, X_train, y_train):
cv = KFold(X_train.shape[0], 5, shuffle=True, random_state=33)
scores = cross_val_score(clf, X_train, y_train, cv=cv)
print 'Average coefficient of determination using 5-fold cross validation:', np.mean(scores)
#最後讓我們看看有多少種迴歸模型可以被使用(其實有更多)。
# 比較有代表性的有3種
In [7]:
# 先用線性模型嘗試, SGD_Regressor from sklearn import linear_model # 這裡有一個正則化的選項penalty,目前14維特徵也許不會有太大影響 clf_sgd = linear_model.SGDRegressor(loss='squared_loss', penalty=None, random_state=42) train_and_evaluate(clf_sgd, X_train, y_train)
Average coefficient of determination using 5-fold cross validation: 0.710809853468In [8]:
# 再換一個SGD_Regressor的penalty引數為l2,結果貌似影響不大,因為特徵太少,正則化意義不大 clf_sgd_l2 = linear_model.SGDRegressor(loss='squared_loss', penalty='l2', random_state=42) train_and_evaluate(clf_sgd_l2, X_train, y_train)
Average coefficient of determination using 5-fold cross validation: 0.71081206667In [9]:
# 再看看SVM的regressor怎麼樣(都是預設引數),
from sklearn.svm import SVR
# 使用線性核沒有啥子提升,但是因為特徵少,所以可以考慮升高維度
clf_svr = SVR(kernel='linear')
train_and_evaluate(clf_svr, X_train, y_train)
Average coefficient of determination using 5-fold cross validation: 0.707838419194In [11]:
clf_svr_poly = SVR(kernel='poly') # 升高維度,效果明顯,但是此招慎用@@,特徵高的話, CPU還是受不了,記憶體倒是小事。其實到了現在,連我們自己都沒辦法直接解釋這些特徵的具體含義了。 train_and_evaluate(clf_svr_poly, X_train, y_train)
Average coefficient of determination using 5-fold cross validation: 0.779288545488In [12]:
clf_svr_rbf = SVR(kernel='rbf') # RBF (徑向基核更是牛逼!) train_and_evaluate(clf_svr_rbf, X_train, y_train)
Average coefficient of determination using 5-fold cross validation: 0.833662221567In [14]:
# 再來個更猛的! 極限迴歸森林,放大招了!!! from sklearn import ensemble clf_et = ensemble.ExtraTreesRegressor() train_and_evaluate(clf_et, X_train, y_train)
Average coefficient of determination using 5-fold cross validation: 0.853006383633In [15]:
# 最後看看在測試集上的表現
clf_et.fit(X_train, y_train)
clf_et.score(X_test, y_test)
Out[15]:
0.83781467779895469總結來看,我們可以通過這個例子得到機器學習不斷進取的快感!一點點提高模型效能,並且,也能感覺到超引數的作用有時比更換模型的提升效果更好。而且這裡也為後續的“特徵選擇”,“模型選擇”等實用話題埋下了伏筆。 2. 無監督學習 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~ 無監督學習(Unsupervised Learning)事實上比起監督學習,區別在於:沒有預測/學習目標(Target)。 這類學習問題往往資料資源更加豐富,因為很大程度上,監督學習(Supervised Learning)經常需要人類來承擔標註工作,從而“教會”計算機做預測工作;而且這個工作有時候還需要專業人士參與,比如Iris那種資料庫,不是專家(這時候專家還是有用的),一般人是分辨不了的。 如果說,監督學習有兩大基本型別:分類和迴歸(事實上還有序列化標註等更加複雜的問題);那麼無監督學習有:聚類、降維等問題。 監督學習問題,我們需要通過標註的“反饋”來“訓練”模型引數;無監督學習問題則更加傾向於尋找資料特徵本身之間的“共性”或者叫“模式”。比如:聚類問題,通過尋找資料之間“相似”的特徵表達,來發現資料的“群落”。降維/壓縮問題則是選取資料具有代表性的特徵,在保持資料多樣性(variance)的基礎上,規避掉大量的特徵冗餘和噪聲,不過這個過程也很有可能會損失一些有用的模式資訊。 2.1 主成分分析 (PCA降維)
~~~~~~~~~~~~~~~~~~~~~
首先我們思考一個小例子,這個完全是我的個人理解,也是經常用來向周圍朋友解釋降低維度,資訊冗餘和PCA功能的。比如,我們有一組2 * 2的資料[(1, 2), (2, 4)}],假設這兩個資料都反映到一個類別(分類)或者一個類簇(聚類)。但是如果我們的學習模型是線性模型,那麼這兩個資料其實只能幫助權重引數更新一次,因為他們線性相關,所有的特徵數值都只是擴張了相同的倍數。如果使用PCA分析的話,這個矩陣的“秩”是1,也就是說,在多樣性程度上,這個矩陣只有一個自由度。 其實,我們也可以把PCA當做特徵選擇,只是和普通理解的不同,這種特徵選擇是首先把原來的特徵空間做了對映,使得新的對映後特徵空間資料彼此正交。 下面就讓我們進入正題,看看PCA在哪些具體應用上可以使用。第一個例子便是手寫數字識別(最終還是應用在監督學習上,不過中間的特徵取樣過程用到PCA)。 In [1]:
import numpy as np
# 先熱個身,牛刀小試
M = np.array([[1, 2], [2, 4]])
M
Out[1]:
array([[1, 2], [2, 4]])In [2]:
np.linalg.matrix_rank(M, tol=None)
# 獲取M矩陣的秩=1
Out[2]:
1In [3]:
# 載入手寫數字的影象畫素資料。對於影象處理,除了後續的各種啟發式提取有效特徵以外,
# 最直接常用的就是畫素資料,每個畫素都是一個數值,反映顏色。
from sklearn.datasets import load_digits
digits = load_digits()
# 這些經典資料的儲存格式非常統一。這是好習慣,統一了介面,也便於快速使用。
digits
Out[3]:
{'DESCR': " Optical Recognition of Handwritten Digits Data Set\n\nNotes\n-----\nData Set Characteristics:\n :Number of Instances: 5620\n :Number of Attributes: 64\n :Attribute Information: 8x8 image of integer pixels in the range 0..16.\n :Missing Attribute Values: None\n :Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)\n :Date: July; 1998\n\nThis is a copy of the test set of the UCI ML hand-written digits datasets\nhttp://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits\n\nThe data set contains images of hand-written digits: 10 classes where\neach class refers to a digit.\n\nPreprocessing programs made available by NIST were used to extract\nnormalized bitmaps of handwritten digits from a preprinted form. From a\ntotal of 43 people, 30 contributed to the training set and different 13\nto the test set. 32x32 bitmaps are divided into nonoverlapping blocks of\n4x4 and the number of on pixels are counted in each block. This generates\nan input matrix of 8x8 where each element is an integer in the range\n0..16. This reduces dimensionality and gives invariance to small\ndistortions.\n\nFor info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.\nT. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.\nL. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,\n1994.\n\nReferences\n----------\n - C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their\n Applications to Handwritten Digit Recognition, MSc Thesis, Institute of\n Graduate Studies in Science and Engineering, Bogazici University.\n - E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.\n - Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.\n Linear dimensionalityreduction using relevance weighted LDA. School of\n Electrical and Electronic Engineering Nanyang Technological University.\n 2005.\n - Claudio Gentile. A New Approximate Maximal Margin Classification\n Algorithm. NIPS. 2000.\n",
'data': array([[ 0., 0., 5., ..., 0., 0., 0.],
[ 0., 0., 0., ..., 10., 0., 0.],
[ 0., 0., 0., ..., 16., 9., 0.],
...,
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 2., ..., 12., 0., 0.],
[ 0., 0., 10., ..., 12., 1., 0.]]),
'images': array([[[ 0., 0., 5., ..., 1., 0., 0.],
[ 0., 0., 13., ..., 15., 5., 0.],
[ 0., 3., 15., ..., 11., 8., 0.],
...,
[ 0., 4., 11., ..., 12., 7., 0.],
[ 0., 2., 14., ..., 12., 0., 0.],
[ 0., 0., 6., ..., 0., 0., 0.]],
[[ 0., 0., 0., ..., 5., 0., 0.],
[ 0., 0., 0., ..., 9., 0., 0.],
[ 0., 0., 3., ..., 6., 0., 0.],
...,
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 0., ..., 10., 0., 0.]],
[[ 0., 0., 0., ..., 12., 0., 0.],
[ 0., 0., 3., ..., 14., 0., 0.],
[ 0., 0., 8., ..., 16., 0., 0.],
...,
[ 0., 9., 16., ..., 0., 0., 0.],
[ 0., 3., 13., ..., 11., 5., 0.],
[ 0., 0., 0., ..., 16., 9., 0.]],
...,
[[ 0., 0., 1., ..., 1., 0., 0.],
[ 0., 0., 13., ..., 2., 1., 0.],
[ 0., 0., 16., ..., 16., 5., 0.],
...,
[ 0., 0., 16., ..., 15., 0., 0.],
[ 0., 0., 15., ..., 16., 0., 0.],
[ 0., 0., 2., ..., 6., 0., 0.]],
[[ 0., 0., 2., ..., 0., 0., 0.],
[ 0., 0., 14., ..., 15., 1., 0.],
[ 0., 4., 16., ..., 16., 7., 0.],
...,
[ 0., 0., 0., ..., 16., 2., 0.],
[ 0., 0., 4., ..., 16., 2., 0.],
[ 0., 0., 5., ..., 12., 0., 0.]],
[[ 0., 0., 10., ..., 1., 0., 0.],
[ 0., 2., 16., ..., 1., 0., 0.],
[ 0., 0., 15., ..., 15., 0., 0.],
...,
[ 0., 4., 16., ..., 16., 6., 0.],
[ 0., 8., 16., ..., 16., 8., 0.],
[ 0., 1., 8., ..., 12., 1., 0.]]]),
'target': array([0, 1, 2, ..., 8, 9, 8]),
'target_names': array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])}
In [4]:
# 老套路
X_digits, y_digits = digits.data, digits.target
In [11]:
from sklearn.decomposition import PCA
from matplotlib import pyplot as plt
# 最關鍵的引數就是n_components = 2個主成分
estimator = PCA(n_components=2)
X_pca = estimator.fit_transform(X_digits)
# scikit-learn的介面設計的很統一。
# 聚類問題經常需要直觀的展現資料,降維度的一個直接目的也為此;因此我們這裡多展現幾個圖片直觀一些。
def plot_pca_scatter():
colors = ['black', 'blue', 'purple', 'yellow', 'white', 'red', 'lime', 'cyan', 'orange', 'gray']
for i in xrange(len(colors)):
px = X_pca[:, 0][y_digits == i]
py = X_pca[:, 1][y_digits == i]
plt.scatter(px, py, c=colors[i])
plt.legend(digits.target_names)
plt.xlabel('First Principal Component')
plt.ylabel('Second Principal Component')
plt.show()
plot_pca_scatter()
2.2 聚類演算法 (K-means等)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
聚類就是找“相似”,只是要定義這個相似需要兩個要素:特徵表示,距離計算;這兩個要素都會影響相似度的結論。
2.3 RBM
~~~~~~~~~~~~~~~~~~~~~~~~~~~
因為深度學習太火,Scikit-learn也加入了何其相關的一個RBM模型,聽說後續版本還有深度監督學習模型,DBN之類的,很期待。
3. 特徵、模型的選擇(高階話題)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3.1 特徵選擇 (feature_selection) 這裡我們繼續沿用Titanic資料集,這次側重於對模型的區分能力貢獻最大的幾個特徵選取的問題。 不良的特徵會對模型的精度“拖後腿”;冗餘的特徵雖然不會影響模型的精度,不過CPU計算做了無用功。 我個人理解,這種特徵選擇與PCA這類特徵壓縮選擇主成分的略有區別:PCA重建之後的特徵我們已經無法解釋其意義了。 In [1]:
# 這部分程式碼和原著的第四章節有相同的效果,但是充分利用pandas會表達的更加簡潔,因此我重新編寫了更加清晰簡潔的程式碼。
import pandas as pd
import numpy as np
titanic = pd.read_csv('http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt')
print titanic.info()
# 還是這組資料
titanic.head()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1313 entries, 0 to 1312 Data columns (total 11 columns): row.names 1313 non-null int64 pclass 1313 non-null object survived 1313 non-null int64 name 1313 non-null object age 633 non-null float64 embarked 821 non-null object home.dest 754 non-null object room 77 non-null object ticket 69 non-null object boat 347 non-null object sex 1313 non-null object dtypes: float64(1), int64(2), object(8) memory usage: 123.1+ KB NoneIn [2]:
# 我們丟掉一些過於特異的,不利於找到共同點的資料列, row.names, name, 同時分離出預測列。
y = titanic['survived']
X = titanic.drop(['row.names', 'name', 'survived'], axis = 1)
In [3]:
# 對於連續的數值特徵,我們採用補完的方式 X['age'].fillna(X['age'].mean(), inplace=True) X.fillna('UNKNOWN', inplace=True)In [4]:
# 剩下的類別型別資料,我們直接向量化,這樣的話,對於有空白特徵的列,我們也單獨視作一個特徵 from sklearn.cross_validation import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=33) from sklearn.feature_extraction import DictVectorizer vec = DictVectorizer() X_train = vec.fit_transform(X_train.to_dict(orient='record')) X_test = vec.transform(X_test.to_dict(orient='record'))In [5]:
print len(vec.feature_names_)
474In [6]:
X_train.toarray()Out[6]:
array([[ 31.19418104, 0. , 0. , ..., 0. , 0. , 1. ], [ 31.19418104, 0. , 0. , ..., 0. , 0. , 0. ], [ 31.19418104, 0. , 0. , ..., 0. , 0. , 1. ], ..., [ 12. , 0. , 0. , ..., 0. , 0. , 1. ], [ 18. , 0. , 0. , ..., 0. , 0. , 1. ], [ 31.19418104, 0. , 0. , ..., 0. , 0. , 1. ]])In [7]:
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(criterion='entropy')
dt.fit(X_train, y_train)
dt.score(X_test, y_test)
# 採用所有特徵的測試精度
Out[7]:
0.81762917933130697In [8]:
from sklearn import feature_selection
fs = feature_selection.SelectPercentile(feature_selection.chi2, percentile=20)
X_train_fs = fs.fit_transform(X_train, y_train)
dt.fit(X_train_fs, y_train)
X_test_fs = fs.transform(X_test)
dt.score(X_test_fs, y_test)
# 採用20%高預測性特徵的測試精度
Out[8]:
0.82370820668693012In [9]:
from sklearn.cross_validation import cross_val_score
percentiles = range(1, 100, 2)
results = []
for i in percentiles:
fs = feature_selection.SelectPercentile(feature_selection.chi2, percentile = i)
X_train_fs = fs.fit_transform(X_train, y_train)
scores = cross_val_score(dt, X_train_fs, y_train, cv=5)
results = np.append(results, scores.mean())
print results
opt = np.where(results == results.max())[0]
print 'Optimal number of features %d' %percentiles[opt]
import pylab as pl
pl.plot(percentiles, results)
pl.show()
[ 0.85063904 0.85673057 0.87501546 0.88622964 0.86590394 0.87097506 0.87303649 0.86997526 0.87097506 0.87300557 0.86997526 0.86893424 0.87098536 0.86490414 0.86385281 0.86791383 0.86488353 0.86892393 0.86791383 0.86284271 0.86487322 0.86792414 0.86894455 0.87303649 0.86892393 0.86998557 0.86689342 0.86488353 0.86895485 0.86689342 0.87198516 0.8638322 0.86488353 0.87402597 0.87299526 0.87098536 0.86997526 0.86892393 0.86794475 0.86486291 0.87096475 0.86587302 0.86387343 0.86083282 0.86589363 0.8608019 0.86492476 0.85774067 0.8608122 0.85779221] Optimal number of features 7In [10]:
from sklearn import feature_selection
fs = feature_selection.SelectPercentile(feature_selection.chi2, percentile=7)
X_train_fs = fs.fit_transform(X_train, y_train)
dt.fit(X_train_fs, y_train)
X_test_fs = fs.transform(X_test)
dt.score(X_test_fs, y_test)
# 選取搜尋到的最好特徵比例的測試精度
Out[10]:
0.8571428571428571In [ ]:
# 由此可見,這個技術對於工程上提升精度還是非常有幫助的。~~~~~~~~~~~~~~~~~~~~~~~~~
3.2 模型(超引數)選擇 由於超引數的空間是無盡的,因此超引數的組合配置只能是“更優”解,沒有最優解。通常情況下,我們依靠“網格搜尋”(GridSearch)對固定步長的超引數空間進行暴力搜尋,對於每組超引數組合代入到學習函式中,視為新模型。為了比較新模型之間的效能,每個模型都會在相同的訓練、開發資料集下進行評估,通常我們採用交叉驗證。因此,這個過程非常耗時,但是一旦獲取比較好的引數,則可以保持一段時間使用,也相對一勞永逸。好在,由於各個新模型的交叉驗證之間是互相獨立的,因此,可以充分利用多核甚至是分散式的計算資源來並行搜尋(Parallel Grid Search)。【Source Code】 In [1]:
from sklearn.datasets import fetch_20newsgroups import numpy as np news = fetch_20newsgroups(subset='all')In [2]:
# 我們首先使用grid_search的單核版本
from sklearn.cross_validation import train_test_split
from sklearn.grid_search import GridSearchCV
X_train, X_test, y_train, y_test = train_test_split(news.data[:3000], news.target[:3000], test_size=0.25, random_state=33)
from sklearn.svm import SVC
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
clf = Pipeline([('vect', TfidfVectorizer(stop_words='english', analyzer='word')), ('svc', SVC())])
# 這裡需要試驗的2個超引數的的個數分別是4、3, svc__gamma的引數共有10^-2, 10^-1...
# 這樣我們一共有12種的超引數組合,12個不同引數下的模型
parameters = {'svc__gamma': np.logspace(-2, 1, 4), 'svc__C': np.logspace(-1, 1, 3)}
# 再考慮每個模型需要交叉驗證3次,因此一共需要訓練36次模型,根據下面的結果,單執行緒下,每個模型的訓練任務耗時5秒左右。
gs = GridSearchCV(clf, parameters, verbose=2, refit=True, cv=3)
%time _=gs.fit(X_train, y_train)
gs.best_params_, gs.best_score_
print gs.score(X_test, y_test)
Fitting 3 folds for each of 12 candidates, totalling 36 fits [CV] svc__gamma=0.01, svc__C=0.1 ..................................... [CV] ............................ svc__gamma=0.01, svc__C=0.1 - 5.1s [CV] svc__gamma=0.01, svc__C=0.1 ..................................... [CV] ............................ svc__gamma=0.01, svc__C=0.1 - 5.3s [CV] svc__gamma=0.01, svc__C=0.1 ..................................... [CV] ............................ svc__gamma=0.01, svc__C=0.1 - 5.2s [CV] svc__gamma=0.1, svc__C=0.1 ...................................... [CV] ............................. svc__gamma=0.1, svc__C=0.1 - 5.1s [CV] svc__gamma=0.1, svc__C=0.1 ...................................... [CV] ............................. svc__gamma=0.1, svc__C=0.1 - 5.2s [CV] svc__gamma=0.1, svc__C=0.1 ...................................... [CV] ............................. svc__gamma=0.1, svc__C=0.1 - 5.3s [CV] svc__gamma=1.0, svc__C=0.1 ...................................... [CV] ............................. svc__gamma=1.0, svc__C=0.1 - 5.7s [CV] svc__gamma=1.0, svc__C=0.1 ...................................... [CV] ............................. svc__gamma=1.0, svc__C=0.1 - 5.8s [CV] svc__gamma=1.0, svc__C=0.1 ...................................... [CV] ............................. svc__gamma=1.0, svc__C=0.1 - 5.9s [CV] svc__gamma=10.0, svc__C=0.1 ..................................... [CV] ............................ svc__gamma=10.0, svc__C=0.1 - 5.4s [CV] svc__gamma=10.0, svc__C=0.1 ..................................... [CV] ............................ svc__gamma=10.0, svc__C=0.1 - 5.5s [CV] svc__gamma=10.0, svc__C=0.1 ..................................... [CV] ............................ svc__gamma=10.0, svc__C=0.1 - 5.5s [CV] svc__gamma=0.01, svc__C=1.0 ..................................... [CV] ............................ svc__gamma=0.01, svc__C=1.0 - 5.2s [CV] svc__gamma=0.01, svc__C=1.0 .............................