Flux 循环神经网络如何构建和训练, 以实现分类

大家好, 我有简单地学过一点机器学习, 在做情感分析时出现了问题. 数据都预处理完了, 然而在训练神经网络时老是报错. Flux的文档说的实在是太简略了, model-zoo的模型跟我要做的差别有点大, 数据集也下载不下来, 无法通过数据复现学习. 所以发此贴求助.

报错信息有两个, 一个是ERROR: DimensionMismatch("arrays could not be broadcast to a common size; got a dimension with lengths 10 and 50") , 另一个是 ERROR: MethodError: no method matching logitcrossentropy(::Array{Float32,2}, ::Bool). 第一个我完全不知道哪里错了, 第二个知道哪里错了但不知怎么改. 一会报错这个, 一会儿报错那个.

我是要做一个情感分析问题, x是句向量, 一个句子用50长度的向量表示, y是分类, 有-1,0,1 三类, 数据处理的部分比较长, 我写了个样本, 跟我的数据结构大概相符和, 下面的代码中, 一共有10个样本

using Flux
using Flux:onehotbatch,logitcrossentropy,RNN


x=randn(50,10)
yy=[-1,1,0,1,1,0,0,1,-1,1]

y=onehotbatch(yy,-1:1)

m = Chain(RNN(50, 32), Dense(32, 3),softmax)

loss(x,y) = Flux.logitcrossentropy(m(x), y)
opt = ADAM(0.001)

Flux.train!(loss, params(m), zip(x,y), opt)

这一段短代码运行起来比较快, 如果需要我发出完整代码和数据也可, 我自己写的数据处理效率不高, 跑起来估计比较久.

实际上, 我对RNN几乎不懂, 让我去钻研内部的原理, 实在没那么多时间, 想做RNN纯粹是为了玩玩, 所以发此贴问下论坛里的大佬们, 以达到"API编程"的目的. 提前致谢!


目前的结论, 代码应该如下

using Flux
using Flux:onehotbatch,logitcrossentropy,RNN,update!
x =[randn(50) for i=1:10]
yy=[-1,1,0,1,1,0,0,1,-1,1]
y=Flux.chunk(onehotbatch(yy,-1:1),10)
m =RNN(1, 3)  
opt = ADAM(0.001)
function eval_model(x)
    out = m.(x)[end]
    Flux.reset!(m)
    out
end
loss(x, y) = logitcrossentropy(eval_model(x) ,y)
println("Training loss before = ", sum(loss.(x, y)))
evalcb() = @show(sum(loss.(x, y)))
Flux.train!(loss, params(m),zip(x,y), opt, cb = Flux.throttle(evalcb,1))

分享一下自己写的垃圾代码, 注释写的比较详细, 希望对大家有帮助.
https://github.com/Jerrywang959/Julia_study/blob/master/Text_analysis/GRU.jl

RNN我不太了解,不过在Flux下,你可以一层一层地来尝试,比如说依次检查

m[1](x)
m[1:2](x)
m[1:3](x)
loss(x, y)
gradient(loss, x, y)
size(m(x)) == size(y)

这些是否正常工作,如果不正常的话,观察一下size是否和你预计的一样(因为这里报错是size的问题)

谢谢, 我发现RNN一次只能接受一个样本的输入, 或者用.批量处理多个样本. 但是还是不知道如何返回params梯度下降训练…我再看看资料. Dense层的参数有2个, RNN的参数有4个, 直接train!居然不对…

修改: .不是批量处理多个样本, 而是处理一个样本的序列

找到了一篇文章救我, 学习好了回来汇报

https://www.juliabloggers.com/a-basic-rnn/

1 个赞

终于搞定了, 把上面的代码改成下面即可

using Flux
using Flux:onehotbatch,logitcrossentropy,RNN,update!
x =[randn(50) for i=1:10]
yy=[-1,1,0,1,1,0,0,1,-1,1]
y=Flux.chunk(onehotbatch(yy,-1:1),10)
m =RNN(50, 3)   # 这是错误示范, 应该是 RNN(1,3)
opt = ADAM(0.001)
function eval_model(x)
    out = m.(x)[end]
    Flux.reset!(m)
    out
end
loss(x, y) = logitcrossentropy(eval_model(x) ,y)
println("Training loss before = ", sum(loss.(x, y)))
evalcb() = @show(sum(loss.(x, y)))
Flux.train!(loss, params(m),zip(x,y), opt, cb = Flux.throttle(evalcb,1))

Flux的Dense层和RNN还不一样, 我之前的数据性质还是Dense层的形状, 因此不行. 要作为RNN的输入, 要把一个样本中的自变量和因变量分别包装在一个[]中,用zip连接输入, 然后用model.()取求其一个样本的数值. RNN会把之前的结果都储存在模型的结构里面, 因此需要取出最后一个层的输出作为分类的输出, 并在处理一次数据之后就reset!储存的数据.

感觉还是模棱两可, 不太懂, 但是好歹不报错了, 出结果了\

我貌似发现了点问题, 晚点再改


更正如下:

问题在于对于Flux默认RNN构建的理解. 例如一个RNN层RNN(10,3), 这意味着一个样本一个时间点的输入为10个, 而不是这个样本的时间点有10个, 下面的例子可以证明. 依次把输入导入RNN相当于一次把一个序列导入RNN

julia> x =[randn(50) for i=1:10];

julia> m =RNN(1, 3)
Recur(RNNCell(1, 3, tanh))

julia> m.(x[1][1:2])
2-element Array{Array{Float64,2},1}:
 [-0.5791196611769295; 0.8740786444940442; -0.8247943913349541]
 [0.9202134292020129; 0.5142736748433837; -0.09461179739106854]

julia> reset!(m)
3-element Array{Float32,1}:
 0.0
 0.0
 0.0

julia> m(x[1][1])
3×1 Array{Float64,2}:
 -0.5791196611769295
  0.8740786444940442
 -0.8247943913349541

julia> m(x[1][2])
3×1 Array{Float64,2}:
  0.9202134292020129
  0.5142736748433837
 -0.09461179739106854

所以在已经有句向量的情况下, RNN层应该是RNN(1,3) ,而不是RNN(50,3). 倘若没有句向量, 一个词的向量长度为100, 此时的 RNN层应该是RNN(100,3)

1 个赞