首页 > 其他分享 >[附课程学习笔记]CS231N assignment 3#1 _ RNN 学习笔记 & 解析

[附课程学习笔记]CS231N assignment 3#1 _ RNN 学习笔记 & 解析

时间:2023-05-07 10:23:13浏览次数:47  
标签:输出 CS231N RNN cache 笔记 Wh np prev Wx

欢迎来到assignment3

从现在开始, 网上的博客数量就少了很多. 毕竟从现在, 我们开始了更具体网络的学习.

这里的组织形式可能会比较怪, 我会将RNN相关的课程内容和代码混在一起, 这样也可以同时作为学习笔记, 也是考虑到RNN之后没有官方讲义之后自己概括性的评说, 感觉比较好组织.

因为最近时间减少的关系, 所以更新速度慢了很多. 不过这个坑我一定会填的hh

COCO数据集和数据格式

COCO数据集是一个CV深度学习中应用广泛的数据集. 因为原始的图像大小为20G, 所以在这个课程中存储的实际上是图片经过全连接层之后的向量, 并按照HDF5文件格式存储. 除此之外, 数据集内还附带了图片的原始链接和captioning. 解说文本是一句话, 有数个单词, 但是这里不处理字符串, 而是将所有单词对应一个数字, 并存储在相应的json文件内,例如<START> => 1, <END> => 2.

因为没有包含图片,所以读取必须从网络获取. 如果出现"另一进程无法访问"的错误, 是因为下载的临时文件删除的冲突问题, 可以将删除的代码注释掉:

这样我们就可以看到几个示例数据了.

RNN的定义和实现

关于RNN, 大家可能在网络上看到最多的图片就是这个:

我们记忆现在的状态和目前正在发生的事情和过去的记忆有关, 而我们根据记忆会做出对应的决策. 我们设状态为ht, ht和当前的输入和过去状态h_t-1有关, 而ht经过一系列变换就得到的输出. 也就是这个图象:

但是, 实际上输入和输出的关系却不止一种, 根据应用场景的不同我们可以抽象不同的输入输出关系:

这五个情况分别对应了:

  • 传统的神经网络
  • 定长输入, 可变输出, 比如根据图片生成文本
  • 可变输入, 定长输出, 例如文本情感分析这样对变长数据的概括性内容
  • 多对多, 输出晚于全部输入, 适用于事后对变长数据的分析, 例如文本翻译
  • 多对多, 但是输入输出同时对应, 适用于实时分析,

在课件中, 除了我们看到上面图片的思维定势, 还有其他的一些巧妙思路. 例如我们不断移动绿色的框, 这样识别就变成了一个动态的过程, 虽然还是分类,:

通用的示意图如下, 对于一对多的模型, 可以直接将x抹去:

 

有了这个准备, 我们就写出了前向推导的代码, 目前我们仅仅是针对一个隐含层块的处理而已:

next_h,cache = None,None
    # 维度: x (N,D) h (N,H) Wx (D,H) Wh (H,H) b (H,)
    next_h = np.tanh(prev_h.dot(Wh)+x.dot(Wx)+b) 
    cache = (next_h,Wh,Wx,prev_h,x)

 需要注意, 这是一个很通用的推导函数, 我们应当根据具体的前向过程进行具体的操作.

梯度下降的部分这里暂时不表, 请参考下面的内容.

在实际的处理当中, 我们处理的应当是一个长度为T的序列, 这样, 我们只能写循环来逐步完成递推的过程. 在整个前向推导过程中, 我们使用相同的权重矩阵, 因此滑动的只有x和对应的h. 为此, 我们维护两个变量prev_h和next_h, 做逐步的循环:

    N, T, _ = x.shape
    H = b.shape[0]
    h = np.zeros((N, T, H))
    prev_h = h0  # 维护变量, 初始的prev_h
    for i in range(T):  # T个时间步
        next_h, cache_ = rnn_step_forward(x[:, i, :], prev_h, Wx, Wh, b) # 单步
        # cache这里用不到, 因为W不变, 而h整个存储就行了
        prev_h = next_h # 迭代
        h[:, i, :] = prev_h
    cache = (h, Wh, Wx, b, x, h0)

梯度下降

首先我们需要知道tanh的导数形式: (σ => sigmoid)

随后我们来看看对于长时域的梯度倒退过程:

其中, h0,h1可以形成一个完整的梯度流过程, 所以每一个h均可以反馈到. 但是这里只要求对最初的h进行梯度计算.

随后我们倒退过tanh层, 因为已知内层为prev_h.dot(Wh)+x.dot(Wx)+b, 所以求导也很简单, 注意规模就可以了, 而b还是老规矩, 广播的反向是求和.

# 结果(N,H) x(N,D)... 剩下的参见前面 
    (next_h, Wh, Wx, prev_h, x) = cache
    dtanh = dnext_h * (1 - next_h * next_h)
    dx = dtanh.dot(Wx.T)
    dprev_h = dtanh.dot(Wh.T)
    dWx = x.T.dot(dtanh)
    dWh = prev_h.T.dot(dtanh)
    db = np.sum(dtanh, axis=0)

 而如果将这个推广到整个时域, 也就是T层矩阵, 除了h之外, 权重矩阵是定值, 直接对每个时间点的梯度进行求和, x每个部分都会有梯度. 

