人工神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)醫(yī)療數(shù)據(jù)分類Python

進(jìn)半年,大量學(xué)習(xí)和實(shí)踐深度學(xué)習(xí)方面的知識,因此希望把所學(xué)的知識進(jìn)行總結(jié)梳理,幫助更多同路的人。在這里連載文章,希望得到更多的人的支持。同時感謝港理工的佳佳博士與我一起完成這本書,一路過來,謝謝。

2.3 使用ANN對醫(yī)療數(shù)據(jù)分類

IBM在2015年5月宣布推出Watson Health服務(wù),收集健康數(shù)據(jù)交給Watson超級計(jì)算機(jī)進(jìn)行分析。目前IBM Waston Health最主要的應(yīng)用便是在癌癥的診療上,通過對醫(yī)學(xué)影像的分析和學(xué)習(xí),幫助醫(yī)生做出對癌癥患者的精準(zhǔn)診斷。另外一方面,在每一個國家的醫(yī)療資源都是十分稀缺的,如何提高基層醫(yī)生的診療水平顯得至關(guān)重要。我們并不需要智能診療會取代醫(yī)生,因?yàn)獒t(yī)療也并不是簡單的診斷,醫(yī)生更重要的價值還在于對診斷結(jié)果的解釋、說明和信用背書,但讓智能診療系統(tǒng)輔助醫(yī)生診斷,則無疑會大大提高普通醫(yī)生的診斷水平和診斷效率,降低誤診率。

因此,人工智能對于輔助醫(yī)生的工作來說,越顯得非常重要。例如:醫(yī)生可以根據(jù)人工智能給出女性最佳生育期的建議,不再僅僅基于她們的年齡,而是基于一系列相關(guān)的個人身體指標(biāo)。

以現(xiàn)代醫(yī)療檢測報(bào)告為例,很多時候我們?nèi)メt(yī)院抽血、檢查細(xì)胞病變等檢查之后,檢查室開出一張看不懂的表格如圖2.3.1,上面有白細(xì)胞、鏈球菌、血小板的參數(shù),醫(yī)生通過檢查這些參數(shù)值得大小和變化,可以預(yù)測判斷該病人是否患有某種病原體。

下面我們將把類似的醫(yī)療數(shù)據(jù)作為背景:首先給出對的醫(yī)療檢測數(shù)據(jù)進(jìn)行數(shù)學(xué)模型的轉(zhuǎn)換,然后通過構(gòu)建一個三層的人工神經(jīng)網(wǎng)絡(luò)ANN對已有的醫(yī)療數(shù)據(jù)進(jìn)行訓(xùn)練, 得到該醫(yī)療數(shù)據(jù)的分類模型,然后對新的數(shù)據(jù)進(jìn)行預(yù)測其所屬分類,因?yàn)槿斯ど窠?jīng)網(wǎng)絡(luò)的隱層對數(shù)據(jù)的分類存在較大影響,因此最后探討隱層的神經(jīng)元節(jié)點(diǎn)數(shù)對實(shí)際的數(shù)據(jù)有如何的影響。

這里寫圖片描述

2.3.1 準(zhǔn)備數(shù)據(jù):從醫(yī)療數(shù)據(jù)到數(shù)學(xué)模型

假設(shè)本次血液檢測項(xiàng)目只有鏈球菌x1和葡萄球菌x2兩種,對于不同的球菌組合,會出現(xiàn)不同的病原體(不同的病癥)。假設(shè)有兩個分類,分別是病原體I和病原體II,病原體I、病原體II分別用用藍(lán)色、紅色表達(dá);x軸表示鏈球菌的值,y軸表示葡萄球菌的值。在本例程這里的數(shù)據(jù)已經(jīng)歸一化處理過后,因此數(shù)據(jù)的分布集中在[-2, 2]之間(想要了解更多關(guān)于歸一化操作請參考機(jī)器學(xué)習(xí)實(shí)踐[1])。

