4、验证正确性、训练网络

知识补充

在上一篇博客中,我们通过手算的方式完成了梯度下降的推导过程,接下来我们将用代码来实现梯度下降,即对参数进行训练。

1、写出softmax导数函数
在这里插入图片描述
diag:对角矩阵
在这里插入图片描述
outer:第一个参数挨个乘以第二个参数得到矩阵。
在这里插入图片描述

def d_softmax(data):
    sm=softmax(data)
    return np.diag(sm)-np.outer(sm,sm)

注意:可能有人会好奇第一项为什么是对角矩阵。首先我们知道对角矩阵里的参数是一个softmax,softmax是这样子的:在这里插入图片描述
返回的是一个向量。而我们现在是对向量进行求导,结合上一个博客中向量求导的知识点,我们可以用对角矩阵来完成。

2、写出tanh导数函数
在这里插入图片描述

def d_tanh(data):
	return np.diag(1/(np.cosh(data))**2)

同样用对角矩阵来实现。
3、验证求导结果
我们虽然根据手算结果写出来了tanh的求导函数和softmax的求导函数,但是万一我们手算结果是错的怎么办?为了避免这个意外,我们接下来将对求导结果进行验证。让我们回到导数的定义:
在这里插入图片描述
在这里插入图片描述

那么我们只要验证1式≈2式就可以了。
首先我们先用字典表示一下两个函数的微分(differential),方便代码的书写,只需要把key换一下就是。

differential={
    
    softmax:d_softmax,tanh:d_tanh}

对d_softmax进行验证:

h=0.0001
func=softmax
input_len=4 #随机数组的长度
for i in range(input_len):#循环,多次验证,让结果更具有说服力
    test_input=np.random.rand(input_len) #产生随机数组
    derivative=differential[func](test_input) #导数 即1式
    value1=func(test_input)#2式
    test_input[i]+=h
    value2=func(test_input)
    print(derivative[i]-(value2-value1)/h)

结果如下:
在这里插入图片描述
可以看出之差在10的-6和10的-7级别,可以忽略不计,说明手算结果是正确的。

注意:可以会不理解这个随机数组是怎么来的。是利用了np.random.rand()函数。如果只要一个参数,这个函数会返回有参数个元素的数组,这些元素是随机[0,1)之间的数据,包含0,不包含1。
在这里插入图片描述

对d_tanh进行验证:
跟softmax一样的,在func那里改一下key就行了。

h=0.00001
func=tanh
input_len=4
for i in range(input_len):
    test_input=np.random.rand(input_len)
    derivative=differential[func](test_input)
    value1=func(test_input)
    test_input[i]+=h
    value2=func(test_input)
    print(derivative[i]-(value2-value1)/h)

结果如下:
在这里插入图片描述
由于是对角矩阵,只在对角线上有值,所有对角线上的差值在10的-6级别,可以验证tanh导函数的正确性。
4、解析label
因为一张图片对应于一个标签,而标签又是一个数。所以我们将数解析为某一位置为1的一维矩阵。

dimensions = [28*28,10]
onehot=np.identity(dimensions[-1])

np.identity()函数是创建一个方阵,dimensions[-1]是该列表的最后一项。比如标签是3,那我只要取出方阵里的第四行(从0开始)出来就可以了。
在这里插入图片描述

5、求平方差函数
这里就是求损失函数loss function:(y-y_pred)**2

#预测结果
def predict(img,parameters):
	l0_in = img+parameters[0]['b']
	l0_out = activation[0](l0_in)
	l1_in = np.dot(l0_out,parameters[1]['w'])+parameters[1]['b']
	l1_out = activation[1](l1_in)
	return l1_out
#平方差函数
def sqr_loss(img,lab,parameters):
	y_pred = predict(img,parameters)
	y = onehot[lab]
	diff = y-y_pred
	return np.dot(diff,diff)

