草庐IT

后CNN探索,如何用RNN进行图像分类

华为云开发者社区 2023-03-28 原文
摘要:RNN可以用于描述时间上连续状态的输出,有记忆功能,能处理时间序列的能力,让我惊叹。

本文分享自华为云社区《用RNN进行图像分类——CNN之后的探索》,作者: Yin-Manny。

一、 写前的思考:

当看完RNN的PPT,我惊叹于RNN可以用于描述时间上连续状态的输出,有记忆功能,能处理时间序列的能力。

当拿到思考题,在CNN框架下加入RNN程序,这是可以实现的吗,如果可以,它的理论依据是什么,它的实现方法是什么,它的效果是怎样的。加入这个有必要吗。

我寻找了CNN combine with RNN的资料,看了CLDNN论文,我知道了:

CNN和RNN直接的不同点:

CNN进行空间扩展,神经元与特征卷积;RNN进行时间扩展,神经元与多个时间输出计算;

RNN可以用于描述时间上连续状态的输出,有记忆功能;CNN则用于静态输出;

CNN高级结构可以达到100+深度;RNN的深度有限。

CNN和RNN组合的意义:

大量信息同时具有时间空间特性:视频,图文结合,真实的场景对话;

带有图像的对话,文本表达更具体;

视频相对图片描述的内容更完整。

但是这对思考题没有什么帮助。

于是我又从RNN分类图像下手,试图弄明白RNN能用于图像分类的原理,首先需要将图片数据转化为一个序列数据,例如MINST手写数字图片的大小是28x28,那么可以将每张图片看作是长为28的序列,序列中的每个元素的特征维度是28,这样就将图片变成了一个序列。同时考虑循环神经网络的记忆性,所以图片从左往右输入网络的时候,网络可以记忆住前面观察东西,然后与后面部分结合得到最后预测数字的输出结果,理论上是行得通的。但是对于图像分类,CNN才是主流,RNN图像分类的理论,对于CNN能有什么帮助呢?

甚至我们知道,循环神经网络还是不适合处理图片类型的数据:

第一个原因是图片并没有很强的序列关系,图片中的信息可以从左往右看,也可以从右往左看,甚至可以跳着随机看,不管是什么样的方式都能够完整地理解图片信息;

第二个原因是循环神经网络传递的时候,必须前面一个数据计算结束才能进行后面一个数据的计算,这对于大图片而言是很慢的,但是卷积神经网络并不需要这样,因为它能够并行,在每一层卷积中,并不需要等待第一个卷积做完才能做第二个卷积,整体是可以同时进行的。

那么我要怎么在CNN中加入RNN程序呢?

初步设想:

把CNN比较深层次的网络提取到的特征序列化,再喂给RNN进行分类,因为我认为这时候CNN提取到的特征比原始图像有更强的序列关系(如下图,越深层得到的特征序列关系越强,比如跳着看可能就难以进行分类了)

二、 如何将图像数据改成序列数据?如何加入RNN系列程序,以改进图片分类的性能?

设image.shape为(h,w)

则令time_steps=h, input_size=w即可将图像数据改成序列数据

令X=[batch_size,h,w]

outputs, states = tf.nn.dynamic_rnn(rnn_cell, X, dtype=tf.float32)即可将X应用于RNN程序

如果是三通道图片,如RGB,则利用图像处理知识将三通道图像转为单通道灰度图像。

例如:

import matplotlib.pyplot as plt # plt 用于显示图片
from PIL import Image
import numpy as np
image1 = Image.open('./1.jpg')
img=np.array(image1)
# 通道转换
def change_image_channels(image):
 # 3通道转单通道
 if image.mode == 'RGB':
        r, g, b = image.split()
 return r,g,b
ima = change_image_channels(image1)
for i in range(3):
 plt.imshow(ima[i])
 plt.show()