我們的目標(biāo)是通過訓(xùn)練一個三層的人工神經(jīng)網(wǎng)絡(luò)ANN模型,對給出不同血液檢測項(xiàng)目(x1、x2)的數(shù)據(jù)進(jìn)行分類。值得注意的是,因?yàn)楹芏鄷r候醫(yī)療數(shù)據(jù)是線性不可分的,因此我們并不能夠簡單地使用一條直線對數(shù)據(jù)進(jìn)行劃分。這意味著不能夠用線性分類器來分類,例如Logistic分類器、貝葉斯分類器等,否則分類的數(shù)據(jù)精度和回召率過低,會對真實(shí)的醫(yī)療診斷結(jié)果造成巨大的影響。

使用神經(jīng)網(wǎng)絡(luò)的好處就是不需要去擔(dān)心特征工程問題,隱層會自動地去尋找特征。

from sklearn import linear_model
from sklearn import datasets
import sklearn
import numpy as np
import matplotlib.pyplot as plt
# 在notebook內(nèi)顯示plt圖
%matplotlib inline
def plot_decision_boundary(pred_func, data, labels):
    '''繪制分類邊界圖'''
    # 設(shè)置最大值和最小值并增加0.5的邊界(0.5 padding)
    x_min, x_max = data[:, 0].min() - 0.5, data[:, 0].max() + 0.5
    y_min, y_max = data[:, 1].min() - 0.5, data[:, 1].max() + 0.5
    h = 0.01

    # 生成一個點(diǎn)陣網(wǎng)格,點(diǎn)陣間距離為h
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))

    # 預(yù)測整個網(wǎng)格當(dāng)中的函數(shù)值
    z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    z = z.reshape(xx.shape)

    # 繪制輪廓和訓(xùn)練樣本
    plt.contourf(xx, yy, z, cmap=plt.cm.Spectral)
    plt.scatter(data[:, 0], data[:, 1], s=40, c=labels, cmap=plt.cm.Spectral)
np.random.seed(0)
X, y = datasets.make_moons(300, noise=0.25)  # 300個數(shù)據(jù)點(diǎn),噪聲設(shè)定0.3
plt.scatter(X[:,0], X[:,1], s = 50,  c = y, cmap=plt.cm.Spectral, edgecolors="Black")
plt.title('Data Example')
plt.show()
這里寫圖片描述

邏輯回歸結(jié)果

下圖顯示了我們邏輯回歸分類器的分類結(jié)果,邏輯回歸算法直接用直線將數(shù)據(jù)分隔開兩類,明顯這樣的分類結(jié)果是遠(yuǎn)遠(yuǎn)不能滿足真實(shí)情況的。

# 使用scikit-learn的線性回歸分類器
clf = sklearn.linear_model.LogisticRegressionCV() 
clf.fit(X, y)

# 顯示分類結(jié)果
plot_decision_boundary(lambda x: clf.predict(x), X, y)
plt.title("Logistic Regression")
<matplotlib.text.Text at 0x10d0de190>
這里寫圖片描述

2.3.2 建立人工神經(jīng)網(wǎng)絡(luò)模型

為了解決現(xiàn)實(shí)情況更多線性不可分的數(shù)據(jù),我們需要建立一個三層神經(jīng)網(wǎng)絡(luò),輸入層、隱層、輸出層。

其中輸入層(第一層)中的神經(jīng)節(jié)點(diǎn)數(shù)目由輸入數(shù)據(jù)的維數(shù)決定,例子有鏈球菌x1和葡萄球菌x2,因此數(shù)據(jù)維度是2,輸入有兩個神經(jīng)元節(jié)點(diǎn)。同理,輸出層(第三層)中的節(jié)點(diǎn)數(shù)目由我們的分類數(shù)決定,例子里為病原體I(0)和病原體II(1),因此輸出層有兩個神經(jīng)元。該神經(jīng)網(wǎng)絡(luò)如圖2.3.4所示。

這里寫圖片描述

根據(jù)實(shí)際情況我們可以增加隱層數(shù)和每一隱層的節(jié)點(diǎn)數(shù),當(dāng)隱層越多、隱層節(jié)點(diǎn)越多,能夠處理更加復(fù)雜數(shù)據(jù)模型,但是伴隨著是更大的開銷:

