前两篇讲了线性回归和感知机,铺垫已经做好了,现在终于可以讲讲逻辑回归了。通过之前的博客我们知道,感知机是线性模型在分类问题上的尝试与改进,那么逻辑回归可以看做是感知机的优化,不了解的小伙伴可以参考博客线性回归和感知机
引言
为了实现分类的功能,感知机通过sign函数将线性模型的输出y映射成1和-1,sign函数如下
sign函数是一个非连续的阶跃函数,它有两个问题:
- 由于是非连续函数,那么模型的目标函数就无法进行微分(感知机是剥离了sign函数设计的目标函数)
- 阶跃函数太过于粗暴,比如y=0.01和y=1最后都判断为同一类,但是明显y=0.01的样本点更接近超平面,也更容易分错;再比如y=0.01和y=-0.01最终分为两类不同类别,但是者两个样本点都离超平面很近,超平面稍有波动,者两个样本点就可能变成同一类了
逻辑回归原理
逻辑回归模型是为了将线性回归模型应用于分类任务而设计的,线性函数的输出值y是一个具有一定范围的连续数值,那么我们能不能设计一种激活函数,既能保证对y处理后的结果任然连续(解决不可微),又能对不同大小的y有不同的评价(解决过于粗暴),比如接近超平面的程度、属于某类别的概率等。
- 激活函数
逻辑回归模型中设计了Sigmoid函数,该函数将y映射到[0,1]这个连续的空间中,能同时满足以上两点要求。在分类任务中,Sigmoid函数的输出值代表判断为某类别的概率,以z=0.5作为分界点,大于0.5判定为一个类别,小于0.5判断为另一个类别。假现在要判断x是否属于类别A,y越大z越接近1,表示为类A的概率越大;y越小z越接近0,表示为类A的概率越小,y接近0时z接近0.5,表示判断为类A不太有把握。
- 数学模型
根据线性模型,Sigmoid函数,得到最终的模型
损失函数
现在是模型已经知道但是模型的参数未知,而且Sigmoid函数将输出变成概率,因此特别适合使用极大似然估计的原理求解模型。
首先需要设计似然函数。假设类别标签为0和1,则有
将两个式子合并得到,根据极大似然估计的原理(可以参考博客极大似然估计)可以设计损失函数如下
为了方便求导,同时ln函数不会改变单调性,所以对等式取对数将乘法运算转化为加法运算;原本的优化目标是最大化损失函数(对应于梯度上升法),但是大家习惯于最小化损失函数(对应梯度下降法),所以在公式前加一个负号,得到最终的损失函数:
有了损失函数,就可以利用梯度下降法来求模型的参数了,前面几篇博客有讲解,这里就不展开了。
注:其实上述损失函数还有另外一个名字:交叉熵损失函数,是不是很神奇?其实极大似然和交叉熵、最大熵的数学原理都有相通之处
代码实现
def GradientDescent(X, Y, lr, iters):
'''
全局梯度下降法,每次迭代以整个数据集更新参数
根据线性回归的损失函数求得梯度G=X.T*(exp(X*theta)/(exp(X*theta)+1)-Y)
:param X: (m,n+1) m个数据,每个数据n维 每个数据n维,在数据前加一列1方便计算wx+b
:param Y: (m,1) m个数据对应的输出结果
:param lr: 学习率
:param iters: 迭代次数
:return: theta: (n+1,1) 权重(b+w)
'''
sample_num = X.shape[0]
dim = X.shape[1]
theta = np.zeros((dim, 1)) # (n+1,1)
XT = X.transpose() # 表示X的转置
for i in range(iters):
Y_ = np.exp(np.dot(X, theta))/(np.exp(np.dot(X, theta))+1) # 模型预测值
gradient = np.dot(XT, Y_-Y)/sample_num # theta的梯度 (n+1,1)
# 梯度更新
theta = theta - lr*gradient
return theta
def logisticRegression(data, label, lr=0.1, iters=200):
sample_num = data.shape[0]
data = np.hstack((np.ones((sample_num, 1)), data)) # 在数据前加一列1方便计算wx+b
theta = GradientDescent(data, label, lr, iters)
return theta
def predict(x, theta):
x = np.hstack((np.ones((1)), x))
pre = np.exp(np.dot(x, theta))/(np.exp(np.dot(x, theta))+1)
if pre >= 0.5:
return 1
return 0
def test(data, label, theta):
sample_num = data.shape[0]
errorcount = 0.0
for i in range(sample_num):
x = data[i]
if label[i] != predict(x, theta):
errorcount += 1
accuracy = 1 - errorcount/sample_num
return accuracy
if __name__ == '__main__':
#加载训练集和验证集
traindata, trainlabel = loadData('../Mnist/mnist_train.csv')
evaldata, evallabel = loadData('../Mnist/mnist_test.csv')
theta = logisticRegression(traindata, trainlabel)
accuracy = test(evaldata, evallabel, theta)
print('accuracy rate is:', accuracy)