文章目录
第4章 神经网络的学习
本章的主题是神经网络的学习。这里所说的“学习”是指从训练数据中 自动获取最优权重参数的过程。本章中,为了使神经网络能进行学习,将导 入损失函数这一指标。而学习的目的就是以该损失函数为基准,找出能使它 的值达到最小的权重参数。为了找出尽可能小的损失函数的值,本章我们将 介绍利用了函数斜率的梯度法
4.1 从数据中学习
神经网络的特征就是可以从数据中学习。
本章将 介绍神经网络的学习,即利用数据决定参数值的方法,并用Python实现对 MNIST手写数字数据集的学习。
4.1.1 数据驱动
数据是机器学习的核心。
神经网络的优点是对所有的问题都可以用同样的流程来解决。
比如,不 管要求解的问题是识别5,还是识别狗,抑或是识别人脸,神经网络都是通 过不断地学习所提供的数据,尝试发现待求解的问题的模式。也就是说,与 待处理的问题无关,神经网络可以将数据直接作为原始数据,进行“端对端” 的学习
4.1.2 训练数据和测试数据
机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和 实验等。首先,使用训练数据进行学习,寻找最优的参数;然后,使用测试 数据评价训练得到的模型的实际能力。
为什么需要将数据分为训练数据和测 试数据呢?因为我们追求的是模型的泛化能力。为了正确评价模型的泛化能 力,就必须划分训练数据和测试数据。另外,训练数据也可以称为监督数据。
泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的 能力。获得泛化能力是机器学习的最终目标。
4.2 损失函数
这个损失函数可以使用任意函数, 但一般用均方误差和交叉熵误差等。
4.2.1 均方误差
def mean_squared_error(y, t):
return 0.5 * np.sum((y-t)**2)
解释:
>>> # 设“2”为正确解
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
>>>
>>> # 例1:“2”的概率最高的情况(0.6)
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> mean_squared_error(np.array(y), np.array(t))
0.097500000000000031
>>>
>>> # 例2:“7”的概率最高的情况(0.6)
#正确解是“2”,神经网络的输出的最大值是“7”。
>>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
>>> mean_squared_error(np.array(y), np.array(t))
0.59750000000000003
我们发现第一个例子的损失函数的值更小,和监督数据之间的 误差较小。也就是说,均方误差显示第一个例子的输出结果与监督数据更加吻合。
4.2.2 交叉熵误差
假设 正确解标签的索引是“2”,与之对应的神经网络的输出是0.6,则交叉熵误差 是−log 0.6 = 0.51;若“2”对应的输出是0.1,则交叉熵误差为−log 0.1 = 2.30。 也就是说,交叉熵误差的值是由正确解标签所对应的输出结果决定的。
def cross_entropy_error(y, t):
delta = 1e-7 #10的负7次方
return -np.sum(t * np.log(y + delta))
参数y和t是NumPy数组。函数内部在计算np.log时,加上了一 个微小值delta。
是因为,当出现np.log(0)时,np.log(0)会变为负无限大,作为保护性对策,添加一个 微小值可以防止负无限大的发生。
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> cross_entropy_error(np.array(y), np.array(t))
0.51082545709933802
>>>
>>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
>>> cross_entropy_error(np.array(y), np.array(t))
2.3025840929945458
第一个例子中,正确解标签对应的输出为0.6,此时的交叉熵误差大约 为0.51。第二个例子中,正确解标签对应的输出为0.1的低值,此时的交叉 熵误差大约为2.3。
4.2.3 mini-batch学习
机器学习使用训练数据进行学习。使用训练数据进行学习,严格来说, 就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。
计算损失函数时必须将所有的训练数据作为对象。也就是说,如果训练数据 有100个的话,我们就要把这100个损失函数的总和作为学习的指标。
train_size = x_train.shape[0] #行数 60000
batch_size = 10
#从train_size随机挑选出10个数,(这个数是第几行)
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask] #这10个图片,这个输出的shape是(10,784)
t_batch = t_train[batch_mask] #对应标签,输出的shape是(10,10)
使用np.random.choice()可以从指定的数字中随机选择想要的数字。比如, np.random.choice(60000, 10)会从0到59999之间随机选择10个数字。如下 面的实际代码所示,我们可以得到一个包含被选数据的索引的数组。
>>> np.random.choice(60000, 10)
array([ 8013, 14666, 58210, 23832, 52091, 10153, 8107, 19410, 27260,21411])
我们只需指定这些随机选出的索引,取出mini-batch,然后使用 这个mini-batch计算损失函数即可。
4.2.4 mini-batch版交叉熵误差的实现
这里,我们来实现一个可以同时处理单 个数据和批量数据(数据作为batch集中输入)两种情况的函数。
def cross_entropy_error(y, t):
#这是 为了防止y=([1,2,3])这是个一维,但是y.shape是(3,)
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0] #代表y总共有多少行
return -np.sum(t * np.log(y + 1e-7)) / batch_size
这里,y是神经网络的输出,t是监督数据。y的维度为1时,即求单个 数据的交叉熵误差时,需要改变数据的形状。并且,当输入为mini-batch时, 要用batch的个数进行正规化,计算单个数据的平均交叉熵误差。
此外,当监督数据是标签形式**(非one-hot表示,而是像“2”“7”这样的 标签)**时,交叉熵误差可通过如下代码实现
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
因此,t为one-hot表示时通过 t * np.log(y)计算的地方,在t为标签形式时,可用np.log( y[np.arange (batch_size), t] )实现相同的处理(为了便于观察,这里省略了微小值1e-7)
作为参考,简单介绍一下np.log( y[np.arange(batch_size), t] )。np.arange (batch_size)会生成一个从0到batch_size-1的数组。比如当batch_size为5 时,
np.arange(batch_size)会生成一个NumPy 数组[0, 1, 2, 3, 4]。因为t中标签是以[2, 7, 0, 9, 4]的形式存储的,所以y[np.arange(batch_size), t]能抽出各个数据的正
确解标签对应的神经网络的输出(在这个例子中, y[np.arange(batch_size), t] 会生成 NumPy 数 组 [y[0,2], y[1,7], y[2,0], y[3,9], y[4,4]])
4.3 数值微分
4.3.1 导数
函数
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
利用微小的差分求导数的过程称为数值微分(numerical differentiation)。而基于数学式的推导求导数的过程,则用“解析 性”(analytic)一词,称为“解析性求解”或者“解析性求导”。
4.3.2 数值微分的例子
def function_1(x):
return 0.01*x**2 + 0.1*x
4.3.3 偏导数
def function_2(x):
return x[0]**2 + x[1]**2
# 或者return np.sum(x**2)
4.4 梯度
代码:
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和x形状相同的数组
#先对x[0]求导,再对x[1]求导
for idx in range(x.size):
tmp_val = x[idx] #
# f(x+h)的计算
x[idx] = tmp_val + h
fxh1 = f(x) # fxh1=f(x+h)
# f(x-h)的计算
x[idx] = tmp_val - h
fxh2 = f(x) # fxh1=f(x-h)
#求出一个导数存入到grad中
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
return grad
例如grad([0,0]),第一个0存x[0]的偏导数。第一个0存x[1]的偏导数
解释:
def function_2(x):
return x[0]**2 + x[1]**2
>>> numerical_gradient(function_2, np.array([3.0, 4.0]))
array([ 6., 8.]) #求导后:2x[0]+2x[1]=2*3+2*4
>>> numerical_gradient(function_2, np.array([0.0, 2.0]))
array([ 0., 4.])
>>> numerical_gradient(function_2, np.array([3.0, 0.0]))
array([ 6., 0.]
4.4.1 梯度法
机器学习的主要任务是在学习时寻找最优参数
神经网络也必须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数取最小值时的参数。
**梯度表示的是各点处的函数值减小最多的方向。**因此, 无法保证梯度所指的方向就是函数的最小值或者真正应该前进的方向。
函数的极小值、最小值以及被称为鞍点(saddle point)的地方, 梯度为 0。
通过不断地沿梯度方向前进, 逐渐减小函数值的过程就是梯度法
寻找最小值的梯度法称为梯度下降法(gradient descent method), 寻找最大值的梯度法称为梯度上升法(gradient ascent method)。
一般来说,神经网络(深度学习)中,梯度法主要是指梯度下降法
Python来实现梯度下降法:
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x #我们输入的x的值
for i in range(step_num):
grad = numerical_gradient(f, x) #numerical_gradient:就是f函数求出x处对应的导数
x -= lr * grad
return x
参数f是要进行最优化的函数,init_x是初始值,lr是学习率learning rate,step_num是梯度法的重复次数。numerical_gradient(f,x)会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由step_num指定重复的次数
解释函数:
>>> def function_2(x):
... return x[0]**2 + x[1]**2
...
>>> init_x = np.array([-3.0, 4.0])
>>> gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)
array([ -6.11110793e-10, 8.14814391e-10])
这里,设初始值为(-3.0, 4.0),开始使用梯度法寻找最小值。最终的结 果是(-6.1e-10, 8.1e-10),非常接近(0,0)。实际上,真的最小值就是(0,0), 所以说通过梯度法我们基本得到了正确结果。
4.4.2 神经网络的梯度
下面,我们以一个简单的神经网络为例,来实现求梯度的代码。为此, 我们要实现一个名为simpleNet的类
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
#防止w每次去的时候都变化
def f(W):
return net.loss(x, t)
#为了对应形状为多维数组的权重参数W,这里使用的 numerical_gradient()和之前的实现稍有不同。
#求的是偏导数,梯度
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
#nditer:相当于把其变成了一维从第一行完了,接着第二行
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index #multi_index指针所指的当前这个数的位置比如(0,1)第一行第二个数,起始:idx=(0,0)
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
it.iternext() #指针指向下一个数
return grad
#损失函数
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
#交叉熵误差
def cross_entropy_error(y, t):
#这是 为了防止y=([1,2,3])这是个一维,但是y.shape是(3,)
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0] #代表y总共有多少行
return -np.sum(t * np.log(y + 1e-7)) / batch_size
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 用高斯分布进行初始化
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t) #当前损失的值
return loss
函数解释
>>> net = simpleNet()
>>> print(net.W) # 权重参数 # self.W = np.random.randn(2,3) 随机生成的如下
[[ 0.47355232 0.9977393 0.84668094],
[ 0.85557411 0.03563661 0.69422093]]
>>>
>>> x = np.array([0.6, 0.9]) #我们输入的值
>>> p = net.predict(x)
>>> print(p)
[ 1.05414809 0.63071653 1.1328074]
>>> np.argmax(p) # 最大值的索引,就是最大值在那个位置
2
>>>
>>> t = np.array([0, 0, 1]) # 正确解标签
>>> net.loss(x, t)
0.92806853663411326
>>> def f(W):
... return net.loss(x, t)
...
#为了对应形状为多维数组的权重参数W,这里使用的 numerical_gradient()和之前的实现稍有不同。
>>> dW = numerical_gradient(f, net.W) #梯度函数,梯度函数比起前有所变化
>>> print(dW)
[[ 0.21924763 0.14356247 -0.36281009]
[ 0.32887144 0.2153437 -0.54421514]]
4.5 学习算法的实现
神经网络学习的基础知识,到这里就全部介绍完了。“损失函 数”“mini-batch”“梯度”“梯度下降法”等关键词已经陆续登场,
4.5.1 2层神经网络的类
首先,我们将这个2层神经网络实现为一个名为TwoLayerNet的类,实现 过程如下所示
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
from common.functions import *
from common.gradient import numerical_gradient
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
#input_size:输入层(例如:758) hidden_size:隐藏层 (例如:50) output_size:输出层 (例如:10)
#weight_init_std小参数
# 初始化权重
self.params = {}
# w1:输入层到第一层的w取随机值,b1同,(758,50)
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
# w2:第一层到输出层的w取随机值,b2同
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
#这里求出y的值
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
#这里求出loss
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
#求精度的
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis=1)
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
函数解释
#1
net = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)
#输入的是(1,784)
net.params['W1'].shape # (784, 100)
net.params['b1'].shape # (100,)
net.params['W2'].shape # (100, 10)
net.params['b2'].shape # (10,)
#输出(1*10)
#2
#随机输入
x = np.random.rand(100, 784) # 伪输入数据(100笔)
t = np.random.rand(100, 10) # 伪正确解标签(100笔)
grads = net.numerical_gradient(x, t) # 计算梯度
grads['W1'].shape # (784, 100)
grads['b1'].shape # (100,)
grads['W2'].shape # (100, 10)
grads['b2'].shape # (10,)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vFnjmBNy-1638088769770)(G:\笔记\深度学习:基于python入门\新建文件夹\12.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5R6qM02U-1638088769770)(C:\Users\94323\AppData\Roaming\Typora\typora-user-images\image-20211128152459223.png)]
TwoLayerNet类有params和grads两个字典型实例变量。params变量中保存 了权重参数,比如params[‘W1’]以NumPy数组的形式保存了第1层的权重参 数。此外,第1层的偏置可以通过param[‘b1’]进行访问。
4.5.2 mini-batch的实现
下面,我们就以 TwoLayerNet类为对象,使用MNIST数据集进行学习
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据,调用了库
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
iters_num = 10000 # 适当设定循环的次数
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
#i循环的次数10000次
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size) #从训练集中随机取batch_size(100)个
x_batch = x_train[batch_mask] #shape为(100,784)
t_batch = t_train[batch_mask] #shape为(100,10)
# 计算梯度
#grad = network.numerical_gradient(x_batch, t_batch)
grad = network.gradient(x_batch, t_batch)
# 更新参数
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
#记录学习过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uFPpzAZE-1638088769772)(C:\Users\94323\AppData\Roaming\Typora\typora-user-images\image-20211128155853826.png)]
# -*- codeing = utf-8 -*-
# @Time : 2021/11/28
# @Author :晓晓
# @File : 第四章.py
# @software: PyCharm
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
from common.functions import *
from common.gradient import numerical_gradient
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
# nditer:相当于把其变成了一维从第一行完了,接着第二行
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index # multi_index指针所指的当前这个数的位置比如(0,1)第一行第二个数,起始:idx=(0,0)
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val # 还原值
it.iternext() # 指针指向下一个数
return grad
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# input_size:输入层(例如:758) hidden_size:隐藏层 (例如:50) output_size:输出层 (例如:10)
# weight_init_std小参数
# 初始化权重
self.params = {}
# w1:输入层到第一层的w取随机值,b1同,(758,50)
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
# w2:第一层到输出层的w取随机值,b2同
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
# 这里求出y的值
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
return y
# 这里求出loss,越接近正确答案值越小
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t)
# 求精度的
def accuracy(self, x, t):
y = self.predict(x) #得到预测值,例如[0.1,0.2,0.3,0.5]
y = np.argmax(y, axis=1) #当axis=1,是在行中比较,选出最大的 列 索引
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t) #这是个函数
#>> def f(W):
#return net.loss(x, t)
#这个函数得出 f = lambda w: net.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
4.6 小结
这是封装的类
# -*- codeing = utf-8 -*-
# @Time : 2021/11/28
# @Author :晓晓
# @File : demo04.py
# @software: PyCharm
import sys, os
import numpy as np
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
from common.functions import *
from common.gradient import numerical_gradient
#激活函数,进行信号的转换,信号被传送给下一个神经元。
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
#交叉熵误差 ,输出值越小代表越精确
def cross_entropy_error(y, t):
#这是 为了防止y=([1,2,3])这是个一维,但是y.shape是(3,)
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0] #代表y总共有多少行
return -np.sum(t * np.log(y + 1e-7)) / batch_size
#梯度下降,算出来的是导数
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
# nditer:相当于把其变成了一维从第一行完了,接着第二行
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index # multi_index指针所指的当前这个数的位置比如(0,1)第一行第二个数,起始:idx=(0,0)
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2 * h)
x[idx] = tmp_val # 还原值
it.iternext() # 指针指向下一个数
return grad
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# input_size:输入层(例如:758) hidden_size:隐藏层 (例如:50) output_size:输出层 (例如:10)
# weight_init_std小参数
# 初始化权重
self.params = {}
# w1:输入层到第一层的w取随机值,b1同,(758,50)
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
# w2:第一层到输出层的w取随机值,b2同
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
# 这里求出y的值
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2) # 例如[0.1,0.2,0.3,0.5],输出每个的概率
return y
# 这里求出loss,越接近正确答案值越小,
#损失函数
# x:输入数据, t:监督数据
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y, t) #输出值越小代表越精确
# 求精度的
def accuracy(self, x, t):
y = self.predict(x) #得到预测值,例如[0.1,0.2,0.3,0.5]
y = np.argmax(y, axis=1) #当axis=1,是在行中比较,选出最大的 列 索引
t = np.argmax(t, axis=1)
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# x:输入数据, t:监督数据
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t) #这是个函数
#>> def f(W):
#return net.loss(x, t)
#这个函数得出 f = lambda w: net.loss(x, t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
函数调用
import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据,调用了库
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
iters_num = 10000 # 适当设定循环的次数
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1 #学习率
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
#i循环的次数10000次
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size) #从训练集中随机取batch_size(100)个
x_batch = x_train[batch_mask] #shape为(100,784)
t_batch = t_train[batch_mask] #shape为(100,10)
# 计算梯度
grad = network.numerical_gradient(x_batch, t_batch)
#grad = network.gradient(x_batch, t_batch)
# 更新参数
#用计算出来的梯度更新w和b的值
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
#记录学习过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()