Tensorflow 体验篇-2 使用 Tensorflow 做逻辑回归
逻辑回归
逻辑回归是机器学习中最重要的概念之一。所谓逻辑回归就是在几组数据集中,利用广义线性回归的方法,对数据进行分类的方法。虽然他叫回归,但本质上是利用回归方法做了分类。其使用前提是:
- 数据符合伯努利二项分布1,也就是说各项分类之间相互独立,每次成功的概率为 p,即$x,y\sim B(\pm1,p)$
- 样本数据线性可分
- 特征空间不是特别大
考虑一个数据集的类型分布为 0 或 1 两种,我们需要对分布在其上的数据进行类别划分,也就是说在一个数集上,$f(x)\to{0,1}$,那么我们使用 sigmoid 核函数对类型进行激活,其公式如下
$$ g(z) = \frac{1}{1+e^{-z}} $$
通过绘制他的图形,可以看到其是个在坐标轴上的 S 型曲线,如图所示 可以看到,当图形在 $(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 函数,是最常见的激活函数之一,非常重要,我们在后续的学习中还会反复遇到他。