逻辑回归

逻辑回归是机器学习中最重要的概念之一。所谓逻辑回归就是在几组数据集中,利用广义线性回归的方法,对数据进行分类的方法。虽然他叫回归,但本质上是利用回归方法做了分类。其使用前提是:

  1. 数据符合伯努利二项分布1,也就是说各项分类之间相互独立,每次成功的概率为 p,即$x,y\sim B(\pm1,p)$
  2. 样本数据线性可分
  3. 特征空间不是特别大

考虑一个数据集的类型分布为 0 或 1 两种,我们需要对分布在其上的数据进行类别划分,也就是说在一个数集上,$f(x)\to{0,1}$,那么我们使用 sigmoid 核函数对类型进行激活,其公式如下

$$ g(z) = \frac{1}{1+e^{-z}} $$

通过绘制他的图形,可以看到其是个在坐标轴上的 S 型曲线,如图所示 sigmoid 函数图形 可以看到,当图形在 $(0,1)$ 之间分布区分,在 $z\to-\infty$ 时,类别趋近 0,在 $z\to\infty$ 时,类别趋近 1,且两者分别在 0 轴附近进行显著区分,以 0.5 作为分界线。所以我们只需要让我们的两个类别的数据在 0 轴左右尽可能的相互远离,类别就会在 sigmoid 函数上显现。那么我们设 $h_\theta(x)$ 为变量$x$在线性权重为 $\theta$ 时的类别为 1 概率,即,设$x$的线性函数:

$$ z = \theta_0 + \theta_1x_1 + \theta_2x_2 … + \theta_nx_n = \theta^Tx $$

那么他在 sigmoid 核函数的值就是其数据类别为 1 的概率,也就是:

$$ h_\theta(x) = g(\theta^Tx) = \frac{1}{1+e^{-\theta^Tx}} $$

我们就把分类问题,转换成了求线性参数 $\theta$,让 $h_\theta(x)$ 的值更接近我们样本数据的类别分布,把一个非线性的问题,转换成了线性问题。所以可以说线性回归是一切机器学习算法的基础。

损失函数

我们让 $h_\theta(x)$ 为数据类别为 1 时的概率,因为类型是二元的,我们可以得到各类别的概率:

$$ P(y=1|x;\theta) = h_\theta(x) $$ $$ P(y=0|x;\theta) = 1 - h_\theta(x) $$

又因为类别不是 0 就是 1,不妨我们可以做一个统一的公式:

$$ P(y|x;\theta) = (h_\theta(x))^y(1-h_\theta(x))^{1-y} $$

又因为所有数据都是相互独立的,所以我们可以写出如下的似然函数:

$$ \begin{align} L(\theta) &= P(y|x;\theta)\\\
&= \prod P(y_i|x_i;\theta)\\\
&= \prod (h_\theta(x_i))^y_i(1-h_\theta(x_i))^{1-y_i} \end{align} $$

我们求其对数,将乘法简化为加法:

$$ \begin{array}{lcl} l(\theta) & = & logL(\theta) \\\
& = & \sum y_ilogh(x_i)+(1-y_i)log(1-h(x_i)) \end{array} $$

现在我们想求得最接近数据类型分布的 $\theta$,也就是取值 $\theta$ 让 $l$ 的值最大,而这个 $l$ 正是我们的损失函数(的反数,实际使用的时候取负就行了)。求值过程也就是最大似然估计法,实际操作中,我们用梯度上升法,也就是对每次迭代的 $\theta$ 迭代一个在其位置的导数,令其快速接近最大似然值:

$$ \theta_{j+1} = \theta_j + \alpha (y_j - h_\theta(x_j))x_j $$

生成模拟数据

有了以上的理论知识,我们可以进行实验。首先我们创建一系列模拟数据,创建的两组相互区分的二维数据,假设两组数据的分布接近 $(-1,-1)$ 和 $(1,1,)$,那么算法应该能把两组数据区分到 $y_0=(-1,-1)$ 和 $y_1=(1,1)$,假设分布满足高斯分布,则创建数据:

import numpy as np

N = 100
# 创建0.1为协方差的,均值在(-1,-1)附近的,满足高斯分布的2维数据集

