背景
小編最近購買了《機器學習之路》一書,發現這本書也算有特色,沒有講複雜的數學原理和推導,很適合想快速進入這個行業,能儘快的會去使用模型的朋友。
為對作者表示尊重,所以在這裡向大家推薦購買正版圖書。
模型百科——LR
LR:邏輯分類(Logistic Classification),是一種線性分類模型
理解LR
把每個特徵對分類結果的“作用”加起來——這就是線性模型。
邏輯分類(Logistic Classification)是一種線性模型,可以表示為y = w * x + b,其中w是訓練得到的權重參數(Weight);x是樣本特徵數據;b是偏置(Bias)。需要說明的是有些資料中邏輯分類也叫作邏輯迴歸(Logistic Regression),但它本身是用作分類問題的。
邏輯分類模型預測一個樣本分為三步:
計算線性函數
從分數到概率的轉換
從概率到標籤的轉換
第一步:線性函數
這一節繼續以IRIS花卉數據集為例講解,邏輯分類的第一步:對於某個輸入樣本x,讓它通過一個線性函數,輸出一個數值向量S。S向量的長度和分類的類別數量一致,其中向量的每個值是模型對當前樣本是否屬於該類別的“打分”,見下圖。
import numpy as np
對於邏輯分類,訓練模型的目標就是找到合適的w和b,讓輸出的預測值變得可靠。舉個簡單例子,下面將輸入的特徵數值打印在平面座標系上,那麼線性表達式的w和b就好比一條直線的斜率和截距,這兩個參數調整直線的位置,讓直線變得儘可能接近所有的樣本點。
第二步:將分數變成概率
首先,我們將輸入數據經過線性函數,得到一個分數向量,這個向量如何變換成標籤化的結果呢?
答案是我們將線性表達式的輸出結果“分數”,先變換成“概率”,再對應標籤——概率最高的類別標籤就是樣本的標籤。完成分數到概率變換的函數可以有很多,常用的是sigmoid函數和softmax函數,分別適用於不同的使用場景。
sigmoid函數
sigmoid函數適用於只對一種類別進行分類的場景,通過設置函數閾值(Shrehold),當sigmoid函數輸出值大於閾值,則認為“是”這一類別;否則認為“不是”這一類別。
def sigmoid(s):
直觀地講,sigmoid就做了兩件事:
將輸入的“分數”的範圍映射在(0, 1)之間
以“對數”的方式完成到(0, 1)的映射,凸顯大的分數的作用,使其輸出的概率更高;抑制小分數的輸出概率
函數閾值是一個可調節的參數,一般根據模型的成績以及任務的需求調試。
softmax函數
softmax函數就是sigmoid函數的“多類別”版本,可以將輸出值對應到多個類別標籤,概率值最高的一項就是模型預測的標籤。
def softmax(s):
同樣,softmax就做了兩件事:
將輸入的“分數”的範圍映射在(0, 1)之間,並且所有分數的和為1
以“對數”的方式完成到映射,凸顯其中最大的分數並抑制遠低於最大分數的其他數值
由於IRIS的標籤是三個類別,這裡選擇softmax函數作為分數-概率轉化函數。我們可以通過代碼側面感覺一下softmax函數的作用效果。
1. 隨機模擬一個scores輸出,接著把softmax函數plot出來觀察:當x的數值越大,第一個分類的概率值越高,其它的越低
#WeChat: abu_quant%matplotlib inline # 輸出圖畫到notebook端
[<matplotlib.lines.Line2D at 0x1175f4810>,
2. 將分數擴大/縮小100倍,觀察輸出的概率值變化
scores = np.array([2.0, 1.0, 0.1])
[ 0.65900114 0.24243297 0.09856589]
分數擴大100倍後,概率值大的越大,小的越小;即:分類器對分類的結果更加“自信”;反之縮小100倍後,分類器顯得對分類的結果很“猶豫”。
第三步:從概率到類別
到這裡就很簡單了,我們選擇概率最高的類別作為預測的類別標籤。
總結一下,邏輯分類分為三步完成預測樣本分類的任務:
經過線性函數,將x變換成在不同類別上的預測“分數”S
通過Softmax函數(我們會在下文中有更詳細的介紹),將每個類別上的分數轉換成對應的概率P。概率值越高,就預測樣本越可能是這種類別
將概率向量P變換成類別y,選出概率值最高的那個維度就是模型預測的類別
簡而言之,邏輯分類的預測:線性函數 -> 概率 -> 類別
使用LR
下面將通過“泰坦尼克號生成預測”這一實驗案例,介紹使用LR模型時的一些數據、特徵的處理技巧。
泰坦尼克號生成預測
泰坦尼克號的沉沒是歷史上最臭名昭著的沉船之一。 1912年4月15日,在它的首航中,泰坦尼克號在與冰山撞擊後沉沒,在2224名乘客和船員中喪生1502人。 這一聳人聽聞的悲劇震撼了國際社會,併為船舶制定了更好的安全條例。海難導致這種生命損失的原因之一是乘客和船員沒有足夠的救生艇。 雖然有一些運氣涉及倖存下沉,一些人群比其他人更可能生存,如婦女,兒童和上層階級。
在這個例子中,我們的任務是完成對什麼樣的人可能生存的分析,應用機器學習的工具來預測哪些乘客倖免於悲劇。
第一步:任務描述:
樣本數:891名乘客信息(小數據集)
原始特徵數:11
PassengerId:乘客id
Pclass:幾等艙(1 = 一等艙,2 = 二等艙,3 = 三等艙)
Name:名字
Sex:性別
Age:年齡
SibSp:兄弟姐妹/配偶的數量
Parch:父母/孩子的數量
Ticket:機票號碼
Fare:票價
Cabin:客艙
Embarked:登船的港口(C = 瑟堡,Q = 皇后鎮,S = 南安普頓)
目標:預測Survived(1 = 生存,0 = 死亡)
第二步:觀察數據集
備註:從kaggle下載的原始數據集文件包含train.csv和test.csv兩部分。train.csv用作模型訓練,test.csv的樣本沒有標籤,將模型預測test.csv樣本的結果提交到kaggle平臺,kaggle會給出成績。下文出於講解方便的目的,將忽視test.csv,直接在train.csv分割訓練-測試集,顯式觀測成績。
我們關心的要點有這麼幾個:
採集的樣本是否有缺失數據
不同類別的樣本數量分佈是否基本均勻
#WeChat: abu_quantimport pandas as pd # pandas是python的數據格式處理類庫
<class 'pandas.core.frame.DataFrame'>
data_train.groupby('Survived').count()
PassengerId | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
Survived | |||||||||||
0 | 549 | 549 | 549 | 549 | 424 | 549 | 549 | 549 | 549 | 68 | 549 |
1 | 342 | 342 | 342 | 342 | 290 | 342 | 342 | 342 | 342 | 136 | 340 |
可以看到,樣本類別比例549:342,略不均衡,但差距不大。同時注意到“Age”、“Cabin”、“Embarked”維度的數據有缺失,在開始模型訓練之前,我們需要處理這一情況。
第三步:數據預處理
這裡要處理特徵的輸入數據,使其適合模型去訓練。
首先,觀察並挑選特徵。
data_train.head(3)
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
“Survived”是生存預測的數據標籤。特徵“PassengerId”、“Ticket”、“Name”維度下的數據基本是完全隨機的,這些特徵應該和任務的目標生存預測不相關。
兩類特徵
剩下的輸入特徵分為兩類:一類是有“數值”意義的數據,如:年齡“Age”、票價“Fare”、兄弟姐妹/配偶的數量“SibSp”、父母/孩子的數量“Parch”;另一類是有“類別”意義的數據,比如幾等艙“Pclass”、性別“Sex”、登船港口“Embarked”、客艙“Cabin”,這種特徵下的數值是沒有數量意義的,如特徵“Pclass”(幾等艙)下的樣本數值:“1”和“3”,並不是表示數量或者長度的3倍關係,而僅僅類別標號。
數值意義的特徵
類別意義的特徵
當這些數據流入模型時,模型並不知道哪些是數量意義的數值,哪些是類別意義的數值,所以我們需要把這兩類特徵需要分開處理一下。
處理數據缺失
先處理“數值”意義的特徵數據,第一個問題是“Age”的數據缺失。補全缺失數據的唯一要點就是:儘量保持原始信息狀態。處理手段視情況而定,總共這麼幾種:
扔掉缺失數據
按某個統計量補全;統計量可以是定值、均值、中位數等
拿模型預測缺失值
對於大數據集的小比例缺失,可以直接扔掉這部分數據樣本,因為樣本充分,信息不會損失多少。但泰坦尼克號數據樣本總共891條,每條數據樣本對模型的訓練都是珍貴的,所以我們希望補全這部分信息而不是丟棄。
data_train.info()
<class 'pandas.core.frame.DataFrame'>
“年齡”特徵缺失約20%,可以通過一些統計圖形直觀觀察數據分佈,如下面拿使用計直方圖觀察原始數據分佈:
%matplotlib inline
<matplotlib.axes._subplots.AxesSubplot at 0x113e6ff90>
上圖中,直方圖的橫軸是年齡的數值,每個直方塊的寬度(bins)代表在一個年齡範圍,如Age:0-5這樣。縱軸是歸一化以後的數量(即該區域的樣本數量除以總數),直方塊就是統計樣本落在每個區間的數量比例。
按均值函數填充後,觀察分佈:
def set_missing_ages(p_df):
<matplotlib.axes._subplots.AxesSubplot at 0x10f678950>
我們無視了數據內在的聯繫,直接用均值補充數據,這種拍腦袋的做法自然不合理,統計的擬合曲線明顯變形了。於是,下面取出一些感覺上和“年齡”相關的特徵,比如船票、家庭成員數量等,扔進線性迴歸模型中,通過模型預測年齡看看:
from abupy import AbuML
<matplotlib.axes._subplots.AxesSubplot at 0x114fc03d0>
下面直觀對比下在最後的模型中,使用不同的數據填充方式造成的成績對比:
data_train_fix1 = set_cabin_type(data_train_fix1)
accuracy mean: 0.798073714675
使用 setmissingages2 ,迴歸模型預測填充的模型成績:
data_train_fix2 = set_cabin_type(data_train_fix2)
accuracy mean: 0.809183974577
歸一化數值數據
對於兩類特徵數據混雜的問題,我們可以看到,票價“Fare”、年齡“Age”這兩個數值特徵和其它特徵明顯不在同一可以比較的尺度上。回顧上一節所說的梯度下降對輸入數據的要求,我們希望所有的輸入數據在差不多同一尺度上可比較,所以下面歸一化這兩個維度的數據。
import sklearn.preprocessing as preprocessing
處理類別意義的特徵
客艙號“Cabin”的缺失程度太嚴重了,在整體樣本很小,204/891 = 77%的缺失程度的數據不可能有效恢復。我們的靈感是客艙號信息“Cabin”雖然無法提取,但可以將“有沒有在客艙”這一信息提取出來。
def set_cabin_type(p_df):
對於類別意義的特徵,數值大小沒有任何數量上的意義。就像前面提到的,對於“Pclass”,1和3並不是表示數量關係,而是類別標號。對於類別標號有意義的只有“是”這一類和“不是”這一類。所以,對於所有的類別意義的特徵,下面將按類別標號重新建立新的特徵,特徵的數值只有1和0,標識樣本“是”這一類或者“不是”這一類。
dummies_pclass = pd.get_dummies(data_train['Pclass'], prefix='Pclass')
Pclass_1 | Pclass_2 | Pclass_3 | |
---|---|---|---|
0 | 0.0 | 0.0 | 1.0 |
1 | 1.0 | 0.0 | 0.0 |
2 | 0.0 | 0.0 | 1.0 |
對於特徵“Embarked”,缺失的數據樣本只有兩條,對於這兩條數據,在按特徵標籤展開時,每個特徵標籤數值都為0。
dummies_embarked = pd.get_dummies(data_train['Embarked'], prefix='Embarked')
Embarked_C 0.0
預處理其它類別數據,把“Sex”的文本換成數字類別標號。
dummies_sex = pd.get_dummies(data_train['Sex'], prefix='Sex')
Sex_female | Sex_male | |
---|---|---|
0 | 0.0 | 1.0 |
1 | 1.0 | 0.0 |
2 | 1.0 | 0.0 |
接下來把處理好的數據維度合併進去,不需要的數據維度扔掉:
df = pd.concat([df, dummies_embarked, dummies_sex, dummies_pclass], axis=1)
到這裡為止,輸入數據就預處理好了,看下模型將要用哪些特徵。
# 選擇哪些特徵作為訓練特徵
Survived | SibSp | Parch | Age_scaled | Fare_scaled | Embarked_C | Embarked_Q | Embarked_S | Sex_female | Sex_male | Pclass_1 | Pclass_2 | Pclass_3 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | -0.592481 | -0.502445 | 0.0 | 0.0 | 1.0 | 0.0 | 1.0 | 0.0 | 0.0 | 1.0 |
接入模型看看成績:
# WeChat: abu_quantfrom abupy import AbuML
accuracy mean: 0.79919731018
構造非線性特徵
我們希望邏輯分類模型可以做得更好。前面提到,邏輯分類是一個”線性模型“,所謂線性模型就是把特徵對分類結果的作用加起來,也就是說線性模型能表示類似於y=x1+x2關係的表達式(y表示分類結果,x1、x2表示特徵對分類的作用),但線性模型無法表示一些非線性的關係如y=x1*x2。所以我們打算人工構造一些新的特徵,彌補線性模型對非線性表達式表達能力的不足。
特徵的非線性的表達式可以分為兩類:
用於表達“數值特徵”本身的非線性因素
用於表達特徵與特徵之間存在非線性關聯,並且這種關聯關係對分類結果有幫助
第一種情況是說特徵對目標類別的作用不是線性關係。比如兩個樣本的特徵數值是1和3,對應的,這個特徵對分類產生的作用其實是1^2和3^2,或者ln1和ln3。這類問題的本質是數字內在的線性數量描述並不符合真實的關係描述。
對於第一種,僅適用於數值特徵,對應的構造特徵的方式有兩種:多項式化和離散化。多項式構造指的是將原有數值的高次方作為特徵;數據離散化是指將連續的數值劃分成一個個區間,以數值是否在區間內作為特徵。高次方讓數值內在表達變得複雜,可描述能力增強;而離散則是讓模型來擬合逼近真實的關係描述。
舉個簡單的實現例子,對於特徵“Age”,可以構造平方特徵,也可以是否滿足Age<=10這一條件劃分區間構造出新特徵。
# 劃分區間
接著嘗試構造新特徵表達特徵與特徵之間的非線性關聯,同樣源自多項式的思路。比如:我們覺得“Pclass”數值越大越不容易生存下來,頭等艙的遇害人員應該比三等艙的更可能被照顧;同時年齡越大的人也越不容易生存下來,越小的越可能被照顧,會不會這兩個特徵之間也有一些關聯,並且這種關聯對生存預測有指導意義呢?
我們可以構造一個新特徵“Age * Class”,加入模型中。
df['Age*Class'] = data_train['Age'] * data_train['Pclass']
看下模型現在用的特徵:
# filter加入新增的特徵
Survived | SibSp | Parch | Age_scaled | Fare_scaled | Embarked_C | Embarked_Q | Embarked_S | Sex_female | Sex_male | Pclass_1 | Pclass_2 | Pclass_3 | Child | Age*Age_scaled | Age*Class_scaled | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | -0.592481 | -0.502445 | 0.0 | 0.0 | 1.0 | 0.0 | 1.0 | 0.0 | 0.0 | 1.0 | 0 | -0.636573 | 0.031376 |
新加入的這些特徵,對模型的表現是否有提升呢?
train_np = train_df.as_matrix()
accuracy mean: 0.804752298264
評估特徵作用
一般而言,機器學習中看一個新特徵是否發揮作用,最常用的方法就是加進去看模型成績是否提升。可以同時觀察模型給特徵分配的權重,看特徵發揮作用的大小:
titanic.importances_coef_pd()
********************LogisticRegression********************
coef | columns | |
---|---|---|
0 | [-0.410298136384] | SibSp |
1 | [-0.173336882199] | Parch |
2 | [-0.208459115049] | Age_scaled |
3 | [0.165577117349] | Fare_scaled |
4 | [0.0] | Embarked_C |
5 | [0.0] | Embarked_Q |
6 | [-0.405822408349] | Embarked_S |
7 | [1.99640288087] | Sex_female |
8 | [-0.715926302128] | Sex_male |
9 | [0.663404977168] | Pclass_1 |
10 | [0.0] | Pclass_2 |
11 | [-1.03255600599] | Pclass_3 |
12 | [1.28640364062] | Child |
13 | [0.0] | Age*Age_scaled |
14 | [-0.129278692295] | Age*Class_scaled |
可以看到,在訓練好的模型中,特徵“Child”有效發揮了作用,而“Age*Class”、“Age*Age”沒有什麼用。
對於一些特定的應用場景中,模型的訓練非常耗時,可能幾天甚至更長。這些應用場景中,需要一些新的數學方法估計新特徵是否有效。機器學習中有很多方法評估特徵在模型中發揮的作用,對於這些方法,只要知道它們基本上都是通過某種數學公式,計算特徵和預測值之間的相關性就可以了。
構造特徵的數學意義
通過人工構造非線性特徵,可以彌補線性模型表達能力的不足。這一手段之所以能夠生效,背後的原因是:低維的非線性關係可以在高維空間線性展開。
解釋這一觀點,讓我想起霍金先生的鉅著《時間簡史》中提及的一個例子:“這正如同看一架在非常多山的地面上空飛行的飛機。雖然它沿著三維空間的直線飛,在二維的地面上它的影子卻是沿著一條彎曲的路徑。——飛機影子的運動軌跡在二維地表上看到的是一條非線性的曲線,起伏不定,很難用函數表示;但在三維空間中,其運動軌跡僅僅是一條筆直的直線而已,一個簡單的線性函數就可以說明。也就是說在二維空間中看到的複雜表達式,當增加一個維度,其表達式可能就變得非常簡單了。
同樣的道理,我們可以增加新的特徵維度,讓分類任務背後的數學表達式變得更簡單,讓分類模型更容易挖掘出信息——這正是構造新特徵有意義的地方:增加特徵維度,構造出模型表達不出來的內在表達式。對於邏輯分類模型而言,就是通過增加新的非線性特徵,完成特徵維度的擴展,構造出模型表達不出的非線性的內在關係。
邏輯分類因為是線性模型,原型簡單,所以有著訓練速度快、易分佈式部署等特點。現在業界對邏輯分類仍然有著廣泛的應用,尤其適合那些數據海量、特徵高維並且稀疏的應用場景。比如在一些涉及海量用戶的個性化推薦任務中,海量數據上,把每個用戶ID作為一個特徵,使用邏輯分類就可以快速有效地完成任務目標。