機器學習之旅---logistic迴歸

一、logistic迴歸分析簡介

    logistic迴歸是研究觀察結果(因變量)爲二分類或多分類時,與影響因素(自變量)之間關係的一種多變量分析方法,屬於概率型非線性迴歸。

    利用logistic迴歸進行分類的主要思想是:根據現有數據對分類邊界線建立迴歸公式,以此進行分類。這裏“迴歸”是指通過最優化方法找到最佳擬合參數集,作爲分類邊界線的方程係數。通過分類邊界線進行分類,具體說來就是將每個測試集上的特徵向量乘以迴歸係數(即最佳擬合參數),再將結果求和,最後輸入到logistic函數(也叫sigmoid函數),根據sigmoid函數值與閾值的關係,進行分類。也就是說logistic分類器是由一組權值係數組成,最關鍵的問題就是如何求得這組權值。

    在流行病學研究中,logistic迴歸常用於分析疾病與各種危險因素間的定量關係。這次將利用logistic迴歸預測病馬的死亡率,病馬樣本有368個,每個樣本有21個特徵點,即21種危險因素。接下來,本文首先介紹利用梯度上升法及改進的隨機梯度上升法尋找最佳迴歸係數,然後用matlab實現基於logistic迴歸的病馬死亡率預測。


二、如何尋找最優化參數

1.logistic迴歸模型

     給定n個特徵x=(x1,x2,…,xn),設條件概率P(y=1|x)爲觀測樣本y相對於事件因素x發生的概率,用sigmoid函數表示爲:

    

    那麼在x條件下y不發生的概率爲:

    

    假設現在有m個相互獨立的觀測事件y=(y1,y2,…,ym),則一個事件yi發生的概率爲(yi= 1)

   

   那麼現在就成了,已知總體事件Y是服從上述概率密度的連續型隨機變量:

     

    由於logistic分類器是由一組權值係數組成的,最關鍵的問題就是如何獲取這組權值。而f(x)中x是由權值向量與特徵向量相乘的結果,即g(x)表示,其中特徵向量是隨機變量,只有向量w是參數,所以權值向量w可以通過極大似然函數估計獲得,並且Y~f(x;w)

【回憶一下似然函數的性質】

1.似然函數是統計模型中參數的函數。給定輸出x時,關於參數θ的似然函數L(θ|x)(在數值上)等於給定參數θ後變量X的概率:L(θ|x)=P(X=x|θ)

2.似然函數的重要性不是它的取值,而是當參數變化時概率密度函數到底是變大還是變小。對於同一個似然函數,如果存在一個參數值,使得它的函數值得到最大,那麼這個值就是最爲合理的參數值。

3.極大似然函數:似然函數取得最大值表示相應的參數能夠使得統計模型最爲合理

 

    那麼對於上述m個觀測事件,其聯合概率密度函數,即似然函數爲:



     現在,我們的目標是求出使這一似然函數的值最大的參數估,也就是求出參數w1,w2,…,wn,使得L(w)取得 最大值。對L(w)取對數:



     由於w有n+1個,根據n+1的對wi的偏導數方程求解w比較不靠譜。可以考慮使用牛頓-拉菲森迭代梯度上升方法求解。梯度上升算法和牛頓迭代相比,收斂速度慢,因爲梯度上升算法是一階收斂,而牛頓迭代屬於二階收斂。


2.梯度上升法

    梯度上升法基於的思想是要找到某函數的最大值,最好的方法是沿着該函數的梯度方向探尋。函數f(x,y)的梯度可以如下表示,前提是函數f(x,y)必須在待計算的點上有定義並且可微。

        

    在梯度上升法中,待計算點沿梯度方向每移動一步,都是朝着函數值增長最快的方向。若移動的步長記作a,則梯度算法的迭代公式如下:

       

    迭代停止條件爲:迭代次數達到某個指定的值或者算法達到某個可以允許的誤差範圍。現在繼續對剛纔的似然函數進行推到:



其中,



根據梯度上升算法有,



進一步得到,



接下來將根據上述推到,實現一個利用logistic迴歸得到分類邊界的例子,由於不擅長python,我索性用matlab改寫了。現在有100個樣本點及對應分類標籤,每個樣本點有2個特徵。【文本文件中有100行*3列數據,自己導入matlab,做成*.mat文件即可】

clc;
clear;

load x
load y
load label

