鱼书学习笔记
第一章 python入门
1.4.2 类
用户自己定义新的类,可以自己创建数据类型、类的函数和属性。
class 类名:
def __init__(self, 参数, ...): #构造函数
...
def 方法名1(self, 参数, ...): #方法1
__init__是进行初始化的方法,也称为构造函数,只有在生成类的实例时被调用一次。示例如下:
类Man生成了实例m,类的构造函数(初始化方法)接收参数name,然后用这个参数初始化实例变量self.name。实例变量是存储在各个实例中的变量,通过在self后面添加属性名来生成或访问实例变量。
class Man:
def __init__(self, name):
self.name = name
print("Initialized")
def hello(self):
print("Hello " + self.name + "!")
def goodbye(self):
print("Good-bye " + self.name + "!")
m = Man("David")
m.hello()
m.goodbye()
cmd运行python文件:
C:\Users\Air>python F:\blogs\鱼书\man.py
Initialized
Hello David!
Good-bye David!
1.5 numpy
numpy数组可以进行“对应元素的”加减乘除运算(前提为数组元素个数相同),还可以和单一数值(标量)组合运算。
x = np.array([1.0, 2.0, 3.0]) x-y x/2
一维数组——向量,二维数组——矩阵。可将一般化后的向量或矩阵统称为张量。

广播运算:
形状不同的数组之间的运算。


访问元素
- 索引访问:
![索引]()
- for语句访问:
![for语句]()
- 转为一维数组访问:
![转为数组]()
- 获取满足条件元素:
不等运算符后得到布尔型数组,利用它取出满足条件元素
![布尔型]()
第2章 感知机
2.1 是什么

$x_1$、$x_2$是输入信号,$y$是输出信号,$w_1$、$w_2$是权重,o是“神经元”或“节点”,它会计算传送过来的信号综合,超过“阈值$\theta$”才输出1,即神经元被激活。
2.2 简单逻辑电路
- 人思考感知机的构造模型,并把训练数据交给计算机,计算机学习确定合适的参数。
- 3个门电路只有参数(权重和阈值)不同。
2.2.1 与门
| $x_1$ | $x_2$ | $y$ |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
用感知机表示这个门就是确定能满足上表的参数值组合。比如($w_1$,$w_2$,$\theta$)取(0.5,0.5,0.7)。
2.2.2 与非门和或门
与非门:仅当$x_1$、$x_2$同时为1时输出0,其他输出1。比如($w_1$,$w_2$,$\theta$)取(-0.5,-0.5,-0.7),只需把与门的参数取反。
或门:只要有一个输入为1,就输出1。
2.3 感知机的实现

# 定义接收参数x1和x2的AND函数 与门
def AND(x1, x2):
# 初始化参数
w1, w2, theta = 0.5, 0.5, 0.7
tmp = x1*w1+x2*w2
if tmp > theta:
return 1
else:
return 0
AND(0, 0)

$w_1$、$w_2$是控制输入信号重要性的参数,偏置是调整神经元被激活的容易程度的参数。
## 导入权重和偏置,计算2.2式
import numpy as np
x = np.array([0,1])
w = np.array([0.5, 0.5])
b = -0.7
print(w*x)
# [0. 0.5]
print(np.sum(w*x))
# 0.5
print(np.sum(w*x)+b)
# -0.19999999999999996
## 与门
def AND(x1, x2):
x = np.array([x1, x2])
w = np.array([0.5, 0.5])
b = -0.7
tmp = np.sum(w*x) + b
if tmp <= 0:
return 0
else:
return 1
2.4 感知机局限性
2.4.1 异或门
异或门也称为逻辑异或电路,仅当有1个1时输出1。
- 或门
参数为(-0.5, 1.0, 1.0)
![或门示意图]()
- 异或门
无法用直线分割空间
![异或门示意图]()
2.4.2 线性和非线性
感知机的局限性在于它只能表示由一条直线分割的空间。曲线分割而成的空间称为非线性空间,直线分割而成的空间称为线性空间
2.5 多层感知机

上、下、右分别为与非门、或门、与门。
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
与门、或门是单层感知机,异或门是2层感知机。通过叠加层,感知机能更加灵活的表示,理论上还可以表示计算机进行的处理。

第3章 神经网络
3.1 从感知机到神经网络
图中网络一共由3层神经元构成,其中中间层也称为隐藏层,但实际上只有2层神经元有权重。

