逻辑回归杂谈

浏览: 1455

本文聊聊逻辑回归(logistic regression)。

逻辑回归虽然名字中带有回归二字,但是它并不是一种回归算法,而是一种分类算法,并且它是在工业界十分常用的一种分类算法。跟回归问题不同,logistic回归中的因变量是分类型变量(比如0,1这种二分类或者0,1,2,3这种多分类问题)。logistic回归的自变量和因变量之间不一定是呈线性关系的。logistic回归最终得到的是因变量取某个值的概率与所有自变量之间的关系。

逻辑回归其实是一种广义线性模型(generalized linear model)。逻辑回归既可以用于二分类,又可以用于多分类,比较常用的是二分类,这种情形比较容易解释。

逻辑回归首先通过一个线性函数对自变量进行变换,然后利用sigmoid函数进行变换,可以得到取值为1的概率,然后将此概率与给定的阈值相比较即可预测某个样本属于0这一类还是属于1这一类。线性函数即为所有特征乘以相应的权重然后累加再加上偏置。sigmoid函数形式如下:

               f(x) = 1/(1+e^-x)

逻辑回归中的参数一般使用极大似然法(maximum likelihood estimation)来估计。对于第i个样本,它的因变量y_i = 1的概率是p_i,则y_i = 0的概率就是1-p_i,那么针对第i个样本,它的似然概率为

                p(y_i)= p_i^y_i * (1-p_i)^(1-y_i) 。

所有样本的似然概率相乘就得到整个样本集的似然概率。通常都是对这个似然函数取对数,然后再估计它的极大值就可以得到逻辑回归中所有参数的估计值。

求解逻辑回归,其实是一个最优化问题,一般可以利用随机梯度下降法(stocastic gradient decsent)和拟牛顿迭代法(quasi-newton method)来优化。

梯度下降法存在一个问题,即容易陷入局部最优,并且每次对当前样本计算损失函数时,都需要遍历全部样本才能得到损失函数的值,因此会造成计算速度比较慢。

而对于随机梯度下降法,它在计算损失函数的时候只计算当前的代价,最终损失函数是在全部样本迭代一遍然后再求和。更新参数的时候并不是依次遍历样本,而是从样本集中随机选择一个样本进行计算,这种方法收敛速度快,并且可以避免局部最优,并且可以借助参数服务器加以并行。

还有一些其他优化算法,比如拟牛顿法,BFGS,L-BFGS等。

如果在训练集上拟合得很好,但是在预测集上却达不到这种效果,这就称为过拟合现象。为了避免过拟合,可以采取以下措施:

减少特征个数,既可以人工选择保留多少特征,也可以利用算法来选取特征

正则化,其中包含l1正则化(这种方法使得部分特征的权重为0)和l2正则化(这种方法会迫使特征的权重比较小)。

逻辑回归借助softmax函数可以用于多分类问题。它可以输出某个样本属于哪一类的概率,并且满足条件全部概率加起来=1。那么对于多分类问题,如何在softmax和多个LR之间选择呢?

如果所有类别之间互斥(比如苹果,梨,橘子,桃),利用softmax。如果类别之前有联系(比如多个歌手的歌手混在一起时区分哪种歌声来自哪个歌手),这个时候使用多个逻辑回归更为合适。

 

优缺点:

Logistic回归优点:

实现简单;

分类时计算量非常小;

速度很快,存储资源低;

缺点:

容易欠拟合;

一般准确度不太高;

必须线性可分

image.png

基于spark的逻辑回归示例如下:

下面的例子是利用二项和多项逻辑回归模型进行二分类的示例,其中加入了约束项。

首先是利用多项逻辑回归来训练二分类问题,这是因为二分类问题是多分类问题的一种特例。

import org.apache.spark.ml.classification.{
   LogisticRegression}

// Load training data

val training = spark.read.format("libsvm")
 .
load("data/mllib/sample_libsvm_data.txt")

val lr = new LogisticRegression()
 .setMaxIter(10)
 .setRegParam(0.3)
 .setElasticNetParam(0.8)

// Fit the model

val lrModel = lr.fit(training)

// Print the coefficients and intercept
// for logistic regression

println(s"Coefficients:"+
   s"${lrModel.coefficients}"+
   s"Intercept: ${lrModel.intercept}"
)

// We can also use the multinomial family
// for binary classification

val mlr = new LogisticRegression()
 .setMaxIter(10)
 .setRegParam(0.3)
 .setElasticNetParam(0.8)
 .setFamily("multinomial")

val mlrModel = mlr.fit(training)

// Print the coefficients and intercepts
// for logistic regression with
// multinomial family

println(s"Multinomial coefficients: " +
   s"${mlrModel.coefficientMatrix}"
)

println(s"Multinomial intercepts: " +
   s"${mlrModel.interceptVector}"
)

下面是利用二项逻辑回归训练二分类问题。

