• Pytorch LSTM 代码解读及自定义双向 LSTM 算子 1. 理论 关于 LSTM 的理论部分可以参考 Paper Long Short-Term Memory Based Recurrent Neural Network Architectures for Large Vocabulary Speech Recognition ...
Pytorch LSTM 代码解读及自定义双向 LSTM 算子
1. 理论
关于 LSTM 的理论部分可以参考
Paper
Long Short-Term Memory Based Recurrent Neural Network Architectures for Large Vocabulary Speech Recognition
解析
Understanding LSTM Networks人人都能看懂的LSTM
Pytorch LSTM 算子
LSTM 文档
LSTMCell 前向计算过程如下：
2. 源代码
python 代码中仅仅能看到 _VF.lstm
# https://github.com/pytorch/pytorch/blob/master/torch/nn/modules/rnn.py
# line 688
if batch_sizes is None:
result = _VF.lstm(input, hx, self._flat_weights, self.bias, self.num_layers,
self.dropout, self.training, self.bidirectional, self.batch_first)
else:
result = _VF.lstm(input, batch_sizes, hx, self._flat_weights, self.bias, self.num_layers,
self.dropout, self.training, self.bidirectional)

转到 C++ 代。代码逻辑比较清晰，最终的计算是在 LSTMCell 中实现的。
# https://github.com/pytorch/pytorch/blob/49777e67303f608987ec0948c7fd8f46f6d3ca83/torch/csrc/api/src/nn/modules/rnn.cpp
# line 275
std::tie(output, hidden_state, cell_state) = torch::lstm(
input,
{state[0], state[1]},
flat_weights_,
options.with_bias(),
options.layers(),
options.dropout(),
this->is_training(),
options.bidirectional(),
options.batch_first());

# https://github.com/pytorch/pytorch/blob/1a93b96815b5c87c92e060a6dca51be93d712d09/aten/src/ATen/native/RNN.cpp
# line 855
std::tuple<Tensor, Tensor, Tensor> lstm(
const Tensor& _input, TensorList hx,
TensorList _params, bool has_biases,
int64_t num_layers, double dropout_p, bool train, bool bidirectional, bool batch_first) {
TORCH_CHECK(hx.size() == 2, "lstm expects two hidden states");
if (at::cudnn_is_acceptable(_input)) {
Tensor output, hy, cy;
lstm_cudnn_stub(_input.type().device_type(), output, hy, cy, _input, hx, _params, has_biases,
num_layers, dropout_p, train, bidirectional, batch_first);
return std::make_tuple(output, hy, cy);
}

if (use_miopen(_input, dropout_p)) {
Tensor output, hy, cy;
lstm_miopen_stub(_input.type().device_type(), output, hy, cy, _input, hx, _params, has_biases,
num_layers, dropout_p, train, bidirectional, batch_first);
return std::make_tuple(output, hy, cy);
}
check_device(_input, _params, hx);
auto input = batch_first ? _input.transpose(0, 1) : _input;
auto params = gather_params(_params, has_biases);
auto results = _lstm_impl<FullLayer, FullBidirectionalLayer>(
input, params, hx[0], hx[1], num_layers, dropout_p, train, bidirectional);
if (batch_first) {
std::get<0>(results) = std::get<0>(results).transpose(0, 1);
}
return results;
}

# line 679
template<template<typename,typename> class LayerT, template<typename,typename> class BidirLayerT, typename cell_params, typename io_type>
std::tuple<io_type, Tensor, Tensor> _lstm_impl(
const io_type& input,
const std::vector<cell_params>& params, const Tensor& hx, const Tensor& cx,
int64_t num_layers, double dropout_p, bool train, bool bidirectional) {
// It's much more useful for us to work on lists of pairs of hx and cx for each layer, so we need
// to transpose a pair of those tensors.
auto layer_hx = hx.unbind(0);
auto layer_cx = cx.unbind(0);
int64_t total_layers = layer_hx.size();
std::vector<typename LSTMCell<cell_params>::hidden_type> hiddens;
hiddens.reserve(total_layers);
for (int64_t i = 0; i < total_layers; ++i) {
hiddens.emplace_back(std::move(layer_hx[i]), std::move(layer_cx[i]));
}

auto result = _rnn_impl<LSTMCell<cell_params>, LayerT, BidirLayerT>(input, params, hiddens, num_layers, dropout_p, train, bidirectional);

// Now, we need to reverse the transposed we performed above.
std::vector<Tensor> hy, cy;
hy.reserve(total_layers); cy.reserve(total_layers);
for (auto & hidden : result.final_hidden) {
hy.push_back(std::move(std::get<0>(hidden)));
cy.push_back(std::move(std::get<1>(hidden)));
}

return std::make_tuple(result.outputs, at::stack(hy, 0), at::stack(cy, 0));
}

# line 652
template<typename CellType, template<typename,typename> class LayerT, template<typename,typename> class BidirLayerT, typename cell_params, typename io_type>
LayerOutput<io_type, std::vector<typename CellType::hidden_type>> _rnn_impl(
const io_type& input,
const std::vector<cell_params>& params,
const std::vector<typename CellType::hidden_type>& hiddens,
int64_t num_layers, double dropout_p, bool train, bool bidirectional) {
using hidden_type = typename CellType::hidden_type;
CellType cell;
if (bidirectional) {
using BidirLayer = BidirLayerT<hidden_type, cell_params>;
auto bidir_result = apply_layer_stack(BidirLayer{cell}, input, pair_vec(hiddens), pair_vec(params), num_layers, dropout_p, train);
return {bidir_result.outputs, unpair_vec(std::move(bidir_result.final_hidden))};
} else {
return apply_layer_stack(LayerT<hidden_type,cell_params>{cell}, input, hiddens, params, num_layers, dropout_p, train);
}
}