这里需要注意, "dh"是我们从y -> h 计算得到的梯度, 因为我们ht有两个走向: 转化为输出y和下一个h_t+1, dh实际上是从y而来的梯度, 实际上ht的变化源于ht+1和yt,所以在rnn_backward中相加即可.剩下的直接循环就可以了.

(h, Wh, Wx, b, x, h0) = cache
    N, T, H = dh.shape
    _, _, D = x.shape
    dx = np.zeros((N, T, D)) # 初始化
    dh0 = np.zeros((N, H)) # 输出梯度
    dWx = np.zeros((D, H))
    dWh = np.zeros((H, H))
    db = np.zeros(H)
    dprev_h_ = np.zeros((N, H)) # 梯度流类似初始化变量

    for i in range(T - 1, -1, -1):  # 从后往前计算
        prev_h = h0 if i == 0 else h[:, i - 1, :]
        cachei = (h[:, i, :], Wh, Wx, prev_h, x[:, i, :])
        dx[:, i, :], dprev_h_, dWx_, dWh_, db_ = rnn_step_backward(dh[:, i, :] + dprev_h_, cachei)
        dWh += dWh_
        dWx += dWx_
        db += db_
    dh0 = dprev_h_

词嵌入

所谓词嵌入(word embedding)是NLP里面的概念, 意即将文本转化成向量. 

在这里, 我们怎么理解这里的x和W呢? 这里实质上完成的应当是从输出数字到单词的转换, 比如我们原本的输出是2(不过实际上应该是第三个元素更大的向量, 经由softmax转化为one-hot,但是其可以被坍缩为数字2,所以这么说了), 就把2作为x, 对应的输出向量可能是[0=> ..., 1=> <START> , 2=> <END>].  代码看起来很简单:

out = W[x]
cache = (x, W.shape)

但是很让人摸不着头脑. 为什么是这样呢? 比如题目中输入的x和W就是:

我们可以看作这里的单词数量共有5个, 原本输出X是两个句子: 第一句话是单词0, 单词3, 单词1, 单词2. 而W记录了5个单词分别代表什么. 因此输出翻译成句子就是直接对矩阵切片. numpy的切片很强大, 我们直接W[0]得到的就是第一个单词的内容, 直接W[x]就会直接将x内的每一个元素替换成W内的元素.

现在我们从输出得到了dout, 需要得到dW. 因为x其实本质上只是对W进行切片, 所以dout切片也应该可以得到dW. 我们已经知道, 输出的格式为(N,T,D). dout的每一个向量都应该被加总到dW的对应位置, 而这个位置存储在x里面. 最终看起来也是大道至简:

    x, W_shape = cache
    dW = np.zeros(W_shape)
    np.add.at(dW, x, dout)

其中np.add.at是干什么的呢? 下面有个例子:

add函数类似于+=这个符号, 而add.at函数第二个参数表明加的位置, 第三个参数表明加的数量. 这个函数的作用就是对dW按照x的指示加上dout的特定部分.

随后需要将h转化成输出向量和softmax输出, 这点可以参照之前的博客, 并未要求我们完成. 此外, 在softmax层中, 也确实完成了我们前面说的探索过程.

实现RNN captioning

下面我们在rnn.py内完成我们的过程. 首先根据注释我们看出它的处理方式是: 因为我们要计算误差, 所以需要将字幕同步, 但是因为第二个时刻开始才有有效输出, 所以caption_in从<START>开始, 掐掉了最后的<END>, 而caption_out仅仅缺失<START>, <END>理论就在最后一个输出的下一个位置 ,随后就是一系列参数的读取.

这个计算loss过程和我最初想法很不一样, 即: 将原始图像特征直接经过全连接层转化为h的初始值, 随后输入真实字幕的一个单词, 让它来预测下一个, 也就是我专注于训练这个网络模型从这个单词推断下一个单词的能力, 但是在测试过程中我们就应当直接输入上一个的单词输出了.

# 将图像的特征转化为h
        h_i, cache_proj = affine_forward(features, W_proj, b_proj)
        # caption_in => 词嵌入向量
        captions_embedding, cache_embedding = word_embedding_forward(captions_in, W_embed)
        # 推演
        if self.cell_type == 'rnn':
            h, cache_rnn = rnn_forward(captions_embedding, h_i, Wx, Wh, b)
        else: # LSTM暂时没有实现
            h, cache_rnn = None, None
        #  转化成y
        scores, cache_out = temporal_affine_forward(h, W_vocab, b_vocab)
        # 计算损失. 这实质上就是前面给你的函数用法
        loss, dscores = temporal_softmax_loss(scores, captions_out, mask)

        # 反向传播求梯度
        dh, grads["W_vocab"], grads["b_vocab"] = temporal_affine_backward(dscores, cache_out)
        if self.cell_type == 'rnn':
            dembedding, dh0, grads["Wx"], grads["Wh"], grads["b"] = rnn_backward(dh, cache_rnn)
        else:
            dembedding, dh0 = None, None
        grads["W_embed"] = word_embedding_backward(dembedding, cache_embedding)
        # dfeatures,grads["W_proj"],grads["b_proj"] = affine_relu_backward(dh0,cache_proj)
        dfeatures, grads["W_proj"], grads["b_proj"] = affine_backward(dh0, cache_proj)

        return loss, grads

