深度学习的基础简单单元就是线性回归,线性回归的背景知识我们都有很多丽了解,最简单的就是一元线性回归,复杂的神经网络就是很多(相当大)的线性单元构成,包括卷积等。为了体现微分的数值计算深入理解深度学习是如何工作的,尽管强大的深度学习框架可以减少大量重复性工作,但若过于依赖它提供的便利,会导致我们很难理解工作原理。因此,这里介绍如何只利用C++
来实现一个一元线性回归的训练。
原始数据集
变量 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
1.1 | 1.3 | 1.5 | 2 | 2.2 | 2.9 | 3 | 3.2 | 3.2 | 3.7 | |
39343 | 46205 | 37731 | 43525 | 39891 | 56642 | 60150 | 54445 | 64445 | 57189 |
神经网络的构建
假设
一元线性回归模型 ,其中为参数,表示在条件下对应的学习值,也即为学习值。
代价函数
当前参数的对该模型的一个拟合评价,对于线性回归模型很显然它的差向量模值是体现参数好坏的一个标准。
梯度下降优化算法
梯度下降法来源于牛顿迭代算法,牛顿迭代算法就是找最低点通过梯度去计算,对已有的自变量进行微调,这里的是指牛顿下降法更新的幅度,也就是在机器学习中的学习率,他的高低影响了深度学习学习的效果和城西运行的时间。
C++源代码
gitee下载地址 Net.h模型头文件
#pragma once
#include <iostream>
#include <vector>
#include<random>
#include <iomanip>
#include<string>
using namespace std;
class Net
{
private:
vector<double> X;//预存特征数据
vector<double> Y;//预存标签数据
vector<double> Y_;//预测标签值
vector<double> L;//每项的损失
double loss;//总损失
double W[2];//权向量,模型中训练的参数值
double gW[2];//梯度下降法值,保存模型中训梯度
double lr;//学习率
int train_epochs;//学习代数
/**以下为C++表格和进度条绘制简单代码**/
bool process;
void drawLine(vector<int> max, int columns);
void drawLine(int max, int columns);
void drawDatas(vector<int> max, vector<vector<double>> Str, vector<string> D, int columns, int row);
void drawDatas(int max, vector<vector<double>> Str, vector<string> D, int columns, int row);
void show(int epochs);
/**以上为C++表格和进度条绘制简单代码**/
public:
Net(double x[], double y[], int num,double lr,int train_epochs);//构造函数
void model();//训练函数
void predict();//预测函数
void calLoss();//损失函数
void grad();//计算梯度函数
void output();//输出函数
/**以下为C++表格和进度条绘制简单代码**/
void setProcess(bool process);
/**以上为C++表格和进度条绘制简单代码**/
};
Net.cpp
#include "Net.h"
/**
x:输入特征
y:标签
num:数据集大小
lr:学习率
train_epochs:训练代数
**/
Net::Net(double x[], double y[], int num,double lr,int train_epochs)
{
/*进行数据填充操作**/
for (int i = 0; i < num; i++)
{
this->X.push_back(x[i]);
this->Y.push_back(y[i]);
}
this->lr = lr;
this->train_epochs = train_epochs;
this->process = false;
}
void Net::model()
{
default_random_engine dre(0);//设置随机种子
uniform_real_distribution<double> rand_real(-1.0, 1.0);
W[0] = rand_real(dre);//类似于python深度学习库中学习参数预先赋值
W[1] = rand_real(dre);
int epochs = 0;
while (epochs++< this->train_epochs)//迭代
{
this->predict();//计算预测
this->calLoss();//计算损失
show(epochs);
this->grad();//计算梯度
this->W[0] = this->W[0] - this->lr*this->gW[0];//计算梯度下降法
this->W[1] = this->W[1] - this->lr*this->gW[1];//计算梯度下降法
}
predict();//最终预测
calLoss();//最终顺式
cout << "****************" << endl;
}
void Net::predict()
{
Y_.clear();//对已有的预测清零
for (int i = 0; i < X.size(); i++)
{
Y_.push_back( W[0] + W[1] * X[i]);//预测
}
}
void Net::calLoss()
{
L.clear();//对已有的单损失清零
this->loss = 0;//对已有的总损失清零
for (int i = 0; i < X.size(); i++)
{
L.push_back((Y_[i]- Y[i])*(Y_[i] - Y[i]));//计算单损失
this->loss += L[i];
}
this->loss /= (2 * L.size());//计算总损失
}
void Net::grad()
{
double deltax = 0.00001;//数值梯度deltax趋于0
double deltay = 0;
vector<double> temp(X.size());//数值梯度每项损失
for (int i = 0; i < X.size(); i++)
{
temp[i] = W[0]+ deltax + W[1] * X[i];
deltay += (temp[i] - Y[i])*(temp[i] - Y[i]);
}
deltay /= (2 * X.size());//数值梯度总损失当数值梯度deltax趋于0
gW[0] = (deltay- this->loss )/ deltax;
//下面同理
deltay = 0;
for (int i = 0; i < X.size(); i++)
{
temp[i] = W[0] + (W[1] + deltax )* X[i];
deltay += (temp[i] - Y[i])*(temp[i] - Y[i]);
}
deltay /= (2 * X.size());
gW[1] = (deltay - this->loss) / deltax;
}
void Net::output()
{
vector<string> D;
D.push_back("W[0]");
D.push_back("W[1]");
D.push_back("loss");
vector<double> row;
row.push_back(this->W[0]);
row.push_back(this->W[1]);
row.push_back(this->loss);
vector<vector<double>> data;
data.push_back(row);
int max = 9;
this->drawDatas(max, data, D, D.size(), data.size());
///
D.clear();
D.push_back("X");
D.push_back("Y");
D.push_back("Y_");
D.push_back("L");
data.clear();
for (int i = 0; i < X.size(); i++)
{
row.clear();
row.push_back(X[i]);
row.push_back(Y[i]);
row.push_back(Y_[i]);
row.push_back(L[i]);// cout << X[i]<<" " << Y[i] << " " << Y_[i] << " " << L[i] << endl;
data.push_back(row);
}
this->drawDatas(max, data, D, D.size(), data.size());
}
/**以下为C++表格和进度条绘制简单代码**/
void Net::setProcess(bool process)
{
this->process = process;
}
void Net::drawLine(vector<int> max, int columns) { //画行线
for (int i = 0; i < columns; i++) {
cout << "+-";
for (int j = 0; j <= max[i]; j++) {
cout << '-';
}
}
cout << '+' << endl;
}
void Net::drawLine(int max, int columns) { //画行线
for (int i = 0; i < columns; i++) {
cout << "+-";
for (int j = 0; j <= max; j++) {
cout << '-';
}
}
cout << '+' << endl;
}
void Net::drawDatas(vector<int> max, vector<vector<double>> Str, vector<string> D, int columns, int row) {
drawLine(max, columns);
for (int i = 0; i < D.size(); i++) {
cout << "| " << setw(max[i]) << setiosflags(ios::left) << setfill(' ')<< D[i] << ' ';
}
cout << '|' << endl;
drawLine(max, columns);
for (int i = 0; i < row; i++) {
for (int j = 0; j < columns; j++) {
cout << "| " << setw(max[j]) << setiosflags(ios::left) << setfill(' ');
cout << Str[i][j] << ' ';
}
cout << '|' << endl;
}
drawLine(max, columns);
}
void Net::drawDatas(int max, vector<vector<double>> data, vector<string> header, int columns, int row) {
drawLine(max, columns);
for (int i = 0; i < header.size(); i++) {
cout << "| " << setw(max) << setiosflags(ios::left) << setfill(' ') << header[i] << ' ';
}
cout << '|' << endl;
drawLine(max, columns);
for (int i = 0; i < row; i++) {
for (int j = 0; j < columns; j++) {
cout << "| " << setw(max) << setiosflags(ios::left) << setfill(' ');
cout << data[i][j] << ' ';
}
cout << '|' << endl;
}
drawLine(max, columns);
}
void Net::show(int epochs)
{
if (process)
{
cout << "第" << epochs << "代:" << loss << endl;
}
else
{
cout.width(3);//i的输出为3位宽
cout << 100*epochs/ this->train_epochs << "%";
cout << "\b\b\b\b";//回删三个字符,使数字在原地变化
}
}
/**以上为C++表格和进度条绘制简单代码**/