阅读上一篇 深度学习的“Hello World”
今天主要讲神经网络的数学基础,涉及的数学包括线性代数、矩阵分析、微积分和数理统计等科目。主要讲清楚两个概念张量和梯度,这两个概念对于了解和掌握机器学习(深度学习)尤为重要,在介绍这两个概念之前,我们来了解一下Google的神经网络游乐园。
Google是人工智能的领导者,在人工智能方面的建树,无需赘述了。Google官方有一个神经网站游乐园,可以让你形象直观的观察神经网络训练的过程,包括训练结果的变化,你可以修改神经网络的层数,以及相关的参数,再运行,看起来很直观,如果你感兴趣,还可以到Google官方的Github上下载该项目在本地运行,以下是从运行中截取的一张图:
你可以直观的看到数据在网络中流动的情形,Google的机器学习框架叫做 TensorFlow,这是个很形象的比喻,意思是 张量(Tensor)在神经网络中流动(Flow),可以看出张量是多么重要!张量对这个领域非常重要,重要到Google 的TensorFlow 都以它来命名。下面我们就来谈谈张量。
神经网络使用的数据存储在多维Numpy 数组中,也叫张量(tensor),所以张量其实就是多维数组,所以不能叫做矩阵,矩阵只是二维的数组,张量所指的维度是没有限制的。一般来说,当前所有机器学习系统都使用张量作为基本数据结构。那么什么是张量?张量这一概念的核心在于,它是一个数据容器。它包含的数据几乎总是数值数据,因此它是数字的容器。你可能对矩阵很熟悉,它是二维张量。张量是矩阵向任意维度的扩展。下面对0到3维张做一些描述和实验,以加深理解。
注意,张量的维度(dimension)通常叫作轴(axis)
仅包含一个数字的张量叫作标量(scalar,也叫标量张量、零维张量、0D 张量)。在Numpy中,一个float32 或float64 的数字就是一个标量张量(或标量数组)。你可以用ndim 属性来查看一个Numpy 张量的轴的个数。标量张量有0 个轴(ndim=0)。张量轴的个数也叫作阶(rank)。
数字组成的数组叫作向量(vector)或一维张量(1D张量)。一维张量只有一个轴。
向量组成的数组叫作矩阵(matrix)或二维张量(2D 张量)。矩阵有2个轴(通常叫作行和列)。你可以将矩阵直观地理解为数字组成的矩形网格。
将多个矩阵组合成一个新的数组,可以得到一个3D 张量,你可以将其直观地理解为数字组成的立方体。将多个3D 张量组合成一个数组,可以创建一个4D 张量,以此类推。深度学习处理的一般是0D 到4D 的张量,但处理视频数据时可能会遇到5D 张量。
张量是由以下三个关键属性来定义的:
把一个数不断的加[]让它变成多维数组,以及演示如何取数组的元素。
>>> data = np.array(12)
>>> print(data.ndim, data.shape,data)
0 () 12
>>> data = np.array([data])
>>> print(data.ndim, data.shape,data)
1 (1,) [12]
>>> data = np.array([data,[13]])
>>> print(data.ndim, data.shape,data)
2 (2, 1) [[12][13]]
>>> data = np.array([data,data,data])
>>> print(data.ndim, data.shape,data)
3 (3, 2, 1) [[[12][13]][[12][13]][[12][13]]]
>>> data.ndim, data.shape,data
(3, (3, 2, 1), array([[[12],[13]],[[12],[13]],[[12],[13]]]))
>>> data[0]
array([[12],[13]])
>>> data[1]
array([[12],[13]])
>>> data[2]
array([[12],[13]])
>>> data[0][1][1]
Traceback (most recent call last):
File "" , line 1, in <module>
IndexError: index 1 is out of bounds for axis 0 with size 1
>>> data[0][1][0]
13
为了很好的理解梯度,我们先来回顾一下导数,偏导数的定义
在高等数学中学过导数,导数就是表示某个瞬间的变化量,它可以定义成下面的式子:
d f ( x ) d x = lim x → 0 f ( x + h ) − f ( x ) h \frac{df(x)}{dx}=\lim_{x \to 0}\frac{f(x+h)-f(x)}{h} dxdf(x)=limx→0hf(x+h)−f(x)
利用微小的差分求导数的过程称为数值微分(numerical differentiation)。
有多个变量的函数的导数称为偏导数,比如一个有两个变量的函数:
f ( x 0 , x 1 ) = x 0 2 + x 1 2 f(x0,x1) = x0^2 + x1^2 f(x0,x1)=x02+x12
在某一个具体的点(3,4),就有两个导数,用数学表达式表示如下: θ f θ x 0 , θ f θ x 1 \frac {\theta f}{\theta x0},\frac {\theta f}{\theta x1} θx0θf,θx1θf
该函数图像如下:
由全部变量的偏导数汇总而成的张量称为梯度(gradient)
我们按变量分别计算了x0 和 x1的偏导数。现在,我们希望一起计算x0和x1的偏导数。比如,我们来考虑求x0 =3,x1 =4时的偏导数: ( θ f θ x 0 , θ f θ x 1 ) (\frac {\theta f}{\theta x0},\frac {\theta f}{\theta x1}) (θx0θf,θx1θf)
这样由所有变量的偏导数汇总而成的向量成为梯度。可以看出梯度实质是一个张量,内容是所有变量在某点的偏导数的集合,只不过使用张量一次性方便描述和计算。
为量巩固对导数、偏导数、梯度的理解和认识,练习时最好的方式,以下是练习的全部代码,根据需要打开 Test Case部分的注释,代码已经自带解释了。
# coding: utf-8
import matplotlib.pylab as plt
import numpy as np
class Diff2Gradient(object):
def numerical_diff(self, f, x):
"""
函数求导数,实例是求1个自变量的函数
:param f: 传递的参数可以是一个函数
:param x: x 可以是多维数组,代表多个点
:return: 返回x中点的导数,值是跟x.shape相同的数组
"""
h = 1e-4 # 0.0001
return (f(x + h) - f(x - h)) / (2 * h)
def numerical_gradient(self, f, X):
"""
求导数
:param f:
:param X: 可以是多维数组
:return: 返回X.shape的导数值
"""
if X.ndim == 1:
return self._numerical_gradient_no_batch(f, X)
else:
grad = np.zeros_like(X)
# 维度上循环
for idx, x in enumerate(X):
# print('{}->{}'.format(idx, x))
grad[idx] = self._numerical_gradient_no_batch(f, x)
return grad
def _numerical_gradient_no_batch(self, f, x):
"""
返回一行的导数值
:param f: 一个自变量函数
:param x: x 可以是一维数组
:return:
"""
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
# 这是把整个x变量带入函数,x里只有x[idx]值变了
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val
return grad
def tangent_line(self, f, x):
"""
求切线函数
:param f: 原来函数
:param x: 选的x点
:return: 一个函数(形式 kx+b)
"""
d = self.numerical_diff(f, x)
y = f(x) - d * x
return lambda t: d * t + y
def function_1(x):
return 0.01 * x ** 2 + 0.1 * x
def function_2(x):
if x.ndim == 1:
return np.sum(x ** 2)
else:
return np.sum(x ** 2, axis=1)
if __name__ == '__main__':
d2g = Diff2Gradient()
# # Test Case 1: 自变量只有一个的情况
# # 传入参数说明,传入1个值,返回一个值的导数,传入数组就返回数值中
# # 每个值处的导数,这是Numpy看起来有点神奇的地方。
# print(d2g.numerical_diff(function_1, 2.5))
# print(d2g.numerical_diff(function_1, np.array([2.5])))
# print(d2g.numerical_diff(function_1, np.array([2.5, 4, 5])))
# print(d2g.numerical_diff(function_1, np.array([[2.5, 3, 5, 6], [12, 2.5, 4, 3]])))
#
# x = np.arange(0.0, 20.0, 0.1)
# y = function_1(x)
# plt.xlabel("x")
# plt.ylabel("f(x)")
#
# tf = d2g.tangent_line(function_1, 5)
# y2 = tf(x)
#
# # 绘制f(x)函数和切线,这里要理解函数可以作为
# # 参数传递的技巧,再一次展现Python作为动态语
# # 言的一个优势。
# plt.plot(x, y)
# plt.plot(x, y2)
# plt.show()
# Test Case 2:
x0 = np.arange(-2, 2.5, 1)
x1 = np.arange(-1, 3.5, 1)
# 返回两个数组组成的数组,一个数组[x1.size * x0.size].
# 每行是x0所有数值;第二个数组是[x1.size * x0.size],
# 每1行的值是x1[0]第2行值是x1[1],以此类推。
X, Y = np.meshgrid(x0, x1)
X = X.flatten()
Y = Y.flatten()
points = np.array([X, Y])
grad = d2g.numerical_gradient(function_2, points)
c = np.random.randn(len(X)) # arrow颜色
plt.figure()
plt.quiver(X, Y, -grad[0], -grad[1], c, angles="xy")
plt.xlim([-2, 2])
plt.ylim([-2, 2])
plt.xlabel('x0')
plt.ylabel('x1')
plt.grid()
plt.legend()
plt.draw()
plt.show()
继续下一篇阅读 神经网络的数学基础:张量运算