# line 623
template<typename io_type, typename hidden_type, typename weight_type>
LayerOutput<io_type, std::vector<hidden_type>>
apply_layer_stack(const Layer<io_type, hidden_type, weight_type>& layer, const io_type& input,
const std::vector<hidden_type>& hiddens, const std::vector<weight_type>& weights,
int64_t num_layers, double dropout_p, bool train) {
TORCH_CHECK(num_layers == hiddens.size(), "Expected more hidden states in stacked_rnn");
TORCH_CHECK(num_layers == weights.size(), "Expected more weights in stacked_rnn");

auto layer_input = input;
auto hidden_it = hiddens.begin();
auto weight_it = weights.begin();
std::vector<hidden_type> final_hiddens;
for (int64_t l = 0; l < num_layers; ++l) {
auto layer_output = layer(layer_input, *(hidden_it++), *(weight_it++));
final_hiddens.push_back(layer_output.final_hidden);
layer_input = layer_output.outputs;

if (dropout_p != 0 && train && l < num_layers - 1) {
layer_input = dropout(layer_input, dropout_p);
}
}

return {layer_input, final_hiddens};
}

# line
template <typename dir_hidden_type, typename cell_params>
struct FullBidirectionalLayer
: Layer<Tensor, pair_of<dir_hidden_type>, pair_of<cell_params>> {
using hidden_type = pair_of<dir_hidden_type>;
using param_type = pair_of<cell_params>;
using output_type = typename Layer<Tensor, hidden_type, param_type>::output_type;

FullBidirectionalLayer(Cell<dir_hidden_type, cell_params>& cell)
: layer_(cell) {};

output_type operator()(
const Tensor& input,
const hidden_type& input_hidden,
const param_type& params) const override {
std::vector<Tensor> step_inputs;
if (input.device().is_cpu()) {
auto input_w = params.first.linear_ih(input);
step_inputs = input_w.unbind(0);
auto fw_result = layer_(
step_inputs, input_hidden.first, params.first, true);
auto fw_output = at::stack(fw_result.outputs, 0);
input_w = params.second.linear_ih(input);
step_inputs = input_w.unbind(0);
auto rev_step_inputs = reverse(std::move(step_inputs));
auto rev_result =
layer_(rev_step_inputs, input_hidden.second, params.second, true);
std::reverse(rev_result.outputs.begin(), rev_result.outputs.end());
auto rev_output = at::stack(rev_result.outputs, 0);
return {at::cat({fw_output, rev_output}, fw_output.dim() - 1),
std::make_pair(fw_result.final_hidden, rev_result.final_hidden)};
}

step_inputs = input.unbind(0);
auto fw_result = layer_(step_inputs, input_hidden.first, params.first);
auto fw_output = at::stack(fw_result.outputs, 0);
auto rev_step_inputs = reverse(std::move(step_inputs));
auto rev_result =
layer_(rev_step_inputs, input_hidden.second, params.second);
std::reverse(rev_result.outputs.begin(), rev_result.outputs.end());
auto rev_output = at::stack(rev_result.outputs, 0);
return {at::cat({fw_output, rev_output}, fw_output.dim() - 1),
std::make_pair(fw_result.final_hidden, rev_result.final_hidden)};
}

std::vector<Tensor> reverse(std::vector<Tensor>&& x) const {
std::reverse(x.begin(), x.end());
return std::move(x);
}

FullLayer<dir_hidden_type, cell_params> layer_;
};

# line 370
template<typename hidden_type, typename cell_params>
struct FullLayer : Layer<Tensor, hidden_type, cell_params> {
using output_type =
typename Layer<Tensor, hidden_type, cell_params>::output_type;
using unstacked_output_type = LayerOutput<std::vector<Tensor>, hidden_type>;

FullLayer(Cell<hidden_type, cell_params>& cell)
: cell_(cell) {};

unstacked_output_type operator()(
const std::vector<Tensor>& step_inputs,
const hidden_type& input_hidden,
const cell_params& params,
bool pre_compute_input = false) const {
std::vector<Tensor> step_outputs;
auto hidden = input_hidden;
for (const auto& input : step_inputs) {
hidden = cell_(input, hidden, params, pre_compute_input);
step_outputs.emplace_back(hidden_as_output(hidden));
}
return {step_outputs, hidden};
}

output_type operator()(
const Tensor& inputs,
const hidden_type& input_hidden,
const cell_params& params) const override {
if (inputs.device().is_cpu()) {
const auto inputs_w = params.linear_ih(inputs);
auto unstacked_output =
(*this)(inputs_w.unbind(0), input_hidden, params, true);
return {at::stack(unstacked_output.outputs, 0),
unstacked_output.final_hidden};
}
auto unstacked_output = (*this)(inputs.unbind(0), input_hidden, params);
return {at::stack(unstacked_output.outputs, 0),
unstacked_output.final_hidden};
}

Cell<hidden_type, cell_params>& cell_;
};

