編者按:機器學習開放課程第四課,Mail.Ru數據科學家Yury Kashnitsky深入講解線性分類和線性回歸的理論與實踐。
xkcd譯文:「如你所見,到下個月底,你會有48位丈夫。值得吃一大塊蛋糕慶祝下。」(橫軸為日期:昨天、今天;縱軸為丈夫數量。)
歡迎參加我們的第4課。這次我們將呈現最重要的主題——線性模型。如果你準備好了數據,打算開始訓練模型,那麼你最有可能首先嘗試線性回歸或邏輯回歸(具體取決於你的任務是回歸還是分類)。
本文包括線性模型的理論和實踐(在實際任務中的使用)。此外,你將參加一項Kaggle競賽,解決一個基於瀏覽歷史識別用戶的問題。
回歸
最小二乘法
最大似然估計
偏置-方差分解
線性回歸正則化
線性分類
線性分類器
作為線性分類器的邏輯回歸
最大似然估計和邏輯回歸
邏輯損失的L2正則化
邏輯回歸正則化示例
邏輯回歸的優缺點
驗證和學習曲線
課內Kaggle競賽「我知道你是誰」
作業四
相關資源
我們的線性模型學習從線性回歸開始。首先,需要指定一個模型將因變量y和解釋因素(特徵)聯繫起來;對線性模型而言,依賴函數的形式如下:
如果我們為每項觀測加上一個虛維度x0 = 1 (偏置),那麼上面的線性形式可以改寫為一個略微緊湊的形式:
如果我們有一個特徵觀測矩陣,其中矩陣的行是數據集中的觀測,那麼需要在左邊加上一列。
我們可以定義模型為:
其中:
上述表達式也可以寫成這樣(寫出每項觀察):
模型具有如下限制(否則它就不是線性回歸了):
權重wi的估計滿足如下條件時,我們稱其為線性:
其中,∀k,ωki僅依賴於X中的樣本,而且幾乎一定以非線性的方式。由於尋求最佳權重的解是一個線性估計,這一模型被稱為線性回歸。讓我們再引入一項定義。當期望值等於真實而未知的估計參數的值時,權重估計稱為無偏(unbiased):
計算這些權重的方法之一是普通最小二乘法(OLS)。OLS最小化因變量的實際值和模型給出的預測值之間的均方誤差:
為了解決這一優化問題,我們需要計算模型參數的導數。我們將導數設為零,然後求解所得關於w的等式。
矩陣求導小抄:
讓我們開始計算:
基於上述定義和條件,我們可以說,根據高斯-馬爾可夫定理,模型參數的OLS估計是所有線性無偏估計中最優的,即,它們給出最低的方差。
最大似然估計
有人可能會問,為何我們選擇最小化均方誤差而不是別的什麼?畢竟,我們可以最小化殘差的平均絕對值。如果我們改變了最小化的值,唯一會發生的事就是我們將超出高斯-馬爾可夫定理的條件,因此我們的估計將不再是最佳的線性無偏估計。
在我們繼續之前,讓我們稍微離題一下,通過一個簡單的例子講解下最大似然估計。
許多人大概都記得乙醇的化學式,所以我決定做一個試驗判定人們是否記得簡單的甲醇化學式:CH3OH. 我們調查了400人,發現只有117個人記得甲醇的化學式。那麼,下一個受訪者知道甲醇化學式的概率為117/400 ≈ 29%會是一個合理的假定。讓我們展示下這個直觀的估計不僅很好,同時也是最大似然估計。估計來自哪裡?回憶下伯努利分布的定義:如果一個隨機變量只有兩個值(1和0,相應的概率為θ和1 - θ),那麼該隨機變量滿足伯努利分布,遵循以下概率分布函數:
這一分布正是我們所需要的,分布參數θ是一個人知道甲醇化學式的概率估計。在我們的400個獨立試驗中,讓我們將試驗的結果記為x = (x1, x2, ..., x400)。讓我們寫下數據(觀測)的似然,即正好觀測到117個隨機變量θ = 1的實例和283個隨機變量θ = 0的實例:
接著,我們將最大化這一θ的表達式。最常見的情況是,我們並不最大化似然p(x | θ),轉而最大化其對數(這一單調變換不影響解答,但大大簡化了計算):
為了找到最大化上式的θ,我們求θ的導數,設為零,求解所得等式:
結果發現,我們的直觀估計正好是最大似然估計。現在讓我們將這一推理過程應用到線性回歸問題上,嘗試找出均方誤差背後有什麼。為此,我們需要從概率論的角度來看線性回歸。我們的模型和之前是一樣的:
不過,現在讓我們假定隨機誤差符合均值為零的正態分布:
據此改寫模型:
由於樣本是獨立抽取的(不相關誤差是高斯-馬爾可夫定理的條件之一),數據的似然看起來會是密度函數p(yi)的積。讓我們考慮對數似然,對數似然允許我們用和替換積:
我們想要找到最大似然假設,即,我們需要最大化表達式p(y ∣ X, w) 以得到wML。這和最大化其對數是一回事。注意,當我們針對某個參數最大化函數時,我們可以丟棄所有不依賴這一參數的成員:
所以,我們看到了,最大化數據的似然和最小化均方誤差是一回事(給定以上的假定)。實際上,這是因為誤差是正態分布的。
偏置-方差分解
讓我們稍微談下線性回歸預測的誤差性質(實際上,這一討論在所有機器學習算法上都成立)。我們已經提到:
因此,點x的誤差可分解為:
為了簡明,我們將省略函數的參數。讓我們分別考慮每個成員。據以下公式:
我們很容易就能分解前兩項:
注意:
最後我們來處理和的最後一項。回憶一下,誤差和目標變量相互獨立:
最後,讓我們把這些合併到一起:
我們已經達到了我們的終極目標——最後的等式告訴我們,任何線性模型的預測誤差由三部分組成:
儘管我們對σ2無能為力,我們可以影響前兩項。理想情況下,我們希望同時取消這兩項(見下圖中左上),但是,在實踐中,常常需要在偏置和不穩定(高方差)間尋找平衡。
一般而言,當模型的計算增加了(例如,自由參數的數量增加了),估計的方差(分散程度)也會增加,但偏置會下降。由於模型完全記下了訓練集而沒能概括訓練集,小小的變動將導致未預期的結果(過擬合)。另一方面,如果模型太弱,它將不能夠學習模式,導致學習偏離正解較遠的不同答案。
高斯-馬爾可夫定理斷言,在線性模型參數估計問題中,OLS估計是最佳的線性無偏估計。這意味著,如果存在任何無偏線性模型g,我們可以確信
線性回歸正則化
在一些情形下,我們可能會為了穩定性(降低模型的方差)特意增加模型的偏置。高斯-馬爾可夫定理的條件之一就是矩陣X是滿秩的。否則,OLS解w = (XTX)-1XTy不存在,因為逆矩陣(XTX)-1不存在。換句話說,矩陣XTX將是奇異矩陣或退化矩陣。這被稱為病態問題。這類問題必須加以矯正,也就是說,矩陣XTX需要變為非退化矩陣或非奇異矩陣(這正是這一過程叫做正則化的原因)。我們常常能在這類數據中觀察到所謂的多重共線性:當兩個或更多特徵高度相關,也就是矩陣X的列之間「幾乎」存在線性依賴。例如,在基於參數預測房價這一問題中,屬性「含陽臺面積」和「不含陽臺面積」會有一個「幾乎是」線性的關係。形式化地說,包含這類數據的矩陣XTX是可逆的,但由於多重共線性,一些本徵值會接近零。在XTX的逆矩陣中,會出現一些極端巨大的本徵值,因為逆矩陣的本徵值為1/(λi)。這一本徵值的波動會導致模型參數估計的不穩定,即,在訓練數據中加入一組新的觀測會導致完全不同的解。有一種正則化的方法稱為吉洪諾夫正則化,大致上是在均方誤差中加上一個新成員:
吉洪諾夫矩陣常常表達為單位矩陣乘上一個係數:
在這一情形下,最小化均方誤差問題變為一個L2正則限定問題。如果我們對新的損失函數求導,設所得函數為零,據w重整等式,我們便得到了這一問題的解:
這類回歸被稱為嶺回歸。嶺為對角矩陣,我們在XTX矩陣上加上這一對角矩陣,以確保我們能得到一個常規矩陣。
這樣的解降低了分散程度,但增加了偏置,因為參數的正則向量同時最小化了,這導致解朝零移動。在下圖中,OLS解為白色虛線的交點。藍點表示嶺回歸的不同解。可以看到,通過增加正則化參數λ,我們使解朝零移動。
線性分類器
線性分類器背後的基本思路是,目標分類的值可以被特徵空間中的一個超平面分開。如果這可以無誤差地達成,那麼訓練集被稱為線性可分。
我們之前已經了解了線性回歸和普通最小二乘法(OLS)。現在考慮一個二元分類問題,將目標分類記為「+1」(正面樣本)和「-1」(負面樣本)。最簡單的線性分類器可以通過回歸定義:
其中
x是特徵向量(包括標識);
w是線性模型中的權重向量(偏置為w0);
sign(∙)是符號函數,返回參數的符號;
a(x)是分類x的分類器。
作為線性分類器的邏輯回歸
邏輯回歸是線性分類器的一個特殊情形,邏輯回歸有一個額外的好處,可以預測樣本xi為分類「+」的概率p+。
不僅能夠預測一個回應(「+1」或「-1」),還能預測相應的概率,對於很多業務問題(比如,信用評分,這一問題傳統上使用邏輯回歸)而言,這是一個非常重要的需求。
銀行選擇一個閾值p*以預測貸款違約的概率(上圖中閾值為0.15),超過閾值就不批准貸款。此外,還可以將預測概率乘以未償還金額,以得到客戶造成的期望損失,這可以構成有用的業務指標。
為了預測概率p+ ∈ [0, 1],我們可以從使用OLS構造線性預測開始:
不過,為了將所得結果轉換為[0, 1]區間內的概率,我們需要某個函數
邏輯回歸使用如下函數:
%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)),這是某一事件是否發生的概率之比。顯然,概率和比值比包含同樣的信息,不過P(X)的範圍是0到1,而OR(X)的範圍是0到∞。
如果我們計算OR(X)的對數,那麼顯然我們有log OR(X) ∈ ℝ. 我們在OLS中將用到這個。
讓我們看看邏輯回歸是如何做出預測的:
目前而言,讓我們假設我們已經通過某種方式得到了權重w,即,模型已經訓練好了。之後我們將看下這是如何做的。
步驟一 計算
等式WTX = 0定義了將樣本分為兩類的超空間。
步驟二 計算對數比值比:
步驟三 現在我們已經有了將一個樣本分配到「+」分類的概率OR+,我們可以據此計算p+:
我們看到,上式的右邊我們得到了sigmoid函數。
所以,邏輯回歸預測將一個樣本分配為「+」分類的概率(假定我們已知模型的特徵和權重),這一過程是通過對權重向量和特徵向量的線性組合進行sigmoid變換完成的:
下面我們將看下模型是如何訓練的。我們將再次依靠最大似然估計。
最大似然估計和邏輯回歸
現在,讓我們看下從MLE出發如何進行邏輯回歸優化,也就是最小化邏輯損失函數。我們前面已經見過了將樣本分配為「+」分類的邏輯回歸模型:
「-」分類相應的表達式為:
這兩個表達式可以組合成一個:
表達式M(xi) = yiwTxi稱為目標xi的分類邊緣。如果邊緣非負,則模型正確選擇了目標xi的分類;如果邊緣為負,則目標xi被錯誤分類了。注意,邊緣僅針對訓練集中的目標(真實目標分類標籤yi已知的目標)而言。
為了精確地理解我們為何得出這一結論,讓我們轉向線性分類器的幾何解釋。
首先,我會建議看下線性代數的一個經典入門問題:找出向徑xA與平面wTx = 0的距離。
答案:
從答案中,我們可以看到,表達式wTxi的絕對值越大,點xi離平面wTx = 0的距離就越遠。
因此,表達式M(xi) = yiwTxi是模型對目標xi分類的「信心」:
如果邊緣的絕對值較大,且為正值,那麼分類的標籤是正確的,且目標離分界超平面很遠,也就是模型對分類很自信。如下圖點x3所示;
如果邊緣的絕對值較大,且為負值,那麼分類的標籤是錯誤的,且目標離分界超平面很遠(目標很可能是一個異常值;例如,它可能是訓練集中一個錯誤標記的值)。如下圖點x1所示;
如果邊緣的絕對值較小,那麼目標距離分界超平面很近,邊緣的符號決定目標是否被正確分類了。如下圖點x2和x4所示。
現在讓我們計算數據集的似然,即基於數據集X觀測到給定向量y的概率。我們將做一個強假設:目標來自一個獨立分布(i.i.d.)。
其中,ℓ為數據集X的長度(行數)。
像我們經常幹的那樣,對這個表達式取對數,因為和要比積容易優化得多:
最大化似然等價於最小化以下表達式:
這就是邏輯損失函數。
用邊緣改寫邏輯損失函數,我們有:
我們將這一函數的圖像和0-1損失函數的圖像繪製在一張圖上。0-1損失函數簡單地懲罰模型誤差(邊緣為負)為1:
上圖體現了這樣一個想法,如果我們不能夠直接最小化分類問題的誤差數量(至少無法通過梯度方法最小化——0-1損失函數在零處的導數趨向無窮),我們可以最小化它的上界。對邏輯損失函數而言,以下是成立的:
我們希望能通過降低分類誤差數的上界,降低分類誤差數本身。
邏輯損失的L2正則化
邏輯回歸的L2正則化和嶺回歸的情況基本一樣。我們轉而最小化下式:
在邏輯回歸中,通常使用正則化係數的倒數C = 1/λ:
下面我們將通過一個例子直觀地理解正則化。
讓我們看下正則化是如何影響分類的質量的(數據集為吳恩達機器學習課程中的微晶片測試)。我們將使用基於多項式特徵的邏輯回歸,然後改變正則化參數C. 首先,我們將看看正則化是如何影響分類器的分界的,並直觀地識別欠擬合和過擬合。接著,我們將通過交叉驗證和網格搜索選擇數值上接近最優值的正則化參數。
# 關閉警告
# 如果你喜歡開著警告,可以注釋掉下面兩行
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方法加載數據。這個數據集內有118個微晶片(目標),其中有兩項質量控制測試的結果(兩個數值變量)和微晶片是否投產的信息。變量已經居中了,也就是列中的值已經減去其均值。所以,「平均」微晶片的測試值為零。
# 加載數據
data = pd.read_csv('../../data/microchip_tests.txt',
header=None, names = ('test1','test2','released'))
# 了解數據集的基本信息
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 = data.iloc[:,:2].values
y = data.iloc[:,2].values
繪製數據,橙點對應有缺陷的晶片,藍點對應正常晶片。
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();
定義一個顯示分類器的分界曲線的函數。
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)
我們為兩個變量x1和x2定義如下多形式特徵:
例如,d = 3時的特徵如下:
特徵的數量呈指數型增長,為100個變量創建d較大(例如d = 10)的多項式特徵成本很高。更重要的是,不需要如此。
我們將使用sklearn的邏輯回歸實現。我們將創建一個對象,為矩陣X加上多項式特徵(d不超過7)。
poly = PolynomialFeatures(degree=7)
X_poly = poly.fit_transform(X)
X_poly.shape
結果:
(118, 36)
讓我們訓練邏輯回歸,正則化係數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,也就是說,我們削弱了正則化,現在的模型權重可以比之前有更大的值(絕對值更大)。這使得分類器在訓練集上的精確度改善了(提高到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
我們為什麼不接著進一步增加C呢?比如,將C增加到10000?這回,很明顯正則化不夠強,我們看到了過擬合。注意,C = 1時,「平滑」邊界對應的訓練集上的正確解答並沒有低多少。但我們很容易可以想像得到,我們之前的模型將在新數據上工作得更好。
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
為了討論以上這些結果,讓我們改寫一下邏輯回歸優化的函數:
部分和:
參數C越大,模型可恢復的數據中的關係就越複雜(直觀地說,C對應模型的「複雜度」——模型能力)。
如果正則化過強,即C值很小,最小化邏輯損失函數問題的解可能是一個許多權重過小或為零的解。這樣的模型對誤差的「懲罰」也不夠(即,在函數J中,權重的平方和「權重過高」,誤差L可能相對很大)。在這一情形下,模型將會欠擬合,如我們在第一個情形下所看到的那樣。
相反,如果正則化過弱,即C值很大,由絕對值很大的分量組成的向量w可能變成優化問題的解。在這一情形下,L對優化的函數J貢獻較大。大致上,這樣的模型對在訓練集的目標上犯錯過於「恐懼」,因而會過擬合,如我們在第三個情形下所看到的那樣。
邏輯回歸不會「理解」(或「學習」)選擇C的值,這和權重w的情況不一樣。這就是說,C的值無法通過解決邏輯回歸的優化問題而確定。我們之前碰到過類似的情況——決策樹無法在訓練過程中「學習」選擇深度限制。因此,C是模型的超參數,通過交叉驗證調節;決策樹的max_depth同理。
正則化參數調整
讓我們確定上述例子中正則化參數C的最優值。我們可以使用LogisticRegressionCV——網格搜索參數後進行交叉驗證。這個類是專門為邏輯回歸設計的。對任意模型而言,使用GridSearchCV或RandomizedSearchCV,或者特殊的超參數優化算法,比如hyperopt中實現的算法。
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_
結果:
array([198.8827857])
讓我們看下超參數C是如何影響模型的質量的:
plt.plot(c_values, np.mean(logit_searcher.scores_[1], axis=0))
plt.xlabel('C')
plt.ylabel('Mean CV-accuracy');
最後,選擇C值「最佳」(C值較大加重算力負擔,也容易導致過擬合)的區域:
plt.plot(c_values, np.mean(logit_searcher.scores_[1], axis=0))
plt.xlabel('C')
plt.ylabel('Mean CV-accuracy');
plt.xlim((0,10));
回憶一下,這些曲線被稱為驗證曲線。之前我們手工創建了驗證曲線,不過sklearn有構建這些曲線的特殊方法,我們以後將直接使用sklearn的相應方法。
分析IMDB影評
現在讓我們做個小練習!我們想要解決IMDB影評的二元分類問題。我們有一個訓練集,其中包含標記好的影評,12500條好評,12500條差評。直接開始機器學習並不容易,因為我們並沒有矩陣X;我們需要準備它。我們將使用一個簡單方法:詞袋模型。影評的特徵將由整個語料庫中的每個詞的出現情況表示。語料庫是所有用戶影評。下圖展示了這一思路:
%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
數據集可通過以下地址下載:
http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
數據集的簡要描述: ai.stanford.edu/~amaas/data/sentiment/
# 請修改文件路徑
reviews_train = load_files("/Users/y.kashnitsky/Yandex.Disk.localized/ML/data/aclImdb/train")
text_train, y_train = reviews_train.data, reviews_train.target
看看訓練集和測試集中各有多少條數據:
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]
# 請修改文件路徑
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]
下面是影評的一些例子。
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.'
上面這條影評是差評還是好評?
y_train[1]
0
沒錯,和我們預料的一樣,這是一條差評。
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
是好評。
單詞簡單計數
首先,我們使用CountVectorizer創建包含所有單詞的字典。
cv = CountVectorizer()
cv.fit(text_train)
len(cv.vocabulary_)
74849
如果你查看下「單詞」的樣本(我們還是稱作token吧),你會發現我們省略了文本處理的許多重要步驟(自動化文本處理自身就可以成為一個獨立的文章系列)。
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_train = cv.transform(text_train)
X_train
<25000x74849 sparse matrix of type '<class 'numpy.int64'>'
with 3445861 stored elements in Compressed Sparse Row format>
讓我們看下這一轉換是如何進行的。
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)
接下來,我們對測試集應用同樣的操作。
X_test = cv.transform(text_test)
下一步是訓練邏輯回歸。
%%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
讓我們看下訓練集和測試集上的精確度。
round(logit.score(X_train, y_train), 3), round(logit.score(X_test, y_test), 3)
(0.998, 0.86699999999999999)
模型的係數可以美觀地顯示。
def visualize_coefficients(classifier, feature_names, n_top_features=25):
# 獲取絕對值較大的係數
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())
為了讓我們的模型更好,我們可以優化邏輯回歸的正則化係數。我們將使用sklearn.pipeline,因為CountVectorizer只應該應用於訓練數據(為了避免「偷窺」測試集和在測試集上計算詞頻)。在這一情形下,pipeline確定正確的行動序列:應用CountVectorizer,然後訓練邏輯回歸。
%%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,以及相應的交叉驗證評分:
grid_logit.best_params_, grid_logit.best_score_
({'logisticregression__C': 0.10000000000000001}, 0.88527999999999996)
plot_grid_scores(grid_logit, 'logisticregression__C')
驗證集上的結果:
grid_logit.score(text_test, y_test)
0.87907999999999997
現在讓我們用隨機森林來分類。我們看到,邏輯回歸事半功倍。
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問題
現在讓我們看一個線性模型表現不佳的例子。
線性分類定義的是一個非常簡單的分界平面——一個超平面。最著名的超平面(或直線)無法無誤差地切分的玩具例子是XOR問題。
XOR即異或,其真值表如下:
XOR是一個簡單的二元分類問題,其中兩個分類呈對角交叉分布。
# 創建數據集
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);
顯然,我們無法劃出一條直線無誤差地將兩個分類分開。因此,邏輯回歸在這一任務上的表現很差。
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)
# 為網格上的每個數據點繪製判定函數
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")
然而,如果我們給出多項式特徵作為輸入(這裡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")
這裡,邏輯回歸仍然生成了一個超平面,不過是6維特徵空間(1、x1、x2、x12、x1x2、x22)中的超平面。當我們將這個超平面投影到原特徵空間(x1、x2)時,分界是非線性的。
在實踐中,多項式特徵確實有幫助,不過顯式創建它們在算力上是低效的。使用核技巧的SVM要快很多。在這一方法中,只計算高維空間中目標之間的距離(由核函數定義),而不用生成大量特徵組合。
現在,我們對模型驗證、交叉驗證、正則化已經有所了解,讓我們考慮一個更大的問題:
如果模型的質量不盡人意,該怎麼辦?
我們應該讓模型更複雜還是更簡單?
我們應該加入更多特徵嗎?
我們是否只是需要更多數據用於訓練?
這些問題的答案並不明顯。特別是,有時候一個更複雜的模型會導致表現退化。另一些時候,增加新的觀測並不會帶來可以觀察得到的變化。事實上,做出正確決定,選擇正確方法,從而改進模型的能力區分了優秀的專業人員和糟糕的專業人員。
讓我們回頭看看電信運營商離網率數據集。
%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
我們將使用隨機梯度下降訓練邏輯回歸。我們將在之後的課程專門討論梯度下降。
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曲線,查看下不同正則化參數導致的模型在訓練集和參數集上的不同表現。
趨勢相當明顯,這樣的趨勢在其他問題中也同樣常見:
需要多少數據?
數據是越多越好。但我們如何理解新數據是否在任何情況下都有幫助呢?例如,如何評估花費N來加倍數據集是否合理呢?
由於新數據可能無法取得,合理的做法是改變訓練集的大小,然後看解答的質量如何依賴於訓練數據的數量。這樣我們就得到了學習曲線。
這個想法很簡單:我們將誤差顯示為訓練中使用的樣本數的函數。模型的參數事先固定。
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()
讓我們看看線性模型的結果。我們將把正則化係數設定為較大的數。
plot_learning_curve(degree=2, alpha=10)
一個典型的狀況:對於少量數據而言,訓練集和交叉驗證集之間的誤差差別相當大,這暗示了過擬合。同樣的模型,不過使用大量數據,誤差「收斂」,暗示了欠擬合。
如果我們加入更多數據,訓練集的誤差不會增加。另一方面,測試集上的誤差也不會下降。
所以,我們看到誤差「收斂」,加入新數據無濟於事。事實上這個情況對業務來說很重要。我們可能把數據集大小增大10倍,但是,如果我們不改變模型的複雜度,數據的增加沒有幫助。因此「設定一次,使用十次」的策略可能無法奏效。
如果我們將正則化係數降低到0.05,會怎麼樣?
我們看到了好兆頭——曲線逐漸收斂,如果我們移動到更右,也就是加入更多數據,我們可以進一步改善模型在驗證集上的表現。
plot_learning_curve(degree=2, alpha=0.05)
如果我們把alpha設為10-4,讓模型更複雜,會出現什麼情況?
我們看到了過擬合——在訓練集和驗證集上,AUC都下降了。
plot_learning_curve(degree=2, alpha=1e-4)
構建這些曲線可以幫助我們理解朝哪個方向走,如何恰當地為新數據調整模型複雜度。
學習曲線和驗證曲線的一些結論:
訓練集上的誤差本身不能說明模型的質量。
交叉驗證誤差顯示了模型對數據擬合得有多好(數據的現有趨勢)同時保留了多少對新數據的概括能力。
驗證曲線是一個根據模型複雜度顯示訓練集和驗證集上的結果的圖形:
學習曲線是一個根據觀測數量顯示訓練集和驗證集上的結果的圖形:
Kaggle競賽頁面:
https://www.kaggle.com/c/catch-me-if-you-can-intruder-detection-through-webpage-session-tracking2
我們將解決一個入侵者檢測問題(通過分析上網用戶的行為)。這是一個結合了數據分析和行為心理學的複雜而有趣的問題。例如,Yandex根據用戶的行為模式解決郵箱入侵檢測問題。概括起來,入侵者的行為模式可能和郵箱所有者不一樣:
所以我們可以檢測到入侵者,將其扔出郵箱,要求通過簡訊驗證碼認證身份。
Google Analytics研發了類似的技術,並發表了相應的論文。你可以通過搜索「Traversal Pattern Mining」(遍歷模式挖掘)和「Sequential Pattern Mining」(序列模式挖掘)了解更多關於這一主題的信息。
在這一競賽中,我們將解決一個類似的問題:我們的算法將分析特定用戶頻繁訪問的網站序列,預測這個人是不是一個名為Alice的用戶還是一個入侵者(其他人)。我們將使用ROC-AUC作為指標。
數據下載和轉換
如果你之前沒有註冊過Kaggle帳號的話,註冊一下。然後到競賽頁面,下載數據。
首先,加載訓練集和測試集。探索下數據:
%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
# 加載訓練集和測試集
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')
# 轉換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)
# 據時間排序數據
train_df = train_df.sort_values(by='time1')
# 查看下訓練集的開始幾行
train_df.head()
訓練集包含以下特徵:
用戶會話的選取方式保證這些會話不超過半小時或包含超過十個網站,也就是說,一旦一個用戶訪問過了十個網站,或者會話時長超過了半小時,我們就認為這一會話結束了。
表中有一些空值,意味著某些會話包含不到十個網站。將這些空值替換為0,並將列類型改為整數。同時,加載網站字典,看看是什麼樣的:
# 將site1、……、site10列類型轉為整數,同時用零填充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')
# 加載網站字典
with open(r"../../data/site_dic.pkl", "rb") as input_file:
site_dict = pickle.load(input_file)
# 創建字典的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
簡單起見,我們將只使用會話中訪問的站點(不考慮訪問時長)。這一數據選擇背後的依據是:Alice有她偏愛的站點,我們在會話中看到更多這些站點,這一會話是Alice的會話的可能性就越高,反之亦然。
讓我們準備下數據。首先,我們從訓練集中排除目標變量,這樣,訓練集和測試集就有了相同數量的列,我們可以將其合併為一個dataframe,對其一併進行轉換。
# 目標變量
y_train = train_df['target']
# 合併為一個dataframe
full_df = pd.concat([train_df.drop('target', axis=1), test_df])
# 分割訓練集和測試集的索引
idx_split = train_df.shape[0]
只保留dataframe的site1, site2, ..., site10。
# 訪問過的網站索引構成的dataframe
full_sites = full_df[sites]
full_sites.head()
會話是網站索引的序列,這一數據表示不便於通過線性方法處理。我們需要將其轉換為如下表示形式:每個網站對應一個特徵(列),其值為會話中訪問該網站的次數。
# 索引序列
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:]
如果你明白剛剛發生了什麼,那你可以直接跳到下一節(也許你也可以處理邏輯回歸?),否則,讓我們一起來搞明白髮生了什麼。
稀疏矩陣
讓我們估計一下,在上面的例子中,儲存我們的數據需要多少內存。合併後的dataframe包含336k樣本,每個樣本包含48k整數特徵。因此我們大致需要的內存為:
336K * 48K * 8 bytes = 16G * 8 Bytes = 128 GB
顯然,凡夫俗子沒有這麼多內存(嚴格來說,Python可能允許你創建這樣一個矩陣,但對其進行任何操作都不容易)。一個有趣的事實是,我們的矩陣的大多數元素值為零。如果我們計算非零元素,那麼大概有一百八十萬,所佔比例略高於所有矩陣元素的0.01%. 這樣一種大多數元素為零的矩陣,稱為稀疏矩陣,零元素數量和總元素的比例則稱為矩陣的稀疏度。
scipy.sparse庫可用於處理稀疏矩陣,參考它的文檔了解稀疏矩陣的類型,如何處理,何時使用稀疏矩陣最高效。注意,稀疏矩陣只包含非零元素。最後,讓我們看看稀疏矩陣佔用的內存大小(顯然,稀疏矩陣顯著節省了內存):
# 稀疏矩陣佔用多少內存?
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
讓我們通過一個迷你的例子探索下這一矩陣是如何形成的。假設我們的用戶會話表是這樣的:
總共有3個會話,每次會話不超過3個站點。用戶訪問的不同站點總數為4(表中的1到4表示這4個站點)。讓我們假定這4個站點是:
vk.com
habrahabr.ru
yandex.ru
ods.ai
如果用戶在會話時訪問了不到三個站點,後面的值將為零。我們想要將原dataframe轉換為每個會話顯示某一特定站點的訪問數,如下表所示:
為此,我們使用csr_matrix ((data, indices, indptr))創建一個頻率表。這裡,我們顯式地設定所有參數,讓你能更清楚地理解這一過程:
# 創建由1組成的列表,長度等於原dataframe中元素的數量(9)
data = [1] * 9
# 網站id
indices = [1, 0, 0, 1, 3, 1, 2, 3, 4]
# 切分行(會話)的索引
indptr = [0, 3, 6, 9]
# 將上述三個變量聚合為一個元組,然後構建矩陣
# 顯示矩陣時,將其轉為通常的「密集」矩陣
csr_matrix((data, indices, indptr)).todense()
matrix([[2, 1, 0, 0, 0],
[0, 2, 0, 1, 0],
[0, 0, 1, 1, 1]])
你也許注意到了,所得矩陣的列數目不是四(不同網站的總數),而是五。第零列是額外增加的列,顯示每次會話少訪問了幾個站點(在我們的迷你例子中,會話的長度為3)。這一列是多餘的,需要從dataframe中移除。
使用稀疏矩陣的另一個好處是,有為其特製的矩陣操作和機器學習算法實現,有時能通過利用數據結構的特點顯著加速操作。邏輯回歸也適用。現在,萬事俱備,我們可以創建第一個模型了。
訓練第一個模型
我們將使用sklearn的邏輯回歸實現(默認參數)。數據集的前90%用於訓練(數據集按時間排序),剩餘10%用於驗證。
def get_auc_lr_valid(X, y, C=1.0, seed=17, ratio = 0.9):
# 將數據分為訓練集和驗證集
idx = int(round(X.shape[0] * ratio))
# 訓練分類器
lr = LogisticRegression(C=C, random_state=seed,
solver='lbfgs', n_jobs=-1).fit(X[:idx, :], y[:idx])
# 為驗證集做出預測
y_pred = lr.predict_proba(X[idx:, :])[:, 1]
# 計算質量
score = roc_auc_score(y[idx:], y_pred)
return score
%%time
# 選擇訓練集
X_train = full_sites_sparse[:idx_split, :]
# 在驗證集上計算量度
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
第一個模型在驗證集上的表現接近0.92 ROC-AUC。讓我們將其作為第一條基線(作為一個開始)。為了在測試集上進行預測,我們需要在整個訓練集上再次訓練模型(到此為止,我們的模型只使用了數據的一部分進行訓練),這將增加模型的概括能力:
# 將預測寫入文件的函數
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)
# 在整個訓練數據集上訓練模型
# 為了可重複性,將random_state設為17
# 顯式設置C=1(這是默認值)
lr = LogisticRegression(C=1.0, solver='lbfgs',
random_state=17).fit(X_train, y_train)
# 在測試數據集上進行預測
X_test = full_sites_sparse[idx_split:,:]
y_test = lr.predict_proba(X_test)[:, 1]
# 寫入預測結果至提交文件
write_to_submission_file(y_test, 'baseline_1.csv')
如果你遵循這些步驟,並將答案上傳到競賽頁面,你將在公開排行榜上取得ROC AUC = 0.91707的成績。
你的任務是通過特徵工程、特徵縮放和正則化進一步改善模型。你將首先嘗試添加一些明顯的特徵(比如瀏覽網站的時刻,會話中的網站數目,等等)。我們鼓勵你在課程的學習過程中嘗試新的想法和模型,並參與競賽——這很有趣!
截止日期:March 11, 23:59 CET
I. Goodfellow、Y. Bengio、A. Courville所著《Deep Learning》(深度學習)一書提供了緊湊而出色的線性模型綜述。
基本上每本ML教材都涉及線性模型。我們推薦C. Bishop的《Pattern Recognition and Machine Learning》和K. Murphy的《Machine Learning: A Probabilistic Perspective》。
如果你打算從統計學的視角概覽線性模型,可以看下T. Hastie、R. Tibshirani、J. Friedman的《The elements of statistical learning》。
P. Harrington的《Machine Learning in Action》將引導你完全使用Python實現經典ML算法。
scikit-learn庫。scikit-learn的開發者致力於編寫極為清晰的文檔。
Scipy 2017 scikit-learn教程(Alex Gramfort、Andreas Mueller)。
MTH594課程 Advanced data mining: theory and applications包含很多非常好的材料。
GitHub倉庫rushter/MLAlgorithms裡有許多ML算法的實現,其中包括線性回歸和邏輯回歸。
歡迎留言分享其他資源。
原文地址:https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-4-linear-classification-and-regression-44a41b9b5220