1.更多隱層意味著我們的網(wǎng)絡(luò)模型越大,引入更多的權(quán)值參數(shù),占用更多GPU顯存;

2.參數(shù)數(shù)量越多,數(shù)據(jù)過度擬合的可能性就越大,網(wǎng)絡(luò)就有可能不穩(wěn)定,預(yù)測效果反而下降;

那么如何選擇適當(dāng)?shù)碾[層數(shù)呢?每層隱藏層的神經(jīng)元數(shù)應(yīng)該設(shè)置多少呢?這是一個工程問題,不同的數(shù)據(jù)會有不一樣的結(jié)果,所以作者覺得神經(jīng)網(wǎng)絡(luò)的設(shè)計(jì)是一種工程藝術(shù),我們需要去嘗試、訓(xùn)練、預(yù)測、評估,才能最終決定整個網(wǎng)絡(luò)模型的形態(tài),感受深度學(xué)習(xí)的魅力所在。

在隱層我們需要一個激活函數(shù),激活函數(shù)把輸出層轉(zhuǎn)換成為下一層的輸入層。非線性的激活函數(shù)能夠讓我們?nèi)ヌ幚矸蔷€性的問題。常用的有tanh函數(shù)、sigmoid函數(shù)或者ReLUs函數(shù)。例子里我們選擇使用tanh函數(shù),你也可以嘗試把tanh函數(shù)換成其他函數(shù)查看輸出。

最后通過softmax分類器把激活函數(shù)的輸出分?jǐn)?shù)轉(zhuǎn)換成為概率。

Softmax分類器

預(yù)測網(wǎng)絡(luò)

人工神經(jīng)網(wǎng)絡(luò)的預(yù)測使用向前反饋操作,我們可以理解為激活函數(shù)和矩陣乘法的操作。假設(shè)輸入x是二維,我們可以根據(jù)計(jì)算公式得到輸出分類結(jié)果$\hat{y}$:

$z_1=xW_1+b_1$

$a_1=tanh(Z_1)$

$z2=a_1W_2+b_2$

$a_2=\hat{y}=softmax(Z_2)$

$z_i$是第$i$層的輸入,$a_i$是第$i$層激活函數(shù)處理后的輸出。$W_1,b_1,W_2,b_2$是神經(jīng)網(wǎng)絡(luò)的參數(shù),用來學(xué)習(xí)和訓(xùn)練用的數(shù)據(jù),這里可以把它們當(dāng)做在網(wǎng)絡(luò)中的的矩陣。圖2.3.5是圖2.3.4的展開。如果在隱層中使用100個神經(jīng)元節(jié)點(diǎn),那么有$W_1\in\mathbb{R}^{2100}, b_1\in\mathbb{R}{100},W_2\in\mathbb{R}{1002}, b_2\in\mathbb{R}^{2}$,從上面的公式可以看出來增加隱層的節(jié)點(diǎn)數(shù)會大量的增加網(wǎng)絡(luò)
的參數(shù)。

這里寫圖片描述

參數(shù)學(xué)習(xí)

學(xué)習(xí)參數(shù)意味著需要找$W_1,b_1,W_2,b_2$,使得我們的數(shù)據(jù)誤差最小。那么如何定義誤差?我們稱這個檢測誤差的函數(shù)叫做損失函數(shù)(loss function)。這里我們使用較為常用交叉熵?fù)p失函數(shù)(也叫做負(fù)對數(shù)似然函數(shù))。如果我們有N個訓(xùn)練樣本和對應(yīng)C個分類,那么預(yù)測值$\hat{y}$與實(shí)際值y的損失是:

$$L(y,\hat{y})=-\frac{1}{N}\sum_{n\in N}\sum_{i\in C}y_{n,i}{log\hat{y}_{n,i}}$$

當(dāng)實(shí)際值$y$與預(yù)測值$\hat{y}$之間的概率分布差別越大,損失就越大。因此通過尋找好的權(quán)重參數(shù),最大限度地減少損失,提高數(shù)據(jù)分類的準(zhǔn)確性。