x_zeros = np.random.multivariate_normal(
    mean=np.array((-1,-1)),
    cov=.1*np.eye(2),
    size=(N//2,)
)
y_zeros = np.zeros((N//2,))

# 创建0.1为协方差的,均值在(1,1)附近的,满足高斯分布的2维数据集

x_ones = np.random.multivariate_normal(
    mean=np.array((1,1)),
    cov=.1*np.eye(2),
    size=(N//2,)
)
y_ones = np.ones((N//2,))

我们绘制一下这个图像:

%matplotlib inline
import matplotlib
import numpy as np
import matplotlib.pyplot as plt

plt.scatter([x[0] for x in x_zeros], [x[1] for x in x_zeros], c="blue")
plt.scatter([x[0] for x in x_ones], [x[1] for x in x_ones], c="red")

数据分布图

我们生成了两组数据,他们在二维空间中明显区分。

创建训练网络

通过理论分析,我们创建训练网络:

import tensorflow as tf

with tf.name_scope("placeholders"):
    x = tf.placeholder(tf.float32, (N, 2))
    y = tf.placeholder(tf.float32, (N,))
with tf.name_scope("weights"):
    W = tf.Variable(tf.random_normal((2,1)))
    b = tf.Variable(tf.random_normal((1,)))
with tf.name_scope("prediction"):
    # 创建 z 函数的定义

    y_logit = tf.squeeze(tf.matmul(x,W) + b)
    # 利用 sigmoid 函数计算类别

    y_one_prob = tf.sigmoid(y_logit)
    # 四舍五入求整获得类型

    y_pred = tf.round(y_one_prob)
with tf.name_scope("loss"):
    # 定义损失函数,这里使用的是交叉熵损失函数,本质上和负对数似然损失函数是一样的

    entropy = tf.nn.sigmoid_cross_entropy_with_logits(logits=y_logit, labels=y)
    l = tf.reduce_sum(entropy)
with tf.name_scope('optim'):
    # 以 0.01 为步长,定义梯度下降法

    train_op = tf.train.AdamOptimizer(0.01).minimize(l)
with tf.name_scope("summaries"):
    tf.summary.scalar("loss", l)
    merged = tf.summary.merge_all()

迭代训练

我们对训练网络进行迭代训练:

n_steps = 1000 # 训练迭代 1000 次

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for i in range(n_steps):
        feed_dict = {x: x_np, y: y_np}
        _, summary, loss = sess.run([train_op, merged, l], feed_dict=feed_dict)
        print("loss: %f" % loss)
        train_writer.add_summary(summary, i)

    # Get weights

    w_final, b_final = sess.run([W, b])

    # Make Predictions

    y_pred_np = sess.run(y_pred, feed_dict={x: x_np})

得到训练结果

训练结果

可以看到,数据的损失函数在 1000 次迭代后,降到了 0.22

评估训练结果

我们使用 sklearn 的 accuracy_score 来计算分类准确性,他是计算所有分类正确的比例,即分类正确的数量比上总数量的比例:

from sklearn.metrics import accuracy_score

score = accuracy_score(y_np, y_pred_np)
print("分类指标: ", score)
>>> 分类指标 1.0

我们可以看到,所有的数据都被正确的分类了。 我们再绘制分类函数的图像,以获得更直观的体现:

from scipy.special import logit

plt.clf()
plt.xlabel("x")
plt.ylabel("y")
plt.title("True Model versus Learned Model ")

x_left = -2
# 计算概率为 0.5 时,x2 所在代位置

y_left = (1./w_final[1]) * (-b_final + logit(.5) - w_final[0]*x_left)

x_right = 2
# 计算概率为 0.5 时,x2 所在代位置

y_right = (1./w_final[1]) * (-b_final + logit(.5) - w_final[0]*x_right)

plt.scatter([x[0] for x in x_zeros], [x[1] for x in x_zeros], c="blue")
plt.scatter([x[0] for x in x_ones], [x[1] for x in x_ones], c="red")

plt.plot([x_left, x_right], [y_left, y_right], color='k')

得到图像:

分类结果显示

可以看到数据被合理区分了。 再使用命令查看线性函数计算的下降过程:

tensorboard --logdir logs/train

得到图像:

损失函数下降曲线

我们可以看到损失函数的下降基本达到了其极限。

问题回顾

我在运行中遇到了如下问题

---------------------------------------------------------------------------  
TypeError Traceback (most recent call last) 
<ipython-input-3-8562f2a61f5e> in <module>
6 mean=np.array((-1,-1)),
7 cov=.1*np.eye(2),  
----> 8  size=(N/2,)   
9 )  
10 y_zeros = np.zeros((N/2,))

mtrand.pyx in mtrand.RandomState.multivariate_normal()
mtrand.pyx in mtrand.RandomState.standard_normal()
mtrand.pyx in mtrand.cont0_array()
TypeError: 'float' object cannot be interpreted as an integer

这是因为在 python3 中,除号 $/$ 默认是返回浮点数,需要像 python2 一样返回整数的话,需要用双除,即 $//$

总结

本文学习了逻辑回归的基本概念,并用 tensorflow 实现了逻辑回归的基本操作。其基本操作也如线性回归一样,就是 定义模型,设定损失函数,迭代,评估。逻辑回归的公式虽然多,但是基本思路还是线性回归的思路,同时他也是机器学习的重要方法,特别是 sigmoid 函数,是最常见的激活函数之一,非常重要,我们在后续的学习中还会反复遇到他。


  1. https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%A0%85%E5%88%86%E4%BD%88 ↩︎