最近在实习的时候小组组织了一个机器学习的讲座,说来也很惭愧,作为一个专业为模式识别的研究生,除了调调库,调调参,还真的没有认认真真的深入去研究过机器学习的算法原理。所以趁着这一次的作业机会,好好地推导理解了一下LogisticRegression的原理。
logistic回归是一种广义线性模型,这类模型因为因变量的不同定义的名称也不一样,如果是连续的,就是多重线性回归,如果是二项分布,就是logistic回归,其他的还有诸如passion回归,负二项回归等等之类。
logistic回归的因变量可以是二分非线性差分方程类的,也可以是多分类的,但是通常在实际当中二分类用的比较多,也可能是因为形式比较简单,容易理解吧。
为了说明清楚logistic回归的原理,我们先从一个简单的小例子说起来引入极大似然估计,当然这个例子也是看的网上一个博客的。
原文地址:深入浅出最大似然估计(Maximum Likelihood Estimation)
问题是这样的:在一个袋子里面装着白色和红色两种颜色的球,从袋子里面每次取出一个球然后放回去,这样重复取出10次之后得到的结果是白色球出现了7次,红色球出现了3次。问:随机取出一个球是白色球的概率为多少?
这个问题一看很简单嘛,由样本估计总体,所以白色球的概率为
的确这个计算结果是正确的,但是它是基于样本对于总体的一个估计得出的,我们有没有办法用理论的方法来显式的表达出同样的结果呢?当然是可以的。这就是极大似然估计(Maximum likelihood estimation, 简称MLE)提出的意义所在了。
极大似然估计是一种统计方法,它用来求一个样本集的相关概率密度函数的参数。这个方法最早是遗传学家以及统计学家罗纳德·费雪爵士在1912年至1922年间开始使用的。直观的意思就是说通过已知的样本来得出与已有的样本最相似的模型。具体是什么意思呢,先从贝叶斯分类器说起更加好理解。
贝叶斯决策
经典的贝叶斯公式为
其中 p(w) p ( w ) 为先验概率,代表的意思是类别 w w 分布的概率;p(x|w) p ( x | w ) 为条件概率,表示在类别 w w 中,发生事件x x 的概率;而 p(w|x) p ( w | x ) 为后验概率,代表的意思是在事件 x x 发生的情况下,该事件属于类别w w 的概率。而贝叶斯分类就是在这个后验概率的概念上对样本做出的一个分类判断,当后验概率越大,说明某个事件属于这个类别的可能性越大,那么我们就越有理由将它归于这个类别下。
概率论的概念忘了?没关系,我们来看一个非常直观易懂的例子来说明整个的计算过程。已知:在一个大学当中男生戴眼镜的概率为 23 2 3 ,女生戴眼镜的概率为 14 1 4 ,并且该大学当中男女比例为7:3,问:若你在大学当中遇到一个戴眼镜的童鞋,请问ta为男生或者女生的概率分别为多少?
我们来看看这个问题怎么解决:
首先假设: w1 w 1 代表男生, w2 w 2 代表女生, x x 代表戴眼镜。
那么由问题当中已知的条件可以得到
而男生和女生戴眼镜的事件相互独立,所以可以得到
根据贝叶斯公式计算得到遇到一个戴眼镜的童鞋属于男生与女生的概率分别为
怎么样很简单吧!然而在实际的问题当中,我们大多时候是没办法清楚地知道先验概率和条件概率的,而只有一些抽样的样本是已知的。在这种情况下一种可行解决的办法就是利用样本来估计总体的先验概率与条件概率,然后再套用贝叶斯公式来求解。这也是贝叶斯分类器的实现原理。
先验概率的估计较简单,1、每个样本所属的自然状态都是已知的(有监督学习);2、依靠经验;3、用训练样本中各类出现的频率估计。
类条件概率的估计(非常难),原因包括:概率密度函数包含了一个随机变量的全部信息;样本数据可能不多;特征向量x的维度可能很大等等。总之要直接估计类条件概率的密度函数很难。解决的办法就是,把估计完全未知的概率密度转化为估计参数。这里就将概率密度估计问题转化为参数估计问题,极大似然估计就是一种参数估计方法。当然了,概率密度函数的选取很重要,模型正确,在样本数量趋向无穷时,我们会得到较准确的估计值,如果模型都错了,那估计半天的参数,肯定也没啥意义了。下面的博客里面有更加详尽的解释
极大似然估计详解
为了直观的看懂什么是极大似然估计,我们再回到原先的红白球问题当中。
首先定义如下计算式
上式中 f(x|θ) f ( x | θ ) 代表在参数 θ θ 下的模型,其中 x x 代表样本的输入特征,式子的整体意义代表样本x1,x2 x 1 , x 2 分别所产生的输出的概率相乘的结果。
而其中 θ θ 是未知的,就是我们所需要估计的参数,这样我们可以定义极大似然估计公式为
如果上式求导不方便则可以对方程左右两边同时取对数得到求和的形式
还有一个平均似然函数就是上面的对数似然函数进行一个平均操作
求解最大似然函数的过程就是一个寻找最优参数 θ θ 使得模型最适合样本的过程,所以得到优化方程如下
这里讨论的情况是2个样本的情形,扩展到多次采用的情形下
我们定义 M(x|θ)=θ M ( x | θ ) = θ 为模型(也就是之前公式中的 f f ),该模型中只有一个参数表示抽到白球的概率为θ θ ,那么抽到红球的概率为 (1−θ) ( 1 − θ ) ,因此10次抽取抽到白球7次的概率可以表示为
将其描述为平均似然可得
那极大似然估计就是找到一个合适的 θ θ ,使得平均似然函数的值最大。这里可以这么理解,当平均似然函数的值最大的时候代表这10个样本所产生的输出最接近样本的输出,即该模型最贴近样本的模型,而同样样本的模型可以用来估计总体的模型。因此我们可以对平均似然的公式对 θ θ 求导(当有多个参数求偏导),并使得导数为0来求得极值点使得平均似然函数的值最大。
由此可得,当抽取白球的概率为0.7时,最可能产生10次抽取抽到白球7次的事件。
以上就是极大似然估计的解释。
我们知道线性回归的模型求得是输入向量与输出之间的一个线性关系,即
输出量是连续的,而logistic回归也是基于线性回归的的思想构建的,所以也叫广义线性回归模型。它的模型在线性方程上采用了一个激活函数sigmoid
sigmoid的输入输出关系如下所示
应用到线性回归模型上面就是
于是,线性模型的输出被映射到了0-1之间,当线性模型输出为0时,激活函数的输出刚好位于 12 1 2 处,刚好将线性模型的平面分成了两部分。这也就为分类提供了依据。
当 hθ(x) h θ ( x ) <0.5 则说明当前数据属于A类
当 hθ(x) h θ ( x ) >0.5 则说明当前数据属于B类
所以我们可以将sigmoid函数看成是样本的概率密度函数。
所以我们可以得出
其中 P(y=1|x;θ) P ( y = 1 | x ; θ ) 表示模型参数为 θ θ 时输入样本 x x 的输出为类别1的概率。
有了以上的公式,我们就可以结合极大似然估计来求解模型的参数了
首相上面的概率公式可以写在一起为
因为样本数据独立,所以它们的联合分布可以表示为各边际分布的的乘积,于是得到似然函数
相应的对数似然函数为
极大似然估计的求解就是求出使得似然函数取到最大值是时候 θ θ 值,也就是对 θ θ 求导数,这里 θ θ 是一个矩阵,于是要对矩阵中的各个参数求偏导数,并且令偏导数为0,求解出方程组来得到最佳参数。而求解偏导数的过程多多少少有些繁琐,这里我们也可以使用梯度下降法来求解。
具体的求解过程可以参考文章
[机器学习笔记1]Logistic回归总结
/**
* @author ALiang
* @date 2018/06/14
*/
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
public class LogisticRegression {
//训练数据
private double[][] trainData;
//样本的标签 二分类为 0 或 1
private double[] label;
//每次预测的值
private double[] predict;
//样本数量
private int sampleNum;
//样本的特征维度
private int sampleDim;
//模型参数 这里假设是线性模型 y = wx + b ; (b = 0)
private double[] parameters;
//梯度下降的步长
private double sigma = 0.001;
//模型停止的阈值
private double epsilon = 1e-9;
private void readData(double[][] trainData, double[] label) {
if (trainData == null || label == null) {
throw new RuntimeException("训练数据无效!");
}
sampleNum = trainData.length;
sampleDim = trainData[0].length;
//参数初始化 这里完全随机 更好可以使用一些分布
parameters = new double[sampleDim];
Random rand = new Random();
for(int i = 0; i < parameters.length; i++){
parameters[i] = rand.nextDouble();
}
this.trainData = trainData;
this.label = label;
this.predict = new double[sampleNum];
}
/**
* 训练器
* @param trainData
* @param label
* @param maxIters
* @param debug
*/
public void train(double[][] trainData, double[] label, int maxIters, boolean debug) {
//准备数据
readData(trainData, label);
System.out.println("开始训练...");
//训练
for (int i = 0; i < maxIters ; i++) {
//一次前向传播
forward();
double error = calcError();
if(debug){
System.out.println("第" + i + "次的平均误差:" + error);
}
if(error < epsilon){
break;
}
//一次反向参数更新
backward();
}
System.out.println("训练完成...");
}
/**
* 模型预测
* @param data
* @return
*/
public double predict(double[] data){
return sigmoid(forwardEachSample(data));
}
public double[] predict(double[][] data){
double[] predict = new double[data.length];
for(int i = 0; i < data.length; i++){
predict[i] = sigmoid(forwardEachSample(data[i]));
}
return predict;
}
/**
* 前向传播
*
* @return
*/
private double[] forward() {
for (int i = 0; i < sampleNum; i++) {
predict[i] = sigmoid(forwardEachSample(trainData[i]));
}
return predict;
}
/**
* 最大似然估计
* 批量梯度下降求取参数
*/
public void backward() {
for (int i = 0; i < parameters.length; i++) {
double f = 0.0;
for (int j = 0; j < sampleNum; j++) {
/**
* 推导公式 wj = wj + n * sigma(yi - zi) * xji;
*/
f += (label[j] - predict[j]) * trainData[j][i];
}
parameters[i] += sigma * f;
}
}
/**
* 向量相乘
* @param a
* @return
*/
public double forwardEachSample(double[] a) {
double sum = 0.0d;
for (int i = 0; i < a.length; i++) {
sum += a[i] * parameters[i];
}
return sum;
}
/**
* 激活函数
* @param a
* @return
*/
public double sigmoid(double a) {
return 1 / (1 + Math.exp(-a));
}
/**
* 计算每次迭代误差
* @return
*/
public double calcError() {
double error = 0.0d;
for (int i = 0; i < predict.length; i++) {
error += Math.abs(predict[i] - label[i]);
}
return error / predict.length;
}
public double calcPredictError(double[] label, double[] predict){
assert (label.length == predict.length);
double sumError = 0.0d;
int sumErrorCount = 0;
for(int i = 0; i < label.length; i++){
sumError += Math.abs(label[i] - predict[i]);
if((int)label[i] != (int)Math.round(predict[i])){
sumErrorCount ++;
}
}
System.out.println("总的误差为:" + sumError);
System.out.println("平均误测误差为:" + sumError / label.length);
System.out.println("预测正确数目:" + (label.length - sumErrorCount) + "/" + label.length);
System.out.println("预测正确率:" + (1 - sumErrorCount * 1.0 / label.length));
return sumError / label.length;
}
/**
* 测试程序
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new File("src/irisData/data.txt"));
String[] names = {"Iris-setosa","Iris-versicolor", "Iris-virginica"};
List list = new ArrayList<>();
while(sc.hasNextLine()){
list.add(sc.nextLine());
}
double[][] train_data = new double[list.size()][4];
double[] label = new double[list.size()];
int idx = 0;
for(String s : list){
String[] data = s.split(",");
for(int i = 0; i < 4; i++){
train_data[idx][i] = Double.parseDouble(data[i]);
}
if(data[4].equals(names[2])){
label[idx] = 1;
}else
label[idx] = 0;
idx++;
}
LogisticRegression lr = new LogisticRegression();
lr.train(train_data, label, 50000, true);
double[] predict = lr.predict(train_data);
lr.calcPredictError(label, predict);
}
}