逻辑回归算法是一种用于解决分类问题的机器学习算法,尽管名字中包含"回归"一词,但实际上逻辑回归是一种分类算法,而不是回归算法。逻辑回归通常用于处理二分类问题(离散值的预测),即将输入数据分为两个类别。它的输出是一个介于0和1之间的概率值,表示某个样本属于某个类别的概率。
目的:
逻辑回归算法基于逻辑函数(也称为Sigmoid函数)来建模输出,将线性组合的特征与Sigmoid函数结合,从而得到一个在0到1之间的概率值。在训练过程中,逻辑回归通过最大化似然函数或最小化损失函数(比如交叉熵损失函数)来学习模型参数,以便能够对新的样本进行分类。
作用:
逻辑回归常用于二分类问题,例如判断邮件是垃圾邮件还是非垃圾邮件、患者是否患有某种疾病等。虽然逻辑回归是一种简单的算法,但在许多实际应用中仍然非常有用,并且可以作为其他更复杂分类算法的基准。
逻辑回归算法的结构相对简单而直观。让我用文字描述一下逻辑回归算法的基本结构:
总的来说,逻辑回归算法的结构包括输入层、权重、线性组合、激活函数、输出层、损失函数和优化算法。这些组件共同作用,使得逻辑回归能够有效地进行二分类任务的预测。希望这个简要的描述能帮助您更好地理解逻辑回归算法的结构!
1.读取文件,存储数据
importnumpyasnpimportpandasaspdimportmatplotlib.pyplotasplt#引入所需要的库path='ex2data1.txt'#文件路径data=pd.read_csv(path,header=None,names=['Exam1','Exam2','Admitted'])#存储文件数据data.head()#简化文件数据2.提取与观测数据(注:这里不是重点,只是通过图像的形式能让你有更直观的理解)
positive=data[data['Admitted'].isin([1])]negative=data[data['Admitted'].isin([0])]fig,ax=plt.subplots(figsize=(12,8))ax.scatter(positive['Exam1'],positive['Exam2'],s=50,c='b',marker='o',label='Admitted')ax.scatter(negative['Exam1'],negative['Exam2'],s=50,c='r',marker='x',label='NotAdmitted')ax.legend()ax.set_xlabel('Exam1Score')ax.set_ylabel('Exam2Score')plt.show()这段代码是用来创建一个散点图来可视化数据集中的两个特征(Exam1和Exam2)和它们与是否被录取的关系。让我逐句解释一下这段代码:
data['Admitted'].isin([1])这段代码是Pandas中的一种用法,它的作用是检查DataFrame(数据框)中'Admitted'列中的每个元素是否等于1。具体来说,isin([1])方法会返回一个布尔值的Series,其中每个元素都表示对应位置的元素是否等于1。如果某个元素等于1,则对应位置的值为True,否则为False。
在这种情况下,这段代码被用来筛选出'Admitted'列中值为1的行,即被录取的数据点。这样可以方便地从数据集中提取出符合特定条件的子集,进行进一步的分析或可视化操作。
当我们解析这段代码ax.scatter(positive['Exam1'],positive['Exam2'],s=50,c='b',marker='o',label='Admitted')时,我们可以逐个参数来理解它的作用:
综合起来,这段代码的作用是在散点图中绘制被录取的学生数据点,横坐标为第一次考试成绩,纵坐标为第二次考试成绩,点的大小为50,颜色为蓝色,形状为圆圈,并且在图例中标记为'Admitted'。这样可以直观地展示被录取学生的分布情况。
这段代码fig,ax=plt.subplots(figsize=(12,8))主要是用来创建一个新的图形(figure)和一个或多个子图(axes)对象。让我来解释一下其中的细节:
综合起来,这段代码的作用是创建一个新的图形和子图对象,图形的大小为宽度12英寸,高度8英寸。这样可以为后续的数据可视化操作提供一个基础框架。
3.数据准备与处理
#addonescolumn,添加一组全1向量data.insert(0,'Ones',1)#setX(trainingdata)andY(targetvariable)X=data.iloc[:,0:-1].valuesY=data.iloc[:,-1].valuestheta=np.zeros(3)#参数矩阵初始化cost(theta,X,Y)#计算初始的代价,这里的cost函数暂且看不懂没关系,下面有解释gradient(theta,X,Y)#进行梯度下降求最合适的参数矩阵,这里的gradient函数暂且看不懂也没关系,下面有解释#以上大概就是简单逻辑回归的全部步骤,接下来就是单独对这几个函数进行解释这段代码是用Python编写的,主要是用于设置训练数据和目标变量。让我逐句解释:
这些代码片段一起构成了准备线性回归模型所需的数据和参数的过程。
当数据全部准备好后X,Y,θ的值分别是:
一定要注意的是,这里的X,Y,θ本质上都是数组而不是矩阵,但其运算和使用的时候与数组几乎并无差别,所以为了易于接受理解,本文我全采用矩阵的口吻,至于为什么是数组,因为在Python中,如果你使用的是NumPy库中的数组(numpyarray),数组之间是可以进行乘法操作的。当你对两个NumPy数组进行乘法操作时,它们会执行元素级别的乘法,也就是对应位置的元素相乘。例如,如果有两个NumPy数组array1和array2,你可以通过array1*array2来实现元素级别的乘法操作。这意味着它们的形状必须相容,例如两个一维数组的长度必须相同,或者对于多维数组,对应维度的大小必须一致。总的来说,在Python中,如果你使用NumPy库中的数组,你可以对数组之间进行乘法操作。这种元素级别的乘法可以在很多数据处理和科学计算的情况下非常有用。
Sigmoid函数是一种常用的激活函数,也称为逻辑函数,通常用于二分类问题中。别看它叫逻辑回归,其实和逻辑和回归问题没多大关系,逻辑是因为音译的原因,回归的话大概率是因为借助了回归的思想原理。
在逻辑回归中,Sigmoid函数扮演着关键的角色。逻辑回归是一种广泛应用于分类问题的机器学习算法,它的目标是将输入特征映射到一个介于0和1之间的概率值,表示某个样本属于正类的概率。Sigmoid函数的作用是将逻辑回归模型的输出转换为这种概率值。
Sigmoid函数具有S形状曲线,当z趋近于正无穷大时,Sigmoid函数的值趋近于1;当z趋近于负无穷大时,Sigmoid函数的值趋近于0。这种性质使得Sigmoid函数在将实数映射到概率值时非常有效。
具体来说,逻辑回归模型会对输入特征进行加权求和,并将结果输入到Sigmoid函数中,该函数将实数值映射到0到1之间。这样,对于给定的输入特征,逻辑回归模型会输出一个介于0和1之间的概率值,表示该样本属于正类的可能性。
通过Sigmoid函数的作用,逻辑回归模型可以进行二分类预测,将连续的实数输出转换为概率值,并根据设定的阈值来判断样本属于哪个类别。这种机制使得逻辑回归成为处理二分类问题的一种强大工具。
defsigmoid(z):return1/(1+np.exp(-z))这里的z就是θ的转置乘以特征矩阵x,也就是θ^(T)X,在这段代码中的话没有对z进行拓展和细节化处理,可以增加程序以及函数的灵活性
引入:既然我们知道了拟合函数,知道了我们最终的目的就是通过这个函数更好去预测一个介于0~1的概率值,那么我们就需要找到一组合适的参数来最大限度地减少其中预测值和真实值的总误差(虽然真实值不是0就是1,但是预测值不一定总是整数的1或0),此时我们就能够通过构造代价函数找出总误差值最小的那组系数。
其次,我们发现该代价函数分成了两段,这又是为什么
逻辑回归中的代价函数通常是由两部分组成的,这种设计有其特定的意义和作用。在逻辑回归中,常用的代价函数是交叉熵损失函数(Cross-EntropyLoss)。这个损失函数在逻辑回归中被广泛使用,因为它能很好地衡量模型输出与实际标签之间的差异。
交叉熵损失函数的两段分别是针对正类别和负类别的情况。
这种两段式的设计使得代价函数能够更好地对模型的预测进行惩罚,当模型的预测与实际标签不一致时,会产生较大的损失。通过这种设计,模型在训练过程中能够更好地学习正确的分类边界,提高分类的准确性。
defcost(theta,X,Y):first=Y*np.log(sigmoid(X@theta.T))second=(1-Y)*np.log(1-sigmoid(X@theta.T))return-1*np.mean(first+second)通过这个函数,我们就能求出对于某组参数值θ所对应的代价值,然后通过比较每组θ值的代价值大小来选出最合适,误差最小,拟合效果最好的θ参数矩阵。那么问题来了,我们怎么更新θ参数矩阵呢便是梯度下降了。
代码复现:
方法一
#计算步长defgradient(theta,X,Y):return(1/len(X)*X.T@(sigmoid(X@theta.T)-Y))#使用Scipy.optimize.fmin_tnc优化函数拟合最优的θimportscipy.optimizeasoptresult=opt.fmin_tnc(func=cost,x0=theta,fprime=gradient,args=(X,Y))result#(array([-25.16131862,0.20623159,0.20147149]),36,0)#这里是为了输出θ,更直观的看看cost(result[0],X,Y)scipy.optimize.fmin_tnc是SciPy库中的一个函数,用于执行无约束最小化(优化)问题的优化算法。TNC代表TruncatedNewtonConjugate-Gradient,是一种基于牛顿共轭梯度方法的优化算法。这个函数的主要作用是寻找使得给定目标函数最小化的参数值。在优化过程中,它使用梯度信息来迭代地调整参数值,以便找到使目标函数最小化的最优解。
scipy.optimize.fmin_tnc函数通常需要提供以下几个参数:
通过调用scipy.optimize.fmin_tnc函数,可以方便地在Python中进行数值优化,寻找目标函数的最小值。这个函数在处理大规模的无约束优化问题时非常有用,尤其是当目标函数具有复杂的形式或者无法通过解析方法求解时。
方法二:
#传统梯度下降法theta=np.matrix(theta)X=np.matrix(X)Y=np.matrix(Y)parameters=int(theta.ravel().shape[1])grad=np.zeros(parameters)print(X.shape,theta.shape,(theta.T).shape,(X*theta.T).shape)error=sigmoid(X*theta.T)-Yforiinrange(parameters):term=np.multiply(error,X[:,i])grad[i]=np.sum(term)/len(X)returngrad让我逐句来解释一下:
这段代码的目的是计算逻辑回归模型的梯度,用于更新参数以最小化损失函数。
方法三:
#使用Scipy.optimize.minimize拟合最优的thetares=opt.minimize(fun=cost,x0=np.array(theta),args=(X,np.array(Y)),method='Newton-CG',jac=gradient)res#结果:fun:0.20349770451259855#jac:array([1.62947970e-05,1.11339134e-03,1.07609314e-03])#message:'Optimizationterminatedsuccessfully.'#nfev:71#nhev:0#nit:28#njev:242#status:0#success:True#x:array([-25.16576744,0.20626712,0.20150754])(这里就是我们想要的theta矩阵)cost(res.x,X,Y)scipy.optimize.minimize是SciPy库中一个非常强大的函数,用于解决多种数学优化问题,包括最小化或最大化标量函数、约束优化、全局优化等。这个函数提供了一个统一的接口,可以通过指定不同的方法和选项来解决各种优化问题。
使用scipy.optimize.minimize可以通过指定目标函数、初始猜测值、优化方法、约束条件等参数来寻找目标函数的最小值或最大值。它支持多种优化算法,如共轭梯度法、BFGS算法、L-BFGS-B算法等,以及处理约束优化问题的方法。
一般来说,scipy.optimize.minimize函数需要提供以下几个参数:
通过调用scipy.optimize.minimize函数,可以在Python中方便地进行各种数学优化问题的求解,无论是简单的无约束优化问题还是复杂的带约束优化问题。这个函数在科学计算、机器学习、工程优化等领域都有广泛的应用。
方法一和方法三是借助优化函数库,方法二是传统的梯度下降
好了,到这里我们应该就能得到一个拟合效果相对较好的假设函数了,接下来就是使用该函数进行预测分析并且计算校验一下该模型的准确率
因为逻辑回归不同于线性回归对连续值的预测,所计算出来的值就是结果值,逻辑回归主要还是分类问题,需要将计算所得的结果进行适当处理以求得到最终的结果
模型使用与预测:
defpredict(theta,X):probability=sigmoid(X@theta.T)return[1ifx>=0.5else0forxinprobability]X为特征矩阵,theta为之前求出来的较合适的θ参数组,这里的输出值也就是每一个样本的分类结果(对应0或1两种)
那么我们既然利用模型预测出了对应结果,不妨看看我们的模型的预测结果的准确率是多少(这里说一下,之前的预测不是真的"预测",需要我们同时也拿到Y值真实结果,只有这样子才能判断我们训练出来的模型准不准确)
模型准确率计算:
theta_min=np.matrix(result[0])predictions=predict(theta_min,X)correct=[1ifa^b==0else0for(a,b)inzip(predictions,Y)]accuracy=(sum(correct)/len(correct))print('accuracy={0:.0f}%'.format(accuracy*100))当你执行这段代码时,它做了以下几件事情:
这段代码主要用于评估一个模型的准确率,通过比较模型预测结果和实际标签,然后计算出准确率并输出。
(1)
zip(predictions,Y)是Python中的一个内置函数,它接受多个可迭代对象作为参数,并返回一个将这些可迭代对象中对应位置的元素打包成元组的迭代器。
在这种情况下,zip(predictions,Y)将两个可迭代对象predictions和Y中对应位置的元素一一配对,形成一个迭代器。例如,如果predictions是[0,1,0],Y是[1,1,0],那么zip(predictions,Y)将生成一个迭代器,其中第一个元素是(0,1),第二个元素是(1,1),第三个元素是(0,0)。
在上面提到的代码中,zip(predictions,Y)的作用是将模型的预测值predictions和实际标签Y对应位置的元素配对,这样可以方便地同时遍历这两个列表,进行比较和操作。
(2)
这行代码中的1ifa^b==0else0是一个条件表达式,也称为三元运算符。它的作用是根据条件a^b==0的真假来返回不同的值。在这里,a^b表示a和b的异或运算,即按位异或操作。(在很多编程语言中,包括Python,^符号通常用来表示按位异或运算,而不是乘方运算。在Python中,^运算符被用于执行位运算,对两个数的二进制位进行异或操作。如果你想进行乘方运算,应该使用**运算符,而不是^。所以在这种情况下,a^b表示的是a和b的按位异或运算,而不是乘方运算。如果a和b相等,那么a和b的每一位都是相同的,因此它们的异或操作结果为0。换句话说,如果a和b相等,那么a^b的值将为0。而如果a和b不相等,那么它们的二进制表示中至少有一位是不同的。在这种情况下,进行异或操作会使得对应的位为1。所以,如果a和b不相等,a^b的值将不为0。)
如果a^b的结果等于0,那么条件a^b==0就为真,此时该条件表达式会返回1;否则,即a^b!=0,条件表达式返回0。
因此,整个列表推导式correct=[1ifa^b==0else0for(a,b)inzip(predictions,Y)]的作用是根据predictions和Y中对应位置的元素进行异或运算,如果结果为0,则对应位置的correct列表中的元素为1,否则为0。这样可以根据模型的预测值和实际标签的异或结果来判断预测是否正确,并将结果存储在correct列表中。
1.那么以上就是基础的模型框架了,如果你想对该模型了解的更深一点,那么还有查准率,召回率,调和平均数这几个数值可以调
#support标签中出现的次数#precision查准率,recall召回率,f1-score调和平均数fromsklearn.metricsimportclassification_reportprint(classification_report(Y,predictions))关于这里的这三个率是什么意思我后面会讲。
2.决策边界:
我们就是根据该样本点落在这条拟合线的左右两边来判断出最终结果的
3.特征映射
deffeature_mapping(x,y,power,as_ndarray=False):data={'f{0}{1}'.format(i-p,p):np.power(x,i-p)*np.power(y,p)foriinrange(0,power+1)forpinrange(0,i+1)}ifas_ndarray:returnpd.DataFrame(data).valueselse:returnpd.DataFrame(data)这段代码是一个Python函数,名为feature_mapping,用于进行特征映射。让我逐句解释它:
这段代码主要完成了一个特征映射的过程,可以将输入的x和y映射到更高维度的特征空间中。希望这个解释能够帮助你理解这段代码的功能。如果有任何其他问题或需要更多解释,请随时告诉我。
当考虑一个简单的特征映射示例时,我们可以想象一个二维数据集,其中包含一些数据点,它们无法通过一条直线进行良好的分类。这时,我们可以利用特征映射将这些数据点映射到一个更高维的空间,以便更好地进行分类。
假设我们有一个二维数据集,包括两个特征:x1和x2。在二维空间中,这些数据点可能无法通过一条直线很好地分开两个类别。但是,如果我们将这些二维数据点通过特征映射映射到三维空间,我们可以添加一个新的特征x3=x12+x22.这样,我们就将二维数据映射到了一个更高维的三维空间。
在这个新的三维空间中,数据点可能会呈现出线性可分的特性,即存在一个平面可以很好地将两个类别分开。通过这种方式,我们利用特征映射将原始数据转换为一个更适合分类的形式。
这个简单的例子展示了特征映射如何可以帮助我们处理原始数据,并使得数据更容易分类。
简单例子:
想象一下你正在准备烤蛋糕。在配方中,你有两种原料:面粉和砂糖。现在,你想要根据这两种原料的不同比例来判断蛋糕的口感是甜蜜还是偏向面粉味。
在原始的二维空间中,你只能看到面粉和砂糖的比例,无法准确判断口感。但是,通过特征映射,你可以引入一个新的特征,比如面粉的平方加上砂糖的平方,这样就将二维空间映射到了三维空间。
在这个新的三维空间中,你可以更好地区分不同口感的蛋糕。比如,如果面粉和砂糖的平方和较大,可能代表口感更甜蜜;反之,如果平方和较小,可能代表口感更偏向面粉味。
通过这种特征映射,你在更高维度的空间中可以更好地理解和分类不同口感的蛋糕,就像在二维空间中无法做到的那样。
好了,现在我们对大致模型已经有了基本的概念,但大家有没有考虑过这样一个问题,当我们经过过多次数的迭代,使得到的θ参数矩阵越来越准确,拟合效果越来越好,输出值越来越接近真实值,那么当最终拟合的数据与真实值完全相等的时候,拟合的数据确实是100%准确了,但是这样不但会造成拟合的函数扭曲,形状怪异,还会大大减少拟合函数的预测作用和泛化能力,这就是所谓的过拟合问题了。同样的,欠拟合问题也是如此,都是因为一个合适的迭代次数的问题,那么我们在遇到如此问题的时候该怎么处理呢那就是,正则化!
在这里我们对代价函数加上正则化代码复现:
#特征映射与数据准备theta=np.zeros(data.shape[1])X=feature_mapping(x1,x2,power=6,as_ndarray=True)X.shape,Y.shape,theta.shape#Sigmoid激活函数defsigmoid(z):return1/(1+np.exp(-z))#基础代价函数defcost(theta,X,Y):first=Y*np.log(sigmoid(X@theta.T))second=(1-Y)*np.log(1-sigmoid(X@theta.T))return-1*np.mean(first+second)#正则化代价函数defregularized_cost(theta,X,Y,l=1):theta_1n=theta[1:]regularized_term=l/(2*len(X))*np.power(theta_1n,2).sum()returncost(theta,X,Y)+regularized_term#运行正则化代价函数cost(theta,X,Y)regularized_cost(theta,X,Y,l=1)regularized_term=l/(2*len(X))*np.power(theta_1n,2).sum()代码解释(其他部分请看前面):
综合起来,这行代码的作用是:
这个计算过程是正则化项的一部分,用于在训练模型时惩罚模型复杂度,防止过拟合。正则化项通常将模型参数的平方和加入到损失函数中,以确保模型不会过度依赖于任何一个特征,从而提高模型的泛化能力
#基础梯度下降defgradient(theta,X,Y):return(1/len(X)*X.T@(sigmoid(X@theta.T)-Y))#正则化梯度下降defregularized_gradient(theta,X,Y,l=1):theta_1n=theta[1:]regularized_theta=l/len(X)*theta_1n#regularized_theta[0]=0regularized_term=np.concatenate([np.array([0]),regularized_theta])returngradient(theta,X,Y)+regularized_term#returngradient(theta,X,Y)+regularized_thetagradient(theta,X,Y)regularized_gradient(theta,X,Y)regularized_term=np.concatenate([np.array([0]),regularized_theta])代码解释:
这段代码中,regularized_term=np.concatenate([np.array([0]),regularized_theta])的目的是创建一个新的参数向量regularized_term,其中包含了一个额外的零值作为第一个元素,然后将regularized_theta中的所有元素依次添加到新向量中。
让我来详细解释一下这行代码的作用:
因此,整体来说,这行代码的作用是创建一个新的参数向量regularized_term,其中包含了一个额外的零值作为第一个元素,然后将regularized_theta中的所有参数依次添加到新向量中。这种操作通常用于在某些机器学习算法中对参数向量进行处理或扩展。
通过这两个梯度下降,只要我们选择较合适的λ值,就能起到对欠拟合和过拟合很好的约束作用!