本文聊聊逻辑回归(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回归优点:
实现简单;
分类时计算量非常小;
速度很快,存储资源低;
缺点:
容易欠拟合;
一般准确度不太高;
必须线性可分
基于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