機器學習代碼實戰:使用邏輯回歸幫助診斷心臟病
概述
本文向讀者介紹邏輯回歸的基本概念,以及使用邏輯回歸來學習患者病理數據來建立心臟病預測模型。在上一篇文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 中,已經向讀者介紹了算法線性回歸和使用實例,以及回歸、最小二乘法和梯度下降等概念,本文在上篇文章的基礎上,介紹邏輯回歸,它是一種廣義的線性回歸,它能解決很多二分類問題,例如某些疾病的預測中,根據大量現有數據(比如患者的身體檢查指標、血液指標和 X 光片等)建立模型,能夠根據患者數據來預測患有某種疾病的概率。本文中涉及到與文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 重復的概念和代碼將不再重復闡述。
本文應用場景
本文將使用一批患者的心臟檢查樣本數據(附件中的 StatlogHeart.csv 文件),讓程序學習這些樣本數據,來得到一個較好的心臟病預測模型。下面的表 1 中摘取了一小部分這些患者心臟檢查樣本數據:
表 1. 部分患者心臟檢查樣本數據
在表 1 中,每行是一個樣本,除最后一列外,前面的列都是患者的相關檢查數據,最后一列是醫生檢查結果,1 代表無心臟病,2 代表患有心臟病,我們要讓機器學習這些樣本后得到一個模型,這個模型可以輸入樣本表格之外的數據,預測出患者是否會患有心臟病及其概率大小。有很多實現這樣的二分類預測的模型算法,如邏輯回歸、支持向量機和神經網絡等,本文通過比較基本的邏輯回歸算法來實現,后續文章中會介紹通過神經網絡等算法來解決稍微復雜的問題。
邏輯回歸簡介
首先我們簡單介紹下邏輯回歸(Logistic Regression),邏輯回歸是機器學習中一個非常常見的模型,在實際生產環境中也會經常被使用,常用于數據挖掘、疾病自動診斷和經濟預測等領域。例如,探討引發疾病的危險因素,并根據危險因素預測疾病發生的概率等。邏輯回歸最常用于分類,主要用于兩分類問題(即輸出只有兩種,分別代表兩個類別),并且能夠指明每個分類事件發生的概率。通過表 1 的數據,我們的目標是建立一個邏輯回歸模型,如下圖 1 所示:
圖 1. 邏輯回歸
當輸入患者的心臟檢查數據 x,能夠預測其是否患有心臟病及其概率,假設其模型公式為 y = f(x),則對于邏輯回歸,通過函數 f 運算后,y 應該是一個介于 0 和 1 之間的值,越趨于 1,則可能是有心臟病的,并且其對應的 y 值就是發生的概率,反之越趨于 0,則是沒有心臟病的事件發生的概率大。
建立邏輯回歸模型的步驟
我們按照以下三個基本步驟,來分析和建立邏輯回歸模型:
- 構造假設函數(即 H 函數)
- 構造損失函數(即 J 函數)
- 通過某種方法使得損失函數最小,并求得此時的參數θ
構造假設函數
我們的目標是類似預測心臟病發生的概率這樣的二分類模型,即要么發生(發生概率趨于 1),要么不發生(發生概率趨于 0)。Sigmoid 函數就能很好的實現這個目的,Sigmoid 函數形式為:
這個函數的圖形如下圖 2 所示,是一個 S 型曲線,隨著自變量 z 的變化,因變量 g 的值在 0 到 1 之間。
圖 2. Sigmoid 函數曲線
Sigmoid 函數將輸入 z 轉化為一個 0 到 1 之間的數,正好對應為概率的值,g(z)越趨近于 1,表示結果為 1 的概率越大,且概率值就等于 g(z),反之,當 g(z)越趨近于 0,表示結果為 0 的概率越大,且概率值等于 1- g(z)。有了 Sigmoid 函數,我們可以構造預測函數了,在上一篇文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 中,我們介紹了有多個自變量的線性回歸的公式可表示為:
而本文介紹的邏輯回歸,同樣有多個自變量 x,即患者的各項檢查數據,以及一個偏移量 b,但由于要實現二分類,不能像線性回歸那樣直接將 x 與參數θ的點積作為結果,而是將點積作為自變量帶入到 Sigmoid 函數中計算出 y 值,則計算出的結果為 0 到 1 之間的數,即概率的值。將偏移量 b 合并到參數θ中用 表示,并且多加一個對應的自變量
(
恒等于 1),則自變量 x 與參數θ用向量分別表示為:
那么,邏輯回歸的假設函數為:
這個假設函數就是我們的模型函數了,我們希望有一組參數θ,帶入到這個模型后,使得輸入的樣本 x 經過計算后,得到的結果盡可能的趨近于實際結果 y 的值(1 或者 0),為了使這個模型預測的概率盡量準確,接下來要構造損失函數來計算模型預測的準確性。
構造損失函數
線性回歸中我們使用了最小二乘法來構造損失函數,在邏輯回歸中道理類似,我們期望通過假設函數計算后的結果與實際結果值(1 或者 0)盡可能的接近,則該假設函數的參數就是我們想要的一組參數。而與實際結果值越接近,就代表值為 1 或者 0 的概率應該盡量最大,所以將問題轉化為概率來討論,上面的假設函數 h 的值即表示結果取 1 的概率,對于結果為 1 和 0 的概率分別為:
將這兩個公式合二為一:
這里將兩個公式合二為一非常巧妙,使得原本要求 2 個概率的問題轉變為只需求 1 個概率,這樣能簡化后續的計算。如前所述,我們要求一組參數θ使得概率 P 最大,而 似然函數 就用來求參數θ的,所以對上述公式取似然函數就得到一個初始的損失函數:
為了在簡便后續求導的過程,再對上述公式取對數似然函數,這不會影響計算結果:
則最終的損失函數為:
這里的 J(θ)就是最終的損失函數,與線性回歸的區別是,這里是要求θ,使得 J(θ)即概率最大。對于曲線的極值,通過求偏導數為 0 解出θ。通過這個函數對所有樣本進行計算,得到一個概率 J(θ),這個概率也稱為損失。如前文所說,我們要讓這個概率值盡量最大能得到模型公式最好的一組參數,也就是求 max J(θ)。
梯度下降求θ
在前篇文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 中已經介紹了梯度下降(Gradient Descent),在邏輯回歸中,我們同樣可以使用梯度下降來求解最優的一組θ。具體方法,就是對上面公式 J(θ)中的參數求偏導數,得到這個參數對應維度最陡峭的方向,在乘以一個步長 a,就求出每次參數移動的大小:
完成上述公式的求導,最終得到每次迭代的參數更新公式:
其中,設 m 為樣本條數,n 為每條樣本的特征數,并且 0≤i≤m, 0≤j≤n。
公式中的 a 稱為 步長 或者 學習率 ,要注意幾點:
- 每次迭代只對參數有比較小的更新,要經過多次迭代才能得到最終理想的參數。
- 如果步長太大,可能會在最低點附近徘徊,所以訓練模型的時候要注意調整步長。
- 這個方法讓你走到的不一定是整個曲面的最低點,可能只是局部最小,所以往往要考慮起點。
向量化計算
我們的迭代公式每次只求出一個參數的更新值,向量化計算是為了使計算更加快捷,使用向量運算的方式計算梯度與新的參數,每一次迭代,可以求出所有新參數值。邏輯回歸的向量化計算與線性回歸向量化計算的公式推導非常類似,讀者可以參考前篇文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 中的向量化計算章節了解,本文只給出關鍵公式。 設列向量 A 和列向量 E 分別為:
則參數θ的向量化更新步驟為:
1.求
2.求
3.求 (
表示矩陣 x 的轉置矩陣)
這里的更新步驟和線性回歸的參數更新很相似,唯一的區別在第二步中,將 A 帶入 Sigmoid 函數 g 中。后面我們的代碼就用以上步驟來實現參數的更新。
模型的代碼實現
本章節通過教讀者自己編寫代碼實現一個邏輯回歸模型,使讀者能夠更好理解機器學習中一些關鍵點,如參數選擇的重要性,以及為什么一個邏輯回歸模型也要經過大量迭代計算。代碼使用最常用的 Java 語言來實現。和前篇文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 一樣,首先要實現如下矩陣運算,如清單 1 所示:
清單 1. 定義矩陣運算函數
// 矩陣乘積: 左矩陣 * 右矩陣 public static float[][] times(float[][] left, float[][] right)// 常數 a 與矩陣乘積: a * 矩陣 public static float[][] times(float a, float[][] matrix)
// 矩陣相加: 左矩陣 + 乘數 * 右矩陣 public static float[][] add(float[][] left, float multiplier, float[][] right)
// 轉置矩陣 public static float[][] transpose(float[][] matrix)
// 計算數量積 public static float dotProduct(float[] left, float[] right)
// 計算行列式的值 public static float detValue(float[][] det)
// 提取矩陣的余子式 public static float[][] cofactor(float[][] matrix, int row, int col, float[][] result)
// 提取伴隨矩陣 adjointMatrix public static float[][] adjointMatrix(float[][] matrix)</pre>
具體代碼請下載本文的代碼包附件中的 Matrix 類。
準備數據
數據準備的過程要提取、加工數據,從而將數據處理成模型可以使用的格式。我們的患者檢查數據表存儲在名為 StatlogHeart.csv 的文件中,每個屬性值之間以逗號分隔,最后一個值為是否有心臟病診斷結果值(1 為正常,2 為患有心臟病)。如下圖 3 中所示為該文件的一小部分:
圖 3. 數據文件部分內容
讀取文件,并且將數據轉化為矩陣格式,并且將特征縮放為一致的范圍,關于讀取的代碼介紹和特征縮放的概念闡述,請詳見前篇文章 《機器學習代碼實戰:使用線性回歸檢測水泥質量》 ,下面的代碼清單 2 中,是相關方法經過類的封裝后的處理過程:
清單 2.數據處理過程
// 數據文件 MatrixDataFile dataSource = new MatrixDataFile("/data/StatlogHeart.csv"); dataSource.featureScaling();// 處理值數據 float[][] Y = dataSource.getY(); for(int i=0;i<Y.length;i++){ Y[i][0] = (Y[i][0]==1) ? 0:1 ; }
// 訓練與測試數據集 ArrayList<float[][]> trainData = dataSource.getFirst(200); ArrayList<float[][]> testData = dataSource.getLast(100);</pre>
上面的代碼中,我們看到要將結果值轉化為 0 或 1 的值,而且請注意,訓練數據與測試數據選取的行盡量不要重疊,否則會影響訓練的效果。
模型訓練
輸入數據以及初始參數,讓模型不停的通過梯度下降計算出新的參數,使得概率越來越接近真實值,也就是使誤差越來越小的過程,直至其收斂到一個趨于固定的值后,得出最終一組參數,根據得到的參數帶入模型,然后用測試數據驗證模型,如果得到的誤差不滿意,則需要調整初始參數或者數據,繼續上面的過程,直到得到滿意的參數和測試數據誤差為止。這個過程如果訓練數據量很大,參數很多,則時間有可能會比較長,所以對于有非常多參數的數據或者復雜的模型如神經網絡可能需要用到多臺機器并行以及 GPU 卡加速等手段,加快訓練速度。本例的參數數量和訓練數據量不是很大,所以單機就可以比較快速的完成。在代碼清單 3 中,是根據上面提到的向量化計算步驟的實現代碼,代碼中每次 for 循環,會輸出一組誤差比上次循環更小的參數值變量 parameters。
清單 3. 模型訓練代碼
// 中間矩陣 a * xT a_times_xT = Matrix.times(this.alpha, xT);// 迭代 for(int times= 0; times<iterationTimes; times++){ // (1) 求 A = x * parameters; Matrix.times(x, parametersT, A);
// (2) 求 E = g(A) - y Calculate.sigmoid(A, g); Matrix.add(g, -1, y, E); // (3) 求 parametersT := parametersT - a * xT * E Matrix.times(a_times_xT, E, a_times_xT_times_E); Matrix.add(parametersT, -1, a_times_xT_times_E, parametersT); ...
}</pre>
注意在第二步中,使用了 sigmoid 函數,這和線性回歸訓練的時候是不同的。
訓練監控
每一次循環輸出新的誤差值、本次誤差相比上次循環中誤差縮小程度的百分比(即誤差的收斂程度)等相關數據。我們將這些數據記錄下來,可以輸出為表格或者圖形從而在訓練的過程中進行分析。下面所示的代碼清單 4 中,通過 LogicRegressionMonitor 類來顯示監控輸出結果通。
清單 4.訓練監控代碼
testData.get(1));LogicRegressionMonitor monitor = new LogicRegressionMonitor(); monitor.showResult(LR,100,1.2);測試模型
經過訓練,得到最終一組參數 parameters 后,將參數帶入模型公式,再用測試數據計算誤差,誤差越小則得出的參數越趨近于最優。如果對得到的參數不滿意,則需要調整超參數再次進行訓練,直到得到滿意的一組參數為止。
模型訓練實例
下面,讓我們看看如何開始訓練檢查數據,得出一個模型來預測心臟病。
調超參數
超參數是在開始學習過程之前設置的參數,本例中主要是步長和迭代次數。
- 步長 :每次梯度下降移動的距離。步長太小,訓練過程比較長,步長太大則可能會出現在一個凹點來回跨越的情況。
- 迭代次數:本例中手動設置迭代次數,次數過少,會擬合不充分,次數過多,則時間會比較長。
對于超參數,有時候我們可以先用小批量的數據迭代,搜索出比較好的超參數區間范圍,然后再帶入訓練模型進行大批量數據訓練,具體搜索方法不在本文討論范疇,請讀者參考相關文章。我們通過封裝的 LogicRegression 類建立一個邏輯回歸模型,train 方法輸入θ的初始值和上面 2 個超參數并且開始訓練,如下面代碼清單 5。
清單 5. 輸入參數開始訓練
// 建立模型開始訓練 LogicRegression LR = new LogicRegression(); LR.setXY(trainData.get(0),trainData.get(1)); LR.train(new float[]{1,1,1,1,1, 1,1,1,1,1, 1,1,1, 1}, 0.0015f, 2000);
對要通過訓練得到的一組參數θ的初始值,我們可以全部設置為 1。
測試與監控
經過多次訓練與調整后,得到一組比較滿意的參數,訓練后的監控輸出如下圖 4 所示。
圖 4. 監控輸出
從圖 3 的輸出中可以看出,隨著迭代的不斷進行,損失函數 L(θ)的值在逐漸增大,即不斷更新的參數帶入模型后,預測的概率越來越接近真實值,而且前后兩次訓練的結果差異逐漸縮小收斂,并且差值百分比趨近于一個固定的值,收斂的過程如下圖 5 的收斂曲線所示,迭代到最后誤差值逐漸成為一條水平線,不再下降:
圖 5. 收斂曲線
最終得到了一組比較滿意的模型的參數: [-0.1732262, 1.247153, 2.0413065, 1.9182386, 1.062683, -0.6875705, 0.48136407, -1.7114192, 0.86453587, 1.7943255, 0.70586383, 3.6919918, 1.4430027, -1.274113]
其中第一列是 0 參數 b,接下來是患者檢查數據中的每個特征的參數,對應到我們的邏輯回歸公式中的各個θ值,可以通過這組參數,建立出預測心臟病預測得到模型,當預測的結果值高時,則患者患有心臟病的概率就高。下面的圖 6 顯示了使用這組參數和測試數據的預測值與實際值對比效果。
圖 6. 預測值與實際值對比曲線
紅色點為實際患有心臟病的患者數據帶入模型后的預測結果,藍色點為無心臟病的患者數據帶入模型后的計算結果,可以看出,患有心臟病的患者的紅色點大部分落在了 S 曲線的趨近 1 的一端,而無心臟病的患者的點趨近 0 的一端,由于機器學習誤差的存在,只有一小部分紅色點在曲線中下端,但總體來講基本在一定的誤差范圍內,絕大部分點都正確落在了 S 曲線上相應的位置,能夠近似預測心臟病的概率。
結束語
本文介紹了如何利用邏輯回歸來幫助預測心臟病,邏輯回歸適合二分類問題,速度也比較快,總的來講,邏輯回歸模型和訓練與線性回歸很類似,都是通過梯度下降法,經過多次迭代求得模型的參數,而邏輯回歸為了實現二分類的應用了 Sigmoid 函數以及概率論。還有很多多個分類的問題,則需要利用其它模型如決策樹、神經網絡等,在以后的文章中再做介紹。本文介紹的模型也不是一成不變的,模型與數據所對應的實際產生環境有密切關系,如患者數據的多樣化,新技術的應用等,這個時候就需要考慮將這些因素考慮到模型中,或者考慮通過正則化處理一些不重要的屬性,本文就不再敖述。
本文還有一些不足和可以改進的地方,比如算法代碼實現與精度、自動結束迭代得到最優結果和測試數據的選擇等很多地方都不足。歡迎讀者批評指正,謝謝。本文僅代表筆者個人觀點。
數據來源:UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science.
參考資源
- 參考 斯坦福大學機器學習課程 ,查看更多邏輯回歸以及機器學習的內容。
- 參考《機器學習代碼實戰:使用線性回歸檢測水泥質量》,查看關于線性回歸的內容。
- 參考 心臟病研究數據 ,了解更多關數據的內容。
來自:https://www.ibm.com/developerworks/cn/analytics/library/ba-lo-ml-use-logistic-regression/index.html?ca=drs-&utm_source=tuicool&utm_medium=referral