注意:求平方差的时候没有用平方而是用dot(内积)表示,这样是为了减少计算量,提高计算速度,实质是一样的。y_pred是一个一维数组:
在这里插入图片描述
y也是一维数组,两个一维数组作差,diff也是一维数组。np.dot()函数就是内积运算。对于两个行列相同的向量a,b:
在这里插入图片描述
a和b的点积公式为:
在这里插入图片描述
所以,也是平方的意思。

6、计算梯度
由我们手算出的结果进行代码实现:
在这里插入图片描述

#为了计算方便,d_tanh不用对角矩阵表示,也是为了向量的维数一致。
def d_tanh(data):
	return 1/(np.cosh(data))**2

def grad_parameters(img,lab,parameters):
    l0_in = img+parameters[0]['b']
    l0_out = activation[0](l0_in)
    l1_in = np.dot(l0_out,parameters[1]['w'])+parameters[1]['b']
    l1_out = activation[1](l1_in)

    diff = onehot[lab]-l1_out
    #将(y-y_pred)*d_somax(L1)提出来,就不用每一次都计算
    act1 = np.dot(differential[activation[1]](l1_in),diff)
	# 与上文优化d_tanh有关,将矩阵乘法化为数组乘以矩阵
	grad_b0 = -2*differential[activation[0]](l0_in)*np.dot(parameters[1]['w'],act1)
    grad_b1 = -2*act1
    grad_w1 = -2*np.outer(l0_out,act1)

    return {
    
    'b0':grad_b0,'b1':grad_b1,'w1':grad_w1}

7、验证梯度
同样,这梯度是我们手算出来的,为了避免错误,还是要验证一下。同上面的验证求导结果一样,换一句话说就是对三个参数进行求导验算。

#b0
h=0.0001
grad_list=[]
for i in range(784):
    img_i=np.random.randint(train_num)
    test_parameters=init_parameters()
    derivative=grad_parameters(train_img[img_i],train_lab[img_i],test_parameters)['b0']
    value1=sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
    test_parameters[0]['b'][i]+=h
    value2=sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
    grad_list.append(derivative[i]-(value2-value1)/h)
np.abs(grad_list).max()
#b1
h=0.0001
for i in range(10):
    img_i=np.random.randint(train_num)
    test_parameters=init_parameters()
    derivative=grad_parameters(train_img[img_i],train_lab[img_i],test_parameters)['b1']
    value1=sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
    test_parameters[1]['b'][i]+=h
    value2=sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
    print(derivative[i]-(value2-value1)/h) 
#w1
h=0.001
grad_list=[]
for i in range(784):
    for j in range(10):
        img_i=np.random.randint(train_num)
        test_parameters=init_parameters()
        derivative=grad_parameters(train_img[img_i],train_lab[img_i],test_parameters)['w1']
        value1=sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
        test_parameters[1]['w'][i][j]+=h
        value2=sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
        grad_list.append(derivative[i][j]-(value2-value1)/h)
np.abs(grad_list).max()

由于b0,w1的数据量大,所以要把结果放入一个列表中,通过求列表绝对值的最大值来说明误差结果。

7、验证(valid)集的精确度

#对验证集的误差进行累加
def valid_loss(parameters):
    loss_accu = 0
    for img_i in range(valid_num):
        loss_accu+=sqr_loss(valid_img[img_i],valid_lab[img_i],parameters)
    return loss_accu
#求验证集的精确度
#当预测出的结果的正确率    
def valid_accuracy(parameters):
    correct = [predict(valid_img[img_i],parameters).argmax()==valid_lab[img_i] for img_i in range(valid_num) ]
    print("validation accuracy:{}".format(correct.count(True)/len(correct)))

argmax()函数返回的是最大数的索引.argmax有一个参数axis,默认是0。
axis=1:按行查找最大元素 axis=0:按列查找最大元素

8、平均梯度
因为数据集里的数据太多了,例如训练集里的数据有50000个,一个数据一个数据的训练太麻烦了。所以我们要进行分组(batch)训练,降低计算量。设每组的大小batch_size为100,那就将训练集就分成了500组,train_batch=500。