# line 273
template <typename cell_params>
struct LSTMCell : Cell<std::tuple<Tensor, Tensor>, cell_params> {
using hidden_type = std::tuple<Tensor, Tensor>;

hidden_type operator()(
const Tensor& input,
const hidden_type& hidden,
const cell_params& params,
bool pre_compute_input = false) const override {
const auto& hx = std::get<0>(hidden);
const auto& cx = std::get<1>(hidden);

if (input.is_cuda()) {
TORCH_CHECK(!pre_compute_input);
auto igates = params.matmul_ih(input);
auto hgates = params.matmul_hh(hx);
auto result = at::_thnn_fused_lstm_cell(
igates, hgates, cx, params.b_ih, params.b_hh);
// Slice off the workspace argument (it's needed only for AD).
return std::make_tuple(std::get<0>(result), std::get<1>(result));
}

pre_compute_input ? input : params.linear_ih(input));
auto chunked_gates = gates.chunk(4, 1);
auto ingate = chunked_gates[0].sigmoid_();
auto forgetgate = chunked_gates[1].sigmoid_();
auto cellgate = chunked_gates[2].tanh_();
auto outgate = chunked_gates[3].sigmoid_();
auto cy = (forgetgate * cx).add_(ingate * cellgate);
auto hy = outgate * cy.tanh();
return std::make_tuple(hy, cy);
}

};

3. 自己实现双向 LSTM
经过测试下面的 custom_bilstm 和 lstm 的效果是一致的（注意权重的初始化）。
class lstm(nn.Module):
def __init__(self):
super(lstm_o, self).__init__()
self.rnn = nn.LSTM(512, 256, bidirectional=True, batch_first=True)

def forward(self, input):
self.rnn.flatten_parameters()
recurrent, _ = self.rnn(input)
return recurrent

# 借助 nn.LSTMCell 实现双向 LSTM
class custom_bilstm(nn.Module):
def __init__(self):
super(custom_bilstm, self).__init__()
self.rnn = nn.LSTMCell(512, 256)
self.rnn1 = nn.LSTMCell(512, 256)

def forward(self, input):
recurrent, f_cx = self.rnn(input[:, 0, :])
fwd = [recurrent]
for i in range(1, input.shape[1]):
recurrent, f_cx = self.rnn(input[:, i, :], (recurrent, f_cx))
fwd.append(recurrent)
forward = torch.stack(fwd, dim=0).squeeze(1)

input_reverse = torch.flip(input, dims=[1])
recurrent_b, b_cx = self.rnn1(input_reverse[:, 0, :])
bwd = [recurrent_b]
for i in range(1, input_reverse.shape[1]):
recurrent_b, b_cx = self.rnn1(input_reverse[:, i, :], (recurrent_b, b_cx))
bwd.append(recurrent_b)
backward = torch.stack(bwd, dim=0).squeeze(1)
backward_reverse = torch.flip(backward, dims=[0])

展开全文
• 今天小编就为大家分享一篇基于pytorchlstm参数使用详解，具有很好的参考价值，希望对大家有所帮助。一起跟随小编过来看看吧
• 最近阅读了pytorchlstm的源代码，发现其中有很多值得学习的地方。 首先查看pytorch当中相应的定义 \begin{array}{ll} \\ i_t = \sigma(W_{ii} x_t + b_{ii} + W_{hi} h_{t-1} + b_{hi}) \\ f_t = \sigma(W_{if}...
最近阅读了pytorch中lstm的源代码，发现其中有很多值得学习的地方。 首先查看pytorch当中相应的定义
        \begin{array}{ll} \\
i_t = \sigma(W_{ii} x_t + b_{ii} + W_{hi} h_{t-1} + b_{hi}) \\
f_t = \sigma(W_{if} x_t + b_{if} + W_{hf} h_{t-1} + b_{hf}) \\
g_t = \tanh(W_{ig} x_t + b_{ig} + W_{hg} h_{t-1} + b_{hg}) \\
o_t = \sigma(W_{io} x_t + b_{io} + W_{ho} h_{t-1} + b_{ho}) \\
c_t = f_t \odot c_{t-1} + i_t \odot g_t \\
h_t = o_t \odot \tanh(c_t) \\
\end{array}

对应公式： 圈1：

f

t

=

σ

(

W

i

f

x

t

+

b

i

f

+

W

h

f

h

t

−

1

+

b

h

f

)

f_t = \sigma(W_{if} x_t + b_{if} + W_{hf} h_{t-1} + b_{hf})

圈2：

i

t

=

σ

(

W

i

i

x

t

+

b

i

i

+

W

h

i

h

t

−

1

+

b

h

i

)

i_t = \sigma(W_{ii} x_t + b_{ii} + W_{hi} h_{t-1} + b_{hi})

圈3：

g

t

=

tanh

⁡

(

W

i

g

x

t

+

b

i

g

+

W

h

g

h

t

−

1

+

b

h

g

)

g_t = \tanh(W_{ig} x_t + b_{ig} + W_{hg} h_{t-1} + b_{hg})

圈4：

o

t

=

σ

(

W

i

o

x

t

+

b

i

o

+

W

h

o

h

t

−

1

+

b

h

o

)

o_t = \sigma(W_{io} x_t + b_{io} + W_{ho} h_{t-1} + b_{ho})

圈5：

c

t

=

f

t

⊙

c

t

−

1

+

i

t

⊙

g

t

c_t = f_t \odot c_{t-1} + i_t \odot g_t

圈6：

h

t

=

o

t

⊙

tanh

⁡

(

c

t

)

h_t = o_t \odot \tanh(c_t)

调用lstm的相应代码如下：
import torch
import torch.nn as nn
bilstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2, bidirectional=True)
input = torch.randn(5, 3, 10)
h0 = torch.randn(4, 3, 20)
c0 = torch.randn(4, 3, 20)
#with  open('D://test//input1.txt','w')  as  f:
#    f.write(str(input))
#with  open('D://test//h0.txt','w')  as  f:
#    f.write(str(h0))
#with  open('D://test//c0.txt','w')  as  f:
#    f.write(str(c0))
output, (hn, cn) = bilstm(input, (h0, c0))
print('output shape: ', output.shape)
print('hn shape: ', hn.shape)
print('cn shape: ', cn.shape)

