一、普通的线性回归
线性回归主要采用最小二乘法来实现,主要思想如下:
X = ( x 11 x 12 ⋯ x 1 d 1 x 21 x 22 ⋯ 5 1 ⋮ ⋮ ⋱ ⋮ ⋮ x m 1 x m 2 ⋯ x m d 1 ) X=\left( \begin{matrix} x_{11} & x_{12} & \cdots & x_{1d} & 1 \\ x_{21} & x_{22} & \cdots & 5 & 1 \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ x_{m1} & x_{m2} & \cdots & x_{md} & 1 \\ \end{matrix} \right) X= x11x21⋮xm1x12x22⋮xm2⋯⋯⋱⋯x1d5⋮xmd11⋮1
X为一个m行d+1列的矩阵,其中m行为数据的数量,d+1列表示参数的数量。d表示未知数系数的个数,1用来与截距相乘,相当于b。
ω ^ ∗ = arg min ω ( y − X ω ^ ) T ( y − X ω ^ ) \hat{\omega}^*=\mathop{\arg\min}\limits_{\omega}(y-X\hat{\omega})^T(y-X\hat{\omega}) ω^∗=ωargmin(y−Xω^)T(y−Xω^)
损失函数如下:
E ω ^ = ( y − X ω ^ ) T ( y − X ω ^ ) E_{\hat\omega}=(y-X\hat{\omega})^T(y-X\hat{\omega}) Eω^=(y−Xω^)T(y−Xω^)
对于一个梯度可求且最大次方项为正的函数来讲,极小值点为导数为0的点。
∂ E ω ^ ∂ ω ^ = 2 X T ( X ω ^ − y ) = 0 \frac{\partial E_{\hat\omega}}{\partial\hat\omega}=2X^T(X\hat\omega-y)=0 ∂ω^∂Eω^=2XT(Xω^−y)=0
( X ω ^ − y ) = 0 (X\hat\omega-y)=0 (Xω^−y)=0
X ω ^ = y X\hat\omega=y Xω^=y
X T X ω ^ = X T y X^TX\hat\omega=X^Ty XTXω^=XTy
ω ^ ∗ = ( X T X ) − 1 X T y \hat\omega^*=(X^TX)^{-1}X^Ty ω^∗=(XTX)−1XTy
但是,矩阵求逆的前提是,当前矩阵是满秩的,也就是说它是非奇异矩阵,这就要求, X T X X^TX XTX 矩阵中的数据都是线性无关的,如果其中的数据存在线性相关性,那么是无法求逆的。
这对于一个高维特征的数据是很难做到的,因为维度之间很可能存在相关性,完全一致之后就会成为奇异矩阵(无法求逆)。
因此,岭回归就产生了。专门用来解决上述问题。
二、岭回归(Ridge Regression)
岭回归主要做法是,对上述公式的对角线上加了一个很小的值(岭系数),这样无论如何, X T X X^TX XTX 就都可以求导了。
ω ^ ∗ = ( X T X + λ I ) − 1 X T y (1) \hat\omega^*=(X^TX+\lambda I)^{-1}X^Ty\tag{1} ω^∗=(XTX+λI)−1XTy(1)
岭回归不是随便加的 λ \lambda λ,因为我们要防止曲线过拟合,就需要控制多元函数的各个系数都不要太大,如果太大,就会造成过拟合,因此在损失函数后加上一个正则项。
E ω ^ = ( y − X ω ^ ) T ( y − X ω ^ ) + λ ω T ω = ( y T y − y T X ω ^ − ω ^ T X T y + ω ^ T X T X ω ^ ) + λ ω T ω \begin{aligned} E_{\hat\omega}&=(y-X\hat{\omega})^T(y-X\hat{\omega})+\lambda \omega^T\omega \\ &=(y^Ty-y^TX\hat{\omega}-\hat\omega^T X^Ty+\hat\omega^T X^TX\hat\omega)+\lambda\omega^T\omega \end{aligned} Eω^=(y−Xω^)T(y−Xω^)+λωTω=(yTy−yTXω^−ω^TXTy+ω^TXTXω^)+λωTω
∂ E ω ^ ∂ ω ^ = − X T Y − X T Y + 2 X T X ω ^ + 2 λ ω ^ = 2 ( − X T Y + X T X ω ^ + λ ω ^ ) = 0 \begin{aligned} \frac{\partial E_{\hat\omega}}{\partial\hat\omega}&=-X^TY-X^TY+2X^TX\hat\omega+2\lambda\hat\omega\\ &=2(-X^TY+X^TX\hat\omega+\lambda\hat\omega)\\ &=0 \end{aligned} ∂ω^∂Eω^=−XTY−XTY+2XTXω^+2λω^=2(−XTY+XTXω^+λω^)=0
X T X ω ^ + λ ω ^ = X T Y X^TX\hat\omega+\lambda\hat\omega=X^TY XTXω^+λω^=XTY
( X T X + λ ) ω ^ = X T Y (X^TX+\lambda)\hat\omega=X^TY (XTX+λ)ω^=XTY
ω
^
=
(
X
T
X
+
λ
I
)
−
1
X
T
Y
(2)
\hat\omega=(X^TX+\lambda I)^{-1}X^TY\tag{2}
ω^=(XTX+λI)−1XTY(2)
将 (2) 式和 (1) 式进行对比,可以发现,二者是相同的。
三、代码实现
代码实现注释已经较为清晰,在此不再赘述具体实现过程。
package weka.classifiers.myf;
import weka.classifiers.Classifier;
import weka.core.*;
import weka.core.matrix.Matrix;
/**
* @author YFMan
* @Description 自定义的 线性回归 分类器
* @Date 2023/5/9 15:45
*/
public class myLinearRegression extends Classifier {
// 用于存储 线性回归 系数 的数组
private double[] m_Coefficients;
// 类别索引
private int m_ClassIndex;
// 存储训练数据
private Instances m_Instances;
/*
* @Author YFMan
* @Description 根据训练数据 建立 线性回归模型
* @Date 2023/5/9 22:08
* @Param [data] 训练数据
* @return void
**/
public void buildClassifier(Instances data) throws Exception {
// 存储训练数据
m_Instances = data;
// 初始化类别索引
m_ClassIndex = data.classIndex();
// 用来存储 线性回归 系数 的数组
m_Coefficients = null;
// 初始化数据矩阵 X
// 高度是样例数量,宽度是属性数量+1(1作为 截距参数b 的输入)
Matrix X = new Matrix(data.numInstances(), data.numAttributes());
// 初始化数据矩阵 Y
// 高度是样例数量,宽度是1
Matrix Y = new Matrix(data.numInstances(), 1);
// 初始化矩阵值
for (int i = 0; i < data.numInstances(); i++) {
int column = 0;
for (int j = 0; j < data.numAttributes(); j++) {
if (j != data.classIndex()) {
X.set(i, column, data.instance(i).value(j));
column++;
} else {
Y.set(i, 0, data.instance(i).value(j));
}
}
}
// 设置 X 的最后一列为 1,用于计算 截距参数b
for (int i = 0; i < data.numInstances(); i++) {
X.set(i, data.numAttributes() - 1, 1);
}
// 计算XTX
Matrix XTX = X.transpose().times(X);
// 计算XTY
Matrix XTY = X.transpose().times(Y);
// 由于XTX可能是奇异矩阵,所以需要加一个岭回归系数
for (int i = 0; i < XTX.getRowDimension(); i++) {
XTX.set(i, i, XTX.get(i, i) + 0.0001);
}
// 计算系数矩阵
Matrix solution = XTX.inverse().times(XTY);
// 将系数矩阵转换为数组
m_Coefficients = new double[solution.getRowDimension()];
for (int i = 0; i < solution.getRowDimension(); i++) {
m_Coefficients[i] = solution.get(i, 0);
}
}
/*
* @Author YFMan
* @Description 利用 建立的线性模型 对样例进行分类
* @Date 2023/5/9 22:10
* @Param [instance] 待分类的样例
* @return double
**/
public double classifyInstance(Instance instance) throws Exception {
// 计算回归模型的预测值
double result = 0;
int column = 0;
for (int i = 0; i < instance.numAttributes(); i++) {
if (m_ClassIndex != i) {
result += instance.value(i) * m_Coefficients[column];
column++;
}
}
// 加上截距参数
result += m_Coefficients[column];
// 返回预测值
return result;
}
/*
* @Author YFMan
* @Description 输出建立的线性模型
* @Date 2023/5/9 22:29
* @Param []
* @return java.lang.String
**/
public String toString() {
try {
StringBuilder text = new StringBuilder();
int column = 0;
boolean first = true;
text.append("\nLinear Regression Model\n\n");
text.append(m_Instances.classAttribute().name()).append(" =\n\n");
for (int i = 0; i < m_Instances.numAttributes(); i++) {
if (i != m_ClassIndex) {
if (!first)
text.append(" +\n");
else
first = false;
text.append(Utils.doubleToString(m_Coefficients[column], 12, 4)).append(" * ");
text.append(m_Instances.attribute(i).name());
column++;
}
}
text.append(" +\n").append(Utils.doubleToString(m_Coefficients[column], 12, 4));
return text.toString();
} catch (Exception e) {
return "Can't print Linear Regression!";
}
}
/*
* @Author YFMan
* @Description 主函数 生成一个线性回归函数预测器
* @Date 2023/5/9 22:35
* @Param [argv]
* @return void
**/
public static void main(String[] argv) {
runClassifier(new myLinearRegression(), argv);
}
}
四、结果分析
在weka平台中的cpu.arff数据集上进行实验。
我们自己写的模型结果:
weka中的线性回归模型结果:
可以看到,weka平台中的算法和我们自己实现的结果,训练得到的参数是一致的,不同点在于weka平台中的算法有对属性进行选择,并没有使用所有特征进行训练(weka使用了5个特征),而我自己实现的没有进行属性选择,所以一共有6个特征。