逻辑回归其实是只有一个神经元的神经网络,是一个天然的二分类算法,如下图所示(图为网图):
前面这部分是一个线性的模型,如果你熟悉线性回归的话应该不陌生。
写出来应该是这样:
和多元线性回归的式子是一模一样的。
中间这部分是激活函数,一般是S型函数sigmoid,其中
import numpy as np
def sigmoid(z):
return 1. / (1. + np.exp(-z))
#画出sigmoid函数图像
import matplotlib.pyplot as plt
x = np.linspace(-10,10,100)
y = sigmoid(x)
plt.plot(x,y)
plt.title("sigmoid(z)")
plt.show()
你可以看出来sigmoid函数的值域是(0,1)之间,如果函数的输出值大于0.5,就认为它属于1这类,小于0.5就属于0类。
以上就是逻辑回归的执行流程。
有关线性回归的部分我就不解释了,没有这个基础的可以看我专栏的其他文章:
夜星辰:多元线性回归(附代码实现)zhuanlan.zhihu.com
夜星辰:一元线性回归(附代码实现)zhuanlan.zhihu.com
夜星辰:为什么线性回归也是神经网络zhuanlan.zhihu.com
所有的AI问题,其实都能分为两个部分,一部分是模型,还有一部分是优化。
我们刚刚介绍了逻辑回归的模型部分,现在的问题是如何优化。
逻辑回归的优化和线性回归有些不同,线性回归能直接用公式解出最佳的参数,如:正规方程,最小二乘法。
但是逻辑回归
只能用梯度下降法来逐步优化参数。
按照优化的套路,我们的目标是使损失函数最小,逻辑回归的损失函数是交叉熵。
交叉熵损失函数
(其中y是真实值标签,
是预测值)
这个损失函数咋一看非常难懂,不过考虑到逻辑回归只是一个二分类算法的话,标签y的值只能是0,或者1。我们就可以试着把交叉熵损失函数分解成更简单的形式。
当标签即真实值y是0时:
当标签即真实值y是1时
y_hat = np.linspace(0.01,0.99,100) #防止log0错误
y_0 = -np.log(1-y_hat)
plt.plot(y_hat,y_0)
plt.title("if y = 0 cost = $-ln(1-hat{y})$")
plt.show()
y_1 = -np.log(y_hat)
plt.plot(y_hat,y_1)
plt.title("if y = 1 $cost = -ln(hat{y})$")
plt.show()
由上图可知,
预测值和真实值越接近,loss就会越小,反之,预测值和真实值相差越大,loss的值就会越大。由此我们可以根据损失函数求其参数的梯度,使用梯度下降法求出其最优解。
梯度下降的优化公式
偏导数我都算好了,以下是优化的公式,别的不知道可以,这个不知道铁定写不出代码来。
因为逻辑回归是一个天然的二分类算法,所以这次我们就使用sklearn自带的乳腺癌预测数据集来测试代码。
导入数据并划分测试集
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
x_data = cancer.data
y_data = cancer.target
x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size=0.2, random_state=666666)
生成偏置b
操作和之前的多元线性回归一样
X_b = np.hstack([np.ones((len(x_train), 1)), x_train])
X_b
生成系数
theta = np.zeros(X_b.shape[1]) #这个系数可以随机初始化,你可以初始化为正态分布的
#theta = np.random.randn(X_b.shape[1])
系数与特征做矩阵乘法输入到sigmoid函数里
print("{:.10f}".format(sigmoid(X_b.dot(theta))[0])) #得到预测结果
以上就完成了一次正向传播,预测。
优化参数
#损失函数
def J(theta):
y_hat = sigmoid(X_b.dot(theta))
return - np.sum(y_train*np.log(y_hat) + (1-y_train)*np.log(1-y_hat)) / len(y_train)
#梯度
def dJ(theta):
return X_b.T.dot(sigmoid(X_b.dot(theta)) - y_train) / len(y_train)
#梯度下降法优化
iter_num = 0
max_iter = 10000
learing_rate = 0.01
while iter_num < max_iter:
iter_num += 1
last_theta = theta
theta = theta - learing_rate * dJ(theta)
if (abs(J(theta) - J(last_theta)) < 1e-7):
break
结果优化后得到了最终参数
intercept = theta[0] #截距
coef = theta[1:] #系数
得到预测结果
y_predict = sigmoid(X_b.dot(theta))
y_predict = np.array(y_predict >= 0.5 ,dtype='int')
y_predict
将其封装好,和sklearn一样:
import numpy as np
class MyLogisticRegression:
def __init__(self,learning_rate=0.001,max_iter=10000):
self._theta = None
self.intercept_ = None
self.coef_ = None
self.learning_rate = learning_rate
self.max_iter = max_iter
def _sigmoid(self,z):
return 1. / (1. + np.exp(-z))
def fit(self,x_train,y_train):
def J(theta, X_b, y_train):
y_hat = self._sigmoid(X_b.dot(theta))
return - np.sum(y_train*np.log(y_hat) + (1-y_train)*np.log(1-y_hat)) / len(y_train)
def dJ(theta, X_b, y_train):
y_hat = self._sigmoid(X_b.dot(theta))
return X_b.T.dot(y_hat - y_train) / len(y_train)
X_b = np.hstack([np.ones((len(x_train), 1)), x_train])
self._theta = np.random.randn(X_b.shape[1]) #这里我用了随机初始化,初始化为正态分布
iter_num = 0
while iter_num < self.max_iter:
iter_num += 1
last_theta = self._theta
self._theta = self._theta - self.learning_rate * dJ(self._theta,X_b,y_train)
if (abs(J(self._theta,X_b,y_train) - J(last_theta,X_b,y_train)) < 1e-7):
break
self.intercept_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
def predict(self,x_predict):
X_b = np.hstack([np.ones((len(x_predict), 1)), x_predict])
y_predict = self._sigmoid(X_b.dot(self._theta))
y_predict = np.array(y_predict >= 0.5 , dtype = 'int')
return y_predict
def score(self,x_test,y_test):
y_predict = self.predict(x_test)
sum_acc = np.sum(y_predict==y_test)
return sum_acc / len(y_test)
def __repr__(self):
return "LogisticRegression()"