这里的input = (seq_len, batch_size, input_size)，h_0 = (num_layers * num_directions, batch_size, hidden_size)，c_0 = (num_layers * num_directions, batch_size, hidden_size) ，输出部分的output = (5,3,40)，h0 = (4,3,20)，c0 = (4,3,20) 解读一下这里的(seq_len,batch_size,input_size)的含义，事实上如果换成(batch_size,seq_len,input_size)这样更好理解，如果在nlp操作的过程中，batch_size相当于每一个批次取出多少句子，seq_len相当于每次取出句子的长度，input_size相当于每一个句子之中单词的权重维度。 观察初始化部分的源代码 可以看出这里当为lstm层的时候，gate_size = 4*hidden_size
这里当bidirectional = True时num_directions = 2,当bidirectional = False时num_directions = 1。 self._flat_weigts_names中的数值，因为这里总共定义了两层，所以’weight_ih_l0’ = [80,10],‘weight_hh_l0’ = [80,20],‘bias_ih_l0’ = [80],‘bias_hh_l0’ = [80],‘weight_ih_l0_reverse’ = [80,10],‘weight_hh_l0_reverse’ = [80,20],‘bias_ih_l0_reverse’ = [80],‘bias_hh_l0_reverse’ = [80] ‘weight_ih_l1’ = [80,40],‘weight_hh_l1’ = [80,20],‘bias_ih_l1’ = [80],‘bias_hh_l1’ = [80] ‘weight_ih_l1_reverse’ = [80,40],‘weight_hh_l1_reverse’ = [80,20],‘bias_ih_l1_reverse’ = [80],‘bias_hh_l1_reverse’ = [80] 关于这些数组的意义回读一下之前的注释内容 这里面的

w

e

i

g

h

t

i

h

l

[

k

]

=

[

80

,

10

]

weight_ih_l[k] = [80,10]

，其中的80是由

4

∗

h

i

d

d

e

n

s

i

z

e

=

4

∗

20

4*hidden_size = 4*20

得到的，这4个参数分别为W_ii,W_if,W_ig,W_io,而weight_ih_l[k]是由这四个参数拼接得来的[80,10],同理可得到对应的weight_ih_l[k],weight_hh_l[k],bias_ih_l[k],bias_hh_l[k]的相应的含义。 其中，input = [5,3,10],h0 = [4,3,20],c0 = [4,3,20] 对应的lstm结构图如下所示 h0中的[4,3,20]中的h0[0],h0[1],h0[2],h0[3]分别对应着h[0],h[1],h[2],h[3],每一个的shape都等于[3,20] 同理c0的原理一致。 对于公式进行分析 对于第一层的内容： 公式1：

f

t

=

σ

(

x

t

[

3

,

10

]

∗

W

i

f

T

[

10

,

20

]

+

b

i

f

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

f

[

20

,

20

]

+

b

h

f

[

20

]

)

=

[

3

,

20

]

f_t = \sigma(x_t[3,10]*W_{if}^{T}[10,20] + b_{if}[20] + h_{t-1}[3,20]*W_{hf}[20,20] + b_{hf}[20]) = [3,20]

公式2：

i

t

=

σ

(

x

t

[

3

,

10

]

∗

W

i

i

T

[

10

,

20

]

+

b

i

i

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

i

[

20

,

20

]

+

b

h

i

[

20

]

)

=

[

3

,

20

]

i_t = \sigma(x_t[3,10]*W_{ii}^{T}[10,20] + b_{ii}[20] + h_{t-1}[3,20]*W_{hi}[20,20] + b_{hi}[20]) = [3,20]

公式3：

g

t

=

tanh

⁡

(

x

t

[

3

,

10

]

∗

W

i

g

T

[

10

,

20

]

+

b

i

g

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

g

[

20

,

20

]

+

b

h

g

[

20

]

)

=

[

3

,

20

]

g_t = \tanh(x_t[3,10]*W_{ig}^{T}[10,20] + b_{ig}[20] + h_{t-1}[3,20]*W_{hg}[20,20] + b_{hg}[20]) = [3,20]

公式4：

o

t

=

σ

(

x

t

[

3

,

10

]

∗

W

i

o

T

[

10

,

20

]

+

b

i

o

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

o

[

20

,

20

]

+

b

h

o

[

20

]

)

=

[

3

,

20

]

o_t = \sigma(x_t[3,10]*W_{io}^{T}[10,20] + b_{io}[20] + h_{t-1}[3,20]*W_{ho}[20,20] + b_{ho}[20]) = [3,20]

公式5：

c

t

=

f

t

[

20

,

20

]

⊙

c

t

−

1

[

20

,

20

]

+

i

t

[

20

,

20

]

⊙

g

t

[

20

,

20

]

=

[

20

,

20

]

c_t = f_t[20,20] \odot c_{t-1}[20,20] + i_t[20,20] \odot g_t[20,20] = [20,20]

公式6：

h

t

=

o

t

[

20

,

20

]

⊙

tanh

⁡

(

c

t

)

[

20

,

20

]

=

[

20

,

20

]

h_t = o_t[20,20] \odot \tanh(c_t)[20,20] = [20,20]

对于第二层以及后续层的内容分析： 公式1：

f

t

=

σ

