logistic regression(1)

  憋了好久,终于找到些不一样的东西分享出来,是关于逻辑回归的实现。其实有不少革命先驱已经分享了各种不同语言来实现逻辑回归,那么我的独特啥地方呢?作为 Rstudio 的忠实用户和粉丝,基于 Rcpp 包来完成的,算法实现用 C++ , 算法调用交还给 R , 其实也就是简单模拟一下算法开发。

  具体逻辑回归算法的推导,就不罗嗦了,网络上有大把大把详尽的资源。这里要引入两个作为结果的公式,它们是算法的核心,算法实现围绕这两个公式:

  • J(θ)=1mi=1n(yiloghθ(xi)+(1yi)log(1hθ(xi)))

  • θj:=θjα1mi=1m(hθ(x(i))yi)x(i)j

  一个是逻辑回归的损失函数,一个是批量梯度下降参数的迭代公式,整个算法核心部分就是依靠这两个公式来,这里特殊说明字母 i 代表数据集的行,字母 j 代表数据集的列,清楚这些,实现代码时候就不会搞混了。
  Rstudio 中可创建cpp 文件,不多说,直接上代码:

#include <Rcpp.h>
#include <math.h>

using namespace Rcpp;
using namespace std;

// [[Rcpp::export]]
double sigmoid(double z)
{
  return exp(z) / (1+exp(z));
}

// [[Rcpp::export]]
NumericVector Logitis(NumericMatrix x, const int &nrow, const int &ncol, 
                      NumericVector y, double learning_rate, int max_iter, double min_error) {
  // i nrow  
  // j ncol 
  // x = i X j  
  int iter = 0;
  double final_loss = 10;
  // inital weight
  NumericVector weight(ncol); 
  for (int j = 0; j < ncol; ++j) {
    weight[j] = 1; 
  }
  // set iter times and control error
  while (iter < max_iter && final_loss > min_error){
    // Batch gradient descent
    NumericVector sum(ncol);
    // calculate sum
    for (int k = 0;k < ncol; ++k){
       for (int i = 0;i < nrow; ++i){
         double h = 0;
           for (int j = 0;j < ncol;j++){
              h += weight[j] * x(i, j);
           }
         sum[k] += (y[i] - sigmoid(h)) * x(i, k);
        }
    }
    // update weight
    for (int k = 0; k < ncol; ++k){
       weight[k] += learning_rate * ((double) 1 / nrow) * sum[k];
    }
    }
    // Square error
    double loss = 0;
    final_loss = 0;
    for (int i = 0;i < nrow;++i){
      double h = 0;
      for (int j = 0;j < ncol;j++){
        h += weight[j] * x(i, j);
      }
      loss += (y[i] * log10(sigmoid(h)) + (1 - y[i]) * log10(1 - sigmoid(h)));
    }
    final_loss = - ((double) 1 / nrow) * loss;
    cout << "error: "<< final_loss << endl;
    iter++;
  }
  return weight;
}
// [[Rcpp::export]]
LogicalVector Logitis_predict(NumericMatrix x, NumericVector weight, 
                              const int &nrow, const int &ncol) {
  // call Logitis and train model
  LogicalVector Y(nrow);
  for (int i = 0; i < nrow; i++){
    double h = 0;
    for (int j = 0; j < ncol; j++){
      h += weight[j] * x(i, j);
    }
    if (sigmoid(h) >= 0.5) {
      Y[i] = 1;
    } else {
      Y[i] = 0;
    }
  }
  return Y;
}

  这里sigmoid是逻辑回归的经典S曲线函数, Logitis来实现批量梯度下降训练出模型的权重, Logitis_predict使用模型权重来做预测,这里Logitis以最大迭代数和损失函数来控制迭代次数来保证模型最后收敛。还有这里NumericMatrix,NumericVector,LogicalVector这是 Rcpp 特有的类类型用来与R无缝对接,下面来看看它们如何无缝对接。