激活函数将输入信号的总和转换为输出。
感知机中神经元之间流动的是0或1的二元信号,而神经网络中流动的是连续的实数值信号。
朴素感知机指单层网络,指的是激活函数使用了阶跃函数的模型;
多层感知机指神经网络,即使用sigmoid函数等平滑的激活函数的多层网络。
3.2 激活函数
神经网络的激活函数必须使用非线性函数,若使用线性函数的话,加深神经网络的层数没有意义了。
例如:把$h(x)=c(x)$作为激活函数,添加隐藏层对应3层神经网络$y(x)=h(h(h(x)))$与$y(x)=a(x)$等价,其中$a=c^3$,无法发挥多层网络带来的优势。
-
阶跃函数
以0为界,输出从0切换为1,它的值呈阶梯式变化。## 阶跃函数 def step_function(x): if x > 0: return 1 else: return 0但是这里的参数x只能接受实数(浮点数),不允许取numpy数组,考虑如下操作:
import numpy as np def step_function(x): return np.array(x>0, dtype=np.int_) # y = x >0 #得到布尔型数组 # return y.astype(np.int) #布尔型转换为int型报错原因!最新的numpy已移除.int,应使用.int_。
绘制阶跃函数如下:x = np.arange(-5.0, 5.0, 0.1) # 在-5.0到5.0范围内,以0.1为单位 y = step_function(x) print(y) plt.plot(x, y) plt.xlim(-6, 6) plt.ylim(-0.1, 1.1) # 指定y轴范围 plt.show()![阶跃函数]()
-
sigmoid函数
$$h(x)=\frac{1}{1+exp(-x)}$$
def sigmoid(x): return 1/(1+np.exp(-x)) x = np.arange(-5.0, 5.0, 0.1) # 在-5.0到5.0范围内,以0.1为单位 y = sigmoid(x) print(y) plt.plot(x, y) plt.xlim(-6, 6) plt.ylim(-0.1, 1.1) #指定y轴范围 plt.show()![sigmoid函数]()
-
ReLU函数
输入大于0时,直接输出该值;输入小于等于0时,输出0。def relu(x): return np.maximum(0, x) x = np.arange(-5.0, 5.0, 0.1) y = relu(x) print(y) plt.plot(x, y) plt.xlim(-6, 6) plt.ylim(-0.1, 1.1) plt.show()![ReLU函数]()
3.3 多维数组的运算

上述网络省略了偏置和激活函数,只有权重。
一维数组维度(2,)到底是2×1还是1×2


3.4 3层神经网络的实现×
3.5 输出层的设计
输出层所用激活函数:回归-恒等函数,二元分类-sigmoid函数,多元分类-softmax函数。
3.5.1 softmax函数
假设输出层共有$n$个神经元,计算第$k$个神经元的输出$y_k$,分子是输入信号$a_k$的指数函数,分母是所有输入信号的指数函数的和。
$$y_k = \frac{exp(a_k)}{\sum_{i=1}^{n}exp(a_i)}$$
由图可以看出,输出层的各个神经元都受到所有输入信号的影响。

可以根据函数公式实现softmax:
def softmax(a):
# 输入a是一个数组
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
但是当输入过大时,分子的结果会返回一个表示无穷大的inf,如果在这些超大值之间进行除法运算,结果会出现“不确定nan(not a number)”的情况,即溢出。于是对其进行改造,这里的$C'$可以使用任何值,但是为了防止溢出,一般使用输入信号中的最大值。

