【机器学习】逻辑回归的C++实现
代码框架与感知机一致,区别仅在于train和test函数上。
train
逻辑回归和感知机一样,也是来解决二分类问题。但输出的为当前标签为1的概率,所以和感知机只训练错误样例不同,我们需要对每个样本进行训练。
我们需要的输出范围为[0, 1],但是线性方程\(f(x) = wx + b\)的范围为负无穷到正无穷,所以我们要对输出进行变化(感知机的变换函数为符号函数)。于是我们使用sigmoid函数,sigmoid除了能够将输入调整到0到1的功能外,还具备很好的导数性质。sigmoid函数表示为:
\[f(z)=\frac{1}{1 + e^{-z}}
\]
则逻辑回归的整体数学形式为:
\[y = \frac{1}{1 + e^{-wx}}
\]
输出的值为样本为1的概率,为了方便后续计算,我们将其设为\(p\),即:
\[P(y=1 \mid x)=p=\frac{1}{1 + e^{-wx}}
\]
上式对\(w\)求导,可以得到\(p'=p(1-p)x\),其导数形式也非常简单,有利于后续的参数更新。
在当前\(w\)参数下,对于任意输入样本,逻辑回归的预测结果概率可以表示为:
\[P(y \mid x)=\left\{\begin{array}{r}
p, \quad y=1 \\
1-p, \quad y=0
\end{array}\right.
\]
综合起来可以写为:
\[P(y \mid x)=p^{y}(1-p)^{1-y}
\]
我们要求得一组\(w\)参数,使当前抽样样本发生的概率最大,也就是取极大似然估计,再加上负号就可以定义为逻辑回归的损失函数。所以我们对所有概率连乘,两边取log变成连加,再取负号,得到逻辑回归的损失函数:
\[F(x)=-\sum{ylogp + (1-y)log(1-p)}
\]
得到目标函数后,我们对\(w\)求偏导,得\(w\_delta[i] = -(y[i]-p[i])x[i]\)。我们使用随机梯度下降的方法,每次输入样本后都能更新\(w = w - h * w\_delta\)。
void train(int itera) {
int m = train_set.size();
int n = train_set[0].size();
w.assign(n + 1, 0); // w与b合并,维度+1
double h = 0.0001;
for (int i = 0; i < itera; i++) { // 开始迭代
for (int j = 0; j < m; j++) {
Vec xi = train_set[j]; // 当前训练数据,一维向量
double yi = train_label[j]; // 当前label
double exp_wx = exp(mul_vv(w, xi)); // 先计算好,避免重复运算
Vec w_delta = mul_vd(xi, h * (yi - exp_wx / (1 + exp_wx))); // 计算w更新值
w = add_vv(w, w_delta); // 更新w
}
}
}
test
测试时,只需要计算预测为1的概率p即可。p>=0.5则预测为1,否则预测为0。
double test() {
int m = test_set.size();
int n = test_set[0].size();
double err_cnt = 0;
for (int i = 0; i < m; i++) {
Vec xi = test_set[i];
double yi = test_label[i];
double exp_wx = exp(mul_vv(w, xi));
double predict = exp_wx / (1 + exp_wx);
double res = predict >= 0.5 ? 1 : 0;
if (res != yi) err_cnt++;
}
double acc_rate = 1 - (err_cnt / m);
return acc_rate;
}