//应用
static void GradientDescent2()
{
//现有一组线性相关的数据,要求通过训练得到一个最接近它们线性关系的函数
//现假设它们的线性关系函数为 y = w * x + b,则有 cost(w,b) = |(w*x+b)-y| 的最小值接近0时越准确,这里x,y为给出的已知数,w,b为所求变量
//即求cost(w,b) = (w * x + b-y)^2的最小值
//求出cost(w,b) w b偏导,即梯度grad(cost(w,b)) =(2*(wx+b-y)*x , 2*(wx+b-y)*1)
//假设我们拿到的真实的数据dataXY, 这里用模拟数据代替
double[][] dataXY = new double[300][];
var r = new Random();
Enumerable.Range(0, dataXY.Length-1).ToList().ForEach(m =>
{
var x = r.NextDouble()*100;
var couple = new double[2];
couple[0] = x;
couple[1] = x * 3 + 3;
dataXY[m] = couple;
});
//y = weight * x + bias
double weight = 0, bias = 0;
double rate = 0.1;
int repeatTrain = 30;//重复训练次数
int trainTimes = 0; //实际训练次数
//训练一对数据
Action<double,double> trainOneData = (x,y) =>
{
//模仿上面的写法运行后发现结果错误,是因为xy为随机数值,梯度数值跨度太大,
//不符合让weight和bias运行一次后略微左移右移的处理逻辑
//var w = weight - rate * 2 *(weight*x + bias - y)*x;
//var b = bias - rate * 2*(weight*x + bias - y)*1;
//weight = w;
//bias = b;
//这里我们需要对真实的梯度进行单位处理,不让它们摇摆的过于夸张,还要保证梯度的方向维持正确
var realgradw = 2 * (weight * x + bias - y) * x;
var realgradb = 2 * (weight * x + bias - y) * 1;
var unit = Math.Max(Math.Abs(realgradw), Math.Abs(realgradb));
unit = Math.Abs(unit) < double.Epsilon?1 : unit;
var unitw = realgradw/unit;
var unitb = realgradb / unit;
//同时除以2个梯度中绝对值的最大的值,这样梯度的大小合适了,比例未变
var w = weight - rate * unitw;
var b = bias - rate * unitb;
weight = w;
bias = b;
//1、当rate=0.001经过运行后发现 w =3.007 b=0.12, w下降的速度很快,b的下降速度太慢未达到最优解
//因为 realgradw 中存在一个x^2因子,所以w下降的速度更快,需要想办法加快b的下降速度
//2、首先我们尝试加大步长rate=0.1,(w=3.004 b=2.932)(w=3.1,b=2.64)(w=3.1,b=1.64)数据不太稳定和精确
//3、尝试加大训练次数此时的结果较精确,但训练的时间过长效率太低
//4、尝试训练开始的时候选一个较大的步长,并且在训练的过程中逐步减小步长的值
trainTimes++;
if (trainTimes < 1000&& trainTimes > 0) rate = 0.5;
if (trainTimes < 2000 && trainTimes > 1000) rate = 0.5;
if (trainTimes < 3000 && trainTimes > 2000) rate = 0.3;
if (trainTimes < 5000 && trainTimes > 3000) rate = 0.1;
if (trainTimes < 7000 && trainTimes > 5000) rate = 0.05;
if ( trainTimes > 7000) rate = 0.001;
//w=3.0001,b=2.9989基本准确,但是当训练的数据较大时(var x = r.NextDouble()*1000;)结果还是不太理想
//还有优化的空间
};
//把所有的数据训练一次
Action<double[][]> trainAllData = data =>
{
for (int i = 0; i < data.Length-1; i++)
{
trainOneData(data[i][0], data[i][1]);
Console.WriteLine($"过程 weight={weight}; bias={bias}");
}
};
//把所有数据重复训练30次
for (int i = 0; i < repeatTrain; i++)
{
trainAllData(dataXY);
}
Console.WriteLine($"结果 weight={weight}; bias={bias};\r\n 通过学习得到的方程为 y = {weight}*x+{bias}");
Console.ReadKey();
}
//5、这里我们引入sigmoid函数g(x)=1/(1+exp(-x)),将其求导后函数化简g'(x) =g(x)*( 1-g(x) )
//上面我们已经cost函数2个方向的偏导函数,这里我们只需要将原函数的值其带入g'(x)后乘以其偏导即可
//这里我们令rate = 5,之后不用调节rate的值,通过sigmoid函数后函数值会自动收敛
rate = 5;
var y1 = weight*x + bias;
var dgx = 1/(1 + Math.Exp(-y1))*(1 - 1/(1 + Math.Exp(-y1)));
unitw = unitw * dgx;
unitb = unitb * dgx;
var w = weight - rate * unitw;
var b = bias - rate * unitb;
weight = w;
bias = b;