可以根据函数公式实现softmax:
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
softmax函数输出0.0-1.0之间的实数,且输出值总和为1,所以可以把输出解释为“概率”。
神经网络只把输出值最大的神经元所对应的类别作为识别结果,即使使用softmax函数,输出值最大的神经元位置也不会改变。
对于分类问题,输出层神经元数量一般设定为类别的数量。
! 求解机器学习问题的推理(分类)阶段一般会省略输出层的softmax函数。
3.6 手写数字识别应用
假设学习(训练)已全部结束,使用学习到的参数先实现神经网络的“推理处理”,也称为前向传播。
3.6.1 MNIST数据集
0到9的数字图像,6万张训练,1万张测试,28×28像素的灰度图像(1通道),各个像素取值在0到255之间。
load_mnist函数以“(训练图像,训练标签),(测试图像,测试标签)”的形式返回读入的MNIST数据。此外,load_mnist(flatten=True, normalize=False, one_hot_lable=False)有3个参数。
flatten是否展开输入图像为一维数组,若为True——由784个元素构成的一维数组;若为False——为1×28×28的三维数组。normalize是否将输入图像正规化为0.0-1.0的值。本例中将图像的各个像素值除以255将其限制在0.0-1.0之间。one_hot_label是否将标签保存为独热编码,例如[0,0,1,0,0,0,0,0,0,0],若为False,只简单保存为7、2等。
## 导入数据
import numpy as np
import sys
import os
sys.path.append('F:\\blogs\\鱼书') # 将父目录(dataset所在的目录)添加到系统路径中
'''会添加路径是,要用双斜杠\\'''
# 现在尝试导入
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
print(x_train.shape)
print(t_train.shape)
print(x_test.shape)
print(t_test.shape)
#(60000, 784)
#(60000,)
#(10000, 784)
#(10000,)
## 显示图像
import numpy as np
from PIL import Image
def img_show(img):
# 参数img是一个NumPy数组格式的图像
pil_img = Image.fromarray(np.uint8(img))
# 将输入的img数组转换为无符号8位整数类型
pil_img.show()
img = x_train[0]
label = t_train[0]
print(label)
print(img.shape)
# (784,)
img = img.reshape(28, 28)
img_show(img)
3.6.2 神经网络的推理处理
首先,定义3个主要函数。
def get_data():
(x_train, t_train), (x_test, t_test) = \
load_mnist(flatten=True, normalize=True, one_hot_label=False)
return x_test, t_test
# 因为只推理不学习,所以不需要训练集,只用测试集获得识别精度
## pkl文件见CSDN
import pickle
def init_network():
with open("F:\\blogs\\鱼书\\dataset\\sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network
def predict(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = softmax(a3)
return y
使用以上3个函数实现推理,评价识别精度。
首先获得数据集,生成网络,然后用for语句逐一取出保存在x中的图像数据,用predict函数进行分类。以numpy数组形式输出各个标签对应的概率,取最高者作为预测标签,与正确标签比较将回答正确的概率作为识别精度。
x, t = get_data()
import numpy as np
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
y = predict(network,x[i])
p = np.argmax(y)
# 获取概率最高的索引
if p == t[i]:
accuracy_cnt += 1
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
3.6.3 批处理
关注各层权重的形状,输入一个由784个元素构成的一维数组后,输出一个有10个元素的一维数组。
x , _ = get_data()
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']
x.shape #(10000, 784)
x[0].shape #(784,)
W1.shape #(784, 50)
W2.shape #(50, 100)
W3.shape #(100, 10)

现在考虑打包输入多张图像的情形。这种打包式的输入数据称为批。批处理一次性计算大型数组要比分开逐步计算各个小型数组速度更快。

x, t = get_data()
network = init_network()
batch_size = 100 # 批数量
accuracy_cnt = 0
for i in range(0,len(x), batch_size):
# range(start, end, step)生成start到end-1
x_batch = x[i:i+batch_size]
# 取出[0:100],[100:200]
y_batch = predict(network,x_batch)
# 维度为100×10
p = np.argmax(y_batch, axis=1)
# 指定在100×10的数组中,沿着第1维方向找值最大的元素的索引
## 矩阵第0维是列方向,第1维视行方向
accuracy_cnt += np.sum(p == t[i:i+batch_size])
# print(y_batch.shape)
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
批处理前后的识别精确度都是0.9352。

第4章 神经网络的学习
“学习”从训练数据中自动获取最优权重参数的过程,目的是利用函数斜率的梯度法使损失函数最小。
4.1 从数据中学习
对于线性可分问题,第2章的感知机可以利用数据自动学习,通过有限次迭代调整权重,一定能找到有效超平面,学习过程会终止。但是非线性可分问题无法通过学习来解决。
4.1.1 数据驱动
神经网络中,连图像中包含的重要特征量也都是由机器学习的,对所有的问题都可以用同样的流程解决。深度学习也称为端到端机器学习,从原始数据(输入)中获得目标结果(输出)。

4.1.2 训练和测试
为增却评价模型的泛化能力,划分出训练数据学习,寻找最优参数,然后使用测试数据评价训练得到的模型的实际能力。泛化能力指处理未被观察过的数据的能力。过拟合是只对某个数据集过度拟合的状态。
4.2 损失函数
损失函数表示神经网络性能的“恶劣程度”指标,即当前的神经网络对监督数据在多大程度上不拟合、不一致。
神经网络的学习通过损失函数表示现在的状态,然后以这个指标为基础寻找最优权重参数。损失函数可以使用任意函数,一般用均方误差和交叉熵误差。
4.2.1 均方误差
$$E = \frac{1}{2}\sum_{k}(y_k - t_k)^2$$
$y_k$表示神经网络的输出,$t_k$表示监督数据,$k$表示数据的维数。$t$是监督数据,将正确标签设为1,其他均设为0,即 one-hot表示。

def mean_squared_error(y, t):
return 0.5*np.sum((y - t)**2)
4.2.2 交叉熵误差
$$E = - \sum_{k}t_k\log y_k$$
其中log表示以e为底数的自然对数,$y_k$是神经网络输出,$t_k$是正确解标签。交叉熵误差的值是由正确解标签所对应的输出结果决定的。
WHY? 因为独热编码,其他标签的t为0。不是因为这个,交叉熵误差的定义就是只关注正确标签,具体理解件4.2.4mini-batch的交叉熵误差实现)
$y_k$一定小于等于1,故$\log y_k$一定小于等于0。交叉熵误差随输出增大而减小,正确解标签对应的输出越大,交叉熵误差大于且接近0。
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
加上一个微小值,防止出现log(0)时得到inf,导致后续计算无法进行。
4.2.3 mini-batch学习
前面的例子都是针对单个数据的损失函数,如果要求所有训练数据的损失函数的总和,以交叉熵误差为例,可写成下式:
$$E = - \frac{1}{N}\sum_{n}\sum_{k}t_{nk}\log y_{nk}$$
除以N来求单个数据的“平均损失函数”,通过平均化,可以获得和训练数据的数量无关的统一指标。
另外,实际应用中如果以全部数据为对象求损失函数的和,计算过程要花费较长时间,因此选出一部分即mini-batch,作为全部数据的“近似”。
import numpy as np
import sys
sys.path.append('F:\\blogs\\鱼书') # 将父目录(dataset所在的目录)添加到系统路径中
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
print(x_train.shape) #(60000, 784)
print(t_train.shape) #(60000, 10)
如何从训练数据中随机抽取10笔数据?
train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
# 从0-59999之间随机选择10个数字,即被选数据的索引
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
4.2.4 mini-batch版交叉熵误差的实现
- 独热编码版
def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1, y.size) # 通过reshape将y和t转换为二维数组(形状为[1, 特征数]),统一批量样本和单个样本的处理逻辑 batch_size = y.shape[0] return - np.sum(t * np.log(y + 1e-7)) / batch_size - 正常标签版
y[np.arange(batch_size), t]能抽出各个数据的正确解标签对应的神经网络的输出(在这个例子中,会生成NumPy数组[y[0,2], y[1,7], y[2,0], y[3,9], y[4,4]])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
4.2.5 为什么设置损失函数
识别精度:预测正确的样本数占总样本数的比例 ,是离散的,无法直接用于参数优化;
参数导数:损失函数对该参数的偏导数即梯度,导数的符号决定参数调整方向,导数的大小决定调整的幅度,若损失函数对某个权重的导数为正,就需要减小它来降低损失。
识别精度对微小的参数变化几乎没反应,即便有,它的值也是不连续地、突然地变化。若以识别精度为指标,则参数的导数在绝大多数地方都变为0.
4.3 数值微分
梯度法使用梯度的信息决定前进的方向。本节介绍梯度是什么、有什么性质。
数值微分就是用数值方法近似求解函数的导数的过程,利用微小的差分求倒数。而在基于数学式的推导求导数的过程,则成为解析性求导,得到的导数是不含误差的。
4.3.1 导数
导数就是表示某个瞬间的变化量。
不好的求函数导数的程序:
def numerical_diff(f, x):
h = 10e-50
return (f(x+h) - f(x)) / h
两处需改进,首选10e-50是有50个连续的0的微小值,会产生舍入误差,超出计算机可计算的范围,故将微小值改为1e-4;上述差分方法是前向差分,为减小误差,使用中心差分,以x为中心。