[r,c] = size(x);
A = ones(1,r)';
A = [A x y];
L = label;
alpha = 0.001;
maxCycles = 500;
weights = ones(1,3)';

for k=1:1:maxCycles
    h = sigmoid(A*weights,r);
    error = (L - h);
    weights = weights + alpha * A' * error;
end

%%畫圖
for k = 1:1:r
    hold on
    if L(k,1) == 1
        plot(A(k,2),A(k,3),'r+');
    else
        plot(A(k,2),A(k,3),'b*');
    end
end

%%畫分割線
hold on
dx = -3:3;
dy = (-weights(1)-weights(2)*dx)/weights(3);
plot(dx,dy,'k-','lineWidth',3);

function result = sigmoid(inX,r)
    result = [];
    for k = 1:1:r
        result(k,1) = 1.0 / (1 + exp(-inX(k,1)));
    end
end

分類邊界:



注:爲了方便計算,這裏爲每個樣本點新增一列特徵X0,都爲1.0,否則分類邊界將過零點,不準確了。

分界線的方程: w0 * x0 + w1 * x1 + w2 * x2 = 0


3.改進的梯度上升法

    梯度上升法在每次更新迴歸係數時都需要遍歷整個數據集,該方法在處理100個左右數據集時效率尚可,但如果有10億樣本和成千上萬的特徵,那麼這個方法的計算量就太大了。改進方法是一次僅用一個點來更新迴歸係數。它是一種在線學習算法【即:可以在新的樣本到來時對分類器進行增量式更新。】,每一次處理的數據的操作被稱爲“批處理”

clc;
clear;

load x
load y
load label

[r,c] = size(x);
A = ones(1,r)';
A = [A x y];
L = label;
alpha = 0.01;
weights = ones(1,3)';

for k = 1:1:r
    h = sigmoid2(sum(A(k,:,:)*weights));
    error = L(k) - h;
    weights = weights + alpha * error * A(k,:,:)';
end

%%畫圖
for k = 1:1:r
    hold on
    if L(k,1) == 1
        plot(A(k,2),A(k,3),'r+');
    else
        plot(A(k,2),A(k,3),'b*');
    end
end

%%畫分割線
hold on
figure(1);
dx = -3:3;
dy = (-weights(1)-weights(2)*dx)/weights(3);
plot(dx,dy,'k-','lineWidth',3);

%畫各種迭代次數
figure(2);
weights = ones(1,3)';
for j=1:1:200
    for k = 1:1:r
        h = sigmoid2(sum(A(k,:,:)*weights));
        error = L(k) - h;
        weights = weights + alpha * error * A(k,:,:)';
    end
    hold on
    subplot(3,1,1);
    %axis([1 2000 -3 1]);
    plot(j,weights(1),'r.');
    hold on
    subplot(3,1,2);
    plot(j,weights(2),'b.');
    hold on
    subplot(3,1,3);
    plot(j,weights(3),'k.');
end

function result = sigmoid2(inX)
   result = 1.0 / (1 + exp(-inX));
end

分類邊界:



從上圖結果來看,分類器錯分的樣本點很多。原因是,梯度上升法的分類邊界是在整個數據集上經過500次迭代得到的。目前改進的梯度上升法,沒有迭代。

此外,一個判斷優化算法優劣的可靠方法是看它是否收斂,也就是說參數是否達到了穩定值,是否還會不斷變化。現在讓隨機梯度法在數據集上運行200次,得到下圖:

200次迭代過程中,3個參數的收斂情況:從上到下分別對應w0,w1,w2【不知道書上的圖是怎麼畫的,反正我這邊是這樣的】


產生上述波動的原因,是存在一些不能正確分類的樣本點【數據集並非線性可分】,在每次迭代時會引發數據的變化。下面將通過隨機梯度法,來避免波動,從而收斂到某個值,並加快收斂速度。


4.隨機梯度上升法

改進有兩個地方:

。alpha在每次迭代的時候都會調整,alpha的值是不斷變小的,但永遠不能減小到零,保證在多次迭代之後,新數據仍然有一定的影響。

。通過隨機選取樣本來更新迴歸係數,另外增加了一個迭代次數參數。

clc;
clear;

load x
load y
load label

[r,c] = size(x);
A = ones(1,r)';
A = [A x y];
L = label;
weights = ones(1,3)';