library(Rcpp)
# load Cpp file
sourceCpp(file = "logitis.cpp")
# DataSet
DataSet <- iris[1:100, ]
DataSet[, 5] <- ifelse(DataSet$Species == "setosa", 1, 0)
index <-  sample(2, nrow(DataSet), replace = TRUE, prob = c(0.7, 0.3))
# train data
trainData <- DataSet[index == 1, ]
train_X <- as.matrix(trainData[, -5])
train_Y <- trainData[, 5]
# test data
testData <- DataSet[index == 2, ]
test_X <- as.matrix(testData[, -5])
test_Y <- testData[, 5]
for (i in c(0, 10, 15, 20)){
  # weight
  weight <- Logitis(train_X, nrow = nrow(train_X), ncol = ncol(train_X), train_Y, 0.1, i, 0.1)
  # predict 
  pred_y <- Logitis_predict(test_X, weight, nrow = nrow(test_X), ncol = ncol(test_X))
  # Evaluation
  evaluation_result <- pred_y == test_Y
  cat("Accuracy: ", length(evaluation_result[evaluation_result == TRUE]) / length(test_Y), "\n")
}

  这里其实 R 作为主程序启动, C++ 代码保存在 logitis.cpp 文件中, 通过Rcpp包下的函数sourceCpp来加载cpp文件中的定义的函数到全局环境下让 R 来调用,因为算法本身是针对二分类问题,所以截取了iris 数据集的前两类来测试算法的效果。

Accuracy:  0.5454545 
error: 2.49176
error: 1.75373
error: 1.03305
error: 0.472978
error: 0.322857
error: 0.298091
error: 0.277371
error: 0.258426
error: 0.24134
error: 0.225961
Accuracy:  0.6969697 
error: 2.49176
error: 1.75373
error: 1.03305
error: 0.472978
error: 0.322857
error: 0.298091
error: 0.277371
error: 0.258426
error: 0.24134
error: 0.225961
error: 0.212105
error: 0.199599
error: 0.188288
error: 0.178033
error: 0.168713
Accuracy:  0.969697 
error: 2.49176
error: 1.75373
error: 1.03305
error: 0.472978
error: 0.322857
error: 0.298091
error: 0.277371
error: 0.258426
error: 0.24134
error: 0.225961
error: 0.212105
error: 0.199599
error: 0.188288
error: 0.178033
error: 0.168713
error: 0.16022
error: 0.152461
error: 0.145354
error: 0.138829
error: 0.132821
Accuracy:  1 
error: 2.49176
error: 1.75373
error: 1.03305
error: 0.472978
error: 0.322857
error: 0.298091
error: 0.277371
error: 0.258426
error: 0.24134
error: 0.225961
error: 0.212105
error: 0.199599
error: 0.188288
error: 0.178033
error: 0.168713
error: 0.16022
error: 0.152461
error: 0.145354
error: 0.138829
error: 0.132821
error: 0.127278
error: 0.122151
error: 0.117397
error: 0.112981
error: 0.108869
error: 0.105033
error: 0.101447
error: 0.0980892
Accuracy:  1 

  当迭代次数为零时,基本是瞎蒙的预测,但是随着迭代次数的增加模型误差率在逐步减小,准确率也在提升,最终达到了100%
  因为数据量本身不大,加上数据比较特殊,所以有这样的结果,使用 R 的 glm 函数来训练一个模型对比一下效果。

gmodel <- glm(Species~., data=trainData, family = binomial(link = "logit"))
temp <- predict(gmodel, as.data.frame(test_X))
rpred_y <- (exp(temp) / (1 + temp)) >= 0.5
table(rpred, test_Y)
#            test_Y
# rpred         0  1
#        FALSE 15  0
#        TRUE   0 18

  观察混淆矩阵,发现准确率也是100%,所以并不是我模型训练的好,而是数据集本身就比较特殊,可分划分性强,不过也间接说明了我实现的模型和原生态的效果一下,(沾沾自喜一下), 回头再通过比较大的数据量来对比测试一下它们的性能差距好了。

猜你喜欢

转载自blog.csdn.net/qq_24834541/article/details/78420504