【自学记录】深度学习进阶:自然语言处理(第一章 神经网络的复习)

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.ndim
1相当于一维数组,代表只有一个数据。
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就好啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值