基于Logistic回归和Sigmoid函数的分类,首先看下它的优缺点。
优点:计算代价不高,易于理解与实现
缺点:容易欠拟合,分类精度可能不高
使用数据类型:数值型和标称型数据
首先,我们想要的函数应该是能够接受所有的输入然后预测出类别。例如输出0或者1.或许你曾今接触过这种性质的函数,该函数称为海维塞德阶跃函数,也可以直接称为单位阶跃函数。然而海维塞德阶跃函数的问题在于:该函数在跳跃点上从0瞬间跳跃到1,这个瞬间跳跃过程很难处理。幸好,另一个函数也有类似的性质,且数学上更易处理,这就是Sigmoid函数。具体公式如下:
当x为0时,Sigmoid函数值为0.5。随着x的增大,对应的Sigmoid值将逼近于1;而随着x的减小,Sigmoid的值将逼近于0。如果横坐标的刻度足够大,Sigmoid函数看起来就像一个阶跃函数。
因此,为了实现逻辑回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有的结果值相加,将这个总和带入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被归入1类,小于0.5的即被归入0类。所以,逻辑回归也可以被看成是一种概率估计。确定函数形式之后,问题变成:最佳回归系数是多少?
Sigmoid函数的输入记为z,得出 z = w0*x0 + w1*x1 + w2*x2 + ...... + wn*xn。如果采用向量的写法,可以写成z = w^T*x,它表示将这两个数值向量对应元素相乘然后相加。其中向量x是分类器的输入数据,向量w也就是最佳系数。
梯度上升法
梯度上升法基于的思想是:要找到某个函数的最大值,最好的办法就是沿着该函数的梯度方向探寻。梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记作a。用向量表示的话,梯度上升算法的迭代公式如下:w := w + a * f(w)
该公式将一直被迭代执行,直到达到某个停止条件为止,比如迭代次数达到某个指定值或是算法达到某个可以允许的误差范围。
梯度下降算法与梯度上升算法是一样的,只是公式中的加法变成减法。对应的公式可以写成:w := w - a * f(w)
梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。
伪代码如下:
每个回归系数初始化为1
重复R次:
计算每个数据集的梯度
使用alpha * gradient更新回归系数的向量
返回回归系数
随机梯度上升法
梯度上升算法在每次更新回归系数时候都需要遍历整个数据集,该方法在处理100个左右的数据集时候还尚可,但是如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以在新样本到来时候对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。
伪代码如下:
每个回归系数初始化为1
对数据集中打的每个样本
计算该样本的梯度
使用alpha * gradient更新回归系数值
返回回归系数值
在随机梯度上升算法中会存在一些小的周期性波动 。不难理解,产生这种现象的原因是存在一些不能正确分类的样本点,数据集并非线性可分,我们期望算法避免来回波动,从而收敛到某一个值,同时还期望收敛速度能够加快,为此改进了随机梯度上升算法。改进的方面如下:
1、alpha在每次迭代的时候都会调整,缓解数据波动。
2、随机选取样本来更新回归系数,减少周期性波动。
3、随机梯度上升算法可以进行多次迭代。
下面是用Java语言简单实现的梯度上升算法与随机梯度上升算法:
public abstract class AbstractBuilder {
//Sigmoid函数计算
protected double sigmoid(double value) {
return 1d / (1d + Math.exp(-value));
}
//Sigmoid函数计算
protected double sigmoid(double[] data, double[] weights) {
double z = 0.0;
for (int i = 0, len = data.length; i < len; i++) {
z += data[i] * weights[i];
}
return sigmoid(z);
}
//分类预测数据
protected int classify(double[] data, double[] weights) {
double predict = sigmoid(data, weights);
return predict > 0.5 ? 1 : 0;
}
protected void show(double[] values) {
for(double value : values) {
System.out.println("value: " + value);
}
}
//初始化数据集
protected DataSet initDataSet() {
DataSet dataSet = null;
try {
String path = AbstractBuilder.class.getClassLoader().getResource("trainset/regression.txt").toURI().getPath();
dataSet = DataSetHandler.load(path);
} catch (URISyntaxException e) {
e.printStackTrace();
}
return dataSet;
}
//初始化数据集
protected DataSet initDataSet(String path) {
return DataSetHandler.load(path);
}
//计算回归系数
public abstract double[] calculateWeights(DataSet dataSet);
public DataResult run() {
return run(null);
}
public DataResult run(String path) {
DataSet dataSet = null == path ? initDataSet() : initDataSet(path);
double[] weights = calculateWeights(dataSet);
List<Integer> categories = dataSet.getCategories();
int index = 0;
DataResult dataResult = new DataResult();
for (double[] data : dataSet.getDatas()) {
dataResult.getDatas().add(data);
dataResult.getActualValues().add(categories.get(index++));
dataResult.getPredictedValues().add(classify(data, weights));
}
dataResult.statistics();
return dataResult;
}
}
//梯度上升算法
public class GradientAscentBuilder extends AbstractBuilder {
private int ITER_NUM = 100;
public double[] calculateWeights(DataSet dataSet) {
double[][] datas = dataSet.getDatas();
List<Integer> categories = dataSet.getCategories();
double[] errors = new double[datas.length];
double[] weights = new double[]{1.0, 1.0};
double alpha = 0.001;
for (int i = 0; i < ITER_NUM; i++) {
for (int j = 0, len1 = datas.length; j < len1; j++) {
double h = sigmoid(datas[j], weights);
errors[j] = categories.get(j) - h;
}
for (int k = 0, len2 = datas[0].length; k < len2; k++) {
for (int j = 0, len1 = datas.length; j < len1; j++) {
weights[k] += alpha * errors[j] * datas[j][k];
}
}
show(weights);
}
return weights;
}
public static void main(String[] args) {
new GradientAscentBuilder().run();
}
}
//随机梯度上升算法
public class RandomGradientAscentBuilder extends AbstractBuilder {
public double[] calculateWeights(DataSet dataSet) {
double[][] datas = dataSet.getDatas();
List<Integer> categories = dataSet.getCategories();
double[] weights = new double[]{1.0, 1.0};
double alpha = 0.01;
for (int i = 0, len = datas.length; i < len; i++) {
double h = sigmoid(datas[i], weights);
double error = categories.get(i) - h;
for (int j = 0, len1 = weights.length; j < len1; j++) {
weights[j] += alpha * error * datas[i][j];
}
}
show(weights);
return weights;
}
public static void main(String[] args) {
new RandomGradientAscentBuilder().run();
}
}
代码托管:https://github.com/fighting-one-piece/repository-datamining.git