C++实现
“linear_regression.h”
//二分类逻辑回归模型
struct elem_log
{
double y;
double* x; //用数组传入自变量数据(x[0]=1,便于之后的计算)
};
class logistic_reg
{//与线性回归的不同点在于hx不同
public:
logistic_reg(int xnum, elem_log* p, int size, double rate); //初始化(训练集的x0全部固定为1)
~logistic_reg(); //析构
void scaling(); //特征缩放器(将所有特征值统一缩放至1--10)
void scalback(); //特征还原(参数缩放)
double hx(int i); //sigmoid函数:计算第i组训练集数据对应的hx函数值(0--setsize-1)
double cost_fuction(); //返回当前预测方程对应代价函数的值
void update(); //同时更新方程参数
void find(); //最小化代价函数,找到收敛点时对应的方程参数
double* get_par(int &par_num); //获得当前方程的参数
double est_val(double* x); //使用拟合后的回归方程进行预测
private:
int x_num; //自变量个数
elem_log* tran_set; //训练集(读入时将x【0】赋值为1)
int setsize; //训练集数据量
double* par; //参数数组(大小为实际x_num+1):h(x)=par0*x0(赋值为1)+par1*x1+par2+x^2+...+par(x_num)*x^x_num
double learn_rate; //学习速率
double* x_scal; //特征缩放率
};
“linear_regression.cpp”
//二分类逻辑回归
logistic_reg::logistic_reg(int xnum, elem_log* p, int size, double rate)
{//初始化(训练集的x0全部固定为1)
//参数列表:自变量数目,训练集地址,训练集容量,学习速率
x_num = xnum + 1; //设置自变量数目
setsize = size; //获取训练集大小
tran_set = p; //指针指向训练集数组
learn_rate = rate; //设置学习速率
par = new double[xnum + 1]; //系数初始化为0
memset(par, 0, sizeof(double)*(xnum + 1));
x_scal = new double[xnum + 1]; //特征缩放率初始化为1
for (int i = 0;i < x_num;i++)
x_scal[i] = 1;
}
logistic_reg::~logistic_reg()
{//析构
tran_set = NULL;
setsize = 0;
delete[]par;
par = NULL;
delete[]x_scal;
x_scal = NULL;
}
void logistic_reg::scaling()
{//特征缩放器(将所有特征值统一缩放至1--10)
for (int j = 0;j < x_num;j++)
{//以第一组数据确定缩放率
while (tran_set[0].x[j] > 10 || tran_set[0].x[j] < 1)
{
if (tran_set[0].x[j] > 10)
{
tran_set[0].x[j] /= 10.0;
x_scal[j] *= 10.0;
}
else
{
tran_set[0].x[j] *= 10.0;
x_scal[j] /= 10.0;
}
}
}
for (int i = 1;i < setsize;i++)
{//对剩余数据进行缩放
for (int j = 0;j < x_num;j++)
{
tran_set[i].x[j] /= x_scal[j];
}
}
}
void logistic_reg::scalback()
{//特征还原(参数缩放)
for (int i = 0;i < x_num;i++)
par[i] /= x_scal[i];
}
double logistic_reg::hx(int i)
{//sigmoid函数:计算第i组训练集数据对应的hx函数值(0--setsize - 1)
double sum=0;
for (int j = 0;j < x_num;j++)
sum += par[j] * tran_set[i].x[j];
double hx = 1 / (1 + exp(0 - sum)); //double exp(double x)----求e的x次幂
return hx;
}
double logistic_reg::cost_fuction()
{//返回当前预测方程对应代价函数的值
double cost = 0;
for (int i = 0;i < setsize;i++)
{
cost += (tran_set[i].y*log(hx(i)) + (1 - tran_set[i].y)*log(1 - hx(i)));
}
return -1.0 / (double)setsize*cost;
}
void logistic_reg::update()
{//同时更新方程参数
double* sum = new double[x_num];
for (int j = 0;j < x_num;j++)
{
sum[j] = 0;
for (int i = 0;i < setsize;i++)
{
sum[j] += (hx(i) - tran_set[i].y)*tran_set[i].x[j];
}
}
for (int i = 0;i < x_num;i++)
par[i] -= learn_rate * sum[i] / (double)setsize;
delete[]sum;
}
void logistic_reg::find()
{//最小化代价函数,找到收敛点时对应的方程参数
scaling(); //数据放缩
double cost_pre, cost_last;
cost_pre = cost_fuction();
update(); //更新参数
cost_last = cost_fuction();
while (cost_pre != cost_last)
{//寻找收敛点
/*cout << cost_pre << " " << cost_last << endl; //用来选择学习率*/
cost_pre = cost_last;
update();
cost_last = cost_fuction();
}
//获得假设函数最优拟合时的参数
scalback();
//特征还原,参数缩放
}
double* logistic_reg::get_par(int &par_num)
{//获得当前方程的参数
par_num = x_num;
return par;
}
double logistic_reg::est_val(double* x)
{//使用拟合后的回归方程进行预测
double sum = 0;
for (int j = 0;j < x_num;j++)
sum += par[j] * x[j];
double hx = 1 / (1 + exp(0 - sum)); //double exp(double x)----求e的x次幂
return hx;
}
主函数部分:
//二分类逻辑回归测试
int main()
{
int size, xnum;
cout << "请输入训练集容量:";
cin >> size;
cout << "请输入变量个数: ";
cin >> xnum;
elem_log transet[200];
for (int i = 0;i < size;i++)
{
transet[i].y = 0;
transet[i].x = new double[xnum + 1];
memset(transet[i].x, 0, sizeof(double)*(xnum + 1));
}
cout << "请输入训练集数据:" << endl;
for (int i = 0;i < size;i++)
{
transet[i].x[0] = 1;
for (int j = 1;j <= xnum;j++)
cin >> transet[i].x[j];
cin >> transet[i].y;
}
logistic_reg obj(xnum, transet, size, 0.4);
obj.find();
double*par = NULL;
int parnum;
par = obj.get_par(parnum);
cout << "h(x)=" << par[0];
for (int i = 1;i < parnum;i++)
{
if (par[i] > 0)
cout << '+' << par[i] << "*x" << i;
else
{
if (par[i] < 0)
cout << par[i] << "*x" << i;
}
}
cout << endl;
double*x = new double[xnum + 1];
memset(x, 0, sizeof(double)*(xnum + 1));
string str;
double ans;
while (cin >> str)
{//预测部分
if (str == "end")
break;
x[0] = 1;
for (int i = 1;i <= xnum;i++)
cin >> x[i];
ans = obj.est_val(x);
cout << ans << endl;
}
delete[]x;
for (int i = 0;i < size;i++)
{
delete[]transet[i].x;
}
return 0;
}
运行结果
用octave可视化:
>> A=load('ex2data1.txt');
>> x1=A(:,[1]);
>> x2=A(:,[2]);
>> y=A(:,[3]);
>> pos = find(y==1); neg = find(y == 0);
>> X=[x1,x2];
>> plot(X(pos, 1), X(pos, 2), 'k+','LineWidth', 2,'MarkerSize', 7);
>> hold on;
>> plot(X(neg, 1), X(neg, 2), 'ko', 'MarkerFaceColor', 'y','MarkerSize', 7);
>> hold on;
>> x2=(-0.206231*x1+25.1612)/0.201471;
>> plot(x1,x2,'r');
>> xlabel('Exam 1 score');
>> ylabel('Exam 2 score');
>> title('logistic regression');
>> legend('Admitted','Not admitted');
>> print -dpng 'linear_logistic.png';
按照自己做出的模型得出的决策边界进行分类如上图。