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