邏輯回歸的例子
邏輯回歸是經典的分類算法。為了簡單,我們考慮二分類。這意味著,我們要處理識別二個分類的問題,我們的標簽為 0 或 1。 我們要一個與線性回歸不同的激活函數,不同的損失函數,神經元的輸出略有不同。我們的目的是能構建模型能預測一個新的觀察屬于兩個分類中的哪一個。 對于輸入 x神經元應輸出概率P(y = 1| x) 作為分類1.我們分類觀察作為分類1,如果 P(y = 1| x) > 0.5, 或者分類 0,如果P(y = 1| x) < 0.5.
損失函數
作為損失函數,我們使用交叉熵. 對于一個觀察函數為
對于多于一個觀察 損 失函數是求和所有的觀察
后面我們可以從頭書寫完整的邏輯回歸,但是現在,tensorflow會為我們處理細節--微分,梯度下降,等的實現。我們只需要構建對的神經元。
激活函數
記住:我們想要神經元輸出觀察是分類 0或分類 1的概率。因些,我們想要激活函數輸出0 到1的值。否則我們不能把它作為概率。對于邏輯回歸,我們使用 sigmoid函數作為激活函數.
數據集
要構建一個有趣的模型,我們要使用修改版的 MNIST數據集。你可以在 http://yann.lecun.com/exdb/ mnist/找到相關信息。
?MNIST數據集是大的手寫字數據集可以用來訓練我們的模型。 MNIST數據集包含70,000張圖像。 “原始的黑白圖像 (bilevel)來自NIST的大小被歸一化以適合 20×20像素盒子而保留它們的比例.結果圖像包含灰度水平作為歸一化算法使用的 ?anti-aliasing技術的結果。圖像集中于 28×28圖像,通過計算像素質心,我后轉換圖像使中心在28×28 區域內” (來源: http://yann. lecun.com/exdb/mnist/).
我們的特征是每個像素的灰度值,所以我們有28 × 28 = 784個特征取值為0到255 (灰度值).數據集包含10個數字,從0到9. 用下面的代碼,你可以準備數據以供下一節使用。通常,我們先導入必要的庫.
?#List3-46
from ?sklearn.datasets ?import ?fetch_mldata
然后加載數據.
mnist = fetch_mldata('MNIST original') X,y ?= ?mnist["data"], ?mnist["target"]
現在X包含輸入圖像且y是目標標簽 (記住在機器學習里我們想要預測的值稱為目標). 只要輸入 X.shape就會得到 X的形狀: (70000, 784). 注意 X 有70,000行 (每行是一張圖像)和 784列 (每列是一個特征,或像素灰度值,本例中).我們檢查一下數據集里有多少個數字.
?#List3-47
for i in range(10):
print ("digit", i, "appears", np.count_nonzero(y == i), "times")
結果如下:
digit | 0 | appears | 6903 | times |
digit | 1 | appears | 7877 | times |
digit | 2 | appears | 6990 | times |
digit | 3 | appears | 7141 | times |
digit | 4 | appears | 6824 | times |
digit | 5 | appears | 6313 | times |
digit | 6 | appears | 6876 | times |
digit | 7 | appears | 7293 | times |
digit | 8 | appears | 6825 | times |
digit | 9 | appears | 6958 | times |
定義一個函數來可視化數字是有用的.
?#List3-48
def ?plot_digit(some_digit):
some_digit_image?? ?=?? ?some_digit.reshape(28,28)? plt.imshow(some_digit_image,?? ?cmap?? ?=?? ?matplotlib.cm.binary,?? ?interpolation
= "nearest") plt.axis("off") plt.show()
例如, 我們隨機的作圖 (見圖 3-46).
plot_digit(X[36003])
圖 3-46. 數據集里第 36,003個數字,它很容易的識別為 5
我們這里要實現的模型是二分類的簡單的邏輯回歸。所以數據集必須減少為兩個分類,這種情況,兩個數字。我們選擇1和2.我們提取數據集,只呈現 1或 2. 我們的神經元會試圖識別給定的圖像是分類 0 (數字 1)還是分類 1 (數字 2).
X_train = X[np.any([y == 1,y == 2], axis = 0)] y_train ?= ?y[np.any([y ?== ?1,y ?== ?2], ?axis ?= ?0)]
接下來,輸入觀察必須歸一化. (注意,當你使用 sigmoid 激活函數時你不想讓你的輸入數據過大,因為有784個值.)
X_train_normalised? ?=? ?X_train/255.0
我們選擇 255,因為特征是像素灰度值,取值為 0到255.后面我們討論為什么要歸一化輸入特征。現在相信我這是必要的步驟。 每一列,我們想要一個輸入觀察,每一行表示特征,所以我們要改變張量的形狀
X_train_tr ?= ?X_train_normalised.transpose() y_train_tr?? ?=?? ?y_train.reshape(1,y_train.shape[0])
我們可以定義變量n_dim 包含特征數
n_dim?? ?=?? ?X_train_tr.shape[0]
現在時重點。數據集的標簽為 1或 2 (它們只告訴你圖像表示哪個數字). 然而,我們要構建我們的損失函數使用分類標簽為0和1, 所以我們要改變 y_train_tr數組的尺度。
注意??? 當處理二分類時,記得檢查標簽的值。有時候,使用錯誤的標簽 (不是 0 和 1)會浪費很多時間來理解模型為什么不工作。
y_train_shifted = y_train_tr - 1
現在所有表示 1的圖像來標簽 0, 所有表示2的圖像有標簽 1.最后,我們給變理合適的名稱.
Xtrain = X_train_tr ytrain ?= ?y_train_shifted
圖3-47展示了我們處理的數字.
圖3-47. 從數據集隨機選取的6個數字。相對的標簽在括號里 (記住現在標簽為0或 1).
tensorflow實現
?tensorflow實現并不難,與線性回歸幾乎相同。首先定義placeholders和variables.
tf.reset_default_graph()
X ?= ?tf.placeholder(tf.float32, ?[n_dim, ?None]) Y? ?=? ?tf.placeholder(tf.float32, ?[1, ?None])
learning_rate ?= ?tf.placeholder(tf.float32, ?shape=())
W? ?=? ?tf.Variable(tf.zeros([1, ?n_dim])) b??? ?=??? ?tf.Variable(tf.zeros(1))
init? ?=? ?tf.global_variables_initializer()
注意代碼與上面的線性回歸相同。但是我們必須定義不同的損失函數和不神經元輸出 (sigmoid函數).
y_? ?=? ?tf.sigmoid(tf.matmul(W,X)+b)
cost ?= ?- ?tf.reduce_mean(Y ?* ?tf.log(y_)+(1-Y) ?* ?tf.log(1-y_))
training_step?? ?=?? ?tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)
我們用sigmoid函數作為神經元的輸出,使用 tf.sigmoid(). 運行模型的代碼與線性回歸相同.我們只改變函數名稱.
def? ?run_logistic_model(learning_r,? ?training_epochs,? ?train_obs, train_labels, ?debug ?= ?False):
sess ?= ?tf.Session() sess.run(init)
cost_history ?= ?np.empty(shape=[0], ?dtype ?= ?float)
for epoch in range(training_epochs+1):
sess.run(training_step, ?feed_dict ?= ?{X: ?train_obs, ?Y: ?train_labels, learning_rate:?? ?learning_r})
cost_ = sess.run(cost, feed_dict={ X:train_obs, Y: train_labels, learning_rate: ?learning_r})
cost_history ?= ?np.append(cost_history, ?cost_)
if (epoch % ?500 == 0) & ?debug:
print("Reached epoch",epoch,"cost J =", str.format('{0:.6f}', cost_))
return sess, cost_history
我們運行模型并看一下結果.我們從學習速率0.01開始.
sess, ?cost_history ?= ?run_logistic_model(learning_r ?= ?0.01,
training_epochs = 5000, train_obs ?= ?Xtrain, train_labels? ?=? ?ytrain, debug = True)
代碼的輸出如下 ( 3000 epochs后停止):
Reached epoch 0 cost J = 0.678598 Reached epoch 500 cost J = 0.108655 Reached epoch 1000 cost J = 0.078912 Reached epoch 1500 cost J = 0.066786 Reached epoch 2000 cost J = 0.059914 Reached epoch 2500 cost J = 0.055372 Reached epoch 3000 cost J = nan
發生什么了? 突然,某些點,我們的損失函數取nan (不是數字).看來模型在某個點工作得不好。如果學習速率太大,或初始化權重錯誤,你的值
y?(i )? = P (y(i )? = 1|x(i ) ) 或能接按于零或1?( sigmoid函數取值接近 0 或1 以于非常大的正或負的z值).記住,在損失函數里,你有兩個項tf.log(y_)和 tf.log(1-y_), 因為 log函數對于值零沒有定義,如果 y_ 為 0或 1, 你會得到 nan, 因為代碼試圖評估tf.log(0).例如,我們可以運行模型使用學習速率 2.0.只要一個 epoch, 你的損失函數就得到nan值。很容易理解原因,如果你打印b在第一個訓練步前后。簡單的修改你的模型代碼,使用下面的版本 :
def? ?run_logistic_model(learning_r,? ?training_epochs,? ?train_obs,? ?train_ labels, ?debug ?= ?False):
sess ?= ?tf.Session() sess.run(init)
cost_history = np.empty(shape=[0], dtype = float) for epoch in range(training_epochs+1):
print ('epoch: ', epoch)
print(sess.run(b, ?feed_dict={X:train_obs, ?Y: ?train_labels, learning_rate:? ?learning_r}))
sess.run(training_step, ?feed_dict ?= ?{X: ?train_obs, ?Y: ?train_labels, learning_rate:?? ?learning_r})
print(sess.run(b, ?feed_dict={X:train_obs, ?Y: ?train_labels, learning_rate:? ?learning_r}))
cost_ = sess.run(cost, feed_dict={ X:train_obs, Y: train_labels, learning_rate: ?learning_r})
cost_history ?= ?np.append(cost_history, ?cost_)
if (epoch % ?500 == 0) & ?debug:
print("Reached epoch",epoch,"cost J =", str.format('{0:.6f}', cost_))
return sess, cost_history
你得到下面的結果 (訓練一個epoch):
epoch:??? 0
[ 0.]
[-0.05966223]
Reached epoch 0 cost J = nan epoch:????? 1
[-0.05966223]
[ nan]
你看到b從0變到 -0.05966223然后再到 nan? 因此, z = wTX + b 這為 nan, 然后y = σ(z) 也變為 nan, 最后損失函數是 y的函數,結果為nan. 這是因為學習速率過大。解決方案呢? 你應該試試不同的學習速率 (更少的值).
我們試試并得到更穩定的結果,在 2500 epochs之后.我們運行模型:
sess,? ?cost_history? ?=? ?run_logistic_model(learning_r? ?=? ?0.005,
training_epochs = 5000, train_obs ?= ?Xtrain, train_labels? ?=? ?ytrain, debug = True)
輸出如下
Reached epoch 0 cost J = 0.685799 Reached epoch 500 cost J = 0.154386 Reached epoch 1000 cost J = 0.108590 Reached epoch 1500 cost J = 0.089566 Reached epoch 2000 cost J = 0.078767 Reached epoch 2500 cost J = 0.071669 Reached epoch 3000 cost J = 0.066580 Reached epoch 3500 cost J = 0.062715 Reached epoch 4000 cost J = 0.059656 Reached epoch 4500 cost J = 0.057158 Reached epoch 5000 cost J = 0.055069
再也沒有nan輸出了。你可以在圖 3-48看損失數.要評估我們的模型,我們必須選擇優化量度 (如前所述).對于二分類問題,分類量度是準確率 ?(記為 a) 可以理解為結果與真實值的差的量度.數學上,它這樣計算
要得到準確率,我們運行下面的代碼. (記住 我們分類觀察i為分類 0 如果 P(y(i) = 1| x(i)) < 0.5, 或者分類 1如果 P(y(i) = 1| x(i)) > 0.5.)
correct_prediction1? ?=? ?tf.equal(tf.greater(y_,? ?0.5),? ?tf.equal(Y,1)) accuracy ?= ?tf.reduce_mean(tf.cast(correct_prediction1, ?tf.float32)) print(sess.run(accuracy, ?feed_dict={X:Xtrain, ?Y: ?ytrain, ?learning_rate: 0.05}))
使用這個模型,我們得到準確率為 98.6%. 這對于一個神經元的網絡不差。
圖 3-48. 損失函數與 epochs,學習速率為0.005
你可以運行前面的模型更多的epochs (使用學習速度為 0.005) 。你會發現,在7000 epochs,又出現 nan 。解決方案是減少學習速率增加 epochs數。簡單的解決方案是,每 500 減半學習速率,就會消除 nans.后面詳細討論相似的方法.
你可以運行前面的模型更多的epochs (使用學習速度為 0.005) 。你會發現,在7000 epochs,又出現 nan 。解決方案是減少學習速率增加 epochs數。簡單的解決方案是,每 500 減半學習速率,就會消除 nans.后面詳細討論相似的方法.