1、 逻辑回归
逻辑回归常用于二分类,利用sigmoid函数作为激活函数,可以形成决策边界
若
,则
,那么分类结果为1
若
,则
,那么分类结果为0
其中
为权重的转置,
为数据的各个特征,
为决策边界。
如果是两个特征,x1为x,x2为y,相应的决策边界就为一条直线
from numpy import *
import numpy as np
import matplotlib.pyplot as plt
filename='data' #文件目录
#逻辑回归用于找出用于分类的曲线
def loadDataSet(): #读取数据(这里只有两个特征)
dataMat = []
labelMat = []
fr = open(filename)
for line in fr.readlines(): #返回行的列表
lineArr = line.strip().split() #split按空格对字符串拆分,并返回列表/strip用于删除开头结尾的空格
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) #前面的1,表示方程的常量。比如两个特征X1,X2,共需要三个参数,W1*X0+W2*X1+W3*X2,X0为1时,为常数
labelMat.append(int(lineArr[2]))
return dataMat,labelMat
def sigmoid(inX): #sigmoid函数
return 1.0/(1+exp(-inX))
def gradAscent(dataMat, labelMat): #梯度上升求最优参数
dataMatrix=mat(dataMat) #将读取的数据转换为二维矩阵
classLabels=mat(labelMat).transpose() #将读取的数据转换为矩阵并转置为一列矩阵
m,n = shape(dataMatrix)
alpha = 0.001 #设置梯度的阀值,该值越大梯度上升幅度越大
maxCycles = 500 #设置迭代的次数,一般看实际数据进行设定,有些可能200次就够了
weights = ones((n,1)) #设置初始的参数,并都赋默认值为1。注意这里权重以矩阵形式表示三个参数。
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights)
error = (classLabels - h) #求导后差值
weights = weights + alpha * dataMatrix.transpose()* error #迭代更新权重
return weights
def stocGradAscent0(dataMat, labelMat): #随机梯度上升,当数据量比较大时,每次迭代都选择全量数据进行计算,计算量会非常大。所以采用每次迭代中一次只选择其中的一行数据进行更新权重。
dataMatrix=mat(dataMat)
classLabels=labelMat
m,n=shape(dataMatrix)
alpha=0.01
maxCycles = 1000
weights=ones((n,1))
for k in range(maxCycles):
for i in range(m): #遍历计算每一行
h = sigmoid(sum(dataMatrix[i] * weights))
error = classLabels[i] - h
weights = weights + alpha * error * dataMatrix[i].transpose()
return weights
def stocGradAscent1(dataMat, labelMat): #改进版随机梯度上升,在每次迭代中随机选择样本来更新权重,并且随迭代次数增加,权重变化越小。
dataMatrix=mat(dataMat)
classLabels=labelMat
m,n=shape(dataMatrix)
weights=ones((n,1))
maxCycles=500
for j in range(maxCycles): #迭代
dataIndex=[i for i in range(m)]
for i in range(m): #随机遍历每一行
alpha=4/(1+j+i)+0.0001 #随迭代次数增加,权重变化越小。
randIndex=int(random.uniform(0,len(dataIndex))) #随机抽样
h=sigmoid(sum(dataMatrix[randIndex]*weights))
error=classLabels[randIndex]-h
weights=weights+alpha*error*dataMatrix[randIndex].transpose()
del(dataIndex[randIndex]) #去除已经抽取的样本
return weights
def plotBestFit(weights): #画出最终分类的图
dataMat,labelMat=loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0]
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
for i in range(n):
if int(labelMat[i])== 1:
xcord1.append(dataArr[i,1])
ycord1.append(dataArr[i,2])
else:
xcord2.append(dataArr[i,1])
ycord2.append(dataArr[i,2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
ax.scatter(xcord2, ycord2, s=30, c='green')
x = arange(-3.0, 3.0, 0.1)
y = (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x, y)
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
def predict(x,weights):
x=np.array(x)
if sigmoid(x.dot(weights)) >0.5:
print(1)
else:
print(0)
def main():
dataMat, labelMat = loadDataSet()
weights=gradAscent(dataMat, labelMat).getA()
print(weights)
plotBestFit(weights)
# predict([1,0.85,6.92],weights)
m,n=np.array(dataMat).shape
result=[]
for i in range(m):
result.append(predict(dataMat[i],weights))
print(result)
if __name__=='__main__':
main()
data的数据为:
-0.017612 14.053064 0
-1.395634 4.662541 1
-0.752157 6.538620 0
-1.322371 7.152853 0
0.423363 11.054677 0
0.406704 7.067335 1
0.667394 12.741452 0
-2.460150 6.866805 1
0.569411 9.548755 0
-0.026632 10.427743 0
0.850433 6.920334 1
1.347183 13.175500 0
1.176813 3.167020 1
-1.781871 9.097953 0
-0.566606 5.749003 1
0.931635 1.589505 1
-0.024205 6.151823 1
-0.036453 2.690988 1
-0.196949 0.444165 1
1.014459 5.754399 1
1.985298 3.230619 1
-1.693453 -0.557540 1
-0.576525 11.778922 0
-0.346811 -1.678730 1
-2.124484 2.672471 1
1.217916 9.597015 0
-0.733928 9.098687 0
-3.642001 -1.618087 1
0.315985 3.523953 1
1.416614 9.619232 0
-0.386323 3.989286 1
0.556921 8.294984 1
1.224863 11.587360 0
-1.347803 -2.406051 1
1.196604 4.951851 1
0.275221 9.543647 0
0.470575 9.332488 0
-1.889567 9.542662 0
-1.527893 12.150579 0
-1.185247 11.309318 0
-0.445678 3.297303 1
1.042222 6.105155 1
-0.618787 10.320986 0
1.152083 0.548467 1
0.828534 2.676045 1
-1.237728 10.549033 0
-0.683565 -2.166125 1
0.229456 5.921938 1
-0.959885 11.555336 0
0.492911 10.993324 0
0.184992 8.721488 0
-0.355715 10.325976 0
-0.397822 8.058397 0
0.824839 13.730343 0
1.507278 5.027866 1
0.099671 6.835839 1
-0.344008 10.717485 0
1.785928 7.718645 1
-0.918801 11.560217 0
-0.364009 4.747300 1
-0.841722 4.119083 1
0.490426 1.960539 1
-0.007194 9.075792 0
0.356107 12.447863 0
0.342578 12.281162 0
-0.810823 -1.466018 1
2.530777 6.476801 1
1.296683 11.607559 0
0.475487 12.040035 0
-0.783277 11.009725 0
0.074798 11.023650 0
-1.337472 0.468339 1
-0.102781 13.763651 0
-0.147324 2.874846 1
0.518389 9.887035 0
1.015399 7.571882 0
-1.658086 -0.027255 1
1.319944 2.171228 1
2.056216 5.019981 1
-0.851633 4.375691 1
-1.510047 6.061992 0
-1.076637 -3.181888 1
1.821096 10.283990 0
3.010150 8.401766 1
-1.099458 1.688274 1
-0.834872 -1.733869 1
-0.846637 3.849075 1
1.400102 12.628781 0
1.752842 5.468166 1
0.078557 0.059736 1
0.089392 -0.715300 1
1.825662 12.693808 0
0.197445 9.744638 0
0.126117 0.922311 1
-0.679797 1.220530 1
0.677983 2.556666 1
0.761349 10.693862 0
-2.168791 0.143632 1
1.388610 9.341997 0
0.317029 14.739025 0
当数据不能通过一条直线区分的时候,需要像线性回归一样运用多项式曲线进行分割。
下面是利用sklearn实现多项式逻辑回归:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(666)
x=np.random.normal(0,1,size=(200,2))
y=np.array(x[:,0]**2+x[:,1]<1.5,dtype='int') #将bool值强制变成int型的数组
for _ in range(20):
y[np.random.randint(200)]=1 #加入噪音
plt.scatter(x[y==0,0],x[y==0,1]) #选出y的0时所对应的x的索引,y==0时,满足点在圆外
plt.scatter(x[y==1,0],x[y==1,1]) #选出y的0时所对应的x的索引,y==0时,满足点在圆外
plt.show()
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,random_state=666)
#利用sklearn中的逻辑回归
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train) #默认使用L2正则化
result=log_reg.score(x_test,y_test)
print(result)
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
def Poly_log(degree,C,penalty='l2'):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std",StandardScaler()),
("Log",LogisticRegression(C=C,penalty=penalty)) #C为正则化前面的系数,penalty为正则项类型,使用L1还是L2
])
poly_log_reg=Poly_log(degree=20,C=0.1,penalty='l1')
poly_log_reg.fit(x_train,y_train)
print(poly_log_reg.score(x_train,y_train))
result2=poly_log_reg.score(x_test,y_test)
print(result2)
LogisticRegression中的超参数有C和penalty,其中C为损失函数+正则化项后,将正则化,项系数变为1后损失函数的系数,penalty表示的是正则化采用L1还是L2,如果多项式的维度(degree)较高,则可以通过采用L1正则化,使得一些维度前面系数为0,从而提升准确度。
2、逻辑回归的多分类问题
此方法为通用方法,适用于其他多分类问题
- OvR(one vs rest)
- OvO(one vs one)
(1)OvR:选择一个为一个类别,其他的为另一类别
分别选择其中的一个为一个类别,计算其概率,再计算是剩余的类别的概率。n个类别就进行n此分类,再选择分类得分最高的。
(2)OvO:在多类别中挑出两个类别,再进行二分类任务,然后两两比较得出新来的数据在哪个类别中的概率最大,就是哪个类别。
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,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
#sklearn中默认使用ovr多分类
log_reg.fit(x_train,y_train)
print(log_reg.score(x_train,y_train))
##采用ovo方式进行多分类任务
log_reg2=LogisticRegression(multi_class="multinomial",solver="newton-cg")
log_reg2.fit(x_train,y_train)
print(log_reg2.score(x_test,y_test))
输出:
0.9553571428571429
1.0
可以看出ovo的分类方式的正确率比ovr更高
采用下面的方式可以对所有的二分类器进行多分类任务:
from sklearn.multiclass import OneVsRestClassifier
ovr=OneVsRestClassifier(log_reg) #log_reg为二分类器
ovr.fit(x_train,y_train)
print(ovr.score(x_test,y_test))
from sklearn.multiclass import OneVsOneClassifier
ovo=OneVsOneClassifier(log_reg)
ovo.fit(x_train,y_train)
print(ovo.score(x_test,y_test))
3、分类准确度
对于极度偏斜的数据,只使用准确度是不够的。例如预测癌症的发病率的准确度是99.9%的模型,因为癌症的发病率本身就很低,如果发病率为0.1%,那么只要说所有人都是健康的,那这个模型的准确度就是99.9%,相当于这个模型什么都没有做。
使用混淆矩阵做进一步分析。
如果预测的是0,真实值是0,就为TN;如果预测为1,真实值为0,就为FP;预测为0,真实值为1,就为FN,预测为1,真实值为1,就为TP。
(1)精准率
精准率:预测结果为1的时候,预测正确的概率
原因:在有偏(极度偏斜)数据中,通常将1作为真正关注的对象,那么在这种情况下预测正确的概率就作为精准率。(比如:我们真正关心的是预测出了多少癌症病人,而实际上得癌症的又有多少人,即我们预测患癌症的成功率为多少)
(2)召回率
我们关注的事件真实的发生的情况下,成功预测的概率(比如:现有的癌症患者有多少人,而我们能预测出多少人)
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
digits=datasets.load_digits()
x=digits.data
y=digits.target.copy() #因为如果y不经过拷贝,下面修改y的值会直接修改digits.target的值,y与target指向相同
#因为手写数字中并没有发生很大的偏斜,所以人为让它产生偏斜
#将数字为9的数看作1,其余数字看作0,将十分类变成二分类
y[digits.target==9]=1
y[digits.target!=9]=0
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test =train_test_split(x,y,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
print(log_reg.score(x_test,y_test))
#因为数据是极度倾斜的,及时全部猜0,也有90%的正确率,所以采用混淆矩阵
y_log_predict=log_reg.predict(x_test)
def TN(y_true,y_predict):
assert len(y_true)==len(y_predict)
return np.sum((y_true==0)&(y_predict==0))
def FP(y_true,y_predict):
assert len(y_true)==len(y_predict)
return np.sum((y_true==0)&(y_predict==1))
def FN(y_true,y_predict):
assert len(y_true) == len(y_predict)
return np.sum((y_true==1)&(y_predict==0))
def TP(y_true,y_predict):
assert len(y_true) == len(y_predict)
return np.sum((y_true == 1) & (y_predict == 1))
def confusion_matrix(y_true,y_predict):
return np.array([
[TN(y_true,y_predict),FP(y_true,y_predict)],
[FN(y_true,y_predict),TP(y_true,y_predict)]
])
print(confusion_matrix(y_test,y_log_predict))
#求精准率
def precision_score(y_true,y_predict):
tp=TP(y_true,y_predict)
fp=FP(y_true,y_predict)
try: #防止分母为0
return tp/(tp+fp)
except:return 0.0
print(precision_score(y_test,y_log_predict))
#召回率
def recall_score(y_true,y_predict):
tp = TP(y_true, y_predict)
fn=FN(y_true,y_predict)
try:
return tp/(tp+fn)
except:
return 0.0
print(recall_score(y_test,y_log_predict))
在sklearn中的混淆矩阵、精准率、召回率
from sklearn import datasets
digits=datasets.load_digits()
x=digits.data
y=digits.target.copy() #因为如果y不经过拷贝,下面修改y的值会直接修改digits.target的值,y与target指向相同
#因为手写数字中并没有发生很大的偏斜,所以人为让它产生偏斜
#将数字为9的数看作1,其余数字看作0,将十分类变成二分类
y[digits.target==9]=1
y[digits.target!=9]=0
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test =train_test_split(x,y,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
print(log_reg.score(x_test,y_test))
#因为数据是极度倾斜的,及时全部猜0,也有90%的正确率,所以采用混淆矩阵
y_log_predict=log_reg.predict(x_test)
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test,y_log_predict))
from sklearn.metrics import precision_score
print(precision_score(y_test,y_log_predict))
from sklearn.metrics import recall_score
print(recall_score(y_test,y_log_predict))
0.9755555555555555 #逻辑回归的准确率
[[403 2] #混淆矩阵
[ 9 36]]
0.9473684210526315 #精准率
0.8 #召回率
当然,有时候精准率高,召回率低,也有时候是精准率低,召回率高,所以选择哪个标准需要视情况而定。如股票预测,更注重精准率(我们做的股票升值的预测中有多少是正确的,如果预测错误会带来很大损失),而对于召回率我们不看重,因为有些股票处于上升期,我们或许有些没有预测到,但这并不会带来多大损失。如在病人诊断过程中,我们更看重召回率,如果一些人有病,而我们没有诊断出来,那么会带来很严重的影响。
(3)F1 score
有时候需要同时关注精准率和召回率,所以运用新的指标F1 score,兼顾精准率和召回率
,是精准率和召回率的调和平均值:
from sklearn.metrics import f1_score
f1=f1_score(y_test,y_log_predict)
print(f1)
# f1 score的值,比准确率更有说服力
0.8674698795180723
(4)precision—recall的平衡(曲线)
按照上图分类,预测为1的五个图形里面,有四个真实值为1,那么精准率为0.8,真实值为1的有六个图形,其中预测值为1的有四个,所以召回率为0.67。
改变阈值右移,如上图所示,右边为1,左边为0,可以看到精准率为1,召回率为0.33;阈值左移,可以得到精准率为0.75,召回率为1。由上图可以看到当精准率增加时,召回率在减小,两者互相牵制。
下面通过改变阈值,找到精准率和召回率的平衡点:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
digits=datasets.load_digits()
x=digits.data
y=digits.target.copy() #因为如果y不经过拷贝,下面修改y的值会直接修改digits.target的值,y与target指向相同
#因为手写数字中并没有发生很大的偏斜,所以人为让它产生偏斜
#将数字为9的数看作1,其余数字看作0,将十分类变成二分类
y[digits.target==9]=1
y[digits.target!=9]=0
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test =train_test_split(x,y,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
#得到逻辑回归的决策值,一般为0,可以改变阈值
decision_score=log_reg.decision_function(x_test)
precision=[]
recalls=[]
##y_predict2=np.array(decision_score>=5,dtype='int') 通过这个方法可以改变阈值
#以决策值阈值的最小值和最大值作为范围,遍历寻找适合的阈值
thresholds=np.arange(np.min(decision_score),np.max(decision_score),0.1)
for threshold in thresholds:
y_predict2=np.array(decision_score>=threshold,dtype='int')
precision.append(precision_score(y_test,y_predict2))
recalls.append(recall_score(y_test,y_predict2))
plt.plot(thresholds,precision,label='precision')
plt.plot(thresholds,recalls,label='recall')
plt.legend()
plt.show()
plt.plot(precision,recalls)
plt.xlabel('precision')
plt.ylabel('recall')
plt.show()
可以看出精准率增加时召回率减小。也可以根据具体要求找出适合的阈值
将精准率和召回率放在一张图中可以直观的找出两者的平衡点。大约在精准率为0.9开始,精准率上升,召回率急剧下降。所以在0.9的时候是比较好的平衡点。
(5)ROC曲线
描述TPR和FPR之间的关系
- TPR:和召回率相同,表示TP除以真实值为1的数
- FPR:用FP除以真实值为0的数
用sklearn实现ROC曲线
import matplotlib.pyplot as plt
from sklearn import datasets
digits=datasets.load_digits()
x=digits.data
y=digits.target.copy() #因为如果y不经过拷贝,下面修改y的值会直接修改digits.target的值,y与target指向相同
#因为手写数字中并没有发生很大的偏斜,所以人为让它产生偏斜
#将数字为9的数看作1,其余数字看作0,将十分类变成二分类
y[digits.target==9]=1
y[digits.target!=9]=0
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test =train_test_split(x,y,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
#得到逻辑回归的决策值,一般为0,可以改变阈值
decision_score=log_reg.decision_function(x_test)
#sklearn中的ROC曲线
from sklearn.metrics import roc_curve
fpr,tpr,threshold=roc_curve(y_test,decision_score)
plt.plot(fpr,tpr)
plt.show()
fpr和tpr之间的关系可以看出,两者的变化趋势相同,曲线所包围的面积越大越好。
曲线下面包含的面积可以通过sklearn求得
from sklearn.metrics import roc_auc_score
print(roc_auc_score(y_test,decision_score))
(6)总结:
对于有偏数据,检查精准率和召回率是很有必要的,而ROC曲线用来比较几个模型谁更好一点(通过比较包含面积大小来确定)
4、多分类问题中的混淆矩阵
依然利用sklearn中的confusion_matrix函数求出多酚类问题的混淆矩阵,并通过求出混淆矩阵每一行中的元素所占每一行总数的百分比,并将其用像素图的方式显示出来,可以清楚的看到对哪一个值的预测不准确。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
#创建手写数字的十分类问题
digits=datasets.load_digits()
x=digits.data
y=digits.target
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test =train_test_split(x,y,random_state=666)
from sklearn.linear_model import LogisticRegression
log_reg=LogisticRegression()
log_reg.fit(x_train,y_train)
log_reg.score(x_test,y_test)
y_predict=log_reg.predict(x_test)
from sklearn.metrics import precision_score
#精确率默认是在二分类问题中使用,需要改变average参数才可以使用在多分类问题中
print(precision_score(y_test,y_predict,average='micro'))
#使用混淆矩阵
from sklearn.metrics import confusion_matrix
cfm=confusion_matrix(y_test,y_predict)
#显示预测犯错误的地方
row_sums=np.sum(cfm,axis=1) #求出混淆矩阵每一行的和
error_matrix=cfm/row_sums #求出每一行中每一个元素所占这一行的百分比
#将矩阵中对角线上的元素都定位0
np.fill_diagonal(error_matrix,0)
plt.matshow(error_matrix,cmap=plt.cm.gray)
plt.show()
越亮的地方表示预测值越不准确,图中可以看出,当真值为1时预测值却是9的时候,以及真值为3预测值却是9的时候是犯错最多的时候。
在知道犯错最多的地方后可以进一步调整算法,通过改变两种情况下(1和9还有3和9)的逻辑回归阈值来提高多分类问题的准确度。
当然,问题出在算法上是有可能的,但是也可能出现在数据上面,可以挑出1和3还有9的图片进行查看。