4.3.2 × 数值微分的例子
4.3.3 偏导数
偏导数需要将多个变量中的某一个变量定为目标变量,并将其他变量固定为某个值得到新函数,对新定义的函数应用之前的求数值微分的函数,得到偏导数。
4.4 梯度
上一节中,按变量分别计算了两个变量的偏导数,现在想要一起计算。
梯度:像这样$(\frac{\partial{f}}{\partial{x_0}},\frac{\partial{f}}{\partial{x_1}})$由全部变量的偏导数汇总而成的向量称为梯度。
× 书101-102 pass
4.4.1 梯度法
一般而言,损失函数很复杂,空间庞大,通过不断地沿梯度方向前进来寻找函数最小值或尽可能小的值的方法就是梯度法。
注意:梯度表示的是个点处的函数值减小最多的方向,无法保证指向函数最小值。

因为函数极小值、最小值和鞍点处的梯度为0。极小值是局部最小值;鞍点是从某个方向上刊是极大值,从另一个方向上看则是极小值的点。此外当函数很复杂且扁平时,学习可能会进入一个几乎平坦的地区,陷入无法前进的“学习高原”。
$$x_0 = x_0 - \eta\frac{\partial{f}}{\partial{x_0}}$$
$$x_1 = x_1 - \eta\frac{\partial{f}}{\partial{x_1}}$$
式中的$\eta$称为学习率,它决定在一次学习中,应该学习多少,以及在多大程度上更新参数。上式表示更新一次的式子,该步骤会反复执行,逐渐减小函数值。









浙公网安备 33010602011771号