例子里我們使用梯度下降算法去找損失函數(shù)的最小值,使用一個固定的學(xué)習(xí)速率實(shí)現(xiàn)批量梯度下降算法(在實(shí)際工程當(dāng)中通常是使用隨機(jī)梯度下降或minibatch梯度下降算法的)。

梯度下降首先需要對神經(jīng)網(wǎng)絡(luò)模型用到的參數(shù)進(jìn)行求導(dǎo):$\frac{\partial L}{\partial W_1},\frac{\partial L}{\partial b_1},\frac{\partial L}{\partial W_2},\frac{\partial L}{\partial b_2}$。而求得這些梯度則使用著名的BP算法,在這里不在給出推導(dǎo)過程,而是直接使用推導(dǎo)后的公式:

$\delta _3=\hat{y}-y$

$\delta _2=(1-tanh2z_1)*\delta_3W_2T$

$\frac{\delta L}{\delta W_2}=\alpha _1^T \delta_3$

$\frac{\delta L}{\delta b_2}=\delta _3$

$\frac{\delta L}{\delta w_1}=x^T\delta _2$

$\frac{\delta L}{\delta b_1}=\delta _2$

2.3.3 Python實(shí)現(xiàn)ANN

首先定義一些梯度下降的時候用到的一些變量:

class Config:
    input_dim = 2  # 輸入的維度
    output_dim = 2  # 輸出的分類數(shù)

    epsilon = 0.01  # 梯度下降學(xué)習(xí)速度
    reg_lambda = 0.01  # 正則化強(qiáng)度

下面的損失函數(shù)的實(shí)現(xiàn),通過這個函數(shù)我們觀察模型訓(xùn)練的效果:

def calculate_loss(model, X, y):
    '''
    損失函數(shù)
    '''
    num_examples = len(X)  # 訓(xùn)練集大小
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # 正向傳播計(jì)算預(yù)測值
    z1 = X.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    # 計(jì)算損失值
    corect_logprobs = -np.log(probs[range(num_examples), y])
    data_loss = np.sum(corect_logprobs)
    # 對損失值進(jìn)行歸一化(可以不加)
    data_loss += Config.reg_lambda / 2 * \
        (np.sum(np.square(W1)) + np.sum(np.square(W2)))
    return 1. / num_examples * data_loss

接下來實(shí)現(xiàn)預(yù)測函數(shù),預(yù)測的時候只需要對模型進(jìn)行一次向前傳播,然后返回分類結(jié)果中概率最大的一項(xiàng):

def predict(model, x):
    '''
    預(yù)測函數(shù)
    '''
    W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
    # 向前傳播
    z1 = x.dot(W1) + b1
    a1 = np.tanh(z1)
    z2 = a1.dot(W2) + b2
    exp_scores = np.exp(z2)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
    return np.argmax(probs, axis=1)

接下是整個人工神經(jīng)網(wǎng)絡(luò)模型的函數(shù),這個函數(shù)實(shí)現(xiàn)了使用向后傳播算法來計(jì)算批量梯度下降,使用了2.3.2中參數(shù)學(xué)習(xí)的公式:

