編者按:機(jī)器學(xué)習(xí)開放課程第四課,Mail.Ru數(shù)據(jù)科學(xué)家Yury Kashnitsky深入講解線性分類和線性回歸的理論與實(shí)踐。
譯文:“如你所見,到下個(gè)月底,你會(huì)有48位丈夫。值得吃一大塊蛋糕慶祝下?!保M軸為日期:昨天、今天;縱軸為丈夫數(shù)量。)
歡迎參加我們的第4課。這次我們將呈現(xiàn)最重要的主題——線性模型。如果你準(zhǔn)備好了數(shù)據(jù),打算開始訓(xùn)練模型,那么你最有可能首先嘗試線性回歸或邏輯回歸(具體取決于你的任務(wù)是回歸還是分類)。
本文包括線性模型的理論和實(shí)踐(在實(shí)際任務(wù)中的使用)。此外,你將參加一項(xiàng)Kaggle競賽,解決一個(gè)基于瀏覽歷史識(shí)別用戶的問題。
回歸
最小二乘法
最大似然估計(jì)
偏置-方差分解
線性回歸正則化
線性分類
線性分類器
作為線性分類器的邏輯回歸
最大似然估計(jì)和邏輯回歸
邏輯損失的L2正則化
邏輯回歸正則化示例
邏輯回歸的優(yōu)缺點(diǎn)
分析IMDB影評(píng)
XOR問題
驗(yàn)證和學(xué)習(xí)曲線
模型需要多復(fù)雜?
需要多少數(shù)據(jù)?
課內(nèi)Kaggle競賽“我知道你是誰”
數(shù)據(jù)下載和轉(zhuǎn)換
稀疏矩陣
訓(xùn)練第一個(gè)模型
作業(yè)四
相關(guān)資源
我們的線性模型學(xué)習(xí)從線性回歸開始。首先,需要指定一個(gè)模型將因變量y和解釋因素(特征)聯(lián)系起來;對(duì)線性模型而言,依賴函數(shù)的形式如下:
如果我們?yōu)槊宽?xiàng)觀測加上一個(gè)虛維度x0 = 1 (偏置),那么上面的線性形式可以改寫為一個(gè)略微緊湊的形式:
如果我們有一個(gè)特征觀測矩陣,其中矩陣的行是數(shù)據(jù)集中的觀測,那么需要在左邊加上一列。
我們可以定義模型為:
其中:
y ∈ ?n 因變量(目標(biāo)變量);
w 模型的參數(shù)向量(在機(jī)器學(xué)習(xí)中,這些參數(shù)經(jīng)常被稱為權(quán)重);
X 觀測及其特征矩陣,大小為n行、m+1列(包括左側(cè)的虛列),秩:rank(X) = m + 1
;
? 對(duì)應(yīng)于隨機(jī)、不可預(yù)測的模型錯(cuò)誤的變量。
上述表達(dá)式也可以寫成這樣(寫出每項(xiàng)觀察):
模型具有如下限制(否則它就不是線性回歸了):
隨機(jī)誤差的期望為零:?i: ??[?i] = 0;
隨機(jī)誤差具有相同的有限方差,這一性質(zhì)稱為等分散性:?i: Var(?i) = σ2 < ∞;
隨機(jī)誤差不相關(guān):?i≠j: Cov(?i, ?j) = 0.
權(quán)重wi的估計(jì)滿足如下條件時(shí),我們稱其為線性:
其中,?k,ωki僅依賴于X中的樣本,而且?guī)缀跻欢ㄒ苑蔷€性的方式。由于尋求最佳權(quán)重的解是一個(gè)線性估計(jì),這一模型被稱為線性回歸。讓我們?cè)僖胍豁?xiàng)定義。當(dāng)期望值等于真實(shí)而未知的估計(jì)參數(shù)的值時(shí),權(quán)重估計(jì)稱為無偏(unbiased):
計(jì)算這些權(quán)重的方法之一是普通最小二乘法(OLS)。OLS最小化因變量的實(shí)際值和模型給出的預(yù)測值之間的均方誤差:
為了解決這一優(yōu)化問題,我們需要計(jì)算模型參數(shù)的導(dǎo)數(shù)。我們將導(dǎo)數(shù)設(shè)為零,然后求解所得關(guān)于w的等式。
矩陣求導(dǎo)小抄:
讓我們開始計(jì)算:
基于上述定義和條件,我們可以說,根據(jù)高斯-馬爾可夫定理,模型參數(shù)的OLS估計(jì)是所有線性無偏估計(jì)中最優(yōu)的,即,它們給出最低的方差。
最大似然估計(jì)
有人可能會(huì)問,為何我們選擇最小化均方誤差而不是別的什么?畢竟,我們可以最小化殘差的平均絕對(duì)值。如果我們改變了最小化的值,唯一會(huì)發(fā)生的事就是我們將超出高斯-馬爾可夫定理的條件,因此我們的估計(jì)將不再是最佳的線性無偏估計(jì)。
在我們繼續(xù)之前,讓我們稍微離題一下,通過一個(gè)簡單的例子講解下最大似然估計(jì)。
許多人大概都記得乙醇的化學(xué)式,所以我決定做一個(gè)試驗(yàn)判定人們是否記得簡單的甲醇化學(xué)式:CH3OH. 我們調(diào)查了400人,發(fā)現(xiàn)只有117個(gè)人記得甲醇的化學(xué)式。那么,下一個(gè)受訪者知道甲醇化學(xué)式的概率為117/400 ≈ 29%會(huì)是一個(gè)合理的假定。讓我們展示下這個(gè)直觀的估計(jì)不僅很好,同時(shí)也是最大似然估計(jì)。估計(jì)來自哪里?回憶下伯努利分布的定義:如果一個(gè)隨機(jī)變量只有兩個(gè)值(1和0,相應(yīng)的概率為θ和1 - θ),那么該隨機(jī)變量滿足伯努利分布,遵循以下概率分布函數(shù):
這一分布正是我們所需要的,分布參數(shù)θ是一個(gè)人知道甲醇化學(xué)式的概率估計(jì)。在我們的400個(gè)獨(dú)立試驗(yàn)中,讓我們將試驗(yàn)的結(jié)果記為x = (x1, x2, ..., x400)。讓我們寫下數(shù)據(jù)(觀測)的似然,即正好觀測到117個(gè)隨機(jī)變量θ = 1的實(shí)例和283個(gè)隨機(jī)變量θ = 0的實(shí)例:
接著,我們將最大化這一θ的表達(dá)式。最常見的情況是,我們并不最大化似然p(x | θ),轉(zhuǎn)而最大化其對(duì)數(shù)(這一單調(diào)變換不影響解答,但大大簡化了計(jì)算):
為了找到最大化上式的θ,我們求θ的導(dǎo)數(shù),設(shè)為零,求解所得等式:
結(jié)果發(fā)現(xiàn),我們的直觀估計(jì)正好是最大似然估計(jì)?,F(xiàn)在讓我們將這一推理過程應(yīng)用到線性回歸問題上,嘗試找出均方誤差背后有什么。為此,我們需要從概率論的角度來看線性回歸。我們的模型和之前是一樣的:
不過,現(xiàn)在讓我們假定隨機(jī)誤差符合均值為零的正態(tài)分布:
據(jù)此改寫模型:
由于樣本是獨(dú)立抽取的(不相關(guān)誤差是高斯-馬爾可夫定理的條件之一),數(shù)據(jù)的似然看起來會(huì)是密度函數(shù)p(yi)的積。讓我們考慮對(duì)數(shù)似然,對(duì)數(shù)似然允許我們用和替換積:
我們想要找到最大似然假設(shè),即,我們需要最大化表達(dá)式p(y ∣ X, w) 以得到wML。這和最大化其對(duì)數(shù)是一回事。注意,當(dāng)我們針對(duì)某個(gè)參數(shù)最大化函數(shù)時(shí),我們可以丟棄所有不依賴這一參數(shù)的成員:
所以,我們看到了,最大化數(shù)據(jù)的似然和最小化均方誤差是一回事(給定以上的假定)。實(shí)際上,這是因?yàn)檎`差是正態(tài)分布的。
偏置-方差分解
讓我們稍微談下線性回歸預(yù)測的誤差性質(zhì)(實(shí)際上,這一討論在所有機(jī)器學(xué)習(xí)算法上都成立)。我們已經(jīng)提到:
目標(biāo)變量的真值是確定性函數(shù)f(x)和隨機(jī)誤差?之和:
誤差符合均值為零、方差一致的正態(tài)分布:
目標(biāo)變量的真值亦為正態(tài)分布:
我們?cè)噲D使用一個(gè)協(xié)變量線性函數(shù)逼近一個(gè)未知的確定性函數(shù)f(x),這一協(xié)變量線性函數(shù),是函數(shù)空間中估計(jì)函數(shù)f的一點(diǎn)(具體而言,我們限定函數(shù)空間的線性函數(shù)家族),即具均值和方差的隨機(jī)變量。
因此,點(diǎn)x的誤差可分解為:
為了簡明,我們將省略函數(shù)的參數(shù)。讓我們分別考慮每個(gè)成員。據(jù)以下公式:
我們很容易就能分解前兩項(xiàng):
注意:
最后我們來處理和的最后一項(xiàng)。回憶一下,誤差和目標(biāo)變量相互獨(dú)立:
最后,讓我們把這些合并到一起:
我們已經(jīng)達(dá)到了我們的終極目標(biāo)——最后的等式告訴我們,任何線性模型的預(yù)測誤差由三部分組成:
平方偏置
方差
不可消除誤差
盡管我們對(duì)σ2無能為力,我們可以影響前兩項(xiàng)。理想情況下,我們希望同時(shí)取消這兩項(xiàng)(見下圖中左上),但是,在實(shí)踐中,常常需要在偏置和不穩(wěn)定(高方差)間尋找平衡。
一般而言,當(dāng)模型的計(jì)算增加了(例如,自由參數(shù)的數(shù)量增加了),估計(jì)的方差(分散程度)也會(huì)增加,但偏置會(huì)下降。由于模型完全記下了訓(xùn)練集而沒能概括訓(xùn)練集,小小的變動(dòng)將導(dǎo)致未預(yù)期的結(jié)果(過擬合)。另一方面,如果模型太弱,它將不能夠?qū)W習(xí)模式,導(dǎo)致學(xué)習(xí)偏離正解較遠(yuǎn)的不同答案。
高斯-馬爾可夫定理斷言,在線性模型參數(shù)估計(jì)問題中,OLS估計(jì)是最佳的線性無偏估計(jì)。這意味著,如果存在任何無偏線性模型g,我們可以確信
線性回歸正則化
在一些情形下,我們可能會(huì)為了穩(wěn)定性(降低模型的方差)特意增加模型的偏置。高斯-馬爾可夫定理的條件之一就是矩陣X是滿秩的。否則,OLS解w = (XTX)-1XTy不存在,因?yàn)槟婢仃?XTX)-1不存在。換句話說,矩陣XTX將是奇異矩陣或退化矩陣。這被稱為病態(tài)問題。這類問題必須加以矯正,也就是說,矩陣XTX需要變?yōu)榉峭嘶仃嚮蚍瞧娈惥仃嚕ㄟ@正是這一過程叫做正則化的原因)。我們常常能在這類數(shù)據(jù)中觀察到所謂的多重共線性:當(dāng)兩個(gè)或更多特征高度相關(guān),也就是矩陣X的列之間“幾乎”存在線性依賴。例如,在基于參數(shù)預(yù)測房價(jià)這一問題中,屬性“含陽臺(tái)面積”和“不含陽臺(tái)面積”會(huì)有一個(gè)“幾乎是”線性的關(guān)系。形式化地說,包含這類數(shù)據(jù)的矩陣XTX是可逆的,但由于多重共線性,一些本征值會(huì)接近零。在XTX的逆矩陣中,會(huì)出現(xiàn)一些極端巨大的本征值,因?yàn)槟婢仃嚨谋菊髦禐?/(λi)。這一本征值的波動(dòng)會(huì)導(dǎo)致模型參數(shù)估計(jì)的不穩(wěn)定,即,在訓(xùn)練數(shù)據(jù)中加入一組新的觀測會(huì)導(dǎo)致完全不同的解。有一種正則化的方法稱為吉洪諾夫正則化,大致上是在均方誤差中加上一個(gè)新成員:
吉洪諾夫矩陣常常表達(dá)為單位矩陣乘上一個(gè)系數(shù):
在這一情形下,最小化均方誤差問題變?yōu)橐粋€(gè)L2正則限定問題。如果我們對(duì)新的損失函數(shù)求導(dǎo),設(shè)所得函數(shù)為零,據(jù)w重整等式,我們便得到了這一問題的解:
這類回歸被稱為嶺回歸。嶺為對(duì)角矩陣,我們?cè)?strong>XTX矩陣上加上這一對(duì)角矩陣,以確保我們能得到一個(gè)常規(guī)矩陣。
這樣的解降低了分散程度,但增加了偏置,因?yàn)閰?shù)的正則向量同時(shí)最小化了,這導(dǎo)致解朝零移動(dòng)。在下圖中,OLS解為白色虛線的交點(diǎn)。藍(lán)點(diǎn)表示嶺回歸的不同解??梢钥吹?,通過增加正則化參數(shù)λ,我們使解朝零移動(dòng)。
線性分類器
線性分類器背后的基本思路是,目標(biāo)分類的值可以被特征空間中的一個(gè)超平面分開。如果這可以無誤差地達(dá)成,那么訓(xùn)練集被稱為線性可分。
我們之前已經(jīng)了解了線性回歸和普通最小二乘法(OLS)。現(xiàn)在考慮一個(gè)二元分類問題,將目標(biāo)分類記為“+1”(正面樣本)和“-1”(負(fù)面樣本)。最簡單的線性分類器可以通過回歸定義:
其中
x是特征向量(包括標(biāo)識(shí));
w是線性模型中的權(quán)重向量(偏置為w0);
sign(?)是符號(hào)函數(shù),返回參數(shù)的符號(hào);
a(x)是分類x的分類器。
作為線性分類器的邏輯回歸
邏輯回歸是線性分類器的一個(gè)特殊情形,邏輯回歸有一個(gè)額外的好處,可以預(yù)測樣本xi為分類“+”的概率p+。
不僅能夠預(yù)測一個(gè)回應(yīng)(“+1”或“-1”),還能預(yù)測相應(yīng)的概率,對(duì)于很多業(yè)務(wù)問題(比如,信用評(píng)分,這一問題傳統(tǒng)上使用邏輯回歸)而言,這是一個(gè)非常重要的需求。
銀行選擇一個(gè)閾值p*以預(yù)測貸款違約的概率(上圖中閾值為0.15),超過閾值就不批準(zhǔn)貸款。此外,還可以將預(yù)測概率乘以未償還金額,以得到客戶造成的期望損失,這可以構(gòu)成有用的業(yè)務(wù)指標(biāo)。
為了預(yù)測概率p+ ∈ [0, 1],我們可以從使用OLS構(gòu)造線性預(yù)測開始:
不過,為了將所得結(jié)果轉(zhuǎn)換為[0, 1]區(qū)間內(nèi)的概率,我們需要某個(gè)函數(shù)
邏輯回歸使用如下函數(shù):
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
import numpy as np
def sigma(z):
return 1. / (1 + np.exp(-z))
xx = np.linspace(-10, 10, 1000)
plt.plot(xx, [sigma(x) for x in xx]);
plt.xlabel('z');
plt.ylabel('sigmoid(z)')
plt.title('Sigmoid function');
我們將事件X的概率記為P(X),則比值比OR(X)由下式判定P(X)/(1-P(X)),這是某一事件是否發(fā)生的概率之比。顯然,概率和比值比包含同樣的信息,不過P(X)的范圍是0到1,而OR(X)的范圍是0到∞。
如果我們計(jì)算OR(X)的對(duì)數(shù),那么顯然我們有l(wèi)og OR(X) ∈ ?. 我們?cè)贠LS中將用到這個(gè)。
讓我們看看邏輯回歸是如何做出預(yù)測的:
目前而言,讓我們假設(shè)我們已經(jīng)通過某種方式得到了權(quán)重w,即,模型已經(jīng)訓(xùn)練好了。之后我們將看下這是如何做的。
步驟一 計(jì)算
等式WTX = 0定義了將樣本分為兩類的超空間。
步驟二 計(jì)算對(duì)數(shù)比值比:
步驟三 現(xiàn)在我們已經(jīng)有了將一個(gè)樣本分配到“+”分類的概率OR+,我們可以據(jù)此計(jì)算p+:
我們看到,上式的右邊我們得到了sigmoid函數(shù)。
所以,邏輯回歸預(yù)測將一個(gè)樣本分配為“+”分類的概率(假定我們已知模型的特征和權(quán)重),這一過程是通過對(duì)權(quán)重向量和特征向量的線性組合進(jìn)行sigmoid變換完成的:
下面我們將看下模型是如何訓(xùn)練的。我們將再次依靠最大似然估計(jì)。
最大似然估計(jì)和邏輯回歸
現(xiàn)在,讓我們看下從MLE出發(fā)如何進(jìn)行邏輯回歸優(yōu)化,也就是最小化邏輯損失函數(shù)。我們前面已經(jīng)見過了將樣本分配為“+”分類的邏輯回歸模型:
“-”分類相應(yīng)的表達(dá)式為:
這兩個(gè)表達(dá)式可以組合成一個(gè):
表達(dá)式M(xi) = yiwTxi稱為目標(biāo)xi的分類邊緣。如果邊緣非負(fù),則模型正確選擇了目標(biāo)xi的分類;如果邊緣為負(fù),則目標(biāo)xi被錯(cuò)誤分類了。注意,邊緣僅針對(duì)訓(xùn)練集中的目標(biāo)(真實(shí)目標(biāo)分類標(biāo)簽yi已知的目標(biāo))而言。
為了精確地理解我們?yōu)楹蔚贸鲞@一結(jié)論,讓我們轉(zhuǎn)向線性分類器的幾何解釋。
首先,我會(huì)建議看下線性代數(shù)的一個(gè)經(jīng)典入門問題:找出向徑xA與平面wTx = 0的距離。
答案:
從答案中,我們可以看到,表達(dá)式wTxi的絕對(duì)值越大,點(diǎn)xi離平面wTx = 0的距離就越遠(yuǎn)。
因此,表達(dá)式M(xi) = yiwTxi是模型對(duì)目標(biāo)xi分類的“信心”:
如果邊緣的絕對(duì)值較大,且為正值,那么分類的標(biāo)簽是正確的,且目標(biāo)離分界超平面很遠(yuǎn),也就是模型對(duì)分類很自信。如下圖點(diǎn)x3所示;
如果邊緣的絕對(duì)值較大,且為負(fù)值,那么分類的標(biāo)簽是錯(cuò)誤的,且目標(biāo)離分界超平面很遠(yuǎn)(目標(biāo)很可能是一個(gè)異常值;例如,它可能是訓(xùn)練集中一個(gè)錯(cuò)誤標(biāo)記的值)。如下圖點(diǎn)x1所示;
如果邊緣的絕對(duì)值較小,那么目標(biāo)距離分界超平面很近,邊緣的符號(hào)決定目標(biāo)是否被正確分類了。如下圖點(diǎn)x2和x4所示。
現(xiàn)在讓我們計(jì)算數(shù)據(jù)集的似然,即基于數(shù)據(jù)集X觀測到給定向量y的概率。我們將做一個(gè)強(qiáng)假設(shè):目標(biāo)來自一個(gè)獨(dú)立分布(i.i.d.)。
其中,?為數(shù)據(jù)集X的長度(行數(shù))。
像我們經(jīng)常干的那樣,對(duì)這個(gè)表達(dá)式取對(duì)數(shù),因?yàn)楹鸵确e容易優(yōu)化得多:
最大化似然等價(jià)于最小化以下表達(dá)式:
這就是邏輯損失函數(shù)。
用邊緣改寫邏輯損失函數(shù),我們有:
我們將這一函數(shù)的圖像和0-1損失函數(shù)的圖像繪制在一張圖上。0-1損失函數(shù)簡單地懲罰模型誤差(邊緣為負(fù))為1:
上圖體現(xiàn)了這樣一個(gè)想法,如果我們不能夠直接最小化分類問題的誤差數(shù)量(至少無法通過梯度方法最小化——0-1損失函數(shù)在零處的導(dǎo)數(shù)趨向無窮),我們可以最小化它的上界。對(duì)邏輯損失函數(shù)而言,以下是成立的:
我們希望能通過降低分類誤差數(shù)的上界,降低分類誤差數(shù)本身。
邏輯損失的L2正則化
邏輯回歸的L2正則化和嶺回歸的情況基本一樣。我們轉(zhuǎn)而最小化下式:
在邏輯回歸中,通常使用正則化系數(shù)的倒數(shù)C = 1/λ:
下面我們將通過一個(gè)例子直觀地理解正則化。
讓我們看下正則化是如何影響分類的質(zhì)量的(數(shù)據(jù)集為吳恩達(dá)機(jī)器學(xué)習(xí)課程中的微芯片測試)。我們將使用基于多項(xiàng)式特征的邏輯回歸,然后改變正則化參數(shù)C. 首先,我們將看看正則化是如何影響分類器的分界的,并直觀地識(shí)別欠擬合和過擬合。接著,我們將通過交叉驗(yàn)證和網(wǎng)格搜索選擇數(shù)值上接近最優(yōu)值的正則化參數(shù)。
# 關(guān)閉警告
# 如果你喜歡開著警告,可以注釋掉下面兩行
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.model_selection import GridSearchCV
讓我們使用pandas
庫的read_csv
方法加載數(shù)據(jù)。這個(gè)數(shù)據(jù)集內(nèi)有118個(gè)微芯片(目標(biāo)),其中有兩項(xiàng)質(zhì)量控制測試的結(jié)果(兩個(gè)數(shù)值變量)和微芯片是否投產(chǎn)的信息。變量已經(jīng)居中了,也就是列中的值已經(jīng)減去其均值。所以,“平均”微芯片的測試值為零。
# 加載數(shù)據(jù)
data = pd.read_csv('../../data/microchip_tests.txt',
header=None, names = ('test1','test2','released'))
# 了解數(shù)據(jù)集的基本信息
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 118 entries, 0 to 117
Data columns (total 3 columns):
test1 118 non-null float64
test2 118 non-null float64
released 118 non-null int64
dtypes: float64(2), int64(1)
memory usage: 2.8 KB
讓我們看下開始五行和最后五行:
data.head(5)
data.tail(5)
分離訓(xùn)練集和目標(biāo)分類標(biāo)簽:
X = data.iloc[:,:2].values
y = data.iloc[:,2].values
繪制數(shù)據(jù),橙點(diǎn)對(duì)應(yīng)有缺陷的芯片,藍(lán)點(diǎn)對(duì)應(yīng)正常芯片。
plt.scatter(X[y == 1, 0], X[y == 1, 1], c='blue', label='Released')
plt.scatter(X[y == 0, 0], X[y == 0, 1], c='orange', label='Faulty')
plt.xlabel('Test 1')
plt.ylabel('Test 2')
plt.title('2 tests of microchips. Logit with C=1')
plt.legend();
定義一個(gè)顯示分類器的分界曲線的函數(shù)。
def plot_boundary(clf, X, y, grid_step=.01, poly_featurizer=None):
x_min, x_max = X[:, 0].min() - .1, X[:, 0].max() + .1
y_min, y_max = X[:, 1].min() - .1, X[:, 1].max() + .1
xx, yy = np.meshgrid(np.arange(x_min, x_max, grid_step),
np.arange(y_min, y_max, grid_step))
Z = clf.predict(poly_featurizer.transform(np.c_[xx.ravel(), yy.ravel()]))
Z = Z.reshape(xx.shape)
plt.contour(xx, yy, Z, cmap=plt.cm.Paired)
我們?yōu)閮蓚€(gè)變量x1和x2定義如下多形式特征:
例如,d = 3時(shí)的特征如下:
特征的數(shù)量呈指數(shù)型增長,為100個(gè)變量創(chuàng)建d較大(例如d = 10)的多項(xiàng)式特征成本很高。更重要的是,不需要如此。
我們將使用sklearn
的邏輯回歸實(shí)現(xiàn)。我們將創(chuàng)建一個(gè)對(duì)象,為矩陣X加上多項(xiàng)式特征(d不超過7)。
poly = PolynomialFeatures(degree=7)
X_poly = poly.fit_transform(X)
X_poly.shape
結(jié)果:
(118, 36)
讓我們訓(xùn)練邏輯回歸,正則化系數(shù)C = 10-2。
C = 1e-2
logit = LogisticRegression(C=C, random_state=17)
logit.fit(X_poly, y)
plot_boundary(logit, X, y, grid_step=.01, poly_featurizer=poly)
plt.scatter(X[y == 1, 0], X[y == 1, 1], c='blue', label='Released')
plt.scatter(X[y == 0, 0], X[y == 0, 1], c='orange', label='Faulty')
plt.xlabel('Test 1')
plt.ylabel('Test 2')
plt.title('2 tests of microchips. Logit with C=%s' % C)
plt.legend();
print('Accuracy on training set:',
round(logit.score(X_poly, y), 3))
Accuracy on training set: 0.627
我們可以嘗試將C增加到1,也就是說,我們削弱了正則化,現(xiàn)在的模型權(quán)重可以比之前有更大的值(絕對(duì)值更大)。這使得分類器在訓(xùn)練集上的精確度改善了(提高到0.831)。
C = 1
logit = LogisticRegression(C=C, random_state=17)
logit.fit(X_poly, y)
plot_boundary(logit, X, y, grid_step=.005, poly_featurizer=poly)
plt.scatter(X[y == 1, 0], X[y == 1, 1], c='blue', label='Released')
plt.scatter(X[y == 0, 0], X[y == 0, 1], c='orange', label='Faulty')
plt.xlabel('Test 1')
plt.ylabel('Test 2')
plt.title('2 tests of microchips. Logit with C=%s' % C)
plt.legend();
print('Accuracy on training set:',
round(logit.score(X_poly, y), 3))
Accuracy on training set: 0.831
我們?yōu)槭裁床唤又M(jìn)一步增加C呢?比如,將C增加到10000?這回,很明顯正則化不夠強(qiáng),我們看到了過擬合。注意,C = 1時(shí),“平滑”邊界對(duì)應(yīng)的訓(xùn)練集上的正確解答并沒有低多少。但我們很容易可以想像得到,我們之前的模型將在新數(shù)據(jù)上工作得更好。
C = 1e4
logit = LogisticRegression(C=C, random_state=17)
logit.fit(X_poly, y)
plot_boundary(logit, X, y, grid_step=.005, poly_featurizer=poly)
plt.scatter(X[y == 1, 0], X[y == 1, 1], c='blue', label='Released')
plt.scatter(X[y == 0, 0], X[y == 0, 1], c='orange', label='Faulty')
plt.xlabel('Test 1')
plt.ylabel('Test 2')
plt.title('2 tests of microchips. Logit with C=%s' % C)
plt.legend();
print('Accuracy on training set:',
round(logit.score(X_poly, y), 3))
Accuracy on training set: 0.873
為了討論以上這些結(jié)果,讓我們改寫一下邏輯回歸優(yōu)化的函數(shù):
部分和:
參數(shù)C越大,模型可恢復(fù)的數(shù)據(jù)中的關(guān)系就越復(fù)雜(直觀地說,C對(duì)應(yīng)模型的“復(fù)雜度”——模型能力)。
如果正則化過強(qiáng),即C值很小,最小化邏輯損失函數(shù)問題的解可能是一個(gè)許多權(quán)重過小或?yàn)榱愕慕?。這樣的模型對(duì)誤差的“懲罰”也不夠(即,在函數(shù)J中,權(quán)重的平方和“權(quán)重過高”,誤差L可能相對(duì)很大)。在這一情形下,模型將會(huì)欠擬合,如我們?cè)诘谝粋€(gè)情形下所看到的那樣。
相反,如果正則化過弱,即C值很大,由絕對(duì)值很大的分量組成的向量w可能變成優(yōu)化問題的解。在這一情形下,L對(duì)優(yōu)化的函數(shù)J貢獻(xiàn)較大。大致上,這樣的模型對(duì)在訓(xùn)練集的目標(biāo)上犯錯(cuò)過于“恐懼”,因而會(huì)過擬合,如我們?cè)诘谌齻€(gè)情形下所看到的那樣。
邏輯回歸不會(huì)“理解”(或“學(xué)習(xí)”)選擇C的值,這和權(quán)重w的情況不一樣。這就是說,C的值無法通過解決邏輯回歸的優(yōu)化問題而確定。我們之前碰到過類似的情況——決策樹無法在訓(xùn)練過程中“學(xué)習(xí)”選擇深度限制。因此,C是模型的超參數(shù),通過交叉驗(yàn)證調(diào)節(jié);決策樹的max_depth
同理。
正則化參數(shù)調(diào)整
讓我們確定上述例子中正則化參數(shù)C的最優(yōu)值。我們可以使用LogisticRegressionCV
——網(wǎng)格搜索參數(shù)后進(jìn)行交叉驗(yàn)證。這個(gè)類是專門為邏輯回歸設(shè)計(jì)的。對(duì)任意模型而言,使用GridSearchCV
或RandomizedSearchCV
,或者特殊的超參數(shù)優(yōu)化算法,比如hyperopt
中實(shí)現(xiàn)的算法。
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)
c_values = np.logspace(-2, 3, 500)
logit_searcher = LogisticRegressionCV(Cs=c_values, cv=skf, verbose=1, n_jobs=-1)
logit_searcher.fit(X_poly, y)
logit_searcher.C_
結(jié)果:
array([198.8827857])
讓我們看下超參數(shù)C是如何影響模型的質(zhì)量的:
plt.plot(c_values, np.mean(logit_searcher.scores_[1], axis=0))
plt.xlabel('C')
plt.ylabel('Mean CV-accuracy');
最后,選擇C值“最佳”(C值較大加重算力負(fù)擔(dān),也容易導(dǎo)致過擬合)的區(qū)域:
plt.plot(c_values, np.mean(logit_searcher.scores_[1], axis=0))
plt.xlabel('C')
plt.ylabel('Mean CV-accuracy');
plt.xlim((0,10));
回憶一下,這些曲線被稱為驗(yàn)證曲線。之前我們手工創(chuàng)建了驗(yàn)證曲線,不過sklearn有構(gòu)建這些曲線的特殊方法,我們以后將直接使用sklearn的相應(yīng)方法。
分析IMDB影評(píng)
現(xiàn)在讓我們做個(gè)小練習(xí)!我們想要解決IMDB影評(píng)的二元分類問題。我們有一個(gè)訓(xùn)練集,其中包含標(biāo)記好的影評(píng),12500條好評(píng),12500條差評(píng)。直接開始機(jī)器學(xué)習(xí)并不容易,因?yàn)槲覀儾]有矩陣X;我們需要準(zhǔn)備它。我們將使用一個(gè)簡單方法:詞袋模型。影評(píng)的特征將由整個(gè)語料庫中的每個(gè)詞的出現(xiàn)情況表示。語料庫是所有用戶影評(píng)。下圖展示了這一思路:
%matplotlib inline
import seaborn as sns
import numpy as np
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
數(shù)據(jù)集可通過以下地址下載:
http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
數(shù)據(jù)集的簡要描述: ai.stanford.edu/~amaas/data/sentiment/
# 請(qǐng)修改文件路徑
reviews_train = load_files('/Users/y.kashnitsky/Yandex.Disk.localized/ML/data/aclImdb/train')
text_train, y_train = reviews_train.data, reviews_train.target
看看訓(xùn)練集和測試集中各有多少條數(shù)據(jù):
print('Number of documents in training data: %d' % len(text_train))
print(np.bincount(y_train))
Number of documents in training data: 25000
[12500 12500]
# 請(qǐng)修改文件路徑
reviews_test = load_files('/Users/y.kashnitsky/Yandex.Disk.localized/ML/data/aclImdb/test')
text_test, y_test = reviews_test.data, reviews_test.target
print('Number of documents in test data: %d' % len(text_test))
print(np.bincount(y_test))
Number of documents in test data: 25000
[12500 12500]
下面是影評(píng)的一些例子。
print(text_train[1])
b'Words can\'t describe how bad this movie is. I can\'t explain it by writing only. You have too see it for yourself to get at grip of how horrible a movie really can be. Not that I recommend you to do that. There are so many clich\xc3\xa9s, mistakes (and all other negative things you can imagine) here that will just make you cry. To start with the technical first, there are a LOT of mistakes regarding the airplane. I won\'t list them here, but just mention the coloring of the plane. They didn\'t even manage to show an airliner in the colors of a fictional airline, but instead used a 747 painted in the original Boeing livery. Very bad. The plot is stupid and has been done many times before, only much, much better. There are so many ridiculous moments here that i lost count of it really early. Also, I was on the bad guys\' side all the time in the movie, because the good guys were so stupid. 'Executive Decision' should without a doubt be you\'re choice over this one, even the 'Turbulence'-movies are better. In fact, every other movie in the world is better than this one.'
上面這條影評(píng)是差評(píng)還是好評(píng)?
y_train[1]
0
沒錯(cuò),和我們預(yù)料的一樣,這是一條差評(píng)。
text_train[2]
b'Everyone plays their part pretty well in this 'little nice movie'. Belushi gets the chance to live part of his life differently, but ends up realizing that what he had was going to be just as good or maybe even better. The movie shows us that we ought to take advantage of the opportunities we have, not the ones we do not or cannot have. If U can get this movie on video for around $10, it\xc2\xb4d be an investment!'
這條呢?
y_train[2]
1
是好評(píng)。
單詞簡單計(jì)數(shù)
首先,我們使用CountVectorizer
創(chuàng)建包含所有單詞的字典。
cv = CountVectorizer()
cv.fit(text_train)
len(cv.vocabulary_)
74849
如果你查看下“單詞”的樣本(我們還是稱作token吧),你會(huì)發(fā)現(xiàn)我們省略了文本處理的許多重要步驟(自動(dòng)化文本處理自身就可以成為一個(gè)獨(dú)立的文章系列)。
print(cv.get_feature_names()[:50])
print(cv.get_feature_names()[50000:50050])
['00', '000', '0000000000001', '00001', '00015', '000s', '001', '003830', '006', '007', '0079', '0080', '0083', '0093638', '00am', '00pm', '00s', '01', '01pm', '02', '020410', '029', '03', '04', '041', '05', '050', '06', '06th', '07', '08', '087', '089', '08th', '09', '0f', '0ne', '0r', '0s', '10', '100', '1000', '1000000', '10000000000000', '1000lb', '1000s', '1001', '100b', '100k', '100m']
['pincher', 'pinchers', 'pinches', 'pinching', 'pinchot', 'pinciotti', 'pine', 'pineal', 'pineapple', 'pineapples', 'pines', 'pinet', 'pinetrees', 'pineyro', 'pinfall', 'pinfold', 'ping', 'pingo', 'pinhead', 'pinheads', 'pinho', 'pining', 'pinjar', 'pink', 'pinkerton', 'pinkett', 'pinkie', 'pinkins', 'pinkish', 'pinko', 'pinks', 'pinku', 'pinkus', 'pinky', 'pinnacle', 'pinnacles', 'pinned', 'pinning', 'pinnings', 'pinnochio', 'pinnocioesque', 'pino', 'pinocchio', 'pinochet', 'pinochets', 'pinoy', 'pinpoint', 'pinpoints', 'pins', 'pinsent']
接著,我們將使用單詞的索引編碼訓(xùn)練集文本的句子。我們將使用稀疏矩陣。
X_train = cv.transform(text_train)
X_train
<25000x74849 sparse matrix of type '<class 'numpy.int64'>'
with 3445861 stored elements in Compressed Sparse Row format>
讓我們看下這一轉(zhuǎn)換是如何進(jìn)行的。
print(text_train[19726])
b'This movie is terrible but it has some good effects.'
X_train[19726].nonzero()[1]
array([ 9881, 21020, 28068, 29999, 34585, 34683, 44147, 61617, 66150, 66562], dtype=int32)
接下來,我們對(duì)測試集應(yīng)用同樣的操作。
X_test = cv.transform(text_test)
下一步是訓(xùn)練邏輯回歸。
%%time
logit = LogisticRegression(n_jobs=-1, random_state=7)
logit.fit(X_train, y_train)
CPU times: user 40.9 s, sys: 524 ms, total: 41.4 s
Wall time: 10.5 s
讓我們看下訓(xùn)練集和測試集上的精確度。
round(logit.score(X_train, y_train), 3), round(logit.score(X_test, y_test), 3)
(0.998, 0.86699999999999999)
模型的系數(shù)可以美觀地顯示。
def visualize_coefficients(classifier, feature_names, n_top_features=25):
# 獲取絕對(duì)值較大的系數(shù)
coef = classifier.coef_.ravel()
positive_coefficients = np.argsort(coef)[-n_top_features:]
negative_coefficients = np.argsort(coef)[:n_top_features]
interesting_coefficients = np.hstack([negative_coefficients, positive_coefficients])
# 繪圖
plt.figure(figsize=(15, 5))
colors = ['red' if c < 0 else 'blue' for c in coef[interesting_coefficients]]
plt.bar(np.arange(2 * n_top_features), coef[interesting_coefficients], color=colors)
feature_names = np.array(feature_names)
plt.xticks(np.arange(1, 1 + 2 * n_top_features), feature_names[interesting_coefficients], rotation=60, ha='right');
def plot_grid_scores(grid, param_name):
plt.plot(grid.param_grid[param_name], grid.cv_results_['mean_train_score'],
color='green', label='train')
plt.plot(grid.param_grid[param_name], grid.cv_results_['mean_test_score'],
color='red', label='test')
plt.legend();
visualize_coefficients(logit, cv.get_feature_names())
為了讓我們的模型更好,我們可以優(yōu)化邏輯回歸的正則化系數(shù)。我們將使用sklearn.pipeline,因?yàn)镃ountVectorizer只應(yīng)該應(yīng)用于訓(xùn)練數(shù)據(jù)(為了避免“偷窺”測試集和在測試集上計(jì)算詞頻)。在這一情形下,pipeline確定正確的行動(dòng)序列:應(yīng)用CountVectorizer,然后訓(xùn)練邏輯回歸。
%%time
from sklearn.pipeline import make_pipeline
text_pipe_logit = make_pipeline(CountVectorizer(),
LogisticRegression(n_jobs=-1, random_state=7))
text_pipe_logit.fit(text_train, y_train)
print(text_pipe_logit.score(text_test, y_test))
0.86672
CPU times: user 49.9 s, sys: 571 ms, total: 50.5 s
Wall time: 20.5 s
%%time
from sklearn.model_selection import GridSearchCV
param_grid_logit = {'logisticregression__C': np.logspace(-5, 0, 6)}
grid_logit = GridSearchCV(text_pipe_logit, param_grid_logit, cv=3, n_jobs=-1)
grid_logit.fit(text_train, y_train)
CPU times: user 26.7 s, sys: 1.33 s, total: 28.1 s
Wall time: 1min 16s
讓我們查看下最佳C,以及相應(yīng)的交叉驗(yàn)證評(píng)分:
grid_logit.best_params_, grid_logit.best_score_
({'logisticregression__C': 0.10000000000000001}, 0.88527999999999996)
plot_grid_scores(grid_logit, 'logisticregression__C')
驗(yàn)證集上的結(jié)果:
grid_logit.score(text_test, y_test)
0.87907999999999997
現(xiàn)在讓我們用隨機(jī)森林來分類。我們看到,邏輯回歸事半功倍。
from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier(n_estimators=200, n_jobs=-1, random_state=17)
%%time
forest.fit(X_train, y_train)
CPU times: user 3min 39s, sys: 1.2 s, total: 3min 40s
Wall time: 30.7 s
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
max_depth=None, max_features='auto', max_leaf_nodes=None,
min_impurity_split=1e-07, min_samples_leaf=1,
min_samples_split=2, min_weight_fraction_leaf=0.0,
n_estimators=200, n_jobs=-1, oob_score=False, random_state=17,
verbose=0, warm_start=False)
round(forest.score(X_test, y_test), 3)
0.85499999999999998
XOR問題
現(xiàn)在讓我們看一個(gè)線性模型表現(xiàn)不佳的例子。
線性分類定義的是一個(gè)非常簡單的分界平面——一個(gè)超平面。最著名的超平面(或直線)無法無誤差地切分的玩具例子是XOR問題。
XOR即異或,其真值表如下:
XOR是一個(gè)簡單的二元分類問題,其中兩個(gè)分類呈對(duì)角交叉分布。
# 創(chuàng)建數(shù)據(jù)集
rng = np.random.RandomState(0)
X = rng.randn(200, 2)
y = np.logical_xor(X[:, 0] > 0, X[:, 1] > 0)
plt.scatter(X[:, 0], X[:, 1], s=30, c=y, cmap=plt.cm.Paired);
顯然,我們無法劃出一條直線無誤差地將兩個(gè)分類分開。因此,邏輯回歸在這一任務(wù)上的表現(xiàn)很差。
def plot_boundary(clf, X, y, plot_title):
xx, yy = np.meshgrid(np.linspace(-3, 3, 50),
np.linspace(-3, 3, 50))
clf.fit(X, y)
# 為網(wǎng)格上的每個(gè)數(shù)據(jù)點(diǎn)繪制判定函數(shù)
Z = clf.predict_proba(np.vstack((xx.ravel(), yy.ravel())).T)[:, 1]
Z = Z.reshape(xx.shape)
image = plt.imshow(Z, interpolation='nearest',
extent=(xx.min(), xx.max(), yy.min(), yy.max()),
aspect='auto', origin='lower', cmap=plt.cm.PuOr_r)
contours = plt.contour(xx, yy, Z, levels=[0], linewidths=2,
linetypes='--')
plt.scatter(X[:, 0], X[:, 1], s=30, c=y, cmap=plt.cm.Paired)
plt.xticks(())
plt.yticks(())
plt.xlabel(r'$x_1$')
plt.ylabel(r'$x_2$')
plt.axis([-3, 3, -3, 3])
plt.colorbar(image)
plt.title(plot_title, fontsize=12);
plot_boundary(LogisticRegression(), X, y,
'Logistic Regression, XOR problem')
然而,如果我們給出多項(xiàng)式特征作為輸入(這里d = 2),那么問題解決了。
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
logit_pipe = Pipeline([('poly', PolynomialFeatures(degree=2)),
('logit', LogisticRegression())])
plot_boundary(logit_pipe, X, y,
'Logistic Regression + quadratic features. XOR problem')
這里,邏輯回歸仍然生成了一個(gè)超平面,不過是6維特征空間(1、x1、x2、x12、x1x2、x22)中的超平面。當(dāng)我們將這個(gè)超平面投影到原特征空間(x1、x2)時(shí),分界是非線性的。
在實(shí)踐中,多項(xiàng)式特征確實(shí)有幫助,不過顯式創(chuàng)建它們?cè)谒懔ι鲜堑托У摹J褂煤思记傻腟VM要快很多。在這一方法中,只計(jì)算高維空間中目標(biāo)之間的距離(由核函數(shù)定義),而不用生成大量特征組合。
現(xiàn)在,我們對(duì)模型驗(yàn)證、交叉驗(yàn)證、正則化已經(jīng)有所了解,讓我們考慮一個(gè)更大的問題:
如果模型的質(zhì)量不盡人意,該怎么辦?
我們應(yīng)該讓模型更復(fù)雜還是更簡單?
我們應(yīng)該加入更多特征嗎?
我們是否只是需要更多數(shù)據(jù)用于訓(xùn)練?
這些問題的答案并不明顯。特別是,有時(shí)候一個(gè)更復(fù)雜的模型會(huì)導(dǎo)致表現(xiàn)退化。另一些時(shí)候,增加新的觀測并不會(huì)帶來可以觀察得到的變化。事實(shí)上,做出正確決定,選擇正確方法,從而改進(jìn)模型的能力區(qū)分了優(yōu)秀的專業(yè)人員和糟糕的專業(yè)人員。
讓我們回頭看看電信運(yùn)營商離網(wǎng)率數(shù)據(jù)集。
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV, SGDClassifier
from sklearn.model_selection import validation_curve
data = pd.read_csv('../../data/telecom_churn.csv').drop('State', axis=1)
data['International plan'] = data['International plan'].map({'Yes': 1, 'No': 0})
data['Voice mail plan'] = data['Voice mail plan'].map({'Yes': 1, 'No': 0})
y = data['Churn'].astype('int').values
X = data.drop('Churn', axis=1).values
我們將使用隨機(jī)梯度下降訓(xùn)練邏輯回歸。我們將在之后的課程專門討論梯度下降。
alphas = np.logspace(-2, 0, 20)
sgd_logit = SGDClassifier(loss='log', n_jobs=-1, random_state=17)
logit_pipe = Pipeline([('scaler', StandardScaler()), ('poly', PolynomialFeatures(degree=2)),
('sgd_logit', sgd_logit)])
val_train, val_test = validation_curve(logit_pipe, X, y,
'sgd_logit__alpha', alphas, cv=5,
scoring='roc_auc')
我們將繪制ROC-AUC曲線,查看下不同正則化參數(shù)導(dǎo)致的模型在訓(xùn)練集和參數(shù)集上的不同表現(xiàn)。
趨勢相當(dāng)明顯,這樣的趨勢在其他問題中也同樣常見:
簡單模型的訓(xùn)練誤差和驗(yàn)證誤差很接近,都比較大。這暗示模型欠擬合,參數(shù)數(shù)量不夠。
高度復(fù)雜模型的訓(xùn)練誤差和驗(yàn)證誤差差別很大。這可以解釋為過擬合。當(dāng)參數(shù)數(shù)量過多或者正則化不夠嚴(yán)格時(shí),算法可能被數(shù)據(jù)中的噪聲“轉(zhuǎn)移注意力”,沒能把握數(shù)據(jù)的整體趨勢。
需要多少數(shù)據(jù)?
數(shù)據(jù)是越多越好。但我們?nèi)绾卫斫庑聰?shù)據(jù)是否在任何情況下都有幫助呢?例如,如何評(píng)估花費(fèi)N來加倍數(shù)據(jù)集是否合理呢?
由于新數(shù)據(jù)可能無法取得,合理的做法是改變訓(xùn)練集的大小,然后看解答的質(zhì)量如何依賴于訓(xùn)練數(shù)據(jù)的數(shù)量。這樣我們就得到了學(xué)習(xí)曲線。
這個(gè)想法很簡單:我們將誤差顯示為訓(xùn)練中使用的樣本數(shù)的函數(shù)。模型的參數(shù)事先固定。
from sklearn.model_selection import learning_curve
def plot_learning_curve(degree=2, alpha=0.01):
train_sizes = np.linspace(0.05, 1, 20)
logit_pipe = Pipeline([('scaler', StandardScaler()), ('poly', PolynomialFeatures(degree=degree)),
('sgd_logit', SGDClassifier(n_jobs=-1, random_state=17, alpha=alpha))])
N_train, val_train, val_test = learning_curve(logit_pipe,
X, y, train_sizes=train_sizes, cv=5,
scoring='roc_auc')
plot_with_err(N_train, val_train, label='training scores')
plot_with_err(N_train, val_test, label='validation scores')
plt.xlabel('Training Set Size'); plt.ylabel('AUC')
plt.legend()
讓我們看看線性模型的結(jié)果。我們將把正則化系數(shù)設(shè)定為較大的數(shù)。
plot_learning_curve(degree=2, alpha=10)
一個(gè)典型的狀況:對(duì)于少量數(shù)據(jù)而言,訓(xùn)練集和交叉驗(yàn)證集之間的誤差差別相當(dāng)大,這暗示了過擬合。同樣的模型,不過使用大量數(shù)據(jù),誤差“收斂”,暗示了欠擬合。
如果我們加入更多數(shù)據(jù),訓(xùn)練集的誤差不會(huì)增加。另一方面,測試集上的誤差也不會(huì)下降。
所以,我們看到誤差“收斂”,加入新數(shù)據(jù)無濟(jì)于事。事實(shí)上這個(gè)情況對(duì)業(yè)務(wù)來說很重要。我們可能把數(shù)據(jù)集大小增大10倍,但是,如果我們不改變模型的復(fù)雜度,數(shù)據(jù)的增加沒有幫助。因此“設(shè)定一次,使用十次”的策略可能無法奏效。
如果我們將正則化系數(shù)降低到0.05,會(huì)怎么樣?
我們看到了好兆頭——曲線逐漸收斂,如果我們移動(dòng)到更右,也就是加入更多數(shù)據(jù),我們可以進(jìn)一步改善模型在驗(yàn)證集上的表現(xiàn)。
plot_learning_curve(degree=2, alpha=0.05)
如果我們把a(bǔ)lpha設(shè)為10-4,讓模型更復(fù)雜,會(huì)出現(xiàn)什么情況?
我們看到了過擬合——在訓(xùn)練集和驗(yàn)證集上,AUC都下降了。
plot_learning_curve(degree=2, alpha=1e-4)
構(gòu)建這些曲線可以幫助我們理解朝哪個(gè)方向走,如何恰當(dāng)?shù)貫樾聰?shù)據(jù)調(diào)整模型復(fù)雜度。
學(xué)習(xí)曲線和驗(yàn)證曲線的一些結(jié)論:
訓(xùn)練集上的誤差本身不能說明模型的質(zhì)量。
交叉驗(yàn)證誤差顯示了模型對(duì)數(shù)據(jù)擬合得有多好(數(shù)據(jù)的現(xiàn)有趨勢)同時(shí)保留了多少對(duì)新數(shù)據(jù)的概括能力。
驗(yàn)證曲線是一個(gè)根據(jù)模型復(fù)雜度顯示訓(xùn)練集和驗(yàn)證集上的結(jié)果的圖形:
如果兩條曲線彼此接近,兩者的誤差都很大,這標(biāo)志著欠擬合
如果兩條曲線彼此距離很遠(yuǎn),這標(biāo)志著過擬合
學(xué)習(xí)曲線是一個(gè)根據(jù)觀測數(shù)量顯示訓(xùn)練集和驗(yàn)證集上的結(jié)果的圖形:
如果兩條曲線收斂,增加新數(shù)據(jù)無濟(jì)于事,有必要改變模型復(fù)雜度
如果兩條曲線沒有收斂,增加新數(shù)據(jù)可以改善結(jié)果
Kaggle競賽頁面:
https://www.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2
我們將解決一個(gè)入侵者檢測問題(通過分析上網(wǎng)用戶的行為)。這是一個(gè)結(jié)合了數(shù)據(jù)分析和行為心理學(xué)的復(fù)雜而有趣的問題。例如,Yandex根據(jù)用戶的行為模式解決郵箱入侵檢測問題。概括起來,入侵者的行為模式可能和郵箱所有者不一樣:
郵箱所有者可能在讀完郵件后刪除郵件,而入侵者可能不會(huì)這么做;
入侵者標(biāo)記郵件的方式可能不一樣,甚至移動(dòng)鼠標(biāo)的方式也可能不一樣;
等等。
所以我們可以檢測到入侵者,將其扔出郵箱,要求通過短信驗(yàn)證碼認(rèn)證身份。
Google Analytics研發(fā)了類似的技術(shù),并發(fā)表了相應(yīng)的論文。你可以通過搜索“Traversal Pattern Mining”(遍歷模式挖掘)和“Sequential Pattern Mining”(序列模式挖掘)了解更多關(guān)于這一主題的信息。
在這一競賽中,我們將解決一個(gè)類似的問題:我們的算法將分析特定用戶頻繁訪問的網(wǎng)站序列,預(yù)測這個(gè)人是不是一個(gè)名為Alice的用戶還是一個(gè)入侵者(其他人)。我們將使用ROC-AUC作為指標(biāo)。
數(shù)據(jù)下載和轉(zhuǎn)換
如果你之前沒有注冊(cè)過Kaggle賬號(hào)的話,注冊(cè)一下。然后到競賽頁面,下載數(shù)據(jù)。
首先,加載訓(xùn)練集和測試集。探索下數(shù)據(jù):
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
import pickle
import numpy as np
import pandas as pd
from scipy.sparse import csr_matrix
from scipy.sparse import hstack
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score
from sklearn.linear_model import LogisticRegression
# 加載訓(xùn)練集和測試集
train_df = pd.read_csv('../../data/websites_train_sessions.csv',
index_col='session_id')
test_df = pd.read_csv('../../data/websites_test_sessions.csv',
index_col='session_id')
# 轉(zhuǎn)換time1、……、time10列至datetime類
times = ['time%s' % i for i in range(1, 11)]
train_df[times] = train_df[times].apply(pd.to_datetime)
test_df[times] = test_df[times].apply(pd.to_datetime)
# 據(jù)時(shí)間排序數(shù)據(jù)
train_df = train_df.sort_values(by='time1')
# 查看下訓(xùn)練集的開始幾行
train_df.head()
訓(xùn)練集包含以下特征:
site1 會(huì)話中訪問的第一個(gè)網(wǎng)站;
time1 會(huì)話中訪問第一個(gè)網(wǎng)站的時(shí)間;
……
site10 會(huì)話中訪問的第十個(gè)網(wǎng)站;
time10 會(huì)話中訪問第十個(gè)網(wǎng)站的時(shí)間;
target 目標(biāo)變量,1表示Alice的會(huì)話,0表示其它用戶的會(huì)話。
用戶會(huì)話的選取方式保證這些會(huì)話不超過半小時(shí)或包含超過十個(gè)網(wǎng)站,也就是說,一旦一個(gè)用戶訪問過了十個(gè)網(wǎng)站,或者會(huì)話時(shí)長超過了半小時(shí),我們就認(rèn)為這一會(huì)話結(jié)束了。
表中有一些空值,意味著某些會(huì)話包含不到十個(gè)網(wǎng)站。將這些空值替換為0,并將列類型改為整數(shù)。同時(shí),加載網(wǎng)站字典,看看是什么樣的:
# 將site1、……、site10列類型轉(zhuǎn)為整數(shù),同時(shí)用零填充NA值
sites = ['site%s' % i for i in range(1, 11)]
train_df[sites] = train_df[sites].fillna(0).astype('int')
test_df[sites] = test_df[sites].fillna(0).astype('int')
# 加載網(wǎng)站字典
with open(r'../../data/site_dic.pkl', 'rb') as input_file:
site_dict = pickle.load(input_file)
# 創(chuàng)建字典的dataframe
sites_dict = pd.DataFrame(list(site_dict.keys()),
index=list(site_dict.values()), columns=['site'])
print(u'Websites total:', sites_dict.shape[0])
sites_dict.head()
# Websites total: 48371
簡單起見,我們將只使用會(huì)話中訪問的站點(diǎn)(不考慮訪問時(shí)長)。這一數(shù)據(jù)選擇背后的依據(jù)是:Alice有她偏愛的站點(diǎn),我們?cè)跁?huì)話中看到更多這些站點(diǎn),這一會(huì)話是Alice的會(huì)話的可能性就越高,反之亦然。
讓我們準(zhǔn)備下數(shù)據(jù)。首先,我們從訓(xùn)練集中排除目標(biāo)變量,這樣,訓(xùn)練集和測試集就有了相同數(shù)量的列,我們可以將其合并為一個(gè)dataframe,對(duì)其一并進(jìn)行轉(zhuǎn)換。
# 目標(biāo)變量
y_train = train_df['target']
# 合并為一個(gè)dataframe
full_df = pd.concat([train_df.drop('target', axis=1), test_df])
# 分割訓(xùn)練集和測試集的索引
idx_split = train_df.shape[0]
只保留dataframe的site1, site2, ..., site10。
# 訪問過的網(wǎng)站索引構(gòu)成的dataframe
full_sites = full_df[sites]
full_sites.head()
會(huì)話是網(wǎng)站索引的序列,這一數(shù)據(jù)表示不便于通過線性方法處理。我們需要將其轉(zhuǎn)換為如下表示形式:每個(gè)網(wǎng)站對(duì)應(yīng)一個(gè)特征(列),其值為會(huì)話中訪問該網(wǎng)站的次數(shù)。
# 索引序列
sites_flatten = full_sites.values.flatten()
# 我們想要的矩陣
full_sites_sparse = csr_matrix(([1] * sites_flatten.shape[0],
sites_flatten,
range(0, sites_flatten.shape[0] + 10, 10)))[:, 1:]
如果你明白剛剛發(fā)生了什么,那你可以直接跳到下一節(jié)(也許你也可以處理邏輯回歸?),否則,讓我們一起來搞明白發(fā)生了什么。
稀疏矩陣
讓我們估計(jì)一下,在上面的例子中,儲(chǔ)存我們的數(shù)據(jù)需要多少內(nèi)存。合并后的dataframe包含336k樣本,每個(gè)樣本包含48k整數(shù)特征。因此我們大致需要的內(nèi)存為:
336K * 48K * 8 bytes = 16G * 8 Bytes = 128 GB
顯然,凡夫俗子沒有這么多內(nèi)存(嚴(yán)格來說,Python可能允許你創(chuàng)建這樣一個(gè)矩陣,但對(duì)其進(jìn)行任何操作都不容易)。一個(gè)有趣的事實(shí)是,我們的矩陣的大多數(shù)元素值為零。如果我們計(jì)算非零元素,那么大概有一百八十萬,所占比例略高于所有矩陣元素的0.01%. 這樣一種大多數(shù)元素為零的矩陣,稱為稀疏矩陣,零元素?cái)?shù)量和總元素的比例則稱為矩陣的稀疏度。
scipy.sparse庫可用于處理稀疏矩陣,參考它的文檔了解稀疏矩陣的類型,如何處理,何時(shí)使用稀疏矩陣最高效。注意,稀疏矩陣只包含非零元素。最后,讓我們看看稀疏矩陣占用的內(nèi)存大小(顯然,稀疏矩陣顯著節(jié)省了內(nèi)存):
# 稀疏矩陣占用多少內(nèi)存?
print('{0} elements * {1} bytes = {2} bytes'.
format(full_sites_sparse.count_nonzero(), 8,
full_sites_sparse.count_nonzero() * 8))
# 或者直接:
print('sparse_matrix_size = {0} bytes'.
format(full_sites_sparse.data.nbytes))
1866898 elements * 8 bytes = 14935184 bytes
sparse_matrix_size = 14935184 bytes
讓我們通過一個(gè)迷你的例子探索下這一矩陣是如何形成的。假設(shè)我們的用戶會(huì)話表是這樣的:
總共有3個(gè)會(huì)話,每次會(huì)話不超過3個(gè)站點(diǎn)。用戶訪問的不同站點(diǎn)總數(shù)為4(表中的1到4表示這4個(gè)站點(diǎn))。讓我們假定這4個(gè)站點(diǎn)是:
vk.com
habrahabr.ru
yandex.ru
ods.ai
如果用戶在會(huì)話時(shí)訪問了不到三個(gè)站點(diǎn),后面的值將為零。我們想要將原dataframe轉(zhuǎn)換為每個(gè)會(huì)話顯示某一特定站點(diǎn)的訪問數(shù),如下表所示:
為此,我們使用csr_matrix ((data, indices, indptr))創(chuàng)建一個(gè)頻率表。這里,我們顯式地設(shè)定所有參數(shù),讓你能更清楚地理解這一過程:
# 創(chuàng)建由1組成的列表,長度等于原dataframe中元素的數(shù)量(9)
data = [1] * 9
# 網(wǎng)站id
indices = [1, 0, 0, 1, 3, 1, 2, 3, 4]
# 切分行(會(huì)話)的索引
indptr = [0, 3, 6, 9]
# 將上述三個(gè)變量聚合為一個(gè)元組,然后構(gòu)建矩陣
# 顯示矩陣時(shí),將其轉(zhuǎn)為通常的“密集”矩陣
csr_matrix((data, indices, indptr)).todense()
matrix([[2, 1, 0, 0, 0],
[0, 2, 0, 1, 0],
[0, 0, 1, 1, 1]])
你也許注意到了,所得矩陣的列數(shù)目不是四(不同網(wǎng)站的總數(shù)),而是五。第零列是額外增加的列,顯示每次會(huì)話少訪問了幾個(gè)站點(diǎn)(在我們的迷你例子中,會(huì)話的長度為3)。這一列是多余的,需要從dataframe中移除。
使用稀疏矩陣的另一個(gè)好處是,有為其特制的矩陣操作和機(jī)器學(xué)習(xí)算法實(shí)現(xiàn),有時(shí)能通過利用數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)顯著加速操作。邏輯回歸也適用?,F(xiàn)在,萬事俱備,我們可以創(chuàng)建第一個(gè)模型了。
訓(xùn)練第一個(gè)模型
我們將使用sklearn的邏輯回歸實(shí)現(xiàn)(默認(rèn)參數(shù))。數(shù)據(jù)集的前90%用于訓(xùn)練(數(shù)據(jù)集按時(shí)間排序),剩余10%用于驗(yàn)證。
def get_auc_lr_valid(X, y, C=1.0, seed=17, ratio = 0.9):
# 將數(shù)據(jù)分為訓(xùn)練集和驗(yàn)證集
idx = int(round(X.shape[0] * ratio))
# 訓(xùn)練分類器
lr = LogisticRegression(C=C, random_state=seed,
solver='lbfgs', n_jobs=-1).fit(X[:idx, :], y[:idx])
# 為驗(yàn)證集做出預(yù)測
y_pred = lr.predict_proba(X[idx:, :])[:, 1]
# 計(jì)算質(zhì)量
score = roc_auc_score(y[idx:], y_pred)
return score
%%time
# 選擇訓(xùn)練集
X_train = full_sites_sparse[:idx_split, :]
# 在驗(yàn)證集上計(jì)算量度
print(get_auc_lr_valid(X_train, y_train))
0.9198622553850315
CPU times: user 138 ms, sys: 77.1 ms, total: 216 ms
Wall time: 2.74 s
第一個(gè)模型在驗(yàn)證集上的表現(xiàn)接近0.92 ROC-AUC。讓我們將其作為第一條基線(作為一個(gè)開始)。為了在測試集上進(jìn)行預(yù)測,我們需要在整個(gè)訓(xùn)練集上再次訓(xùn)練模型(到此為止,我們的模型只使用了數(shù)據(jù)的一部分進(jìn)行訓(xùn)練),這將增加模型的概括能力:
# 將預(yù)測寫入文件的函數(shù)
def write_to_submission_file(predicted_labels, out_file,
target='target', index_label='session_id'):
predicted_df = pd.DataFrame(predicted_labels,
index = np.arange(1,
predicted_labels.shape[0] + 1),
columns=[target])
predicted_df.to_csv(out_file, index_label=index_label)
# 在整個(gè)訓(xùn)練數(shù)據(jù)集上訓(xùn)練模型
# 為了可重復(fù)性,將random_state設(shè)為17
# 顯式設(shè)置C=1(這是默認(rèn)值)
lr = LogisticRegression(C=1.0, solver='lbfgs',
random_state=17).fit(X_train, y_train)
# 在測試數(shù)據(jù)集上進(jìn)行預(yù)測
X_test = full_sites_sparse[idx_split:,:]
y_test = lr.predict_proba(X_test)[:, 1]
# 寫入預(yù)測結(jié)果至提交文件
write_to_submission_file(y_test, 'baseline_1.csv')
如果你遵循這些步驟,并將答案上傳到競賽頁面,你將在公開排行榜上取得ROC AUC = 0.91707的成績。
你的任務(wù)是通過特征工程、特征縮放和正則化進(jìn)一步改善模型。你將首先嘗試添加一些明顯的特征(比如瀏覽網(wǎng)站的時(shí)刻,會(huì)話中的網(wǎng)站數(shù)目,等等)。我們鼓勵(lì)你在課程的學(xué)習(xí)過程中嘗試新的想法和模型,并參與競賽——這很有趣!
截止日期:March 11, 23:59 CET
I. Goodfellow、Y. Bengio、A. Courville所著《Deep Learning》(深度學(xué)習(xí))一書提供了緊湊而出色的線性模型綜述。
基本上每本ML教材都涉及線性模型。我們推薦C. Bishop的《Pattern Recognition and Machine Learning》和K. Murphy的《Machine Learning: A Probabilistic Perspective》。
如果你打算從統(tǒng)計(jì)學(xué)的視角概覽線性模型,可以看下T. Hastie、R. Tibshirani、J. Friedman的《The elements of statistical learning》。
P. Harrington的《Machine Learning in Action》將引導(dǎo)你完全使用Python實(shí)現(xiàn)經(jīng)典ML算法。
scikit-learn庫。scikit-learn的開發(fā)者致力于編寫極為清晰的文檔。
Scipy 2017 scikit-learn教程(Alex Gramfort、Andreas Mueller)。
MTH594課程 Advanced data mining: theory and applications包含很多非常好的材料。
GitHub倉庫rushter/MLAlgorithms里有許多ML算法的實(shí)現(xiàn),其中包括線性回歸和邏輯回歸。
歡迎留言分享其他資源。
原文地址:https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-4-linear-classification-and-regression-44a41b9b5220
聯(lián)系客服