(

x

t

[

3

,

20

]

∗

W

i

f

T

[

20

,

20

]

+

b

i

f

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

f

[

20

,

20

]

+

b

h

f

[

20

]

)

=

[

3

,

20

]

f_t = \sigma(x_t[3,20]*W_{if}^{T}[20,20] + b_{if}[20] + h_{t-1}[3,20]*W_{hf}[20,20] + b_{hf}[20]) = [3,20]

公式2：

i

t

=

σ

(

x

t

[

3

,

20

]

∗

W

i

i

T

[

20

,

20

]

+

b

i

i

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

i

[

20

,

20

]

+

b

h

i

[

20

]

)

=

[

3

,

20

]

i_t = \sigma(x_t[3,20]*W_{ii}^{T}[20,20] + b_{ii}[20] + h_{t-1}[3,20]*W_{hi}[20,20] + b_{hi}[20]) = [3,20]

公式3：

g

t

=

tanh

⁡

(

x

t

[

3

,

20

]

∗

W

i

g

T

[

20

,

20

]

+

b

i

g

[

20

]

+

h

t

−

1

[

3

,

20

]

∗

W

h

g

[

20

,

20

]

+

b

h

g

[

20

]

)

=

[

3

,

20

]

g_t = \tanh(x_t[3,20]*W_{ig}^{T}[20,20] + b_{ig}[20] + h_{t-1}[3,20]*W_{hg}[20,20] + b_{hg}[20]) = [3,20]

公式4：

o

t

=

σ

(

x

t

[

3

,

20

]

∗

W

i

o

T

[

20

,

20

]

+

b

i

o

[

20

]

+

W

h

o

[

20

,

20

]

h

t

−

1

+

b

h

o

[

20

]

)

=

[

3

,

20

]

o_t = \sigma(x_t[3,20]*W_{io}^{T}[20,20] + b_{io}[20] + W_{ho}[20,20] h_{t-1} + b_{ho}[20]) = [3,20]

公式5：

c

t

=

f

t

[

20

,

20

]

⊙

c

t

−

1

[

20

,

20

]

+

i

t

[

20

,

20

]

⊙

g

t

[

20

,

20

]

c_t = f_t[20,20] \odot c_{t-1}[20,20] + i_t[20,20] \odot g_t[20,20]

公式6：

h

t

=

o

t

[

20

,

20

]

⊙

tanh

⁡

(

c

t

)

[

20

,

20

]

h_t = o_t[20,20] \odot \tanh(c_t)[20,20]

注意在公式

f

t

=

σ

(

x

t

[

3

,

20

]

W

i

f

[

20

,

40

]

+

b

i

f

[

20

]

+

W

h

f

[

20

,

20

]

h

t

−

1

[

20

,

20

]

+

b

h

f

[

20

]

)

=

[

3

,

20

]

f_t = \sigma(x_t[3,20]W_{if}[20,40] + b_{if}[20] + W_{hf}[20,20] h_{t-1}[20,20] + b_{hf}[20]) = [3,20]

，也就是上面的公式1中，虽然

W

i

f

=

[

20

,

40

]

W_{if} = [20,40]

，但是由于是双向lstm，所以后面的维度40是前向传播加上反向传播的内容，所以对于每一层而言，

W

i

f

=

[

20

,

20

]

W_{if} = [20,20]

。另外一个就是公式之中写的是

W

i

f

∗

x

t

W_{if}*x_t

，但是pytorch之中

W

i

f

∗

x

t

W_{if}*x_t

是无法操作的，因为

W

i

f

W_{if}

的列维度与

x

t

x_t

的行维度不一致，在c++底层实现的pytorch之中有一些维度变换的操作较为复杂，这里就不一一展开了，但是原理与上述进行矩阵乘法操作的原理相同。 pytorch的lstm中的运算与rnn运算有相似之处，具体可以查看我的另外一篇博客 pytorch rnn的理解
展开全文
• pytorchlstm参数与案例理解。_wangwangstone的博客-CSDN博客_torch.lstm RNN_了不起的赵队-CSDN博客_rnn 这里主要要领清楚堆叠lstm层，使用的hidden state从lstm1着一层传到lstm2着一层，而不是一行中的几个lstm...
1.详细讲解官方文档的例子：

这里有个老哥先带你回顾一下lstm的理论知识：
pytorch中lstm参数与案例理解。_wangwangstone的博客-CSDN博客_torch.lstm
RNN_了不起的赵队-CSDN博客_rnn
这里主要要领清楚堆叠lstm层，使用的hidden state从lstm1着一层传到lstm2着一层，而不是一行中的几个lstm1单元连在一块的意思。

这个图就可以理解为3个lstm1连在一块了，为第一张图里面的一行。
import torch
import torch.nn as nn             # 神经网络模块

input = torch.randn(5, 3, 10)
# 输入的input为，序列长度seq_len=5, 每次取的minibatch大小，batch_size=3, 数据向量维数=10（仍然为x的维度）。每次运行时取3个含有5个字的句子（且句子中每个字的维度为10进行运行）

rnn = nn.LSTM(10, 20, 2)
# 输入数据x的向量维数10, 设定lstm隐藏层的特征维度20, 此model用2个lstm层。如果是1，可以省略，默认为1)
# 初始化的隐藏元和记忆元,通常它们的维度是一样的
# 2个LSTM层，batch_size=3, 隐藏层的特征维度20
h_0 = torch.randn(2, 3, 20)
c_0 = torch.randn(2, 3, 20)