def ANN_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
    '''
    網(wǎng)絡(luò)學(xué)習(xí)函數(shù),并返回網(wǎng)絡(luò)
    - nn_hdim: 隱層的神經(jīng)元節(jié)點(diǎn)(隱層的數(shù)目)
    - num_passes: 梯度下降迭代次數(shù)
    - print_loss: 是否顯示損失函數(shù)值
    '''
    num_examples = len(X)  # 訓(xùn)練的數(shù)據(jù)集
    model = {}  # 模型存儲定義

    # 隨機(jī)初始化參數(shù)
    np.random.seed(0)
    W1 = np.random.randn(Config.input_dim, nn_hdim) / np.sqrt(Config.input_dim)
    b1 = np.zeros((1, nn_hdim))
    W2 = np.random.randn(nn_hdim, Config.output_dim) / np.sqrt(nn_hdim)
    b2 = np.zeros((1, Config.output_dim))
    # display_model({'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2})

    # 批量梯度下降
    for i in xrange(0, num_passes + 1):
        # 向前傳播
        z1 = X.dot(W1) + b1  # M_200*2 .* M_2*3 --> M_200*3
        a1 = np.tanh(z1)
        z2 = a1.dot(W2) + b2  # M_200*3 .* M_3*2 --> M_200*2
        exp_scores = np.exp(z2)
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

        # 向后傳播
        delta3 = probs  # 得到的預(yù)測值
        delta3[range(num_examples), y] -= 1  # 預(yù)測值減去實(shí)際值
        delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
        dW2 = (a1.T).dot(delta3)  # W2的導(dǎo)數(shù)
        db2 = np.sum(delta3, axis=0, keepdims=True)  # b2的導(dǎo)數(shù)
        dW1 = np.dot(X.T, delta2)  # W1的導(dǎo)數(shù)
        db1 = np.sum(delta2, axis=0)  # b1的導(dǎo)數(shù)

        # 添加正則化項(xiàng)
        dW1 += Config.reg_lambda * W1
        dW2 += Config.reg_lambda * W2

        # 根據(jù)梯度下降值更新權(quán)重
        W1 += -Config.epsilon * dW1
        b1 += -Config.epsilon * db1
        W2 += -Config.epsilon * dW2
        b2 += -Config.epsilon * db2

        # 把新的參數(shù)加入模型當(dāng)中
        model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

        if print_loss and i % 1000 == 0:
            print("Loss after iteration %i: %f" %
                  (i, calculate_loss(model, X, y)))

    return model

下面我們來看一下訓(xùn)練的網(wǎng)絡(luò)只有隱層節(jié)點(diǎn)數(shù)只有三個的時候訓(xùn)練的結(jié)果:

model = ANN_model(X, y, 3, print_loss=True)  # 建立三個神經(jīng)元的隱層
plot_decision_boundary(lambda x: predict(model, x), X, y)
plt.title("Hidden Layer size 3")
Loss after iteration 0: 0.389105
Loss after iteration 1000: 0.120831
Loss after iteration 2000: 0.116688
Loss after iteration 3000: 0.115724
Loss after iteration 4000: 0.115322
Loss after iteration 5000: 0.115115
Loss after iteration 6000: 0.114996
Loss after iteration 7000: 0.114923
Loss after iteration 8000: 0.114876
Loss after iteration 9000: 0.114845
Loss after iteration 10000: 0.114825
Loss after iteration 11000: 0.114811
Loss after iteration 12000: 0.114801
Loss after iteration 13000: 0.114794
Loss after iteration 14000: 0.114789
Loss after iteration 15000: 0.114786
Loss after iteration 16000: 0.114784
Loss after iteration 17000: 0.114782
Loss after iteration 18000: 0.114781
Loss after iteration 19000: 0.114780
Loss after iteration 20000: 0.114779
這里寫圖片描述

2.3.4 隱層節(jié)點(diǎn)數(shù)對模型的影響

從上圖可以看出人工神經(jīng)網(wǎng)絡(luò)模型的訓(xùn)練效果比Logistic回歸的效果好很多,但是依然可以看出有個別數(shù)據(jù)的分類是錯誤的。接下來通過實(shí)際工程測試去深入了解隱層節(jié)點(diǎn)對模型的影響:

plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 15, 30, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
    plt.subplot(5, 2, i+1)
    plt.title('Hidden Layer size %d' % nn_hdim)
    model = ANN_model(X, y, nn_hdim)
    plot_decision_boundary(lambda x: predict(model, x), X, y)
plt.show()
這里寫圖片描述

通過上圖我們可以看到,隱層數(shù)在低維(4、5)的時候能夠很好地表達(dá)數(shù)據(jù)的分類屬性,隱層節(jié)點(diǎn)數(shù)越高,會造成過度擬合(如隱層數(shù)為50時)。造成這種原因是因?yàn)殡[層數(shù)較低的時候會有更好的歸一化表現(xiàn),當(dāng)隱層數(shù)正價的時候,歸一化過度造成過度擬合。如果想要更好地評估這個醫(yī)療數(shù)據(jù)分類模型,更建議把數(shù)據(jù)分為訓(xùn)練集和測試集,然后檢測測試集的效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容