r=img[:,:,0]
g=img[:,:,1]
b=img[:,:,2]
GRAY = b * 0.114 + g * 0.387 + r * 0.29
im=Image.fromarray(GRAY) # numpy 转 image类
im.show()

效果如下:

正如初步设想所说,把CNN比较深层次的网络提取到的特征序列化,再喂给RNN进行分类,因为我认为这时候CNN提取到的特征比原始图像有更强的序列关系,也许能够改进图片分类的性能?

例如我们将第一层的特征图喂到RNN中

import sys
sys.path.append('..')
import torch
from torch.autograd import Variable
from torch import nn
from torch.utils.data import DataLoader
from torchvision import transforms as tfs
from torchvision.datasets import MNIST
# 定义数据
data_tf = tfs.Compose([
 tfs.ToTensor(),
 tfs.Normalize([0.5], [0.5]) # 标准化
])
train_set = MNIST('./data', train=True, transform=data_tf, download=True)
test_set = MNIST('./data', train=False, transform=data_tf, download=True)
train_data = DataLoader(train_set, 64, True, num_workers=2)
test_data = DataLoader(test_set, 128, False, num_workers=2)
# 定义模型
class rnn_classify(nn.Module):
    def __init__(self, in_feature=28, hidden_feature=100, num_class=10, num_layers=2):
 super(rnn_classify, self).__init__()
 self.rnn = nn.LSTM(in_feature, hidden_feature, num_layers) # 使用两层 lstm
 self.classifier = nn.Linear(hidden_feature, num_class) # 将最后一个 rnn 的输出使用全连接得到最后的分类结果
    def forward(self, x):
 '''
        x 大小为 (batch, 1, 28, 28),所以我们需要将其转换成 RNN 的输入形式,即 (28, batch, 28)
 '''
        x = x.squeeze() # 去掉 (batch, 1, 28, 28) 中的 1,变成 (batch, 28, 28)
        x = x.permute(2, 0, 1) # 将最后一维放到第一维,变成 (28, batch, 28)
        out, _ = self.rnn(x) # 使用默认的隐藏状态,得到的 out 是 (28, batch, hidden_feature)
        out = out[-1, :, :] # 取序列中的最后一个,大小是 (batch, hidden_feature)
        out = self.classifier(out) # 得到分类结果
 return out
net = rnn_classify()
criterion = nn.CrossEntropyLoss()
optimzier = torch.optim.Adadelta(net.parameters(), 1e-1)
# 开始训练
from utils import train
train(net, train_data, test_data, 10, optimzier, criterion)

迭代10次准确率高达98%,因此分类效果还是不错的。

三、 不同的RNN细胞结构、不同的RNN整体结构,对分类性能有什么影响?

不同的细胞结构具有不同的门结构,对长短期记忆有不同的权重

不同的RNN整体结构有不同的层数与架构,对长短期记忆有不同的遗忘属性

常见RNN细胞总结:

BasicRNNCell--一个普通的RNN单元。

GRUCell--一个门控递归单元细胞。

BasicLSTMCell--一个基于递归神经网络正则化的LSTM单元,没有窥视孔连接或单元剪裁。

LSTMCell--一个更复杂的LSTM单元,允许可选的窥视孔连接和单元剪切。

MultiRNNCell--一个包装器,将多个单元组合成一个多层单元。

DropoutWrapper - -一个为单元的输入和/或输出连接添加dropout的包装器。

常见RNN整体结构:

LSTM和GRU,其它的还有向GridLSTM、AttentionCell等

这些在tf.keras.layers.***中都可以直接调用API

因此只需修改下面一行代码的API即可实现不同的RNN细胞结构、不同的RNN整体结构对分类性能的影响的实验。

self.rnn = nn.LSTM(in_feature, hidden_feature, num_layers) # API

由于时间问题我没有运行完代码,直接附上相关资料的实验结果:

一般来说,多层结构的复杂度更高,分类性能会更好,但是产生的时间成本也会更多。

