感知机设置权重的工作还是人工进行的
神经网络的一个重要性质是它可以自动地从数据中学习到合适的权重参数
输入层 第0层
中间层(隐藏层) 第1层
输出层 第2层
激活函数
将输入信号的总和转换为输出信号
比如感知机,h(x)就是激活函数 :
$$
y=h(b+w_1x_1+w_2x_2)
$$
h ( x ) = { 0 ( x ⩽ 0 ) 1 ( x > 0 )
$$
\begin{array}{c}a=b+w_1x_1+w_2x_2\\y=h(a)\end{array}
$$
表示神经元的○中明确显示了激活函数的计算过程
sigmoid函数
$$
h(x)=\frac1{1+\exp(-x)}
$$
$exp(−x)$ 表示$e^{−x}$的意思
$e$ 纳皮尔常数2.7182
1 2 3 4 5 def sigmoid (x ): return 1 / (1 + np.exp(-x)) print (sigmoid(1.0 ))print (sigmoid(2.0 ))
0.7310585786300049
0.8807970779778823
图形
1 2 3 4 5 x = np.arange(-5.0 , 5.0 , 0.1 ) y = sigmoid(x) plt.plot(x, y) plt.ylim(-0.1 , 1.1 ) plt.show()
阶跃函数
1 2 3 def step_function (x ): y = x > 0 return y.astype(np.int )
1 2 def step_function (x ): return np.array(x>0 , dtype=bp.int32)
图形
1 2 3 4 5 6 7 8 9 10 11 import numpy as npimport matplotlib.pylab as pltdef step_function (x ): return np.array(x > 0 , dtype=np.int32) x = np.arange(-5.0 , 5.0 , 0.1 ) y = step_function(x) plt.plot(x, y) plt.ylim(-0.1 , 1.1 ) plt.show()
sigmoid函数和阶跃函数的比较
平滑性
感知机中神经元之间流动的是0或1的二元信号(阶跃函数)
神经网络中流动的是连续的实数值信号(sigmoid函数)
形状相似
非线性函数 ($h(x)=cx$为线性函数)
神经网络的激活函数必须使用非线性函数
线性函数:不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”
ReLU函数
ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0
h ( x ) = { x ( x > 0 ) 0 ( x ⩽ 0 )
1 2 def relu (x ): return np.maximum(0 , x)
多维数组的运算
神经网络的内积
1 2 3 4 5 X = np.array([1 , 2 ]) W = np.array([[1 , 3 , 5 ], [2 , 4 , 6 ]]) Y = np.dot(X, W) print (Y)
3层神经网络的实现
符号:
右上角表示权重和神经元的层号(从输入层开始)
右下角前面对应后一层的神经元
右下角后面对应前一层的神经元
各层间信号传递的实现
灰色的为表示偏置的神经元(右下角的神经元只有一个)
公式表达为
$$
a_1^{(1)}=w_{11}^{(1)}x_1+w_{12}^{(1)}x_2+b_1^{(1)}
$$
矩阵乘法运算公式为
$$
A^{(1)}=XW^{(1)}+B^{(1)}
$$
其中,$A^(1)、X、B^(1)、W^(1)$如下所示
A ( 1 ) = ( a 1 ( 1 ) a 2 ( 1 ) a 3 ( 1 ) ) , X = ( x 1 x 2 ) , B ( 1 ) = ( b 1 ( 1 ) b 2 ( 1 ) b 3 ( 1 ) ) W ( 1 ) = ( w 11 ( 1 ) w 21 ( 1 ) w 31 ( 1 ) w 12 ( 1 ) w 22 ( 1 ) w 32 ( 1 ) )
具体代码和流程
1 2 3 4 5 6 7 8 9 10 import numpy as npX = 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 Z1 = sigmoid(A1) print (A1) print (Z1)
1 2 3 4 5 6 7 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) print (W2.shape) print (B2.shape) A2 = np.dot(Z1, W2) + B2 Z2 = sigmoid(A2)
从第2层到输出层的信号传递的激活函数 ,一般地,回归问题可以使用恒等函数,二元分类问题可以使用sigmoid函数,多元分类问题可以使用softmax函数
代码总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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 def identity_function (x ): '''恒等函数''' return x def sigmoid (n ): '''sigmoid函数''' e = 2.718281828459045 return 1 / (1 + np.exp(-n)) network = init_network() x = np.array([1.0 , 0.5 ]) y = forward(network, x) print (y)
输出层的设计
一般而言,回归问题用恒等函数 ,分类问题用softmax函数
分类问题是数据属于哪一个类别的问题
回归问题是根据某个输入预测一个(连续的)数值的问题
softmax函数
softmax函数数学公式
$$
y_k=\frac{\exp(a_k)}{\sum_{i=1}^n\exp(a_i)}
$$
softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大
softmax函数数学公式改进
$$
\begin{aligned}
y_k=\frac{\exp(a_k)}{\sum_{i=1}^n\exp(a_i)} & =\frac{\mathrm{C}\exp(a_k)}{\mathrm{C}\sum_{i=1}^n\exp(a_i)} \
& =\frac{\exp(a_k+\log\mathrm{C})}{\sum_{i=1}^n\exp(a_i+\log\mathrm{C})} \
& =\frac{\exp(a_k+\mathrm{C}^{\prime})}{\sum_{i=1}^n\exp(a_i+\mathrm{C}^{\prime})}
\end{aligned}
$$
1 2 3 4 5 6 7 def softmax (a ): c = np.max () exp_a = np.exp(a - c) sum_exp_a = np.sum (exp_a) y = exp_a / sum_exp_a return y
softmax函数特征
softmax函数的输出是0.0到1.0之间的实数
softmax函数的输出值的总和是1
可以把softmax函数的输出解释为“概率”
输出层的神经元数量需要根据待解决的问题来决定
手写数字识别
import sys,os sys.path.append(os.pardir)
sys.path.append(os.pardir)语句实际上是把父目录加入到sys.path(Python的搜索模块的路径集)中,从而可以导入父目录下的任何目录中的任何文件
Python有pickle
这个便利的功能。这个功能可以将程序运行中的对象保存为文件。如果加载保存过的pickle文件,可以立刻复原之前程序运行中的对象
MNIST数据集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import numpy as npfrom mnist import load_mnistfrom PIL import Imagedef img_show (img ): pil_img = Image.fromarray(np.uint8(img)) pil_img.show() (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True , normalize=False ) img = x_train[59999 ] label = t_train[59999 ] print (label) print (img.shape) img = img.reshape(28 , 28 ) print (img.shape) img_show(img)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 try : import urllib.request except ImportError: raise ImportError('You should use Python 3.x' ) import os.pathimport gzipimport pickleimport osimport numpy as npurl_base = 'https://ossci-datasets.s3.amazonaws.com/mnist/' key_file = { 'train_img' :'train-images-idx3-ubyte.gz' , 'train_label' :'train-labels-idx1-ubyte.gz' , 'test_img' :'t10k-images-idx3-ubyte.gz' , 'test_label' :'t10k-labels-idx1-ubyte.gz' } dataset_dir = os.path.dirname(os.path.abspath(__file__)) save_file = dataset_dir + "/mnist.pkl" train_num = 60000 test_num = 10000 img_dim = (1 , 28 , 28 ) img_size = 784 def _download (file_name ): file_path = dataset_dir + "/" + file_name if os.path.exists(file_path): return print ("Downloading " + file_name + " ... " ) urllib.request.urlretrieve(url_base + file_name, file_path) print ("Done" ) def download_mnist (): for v in key_file.values(): _download(v) def _load_label (file_name ): file_path = dataset_dir + "/" + file_name print ("Converting " + file_name + " to NumPy Array ..." ) with gzip.open (file_path, 'rb' ) as f: labels = np.frombuffer(f.read(), np.uint8, offset=8 ) print ("Done" ) return labels def _load_img (file_name ): file_path = dataset_dir + "/" + file_name print ("Converting " + file_name + " to NumPy Array ..." ) with gzip.open (file_path, 'rb' ) as f: data = np.frombuffer(f.read(), np.uint8, offset=16 ) data = data.reshape(-1 , img_size) print ("Done" ) return data def _convert_numpy (): dataset = {} dataset['train_img' ] = _load_img(key_file['train_img' ]) dataset['train_label' ] = _load_label(key_file['train_label' ]) dataset['test_img' ] = _load_img(key_file['test_img' ]) dataset['test_label' ] = _load_label(key_file['test_label' ]) return dataset def init_mnist (): download_mnist() dataset = _convert_numpy() print ("Creating pickle file ..." ) with open (save_file, 'wb' ) as f: pickle.dump(dataset, f, -1 ) print ("Done!" ) def _change_one_hot_label (X ): T = np.zeros((X.size, 10 )) for idx, row in enumerate (T): row[X[idx]] = 1 return T def load_mnist (normalize=True , flatten=True , one_hot_label=False ): """读入MNIST数据集 Parameters ---------- normalize : 将图像的像素值正规化为0.0~1.0 one_hot_label : one_hot_label为True的情况下,标签作为one-hot数组返回 one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组 flatten : 是否将图像展开为一维数组 Returns ------- (训练图像, 训练标签), (测试图像, 测试标签) """ if not os.path.exists(save_file): init_mnist() with open (save_file, 'rb' ) as f: dataset = pickle.load(f) if normalize: for key in ('train_img' , 'test_img' ): dataset[key] = dataset[key].astype(np.float32) dataset[key] /= 255.0 if one_hot_label: dataset['train_label' ] = _change_one_hot_label(dataset['train_label' ]) dataset['test_label' ] = _change_one_hot_label(dataset['test_label' ]) if not flatten: for key in ('train_img' , 'test_img' ): dataset[key] = dataset[key].reshape(-1 , 1 , 28 , 28 ) return (dataset['train_img' ], dataset['train_label' ]), (dataset['test_img' ], dataset['test_label' ]) if __name__ == '__main__' : init_mnist()
神经网络的推理处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import sys, ossys.path.append(os.pardir) import numpy as npimport picklefrom dataset.mnist import load_mnistfrom common.functions import sigmoid, softmaxdef get_data (): (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True , flatten=True , one_hot_label=False ) return x_test, t_test def init_network (): with open ("sample_weight.pkl" , 'rb' ) as f: network = pickle.load(f) return network def predict (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 = softmax(a3) return y x, t = get_data() network = init_network() accuracy_cnt = 0 for i in range (len (x)): y = predict(network, x[i]) p= np.argmax(y) if p == t[i]: accuracy_cnt += 1 print ("Accuracy:" + str (float (accuracy_cnt) / len (x)))
np.argmax(x)
函数取出数组中的最大值的索引
正规化 把数据限定到某个范围内的处理,最常规是0~1
预处理 对神经网络的输入数据进行某种既定的转换,比如正规化
批处理
批 打包式的输入数据
批处理一次性计算大型数组要比分开逐步计算各个小型数组速度更快
1 2 3 4 5 6 7 8 batch_size = 100 accuracy_cnt = 0 for i in range (0 , len (x), batch_size): x_batch = x[i:i+batch_size] y_batch = predict(network, x_batch) p = np.argmax(y_batch, axis=1 ) accuracy_cnt += np.sum (p == t[i:i+batch_size])
代码解释
range(start, end, step)
生成的列表中的下一个元素会增加step指定的值
x[i:i+batch_size]
切片抽取批数据
np.argmax(y_batch, axis=1)
沿着第1维方向找到值最大的元素的索引
矩阵的第0维是列方向,第1维是行方向
对于数组或列表的比较,例如 array1 == array2
,会返回一个布尔数组