import org.apache.spark.ml.classification.
 {BinaryLogisticRegressionSummary,
   LogisticRegression}

// Extract the summary from the returned
// LogisticRegressionModel
// instance trained in the earlier example

val trainingSummary = lrModel.summary

// Obtain the objective per iteration.

val objectiveHistory = trainingSummary.
   
objectiveHistory

println("objectiveHistory:")

objectiveHistory.foreach(
   loss => println(loss))

// Obtain the metrics useful to
// judge performance on test data.

// We cast the summary to a
// BinaryLogisticRegressionSummary
// since the problem is a
// binary classification problem.

val binarySummary = trainingSummary.
   asInstanceOf[
         BinaryLogisticRegressionSummary]

// Obtain the receiver-operating
// characteristic
// as a dataframe and areaUnderROC.

val roc = binarySummary.rocroc.show()

println(s"areaUnderROC: "+
   s"${binarySummary.areaUnderROC}"
)

// Set the model threshold to
// maximize F-Measure

val fMeasure = binarySummary.
   fMeasureByThreshold

val maxFMeasure = fMeasure
   .select(max("F-Measure"))
     .
head().getDouble(0)

val bestThreshold = fMeasure
   .where($"F-Measure" === maxFMeasure)
 .select("threshold").head().getDouble(0)

lrModel.setThreshold(bestThreshold)

下面是逻辑回归用于多分类问题的示例。

import org.apache.spark.ml.classification.{
   LogisticRegression}

// Load training data

val training = spark
  .read.format("libsvm")
 .load("data/mllib/sample_"+
       "multiclass_classification_data.txt"
)

val lr = new LogisticRegression()
 .setMaxIter(10)
 .setRegParam(0.3)
 .setElasticNetParam(0.8)

// Fit the model

val lrModel = lr.fit(training)

// Print the coefficients and intercept
// for multinomial logistic regression

println(s"Coefficients:"+
   s"\n${lrModel.coefficientMatrix}"
)

println(s"Intercepts: "+
   s"${lrModel.interceptVector}"
)

基于TensorFlow的逻辑回归示例代码如下:

import tensorflow as tf

## n_features表示特征个数
## n_labels表示类别数
## lambda_是约束因子或称规约系数
def tf_create(n_features, n_labels, lambda_):
   
   examples = tf.placeholder(tf.float64,
       [None, n_features])
   labels = tf.placeholder(tf.float64,
       [None, n_labels])
   weights = tf.Variable(tf.zeros(
       [n_features, n_labels],
       dtype=tf.float64))
   bias = tf.Variable(tf.zeros(
       [n_labels],
       dtype=tf.float64))
   
   hyp = tf.sigmoid(tf.matmul
       (examples, weights) + bias)
   loss = tf.reduce_mean(
           -labels * tf.log(hyp) -
           (1 - labels) * tf.log(1 - hyp))
   reg = lambda_ * tf.nn.l2_loss(weights)
   cost = loss + reg
   
   train = tf.train.AdamOptimizer()
         .minimize(cost)
   predict = tf.argmax(hyp, axis=1)    
   def train_(sess, X, y, iterations):
       for i in range(iterations):
           sess.run(train,
               feed_dict=
                 {examples: X, labels: y})            
   def predict_(sess, X):        
       return sess.run(predict,
           feed_dict={examples: X})    
   return train_, predict_

下面是选择两个特征,三个类别的示例

X = data.as_matrix(columns=
   ['alcohol', 'flavanoids'])

y = label_binarize(data['class'],
   [1, 2, 3])

## 训练测试比例为3:1
X_train, X_test, y_train, y_test =
   train_test_split(X, y,
         test_size=0.25)

with tf.Session() as sess:
   
   train, predict = tf_create(
       X.shape[1],
       y.shape[1],
       0.0)
   sess.run(tf.global_variables_initializer())
   ## 30000 次迭代
   train(sess, X_train, y_train, 30000)

   predictions = predict(sess, X_test)
   y_test = y_test.argmax(axis=1)
   ## 准确率    
   print('accuracy:',
       accuracy_score(y_test,
           predictions))
   ## 精准率
   print('precision:',
       precision_score(y_test,
           predictions, average='macro'))
   ## 召回率
   print('recall:',
       recall_score(y_test,
           predictions, average='macro'))

   plot_boundary(X_train,
           lambda x: predict(sess, x))
   plot_points(X_train,
           y_train.argmax(axis=1))

下面示例是对原始特征进行多项式变换,如此一来就可以得到非线性的分割线或分割面。这里类似于支持向量机中将原始特征升至高维特征空间中。

X = data.as_matrix(columns=
   ['alcohol', 'flavanoids'])

X = transform(X)

## 其余代码跟上面的例子一样



参考资料:

http://spark.apache.org/docs/latest/ml-classification-regression.html

https://github.com/crsmithdev/notebooks/blob/master/ml-logistic-regression/ml-logistic-regression.ipynb

推荐 0
本文由 深度学习 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册