四、 nn.dynamic_rnn输出的final_state.h和output[:,-1,:]是否是相同的?

RNN 是这样一个单元:y_t, s_t = f(x_t, s_{t-1}) ,画成图的话,就是这样:

考虑 Vanilla RNN/GRU Cell(vanilla RNN 就是最普通的 RNN,对应于 TensorFlow 里的 BasicRNNCell),工作过程如下:

这时,s_t = y_t = h_t

对于 LSTM,它的循环部件其实有两部分,一个是内部 cell 的值,另一个是根据 cell 和 output gate 计算出的 hidden state,输出层只利用 hidden state 的信息,而不直接利用 cell。这样一来,LSTM 的工作过程就是:

其中真正用于循环的状态 s_t 其实是 (c_t, h_t) 组成的 tuple(就是 TensorFlow 里的 LSTMStateTuple),而输出 y_t 仅仅是 h_t(例如网络后面再接一个全连接层然后用 softmax 做分类,这个全连接层的输入仅仅是 h_t,而没有 c_t),这时就可以看到区分 RNN 的输出和状态的意义了。

如果是一个多层的 Vanilla RNN/GRU Cell,那么一种简单的抽象办法就是,把多层 Cell 当成一个整体,当成一层大的 Cell,然后原先各层之间的关系都当成这个大的 Cell 的内部计算过程/数据流动过程,这样对外而言,多层的 RNN 和单层的 RNN 接口就是一模一样的:在外部看来,多层 RNN 只是一个内部计算更复杂的单层 RNN。图示如下:

大方框表示把多层 RNN 整体视为一层大的 Cell,而里面的小方框则对应于原先的每一层 RNN。这时,如果把大方框视为一个整体,那么这个整体进行循环所需要的状态就是各层的状态组成的集合,或者说把各层的状态放在一起组成一个 tuple:st=(st(l),st(2),…, st(l))而这个大的 RNN 单元的输出则只有原先的最上层 RNN 的输出,即整体的yt= yt(l)=ht(l)。

最后对于多层 LSTM:

和之前的例子类似,把多层 LSTM 看成一个整体,这个整体的输出就是最上层 LSTM 的输出:yt=ht(l);而这个整体进行循环所依赖的状态则是每一层状态组合成的 tuple,而每一层状态本身又是一个 (c, h) tuple,所以最后结果就是一个 tuple 的 tuple

这样一来,便可以回答问题:final_state.h和output[:,-1,:]是否是相同的

output是RNN Cell的output组成的列表,假设一共有T个时间步,那么 outputs = [y_1, y_2, ..., y_T],因此 outputs[:,-1,:] = y_T;而 final_state.h 则是最后一步的隐层状态的输出,即 h_T。

那么,到底 output[:,-1,:]等不等于final_state.h 呢?或者说 y_T 等不等于 h_T 呢?看一下上面四个图就可以知道,当且仅当使用单层 Vanilla RNN/GRU 的时候,他们才相等。

代码运行结果具体见附件代码第三问。

 

点击关注,第一时间了解华为云新鲜技术~

有关后CNN探索,如何用RNN进行图像分类的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  5. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  6. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  7. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  8. ruby-on-rails - 添加回形针新样式不影响旧上传的图像 - 2

    我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司

  9. ruby-on-rails - 在 Ruby (on Rails) 中使用 imgur API 获取图像 - 2

    我正在尝试使用Ruby2.0.0和Rails4.0.0提供的API从imgur中提取图像。我已尝试按照Ruby2.0.0文档中列出的各种方式构建http请求,但均无济于事。代码如下:require'net/http'require'net/https'defimgurheaders={"Authorization"=>"Client-ID"+my_client_id}path="/3/gallery/image/#{img_id}.json"uri=URI("https://api.imgur.com"+path)request,data=Net::HTTP::Get.new(path

  10. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

随机推荐