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):