batch_size=100
def train_batch(current_batch,parameters):
	#初始化梯度,从每一组的第0张图片开始
    grad_accu = grad_parameters(train_img[current_batch*batch_size+0],train_lab[current_batch*batch_size+0],parameters)
    	#对每一张图片的梯度进行累加
    for img_i in range(1,batch_size):
        grad_tmp = grad_parameters(train_img[current_batch*batch_size+img_i],train_lab[current_batch*batch_size+img_i],parameters)
        for key in grad_accu.keys():
            grad_accu[key] += grad_tmp[key]
    #对梯度进行一个平均,也就是这一组图片的平均梯度
    for key in grad_accu.keys():
        grad_accu[key]/=batch_size
    return grad_accu

8、梯度优化
通过学习率来不断进行优化

import copy #导入copy模块
def combine_parameters(parameters,grad,learn_rate):
    parameter_tmp =copy.deepcopy(parameters)
    parameter_tmp[0]['b']-=learn_rate*grad['b0']
    parameter_tmp[1]['b']-=learn_rate*grad['b1']
    parameter_tmp[1]['w']-=learn_rate*grad['w1']
    return parameter_tmp

copy.deepcopy 深拷贝 拷贝对象及其子对象。这里深拷贝就是给parameters创建一个副本,防止在训练的过程中,参数进行改变,引发参数错误。

9、训练

