逻辑回归(Logistic regression)
,虽然名字中带有回归两个字,但其实它解决的并不是回归问题,而是分类问题。先来看一下它的定义:
在统计学中,逻辑回归是用来对某一类别或事件发生的
概率
进行建模的模型,比如,通过/失败、赢/输、存活/死亡,健康/生病。它也可以应用于多分类的问题,比如,判断一个图片中是否包含猫、狗、狮子等等。每一个在图像中被检测的物体都会被赋予一个
概率,
这个概率的取值范围在0到1之间,且总和为1
。
我们在之前的文章中介绍过
KNN算法
,该算法同样可用于解决分类问题,
它的分类依据是
距离
,即如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。
对比来看的话,从定义中可以看出,逻辑回归是基于
概率
对事件进行分类的,也就是说,
逻辑回归解决的是分类问题,并且分类的依据是概率
。明白了这一点,下面我们就来看一下,它到底是怎么用概率进行分类的。
逻辑回归和线性回归在原理上有很多相似之处,所以对比来看的话会更加清晰。
首先,回顾一下线性回归。假设有一组数据,包含两列,重量和尺寸,现在想看一下二者之间的关系,然后建立一个线性回归模型,用重量预测尺寸,具体步骤如下:
① 获取数据,包含重量和尺寸。
② 建立模型,拟合出一条直线。
在确定这条直线的时候,运用的方法是最小二乘法,它的思路是,计算每个点到拟合直线的距离(残差),先平方再求和,然后通过最小化残差平方和,确定直线的斜率和截距。
③ 预测。
利用重量和尺寸之间的线性关系,如果知道了重量的值,就可以预测对应的尺寸的值。
下面来看一下逻辑回归。和上面用线性回归预测连续值不同,逻辑回归用来预测分类问题,比如二分类的是/不是。如下图中,用重量来判断是否肥胖。
① 获取数据,包含重量和它所属的类别,即是否肥胖。
② 建立模型,拟合出一条S曲线。
与线性回归不同,逻辑回归拟合的是一条S曲线,并且曲线在纵轴的取值范围是0到1,代表的是以重量衡量时,属于肥胖的概率。
比如,对于B的重量,它属于肥胖的概率比较大,大约在0.9以上,对于A的重量,它属于肥胖的概率比较小,大约在0.5左右。
可以看出,相比于线性回归能够根据拟合直线预测出一个具体的值,逻辑回归则是根据拟合曲线预测出一个概率,然后再根据这个概率来判断它应该属于哪个类别。比如,设置一个临界值为0.5,如果得出的概率大于0.5,则归类为是肥胖,否则就归类为不是肥胖。
前面提到,在确定拟合直线时,线性回归用到的是最小二乘法,通过最小化残差平方和,得到直线的斜率和截距。而在逻辑回归中,没有残差的概念,所以也就不能用最小二乘法来确定拟合曲线,而是用最大似然估计。
在线性回归中,为了估计直线的斜率和截距这两个参数,使用的方法是最小二乘法,即最小化残差平方和,而在逻辑回归中,为了估计参数,使用的方法是
最大似然估计
,
即最大化一个
似然函数
,概念“似然”对应的是“可能性likelihood”,
所以也可以理解为最大化一个可能性函数,定义如下。
根据定义,如果想要估计参数,首先得有一个似然函数,然后通过最大化这个似然函数,得到参数的估计值。
在逻辑回归中,我们需要借助一个函数来构建似然函数,这个函数就是
sigmoid函数
,也叫Logistic函数。先来看看它长什么样子,如下图。
从上图可以看出,
sigmoid函数
的形状像一个S,自变量的取值范围是负无穷到正无穷,因变量的取值范围在0到1之间,而且,当自变量大于0时,因变量的值大于0.5,当自变量小于0时,因变量的值小于0.5。
在二分类问题中,因变量y的值只能是0或者1,利用sigmoid函数的特征,如果把临界值设置为0.5,则当自变量大于0,因变量的取值范围在0.5和1之间时,让y等于1,相反,当自变量小于0,因变量的取值范围在0和0.5之间时,让y等于0。
因此,借助sigmoid函数的上述特性,我们可以解决分类问题。明白了这一点,下面就来看一下具体的公式。
首先,在最开始,有自变量x和因变量h(θx),因变量的取值与自变量x和参数θ的取值有关,此时因变量h(θx)的取值范围没有限制,可以是从负无穷到正无穷,如上图例子,其中,θ是要估计的参数。
接下来,为了让h(θx)的取值范围压缩到0和1之间,需要借助sigmoid函数转换一下,最终得到公式(2)中的函数,又因为逻辑函数计算的是概率,因此,最终在等式的右边是一个概率值,含义是在给定自变量x和参数θ的条件下,y=1的概率。
整个分类过程的转换思路如上图所示,下面就来看一下具体的例子。
在上图的例子中,直线将平面分成两个区域,当其大于等于0时,y=1,即直线右侧包含星星的区域,小于0时,y=0,即直线左侧包含圆圈的区域。也就是说,通过这条直线,我们把y=1和y=0的值分开了,这条线叫做
决策边界(Decision boundary)
。
同样,在这个例子中,曲线将平面分成两个区域,当其大于等于0时,y=1,即曲线外侧包含星星的区域,小于0时,y=0,即曲线内部包含圆圈的区域,这条曲线是决策边界。
在上面给出的两个例子中,为了让大家更直观地观察图形效果,我们直接对参数θ进行了赋值,但其实这个参数θ是需要根据数据集估计的,下面,就回到最开始的问题,如何用最大似然估计的方法得到参数θ。
由于原始式子(2)中的公式比较复杂,所以这里我们借助
logit函数
,把原式转换成带有log的函数。
由定义可知,logit函数把原来取值范围在0到1之间的概率,转换成了负无穷到正无穷的范围,如下图。
此时,如果按照线性回归的思路,用最小二乘法,由于这里点到直线的距离可以是无穷大,没法求最小值,所以不能用该方法,而应该用最大似然估计。
第一步,计算这些点对应的log(odds)的值。
第二步,根据p和log(odds)之间的对应关系,求出p值。比如,看A点,计算出它的log(odds)等于-2.1,然后再带入p等式右侧,求出p值等于0.1。当把所有的点都按照上述方式对应到左侧之后,就能够拟合出一条S曲线。
第三步,计算该拟合曲线的可能性,将各样本点所代表的概率值相乘即可。这里需要注意的是,某一个点属于蓝色还是橙色的概率和为1,其中蓝色代表的是y=1,橙色代表的是y=0。因此,如果蓝色点的概率用p表示,则橙色点的概率则等于1-p。
在计算可能性likelihood的时候,通常习惯先取对数,然后拆分成加法计算,这里可以理解为,求likelihood的最大值和求log(likelihood)的最大值是一样的。
然后旋转第一步中右侧曲线的位置,重复执行上述操作,可以得到很多个不同的可能性likelihood的取值,最终,可能性最大的那条曲线拟合出的结果就是我们想要的预测曲线。
上面提到,我们要不断旋转第一步中右侧曲线的位置来计算可能性likelihood,然后取最大的那一个,但是旋转曲线的结果有无穷多个,如果没有一个规则的话,会经历很多次没必要的计算,浪费资源,也浪费感情。
因此,为了解决上述问题,需要介绍另外两个概念,
损失函数(loss function)
和
梯度下降(gradient descent)。
损失函数(loss function)
的概念,在之前介绍线性回归的文章中有介绍过,可以把它理解为真实值和根据模型预测得出的预测值之间的差异。
梯度下降(gradient descent)
是一种优化算法,它的目的是找到一个函数的局部最小值。
因此,我们可以利用梯度下降的方法,去求损失函数的最小值。当损失函数取最小值时,预测值和真实值之间的差距最小,此时得出的拟合曲线和估计出的参数是我们想要得到的最优结果。
首先,看一下在逻辑回归中,损失函数的表达式长什么样子。
如上图中的推导,将可能性likelihood按照y=1和y=0分成两个部分,分别求最大值,然后在各自前面添加一个负号,就转换成求最小值,从而可以推导出右侧的损失函数J(θ)。注意,在二分类问题中,y的值只能是1或者0。
有了损失函数J(θ),接下来,就可以用梯度下降的方法,去求损失函数J(θ)的最小值了,当J(θ)取最小值时的参数θ,就是通过最大似然估计得到的最优参数。
本文暂时不对梯度下降的具体原理和公式做过多介绍,大家只要先知道它是求最小值的一个优化算法就可以啦,下面直接看一下逻辑回归在Python中是如何调用的。
我们可以导入
sklearn.linear_model
模块中
LogisticRegression
类实现该算法,先利用训练集数据构建模型,再用测试集数据进行预测。
逻辑回归算法的实现步骤和KNN算法类似:
① 先导入相应的模块
② 划分训练集和测试集
③ 创建一个分类器
④ 放入训练集数据进行学习,得到预测模型
⑤ 在测试集数据上进行预测
【工具】Python 3
【数据】
tushare.pro
【注】本文注重的是方法的讲解,请大家灵活掌握。
import tushare as ts
import pandas as pd
pd.set_option("expand_frame_repr", False) # 当列太多时不换行
# 设置token
ts.set_token('your token')
pro = ts.pro_api()
# 导入000002.SZ前复权日线行情数据,保留收盘价列
df = ts.pro_bar(ts_code='000002.SZ', adj='qfq', start_date='20190101', end_date='20191231')
df.sort_values('trade_date', inplace=True)
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.set_index('trade_date', inplace=True)
df = df[['close']]
# 计算当前、未来1-day涨跌幅
df['1d_future_close'] = df['close'].shift(-1)
df['1d_close_future_pct'] = df['1d_future_close'].pct_change(1)
df['1d_close_pct'] = df['close'].pct_change(1)
df.dropna(inplace=True)
# ====1代表上涨,0代表下跌
df.loc[df['1d_close_future_pct'] > 0, '未来1d涨跌幅方向'] = 1
df.loc[df['1d_close_future_pct'] <= 0, '未来1d涨跌幅方向'] = 0
df = df[['1d_close_pct', '未来1d涨跌幅方向']]
df.rename(columns={'1d_close_pct': '当前1d涨跌幅'}, inplace=True)
print(df.head())
当前1d涨跌幅 未来1d涨跌幅方向
trade_date
2019-01-03 0.007112 1.0
2019-01-04 0.035728 1.0
2019-01-07 0.004815 0.0
2019-01-08 -0.001997 1.0
2019-01-09 0.013203 0.0
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 创建特征 X 和标签 y
y = df['未来1d涨跌幅方向'].values
X = df.drop('未来1d涨跌幅方向', axis=1).values
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=42)
# 创建一个逻辑回归分类器
logreg = LogisticRegression(solver='liblinear')
# 放入训练集数据进行学习
logreg.fit(X_train, y_train)
# 在测试集数据上进行预测
new_prediction = logreg.predict(X_test)
print("Prediction: {}".format(new_prediction))
# 测算模型的表现:预测对的个数 / 总个数
print(logreg.score(X_test, y_test))
Prediction: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0.