1 第4章整体思路
神经网络的学习:神经网络存在合适的权重(w)和偏置(b),调整权重和偏置以便拟合训练数据的过程叫做“学习”;
个人理解:使用训练数据进行学习,调整参数,让模型预测得更准确,其中参数就是权重和偏置,准确度通过损失函数观察,该往什么方向调整通过损失函数的梯度决定;
神经网络学习步骤:mini-batch-->计算梯度-->更新参数;
2层神经网络代码实现思路:(TwoLayerNet类)
构造函数:提供初始化方法;
predict函数:传入x作为第1层input值,计算数组乘积(参数通过构造函数初始化),使用sigmoid激活函数([0,input])计算出中间值;中间值作为第二层的input值,计算数组乘积(参数通过构造函数初始化),使用softmax函数(化为0.0-1.0间的值)作为输出函数计算输出值y;
loss函数:返回CE损失函数计算后的值;
accuracy函数:每经过一个epoch,就计算精度
gradient函数/numerical_gradient函数:计算梯度
2 详细内容
导入部分:
常用的特征量SIFT,SURF,HOG;
常用的分类器SVM,KNN;
机器学习中,一般使用测试数据与训练数据两部分进行学习和实验。首先,使用训练数据学习,寻找最优的参数,然后使用测试数据评价训练得到的模型的实际能力,获得泛化能力(就是处理未被观察的数据的能力);
训练数据又叫做监督数据;
过拟合:对某个数据集过度拟合的状态;
损失函数
寻找最优参数时,要寻找使损失函数的值尽可能小的参数;寻找的过程通过损失函数的导数实现;如果导数值为负,通过使参数向正方向改变,可以减少Loss的值,若导数值为正,通过使参数向负方向改变,可以减少Loss的值。
从纯数学的角度是比较好理解的,我们要找某个函数最小值,可以求导数找驻点,然后计算最小值;当有正参数参与计算时,可以求导数,改变参数的值以获得最小值;
作者从正反两个方向回答了why的问题,即精度是和阶跃函数都不能产生连续的变化(都是离散的,比如100笔中识别成功20笔,永远是离散的数据),所以改参数没什么用,而建立函数,求导数,更能了解数据走向;我们做数学题也是这个方法,看来这是人类解决问题的一个重要方法;
损失函数-均方误差
E=½∑(y-t)² #y是output值,t是监督数据,k是维数
看上去是基于方差的实现思想,方差本来就是度量随机变量 和其数学期望 (即 均值 )之间的偏离程度。代码直接return公式就行;
损失函数-交叉熵误差CE
E=-∑tlogy #y是Output,t是正确解标签 就是自然对数取反,因为这样结果是正数
实际实现时,为了避免溢出,会在Log里加一个微小值参数delta;代码实现也是直接return公式;
mini-batch学习
就是求大量数据的Loss,然后求这些loss-value的均值;书中给出了CEloss的批次处理公式;
E=-1/n∑∑tlogy
代码实现:
import numpy as np
def cross_entropy_error(y, t):
if y.ndim == 1:#y.ndim维度
t = t.reshape(1, t.size)#转换成1行size列
y = y.reshape(1, y.size)#转换成1行size列
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size:
t = t.argmax(axis=1)#数组最大索引号
batch_size = y.shape[0]#这种写法是行数
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
#公式照抄 range单参数:0-batchsize....
梯度导入-数值微分-导数
数值微分 numerical differentiation
导数表示瞬间变化量,数学中通常是取极限;计算机不能直接求极限,实现方式是选择一个比较小的值,并且将计算方法转化成中心差分的形式计算,有误差是肯定的;
数值微分有误差,为了减少误差可以计算函数的差分。利用微小的差分求导数的过程叫做数值微分。使用过小的值会造成计算机出现溢出的问题(浮点数都是有位数的),所以微小值要选择合适的值;
梯度导入-数值微分-偏导数
有多个变量的函数的导数叫偏导数
书中给出的是固定一个变量,另一个变量转化成常量用求导数的方法求偏导;
梯度
由全部变量的偏导数汇总而成的变量称为梯度;
实际上,梯度会指向各处的函数值降低的方向。更严格地讲,梯度指示的方向是各点出的函数值减少最多的方向。
从代码上看,就是return了导数的数组;
代码实现:
#梯度
def numerical_gradient(f,x):
h = 1e-4;#0.0001
grad = np.zeros_like(x) #生成和x相同的数组
for idx in range(x.size):
tmp_val = x[idx]
x[idx] = tmp_val + h
fxh1 = f(x)
x[idx] = tmp_val - h
fxh2 = f(x)#这个写法好神奇啊
grad[idx] = (fxh1-fxh2)/(2*h)#差分公式
x[idx] = tmp_val
return grad
def function_2(x):
return x[0]**2+x[1]**2#平方之和
grad = test.numerical_gradient(function_2,np.array([3.0,4.0]))
print(grad)
D:\software\Anaconda\python.exe D:\workspace_p\study\ch04\test.py
[6. 8.]
[6. 8.]
Process finished with exit code 0
找最优参数是指损失函数最小值的参数。一般损失函数很复杂,使用梯度来寻找函数最小值的方法叫做梯度法;梯度为0的地方不一定是最小资,且当函数复杂并且呈扁平状时,学习可能进入停滞期;
虽然梯度不一定指向最小值,但是沿着梯度的方向可以最大限度减少函数的值。
梯度法中,函数的取值从当前位置沿着梯度前进,然后在新的地方重新求梯度,如此反复(类似迭代)。梯度法有梯度上升法和梯度下降法,梯度法主要是指下降法;
梯度法代码实现:
#梯度下降法
def gradient_descent(f,init_x,lr=0.01,step_num=100):#lr 学习率
x = init_x;
for i in range(step_num):
grad = numerical_gradient(f,x)
x -= lr*grad#梯度法公式 x0-lr*偏导 就是让x动起来
return x
#发现函数中可以不写文件名,但是在外部调用必须写
init_x = np.array([-3.0,4.0])#总是忘加[]
grad = test.gradient_descent(function_2,init_x=init_x,lr=0.1,step_num=100)
print(grad)
D:\software\Anaconda\python.exe D:\workspace_p\study\ch04\test.py
[-6.11110793e-10 8.14814391e-10]
[-6.11110793e-10 8.14814391e-10]
Process finished with exit code 0
学习率过大的话,会发散成很大的值,学习率过小,基本上没有怎么更新就结束了;像学习率这样的参数叫做超参数,是人工设定的;
代码实现(这章的代码对于我来说有点难以理解了,所以加了些注释):
两层神经网络:
# coding: utf-8
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):
# 初始化权重
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)#创建1维数组 元素个数output_size
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)#cout[0,x]
a2 = np.dot(z1, W2) + b2
y = softmax(a2)#cout 0.0-1.0 带e的那个公式
return y
# 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
# coding: utf-8
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 = 1000 # 适当设定循环的次数
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)
#numpy.random.choice(a, size=None, replace=True, p=None)
#从a(只要是ndarray都可以,但必须是一维的)中随机抽取数字,并组成指定大小(size)的数组
#replace:True表示可以取相同数字,False表示不可以取相同数字
#数组p:与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
for i in range(iters_num):#10000
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]#dataset里的
t_batch = t_train[batch_mask]
# 计算梯度
#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: #max(train_size / batch_size, 1) 每经过一个epoch,就计算识别精度
train_acc = network.accuracy(x_train, t_train)#成功数据百分比
test_acc = network.accuracy(x_test, t_test)
train_acc_list.append(train_acc)#放入train_acc数组
test_acc_list.append(test_acc)#放入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()
end