# 这里有2层lstm，output是最后一层lstm的每个词向量对应隐藏层的输出,其与层数无关，只与序列长度相关
# hn,cn是所有层最后一个隐藏元和记忆元的输出
output, (h_n, c_n)= rnn(input, (h_0, c_0))
##模型的三个输入与三个输出。三个输入与输出的理解见上三输入，三输出

print(output.size(),h_n.size(),c_n.size())
输出：torch.Size([5, 3, 20]) torch.Size([2, 3, 20]) torch.Size([2, 3, 20])
2.来一个用循环神经网络lstm单元训练的mnist数据集分类案例
这里其实和之前用cnn训练mnist数据集差不多，这里也讲了使用rnn分类图像的可能性，可以去这里看看。
使用RNN进行图像分类_GavinZhou的博客-CSDN博客_rnn图像分类
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms

# Device configuration
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# Hyper-parameters
sequence_length = 28
input_size = 28
hidden_size = 128
num_layers = 2
num_classes = 10
batch_size = 100
num_epochs = 2
learning_rate = 0.01

# MNIST dataset
train_dataset = torchvision.datasets.MNIST(root='../../data/',
train=True,
transform=transforms.ToTensor(),

test_dataset = torchvision.datasets.MNIST(root='../../data/',
train=False,
transform=transforms.ToTensor())

batch_size=batch_size,
shuffle=True)

batch_size=batch_size,
shuffle=False)

# Recurrent neural network (many-to-one)
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_classes):
super(RNN, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, num_classes)

def forward(self, x):
# Set initial hidden and cell states
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)

# Forward propagate LSTM
out, _ = self.lstm(x, (h0, c0))  # out: tensor of shape (batch_size, seq_length, hidden_size)

# Decode the hidden state of the last time step
out = self.fc(out[:, -1, :])# 此处的-1说明我们只取RNN最后输出的那个hn
return out

model = RNN(input_size, hidden_size, num_layers, num_classes).to(device)

# Loss and optimizer
criterion = nn.CrossEntropyLoss()

# Train the model
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.reshape(-1, sequence_length, input_size).to(device)
labels = labels.to(device)

# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)

# Backward and optimize
loss.backward()
optimizer.step()

if (i+1) % 100 == 0:
print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch+1, num_epochs, i+1, total_step, loss.item()))

# Test the model
model.eval()
correct = 0
total = 0
images = images.reshape(-1, sequence_length, input_size).to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()

print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))

# Save the model checkpoint
torch.save(model.state_dict(), 'model.ckpt')

输出：Test Accuracy of the model on the 10000 test images: 97.95 %

首先是输入，在这里我解释一下我当时疑惑的地方，首先就是这个images.reshape，
images = images.reshape(-1, sequence_length, input_size).to(device)
我通过试验对这个问题进行了解释：CSDN

然后看文档怎么说的，关于输入
​​​​​​​

并且这个lstm类使用的是动态传参，这下就不难理解为什么可以输入这样传了。

关于输出
完整代码中有一段，意思是我用不着hn,cn这里。
        out, _ = self.lstm(x, (h0, c0))  # out: tensor of shape (batch_size, seq_length, hidden_size)
#这里还有个输出_是代表不重要的意思，python程序员约定俗成的一种写法。也就是hn与cn最后lstm层的hiddenstate 与Cellstate
# Decode the hidden state of the last time step
然后关于out = self.fc(out[:, -1, :])，需要图文并茂才能解释的清清楚楚，感兴趣，可以去看这个链接pytorch 训练过程acc_PyTorch练习（一）循环神经网络(RNN)_亲123456的博客-CSDN博客
​​​​​​​​​​​​​​
然后剩下的也就和普通的cnn分类mnist一样了。

细节补充
输入的参数列表包括:
input_size: 输入数据的特征维数，通常就是embedding_dim(词向量的维度) hidden_size: LSTM中隐层的维度 num_layers: 循环神经网络的层数 bias: 用不用偏置，default=True batch_first: 这个要注意，通常我们输入的数据shape=(batch_size,seq_length,embedding_dim),而batch_first默认是False,所以我们的输入数据最好送进LSTM之前将batch_size与seq_length这两个维度调换 dropout: 默认是0，代表不用dropout bidirectional: 默认是false，代表不用双向LSTM 输入数据包括input, (h_0, c_0):
input: shape = [seq_length, batch_size, input_size]的张量 h_0: shape = [num_layers * num_directions, batch, hidden_size]的张量，它包含了在当前这个batch_size中每个句子的初始隐藏状态，num_layers就是LSTM的层数，如果bidirectional = True,则num_directions = 2,否则就是１，表示只有一个方向 c_0: 与h_0的形状相同，它包含的是在当前这个batch_size中的每个句子的初始细胞状态。h_0,c_0如果不提供，那么默认是０ 输出数据包括output, (h_t, c_t):
output.shape = [seq_length, batch_size, num_directions * hidden_size] 它包含的LSTM的最后一层的输出特征(h_t),ｔ是batch_size中每个句子的长度. h_t.shape = [num_directions * num_layers, batch, hidden_size] c_t.shape = h_t.shapeh_n包含的是句子的最后一个单词的隐藏状态，c_t包含的是句子的最后一个单词的细胞状态，所以它们都与句子的长度seq_length无关。 output[-1]与h_t是相等的，因为output[-1]包含的正是batch_size个句子中每一个句子的最后一个单词的隐藏状态，注意LSTM中的隐藏状态其实就是输出，cell state细胞状态才是LSTM中一直隐藏的，记录着信息，这也就是博主本文想说的一个事情，output与h_t的关系。 ———————————————— 版权声明：本文为CSDN博主「Mr.Ygg」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。 原文链接：https://blog.csdn.net/weixin_44201449/article/details/111129248​​​​​​​

