逻辑回归的原理和python实现
输入
鸢尾花卉数据集,数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性。
输出
根据手动实现的逻辑回归模型对鸢尾花卉数据集分类的预测结果。
原理
逻辑回归处理的是分类问题,线性回归处理的是回归问题,这是两者最本质的区别。逻辑回归算法是一种分类算法,适用于标签取值离散的情况。
逻辑回归的模型为:
其中,代表特征向量,代表逻辑函数,一个常用的逻辑函数公式为:
该函数的图像为:
通过公式和图像,我们可以总结下的作用:对于给定的输入变量,根据选择的参数计算输出变量等于1的概率,即,比如给定的变量,通过给定的参数计算得到 = 0.7,则表示有70%的几率输出变量为正向类,30%的几率为负向类。
在逻辑回归中,当时,预测y=1,当时,预测y=0。而这两种情况分别对应、,在几何上意味着是真正意义上的分界边界函数,常见的比如:(线性分界函数)、(二次分界函数)。在实际的业务场景中,可以选择复杂的分界函数来处理数据。
当分解函数选定之后,有一个非常重要的操作是确定逻辑回归模型的参数,按照机器学习的思路,我们要定义代价函数,通过梯度下降法更新参数,最小化代价函数来得到参数。
在线性回归模型中,采用的代价函数为所有样本误差的平方和,那是因为此时的代价函数是凸函数,一定会得到一个最小值。如果逻辑回归模型采用所有样本误差的平方和作为代价函数,那么得到的代价函数是非凸的,非凸函数有很多的极小值点,影响梯度下降法寻找全局最小值。
根据逻辑回归处理的分类问题本质上只有正、负样本的特性,我们定义逻辑回归的代价函数为:
其中,,其实这么定义也比较容易理解,比如对于一个y=1的正样本,由于是一个在作用域(0,1]上从正无穷大递减至0的函数,如果sigmoid函数模型预测的概率比较小,接近于0,预测为负样本,那么代价函数值就比较大,如果sigmoid函数模型预测的概率比较大,接近于1,预测为正样本,那么代价就比较小,如果概率为1的话,代价函数值就为0了,这不就刚好是我们想要的吗?同理可理解对于y=0时采用的代价函数。
我们将代价函数整理如下:
针对每个求偏导得到:
有了偏导之后,就可以更新参数:
在训练的过程中,有可能遇到到过拟合的问题,我们可以使用正则化的技术来较少过拟合的影响,我们对参数做适当的惩罚,优化代价函数为:
这里注意到,我们一般不对做惩罚。
同时,由于偏导有了改变,更新参数也需要对应改变:
这样我们就可以通过最小化代价函数,一步步更新参数,拟合出想要的参数了,得到一个逻辑回归分类器,但是如果我们处理的是多分类问题该怎么办呢?
假如我们处理的是一个三分类问题,有三个类别0,1,2。我们可以构造3个分类器:
那么如何构造这些分类器呢?我们以为例,在构造这个分类器时,标签类别为0的样本为正样本对应的y修改为1,其余的标签类别非0的样本为负样本对应的y修改为0,这样处理之后进行训练就可以得到一个可以分类0类别与非0类别的分类器了,同理另外两个分类器也可得到。
得到3个分类器后,对于一个样本而言,如何生成最终的类别呢?我们可以让三个分类器分别作用于该样本,值最大的分类器为最终的分类结果。
Python代码实现
import pandas as pd import numpy as np from sklearn.model_selection import train_test_split # 鸢尾花卉数据集,数据集包含150个数据样本,分为3类,每类50个数据,每个数据包含4个属性。 # 可通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。 iris_path = ‘./iris.csv‘ iris_data = pd.read_csv(iris_path) # 将字符串标签映射为int def iris_str_to_int(str): label = {‘setosa‘:0, ‘versicolor‘:1, ‘virginica‘:2} return label[str] new_iris_data = pd.io.parsers.read_csv(iris_path, converters={4:iris_str_to_int}) data = np.array(new_iris_data) # 对数据进行归一化 for col in range(0, len(data[0]) - 1): mean = np.mean(data[:, col]) std = np.std(data[:, col]) data[:, col] = data[:, col] - mean data[:, col] = data[:, col] / std # 划分数据集,30%测试集、70%训练集,使用sklearn的train_test_split方法 x, y = np.split(data, (4,), axis=1) x_train, x_test, y_train, y_test = train_test_split(x,y,test_size = 0.3) # sigmoid方法 def sigmoid(x): return 1.0/(1+np.exp(-x)) # 修改目标y值时使用的方法,因为是多分类,在训练时需要保证只有正类、负类两类 def func(value,lb): if any(value == lb): value = 1 else: value = 0 return value # 选择线性模型(theta_0 + theta_1 * x1 + theta_2 * x2 + ...)来做逻辑回归 # 因为有三类,所以要构造3个分类器,即为h0, h1, h2 # 根据入参的标签值,来对y进行修改, # 比方说,如果label = 0,说明这是h0分类器,那么y == 0为正样本(y值应从0修改为1),其他非0的修改为负样本(y值应修改为0) # 如果label = 1,说明这是h1分类器,那么y == 1为正样本(y值应从1修改为1),其他非1的修改为负样本(y值应修改为0) # 如果label = 2,说明这是h2分类器,那么y中2为正样本(y值应从2修改为1),其他非2的修改为负样本(y值应修改为0) alpha = np.random.random() #学习率 lamda = np.random.random() #正则化系数 theta = np.random.rand(5) # theta参数初始值 print(‘init alpha:{}, lamda:{}, theta:{}‘.format(alpha, lamda, theta)) def cost_line_reg(theta, x, y, label): theta = np.mat(theta) # x特征向量加一个元素x0,x0 == 1 x_one = np.ones(x.shape[0]) x = np.insert(x, 0, values= x_one, axis=1) x = np.mat(x) # 根据label参数指定的分类器种类,对应修改y值 y = np.apply_along_axis(func, axis=1, arr=y, lb=label) y = np.mat(y) # 计算损失值和更新theta for step in range(0,100): first = np.multiply((-y).T, np.log(sigmoid(np.dot(x, theta.T)))) second = np.multiply((1-y).T, np.log(1 - sigmoid(np.dot(x, theta.T)))) reg = (lamda/(2*len(x)) * np.sum(np.power(theta[:, 1:theta.shape[1]], 2))) cost = np.sum(first - second) / (len(x)) + reg # 更新theta for theta_index in range(0, len(theta.T)): h_theta = sigmoid(np.dot(x, theta.T)) sum = np.dot((h_theta.T - y), x[:, theta_index : theta_index + 1]) sum = np.sum(sum)/len(x) # theta0 与 其他的theta元素更新方式有点差异 if theta_index == 0: theta[0,theta_index] = theta[0,theta_index] - alpha * sum else: theta[0, theta_index] = theta[0, theta_index] - alpha * (sum + lamda/len(x) * theta[0, theta_index]) return np.array(theta) theta_result_h0 = cost_line_reg(theta, x_train, y_train, 0) # h0 分类器 theta_result_h1 = cost_line_reg(theta, x_train, y_train, 1) # h1 分类器 theta_result_h2 = cost_line_reg(theta, x_train, y_train, 2) # h2 分类器 print(‘theta_result_h0:{}‘.format(theta_result_h0)) print(‘theta_result_h1:{}‘.format(theta_result_h1)) print(‘theta_result_h2:{}‘.format(theta_result_h2)) # h0,h1,h2三个分类器确定好之后,把生成结果最大的分类器作为最终分类 # 输入是特征向量,返回的是分类结果 def h_theta(x_arr): # 这里默认三个分类器的分界函数都是线性的 h0_result = sigmoid(np.dot(x_arr , theta_result_h0.T)) h1_result = sigmoid(np.dot(x_arr, theta_result_h1.T)) h2_result = sigmoid(np.dot(x_arr , theta_result_h2.T)) # 结果最大的分类器作为最终分类 result_lable = 0 if h0_result > h1_result and h0_result > h2_result: result_lable = 0 elif h1_result > h0_result and h1_result > h2_result: result_lable = 1 elif h2_result > h0_result and h2_result > h1_result: result_lable = 2 return result_lable # 在数据集上进行测试 def verify(x, y): x_one = np.ones(x.shape[0]) x = np.insert(x, 0, values= x_one, axis=1) x = np.mat(x) # 各个真实类别的统计个数 y_real_0_num = 0 y_real_1_num = 0 y_real_2_num = 0 # 各个真实类别等于预测类别的统计个数 y_real_0_prediction_0_num = 0 y_real_1_prediction_1_num = 0 y_real_2_prediction_2_num = 0 # 各类别的预测准确度 accuracy_rite_0 = 0.0 accuracy_rite_1 = 0.0 accuracy_rite_2 = 0.0 accuracy_rite_whole = 0.0 for row in range(0, len(x)): x_arr = x[row,:] y_real = y[row,0] y_prediction = h_theta(x_arr) # 统计各个真实类别的个数 if y_real == 0: y_real_0_num = y_real_0_num + 1 elif y_real == 1: y_real_1_num = y_real_1_num + 1 elif y_real == 2: y_real_2_num = y_real_2_num + 1 # 统计各个真实类别等于预测类别的个数 if y_real == 0 and y_prediction == 0: y_real_0_prediction_0_num = y_real_0_prediction_0_num + 1 elif y_real == 1 and y_prediction == 1: y_real_1_prediction_1_num = y_real_1_prediction_1_num + 1 elif y_real == 2 and y_prediction == 2: y_real_2_prediction_2_num = y_real_2_prediction_2_num + 1 print(‘y_real_0_prediction_0_num[{}],y_real_0_num[{}], y_real_1_prediction_1_num[{}], y_real_1_num[{}], y_real_2_prediction_2_num[{}], y_real_2_num[{}]‘ .format(y_real_0_prediction_0_num, y_real_0_num, y_real_1_prediction_1_num, y_real_1_num, y_real_2_prediction_2_num, y_real_2_num)) accuracy_rite_0 = y_real_0_prediction_0_num / y_real_0_num accuracy_rite_1 = y_real_1_prediction_1_num / y_real_1_num accuracy_rite_2 = y_real_2_prediction_2_num / y_real_2_num accuracy_rite_whole = (y_real_0_prediction_0_num + y_real_1_prediction_1_num + y_real_2_prediction_2_num) / (len(x)) print(‘accuracy_rite_0[{}], accuracy_rite_1[{}], accuracy_rite_2[{}], accuracy_rite_whole[{}]‘.format(accuracy_rite_0, accuracy_rite_1, accuracy_rite_2, accuracy_rite_whole)) print(‘############## train_verify begin ##########‘) verify(x_train, y_train) print(‘############## train_verify end ##########‘) print(‘############## test_verify begin ##########‘) verify(x_test, y_test) print(‘############## test_verify end ##########‘)
代码运行结果
本文档重点在于讨论逻辑回归的原理和实现,所以代码里面的一些参数是随机生成的,每次执行的结果会有少许差异,工业环境中要对参数进行调优,选择最优化的参数。
执行程序,产生的测试结果如下:
init alpha:0.9878106271950878, lamda:0.07590602610318298, theta:[0.09612366 0.49599455 0.38297285 0.9730732 0.54394658] theta_result_h0:[[-1.91985969 -1.42605078 1.81454853 -1.74542862 -1.84684359]] theta_result_h1:[[-0.93718773 0.49878395 -1.18385395 0.59397282 -0.81950924]] theta_result_h2:[[-3.40965892 0.07144545 -0.59527357 2.66257363 2.65466814]] ############## train_verify begin ########## y_real_0_prediction_0_num[39],y_real_0_num[39], y_real_1_prediction_1_num[29], y_real_1_num[33], y_real_2_prediction_2_num[30], y_real_2_num[33] accuracy_rite_0[1.0], accuracy_rite_1[0.8787878787878788], accuracy_rite_2[0.9090909090909091], accuracy_rite_whole[0.9333333333333333] ############## train_verify end ########## ############## test_verify begin ########## y_real_0_prediction_0_num[11],y_real_0_num[11], y_real_1_prediction_1_num[16], y_real_1_num[17], y_real_2_prediction_2_num[17], y_real_2_num[17] accuracy_rite_0[1.0], accuracy_rite_1[0.9411764705882353], accuracy_rite_2[1.0], accuracy_rite_whole[0.9777777777777777] ############## test_verify end ##########