介绍:高效的操作多维数组的函数库。
安装:(前提已经安装了python)
pip install numpy
导入
import numpy as np
创建数组
Numpy最重要的数据结构是多维数组(ndarray)。通过Numpy,你可以轻松创建数组:
# 从Python列表创建一维数组
arr1d = np.array([1, 2, 3, 4, 5])
>[1, 2, 3, 4, 5]
# 从Python嵌套列表创建二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
# 创建全零数组
zeros = np.zeros((3, 4))
>[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]
# 创建全一数组
ones = np.ones((2, 3))
>[[1., 1., 1.],
[1., 1., 1.]]
# 创建指定范围内的数组
range_arr = np.arange(0, 10, 2)
>[0, 2, 4, 6, 8]
# 创建线性间隔的数组
linspace_arr = np.linspace(0, 1, 5)
>[0. , 0.25, 0.5 , 0.75, 1. ]
数组属性
Numpy数组有许多属性,你可以通过它们来了解数组的维度、形状和元素类型:
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr.shape) # 获取数组的形状 n行m列 输出:(3, 3)
print(arr.ndim) # 获取数组的维度 输出:2
print(arr.size) # 获取数组的长度 输出:9
print(arr.dtype) # 获取数组的元素类型 输出:int64
数组操作
Numpy提供了许多数组操作函数,使得数组的操作和计算变得简单高效:
# 数组加法
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
result = arr1 + arr2
>[5, 7, 9]
# 数组乘法
arr = np.array([1, 2, 3])
result = arr * 2
>[2, 4, 6]
# 二维数组乘法
x = np.array([[1, 2], [3, 4]])
y = np.array([[2, 1], [3, 4]])
>[[1, 2], [[2, 1],
[3, 4]] [4, 3]]
>[[2, 2],
[12, 12]]
# 矩阵乘法 点乘运算
mat1 = np.array([[1, 2], [3, 4]])
mat2 = np.array([[5, 6], [7, 8]])
result = np.dot(mat1, mat2)
>[[1, 2], [[5, 6],
[3, 4]] [7, 8]]
>输出: [[19, 22],
[43, 50]]
# 数组索引和切片 与python内置数组操作一致
arr = np.array([1, 2, 3, 4, 5])
print(arr[0]) # 输出:1
print(arr[1:4]) # 输出:[2, 3, 4]
# 数组形状变换
arr = np.array([1, 2, 3, 4, 5, 6])
reshaped_arr = arr.reshape(2, 3)
>[[1, 2, 3],
[4, 5, 6]]
常用数学函数
Numpy提供了许多常用的数学函数,可以直接应用于数组:
arr = np.array([1, 2, 3, 4, 5])
print(np.sum(arr)) # 输出:15
print(np.mean(arr)) # 输出:3.0
print(np.max(arr)) # 输出:5
print(np.min(arr)) # 输出:1
print(np.sin(arr)) # 输出:[0.84147098 0.90929743 0.14112001 -0.7568025 -0.95892427]
print(np.cos(arr)) # 输出:[0.54030231 -0.41614684 -0.9899925 -0.65364362 0.28366219 0.96017029]
print(np.power(arr, 2)) # 输出:[1, 4, 9, 16, 25]
print(np.exp(arr)) # 输出:[2.71828183, 7.3890561, 20.08553692, 54.59815003, 148.4131591 ]
广播是numpy中一种强大的机制,允许对不同形状的数组进行运算,而不需要显式地进行形状匹配或复制数据。
Matplotlib是Python中最流行的数据可视化库之一,可以用来绘制图表内容。
安装Matplotlib
在开始之前,确保你已经安装了Python和Matplotlib。如果还没有安装Matplotlib,可以通过以下命令使用pip进行安装:
pip install matplotlib
导入Matplotlib
在使用Matplotlib之前,首先需要导入它。习惯上,我们使用以下方式导入Matplotlib并简写为plt
:
import matplotlib.pyplot as plt
折线图是Matplotlib中最简单的图表类型之一,它用于显示数据随着变量的变化而变化的趋势。下面是一个简单的绘制折线图的例子:
# 示例数据
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]
# 绘制折线图
plt.plot(x, y)
# 添加标题和标签
plt.title('简单折线图')
plt.xlabel('X轴')
plt.ylabel('Y轴')
# 显示图形
plt.show()
图形绘制如下
散点图常用于显示两个变量之间的关系。下面是一个绘制散点图的例子:
# 示例数据
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]
# 绘制散点图
plt.scatter(x, y)
# 添加标题和标签
plt.title('简单散点图')
plt.xlabel('X轴')
plt.ylabel('Y轴')
# 显示图形
plt.show()
柱状图常用于比较不同类别的数据。下面是一个绘制柱状图的例子:
# 示例数据
categories = ['A', 'B', 'C', 'D', 'E']
values = [10, 25, 15, 30, 20]
# 绘制柱状图
plt.bar(categories, values)
# 添加标题和标签
plt.title('简单柱状图')
plt.xlabel('类别')
plt.ylabel('值')
# 显示图形
plt.show()
饼图常用于显示不同类别占总量的比例。下面是一个绘制饼图的例子:
# 示例数据
categories = ['A', 'B', 'C', 'D', 'E']
values = [10, 25, 15, 30, 20]
# 绘制饼图
plt.pie(values, labels=categories, autopct='%1.1f%%')
# 添加标题
plt.title('简单饼图')
# 显示图形
plt.show()
Matplotlib允许我们自定义图形的样式,包括线条颜色、标记类型、图例等。例如:
x = np.arange(0,6, 0.1)
# 绘制sin图像
y1 = np.sin(x)
# 绘制cos图像
y2 = np.cos(x)
plt.plot(x, y1, label="sin", color='blue')
# 设置图线样式
plt.plot(x, y2, linestyle="--", color='red', label="cos")
plt.xlabel("x")
plt.ylabel("y")
plt.title("sin & cos")
plt.legend()
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9jJZ8AKc-1691499932714)(鱼书笔记.assets/image-20230728194801266.png)]
plt.plot() # 绘制折线图。
plt.scatter() # 绘制散点图。
plt.bar() # 绘制柱状图。
plt.barh() # 绘制水平柱状图。
plt.hist() # 绘制直方图。
plt.pie() # 绘制饼图。
plt.boxplot() # 绘制箱线图。
plt.errorbar() # 绘制误差条形图。
plt.contour() # 绘制等高线图。
plt.imshow() # 绘制图像。
plt.polar() # 绘制极坐标图。
plt.stem() # 绘制离散序列的线型图。
plt.fill() 和 plt.fill_between() # 绘制填充图。
plt.stackplot() # 绘制堆叠区域图。
plt.barbs() # 绘制风羽图。
plt.quiver() # 绘制场矢量图。
plt.streamplot() # 绘制流线图。
plt.hexbin() # 绘制六边形二维直方图。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yayXtuvz-1691499932714)(鱼书笔记.assets/image-20230726224334363.png)]
x1,x2是输入,y是输出,w1,w2是权值,x*w之和超过阀值θ时才会激活y
y = { 0 ( ω 1 x 1 + ω 2 x 2 ≤ θ ) 1 ( ω 1 x 1 + ω 2 x 2 > θ ) y = \begin{cases} 0 \,\,( \omega 1x1 + \omega2x2 \le \theta )\\ 1 \,\,( \omega 1x1 + \omega2x2 > \theta )\\ \end{cases} y={0(ω1x1+ω2x2≤θ)1(ω1x1+ω2x2>θ)
可将 θ \theta θ 变为-b
移到不等式左边,变换为如下表达式。其中 ω 1 \omega1 ω1 和 ω 2 \omega2 ω2 表示权重(用于控制各个信号的重要性),b表示偏置(用于控制神经元被激活的容易程度)。
y = { 0 ( b + ω 1 x 1 + ω 2 x 2 ≤ 0 ) 1 ( b + ω 1 x 1 + ω 2 x 2 > 0 ) y = \begin{cases} 0 \, \,( b+ \omega 1x1 + \omega2x2 \le 0 )\\ 1 \, \,( b+ \omega 1x1 + \omega2x2 > 0 )\\ \end{cases} y={0(b+ω1x1+ω2x2≤0)1(b+ω1x1+ω2x2>0)
与门 AND 代码实现
def AND(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
或门 OR 代码实现
def OR(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.2
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
与非门 NAND 代码实现
def NAND(x1, x2):
x = np.array([x1, x2])
w = np.array([-0.5, -0.5])
b = 0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
单层的感知机,只能划分线性空间,想要实现异或门仅靠单层感知机无法实现,所以借助多层感知机进行非线性的空间划分可以解决异或门无法实现的问题。如下图所示,通过一个与非门,一个或门,一个与门相互连接实现了异或门的功能
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
前面设计与或非门的权重值是人工设计的,后续通过学习神经网络,利用已有的数据学习合适的权重作为参数解决上面的权重问题。
根据上图的函数转换,我们就能转换为h(x),这就是激活函数
激活函数分为阶跃函数和sigmoid函数,其中阶跃函数就是当输入值超过某一阀值时就换转变输出。
阶跃函数
定义如下
h ( x ) = { 0 ( x ≤ 0 ) 1 ( x > 0 ) h(x) = \begin{cases} 0 \,\,( x \le 0 )\\ 1 \,\,( x > 0 )\\ \end{cases} h(x)={0(x≤0)1(x>0)
代码实现
import numpy as np
import matplotlib.pylab as plt
# 定义阶跃函数
def step_function(x):
y = x > 0
return y.astype(int)
X = np.arange(-5.0, 5.0, 0.1)
Y = step_function(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1) # 指定图中绘制的y轴的范围
plt.show()
sigmoid函数
定义如下
h ( x ) = 1 1 + e x p ( − x ) h(x) = \frac{1}{1 + exp(-x)} h(x)=1+exp(−x)1
代码实现
# coding: utf-8
import numpy as np
import matplotlib.pylab as plt
# sigmoid函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
X = np.arange(-5.0, 5.0, 0.1)
Y = sigmoid(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1)
plt.show()
大于0时直接输出x,小于等于0时输出0
h ( x ) = { x ( x > 0 ) 0 ( x ≤ 0 ) h(x) = \begin{cases} x \,\,( x > 0 )\\ 0 \,\,( x \le 0 )\\ \end{cases} h(x)={x(x>0)0(x≤0)
代码实现
import numpy as np
import matplotlib.pylab as plt
# 定义reLU函数
def relu(x):
return np.maximum(0, x)
x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)
plt.plot(x, y)
plt.ylim(-1.0, 5.5)
plt.show()
二维数组点乘二维数组的运算法则等同于线性代数中学习的矩阵相乘的结果。
使用二维数组点乘一维数组的运算过程中我发现与想象的不太一致。像如下两个数组进行点乘运算,按照线性代数中所学,b矩阵应该要求为2行1列。但使用np.array
进行点乘运算结果没有问题。
以下总结了二维点乘一维数组的运算规律
实现该神经网络时,要注意X、W、Y的形状,特别是X和W的对应维度的元素个数是否一致。
代码实现
用数学式表示 a 1 a_1 a1 如下
a 1 ( 1 ) = ω 11 ( 1 ) x 1 + ω 12 ( 1 ) x 2 + b 1 ( 1 ) a^{(1)}_1 = \omega^{(1)}_{11}x_1 + \omega^{(1)}_{12}x2 + b^{(1)}_1 a1(1)=ω11(1)x1+ω12(1)x2+b1(1)
根据矩阵点乘算法规则,那么可以将第一层的加权表示成下面的数学式
A ( 1 ) = X W ( 1 ) + B ( 1 ) A^{(1)} = XW^{(1)} + B^{(1)} A(1)=XW(1)+B(1)
代码实现
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
A1 = np.dot(X, W1) + B1
实现下图a1到z1激活函数的转变(sigmoid函数)
代码实现
Z1 = sigmoid(A1)
print(A1) # [0.3 0.7 1.1]
print(Z1) # [0.57444252 0.66818777 0.75026011]
代码实现
W2 = np.array([[0.1, 0.4],[0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
第2层到第3层(输出层)也跟上面步骤基本一致,但激活函数不同
代码实现
# 定义恒等函数
def identity_function(x):
return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)
这里定义的恒等函数,会将输入按照原样输出,这里用恒等函数是为了和前面第0层到第1层和第1层到第2层的处理流程保持一致
总体代码实现
def identity_function(x):
return x
# 权重和偏置的初始化
def init_network():
network = {}
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
# 将输入信号转换为输出信号的方法
def forward(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
network = init_network()
# 定义两个输入x的初值
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y) # [0.31682708 0.69627909]
感知机中神经元流动的是0或1的二元信号,而神经网络中流动的是连续的实数值信号。
神经网络的激活函数必须使用非线性函数。因为使用线性函数的话,加深神经网络的层数将没有意义
一般而言,对于输出层的激活函数,回归问题用恒等函数,分类问题用softmax函数。
恒等函数,常用在回归问题上
def identity_function(x):
return x
sigmoid函数,用在二元分类问题上
h ( x ) = 1 1 + e x p ( − x ) h(x) = \frac{1}{1 + exp(-x)} h(x)=1+exp(−x)1
def sigmoid(x):
return 1 / (1 + np.exp(-x))
softmax函数,用在多元分类问题上
y k = e x p ( a k ) ∑ i = 1 n e x p ( a i ) y_k = \frac{exp(a_k)}{\sum_{i=1}^n exp(a_i)} yk=∑i=1nexp(ai)exp(ak)
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
其中softmax函数表示在各输出之间都有收到输入信号的影响,如图
之所以要改进softmax函数,是因为计算机所表示的数字是有界限的,比如32位或64位,而 e x e^x ex 可以可以很大,会超过64位所表示数字的最大值,于是对softmax函数进行如下的改进:(1)分子分母同乘以一个常数(2)将常数移到指数函数内部,记为 l o g C logC logC (3)用另一个常数替换 l o g C logC logC(4)实例中常用0减去a数组中的最大值C: $ -C $替换这个 C ′ C' C′
例子:
代码实现改进后的softmax函数
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
我们可以看到输出的y都在0-1之间,且它们的和为1,所以我们可以把他转为概率问题,也就是说输出的越大,他的概率越高,从上图可以看出,输入的a数组元素越大,输出的数组对应元素(即概览)也越大;另外e^x是一个单调递增函数,所以上例中a元素的大小关系和y的大小关系不变,y[2]最大,所以我们在实际上根本不需要softmax函数,直接看a元素就能知道哪个概率最大了(因为softmax需要指数运算,计算量挺大的)
求解机器学习问题的步骤可以分为“学习”和“推理”两个阶段。在学习阶段进行模型的学习,然后,在推理阶段,用学到的模型对未知的数据进行推理(分类)。如前所述,推理阶段一般会省略输出层的softmax函数。在输出层使用softmax函数是因为它和神经网络的学习有关系
由上图可以知道,输出神经元数量由类别数量决定,如输出结果为0-9这10个类别,那么神经元输出则为10个。
神经网络的学习指的是根据训练数据找出相关权重参数的过程
训练数据和测试数据
1.训练数据和测试数据:训练数据为监督数据,就是用来训练模型的,而测试数据就是不包含在训练模型内的数据,用来评判训练后模型好坏的数据。
2.泛化能力:泛化能力其实就是先训练数据训练模型,然后用测试数据进行测试模型,如果测试的成绩好那么他的泛化能力就好。
3.过拟合:根据训练数据训练出来的模型,他可以很好的处理测试已经训练过的数据,但是对没有测试过的测试数据却无法处理,所以模型和训练数据太过拟合以至于没有很好的泛化能力
损失函数是用来评判神经网络好坏的一个重要指标,越低越好,一般有2种评判方法均方误差和交叉熵误差
one-hot表示法:仅正确标签为1,其余为0
数学表达式:
E = 1 2 ∑ k ( y k − t k ) 2 E = \frac{1}{2}\sum_{k}(y_k - t_k)^2 E=21k∑(yk−tk)2
y k y_k yk表示神经网络的输出, t k t_k tk表示监督数据,k表示数据的维数。
代码:
import numpy as np
def mean_squared_error(y, t):
return 0.5 * np.sum((y - t) ** 2)
实例:
数学表达式:
E = − ∑ k ( t k l o g e y k ) E = -\sum_{k} (t_klog_ey_k) E=−k∑(tklogeyk)
其中log表示以e为底的自然对数, y k y_k yk是神经网络的输出, t k t_k tk是正确解标签。并且 t k t_k tk中只有正确解的索引标签为1,其余为0(one-hot表示)
代码实现:
def corss_entropy_error(y, t):
# 在Python中,1e-7 是一个表示科学计数法的数值,也称为浮点数。它表示的是数字 1 乘以 10 的负7次方,即 0.0000001。科学计数法用于表示非常大或非常小的数值,以便简化表示和处理。在这种情况下,1e-7 表示一个非常接近零的小数值。
delta = 1e-7
# log表示以e为底数的自然对数
return -np.sum(t * np.log(y + delta))
代码如下代码中加上了一个微小值delta,因为当出现np.log(0)时会变为负无限大的-inf,这样会导致后续计算无法进行。添加微小值可以防止负无限大的发生。
因为只有t为1时才计算,所以计算量比均方误差小,同时log是个负数的单调递增函数,趋向于0,所以y越大则E的结果越趋向于0,那么其误差结果就越小。
实例:
从上图可以看到第一个例子正确时概率高,损失函数的结果低,所以他的神经网络模型好。
E = − 1 N ∑ n ∑ k ( t n k l o g e ( y n k ) ) E=-\frac{1}{N}\sum_n\sum_k(t_{nk} log_e(y_{nk})) E=−N1n∑k∑(tnkloge(ynk))
这里,假设数据有N个, t n k t_{nk} tnk表示第n个数据的第k个元素的值( y n k y_{nk} ynk是神经网络的输出, t n k t_{nk} tnk是监督数据)。以上表达式就是将N个数据的损失函数的值取平均值。
# 改良交叉熵误差函数的实现
def cross_entropy_error_improved(y, t):
# y的维度为1时
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size= y.shape[0]
# 以下是t为one-hot表示形式的实现
return -np.sum(t * np.log(y + 1e-7)) / batch_size
# 以下是标签表示法,np.arange(batch_size)会生成一个0到batch_size-1的数组
# return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
mini-batch简单说就是采取部分样本计算出的结果近似看为整体的计算结果。
在进行神经网络的学习时,不能将识别精度作为指标。因为如果以识别精度为指标,则参数的导数在绝大多数地方都会变为0。
得益于sigmoid函数的斜率不为0,神经网络的学习才得以正确进行。
导数的定义:表示函数某一点的瞬间变化率,数学表达式如下
d f ( x ) d x = lim h → 0 f ( x + h ) − f ( x ) h \frac{df(x)}{dx} = \lim_{h\rightarrow0} \frac{f(x+h) - f(x)}{h} dxdf(x)=h→0limhf(x+h)−f(x)
考虑代码实现求函数的导数,可以将h设置为非常非常小的值,如 1 0 − 50 10^{-50} 10−50,则代码如下:
def numerical_diff(f, x):
h = 1e-50 # 0.0001
return (f(x+h) - f(x)) / h
需改进点:
1 0 − 50 10^{-50} 10−50在Python中会产生舍入误差(rounding error)。如下运行的结果
>>> np.float32(1e-50)
0.0
使用float32类型的浮点数表示 1 0 − 50 10^{-50} 10−50则直接变成了0.0,无法正确表示。所以需要改进这个微小值。这里考虑使用 1 0 − 4 10^{-4} 10−4即1e-4
f(x+h)-f(x)/h(向前差分)这个误差也很大,因为根据1的改变,h不是一个趋近于0的数,所以误差变大,应该用中心法改成f(x+h)-f(x-h)/2h(中心差分)
改进后代码:
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
注意:这种利用微小差分的导数过程为数值微分,而用数学公式推导的如y=x²导数为y=2x这种交解析性求导,这种叫做真导数
如:y=0.01x²+0.1x的导数实现
运行结果如下:
可以发现改进后的微分代码误差非常小
一个函数有多个自变量时的导数成为偏导数,表达式 ∂ f ∂ x 0 \frac{\partial f}{\partial x_0} ∂x0∂f、 ∂ f ∂ x 1 \frac{\partial f}{\partial x_1} ∂x1∂f
如
f ( x 0 , x 1 ) = x 0 2 + x 1 2 f(x_0, x_1) = x_0^2 + x_1^2 f(x0,x1)=x02+x12
使用matplotlib绘制的图像如下
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 创建数据点
x0 = np.linspace(-10, 10, 100)
x1 = np.linspace(-10, 10, 100)
# 使用 np.meshgrid 函数可以将这两个一维数组转换为两个二维数组 x0 和 x1,这将构成我们的网格。
x0, x1 = np.meshgrid(x0, x1)
f = x0**2 + x1**2
# 创建 3D 图像
fig = plt.figure()
# projection='3d' 指定这是一个三维图像
ax = fig.add_subplot(111, projection='3d')
# 绘制曲面
# cmap='viridis' 指定了颜色映射,这里使用了 Viridis 颜色映射
ax.plot_surface(x0, x1, f, cmap='viridis')
# 设置轴标签
ax.set_xlabel('x0')
ax.set_ylabel('x1')
ax.set_zlabel('f(x0, x1)')
# 显示图像
plt.show()
偏导数实现:原理其实跟一元导数一样,就是带入一个真值消除一个变量而已
由全部变量的偏导数汇总而成的向量( ∂ f ∂ x 0 \frac{\partial f}{\partial x_0} ∂x0∂f, ∂ f ∂ x 1 \frac{\partial f}{\partial x_1} ∂x1∂f)称为梯度
比如我们求一个函数y=x0²+x1²变量有x0,x1,当我们对他全部变量(这里最多只有2个)进行偏导汇总而成的变量叫梯度。
实现梯度的代码如下
# 实现梯度
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x)
for idx in range(x.size):
tem_val = x[idx]
# f(x + h) 的计算
x[idx] = tem_val + h
fxh1 = f(x)
# f(x - h) 的计算
x[idx] = tem_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tem_val # 还原倍数
return grad
从这个图可以看出,梯度指向函数 f ( x 0 , x 1 ) f(x_0,x_1) f(x0,x1)的最低处(最小值),就像指南针一样,所有的箭头都指向同一点。其次我们发现,离“最低处”越远,箭头越大。梯度指示的方向是各点处的函数值减小最多的方向,这是一个重要的性质!
在梯度法中,函数的取值从当前位置沿着梯度方向前进一段距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样不断的沿梯度方向前进,逐渐减小函数值的过程就是梯度法,用数学表达式来表示则如下所示
x 0 = x 0 − η ∂ f ∂ x 0 x_0 = x_0 - \eta\frac{\partial f}{\partial x_0} x0=x0−η∂x0∂f
x 1 = x 1 − η ∂ f ∂ x 1 x_1 = x_1 - \eta\frac{\partial f}{\partial x_1} x1=x1−η∂x1∂f
其中, η \eta η表示更新量,在神经网络的学习中,称为学习率。学习率决定在一次更新中更新的程度。
梯度下降算法代码实现:
# 梯度下降法找最小值
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
用梯度法求函数 f ( x 0 , x 1 ) = x 0 2 + x 1 2 f(x_0, x_1) = x_0^2 + x_1^2 f(x0,x1)=x02+x12的最小值如下
最终结果为(-6.11110793e-10 8.14814391e-10),非常接近(0,0)。实际上,真的最小值就是(0, 0)。所以说通过梯度法我们基本得到了正确结果。用图示来表示梯度法的更新过程则如下:
学习率 η \eta η不可过大也不可过小,太大时结果会发散成很大的数,太小的话结果几乎没更新就结束了
像学习率这样的参数称为超参数。这是一种和神经网络的参数(权重和偏置)性质不同的参数。相对于神经网络的权重参数是通过训练数据和学习算法自动获得的,学习率这样的超参数则是人工设定的。一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。
我们有2*3的W权重参数,L为损失函数,梯度用 ∂ L ∂ W \frac{\partial L}{\partial W} ∂W∂L表示,如下所示
W = { ω 11 ω 12 ω 13 ω 21 ω 22 ω 23 } W = \begin{Bmatrix} \omega_{11}&\omega_{12}&\omega_{13}\\ \omega_{21}&\omega_{22}&\omega_{23}\\ \end{Bmatrix} W={ω11ω21ω12ω22ω13ω23}
∂ L ∂ W = { ∂ L ∂ ω 11 ∂ L ∂ ω 12 ∂ L ∂ ω 13 ∂ L ∂ ω 21 ∂ L ∂ ω 22 ∂ L ∂ ω 23 } \frac{\partial L}{\partial W} = \begin{Bmatrix} \frac{\partial L}{\partial \omega_{11}}&\frac{\partial L}{\partial \omega_{12}}&\frac{\partial L}{\partial \omega_{13}}\\ \frac{\partial L}{\partial \omega_{21}}&\frac{\partial L}{\partial \omega_{22}}&\frac{\partial L}{\partial \omega_{23}}\\ \end{Bmatrix} ∂W∂L={∂ω11∂L∂ω21∂L∂ω12∂L∂ω22∂L∂ω13∂L∂ω23∂L}
∂ L ∂ W \frac{\partial L}{\partial W} ∂W∂L的元素由各个元素关于W的偏导数构成。比如,第一行第一列的元素 ∂ L ∂ ω 11 \frac{\partial L}{\partial \omega_{11}} ∂ω11∂L表示当 ω 11 \omega_{11} ω11稍微变化时,损失函数L会发生多大变化。这里的重点是 ∂ L ∂ W \frac{\partial L}{\partial W} ∂W∂L的形状和W相同。