回归算法之非线性回归

Python,算法,机器学习 2017-12-25

起步

非线性回归是线性回归的延伸,线性就是每个变量的指数都是 1,而非线性就是至少有一个变量的指数不是 1。生活中,很多现象之间的关系往往不是线性关系。选择合适的曲线类型不是一件轻而易举的工作,主要依靠专业知识和经验。常用的曲线类型有 幂函数,指数函数,抛物线函数,对数函数和S型函数

化非线性回归为线性回归

通过变量代换,可以将很多的非线性回归转化为线性回归。比如目标函数假设是 y = b0 + b1x + b2x^2 。那么另 z1 = x, z2 = x^2 。目标函数就变为 y = b0 + b1z1 + b2z2。就可以用线性回归来解方程了而用上一篇文章《回归算法之线性回归》就能解决线性回归的问题。常见的转化模型有:

非线性方程 变换公式 变换后的方程
双曲函数:1/y = b0 + b1/x X = 1/x, Y = 1/y Y = b0 + b1X
幂函数:y = b0 x1^b1 x2^b2 ...xk^bk y' = ln y, b0' = ln b0,x1' = lnx1,... y' = b0' + b1'x1' + b2'x2' + ... + bk'xk'
指数型函数:y = b0 * e^(b1x) y' = ln y, b0' = ln b0, x' = x y' = b0' + b1'x'
对数函数:y = b0 + b1 * ln x y' = y, x' = ln x y' = b0 + b1 * x'

逻辑回归

逻辑回归( Logistic Regression ) 是非线性回归中的一种,在分类问题上有的也能采用逻辑回归分类。这是一个二分类器。比如根据肿瘤大小判断其良性或恶性,线性方程显然不能胜任了:

Image.png

Image.png

逻辑回归模型中,先给定线性函数:

Z = \theta_0 + \theta_1x_1 + \theta_2x_2 + ... + \theta_nx_n

虽然这边是 θ 表示,但其实和线性回归中 b 是一个意思,都是作为自变量的系数。在二分类器中,经常需要一个分界线作为区分两类结果。再次需要一个函数进行曲线平滑化,由此引入 Sigmoid 函数进行转化:

f(x) = \frac1{1 + e^{-x}}

600px-Logistic-curve.svg.png

这样的,可以以 0.5 作为分界线。因此逻辑回归的最终目标函数就是:

\begin{align*}
h_\theta(X) &= g(\theta_0 + \theta_1x_1 + \theta_2x_2 + ... + \theta_nx_n) \\
&= g(\theta^TX) \\
&= \frac1{1 + e^{-\theta^TX}}
\end{align*}

回归是用来得到样本属于某个分类的概率。因此在分类结果中,假设 y 值是 0 或 1,那么正例 (y = 1):

h_\theta(X) = P(y = 1|X;\theta)

反例(y = -1):

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

回想起之前线性会中用到的损失函数:

\begin{align*}
J(\theta) &= \frac1{2m} \sum_{i=1}^m (h(x^{(i)}) - y^{(i)})^2 \\
&= \frac1{2m}(X\theta - y)^T(X\theta-Y)
\end{align*}

我们的目标很明确,就是找到一组 θ ,使得我们的损失函数 J(θ) 最小。最常用的求解方法有两种:梯度下降法( gradient descent ), 牛顿迭代方法( Newton's method )。两种方法都是通过迭代求得的数值解,但是牛顿迭代方法的收敛速度更加快。牛顿迭代方法在此不介绍。

如果在逻辑回归中运用这种损失函数,得到的函数 J 是一个非凸函数,存在多个局部最小值,很难进行求解,因此需要换一个 cost 函数。重新定义个 cost 函数如下:

cost(y^{(i)}, h_\theta(x^{(i)}) = -y^{(i)}log(h_\theta(x^{(i)})) - (1 - y^{(i)})log(1 - h_\theta(x^{(i)}))

梯度下降求解逻辑回归

1042406-20161017221342935-1872962415.png

这就好比是下山,下一步的方向选的是最陡的方向。梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。θ 的更新方程如下

\theta_j : \theta_j - \alpha \frac{\partial}{\partial \theta_j} J(\theta)

其中,偏导是:

\frac{\partial}{\partial \theta_j} J(\theta) = \frac1m\sum_{i=1}^m (h(x^{(i)}) - y^{(i)})x_j^{(i)}

如果将 θ 视为矩阵,可以进行批量更新:

\begin{align*}
\frac{\partial}{\partial \theta} J(\theta) &= X^T (g(X\theta) - Y) \\
\theta &= \theta - \alpha X^T (g(X\theta) - Y)
\end{align*}

python实现逻辑回归

数据源是保存在一个 txt 文件的,其内容类似于:

-0.017612   14.053064   0
-1.395634   4.662541    1
-0.752157   6.538620    0

读取它的数据的函数为:

def genData():
    train_x = []
    train_y = []
    with open("logistic_set.txt") as f:
        for line in f.readlines():
            line = line.strip().split()
            num = len(line)
            train_x.append([float(line[x]) for x in range(num - 1)])
            train_y.append(float(line[-1]))
    return train_x, train_y

Sigmoid函数:

import numpy as np
def sigmoid(x):
    return 1.0 / (1 + np.exp(-x))

逻辑回归的类:

class LogisticReg(object):
    def __init__(self):
        pass
    def fit(self, x, y, learn_rate=0.0005):
        point_num, future_num = np.shape(x)
        new_x = np.ones(shape=(point_num, future_num + 1)) # 多一列x0,全部设为1
        new_x[:, 1:] = x
        self.theta = np.ones(shape=(future_num + 1, 1))

        x_mat = np.mat(new_x)
        y_mat = np.mat(y).T
        J = []
        for i in range(800):
            h = sigmoid(np.dot(x_mat, self.theta))
            # 打印损失函数
            cost = np.sum([ a * -np.log(b) + (1 - a) * -np.log(1 - b)  for a, b in zip(y_mat, h)])
            J.append(cost)
            self.theta -= learn_rate * x_mat.T * (h - y_mat)
        plt.plot(J)
        plt.show()

    def predict(self, row):
        row = np.array([1] + row)
        result = sigmoid(np.dot(row, self.theta))
        return 1 if result > 0.5 else 0

同样,我们会对这个与 sklearn 中的逻辑回归模型进行对比:

mylog = LogisticReg()
x, y = genData()
test_row = [0.6, 12]
mylog.fit(x, y)
print(mylog.theta)
print("LogisticReg predict:", mylog.predict(test_row))

from sklearn.linear_model import LogisticRegression
sk = LogisticRegression()
sk.fit(x, y)
print(sk.intercept_)
print(sk.coef_)
print("sklearn LogisticRegression predict:", sk.predict([test_row]))

输出:

[[ 3.75294089]
 [ 0.44770259]
 [-0.57020354]]
LogisticReg predict: 0
[ 3.83513265]
[[ 0.44732445 -0.58003724]]
sklearn LogisticRegression predict: [ 0.]

可见,我们计算结果的 θ 还是很接近的,且预测结果一致。并且我们把损失函数的结果打印出来:

20171225133911.png

随着迭代次数的增加,损失函数越来越少。

逻辑回归的优缺点

优点:

  • 预测的结果是界于 0 和 1 的概率;
  • 可以适用于连续型和类别型变量;
  • 容易使用和解释。

缺点:

  • 容易欠拟合,分类精度可能不高;
  • 对异常值敏感;

附录

本次实验的全部代码:https://github.com/hongweipeng/learn_ai_example/blob/master/logistic_example.py


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!