大家有什么疑惑的地方欢迎讨论。
参考资料
pytorch实现rnn并且对mnist进行分类
pytorch实现rnn并且对mnist进行分类_weixin_30410119的博客-CSDN博客
pytorch中lstm参数与案例理解//很细了
pytorch中lstm参数与案例理解。_wangwangstone的博客-CSDN博客_torch.lstm
pytorch nn.LSTM()参数详解//上手极快
pytorch nn.LSTM()参数详解_向阳争渡-CSDN博客_nn.lstm参数
lstm_pytorch官方文档//里面可以让你对细节恍然大悟
LSTM — PyTorch master documentation
torch.nn.lstm//整理得真的很好
精度学习torch.nn.lstm - 简书
如何理解LSTM的输入输出格式//补充
如何理解LSTM的输入输出格式_comli_cn的博客-CSDN博客_lstm输入格式
LSTM细节分析理解（pytorch版）//补充
LSTM细节分析理解（pytorch版） - 知乎
展开全文
• 双向LSTM中 output, h_n, c_n 状态详解 LSTM详解(经典之作) class torch.nn.LSTM(*args, **kwargs) 参数列表 input_size：x的特征维度 hidden_size：隐藏层的特征维度 num_layers：lstm隐层的层数，默认为1 bias：...
双向LSTM中 output, h_n, c_n 状态详解
LSTM详解(经典之作)
class torch.nn.LSTM(*args, **kwargs)
参数列表
input_size：x的特征维度 hidden_size：隐藏层的特征维度 num_layers：lstm隐层的层数，默认为1 bias：False则bih=0和bhh=0. 默认为True batch_first：True则input, output的数据格式为 (batch, seq, feature)，不包括(hn,cn) dropout：除最后一层，每一层的输出都进行dropout，默认为: 0 bidirectional：True则为双向lstm默认为False 输入：input, (h0, c0) 输出：output, (hn,cn) 输入数据格式： input(seq_len, batch, input_size) h0(num_layers * num_directions, batch, hidden_size) c0(num_layers * num_directions, batch, hidden_size)
输出数据格式： output(seq_len, batch, hidden_size * num_directions),输出格式受batch_first的影响 hn(num_layers * num_directions, batch, hidden_size)，输出格式不受batch_first的影响 cn(num_layers * num_directions, batch, hidden_size)，输出格式不受batch_first的影响
Pytorch里的LSTM单元接受的输入都必须是3维的张量(Tensors).每一维代表的意思不能弄错。
第一维体现的是序列（sequence）结构,也就是序列的个数，用文章来说，就是每个句子的长度，因为是喂给网络模型，一般都设定为确定的长度，也就是我们喂给LSTM神经元的每个句子的长度，当然，如果是其他的带有带有序列形式的数据，则表示一个明确分割单位长度，
例如是如果是股票数据内，这表示特定时间单位内，有多少条数据。这个参数也就是明确这个层中有多少个确定的单元来处理输入的数据。
第二维度体现的是batch_size，也就是一次性喂给网络多少条句子，或者股票数据中的，一次性喂给模型多少是个时间单位的数据，具体到每个时刻，也就是一次性喂给特定时刻处理的单元的单词数或者该时刻应该喂给的股票数据的条数
第三位体现的是输入的元素（elements of input），也就是，每个具体的单词用多少维向量来表示，或者股票数据中 每一个具体的时刻的采集多少具体的值，比如最低价，最高价，均价，5日均价，10均价，等等
H0-Hn是什么意思呢？就是每个时刻中间神经元应该保存的这一时刻的根据输入和上一课的时候的中间状态值应该产生的本时刻的状态值，
这个数据单元是起的作用就是记录这一时刻之前考虑到所有之前输入的状态值，形状应该是和特定时刻的输出一致
c0-cn就是开关，决定每个神经元的隐藏状态值是否会影响的下一时刻的神经元的处理，形状应该和h0-hn一致。
当然如果是双向，和多隐藏层还应该考虑方向和隐藏层的层数。
展开全文
• 本文主要依据 PytorchLSTM官方文档，对其中的模型参数、输入、输出进行详细解释。 目录 基本原理 模型参数 Parameters 输入Inputs: input, (h_0, c_0) 输出Outputs: output, (h_n, c_n) 变量Variables ...
• ## pytorch中LSTM的细节分析理解

