前提:
- 三层神经网络;
- 前两层网络输出的激活函数为relu函数;
- 最后一层的输出激活函数为softmax(本例是个二分类问题);
- 损失函数为交叉熵函数;
- 209个样本;
- 样本大小为64X64X3;
- 每层神经元个数为:第一层:16,第二层:8,第三层:2
1.如何求softmax函数导数?
1.1神经网络如下:
(三层,两个输出)
1.2前向传播:
1.2.1数学公式:
1.2.2代码:
#三层网络、前向传播
class MLPNet(nn.Module):
def __init__(self):
#定义网络层
self.layer = nn.Linear(64*64*3, 16)
self.layer2 = nn.Linear(16, 8)
self.layer3 = nn.Linear(8, 2)
#定义前向传播(包含激活函数)
def forward(self,x):
x = x.reshape(-1, 64*64*3)
x = self.layer(x)
x = F.relu(x)
x = self.layer2(x)
x = F.relu(x)
x = self.layer3(x)
#sofemax函数的轴不写死
return F.softmax(x, 1)
#softmax函数
def softmax(z):
# Z的形状:209 2 。求每个样本在两个输出得到概率的和。209个样本即209个概率;
#求行方向的和:axis=1;
#求完和后形状变为(209,)。要进行除法运算,需要将(209,)形状通过广播变为(209,1):keepdims=True
return np.divide(np.exp(z), np.sum(np.exp(z), axis=1, keepdims=True))
#softmax函数的轴不写死,如下:
def softmax(z ,dim):
return np.divide(np.exp(z), np.sum(np.exp(z), axis=dim, keepdims=True))
1.3反向传播:
1.3.1数学公式:
1.3.2求导重点
1.3.3最终代码:
#CrossEntropyLoss损失函数
class CrossEntropyLoss(Module):
def __call__(self, output, target ):
self.A = output
self.Y = target
self.m = target.shape[0]
return self.forward(self.A ,self.Y)
def forward(self, A ,Y):
self.loss = -np.mean(np.sum(Y * np.log(A), axis=1))
# axis=1:在行方向相加,做平均值。209行,将每行的结果在行方向进行相加。
#-np.mean():为求一批样本的平均损失。
return self
def float(self):
return self.loss
def backward(self):
#第三层
"""
dA3::
#Yj:j是输出的最大值的索引。
#Aj:j所在的最大值索引对应的最大的输出值。
"""
#每一行最大值的索引
j = np.argmax(self.Y, axis=1)
"""
Yj:
取Y中最大值
取值方法:
如下7个样本:
10
01
10
01
01
10
10
找出每个样本中1所在的位置,然后拿这个位置索引A中的输出。
将Y中的1取出:现将每一行切片y[:],然后
例如:
data = np.arange(24).reshape(4, 6)
y = np.array([3, 4, 2, 5]) #每个样本对应的索引不一样:3 10 14 23
print(data)
可看成4个样本,每个样本有5个输出,这5个输出对应不同的值。
通过花式索引找,先全部取出,然后找每个维度中的每个索引:
print(data[[0, 1, 2, 3], y])
打印结果为:
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]]
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]]
[ 3 10 14 23
"""
#J为最大值索引,根据索引将每行最大值索引出来:self.Y[np.arange(209), j],
#其中,np.arange(209)为生成209行。
#Aj与Yj取法相同。
dA3 = -self.Y[np.arange(209), j] / self.A[np.arange(209), j]#loss对A的导数
dA3 = dA3.reshape(-1,1)
"""
dZ3分两种情况:
(1):i!=j:
(2):i=j:
"""
#声明一个(209,2)的零矩阵
grads = np.zeros_like(self.Y)
#首先,将零矩阵填充为Aj*Ai的,然后,把需要改动的变为Aj*(1-Aj)的。
#每个输出*Aj,且Aj为每个样本中最大值索引对应的输出。
#Aj是一维向量
Aj = self.A[np.arange(self.Y.shape[0]), j]
# i != j时:导数为:(-Aj*Ai)
#reshape(-1, 1):将一维向量变为(广播)二维的向量。
grads[:] = -self.A * Aj.reshape(-1, 1)
# i == j时:导数为(Aj(1-AJ))
#grads[np.arange(self.Y.shape[0]), j] :找到对应位置填充为Aj * (1 - Aj)的值
grads[np.arange(self.Y.shape[0]), j] = Aj * (1 - Aj)
#dA3对dZ3的导数
dA3_dZ3 = grads
#dZ3的导数
dZ3 = dA3 * dA3_dZ3
x_input = self.get_cache_input()
A2 = next(x_input)
dW3 = np.dot(dZ3.T, A2) / self.m
dB3 = np.sum(dZ3.T, axis=1,keepdims=True)
#第二层
params = self.parameters()
w3 = next(params).weight # 获取第三层的权重
# relu 的导数
relu_grad = np.copy(A2)#不污染原数据
relu_grad[relu_grad > 0] = 1#找到>0的导数,其余继续为0
dZ2 = np.dot(dZ3, w3) * relu_grad
A1 = next(x_input)
dW2 = np.dot(dZ2.T, A1) / self.m # 一批样本的平均导数
dB2 = np.sum(dZ2.T, axis=1, keepdims=True) #
# 第一层
W2 = next(params).weight
dA1 = np.dot(dZ2, W2)
relu_grad = np.copy(A1)
relu_grad[relu_grad > 0] = 1
dZ1 = dA1 * relu_grad
X1 = next(x_input)
dW1 = np.dot(dZ1.T, X1) / self.m
dB1 = np.sum(dZ1.T, axis=1, keepdims=True)
return {"dW3":dW3, "dB3":dB3, "dW2": dW2, "dB2": dB2, "dW1": dW1, "dB1": dB1}
2.如何小批次加载数据?
- 209个样本;
- 一次输入50个样本;
- 总输入5次;
#数据
class Dataset:
mean = [0.4413, 0.4244, 0.3560]
std = [0.26870, 0.2512, 0.2685]
def __init__(self,train_patch,test_patch):
self.train_x=self.trainData["train_set_x"]
self.train_y=self.trainData["train_set_y"]
#小批量加载
def loader_data(self,batch_size):
#计算加载次数
"""
加载顺序:
0:50
50:100
100:150
...
共加载5次
"""
iter_num=(self.train_set.shape[0]-1+batch_size)//batch_size
for i in range(iter_num):
#每次怎么取
data=self.train_x[i*[batch_size]:(i+1)*batch_size]/255.#取数据
data=(data-self.mean)/self.std
data=self.train_y[i*[batch_size]:(i+1)*batch_size]#取标签
yield data,target#关键的一步。迭代。
#训练
def train(self):
for j ,(input,target) in enumerate(self.dataset.loader_data(batch_size=50)):
pass
注:小批量效果不一定好,因为一次学习大批量数据,确保一次能学习到很多特征。下面实验将数据打乱,检查效果好不。
3如何打乱数据?
数据没打乱,效果不好。例如:一组数据中前大半部分是猫的图片,后小半部分是狗的图片,学习过程如下所示(到狗时,学到的东西太少,正确率变为0%):
#实验
#生成随机数
data=np.arange(10)#生成一组数
print(data)
np.random.shuffle(data)#对原数组进行打乱
print(data)
#打印结果
[0 1 2 3 4 5 6 7 8 9]
[8 5 7 6 2 1 9 3 0 4]
#实现
"""此处略去前半部分代码"""
#打乱数据
def shuffle_data(self,data,target):
index=np.arange(209)
np.random.shuffle(index)
new_x=data[index]
new_y=target[index]
return new_x,new_y
def train(self):
#打乱数据进行训练
data,target=self.dataset.get_train_set()
losses=[]
for i in range(5000):
print("epochs:{}".format(i))
new_x,new_y=self.shuffle_data(data,target)
new_y=new_y[:,np.newaxis]#对标签进行变换
out=self.net(new_x)
loss=self.loss_func(out,new_y)
gards=loss.backword()
self.opt.step(gards)
if i % 4 == 0:
losses.append(loss.float())
print("{}/{},loss:{}".format(i, 5000, loss.float()))
plt.clf()
plt.plot(losses)
plt.pause(0.1)
nn.save(self.net,"models/net.pth")
标题4.one_hot制作
(1)第一种方式
import numpy as np
def to_one_hot(self, x, C):
target = np.zeros((x.shape[0], C))
#合适的位置填充为1
target[np.arange(x.shape[0]), x] = 1
return target
(2)第二种方式
import numpy as np
def to_one_hot2(self, x ,C):
# eye后的索引指定1该在什么位置
target = np.eye(x.shape[0],C)[x]
return target