1. 程式人生 > >深度學習實踐(一)——logistic regression

深度學習實踐(一)——logistic regression

一、準備

為了更深入的理解logistic regression,筆者基本採用純C++的手寫方式實現,其中矩陣方面的運算則呼叫opencv,資料集則來自公開資料集a1a。
實驗環境:

關於配置方面的操作,請參考一下連結:Win10下OpenCV環境搭建(VS2017+OpenCV3.2.0)

二、logistic regression理論基礎

如果想系統的瞭解logistic regression,筆者推薦吳恩達的深度學習系列課程,尤其是其中的實踐作業,需要認真做。


下面筆者簡略的介紹下logistic regression。
這裡寫圖片描述


如上圖就是一個logistic regression的典型例子:

  1. 一張貓的圖片根據rgb可以看成是0-255的之間的數字,所以圖片就轉換成為了一列向量X。
  2. 定義一個維度為(1,X0)維度的引數W行向量,其中X0指圖片列向量的行數。
  3. 將W和X相乘(矩陣相乘),再加上偏置b(為實數),則得到Z。
  4. 再用sigmoid進行限制到(0,1)範圍,輸出A。
  5. 定義loss,並使用梯度下降演算法,更新引數W和b,使A的輸出越來越接近標籤Y。

下面是一些基本公式:
For one example

x ( i ) :

(1)
z ( i ) = w T x ( i ) + b

(2) y ^ ( i ) = a ( i ) = s i g m o i d ( z ( i ) )
(3) s i g m o i d ( w T x + b ) = 1 1 + e ( w T x + b )
(4) L ( a ( i ) , y ( i ) ) = y ( i ) log ( a ( i ) ) ( 1 y ( i ) ) log ( 1 a ( i ) )

The cost is then computed by summing over all training examples:

(5) J = 1 m i = 1 m L ( a ( i ) , y ( i ) )

sigmoid是限制輸出的結果在(0,1)內,它的影象如下:
這裡寫圖片描述
上面loss的公式採用交叉熵代價函式

梯度下降演算法:
梯度下降的一個最直觀的解釋:可以看成從山上走下山的過程。

這裡寫圖片描述
參考連結:

三、實踐

筆者採用的是a1a資料集,其原型為UCI的Adult Data Set ,其大概意思是根據人的特徵來判斷你是否每年的工資大於50k,所以這是一個二分分類問題。
a1a資料集對其進行了簡化,其一共有123個特徵,如下所示為其一行的資料-1 5:1 6:1 17:1 21:1 35:1 40:1 53:1 63:1 71:1 73:1 74:1 76:1 80:1 83:1,其中-1表示未能超過50k(即負類,實際程式設計可以置為0),接著我們可以初始化一個一行零向量(1,123),5:1表示第5個位置為1,以下類推……這樣我們對其資料就有了個大概認識。


接著我們就開始編寫處理資料的函式。這裡需要一些基礎知識,可以參考以下部落格:

void creatMat(Mat &x,Mat &y,String fileName) {
    int line_count = 0;//記錄行數,在矩陣賦值時起到用處
    char buffer[256];//快取區
    ifstream in(fileName);//定義讀取檔案資料流
    if (!in.is_open()) {
        cout << "Error opening file"; exit(1);
    }
    while (!in.eof())
    {
        in.getline(buffer, 100);//按行讀取
        //因為讀取的是字串,下面採用stringstream和atof()將字串轉為浮點數
        stringstream stream;
        stream << buffer;
        string temp_s;//這裡的目的主要是跳過空格
        stream >> temp_s;
        double num1 = atof(temp_s.c_str());//num1為類別標籤即-1或+1
        if (num1 == 1.0) {
            y.at<double>( 0,line_count) = num1;//y矩陣即為標籤矩陣,其已經被初始化為0,所以只要將1的標籤賦值即可
        }
        while (stream >> temp_s) {
            int index = temp_s.find(':');
            string temp1_s = temp_s.substr(0, index);//這裡模仿split()函式
            double t1 = atof(temp1_s.c_str());
            string temp2_s = temp_s.substr(index + 1, temp_s.length());
            double t2 = atof(temp2_s.c_str());
            x.at<double>(t1-1,line_count) = t2;//賦值
        }
        line_count++;
    }
}

然後我們開始編寫sigmoid公式,因為C++和opencv都不帶這個公式。公式為:

(6) s i g m o i d ( Z ) = 1 1 + e ( Z )

Mat sigmoid(const Mat &original) {
    cv::Mat response = original.clone();//防止未初始化和維度不同
    double temp;
    for (int i = 0; i < original.rows; i++) {
        for (int j = 0; j < original.cols; j++) {
            temp = original.at<double>(i, j);
            response.at<double>(i, j) = 1.0 / (1.0 + exp(-temp));

        }
    }
    return response;
}

我們繼續開始編寫cost,公式如下:

(7) J = 1 m i = 1 m { y ( i ) log ( a ( i ) ) ( 1 y ( i ) ) log ( 1 a ( i ) ) }
其中還需用到對矩陣的log,程式碼如下:

Mat change_log(const Mat &original) {
    cv::Mat response = original.clone();//防止未初始化和