1.1 数学和python的复习
今天,终于,借此机会,终于学会用命令行了!学习记录(感谢步老师!!!)
需要注意的点是 np.dot(),当参数是一维数组时,计算向量内积;当参数是二维数组时,计算矩阵乘积。
>>> import numpy as np
>>> x=np.array([1,2,3])
>>> x.__class__#输出类名
<class 'numpy.ndarray'>
>>> x.shape
(3,)
>>> x.ndim
1
>>> W=np.array([[1,2,3],[4,5,6]])
>>> W.shape
(2, 3)
>>> W.ndim
2
>>> W=np.array([[1,2,3],[4,5,6]])
>>> X=np.array([[0,1,2],[3,4,5]])
>>> W+X
array([[ 1, 3, 5],
[ 7, 9, 11]])
>>> W*X
array([[ 0, 2, 6],
[12, 20, 30]])
>>> #广播
>>> A=np.array([[1,2],[3,4]])
>>> A*10
array([[10, 20],
[30, 40]])
>>> A=np.array([[1,2],[3,4]])
>>> b=np.array([10,20])
>>> A*b
array([[10, 40],
[30, 80]])
>>> b=np.array([4,5,6])
>>> np.dot(a,b)#当参数是一维数组时,计算向量内积
32
>>> A=np.array([[1,2],[3,4]])
>>> B=np.array([[5,6],[7,8]])
>>> np.dot(A,B)#当参数是二维数组时,计算矩阵乘积
array([[19, 22],
[43, 50]])
1.2 神经网络的推理
1.2.1 神经网络的推理的全貌图
x 的形状是(10, 2),表示10笔二维数据组织为了1个mini-batch。最终输出的s 的形状是(10, 3)
#Python写出mini-batch版的全连接层变换
def sigmoid(x):
return 1/(1+np.exp(-x))
import numpy as np
x=np.random.randn(10,2)#输入
W1=np.random.randn(2,4)#权重
b1=np.random.randn(4) #偏置
W2=np.random.randn(4,3)#权重
b2=np.random.randn(3) #偏置
h=np.dot(x,W1)+b1
a=sigmoid(h)#sigmoid非线性变换
s=np.dot(a,W2)+b2
以上就是神经网络的推理部分的实现。 接下来,我们使用Python的类,将这些处理实现为层。
1.2.2 层的类化及正向传播的实现
只考虑正向传播,所以我们仅关注代码规范中的以下两点:一是在层中实现forward() 方法;二是将参数整理到实例变量params 中
#sigmoid层
import numpy as np
class Sigmoid:
def __init__(self):
self.params=[]#因为Sigmoid层没有需要学习的参数,所以使用空列表来初始化实例变量params
def forward(self,x):
return 1/(1+np.exp(-x))
#Affine层
class Affine:
def __init__(self,W,b):#Affine层在初始化时接收权重和偏置
self.params=[W,b]#此时,Affine层的参数是权重和偏置(在神经网络的学习时,这两个参数随时被更新)
def forward(self,x):
W,b=self.params#这是个什么语法 列表里只有两个值,所以可以这样取出来吗?[对的!]
out=np.dot(x,W)+b
return out
#输入X经由Affine层、Sigmoid层和Affine层后输出得分S。我们将这个神经网络实现为名为TwoLayerNet 的类,将主推理处理
#实现为predict(x) 方法
class TwoLayerNet:
def __init__(self,input_size,hidden_size,output_size):
I,H,O=input_size,hidden_size,output_size
#初始化权重和偏置
W1=np.random.randn(I,H)
b1=np.random.randn(H)
W2=np.random.randn(H,O)
b2=np.random.randn(O)
#生成层
self.layers=[
Affine(W1,b1),
Sigmoid(),
Affine(W2,b2)
]
#将所有的权重整理到列表中
self.params=[] ##类的实例属性
for layer in self.layers:
self.params+=layer.params
def predict(self,x):
for layer in self.layers:
x=layer.forward(x)
return x
x=np.random.randn(10,2)
model=TwoLayerNet(2,4,3)#实例化一个网络类
s=model.predict(x)
print(s)
其中,
for layer in self.layers:
self.params+=layer.params
因为各个层的实例变量params 中都保存了学习参数,所以只需要将它们拼接起来即可。这样一来,TwoLayerNet 的params
变量中就保存了所有的学习参数。像这样,通过将参数整理到一个列表中,可以很轻松地进行参数的更新和保存
>>> a=['A','B']
>>> a+=['c','d']
>>> a
['A', 'B', 'c', 'd']
此外,Python中可以使用+ 运算符进行列表之间的拼接。下面是一个
简单的例子。
######以上代码保存于my_forward_net.py中 23.11.20
1.3 神经网络的学习
1.3.1 损失函数
此章节主要记录一下实现SoftMaxWithLoss层
###common/my_layers.py
import numpy as np
from common.my_function import softmax,cross_entropy_error
class SoftmaxWithLoss:
def __init__(self):
self.params,self.grads=[],[]
self.y=None #softMax的输出
self.t=None #监督标签
def forward(self,x,t):
self.t=t
self.y=softmax(x)
# 在监督标签为one-hot向量的情况下,转换为正确解标签的索引
if self.t.size==self.y.size:
self.t=self.t.argmax(axis=1)
loss=cross_entropy_error(self.y,self.t)
return loss
softMax函数,函数本身比较简单
###common/my_function.py
def softmax(x):
if x.ndim==2:#批处理操作
#下面两行语句都用到了“axis=1”,我理解为把二维数组按照 每行 分隔开来了,求每一行的softmax
x=x-x.max(axis=1,keepdims=True)#防止溢出,减去每组数据中的最大值。keepdims=True,保持其二维数组特性,即保持ndim=2
x=np.exp(x)
x/=x.sum(axis=1,keepdims=True)
elif x.ndim==1:#只有一组数据时的操作
x=x-np.max(x)
x=np.exp(x)/np.sum(np.exp(x))
return x
需要注意的是:1、为了防止e^x溢出,每一组数据都减去了最大值 具体推理看《深度学习入门-基于python的理论与实现》3.5
x=x-np.max(x)
2、 x=x-np.max(x), x=np.exp(x)/np.sum(np.exp(x))都用到了numpy类的广播机制(broadcasting)类似于这样
3、x.ndim2 表示当前是批处理,x是二维数组,x有多少行代表这一批有多少数据,每一行代表一个独立的数据。
x.ndim1相当于一维数组,代表只有一个数据。
4、sum(axis=1,keepdims=True)嘛意思?
执行np.sum(axis=1)后,相当于把每一行数据求和,会降一维,加上keepdims=True后,会保持原数组的ndim,不会降维。
>>> import numpy as np
>>> a=np.array([[1,2],[3,4]])
>>> a.sum(axis=1,keepdims=True)
array([[3],
[7]])
>>> b=np.array([[1,2],[3,4]])
>>> b.sum(axis=1)
array([3, 7])
cross_entropy_error函数
函数本身也比较简单,重要的是带入批处理后,的代码实现过程。
###common/my_function.py
def cross_entropy_error(y,t):
if y.ndim==1:#只有一组数据的操作
#一维数组的y ndim=1
#这样做使y的 ndim=2,与接下来的批处理操作统一
t=t.reshape(1,t.size)
y=y.reshape(1,y.size)
#接下来是批处理操作
# 在监督标签为one-hot-vector的情况下,转换为正确解标签的索引
if t.size==y.size: #y是softMax传下来的参数,y=[[0.3,0.2,0.5],[..],[..]..]
t=t.argmax(axis=1)
batch_size=y.shape[0]
return -np.sum(np.log(![请添加图片描述](https://img-blog.csdnimg.cn/eeb11447f05346899bc3bad9afcc2666.jpeg)
+1e-7))/batch_size
注意事项:1、首先要知道y是softMax()处理完毕的数据,y=[[0.3,0.2,0.5],[…],[…]…],t是正确解标签
2、同样可以同时处理批数据( y.ndim2)和单个数据( y.ndim1)的情况
3、 y=y.reshape(1,y.size) 干了嘛事?
>>> a=np.arange(3)
>>> a
array([0, 1, 2])
>>> a[0].size
1
>>> a=a.reshape(1,a.size)
>>> a
array([[0, 1, 2]])
>>> a[0].size
3
答:将一维数组变成了二维?数组,或者说升了一维,方便与批处理操作统一。
因为若y是一维数组,y.shape[0]=1,但是升维后,y.shape[0]=batch_size。
4、在监督标签为one-hot-vector的情况下,转换为正确解标签的索引:
numpy.argmax (array, axis) 用于返回一个numpy数组中最大值的索引值
>>> t=np.array([[0,0,1],[0,1,0],[1,0,0]])
>>> t.argmax(axis=1)
array([2, 1, 0], dtype=int64)
>>>
这里也体现了一维数组时,执行t=t.reshape(1,t.size) 的重要性
5、最后一行!y[np.arange(batch_size),t] 的意思是,每一行的正确解标签的估计值。
1.3.4 计算图
1.3.4.1 乘法节点
乘法节点是z=x×y这样的计算。
反向传播:dz/dx=y,dz/dy=x,具体看图
1.3.4.2分支节点
反向传播:相加!
1.3.4.3 repear节点
反向传播:全部相加!
import numpy as np
D,N=8,7
x=np.random.randn(1,D) #输入1*D 数组
y=np.repeat(x,N,axis=0)#正向传播,一行复制成N行,1*D->N*D
dy=np.random.randn(N,D)#假设的梯度
dx=np.sum(dy,axis=0,keepdims=True)#反向传播,全部相加
1、因为反向传播时要计算总和,所以使用NumPy的sum() 方法。此时,通过指定axis
来指定对哪个轴求和(axis=0,对每一列求和,axis=1,对每一行求和)。
2、通过指定keepdims=True,可以维持二维数组的维数。在上面的例子中,当keepdims=True 时,np.sum()
的结果的形状是(1, D);当keepdims=False 时,形状是(D,)
1.3.4.4 Sum节点
反向传播:全部repeat!
#Sum节点
import numpy as np
D,N=8,7
x=np.random.randn(N,D)#输入N*D
y=np.sum(x,axis=0,keepdims=True)#正向传播,每列相加成一个数,N*D->1*D
dy=np.random.randn(1,D)#假设的梯度
dx=np.repeat(dy,N,axis=0)#反向传播
Sum节点的正向传播通过np.sum() 方法实现,反向传播通过np.repeat() 方法实现。有趣的是,Sum节点和Repeat节点存在逆向关系。所谓逆向关系,是指Sum节点的正向传播相当于Repeat节点的反向传播,Sum节点的反向传播相当于Repeat节点的正向传播。
1.3.4.5 MatMul节点
反向传播:矩阵相乘,具体看第三个图
#MatMul
import numpy as np
class MatMul:
def __init__(self,W):
self.params=[W]
self.grads=[np.zeros_like(W)] #梯度,反向传播的时候才会更新
self.x=None
def forward(self,x):
W,=self.params
out=np.dot(x,W)
self.x=x#反向传播的时候会用到,所以要存一下!
return out
def backward(self,dout):
W,=self.params
dx=np.dot(dout,W.T)#W.T,W的转置
dw=np.dot(self.x.t,dout)
self.grads[0][...]=dw #可以固定NumPy数组的内存地址,覆盖NumPy数组的元素。
return dx
1、self.grads[0][…]=dw #可以固定NumPy数组的内存地址,覆盖NumPy数组的元素。
注意区分浅复制和深复制!!!
1.3.5.1 sigmoid层
import numpy as np
class Sigmoid:
def __init__(self):
self.params,self.grads=[],[]
def forward(self,x):
out=1/(1+np.exp(-x))
self.out=out
return out
def backward(self,dout):
dx=dout*(1.0-self.out)*self.out
return dx
1.3.5.2 Affine层
class Affine:
def __init__(self,W,b):
#params是一个list,params[0]存的是np.array类型的W系数,params[1]寸的是np.array类型的b系数
self.params=[W,b]
self.grads=[np.zeros_like(W),np.zeros_like(b)]
self.x=None
def forward(self,x):
W,b=self.params
out=np.dot(x,W)+b
self.x=x
return out
def backward(self,dout):
W,b=self.params
dx=np.dot(dout,W.T)
dW=np.dot(self.x.t,dout)
db=np.sum(dout,axis=0)
self.grads[0][...]=dW #grads[0]存的是dw,grads[0][...]=dW 可以实现深复制
self.grads[1][...]=db #grads[0]存的是db
return dx
1.3.5.3 SoftmaxWithLoss层
从Softmax层传来的反向传播有y1−t1,y2−t2,y3−t3这样一个很“漂亮”的结果。因为y1,y2,y3是 Softmax层的输出,t1,t2,t3是监督标签,所以y1−t1,y2−t2,y3−t3是 Softmax层的输出和监督标签的差分。神经网络的反向传播将这个差分(误差)传给前面的层。这是神经网络的学习中的一个重要性质。
Softmax with Loss层的反向传播的推导过程在前作《深度学习入门:基于Python的理论与实现》的附录A中有详细说明
class SoftMaxWithLoss:
def __init__(self):
self.params,self.grads=[],[]
self.y=None #softMax的输出 [[0.1,0.2,0.7],[],[]...] 这种
self.t=None #监督标签,可能是one-hot向量,[[0,0,1],[],[]...]这种,可能是一维数组
def forward(self,x,t):
self.t=t
self.y=softmax(x)
# 在监督标签为one-hot向量的情况下,转换为正确解标签的索引
if self.t.size==self.y.size:
self.t=self.t.argmax(axis=1)
loss=cross_entropy_error(self.y,self.t)
return loss
def backward(self,dout=1):
batch_size=self.t.shape[0]
if self.t.size==self.y.size:# 监督数据是one-hot-vector的情况
dx=(self.y-self.t)/batch_size
else:
dx=self.y.copy()
dx[np.arange(batch_size),self.t]-=1
dx=dx/batch_size
return dx
要点:1、若t不是one-hot向量,会执行以下代码
dx=self.y.copy()
dx[np.arange(batch_size),self.t]-=1
dx=dx/batch_size
2、至于为什么会除以batch_size ,见下图 参考
1.3.6 权重的更新
class SGD:
def __init__(self,lr=0.01):
self.lr=lr
def update(self,params,grads):
#params 和grads 在相同索引处分别保存了对应的参数和梯度
for i in range(len(params)):
params[i]-=self.lr*grads[i]
for i in range(len(params)):
params[i]-=self.lr*grads[i]
#注:::上面两行可以写成下面这一句吗?
params-=self.lr*grads
#########################################################23.11.26
1.4 使用神经网络解决问题
1.4.1 螺旋状数据集
import sys
import numpy as np
sys.path.append('D:/0.自主学习/NLP入门/NeutralLanguageProcessing-master/SourceCode/dataset')
import spiral
import matplotlib.pyplot as plt
x,t=spiral.load_data()
print('x',x.shape)#(300, 2)
print('t',t.shape)#(300, 3)
注:原书代码dataset/spiral.py里有一句
t = np.zeros((N*CLS_NUM, CLS_NUM), dtype=np.int)
会报错,np.int弃用了,改成dtype=np.int64就好啦!