前言
最近在看深度学习的书籍,还好这本书有讲一点点基础Python,对于Python零经验的我算是一个小确幸。目前也只看了一半,这次笔记主要是纪录一些自己的疑问和自己的想法,若有地方有误解请各位纠正。
数值微分
梯度首先要先提到微分和偏微分,在此没有讲推导部分,只叙述微分和偏微分在这的用处,有兴趣可以前往维基百科观看。
数值微分
微分
什么是微分?还记得在大一时候我只会算还真的不知道它的用途,现在也只能领悟少许的皮毛。简单来说假如车子在12小时内跑了60公里,那他的速率就是5,而这速率("变化")就是微分,接下来先看公式和例子。
公式:
例如车子在12小时跑了60公里,,通常我们都直接微分或在上面公式就可以得到5,这部分就是他f(x)在x变化,但我们在写程式时候,h趋近于0这时候可能会导致我们除数 = 0,因此使用了两点分差延伸出三个公式。
公式:
前差
中央差
后差
三个都有误差有兴趣可以去看理论,但若不要求非常精準我们还是能拿来使用,当我们取中央差时候我们就可以防止可能除数是等于0的情况。
Python:
# 函数微分 中央分差def num_diff(f, x): h = 1e-4 # 0.0001 return (f(x + h) - f(x - h)) / (2 * h)
微分后我们得到的其实就是他的切线,可带入微分的切线方程式如下图f(x) = x^2 + x。
Python:
# 函数1def fun_1(x): return x ** 2 + x# 微分切线方程式: y = f(a) + d(x − a) = f(a) - d * a + d * x# 这部分回传一个function让我们可以得到函数再把x带入.def tangent_fun(f, a): d = num_diff(f, a) #print(d) y = f(a) - d * a return lambda x: d * x + yx = np.arange(0.0, 20, 00.1)y = fun_1(x)tan_fun = tangent_fun(fun_1, 5)y2 = tan_fun(x)plt.plot(x, y2, label = "tangent line")plt.plot(x, y, label = "line")plt.xlabel("x")plt.ylabel("f(x)")plt.legend()plt.show()
偏微分
我们知道微分是取得f(x)在x变化,那假如现在的函数是我们就必须使用偏微分而不是微分,我们要求在x的变化和在y的变化,如下图,可以看出偏微分就是只对一个未知数微分其余的当作常数,也就是其余未知数保持不变我们对单一未知数求他的变化。
梯度
我们把任一方程式未知数的偏微分求出来把它当作向量统计我们称为梯度,如下图,梯度他是有大小方向的,而往后这是要拿来更新参数将损失缩小,到目前这里就是今天要介绍的一个重点,应该有人跟我一样好奇梯度大小?梯度方向?,今天就要用f(x, y) = x^2 + y^2的梯度来说明给你听。
Python梯度图
首先我看的这本书他有介绍梯度图,若程式码部分忘记公式可以往上拉看微分公式,我们先来看他的梯度求法和图。
Python:
# 一次算x.y两个偏微分(梯度)def num_gradient(f, x): grad = np.zeros_like(x) h = 1e-4 # 0.0001 print(x.size) for idx in range(x.size): tmp_x = x[idx] x[idx] = tmp_x + h fxh1 = f(x) x[idx] = tmp_x - h fxh2 = f(x) grad[idx] = (fxh1 - fxh2) / (2 * h) x[idx] = tmp_x return grad# 计算所有x.y梯度def num_gradients(f, x): grad = np.zeros_like(x) for idex, X in enumerate(x): grad[idex] = num_gradient(f, X) return grad if __name__ == '__main__': x = np.arange(-2.0, 2.0, 1.0) y = np.arange(-2.0, 2.0, 3.0) #将x和y扩展当相同大小.再转一维 X, Y = np.meshgrid(x, y) X = X.flatten() Y = Y.flatten() #求梯度 grad = num_gradients(fun_1, np.array([X, Y])) print(X) print(Y) print(grad) #画出quiver方向图, xlim指定显示大小, label标籤显示, grid显示网格 plt.quiver(X, Y, grad[0], grad[1], angles="xy",color="#666666") plt.xlim([-4, 4]) plt.ylim([-4, 4]) plt.xlabel('x0') plt.ylabel('x1') plt.grid() plt.draw() plt.show()
以上数据:
x = [-2. -1. 0. 1. -2. -1. 0. 1.]
y = [-2. -2. -2. -2. 1. 1. 1. 1.]
x偏 = [-4. -2. 0. 2. -4. -2. 0. 2.]
y偏 = [-4. -4. -4. -4. 2. 2. 2. 2.]
我想有人会跟我一样有疑问我们知道偏微分.梯度那图示怎画出来的? 以下为你解释。
C#梯度图
还记得小学补习班的Boss教数学时跟我说过"大胆假设,小心求证"不要怕算错,虽然这句话由来不是他的,但对我而言告诉我这句话就是补习班Boss交给我的,真是难忘的回忆。首先我们可以知道偏微分出来的x和y的搭配就很像是第一象限到第四象限的组合,这时候我们就可以得到箭头要往哪个象限延伸,然而角度我们可以用atan来取得因为我们有x和y,而长度则是计算我们目前全部座标的两个向量最大总和来当分母,分子则是当下两个向量目前最大值,例如,|a| = (-3, 4), |b| = (1, 2), |c| = (2, 4), 这时候我们可以得知|a|向量是目前最大的abs(-3) + abs(4) = 7,对于全部向量来说7就是分母,假如,当我们要画出b向量时候我们就要画2 / 7因为|b|向量的第二个向量比第一个大所以我们拿来当分子,所以当我们要画|c|长度是4 / 7,以下让我们用C#将我们梯度可视化。
座标Class
class PointF2D{ private float _x; private float _y; public PointF2D(float x, float y) { _x = x; _y = y; } public PointF2D() { _x = 0; _y = 0; } public float X { get { return _x; } set { _x = value; } } public float Y { get { return _y; } set { _y = value; } }}
使用函数
interface Function{ float Formula(float x, float y);}class Function1 : Function{ public float Formula(float x, float y) { return x * x + y * y; }}
微分函数
可以使用多载参数带PointF2D。
PointF2D Fun(Function fun, float x, float y){ PointF2D grad = new PointF2D(); float fun1 = 0.0f; float fun2 = 0.0f; float h = 1e-4f; fun1 = fun.Formula((x + h), y); fun2 = fun.Formula((x - h), y); grad.X = (fun1 - fun2) / (h * 2); fun1 = fun.Formula(x, (y + h)); fun2 = fun.Formula(x, (y - h)); grad.Y = (fun1 - fun2) / (h * 2); return grad;}
绘图Class
可以使用多载参数带PointF2D。
drawGradient函数:
这是里面最主要的函数,用来计算长度的,目前没有优化。
1.因我们得知x向量y向量所以可以使用atan取得弧度(得到我们要画的角度)。
2.之后我们可以先计算Cos和Sin用来取的边长资讯。
3.取的x.y的方向(Direction)也就是要画哪个象限。
4.maxLen计算要画多长。
5.index是目前我们要画的长度因此斜边(bevel) = index / Cos。(Cos = 邻/斜 推算)。
6.x乘上方向.y = Sin * 斜边(Sin = 对/斜 推算)在乘上方向。
7.直到画到我们目前最大长度。
class DrawF{ PointF2D _center; float _offset; public DrawF(float offset) { _center = new PointF2D(5.0f * offset, 5.0f * offset); _offset = offset; } public DrawF(float offset, float x, float y) { _center = new PointF2D(x * offset, y * offset); _offset = offset; } public PointF2D getBlockPoint(float x, float y) { PointF2D point = new PointF2D(); point.X = (_center.X / _offset + x) * _offset; point.Y = (_center.Y / _offset - y) * _offset; return point; } public PointF2D Center { get { return _center; } } public void drawBlock(Graphics graphics , float x, float y) { Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0), 1); float xLen = x * _offset; float yLen = y * _offset; // 水平的分割出垂直数量 for (int row = 0; row <= x; row++) { graphics.DrawLine(pen, 0, row * _offset, yLen, row * _offset); } // 垂直的分割出水平数量 for (int col = 0; col <= y; col++) { graphics.DrawLine(pen, col * _offset, 0, col * _offset, xLen); } } public void drawLine(Graphics graphics , float xS, float yS , float xE, float yE) { Pen pen = new Pen(Color.FromArgb(255, 178, 34, 34), 5); graphics.DrawLine(pen, xS, yS, xE, yE); } public void drawPoint(Graphics graphics, Brush color , float x, float y) { graphics.FillRectangle(color, x - 5.0f, y - 5.0f, 10.0f, 10.0f); } public void drawPointLine(Graphics graphics, Brush color , float x, float y) { graphics.FillRectangle(color, x, y, 2.0f, 2.0f); } // drawGradient画一个梯度 // point: 座标 // U: x向量 // V: y向量 // maxSum: 最大向量和 public void drawGradient(Graphics graphics , PointF2D point , float U, float V, float maxSum) { // 邻边 = U = x // 对边 = V = y float absU = Math.Abs(U); float absV = Math.Abs(V); // atan取弧度(tan = 对边 / 邻边 float radian = absU != 0 ? (float)Math.Atan(absV / absU) : (float)(Math.PI * 0.5); Console.WriteLine(radian / Math.PI * 180); // cos = 邻边 / 斜边, sin = 对边 / 斜边 float xCos = U != 0.0f ? (float)Math.Cos(radian) : 1.0f; float ySin = V != 0.0f ? (float)Math.Sin(radian) : 0.0f; float xDirection = U < 0.0f ? -1.0f : U > 0 ? 1.0f : 0.0f; float yDirection = V < 0.0f ? 1.0f : V > 0 ? -1.0f : 0.0f; // 计算显示长度比例 float max = absU > absV ? absU : absV; float maxLen = (max / maxSum * _offset); for (float index = 0; index < maxLen; index += 0.01f) { // 取得斜边 // 取得x + 方向和y + 方向(对边) float bevel = index / xCos; float x = index * xDirection; float y = ySin * bevel * yDirection; if (Math.Abs(y) > maxLen) { return; } drawPointLine(graphics, Brushes.Black, point.X + x, point.Y + y); } } // drawGradient画一个梯度(多载) // point: 座标 // U: x向量 // V: y向量 public void drawGradient(Graphics graphics , PointF2D point , float U, float V) { float sum = Math.Abs(V) + Math.Abs(U); drawGradient(graphics, point, U, V, sum); } // drawGradients画出所有梯度 // xyPoints: 座标阵列 // uvPoints: 向量阵列 public void drawGradients(Graphics graphics , ArrayList xyPoints , ArrayList uvPoints) { float maxUV = getMaxUV(uvPoints); for (int index = 0; index < xyPoints.Count; index++) { PointF2D xyPoint = (PointF2D)xyPoints[index]; PointF2D uvPoint = (PointF2D)uvPoints[index]; drawGradient(graphics, xyPoint, uvPoint.X, uvPoint.Y, maxUV); } } // getMaxUV取得目前最大向量 // uvPoints: 向量阵列 private float getMaxUV(ArrayList uvPoints) { float maxUV = 0.0f; for (int index = 0; index < uvPoints.Count; index++) { PointF2D uvPoint = (PointF2D)uvPoints[index]; float sum = Math.Abs(uvPoint.X) + Math.Abs(uvPoint.Y); if (maxUV < sum) { maxUV = sum; } } return maxUV; }}
Picture画出
private void pictureBox1_Paint(object sender, PaintEventArgs e){ DrawF draw = new DrawF(50.0f, 5.0f, 5.0f); draw.drawBlock(e.Graphics, 10.0f, 10.0f); draw.drawPoint(e.Graphics, Brushes.Red, draw.Center); ArrayList xyPoints = new ArrayList(); ArrayList uvPoints = new ArrayList(); float[] x = { -2, -1, 0, 1, -2, -1, 0, 1 }; float[] y = { -2, -2, -2, -2, 1, 1, 1, 1 }; Function1 fun = new Function1(); for (int index = 0; index < 8; index++) { PointF2D xyPoint = draw.getBlockPoint(x[index], y[index]); PointF2D uvPoint = Fun(fun, x[index], y[index]); Console.Write(uvPoint.X + " " + uvPoint.Y); xyPoints.Add(xyPoint); uvPoints.Add(uvPoint); draw.drawPoint(e.Graphics, Brushes.Blue, xyPoint); } draw.drawGradients(e.Graphics, xyPoints, uvPoints);}
结果
放大50倍绘製10x10方格(5, 5)红色点为中心。
结语
上面函数很多没优化,但或许这样会比较好,优化虽然会增加效率但也会减少可读性,接下来希望有时间能把深度学习看完,今天已经混一天但还是把文章写出来了,祝大家假日愉快。