1 import numpy as np 2 3 ''' 4 前向传播函数: 5 -x :包含输入数据的numpy数组,形状为(N,d_1,...,d_k) 6 -w :形状为(D,M)的一系列权重 7 -b :偏置,形状为(M,) 8 9 关于参数的解释: 10 在我们这个例子中输入的数据为 11 [[2,1], 12 [-1,1], 13 [-1,-1], 14 [1,-1]] 15 16 它是一个4行2列的二维数组,那么x的形状就是(4,2), 17 对应的参数N=4,d_1=2。这是我们用来做训练的坐标数据, 18 分别对应了I、II、III、IV象限。 19 在某些应用场景中,x的维度可能更高。比如对于一个20*20像素的4张灰度图,x的形状将是(4,20,20),对应的参数就是N=4,d_1=20,d_2=20。(这里边第一个参数用N表示,它代表的是同时用于计算前向传播的数据有几组,后边的参数d_1~d_k代表的是数据本身的形状。) 20 21 对于这种维度大于2的x来说,需要对其进行重新塑形,也就是将(4,20,20)的高维数组变化为(4,20*20)这样的二位数组。 22 23 为什么要这么做呢?是为了方便计算。这样变换之后高维的向量被“拍扁”成一维向量(长度为20*20的一维向量),对应的W和b也都是一维的,既统一了参数形式,又不会影响数据的正常使用。 24 25 这个“拍扁”的动作,是用上述代码中的这两行完成的: 26 27 N = x.shape[0] # 重置输入参数X的形状 28 x_row = x.reshape(N,-1) # (N,D) 29 x.shape[0]是获取数组x的第0维长度,也就是数据的组数,对于上述的4行2列的数组,其值为4;对于上述(4,20,20)的数组,其值也为4. 30 31 x.reshape(N,-1)是对x重新塑形,即保留第0维,其他维度排列成1维。对于形状为(4,2)的数组,其形状不变,对于形状为(4,20,20)的数组,形状变为(4,20*20)。以此类推。 32 33 在完成reshape后,就可以进行矩阵的线性运算了: 34 35 out = np.dot(x_row, w)+ b # (N,M) 36 .dot就是numpy中的函数,可以实现x_row与w的矩阵相乘。x_row的形状为(N,D),w的形状为(D,M),得到的out的形状是(N,M)。 37 38 cache =(x, w, b) # 缓存值,反向传播时使用 39 上面这句是将当前x,w和b的值缓存下来,留作反向传播时使用。 40 ''' 41 42 43 def affine_forward(x, w, b): 44 result = None 45 N = x.shape[0] 46 x_row = x.reshape(N, -1) 47 result = np.dot(x_row, w) + b # dot是np中的函数。用来做矩阵乘法 48 cache = (x, w, b) 49 return result, cache 50 51 52 # 反向传播函数 53 # - x:包含输入数据的numpy数组,形状为(N,d_1,...,d_k) 54 # - w:形状(D,M)的一系列权重 55 # - b:偏置,形状为(M,) 56 def affine_backward(dout, cache): 57 x, w, b = cache # 读取缓存 58 dx, dw, db = None, None, None # 返回值初始化 59 dx = np.dot(dout, w.T) # (N,D) 60 dx = np.reshape(dx, x.shape) # (N,d1,...,d_k) 61 x_row = x.reshape(x.shape[0], -1) # (N,D) 62 dw = np.dot(x_row.T, dout) # (D,M) 63 db = np.sum(dout, axis=0, keepdims=True) # (1,M) 64 return dx, dw, db 65 66 67 X = np.array([[2, 1], 68 [-1, 1], 69 [-1, -1], 70 [1, -1]]) # 用于训练的坐标,对应的是I、II、III、IV象限 71 t = np.array([0, 1, 2, 3]) # 标签,对应的是I、II、III、IV象限 72 np.random.seed(1) # 有这行语句,你们生成的随机数就和我一样了 73 74 # 一些初始化参数 75 input_dim = X.shape[1] # 输入参数的维度,此处为2,即每个坐标用两个数表示 76 num_classes = t.shape[0] # 输出参数的维度,此处为4,即最终分为四个象限 77 hidden_dim = 50 # 隐藏层维度,为可调参数 78 reg = 0.001 # 正则化强度,为可调参数 79 epsilon = 0.001 # 梯度下降的学习率,为可调参数 80 # 初始化W1,W2,b1,b2 81 W1 = np.random.randn(input_dim, hidden_dim) # (2,50) 82 W2 = np.random.randn(hidden_dim, num_classes) # (50,4) 83 b1 = np.zeros((1, hidden_dim)) # (1,50) 84 b2 = np.zeros((1, num_classes)) # (1,4) 85 86 for j in range(10000): # 这里设置了训练的循环次数为10000 87 # ①前向传播 88 H, fc_cache = affine_forward(X, W1, b1) # 第一层前向传播 89 H = np.maximum(0, H) # 激活 90 relu_cache = H # 缓存第一层激活后的结果 91 Y, cachey = affine_forward(H, W2, b2) # 第二层前向传播 92 # ②Softmax层计算 93 probs = np.exp(Y - np.max(Y, axis=1, keepdims=True)) 94 probs /= np.sum(probs, axis=1, keepdims=True) # Softmax算法实现 95 # ③计算loss值 96 N = Y.shape[0] # 值为4 97 print(probs[np.arange(N), t]) # 打印各个数据的正确解标签对应的神经网络的输出 98 loss = -np.sum(np.log(probs[np.arange(N), t])) / N # 计算loss 99 print(loss) # 打印loss 100 # ④反向传播 101 dx = probs.copy() # 以Softmax输出结果作为反向输出的起点 102 dx[np.arange(N), t] -= 1 # 103 dx /= N # 到这里是反向传播到softmax前 104 dh1, dW2, db2 = affine_backward(dx, cachey) # 反向传播至第二层前 105 dh1[relu_cache <= 0] = 0 # 反向传播至激活层前 106 dX, dW1, db1 = affine_backward(dh1, fc_cache) # 反向传播至第一层前 107 # ⑤参数更新 108 dW2 += reg * W2 109 dW1 += reg * W1 110 W2 += -epsilon * dW2 111 b2 += -epsilon * db2 112 W1 += -epsilon * dW1 113 b1 += -epsilon * db1 114 115 116 test = np.array([[2,-3],[-2,2],[-2,-2],[2,-2]]) 117 H,fc_cache = affine_forward(test,W1,b1) #仿射 118 H = np.maximum(0, H) #激活 119 relu_cache = H 120 Y,cachey = affine_forward(H,W2,b2) #仿射 121 # Softmax 122 probs = np.exp(Y - np.max(Y, axis=1, keepdims=True)) 123 probs /= np.sum(probs, axis=1, keepdims=True) # Softmax 124 print(probs) 125 for k in range(4):