逻辑回归
原理
逻辑回归用于解决分类问题,但是你可能会疑惑,为何名字偏偏称为回归,嘿嘿嘿,这时因为我们在逻辑回归中将连续取值结果的函数,一分为二,连续值便变成了0、1这样的离散值,这样便成了分类,这样说实在过于抽象,下面来具体看一下
- 我们先将样本的特征和样本的概率联系起来,而且我们定义: 通过概率进行分类
- 我们首先计算出p这个概率值,这是一个连续值,如果我们就此打住,这便可以称为一个回归算法,如果我们按照上面的的定义,将不同概率分为两类,这样就成了分类算法
-
我们来看看计算过程,这里注意一下sigma函数
过程 -
对于sigma函数,我们通常选择:
sigma函数 -
总结一下
1
那么问题就变成了,对于给定的xy,寻找theta参数,使得这种方式下能获得最好的分类效果。
-
这时候我们要考虑损失函数,这里不加推导给出:
损失函数
对于所有样本,我们得到损失函数
其中,有:
p_hat值
所以我们得到损失函数完整表达式:
损失函数完整表达式 - 对于此损失函数,不像线性回归中那样,具有公式解,我们目前只能采用梯度下降法进行求解
-
我们在这里特别看看其与线性回归的差别(在求梯度的时候)
梯度比较
我们注意到,其实最大的差别就是y_hat的值,是否使用了sigma函数而已
我们进行矩阵形式化简
矩阵形式
简单实现
对于逻辑回归,其重要性是不用多说的,吴恩达的课程里面占了较大的篇幅。
所以这里的实现写的比较全面,希望读者自己也可以试一试,相信你将获益匪浅!
import numpy as np
from metrics import accuracy_score
#这里采用类进行封装的形式
class LogisticRegression:
def __init__(self):
"""初始化Logistic Regression模型"""
self.coef_ = None
self.intercept_ = None
self._theta = None
def _sigmoid(self,t):
return 1/(1+np.exp(-t))
def fit(self, X_train, y_train, eta=0.01, n_iters=1e4):
"""根据训练数据集X_train, y_train, 使用梯度下降法训练Logistic Regression模型"""
assert X_train.shape[0] == y_train.shape[0], \
"the size of X_train must be equal to the size of y_train"
def J(theta, X_b, y):
y_hat=self._sigmoid(X_b.dot(theta))
try:
return - np.sum(y*np.log(y_hat)+(1-y)*np.log(1-y_hat)) / len(y)
except:
return float('inf')
def dJ(theta, X_b, y):
return X_b.T.dot(self._sigmoid(X_b.dot(theta)) - y) / len(X_b)
def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):
theta = initial_theta
cur_iter = 0
while cur_iter < n_iters:
gradient = dJ(theta, X_b, y)
last_theta = theta
theta = theta - eta * gradient
if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
break
cur_iter += 1
return theta
X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
initial_theta = np.zeros(X_b.shape[1])
self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)
self.intercept_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
def predict_proba(self, X_predict):
"""给定待预测数据集X_predict_proba,返回表示X_predict的结果概率"""
assert self.intercept_ is not None and self.coef_ is not None, \
"must fit before predict!"
assert X_predict.shape[1] == len(self.coef_), \
"the feature number of X_predict must be equal to X_train"
X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
return self._sigmoid(X_b.dot(self._theta))
def predict(self, X_predict):
"""给定待预测数据集X_predict,返回表示X_predict的结果向量"""
assert self.intercept_ is not None and self.coef_ is not None, \
"must fit before predict!"
assert X_predict.shape[1] == len(self.coef_), \
"the feature number of X_predict must be equal to X_train"
proba=self.predict_proba(X_predict)
return np.array(proba>=0.5,dtype='int')
def score(self, X_test, y_test):
"""根据测试数据集 X_test 和 y_test 确定当前模型的准确度"""
y_predict = self.predict(X_test)
return accuracy_score(y_test, y_predict)
def __repr__(self):
return "LinearRegression()"
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris=datasets.load_iris()
x=iris.data
y=iris.target
x=x[y<2,:2]#只取y=0,1的两个分类,为了方便可视化而只取两个特征
y=y[y<2]
我们先看看这堆数据的散点图
原始数据散点图
###使用逻辑回归
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=6)
#实际上这里size默认是0.2
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
结果
决策边界
决策边界其实在二维情况下的函数,就是初中高中的二元一次函数,使用这一直线对数据进行分割。
上面示例的划分如图
划分
对于非直线划分的情况呢?——多项式特性
圆形决策边界
类似于我们在多项式回归中使用的技巧,将x12、x22视为一个特征进行计算即可,由此我们还可以得到其他圆形,曲线一类的......
这里就不进行额外的实现了
用sklearn实现逻辑回归&&正则化
不同于前面多项式回归中的模型正则化,我们有一点点的区别:
正则化区别
常数参数由alpha变成了C,实际上我们可以视为1/alpha,其效果类似,但是使用c进行正则化,主要是考虑到逻辑回归时,L1,L2一般是要为1,所以便于计算,我们取了C放在J前面,其原理类似之前。
#sklearn 中的逻辑回归
import numpy as np
import matplotlib.pyplot as plt
X=np.random.normal(0,1,size=(200,2))
y=np.array(X[:,0]**2+X[:,1]<1.5,dtype='int')#抛物线
for k in range(20):
y[np.random.randint(200)]=1#加20个噪音
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
我们先看看散点图分布
散点图
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(X,y,test_size=0.2,random_state=6)
#实际上这里size默认是0.2
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
这里我们先留意一下逻辑回归的参数,因为后面调参可能会用到
参数
我们先看看使用线性的逻辑回归进行分类的结果,可以预见的,效果并不好,因为我们的数据实际是需要二次函数进行分类的
结果1
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
def PolyLogisticRegression(degree):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scacler",StandardScaler()),
("lin_reg",LogisticRegression())
])
poly_log_reg=PolyLogisticRegression(degree=2)
poly_log_reg.fit(x_train,y_train)
poly_log_reg.score(x_train,y_train)
这里我们使用二阶进行分类,查看结果
结果2
我们可以看到,结果显然好了不少
我们可以接着改变degree进行测试,这里就不做展开了
OVR&&OVO
我们前面进行的都是二分类,但是二分类显然无法满足实际,作为用处最为广泛的分类算法,逻辑回归自然也具有多分类功能。
-
OVR一对剩余所有
OVR - OVO一对一
OVO
一般来说一对一要准确度更高,但是复杂度也更高,遵循排列组合中的Cn2计算出需要进行分类的次数,这显然比一对剩余的n次要复杂的多。
下面使用鸢尾花数据集(3分类)进行三分类
sklearn中非常人性化地给我们自动添加了多分类的功能,查看参数得知,默认是OVR ,即我们不用担心我们现在是几分类问题
#三分类
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris=datasets.load_iris()
x=iris.data
y=iris.target
x=x[:,:2]#为了方便可视化而只取两个特征
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=6)
from sklearn.linear_model import LogisticRegression
#sklearn中自动封装了多分类
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)#默认支持且使用ovr方式
log_reg.score(x_train,y_train)
(这里可视化的代码有点复杂,懒得去理解了,故不作可视化了)
结果1
注意这里的将ovr改成ovo的方式,一是需要改multi_class,二是由于解法不能使用ovr的默认解法,故也许更改,这里查阅手册,可以改成newton-cg
log_reg2=LogisticRegression(multi_class="multinomial",solver="newton-cg")#ovo方式
log_reg2.fit(x_train,y_train)
log_reg2.score(x_train,y_train)
这是不仅仅只用两个特征的例子,使用的完整数据
#使用所有数据
#三分类
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
iris=datasets.load_iris()
x=iris.data
y=iris.target
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=6)
from sklearn.linear_model import LogisticRegression
#sklearn中自动封装了多分类
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)#默认支持且使用ovr方式
log_reg.score(x_train,y_train)
log_reg2=LogisticRegression(multi_class="multinomial",solver="newton-cg")#ovo方式
log_reg2.fit(x_train,y_train)
log_reg2.score(x_train,y_train)
结果2
sklearn中其实也封装了ovr、ovo两个类,我们调用试试
#sklearn中特地封装了ovr、ovo两个类
from sklearn.multiclass import OneVsRestClassifier
ovr=OneVsRestClassifier(log_reg)#传进去一个二分类器即可
ovr.fit(x_train,y_train)#自动使用ovr方式
ovr.score(x_test,y_test)
from sklearn.multiclass import OneVsOneClassifier
ovo=OneVsOneClassifier(log_reg)#传进去一个二分类器即可
ovo.fit(x_train,y_train)#自动使用ovr方式
ovo.score(x_test,y_test)
结果和前面应该是一样的
总结
逻辑回归的全部内容到这里就结束了,这是用的最多的一种机器学习算法,里面含括了不少我们前面学到的重要知识,能够掌握好前面整个部分的代码原理是很重要的,可算是码完字了,溜了溜了!