下面就是直接小批量数据过拟合来验证我们的正确性.  自然效果是很好的,我也就不展示结果了.

问题和解答

回答: 因为我们从目标上就是要做连续推断, 所以很显然, word比char可以给出的信息会更多, 所以word相对不容易出现错误, 但是word会比char编码上复杂, 所以嵌入矩阵等也更难, 而且我们不能无限穷举. 不过还是那句话, 二者没有绝对优劣之分, char也可以有很好的效果, 但是必须建立在良好的网络架构和优质的训练数据的基础上.

标签:输出,CS231N,RNN,cache,笔记,Wh,np,prev,Wx
From: https://www.cnblogs.com/360MEMZ/p/17320668.html

相关文章

  • JavaScript 笔记
    JavaScript简介JavsScript于1995年由BrendanEich用时10天写出,用于网景浏览器。最初的名字叫LiveScript,也被部分员工称为Mocha。那时Java语言很流行,出于商业化的考量,更名为JavaScript,但两者之间没有关联。最早的JS作为脚本语言给浏览器增加一些诸如鼠标跟随等交......
  • 读书笔记
    简介主要讲述了软件安全开发生命周期的相关内容,包括需求分析、设计、编码、测试、发布和维护等各个阶段。内容涵盖了安全威胁的识别、风险评估、安全设计、代码审查、漏洞测试、修复等方面。需求分析在需求分析阶段,需要考虑系统应对哪些威胁,以及如何保证数据的机密性、完整性和......
  • Django笔记三十六之单元测试汇总介绍
    本文首发于公众号:Hunter后端原文链接:Django笔记三十六之单元测试汇总介绍Django的单元测试使用了Python的标准库:unittest。在我们创建的每一个application下面都有一个tests.py文件,我们通过继承django.test.TestCase编写我们的单元测试。本篇笔记会包括单元测试的......
  • UE学习笔记(二)
    一些定义整理TAA将一个完整的像素拆分成多个小像素,对子像素分别采样后计算最终结果随着摄像机距离拉远物体出现闪硕,想要看清当前物体两种方法1.将屏幕百分比放大2.可以利用贴图解决此类问题烘焙本质是预计算——为了减少运行时的计算量,而将部分提前计算出来。优点:节省......
  • 读书笔记
    程序员修炼之道》这本书是由国外技术大牛编写经由国内学者进行翻译的一本有关程序员各方面素养提升的一本好书,第一次看一本书重要的就是看他的序和前言,这本书的序和前言给了我十分深刻的印象,编程是一种技艺,一种需要用心学习的技艺,作为一位刚刚接触编程不久的我看了这本书,我领悟了......
  • helm官档笔记
    https://helm.sh/zh/docs/intro/using_helm/三大概念Chart代表着Helm包。它包含在Kubernetes集群内部运行应用程序,工具或服务所需的所有资源定义。你可以把它看作是Homebrewformula,Aptdpkg,或YumRPM在Kubernetes中的等价物。Repository(仓库)是用来存放和共享cha......
  • 「学习笔记」模拟费用流
    学习自cmd老师的博客。昨天学习了模拟费用流相关内容阿。常见的模拟费用流题大概会长成一个类似匹配的形状。主要的方法大概是两种:“增量-费用任意流”模型或是直接模拟EK算法的增广过程。“增量-费用任意流”模型,即每次在图中增加一些点和边,求新的费用任意流。要注意,如......
  • 矩阵学习笔记
    定义我们把一个\(n\timesm\)的数列叫做矩阵。他可以解决一部分线性递推的题目。特别的,我们常说的向量就是一个\(1\timesn\)的矩阵捏。单位元我们形如这样\(\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix}\)这种只有对角线都是\(1\)的叫做单位元。运算主......
  • mall学习笔记(1)
    参考macrozheng的mall项目搭建的后端。发现电脑带不动虚拟机于是选择Win10下开发(1. Java连接MySQL出现CommunicationsException和SSLHandshakeException问题处理解决方法:在连接url里加上useSSL=false2.Win10下MinIO搭建下载地址:MinIO|Codeanddownloadstocreatehigh......
  • Hudi学习笔记(2)
    https://hudi.apache.org/docs/configurationsHudi配置分类SparkDatasourceConfigsSparkDatasource的配置。FlinkSqlConfigsFlinkSQLsource/sinkconnectors的配置,如:index.type、write.tasks、write.operation、clean.policy、clean.retain_commits、clean.reta......