/// <summary>
/// 寻找函数 y=x^2的最小值点
/// </summary>
static void SimpleGradientDescent()
{
//一元二次 函数 y=x^2 用梯度下降计算近似最小值
double step = 0.1; //步长 > 0
double x = 2; //取一个起始点
double dy = Math.Pow(x, 2); //y的变化值
double changed = dy; //y变化后的值,下一次循环运算的起点
while (dy > 0.00000001) //y的变化值∆y小于某个设定值,说明计算后的新y值不再变化 ,则下降完成
{
//若在x=a(a<0)处y=2x<0说明该点的切线斜率<0 函数递减,极小值点在x=a右侧,
//又(step * 2 * a)<0 则点 x =a - (step * 2 * a)>a 在 x = a的右侧 更接近极小值点
//同理 x=a(a>0)时 函数递增,(step * 2 * a)> 0,点 x= a-(step * 2 * a)<a 在x=a的左侧更接近极小值点
//综合以上,所以当 x 沿 f(x) 导数的 相反数 方向移动时,更接近函数的极小值点
//沿y=x^2的导函数 y=2x下降
x = x - step * 2 * x;
var y1 = Math.Pow(x, 2); //y1 = f(x1)= x1^2变化后的y值
dy = changed - y1; //本次运算的起点changed(初始y值),减去运算后的新y值(y1),为下降的变化值∆y
changed = y1; //changed变化后的y值作为下一次运算的起点
Console.WriteLine($"过程 x={x}");
}
Console.WriteLine($"结果 x={x}时,y=x^2取最小为{Math.Pow(x,2)}");
//结果为x = 10^-4, y = 10^-8
Console.ReadKey();
}
static void SimpleGradientDescent2()
{
//一元二次 函数 y=x^2 用梯度下降计算近似最小值
Console.WriteLine("**************另一种写法***************");
const int cycles = 10000; //足够大的循环次数
double rate = 0.001; //学习步长
double X = 2; //取一个起始点
Enumerable.Range(1, cycles).ToList().ForEach(m =>
{
//沿y=x^2的导函数 y=2x下降
X = X - rate * 2 * X;
Console.WriteLine($"过程 x={X}");
});
Console.WriteLine($"结果 x={X}时,y=x^2取最小为{Math.Pow(X, 2)}");
//结果为 x= 4*10^-9, y= 1.6*10^-17
Console.ReadKey();
}
static void GradientDescent()
{
//Rosenbrock函数f(x,y)=(1-x)^2+100(y-x^2)^2
//求解出它的梯度方向grad(f(x,y)) = ( -2*( 1 - x ) -400( y - x*x )*x , 200( y - x*x ) ) 即x轴方向的偏导,与y轴方向的偏导
//沿着该梯度的反方向就可以快速确定x,y位置的最小点即最小值 f(1,1)min = 0
var x = 3.0;
var y = 3.0;
const int cycles = 30000; //足够大的循环次数
const double rate = 0.0008; //学习步长
Enumerable.Range(1, cycles).ToList().ForEach(m =>
{
var x1 = x - rate*(-2*(1 - x) - 400*(y - x*x)*x);
var y1 = y - rate*200*(y - x*x);
x = x1;
y = y1;
Console.WriteLine($"过程 x={x}; y={y}");
});
Console.WriteLine($"结果 x={x}; y={y}时,f(x,y)=(1-x)^2+100(y-x^2)^2取最小为{(1 - x)*(1 - x) + 100*(y - x *x)*(y - x * x)}");
//x=1.00016, y= 1.00033 ,f(x,y)= 2.75 * 10^-8
Console.ReadKey();
}