理论基础
逻辑斯蒂回归是机器学习里的一种分类模型。本节将介绍逻辑斯蒂回归相关的理论基础知识。
相关知识
最大似然的思想:假如有一个罐子,罐子里有黑白两种颜色的球,球的数目不知,球的颜色比例也不知。这时候我们从罐子中有放回的拿出10个球,即拿出1个球,记录颜色,再放回罐中摇匀,此操作重复10次。假设记录结果显示拿出了7个白球,3个黑球。请问罐中黑白球比例最可能是多少?大部分人都会毫不犹豫的给出答案,。这背后运用到理论支持其实就是最大似然的思想。
似然,指的就是使这种情况发生。最大似然就是使这种情况最大概率发生。接下来,我们来看看最大似然思想是如何得到这样的结果。
假设抽到黑球的概率是,则抽到白球的概率就是
。此时抽了10次,抽到了7个白球3个黑球,这种情况的概率就是
。为了是这种情况最大概率发生,我们可以得到:
令导数为0,得到公式:
解得P=0.3.故抽到黑球的比例是0.3,黑球和白球的比例就是。
模型特点
- 逻辑斯蒂回归的模型能力:分类
给定样本x的特征,判断样本的标签
。
- 逻辑斯蒂回归的学习方法:有监督学习
需要有训练样本对模型进行训练。 - 逻辑斯蒂回归的模型类型:判别式模型
通过训练的特征和类别
,直接计算
作为模型,直接根据样本的特征
计算类别
。
模型推导
最大似然的思想可以总结为"透过现象看本质"。根据上面黑白球的例子,现象就是抽了10个球,3黑7白,本质就是抽到黑球的概率是0.3。而类比在逻辑斯蒂回归中,现象就是训练样本的特征和标签,本质就是逻辑斯蒂回归(LR)模型的模型参数。我们的任务就是寻找一组LR模型的参数,使得训练样本的特征和标签出现的概率最大。
假设我们有如下m个样本,每个样本有n维特征:
标签。LR模型的模型参数为
,根据最大似然的思想,我们认为这m个样本是已经发生的现实情况,故将问题转化为:
其中,就是LR模型。
我们可以看到,逻辑斯蒂回归模型其实是一个分类模型,并不是回归模型。那为什么会有这么一个名字呢?笔者认为逻辑斯蒂回归可以拆解为逻辑斯蒂(logistic)+线性回归。线性回归模型是一个回归模型,模型可以表示为:
模型的取值为。那如何转化为分类问题呢,就需要一个函数映射,将
映射成
(概率表示)且具有连续、光滑、严格单调和关于中点中心对称。而logistic函数(sigmoid)恰好满足所有的需求。logistic函数为
。绘制函数图像的代码如下:
import matplotlib.pyplot as plt
import numpy as np
import math
'''
def sigmoid(x):
return (1/(1+math.exp(-x)))
#如果用上面的sigmoid,会导致分母溢出而报错,推荐用下面的sigmoid自定义函数
print(sigmoid(-10000000))
'''
def sigmoid(x):
return math.exp(-np.logaddexp(0, -x))
x = np.arange(-10., 10., 0.2)
y = [ sigmoid(x_i) for x_i in x]
plt.plot(x, y)
plt.show()
综合logistic函数和线性回归模型函数,我们可以得到逻辑斯蒂回归模型的模型函数:
我们令表示LR模型的模型函数,即
。如果
,我们可以认为该样本有
的概率是正样本,
的概率是负样本,即
和
。将LR的模型函数代回到最大似然的求解问题中,得到:
由于是累乘,我们使用单调的算子将累乘变成累加,且不影响最终结果。得到:
我们令:
若要求解上述的,则需要
且
。根据LR模型的函数,有:
由于是多变量的,故采用梯度上升法进行求解其最大值。如果要求
的最大值,也就是等价于求
的最小值,此时
可以理解成逻辑斯蒂回归模型的损失函数。最终问题等价于用梯度下降法求解
的最小值。梯度下降法的参数更新过程为:
其中为学习率。那么
关于
的偏导数为:
上述的公式表达着这样一个道理:第维特征上的模型参数,它的更新方向为学习率(即学习步长)
乘上每个样本
在第
维度特征上真实值
和模型预测值
的差。
简单来说,就是参数更新的方向是通过模型误差乘上学习步长进行调整的。
进阶优化
特征离散化,将连续的特征离散化成一系列的0和1。
1、特征容易增加或减少,特征解释性强,例如年龄这维特征,比如第一版模型中,特征是
,这维特征可以理解成是否成年。后续第二版是可以直接增加
的特征,可以理解为是否为青年人。
2、特征变成0或1,
中
将变成稀疏向量,其内积乘法运算速度快,计算结果方便存储,容易扩展。
3、特征离散化后,具有极强的鲁棒性。例如年龄这维特征,特征是
。如果年龄数据中出现录入错误的,比如录入了身高数据175,在
这个特征中也仅仅是1,对结果影响没有那么大。但是如果采用的是连续特征,那么
会因为
变得很大。导致结果偏差很大。
4、LR模型本质上还是线性模型,本质上仍然是,如果不离散化,年龄特征
只会有一个系数
,如果离散化,年龄特征可能会变成
,
,
等等一系列离散特征,这样每个特征前的参数都不一样,相当于对于年龄这位特征是分段线性的,提升模型的表达能力。
5、特征离散化后,有利于进行特征交叉。如果不做离散化,可能就只有和
的交叉,离散化之后,就可以是
表示女性未成年,
表示男性青年。特征A离散化为
个值,特征B离散为
个值,那么交叉之后会有
个变量,进一步引入非线性,提升表达能力;
6、特征离散化后,模型更加稳定。比如特征重要度最高的是年龄特征。离散化后为,那么年龄在
岁值都是一样的,不会因为年龄大了一岁,就使得最后的结果发生较大变化。
7、特征离散化后,通过将特征分散,起到弱化特征的作用,避免某一维度特征过大,从而导致过拟合。比如年龄特征的特征重要度
很大,那么模型会过分依赖这维特征。一旦
出现了微小的波动,则都会对模型结果产生巨大的影响。
工程实现
代码中LR_classify为LR分类器模型,具体代码如下。
代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 23 19:52:02 2018
@author: huangzhaolong
"""
from sklearn.datasets import load_iris
import numpy as np
import random
import math
def load_data():
iris = load_iris()
X = iris.data
Y = iris.target
#特征离散化,多分类转二分类
X = X[Y<=1].round()
Y = Y[Y<=1]
data_num = X.shape[0]
index_list = [random.random() >= 0.2 for _ in range(data_num)]
train_index_list = []
test_index_list = []
for i in range(len(index_list)):
if index_list[i]:
train_index_list.append(i)
else:
test_index_list.append(i)
X_train = X[train_index_list]
X_test = X[test_index_list]
Y_train = Y[train_index_list]
Y_test = Y[test_index_list]
return X_train, X_test, Y_train, Y_test
class LR_classify(object):
def sigmoid(self, num_list):
if isinstance(num_list, (int, float)):
return 1/(1+math.exp(-num_list))
else:
result_list = []
for num in num_list:
if isinstance(num, (int, float)):
result_list.append(1/(1+math.exp(-num)))
else:
print ("error type!")
return -1
return result_list
def LR_train(self, X_train, Y_train, train_step = 1000):
#训练步数
train_step = 10
#初始化权重
weights=[random.random() for _ in range(X_train.shape[1]+1)]
#训练集扩展为广义矩阵
X_train = np.hstack((X_train, np.ones(X_train.shape[0]).reshape(X_train.shape[0],1)))
#定义梯度下降的step
learning_rate = 0.001
#对于每个样本
sample_num = X_train.shape[0]
for step in range(train_step):
#预测
pred = self.sigmoid(np.dot(X_train,weights))
for i in range(sample_num):
error = Y_train[i]- pred[i]
weights = weights + learning_rate * error * X_train[i]
if step % 1 == 0:
acc = cal_accuracy([round(z) for z in pred], Y_train)
print("训练过程第",step,"步的准确率为:",acc)
self.weights = weights
def LR_test(self, X_test):
X_test = np.hstack((X_test, np.ones(X_test.shape[0]).reshape(X_test.shape[0],1)))
weights = self.weights
pred = self.sigmoid(np.dot(X_test,weights))
return pred
def cal_accuracy(pred, label):
acc = list(pred-label).count(0)/len(pred-label)
return acc
if __name__=="__main__":
acc = 0
for i in range(1):
X_train, X_test, Y_train, Y_test = load_data()
lr = LR_classify()
lr.LR_train(X_train, Y_train)
pred = lr.LR_test(X_test)
acc += cal_accuracy([round(z) for z in pred],Y_test)
acc = acc/1
print("测试集准确率:",acc)
结果
训练过程第 0 步的准确率为: 0.46153846153846156
训练过程第 1 步的准确率为: 0.46153846153846156
训练过程第 2 步的准确率为: 0.46153846153846156
训练过程第 3 步的准确率为: 0.6153846153846154
训练过程第 4 步的准确率为: 0.8717948717948718
训练过程第 5 步的准确率为: 0.8717948717948718
训练过程第 6 步的准确率为: 0.9871794871794872
训练过程第 7 步的准确率为: 0.9871794871794872
训练过程第 8 步的准确率为: 1.0
训练过程第 9 步的准确率为: 1.0
测试集准确率: 1.0