#对参数进行初始化
parameters=init_parameters()
learn_rate=1#暂时设为1
#对每一组进行训练
for i in range(train_num//batch_size):
    if i%100==99:
        print('running batch {}/{}'.format(i+1,train_num//batch_size))
    gard_tmp=train_batch(i,parameters)
    parameters=combine_parameters(parameters,gard_tmp,1)

训练过程:
在这里插入图片描述
10、训练结果

valid_accuracy(parameters)

在这里插入图片描述
一开始的精确度只有10%,但是经过第一次训练后达到了82.4%

第二次:
在这里插入图片描述
第三次:
在这里插入图片描述
由此,通过不断的训练,验证集的精确度在不断上升。

代码解释

# softmax导数函数
def d_softmax(data):
	sm = softmax(data)
	# diag:对角矩阵  outer:第一个参数挨个乘以第二个参数得到矩阵
	return np.diag(sm)-np.outer(sm,sm)

# tanh导数函数
# def d_tanh(data):
# 	return np.diag(1/(np.cosh(data))**2)
# tanh导数函数优化:
def d_tanh(data):
	return 1/(np.cosh(data))**2

differential = {
    
    softmax:d_softmax,tanh:d_tanh}
#验证导函数结果
#softmax
h=0.0001
func=softmax
input_len=4
for i in range(input_len):
    test_input=np.random.rand(input_len)
    derivative=differential[func](test_input)
    value1=func(test_input)
    test_input[i]+=h
    value2=func(test_input)
    print(derivative[i]-(value2-value1)/h)
#tanh
h=0.00001
func=tanh
input_len=4
for i in range(input_len):
    test_input=np.random.rand(input_len)
    derivative=differential[func](test_input)
    value1=func(test_input)
    test_input[i]+=h
    value2=func(test_input)
    print(derivative[i]-(value2-value1)/h)
# lab解析函数
# 将数解析为某一位置为1的一维矩阵
onehot = np.identity(dimensions[-1])

# 求平方差函数
def sqr_loss(img,lab,parameters):
	y_pred = predict(img,parameters)
	y = onehot[lab]
	diff = y-y_pred
	return np.dot(diff,diff)

# 计算梯度
def grad_parameters(img,lab,init_parameters):
	l0_in = img+parameters[0]['b']
	l0_out = activation[0](l0_in)
	l1_in = np.dot(l0_out,parameters[1]['w'])+parameters[1]['b']
	l1_out = activation[1](l1_in)
	
	diff = onehot[lab]-l1_out
	act1 = np.dot(differential[activation[1]](l1_in),diff)
	
	# 与上文优化d_tanh有关,将矩阵乘法化为数组乘以矩阵
	grad_b0 = -2*differential[activation[0]](l0_in)*np.dot(parameters[1]['w'],act1)
	grad_b1 = -2*act1
	grad_w1 = -2*np.outer(l0_out,act1)

	return {
    
    'b0':grad_b0,'b1':grad_b1,'w1':grad_w1}

#验证参数梯度
# test b1
def test_b1(h):
	for i in range(10):
		img_i = np.random.randint(train_num)
		test_parameters = init_parameters()
		derivative = grad_parameters(train_img[img_i],train_lab[img_i],test_parameters)['b1']
		value1 = sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
		test_parameters[1]['b'][i]+=h
		value2 = sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
		print(derivative[i]-(value2-value1)/h)

# test b0
def test_b0(h):
	grad_list = []
	for i in range(784):
		img_i = np.random.randint(train_num)
		test_parameters = init_parameters()
		derivative = grad_parameters(train_img[img_i],train_lab[img_i],test_parameters)['b0']
		value1 = sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
		test_parameters[0]['b'][i]+=h
		value2 = sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
		grad_list.append(derivative[i]-(value2-value1)/h)
	return grad_list

# test w1
def test_w1(h):
	grad_list = []
	for i in range(784):
		for j in range(10):
			img_i = np.random.randint(train_num)
			test_parameters = init_parameters()
			derivative = grad_parameters(train_img[img_i],train_lab[img_i],test_parameters)['w1']
			value1 = sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
			test_parameters[1]['w'][i][j]+=h
			value2 = sqr_loss(train_img[img_i],train_lab[img_i],test_parameters)
			grad_list.append(derivative[i][j]-(value2-value1)/h)
	return grad_list

def valid_loss(parameters):
	loss_accu = 0
	for img_i in range(valid_num):
		loss_accu+=sqr_loss(valid_img[img_i],valid_lab[img_i],parameters)
	return loss_accu

# valid 集的精确度
def valid_loss(parameters):
    loss_accu = 0
    for img_i in range(valid_num):
        loss_accu+=sqr_loss(valid_img[img_i],valid_lab[img_i],parameters)
    return loss_accu
def valid_accuracy(parameters):
    correct = [predict(valid_img[img_i],parameters).argmax()==valid_lab[img_i] for img_i in range(valid_num) ]
    print("validation accuracy:{}".format(correct.count(True)/len(correct)))
   
#平均梯度 
batch_size=100# 每组个数
def train_batch(current_batch,parameters):
	grad_accu = grad_parameters(train_img[current_batch*batch_size+0],train_lab[current_batch*batch_size+0],parameters)
	for img_i in range(1,batch_size):
		grad_tmp = grad_parameters(train_img[current_batch*batch_size+img_i],train_lab[current_batch*batch_size+img_i],parameters)
		for key in grad_accu.keys():
			grad_accu[key] += grad_tmp[key]
	for key in grad_accu.keys():
		grad_accu[key]/=batch_size
	return grad_accu
#梯度优化
def combine_parameters(parameters,grad,learn_rate):
	parameter_tmp = copy.deepcopy(parameters)
	parameter_tmp[0]['b'] -= learn_rate*grad['b0']
	parameter_tmp[1]['b'] -= learn_rate*grad['b1']
	parameter_tmp[1]['w'] -= learn_rate*grad['w1']
	return parameter_tmp

#对参数进行初始化
parameters=init_parameters()
learn_rate=1#暂时设为1
#对每一组进行训练
for i in range(train_num//batch_size):
    if i%100==99:
        print('running batch {}/{}'.format(i+1,train_num//batch_size))
    gard_tmp=train_batch(i,parameters)
    parameters=combine_parameters(parameters,gard_tmp,1)
    
valid_accuracy(parameters)

每日一句:你只有走完必须走的路,才能过想过的生活。

猜你喜欢

转载自blog.csdn.net/fayoung3568/article/details/117903381