一、原理解释
1.1、从线性回归到逻辑回归
考虑二分类问题,线性回归模型产生的是实值,仍需要转化为0/1值。因此单位阶跃函数(unit-step function)是一个自然的选择,但是阶跃函数不连续,因此考虑采用逻辑函数(logistic function)(Sigmoid函数的一种): 二者图像如下所示:
由此则有:即:
实际上是用线性回归模型的预测结果去逼近真实标记的对数几率。由于是0/1二分类问题上式可改写为:
由此可以得到:
对于给定的实例,按照上式可以求得和,通过比较两个条件概率的大小,将实例分到概率较大的一类。
1.2、模型参数估计
本节采用极大似然估计法(maximum likelihood method)来估计和。
设:,
则似然函数为:
对数似然函数为:
对求极大值,即得到的估计值。由此问题变为以对数似然函数为目标函数的最优化问题,这是一个高阶可导连续凸函数,可以采用梯度下降法进行求解:
二、算法实现
2.1 手动实现
以下是通过最大似然估计来求解逻辑回归的过程
1、导入包和文件
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize
datafile = 'data2.txt'
2、主函数,实现整个逻辑回归的过程,调用scipy 的优化算法fmin_bfgs(拟牛顿法 Broyden-Fletcher-Goldfarb-Shanno)
def LogisticRegression():
data = np.loadtxt(datafile,delimiter=',',dtype=np.float64)
X = data[:,:-1]
Y = data[:,-1]
Xm = MapFeature(X[:,0],X[:,1]) # mapping to polynomial
init_theta = np.zeros((Xm.shape[1],1)) # initial theta
init_lambda = 0.1
J = Cost(init_theta,Xm,Y,init_lambda) # cost function
print('J:',J)
result = optimize.fmin_bfgs(Cost,init_theta,fprime=Gradient,args=(Xm,Y,init_lambda))
#result = optimize.fmin(Cost,init_theta,args=(Xm,Y,init_lambda))
p = Predict(Xm,result)
print('accuracy:{}'.format(np.mean(np.float64(p==Y)*100)))
plot_boundary(result,X,Y)
3、数据映射,因为数据的feature可能很少,导致偏差大,映射为二次方的形式:
def MapFeature(x1,x2):
max_degree = 2 # 1,x1,x2,xi^2,x1x2,x2^2
out = np.ones((x1.shape[0],1)) # output after mapping
for i in range(1,max_degree+1):
for j in range(i+1):
temp = np.multiply(x1**(i-j),(x2**j))
out = np.hstack((out,temp.reshape(-1,1)))
return out
4、代价函数与梯度函数,考虑了正则化
def Cost(init_theta,X,Y,init_lambda):
m = len(Y)
J = 0
p = 1-Sigmoid(np.dot(X,init_theta)) # calculate p
theta = init_theta.copy()
theta[0] = 0
temp = np.dot(np.transpose(theta),theta) # considering regularization
J = (-np.dot(np.transpose(Y),np.log(p)) - np.dot(np.transpose(1-Y),np.log(1-p)))/m + temp*init_lambda/(2*m)
return J
def Gradient(init_theta,X,Y,init_lambda):
m = len(Y)
grad = np.zeros((init_theta.shape[0]))
p = 1-Sigmoid(np.dot(X,init_theta)) # calculate p
theta = init_theta.copy()
theta[0] = 0
grad = -np.dot(np.transpose(X),p-Y)/m + init_lambda/m*theta
return grad
5、激活函数与预测函数
def Sigmoid(z):
return 1.0/(1.0+np.exp(-z))
def Predict(X,theta):
m = X.shape[0]
p = np.zeros((m,1))
p = 1-Sigmoid(np.dot(X,theta))
for i in range(m):
if p[i]>0.5:
p[i]=1
else:
p[i]=0
return p
6、绘图函数,边界以等高线的形式绘制
def plot_boundary(theta,X,Y):
c0 = np.where(Y==0)
c1 = np.where(Y==1)
plt.plot(X[c0,0],X[c0,1],'ro')
plt.plot(X[c1,0],X[c1,1],'y*')
#u = np.linspace(30,100,100)
#v = np.linspace(30,100,100)
u = np.linspace(-1,1.5,50)
v = np.linspace(-1,1.5,50)
z = np.zeros((len(u),len(v)))
for i in range(len(u)):
for j in range(len(v)):
z[i,j] = np.dot(MapFeature(u[i].reshape(1,-1),v[j].reshape(1,-1)),theta)
z = np.transpose(z)
plt.contour(u,v,z,[0,0.01])
plt.show()
7、调用主函数
if __name__=='__main__':
LogisticRegression()
运算结果如下所示:2.2、调用sklearn包
调用sklearn的linear_model中的LogisticRegression模型,实现过程如下:
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
datafile = 'data2.txt'
def Logistic_Regression():
data = np.loadtxt(datafile,delimiter=',',dtype=np.float64)
X = data[:,:-1]
Y = data[:,-1]
x_train,x_test,y_train,y_test = train_test_split(X,Y,test_size=0.2)
scaler = StandardScaler()
scaler.fit(x_train)
x_train = scaler.transform(x_train)
scaler.fit(x_test)
x_test = scaler.transform(x_test)
model = LogisticRegression()
model.fit(x_train,y_train)
predict = model.predict(x_test)
right = sum(predict==y_test)
print('accuracy:',right/len(predict)*100)
if __name__=='__main__':
Logistic_Regression()
模型预测的准确率为 accuracy: 54.166666666666664
三、问题讨论
在以上实现的过程中,遇到了以下问题,现加以分析和阐明。
3.1、正则化
在回归和分类参数估计的过程中,当模型过于简单时,会发生欠拟合,而当模型过于复杂时,会发生过拟合,这两种问题都是要避免的。为避免过拟合,在进行参数估计时,正则化是一种常见方式。
正则化(regularization)通过保留所有的特征,但是减少参数大小,以在学习过程中降低模型复杂度和不稳定程度,从而避免过拟合的危险。
在上述实现过程中,我们正则化项采取了如下的平方项(因为不知道哪些特征需要惩罚,因此对所有参数进行惩罚),其中为正则化参数。
L1、L2正则化
范数的一般化定义:
当p=1时,是L1范数,其表示某个向量中所有元素绝对值的和。
当p=2时,是L2范数, 表示某个向量中所有元素平方和再开根, 也就是欧几里得距离公式。
惩罚项为L1范数的正则化即是L1正则化。因为参数的大小与模型的复杂度成正比,所以模型越复杂,L1范数就越大。使用L1范数也可以实现特征稀疏,导致 W 中许多项变成零。L1正则化是正则化的最优凸近似,相对L2范数的优点是容易求解。
惩罚项为L2范数的正则化即是L2正则化。让L2范数的正则化项最小,可以使W的每个元素都很小,都接近于零。但是与范数L1不同,它不使每个元素都为0,而是接近于零。越小的参数模型越简单,越简单的模型越不容易产生过拟合现象。L2范数不仅可以防止过拟合,而且还使优化求解变得稳定与迅速。
左图为L1正则化的约束项,右图为L2正则化的约束项。通过可视化可以发现,使用L1正则化在取得最优解的时候的值为0,相当于去掉了一个特征,这就是为什么L1正则化可以产生稀疏模型,进而可以用于特征选择。而使用L2正则化在取得最优解的时候特征参数都有其值,且值都不同程度变小。这也可以通过公式看出:
在没有L2正则化项时,参数更新形式如下:
考虑L2正则化项时,参数更新形式变为:
从上式可以看到,与未添加L2正则化的迭代公式相比,每一次迭代,都要先乘以一个小于1的因子,从而使得不断减小,因此总得来看,是不断减小的。
3.2、最小二乘与最大似然
最小二乘法是一种数学优化技术,主要是通过未知参数的选择,使模型的拟合值与观测值之差的平方和达到最小。实质上是对模型优劣的衡量做了一个标准,然后设计算法寻找符合这个标准的参数。对于上述拟合问题,即使:
采用梯度下降算法,则有:
则:
本文所采用的最大似然估计(maximum likelihood estimation, MLE)一种重要而普遍的求估计量的方法。最大似然法明确地使用概率模型,其目标是寻找能够以较高概率产生观察数据的系统发生树。对于最大似然估计来说,最合理的参数估计量应该使得从模型中抽取该n组样本的观测值的概率最大,也就是概率分布函数或者似然函数最大。
最大似然估计需要已知这个概率分布函数,一般假设其满足正态分布函数的特性,在这种情况下,最大似然估计与最小二乘估计是等价的,也就是估计的结果是相同的。
参考资料
[1] https://github.com/lawlite19/MachineLearning_Python
[2] 周志华 著. 机器学习. 北京:清华大学出版社,2016
[3] 李航 著. 统计学习方法. 北京:清华大学出版社,2012
[4] 史春奇等 著. 机器学习算法背后的理论与优化. 北京:清华大学出版社,2019
[5] Peter Harrington 著. 李锐等 译. 机器学习实战. 北京:人民邮电出版社,2013
我知言,我善养吾浩然之气。 ——《孟子·公孙丑上》