千次阅读 多人点赞 2019-08-20 21:02:21
虽然看了一些很好的blog了解了LSTM的内部机制，但对框架中的lstm输入输出和各个参数还是没有一个清晰的认识，今天打算彻底把理论和实现联系起来，再分析一下pytorch中的LSTM实现。 先说理论部分。一个非常有名的...
• PytorchLSTM的公式表示为： it=σ(Wiixt+bii+Whih(t−1)+bhi)it=σ(Wiixt+bii+Whih(t−1)+bhi)i_t = \sigma(W_{ii} x_t + b_{ii} + W_{hi} h_{(t-1)} + b_{hi}) ft=σ(Wifxt+bif+Whfh(t−1)+bhf)ft=σ(Wifxt+...
• Pytorch中创建一个LSTM网络，参数列表如下： 参数 解释 input_size 输入数据的特征维数 hidden_size LSTM中隐层的维度 num_layers 循环神经网络的层数 bias 用不用偏置，default=True batch_first ...
• LSTM函数解释 pytorch版本torch.nn.LSTM函数图解LSTM函数引用图片 笔者最近在写有关LSTM代码，但是对于nn.LSTM函数中的有些参数还是不明白其具体含义，学习过后在此记录。 为了方便说明，我们先解释函数参数的作用...
• 1、序列模型和长期记忆网络 至此，我们已经看到了各种前馈网络。...对于LSTM，对于序列中的每个元素，都有一个对应的隐藏状态ht，原则上可以包含序列中任意点的信息。我们可以使用隐藏状态来预测语言模.
• 使用pytorch框架自定义了一个LSTM结构，压缩文件包含两个文件，一个是modules.py是编写的自定义...pytorch自定义多层双向LSTM结构的程序详解可以参考这篇博客：https://blog.csdn.net/kuan__/article/details/114652053
• pytorch LSTM1初识 一、LSTM简介1 LSTM整体图： 1）遗忘门， 决定是否丢弃或丢弃多少Cell中所存的之前的信息（也即Ct-1中）。sigmoid输出0到1之间的数字，数值的大小操作决定多少信息可以传送过去；当为0时，...
• nn.LSTM(in_dim, hidden_dim, n_layer, batch_first=True):LSTM循环神经网络 参数： input_size： 表示的是输入的矩阵特征数 hidden_size： 表示的是输出矩阵特征数 num_layers 表示堆叠几层的LSTM，默认是1 bias： ...
• 最近想了解一些关于LSTM的相关知识，在进行代码测试的时候，有个地方一直比较疑惑，关于LSTM的输入和输出问题。一直不清楚在pytorch里面该如何定义LSTM的输入和输出。首先看个pytorch官方的例子：# 首先导入LSTM需要...
• 以下为基于双向LSTM的的attention代码，采用pytorch编辑，接下来结合pytorch的语法和Attention的原理，对attention的代码进行介绍和解析。 import torch import numpy as np import ...
• 本篇文章侧重于Pytorch实践过程中对LSTM的使用，主要讲解torch.nn.LSTM输入和输出的内容，不包含LSTM的原理和细节。 LSTM作用在什么上？ LSTM是作用在==一整个序列(sequence)==上的。也就是说，我们输入的是一整个串...
• 导读：本文主要解析Pytorch Tutorial中BiLSTM_CRF代码，几乎注释了每行代码，希望本文能够帮助大家理解这个tutorial，除此之外借助代码和图解也对理解条件随机场(CRF)会有一定帮助，因为这个tutorial代码主要还是在...
• 用于序列标注的双向LSTM-CRF模型 序列标注问题输入为特征序列，输出为类别序列。 大部分情况下，标签体系越复杂准确度也越高，但相应的训练时间也会增加。因此需要根据实际情况选择合适的标签体系。 命名实体识别...
• 一、原理简介 RNN能够记忆上下文信息，因此常常用来...本文将使用较为简单的双向LSTM来完成情感分析。 二、数据处理 本文使用aclImdb数据集，包含了50000条评论数据及其标签，标签包含积极和消极两个类别。按1：1的比
• 利用lstm 和gru 训练一个语言模型 这个语言模型 就是输入一个词预测下一个词是什么 ********************************************************************************************************** emb: torch...
• 本文中的RNN泛指LSTM，GRU等等 CNN中和RNN中batchSize的默认位置是不同的。 CNN中：batchsize的位置是position 0. RNN中：batchsize的位置是position 1. 在RNN中输入数据格式： 对于最简单的RNN，我们可以使用两...
• ## pytorch中的nn.LSTM模块参数详解

千次阅读 多人点赞 2020-01-26 11:03:12
官网：https://pytorch.org/docs/stable/nn.html#torch.nn.LSTM Parameters（参数）： input_size：输入的维度 hidden_size：h的维度 num_layers：堆叠LSTM的层数，默认值为1 bias：偏置 ，默认值：True ...
• 本文针对使用pytorch实现RNN，LSTM和GRU对应参数的详细解析，相信通过阅读此文章，能够让你对循环神经网络有一个很清楚的认识。也希望你能耐心看完，相信会对你有很大的帮助。大佬直接跳过。这篇文章分析的会特别...
• 1、Pytorch中的RNN参数详解 rnn = nn.RNN(*arg,**kwargs) （1）input_size：输入xtx_txt​的维度 （2）hidden_size：输出hth_tht​的维度 （3）num_layers：网络的层数，默认为1层 （4）nonlinearity：非线性激活...
• 点击上方，选择星标，每天给你送干货！来自：python遇见NLP导读：本文主要解析Pytorch Tutorial中BiLSTM_CRF代码，几乎注释了每行代码，希望本文能够帮助大家理解这...
• bidirectional: 如果是True,那么是一个双向RNN 输入:input和h_0 input的形状是{seq_len,batch,input_size},我看大家一般喜欢用{batch,seq_len,input_size}一些 h_0的形状是(num_layers*num_birections,...
• [.src]:[torch.cuda.LongTensor of size 25x64 (GPU 0)] [.trg]:[torch.cuda.LongTensor of size 28x64 (GPU 0)] 搭建模型 Encoder Encoder用的是单层双向GRU 双向GRU的隐藏层状态输出由两个向量拼接而成 ...
• 环境配置 学习目标 学习语言模型，以及如何训练一个语言模型 ...LSTM GRU RNN的训练技巧 Gradient Clipping 如何保存和读取模型 使用库的语法介绍 项目流程 项目代码，部分运行结果与解析 Reference ...
• PyTorch的nn包下面自带很多经典的模型，我们可以快速的引入一个预训练好了的模型用来处理我们的任务，也可以单纯的添加一个这种架构的空白网络称为我们模型的子结构。其中LSTM是使用的相当多的一个，本文介绍nn.LSTM...

...