figure(1);
tmp = [];
for j = 1:1:200
    for k = 1:1:r
        alpha = 4/(j+k+1) + 0.01;
        index = floor(random('unif',1,r,1,1));
%         k = find(tmp == index);
%         if isempty(k)
%             tmp = [tmp index];
%         else
%             continue;
%         end
        h = sigmoid2(sum(A(index,:)*weights));
        error = L(index) - h;
        weights = weights + alpha * error * A(index,:)';
    end
    hold on
    subplot(3,1,1);
    %axis([1 2000 -3 1]);
    plot(j,weights(1),'r.');
    hold on
    subplot(3,1,2);
    plot(j,weights(2),'b.');
    hold on
    subplot(3,1,3);
    plot(j,weights(3),'k.');
end

%%畫圖
figure(2);
for k = 1:1:r
    hold on
    if L(k,1) == 1
        plot(A(k,2),A(k,3),'r+');
    else
        plot(A(k,2),A(k,3),'b*');
    end
end

%%畫分割線
hold on
dx = -3:3;
dy = (-weights(1)-weights(2)*dx)/weights(3);
plot(dx,dy,'k-','lineWidth',3);


function result = sigmoid2(inX)
   result = 1.0 / (1 + exp(-inX));
end

這邊我很質疑《機器學習python實戰》書上的描述和截圖,首先我根本沒看見之前算法的週期性波動和高頻,反而在隨機算法中看到了。【他座標軸axis改動過】

另外一個,就是每次隨機選取樣本後,要從樣本里踢出的,我這麼改了,收斂速度的確快了很多,但是,分類邊界線錯誤沒有改變很多。所以我不踢出了

分類邊界線:



各個參數的迭代結果:【只有w0是收斂比較快的,w1,w2收斂了嘛?】



這種隨機梯度算法相比第一種梯度上升法的計算量:第一種要500次循環,每次300次乘法,現在是200次循環,每次3次乘法1次加法,從矩陣的運算變成值的運算。運算量的確減少了。


三、病馬預測實例

368個樣本,21個特徵點,選300個作爲訓練用,68個作爲測試

書中扯到數據丟失的處理問題,我看了下文本數據,他都弄好了,不要我們自己處理了。

大概意思就是:特徵點丟失,用0代替;類標籤丟失,整條記錄捨棄。所以只剩299個樣本做訓練,67個作爲測試樣本了。


現在將完整展示,如何通過logistic迴歸進行分類預測:

clc;
clear;

load train
load test

[r,c] = size(test);
T_data = train(:,1:21);
T_lab = train(:,22);

P_data = test(:,1:21);
P_lab = test(:,22);

%訓練
weights = train_weights(T_data,T_lab,500);

%預測
% fuck = 0;
% for shit = 1:1:10
    err_count = 0;
    for k=1:1:r
        predict = my_classify(P_data,weights);
        if predict ~= P_lab(k)
            err_count = err_count + 1;
        end
    end
    err_rate = err_count / r;
    disp('the error rate of this test is :');
    err_rate
%     fuck = fuck + err_rate;
% end
% disp('the average error rate of 10 time test is:');
% fuck/10

function weights = train_weights(Data,Lab,iter)
    [r,c] = size(Data);
    weights = ones(1,c)';
    
    for j = 1:1:iter
        for k = 1:1:r
            alpha = 4/(j+k+1) + 0.01;
            ctime = datestr(now, 30);%取系統時間
            tseed = str2num(ctime((end - 5) : end)) ;%將時間字符轉換爲數字
            rand('seed', tseed) ;%設置種子,若不設置種子則可取到僞隨機數
            index = randint(1,1,r) + 1;
            h = sigmoid2(sum(Data(index,:)*weights));
            error = Lab(index) - h;
            weights = weights + alpha * error * Data(index,:)';
        end
    end
end

function result = sigmoid2(inX)
   result = 1.0 / (1 + exp(-inX));
end

function result = my_classify(Data, weights)
    prob = sigmoid(sum(Data*weights));
    if prob > 0.5
        result = 1;
    else
        result = 0;
    end
end

function a = sigmoid(inX)
    a = 1.0 / (1 + exp(-inX));
end

比較蛋疼的問題是,每次跑matlab,隨機數都一樣。。。我已經把系統時間當seed了啊。。。



今天就寫到這裏吧。誰能解決matlab隨機數問題的,可以留言。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章