• 基于python和tensorflow的长短时记忆网络lstm代码
• 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 文档

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])



展开全文
• lstm代码matlab lstm_matlab 长短期记忆的matlab版本该代码适用于 lstm 模型。 各个文件的作用列举如下： lstmcellsetup.m：为前馈反向传播神经网络创建一个 lstmcell 层。 lstmcellff.m：执行前馈传递。 lstmcellbp...
• lstm代码matlab 场景-LSTM 此代码/实现可用于研究目的。 如果您在工作中使用此代码/数据，请引用以下论文： Huynh、Manh 和 Gita Alaghband。 “通过将场景 LSTM 与人体运动 LSTM 相结合来预测轨迹。” 视觉计算国际...
• tensorflow 实现卷积LSTM，参见文章Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcasting
• 此代码是junhyukoh用于生成序列的一个代码，其中有一个例子用于生成一组数。本文主要阐述该例子，并谈谈对lstm的简单理解。本人刚接触DNN两个月，只会caffe一点皮毛，torch，theano...据说Jeff Donahue’s 的lstm代码

代码地址 https://github.com/junhyukoh/caffe-lstm
此代码是junhyukoh用于生成序列的一个代码，其中有一个例子用于生成一组数。本文主要阐述该例子，并谈谈对lstm的简单理解。本人刚接触DNN两个月，只会caffe一点皮毛，torch，theano等不会使用，caffe下的RNN代码不多，本文是其中一个。据说Jeff Donahue’s 的lstm代码即将并入caffe。

一、lstm生成序列例子
本例中随机生成320个数字序列，作为训练样本。构造LSTM网络（分别构造了15个隐藏节点1层LSTM，50个隐藏节点1层LSTM，7个隐藏节点3层LSTM，23个隐藏节点3层LSTM。http://ethereon.github.io/netscope/#/editor 可以根据prototxt生成网络图，挺漂亮），预测不同长度的序列。经过训练之后，无测试数据，让网络自身输出序列。最终拟合的结果还是不错的。
测试时，没有输入，如何输出呢？思想是定位到最开始，一个一个输出。有如下代码：
  for (int i = 0; i < TotalLength; ++i) {
test_clip_blob->mutable_cpu_data()[0] = i > 0; //这句话没看懂啊！
const vector<Blob<float>* >& pred = test_net->ForwardPrefilled();
CHECK_EQ(pred.size(), 1);
CHECK_EQ(pred[0]->count(), 1);
//sequence.cpu_data()[i]是真实数据，*pred[0]->cpu_data()是网络输出数据
log_file << sequence.cpu_data()[i] << " " << *pred[0]->cpu_data() << std::endl;
}12345678
二、lstm简单理解
Andrej Karpathy的直觉RNN http://karpathy.github.io/2015/05/21/rnn-effectiveness/
有一篇好的翻译博客：http://blog.csdn.net/leo_is_ant/article/details/50411020

个人理解，通过网络的展开（序列有几个数字，网络展开就有多少个）学习，网络学习到了输入的数据。也可以理解为一种拟合，用于预测下一个数据或后面几个数据。LSTM是RNN的一个小trick，解决了长时记忆问题，直觉上说，LSTM自带长时记忆。 下一篇将详细介绍LSTM。

展开全文
• ## tensorflow笔记：多层LSTM代码分析

万次阅读 多人点赞 2016-10-08 17:33:34
tensorflow笔记系列： （一） tensorflow笔记：...不过好像官方只给了LSTM代码。那么我们就来看LSTM吧。LSTM的具体原理就不讲了，可以参见深度学习笔记(五)：LSTM，讲的非常清楚。坦白说，这份写LSTM代码有点难，
tensorflow笔记：多层LSTM代码分析

标签（空格分隔）： tensorflow笔记

tensorflow笔记系列：
（一）  tensorflow笔记：流程，概念和简单代码注释
（二） tensorflow笔记：多层CNN代码分析
（三）  tensorflow笔记：多层LSTM代码分析
（四）  tensorflow笔记：常用函数说明
（五） tensorflow笔记：模型的保存与训练过程可视化
（六）tensorflow笔记：使用tf来实现word2vec

之前讲过了tensorflow中CNN的示例代码，现在我们来看RNN的代码。不过好像官方只给了LSTM的代码。那么我们就来看LSTM吧。LSTM的具体原理就不讲了，可以参见深度学习笔记(五)：LSTM，讲的非常清楚。

坦白说，这份写LSTM的代码有点难，倒不是说LSTM的原理有多难，而是这份代码中使用了大量tf提供的现成的操作函数。在精简了代码的同时，也增加了初学者阅读的难度。很多函数的用法我是去看源码，然后自己写示例代码才搞懂的。当然如果能把整份代码搞清楚的话，掌握这么多操作函数还是非常有用的。

这份代码并没有完整的出现在tf给出的示例中见这里，而是只挑选了几个片段简略的介绍了一下。我当时看完之后简直是一头雾水。后来在github找到了这份代码的完整文件，发现这份文件只能在命令行里面运行，需要输入参数，例如

python ptb_word_lm.py --data_path=/tmp/simple-examples/data/ --model small

后来我改写了一下，使之可以直接运行。当然，运行之前需要先手动下载数据集，数据集的地址在这里

分段讲解

总的来看，这份代码主要由三步分组成。
第一部分，是PTBModel,也是最核心的部分，负责tf中模型的构建和各种操作(op)的定义。
第二部分，是run_epoch函数，负责将所有文本内容分批喂给模型（PTBModel）训练。
第三部分，就是main函数了，负责将第二部分的run_epoch运行多遍，也就是说，文本中的每个内容都会被重复多次的输入到模型中进行训练。随着训练的进行，会适当的进行一些参数的调整。
下面就按照这几部分来分开讲一下。我在后面提供了完整的代码，所以可以将完整代码和分段讲解对照着看。

参数设置

在构建模型和训练之前，我们首先需要设置一些参数。tf中可以使用tf.flags来进行全局的参数设置

flags = tf.flags
logging = tf.logging

flags.DEFINE_string(    # 定义变量 model的值为small, 后面的是注释
"model", "small",
"A type of model. Possible options are: small, medium, large.")

flags.DEFINE_string("data_path",   #定义下载好的数据的存放位置
"data_path")
flags.DEFINE_bool("use_fp16", False,    # 是否使用 float16格式？
"Train using 16-bit floats instead of 32bit floats")

FLAGS = flags.FLAGS     # 可以使用FLAGS.model来调用变量 model的值。

def data_type():
return tf.float16 if FLAGS.use_fp16 else tf.float32

细心的人可能会注意到上面有行代码定义了model的值为small.这个是什么意思呢？其实在后面的完整代码部分可以看到，作者在其中定义了几个参数类，分别有small,medium,large和test这4种参数。如果model的值为small，则会调用SmallConfig，其他同样。在SmallConfig中，有如下几个参数：

init_scale = 0.1        # 相关参数的初始值为随机均匀分布，范围是[-init_scale,+init_scale]
learning_rate = 1.0     # 学习速率,在文本循环次数超过max_epoch以后会逐渐降低
num_layers = 2          # lstm层数
num_steps = 20          # 单个数据中，序列的长度。
hidden_size = 200       # 隐藏层中单元数目
max_epoch = 4           # epoch<max_epoch时，lr_decay值=1,epoch>max_epoch时,lr_decay逐渐减小
max_max_epoch = 13      # 指的是整个文本循环次数。
keep_prob = 1.0         # 用于dropout.每批数据输入时神经网络中的每个单元会以1-keep_prob的概率不工作，可以防止过拟合
lr_decay = 0.5          # 学习速率衰减
batch_size = 20         # 每批数据的规模，每批有20个。
vocab_size = 10000      # 词典规模，总共10K个词

其他的几个参数类中，参数类型都是一样的，只是参数的值各有所不同。

PTBModel

这个可以说是核心部分了。而具体来说，又可以分成几个小部分：多层LSTM结构的构建，输入预处理，LSTM的循环，损失函数计算，梯度计算和修剪

LSTM结构

self.batch_size = batch_size = config.batch_size
self.num_steps = num_steps = config.num_steps
size = config.hidden_size       # 隐藏层规模
vocab_size = config.vocab_size  # 词典规模

self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])    # 输入
self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])       # 预期输出，两者都是index序列，长度为num_step

首先引进参数，然后定义2个占位符，分别表示输入和预期输出。注意此时不论是input还是target都是用词典id来表示单词的。

lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0, state_is_tuple=True)

首先使用tf.nn.rnn_cell.BasicLSTMCell定义单个基本的LSTM单元。这里的size其实就是hidden_size。
从源码中可以看到，在LSTM单元中，有2个状态值，分别是c和h，分别对应于下图中的c和h。其中h在作为当前时间段的输出的同时，也是下一时间段的输入的一部分。

那么当state_is_tuple=True的时候，state是元组形式，state=(c,h)。如果是False，那么state是一个由c和h拼接起来的张量，state=tf.concat(1,[c,h])。在运行时，则返回2值，一个是h，还有一个state。

DropoutWrapper

if is_training and config.keep_prob < 1: # 在外面包裹一层dropout
lstm_cell = tf.nn.rnn_cell.DropoutWrapper(
lstm_cell, output_keep_prob=config.keep_prob)

我们在这里使用了dropout方法。所谓dropout,就是指网络中每个单元在每次有数据流入时以一定的概率(keep prob)正常工作，否则输出0值。这是是一种有效的正则化方法，可以有效防止过拟合。在rnn中使用dropout的方法和cnn不同，推荐大家去把recurrent neural network regularization看一遍。
在rnn中进行dropout时，对于rnn的部分不进行dropout，也就是说从t-1时候的状态传递到t时刻进行计算时，这个中间不进行memory的dropout；仅在同一个t时刻中，多层cell之间传递信息的时候进行dropout，如下图所示

上图中，t-2时刻的输入xt−2<!--//--><![CDATA[//><!--
x_{t-2}
//--><!]]>首先传入第一层cell，这个过程有dropout，但是从t−2时刻的第一层cell传到t−1,t,t+1的第一层cell这个中间都不进行dropout。再从t+1时候的第一层cell向同一时刻内后续的cell传递时，这之间又有dropout了。

在使用tf.nn.rnn_cell.DropoutWrapper时，同样有一些参数，例如input_keep_prob,output_keep_prob等，分别控制输入和输出的dropout概率，很好理解。

多层LSTM结构和状态初始化

cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers, state_is_tuple=True)

# 参数初始化,rnn_cell.RNNCell.zero_stat
self._initial_state = cell.zero_state(batch_size, data_type()) 

在这个示例中，我们使用了2层的LSTM网络。也就是说，前一层的LSTM的输出作为后一层的输入。使用tf.nn.rnn_cell.MultiRNNCell可以实现这个功能。这个基本没什么好说的，state_is_tuple用法也跟之前的类似。构造完多层LSTM以后，使用zero_state即可对各种状态进行初始化。

输入预处理

with tf.device("/cpu:0"):
embedding = tf.get_variable(
# vocab size * hidden size, 将单词转成embedding描述
"embedding", [vocab_size, size], dtype=data_type())

# 将输入seq用embedding表示, shape=[batch, steps, hidden_size]
inputs = tf.nn.embedding_lookup(embedding, self._input_data)

if is_training and config.keep_prob < 1:
inputs = tf.nn.dropout(inputs, config.keep_prob)

之前有提到过，输入模型的input和target都是用词典id表示的。例如一个句子，“我/是/学生”，这三个词在词典中的序号分别是0,5,3，那么上面的句子就是[0,5,3]。显然这个是不能直接用的，我们要把词典id转化成向量,也就是embedding形式。可能有些人已经听到过这种描述了。实现的方法很简单。

第一步，构建一个矩阵，就叫embedding好了，尺寸为[vocab_size, embedding_size]，分别表示词典中单词数目，以及要转化成的向量的维度。一般来说，向量维度越高，能够表现的信息也就越丰富。

第二步，使用tf.nn.embedding_lookup(embedding,input_ids) 假设input_ids的长度为len，那么返回的张量尺寸就为[len,embedding_size]。举个栗子

# 示例代码
import tensorflow as tf
import numpy as np

sess = tf.InteractiveSession()

embedding = tf.Variable(np.identity(5,dtype=np.int32))
input_ids = tf.placeholder(dtype=tf.int32,shape=[None])
input_embedding = tf.nn.embedding_lookup(embedding,input_ids)

sess.run(tf.initialize_all_variables())
print(sess.run(embedding))
#[[1 0 0 0 0]
# [0 1 0 0 0]
# [0 0 1 0 0]
# [0 0 0 1 0]
# [0 0 0 0 1]]
print(sess.run(input_embedding,feed_dict={input_ids:[1,2,3,0,3,2,1]}))
#[[0 1 0 0 0]
# [0 0 1 0 0]
# [0 0 0 1 0]
# [1 0 0 0 0]
# [0 0 0 1 0]
# [0 0 1 0 0]
# [0 1 0 0 0]]

第三步，如果keep_prob<1， 那么还需要对输入进行dropout。不过这边跟rnn的dropout又有所不同，这边使用tf.nn.dropout。

LSTM循环

现在，多层lstm单元已经定义完毕，输入也已经经过预处理了。那么现在要做的就是将数据输入lstm进行训练了。其实很简单，只要按照文本顺序依次向cell输入数据就好了。lstm上一时间段的状态会自动参与到当前时间段的输出和状态的计算当中。

outputs = []
state = self._initial_state # state 表示 各个batch中的状态
with tf.variable_scope("RNN"):
for time_step in range(num_steps):
if time_step > 0: tf.get_variable_scope().reuse_variables()
# cell_out: [batch, hidden_size]
(cell_output, state) = cell(inputs[:, time_step, :], state) # 按照顺序向cell输入文本数据
outputs.append(cell_output)  # output: shape[num_steps][batch,hidden_size]

# 把之前的list展开，成[batch, hidden_size*num_steps],然后 reshape, 成[batch*numsteps, hidden_size]
output = tf.reshape(tf.concat(1, outputs), [-1, size])

这边要注意，tf.get_variable_scope().reuse_variables()这行代码不可少，不然会报错，应该是因为同一命名域(variable_scope)内不允许存在多个同一名字的变量的原因。

损失函数计算

# softmax_w , shape=[hidden_size, vocab_size], 用于将distributed表示的单词转化为one-hot表示
softmax_w = tf.get_variable(
"softmax_w", [size, vocab_size], dtype=data_type())
softmax_b = tf.get_variable("softmax_b", [vocab_size], dtype=data_type())
# [batch*numsteps, vocab_size] 从隐藏语义转化成完全表示
logits = tf.matmul(output, softmax_w) + softmax_b

# loss , shape=[batch*num_steps]
# 带权重的交叉熵计算
loss = tf.nn.seq2seq.sequence_loss_by_example(
[logits],   # output [batch*numsteps, vocab_size]
[tf.reshape(self._targets, [-1])],  # target, [batch_size, num_steps] 然后展开成一维【列表】
[tf.ones([batch_size * num_steps], dtype=data_type())]) # weight
self._cost = cost = tf.reduce_sum(loss) / batch_size # 计算得到平均每批batch的误差
self._final_state = state

上面代码的上半部分主要用来将多层lstm单元的输出转化成one-hot表示的向量。关于one-hot presentation和distributed presentation的区别，可以参考这里

代码的下半部分，正式开始计算损失函数。这里使用了tf提供的现成的交叉熵计算函数，tf.nn.seq2seq.sequence_loss_by_example。不知道交叉熵是什么？见这里各个变量的具体shape我都在注释中标明了。注意其中的self._targets是词典id表示的。这个函数的具体实现方式不明。我曾经想自己手写一个交叉熵，不过好像tf不支持对张量中单个元素的操作。

梯度计算

之前已经计算得到了每批数据的平均误差。那么下一步，就是根据误差来进行参数修正了。当然，首先必须要求梯度

self._lr = tf.Variable(0.0, trainable=False)  # lr 指的是 learning_rate
tvars = tf.trainable_variables()

通过tf.trainable_variables 可以得到整个模型中所有trainable=True的Variable。实际得到的tvars是一个列表，里面存有所有可以进行训练的变量。

grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
config.max_grad_norm)

用来计算导数。该函数的定义如下所示

def gradients(ys,
xs,
aggregation_method=None):

虽然可选参数很多，但是最常使用的还是ys和xs。根据说明得知，ys和xs都可以是一个tensor或者tensor列表。而计算完成以后，该函数会返回一个长为len(xs)的tensor列表，列表中的每个tensor是ys中每个值对xs[i]求导之和。如果用数学公式表示的话，那么 g = tf.gradients(y,x)可以表示成 gi=∑j=0len(y)∂yj∂xig=[g0,g1,...,glen(x)]<!--//--><![CDATA[//><!--
g_i=\sum_{j=0}^{len(y)} \frac{\partial y_j}{\partial x_i}\\
g=[g_0,g_1,...,g_{len(x)}]
//--><!]]>梯度修剪

tf.clip_by_global_norm
修正梯度值，用于控制梯度爆炸的问题。梯度爆炸和梯度弥散的原因一样，都是因为链式法则求导的关系，导致梯度的指数级衰减。为了避免梯度爆炸，需要对梯度进行修剪。
先来看这个函数的定义：

def clip_by_global_norm(t_list, clip_norm, use_norm=None, name=None):

输入参数中：t_list为待修剪的张量, clip_norm 表示修剪比例(clipping ratio).

函数返回2个参数： list_clipped，修剪后的张量，以及global_norm，一个中间计算量。当然如果你之前已经计算出了global_norm值，你可以在use_norm选项直接指定global_norm的值。

那么具体如何计算呢？根据源码中的说明，可以得到
list_clipped[i]=t_list[i] * clip_norm / max(global_norm, clip_norm),其中
global_norm = sqrt(sum([l2norm(t)**2 for t in t_list]))

如果你更熟悉数学公式，则可以写作 Lic=Lit∗Ncmax(Nc,Ng)Ng=∑i(Lit)2‾‾‾‾‾‾‾‾√<!--//--><![CDATA[//><!--
L_c^i=\frac{L_t^i*N_c}{max(N_c,N_g)} \\
N_g = \sqrt{\sum_i(L_t^i)^2}
//--><!]]>
其中， Lic<!--//--><![CDATA[//><!--
L_c^i
//--><!]]>和Lig<!--//--><![CDATA[//><!--
L_g^i
//--><!]]>代表t_list[i]和list_clipped[i]， Nc<!--//--><![CDATA[//><!--
N_c
//--><!]]>和Ng<!--//--><![CDATA[//><!--
N_g
//--><!]]>代表clip_norm 和 global_norm的值。
其实也可以看到其实Ng<!--//--><![CDATA[//><!--
N_g
//--><!]]>就是t_list的L2模。上式也可以进一步写作 Lic={Lit,(Ng<=Nc)Lit∗NcNg,(Ng>Nc)Ng=∑i(Lit)2‾‾‾‾‾‾‾‾√<!--//--><![CDATA[//><!--
\begin{align}
&L_c^i=
\begin{cases}
L_t^i, (N_g<=N_c) \\
L_t^i*\frac{N_c}{N_g},(N_g>N_c)
\end{cases}\\
&N_g = \sqrt{\sum_i(L_t^i)^2}
\end{align}
//--><!]]>
也就是说，当t_list的L2模大于指定的Nc<!--//--><![CDATA[//><!--
N_c
//--><!]]>时，就会对t_list做等比例缩放

优化参数

之前的代码已经求得了合适的梯度，现在需要使用这些梯度来更新参数的值了。

# 梯度下降优化，指定学习速率
# self._train_op = optimizer.minimize(grads)

还有一点，要留心一下self._train_op，只有该操作被模型执行，才能对参数进行优化。如果没有执行该操作，则参数就不会被优化。

run_epoch

这就是我之前讲的第二部分，主要功能是将所有文档分成多个批次交给模型去训练，同时记录模型返回的cost,state等记录，并阶段性的将结果输出。

def run_epoch(session, model, data, eval_op, verbose=False):
"""Runs the model on the given data."""
# epoch_size 表示批次总数。也就是说，需要向session喂这么多批数据
epoch_size = ((len(data) // model.batch_size) - 1) // model.num_steps  # // 表示整数除法
start_time = time.time()
costs = 0.0
iters = 0
state = session.run(model.initial_state)
for step, (x, y) in enumerate(reader.ptb_iterator(data, model.batch_size,
model.num_steps)):
fetches = [model.cost, model.final_state, eval_op] # 要获取的值
feed_dict = {}      # 设定input和target的值
feed_dict[model.input_data] = x
feed_dict[model.targets] = y
for i, (c, h) in enumerate(model.initial_state):
feed_dict[c] = state[i].c
feed_dict[h] = state[i].h
cost, state, _ = session.run(fetches, feed_dict) # 运行session,获得cost和state
costs += cost   # 将 cost 累积
iters += model.num_steps

if verbose and step % (epoch_size // 10) == 10:  # 也就是每个epoch要输出10个perplexity值
print("%.3f perplexity: %.3f speed: %.0f wps" %
(step * 1.0 / epoch_size, np.exp(costs / iters),
iters * model.batch_size / (time.time() - start_time)))

return np.exp(costs / iters)

基本没什么其他的，就是要注意传入的eval_op。在训练阶段，会往其中传入train_op，这样模型就会自动进行优化；而在交叉检验和测试阶段，传入的是tf.no_op，此时模型就不会优化。

main函数

这里略去了数据读取和参数读取的代码，只贴了最关键的一部分。

with tf.Graph().as_default(), tf.Session() as session:
# 定义如何对参数变量初始化
initializer = tf.random_uniform_initializer(-config.init_scale,
config.init_scale)
with tf.variable_scope("model", reuse=None,initializer=initializer):
m = PTBModel(is_training=True, config=config)
with tf.variable_scope("model", reuse=True,initializer=initializer):
mvalid = PTBModel(is_training=False, config=config)
mtest = PTBModel(is_training=False, config=eval_config)

注意这里定义了3个模型，对于训练模型，is_trainable=True; 而对于交叉检验和测试模型，is_trainable=False

    summary_writer = tf.train.SummaryWriter('/tmp/lstm_logs',session.graph)

tf.initialize_all_variables().run()  # 对参数变量初始化

for i in range(config.max_max_epoch):   # 所有文本要重复多次进入模型训练
# learning rate 衰减
# 在 遍数小于max epoch时， lr_decay = 1 ; > max_epoch时， lr_decay = 0.5^(i-max_epoch)
lr_decay = config.lr_decay ** max(i - config.max_epoch, 0.0)
m.assign_lr(session, config.learning_rate * lr_decay) # 设置learning rate

print("Epoch: %d Learning rate: %.3f" % (i + 1, session.run(m.lr)))
train_perplexity = run_epoch(session, m, train_data, m.train_op,verbose=True) # 训练困惑度
print("Epoch: %d Train Perplexity: %.3f" % (i + 1, train_perplexity))
valid_perplexity = run_epoch(session, mvalid, valid_data, tf.no_op()) # 检验困惑度
print("Epoch: %d Valid Perplexity: %.3f" % (i + 1, valid_perplexity))

test_perplexity = run_epoch(session, mtest, test_data, tf.no_op())  # 测试困惑度
print("Test Perplexity: %.3f" % test_perplexity)

注意上面train_perplexity操作中传入了m.train_op，表示要进行优化，而在valid_perplexity和test_perplexity中均传入了tf.no_op，表示不进行优化。

完整代码和注释

# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# Unless required by applicable law or agreed to in writing, software
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# ==============================================================================

"""Example / benchmark for building a PTB LSTM model.
Trains the model described in:
(Zaremba, et. al.) Recurrent Neural Network Regularization
http://arxiv.org/abs/1409.2329
There are 3 supported model configurations:
===========================================
| config | epochs | train | valid  | test
===========================================
| small  | 13     | 37.99 | 121.39 | 115.91
| medium | 39     | 48.45 |  86.16 |  82.07
| large  | 55     | 37.87 |  82.62 |  78.29
The exact results may vary depending on the random initialization.
The hyperparameters used in the model:
- init_scale - the initial scale of the weights
- learning_rate - the initial value of the learning rate
- num_layers - the number of LSTM layers
- num_steps - the number of unrolled steps of LSTM
- hidden_size - the number of LSTM units
- max_epoch - the number of epochs trained with the initial learning rate
- max_max_epoch - the total number of epochs for training
- keep_prob - the probability of keeping weights in the dropout layer
- lr_decay - the decay of the learning rate for each epoch after "max_epoch"
- batch_size - the batch size
The data required for this example is in the data/ dir of the
PTB dataset from Tomas Mikolov's webpage:
$wget http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz$ tar xvf simple-examples.tgz
To run:
\$ python ptb_word_lm.py --data_path=simple-examples/data/
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import time

import numpy as np
import tensorflow as tf

flags = tf.flags
logging = tf.logging

flags.DEFINE_string(
"model", "small",
"A type of model. Possible options are: small, medium, large.")
flags.DEFINE_bool("use_fp16", False,
"Train using 16-bit floats instead of 32bit floats")

FLAGS = flags.FLAGS

def data_type():
return tf.float16 if FLAGS.use_fp16 else tf.float32

class PTBModel(object):
"""The PTB model."""

def __init__(self, is_training, config):
"""
:param is_training: 是否要进行训练.如果is_training=False,则不会进行参数的修正。
"""
self.batch_size = batch_size = config.batch_size
self.num_steps = num_steps = config.num_steps
size = config.hidden_size
vocab_size = config.vocab_size

self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])    # 输入
self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])       # 预期输出，两者都是index序列，长度为num_step

# Slightly better results can be obtained with forget gate biases
# initialized to 1 but the hyperparameters of the model would need to be
# different than reported in the paper.
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0, state_is_tuple=True)
if is_training and config.keep_prob < 1: # 在外面包裹一层dropout
lstm_cell = tf.nn.rnn_cell.DropoutWrapper(
lstm_cell, output_keep_prob=config.keep_prob)
cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers, state_is_tuple=True) # 多层lstm cell 堆叠起来

self._initial_state = cell.zero_state(batch_size, data_type()) # 参数初始化,rnn_cell.RNNCell.zero_state

with tf.device("/cpu:0"):
embedding = tf.get_variable(
"embedding", [vocab_size, size], dtype=data_type()) # vocab size * hidden size, 将单词转成embedding描述
# 将输入seq用embedding表示, shape=[batch, steps, hidden_size]
inputs = tf.nn.embedding_lookup(embedding, self._input_data)

if is_training and config.keep_prob < 1:
inputs = tf.nn.dropout(inputs, config.keep_prob)

# Simplified version of tensorflow.models.rnn.rnn.py's rnn().
# This builds an unrolled LSTM for tutorial purposes only.
# In general, use the rnn() or state_saving_rnn() from rnn.py.
#
# The alternative version of the code below is:
#
# inputs = [tf.squeeze(input_, [1])
#           for input_ in tf.split(1, num_steps, inputs)]
# outputs, state = tf.nn.rnn(cell, inputs, initial_state=self._initial_state)
outputs = []
state = self._initial_state # state 表示 各个batch中的状态
with tf.variable_scope("RNN"):
for time_step in range(num_steps):
if time_step > 0: tf.get_variable_scope().reuse_variables()
# cell_out: [batch, hidden_size]
(cell_output, state) = cell(inputs[:, time_step, :], state)
outputs.append(cell_output)  # output: shape[num_steps][batch,hidden_size]

# 把之前的list展开，成[batch, hidden_size*num_steps],然后 reshape, 成[batch*numsteps, hidden_size]
output = tf.reshape(tf.concat(1, outputs), [-1, size])

# softmax_w , shape=[hidden_size, vocab_size], 用于将distributed表示的单词转化为one-hot表示
softmax_w = tf.get_variable(
"softmax_w", [size, vocab_size], dtype=data_type())
softmax_b = tf.get_variable("softmax_b", [vocab_size], dtype=data_type())
# [batch*numsteps, vocab_size] 从隐藏语义转化成完全表示
logits = tf.matmul(output, softmax_w) + softmax_b

# loss , shape=[batch*num_steps]
# 带权重的交叉熵计算
loss = tf.nn.seq2seq.sequence_loss_by_example(
[logits],   # output [batch*numsteps, vocab_size]
[tf.reshape(self._targets, [-1])],  # target, [batch_size, num_steps] 然后展开成一维【列表】
[tf.ones([batch_size * num_steps], dtype=data_type())]) # weight
self._cost = cost = tf.reduce_sum(loss) / batch_size # 计算得到平均每批batch的误差
self._final_state = state

if not is_training:  # 如果没有训练，则不需要更新state的值。
return

self._lr = tf.Variable(0.0, trainable=False)
tvars = tf.trainable_variables()
# clip_by_global_norm: 梯度衰减，具体算法为t_list[i] * clip_norm / max(global_norm, clip_norm)
# clip_by_global_norm 用于控制梯度膨胀,前两个参数t_list, global_norm, 则
# t_list[i] * clip_norm / max(global_norm, clip_norm)
# 其中 global_norm = sqrt(sum([l2norm(t)**2 for t in t_list]))

# 梯度下降优化，指定学习速率

self._new_lr = tf.placeholder(
tf.float32, shape=[], name="new_learning_rate")     #   用于外部向graph输入新的 lr值
self._lr_update = tf.assign(self._lr, self._new_lr)     #   使用new_lr来更新lr的值

def assign_lr(self, session, lr_value):
# 使用 session 来调用 lr_update 操作
session.run(self._lr_update, feed_dict={self._new_lr: lr_value})

@property
def input_data(self):
return self._input_data

@property
def targets(self):
return self._targets

@property
def initial_state(self):
return self._initial_state

@property
def cost(self):
return self._cost

@property
def final_state(self):
return self._final_state

@property
def lr(self):
return self._lr

@property
def train_op(self):
return self._train_op

class SmallConfig(object):
"""Small config."""
init_scale = 0.1        #
learning_rate = 1.0     # 学习速率
num_layers = 2          # lstm层数
num_steps = 20          # 单个数据中，序列的长度。
hidden_size = 200       # 隐藏层规模
max_epoch = 4           # epoch<max_epoch时，lr_decay值=1,epoch>max_epoch时,lr_decay逐渐减小
max_max_epoch = 13      # 指的是整个文本循环13遍。
keep_prob = 1.0
lr_decay = 0.5          # 学习速率衰减
batch_size = 20         # 每批数据的规模，每批有20个。
vocab_size = 10000      # 词典规模，总共10K个词

class MediumConfig(object):
"""Medium config."""
init_scale = 0.05
learning_rate = 1.0
num_layers = 2
num_steps = 35
hidden_size = 650
max_epoch = 6
max_max_epoch = 39
keep_prob = 0.5
lr_decay = 0.8
batch_size = 20
vocab_size = 10000

class LargeConfig(object):
"""Large config."""
init_scale = 0.04
learning_rate = 1.0
num_layers = 2
num_steps = 35
hidden_size = 1500
max_epoch = 14
max_max_epoch = 55
keep_prob = 0.35
lr_decay = 1 / 1.15
batch_size = 20
vocab_size = 10000

class TestConfig(object):
"""Tiny config, for testing."""
init_scale = 0.1
learning_rate = 1.0
num_layers = 1
num_steps = 2
hidden_size = 2
max_epoch = 1
max_max_epoch = 1
keep_prob = 1.0
lr_decay = 0.5
batch_size = 20
vocab_size = 10000

def run_epoch(session, model, data, eval_op, verbose=False):
"""Runs the model on the given data."""
# epoch_size 表示批次总数。也就是说，需要向session喂这么多次数据
epoch_size = ((len(data) // model.batch_size) - 1) // model.num_steps  # // 表示整数除法
start_time = time.time()
costs = 0.0
iters = 0
state = session.run(model.initial_state)
for step, (x, y) in enumerate(reader.ptb_iterator(data, model.batch_size,
model.num_steps)):
fetches = [model.cost, model.final_state, eval_op] # 要进行的操作，注意训练时和其他时候eval_op的区别
feed_dict = {}      # 设定input和target的值
feed_dict[model.input_data] = x
feed_dict[model.targets] = y
for i, (c, h) in enumerate(model.initial_state):
feed_dict[c] = state[i].c   # 这部分有什么用？看不懂
feed_dict[h] = state[i].h
cost, state, _ = session.run(fetches, feed_dict) # 运行session,获得cost和state
costs += cost   # 将 cost 累积
iters += model.num_steps

if verbose and step % (epoch_size // 10) == 10:  # 也就是每个epoch要输出10个perplexity值
print("%.3f perplexity: %.3f speed: %.0f wps" %
(step * 1.0 / epoch_size, np.exp(costs / iters),
iters * model.batch_size / (time.time() - start_time)))

return np.exp(costs / iters)

def get_config():
if FLAGS.model == "small":
return SmallConfig()
elif FLAGS.model == "medium":
return MediumConfig()
elif FLAGS.model == "large":
return LargeConfig()
elif FLAGS.model == "test":
return TestConfig()
else:
raise ValueError("Invalid model: %s", FLAGS.model)

# def main(_):
if __name__=='__main__':
if not FLAGS.data_path:
raise ValueError("Must set --data_path to PTB data directory")
print(FLAGS.data_path)

train_data, valid_data, test_data, _ = raw_data

config = get_config()
eval_config = get_config()
eval_config.batch_size = 1
eval_config.num_steps = 1

with tf.Graph().as_default(), tf.Session() as session:
initializer = tf.random_uniform_initializer(-config.init_scale, # 定义如何对参数变量初始化
config.init_scale)
with tf.variable_scope("model", reuse=None,initializer=initializer):
m = PTBModel(is_training=True, config=config)   # 训练模型， is_trainable=True
with tf.variable_scope("model", reuse=True,initializer=initializer):
mvalid = PTBModel(is_training=False, config=config) #  交叉检验和测试模型，is_trainable=False
mtest = PTBModel(is_training=False, config=eval_config)

summary_writer = tf.train.SummaryWriter('/tmp/lstm_logs',session.graph)

tf.initialize_all_variables().run()  # 对参数变量初始化

for i in range(config.max_max_epoch):   # 所有文本要重复多次进入模型训练
# learning rate 衰减
# 在 遍数小于max epoch时， lr_decay = 1 ; > max_epoch时， lr_decay = 0.5^(i-max_epoch)
lr_decay = config.lr_decay ** max(i - config.max_epoch, 0.0)
m.assign_lr(session, config.learning_rate * lr_decay) # 设置learning rate

print("Epoch: %d Learning rate: %.3f" % (i + 1, session.run(m.lr)))
train_perplexity = run_epoch(session, m, train_data, m.train_op,verbose=True) # 训练困惑度
print("Epoch: %d Train Perplexity: %.3f" % (i + 1, train_perplexity))
valid_perplexity = run_epoch(session, mvalid, valid_data, tf.no_op()) # 检验困惑度
print("Epoch: %d Valid Perplexity: %.3f" % (i + 1, valid_perplexity))

test_perplexity = run_epoch(session, mtest, test_data, tf.no_op())  # 测试困惑度
print("Test Perplexity: %.3f" % test_perplexity)

# if __name__ == "__main__":
#     tf.app.run()



展开全文
• ## Theano LSTM代码解析

千次阅读 2016-05-12 16:30:37
Theano官方LSTM代码解析  本文内容纯属个人观点，有误的地方敬请指正批评。  阅读本文需要一定的python,lstm和Theano的基础知识。    Theano是一种符号语言，它的优点在与它的自动求导机制以及GPU的透明性，...
Theano官方LSTM代码解析
本文内容纯属个人观点，有误的地方敬请指正批评。
阅读本文需要一定的python,lstm和Theano的基础知识。

Theano是一种符号语言，它的优点在与它的自动求导机制以及GPU的透明性，缺点在于调试时极为不便。
基于Theano开发的Deep Learning代码一般分为四个部分：数据准备，模型建立，预训练和训练，测试。代码按照这个顺序进行分析。
Theano的LSTM代码见https://github.com/lisa-lab/DeepLearningTutorials
其中LSTM的代码见lstm.py和imdb.py，数据集为imdb.pkl。
代码如下：
If __name__ = ‘__main__’:
train_lstm(max_epochs=100,test_size=500,)
/*我们从主函数下手开始分析，__main__函数调用了train_lstm()函数，LSTM的整个过程都是从这个函数开始。*/
def train_lstm(
dim_proj=128,
/*word embedding的维数和隐藏层的维数，用默认值。（word embedding是一种将一个词转成一个向量的过程，这里不去深究）*/
patience=10，
/*该参数用于earlystop，如果10轮迭代的误差没有降低，就进行earlystop*/
max_epochs=5000,/*迭代次数（将训练集迭代一轮位一个epoch）*/
diapFreq=10,/*每更新10次显示训练过程，即显示训练、验证和测试误差*/
decay_c=0, /*参数U的正则权重，U为隐藏层ht到输出层的参数*/
lrate=0.0001, /*sgd用的学习率*/
n_words=10000, /*词典大小，用于数据预处理部分，将词用该词在词典中的ID表示，超过10000的用1表示，仅仅用于数据，不做深究*/
encoder='lstm', /*一个标识符，可以去掉，但是必须是lstm*/
saveto='lstm_model.npz', /*保存最好模型的文件，保存训练误差，验证误差和测试误差等等*/
validFreq=370，/*验证频率*/
saveFreq=1110,/*保存频率*/
maxlen=100,/*序列的最大长度，超出长度的数据被抛弃，见数据处理部分*/
batch_size=16,/*训练的batch大小*/
valid_batch_size=64,/*验证集用的*batch大小*/
dataset='imdb',/*用于数据预处理的参数，全局变量datasets的key'imdb'的value为两个处理数据的函数*/
noise_std=0.,/*后边好像没有出现过，恕我无知，我也不知道这是什么鬼*/!!!
use_dropout=True,/*控制dropout，不用dropout的话运行速度较快，但是效果不好，dropout不太好解释，以一定的概率随机丢弃模型中的一些节点，这样可以综合多种模型的结果，进行投票。需要自行补充deeplearning的知识*/
test_size=-1,/*测试集大小，如果为正，就只用这么多测试样本*/
):
model_options = locals().copy()
print("model options", model_options)
/*首先将当先的函数局部作用于的参数copy到字典model_options中，后面的很多函数就以model_options作为参数进行参数传递。*/
maxlen=maxlen)
/*获取处理数据的函数，定义在全局作用域的get_dataset函数中
调用以下代码：*/
defget_dataset(name):
return datasets[name][0], datasets[name][1]
而第二个函数prepare_data负责数据的转换，在训练和测试的时候先将训练数据和测试数据的横轴和纵轴调换，并使数据维度保持一致，后面详细讲*/
if test_size > 0:
# The testset is sorted by size, but we want to keep random
# sizeexample.  So we must select a randomselection of the
# examples.
idx =numpy.arange(len(test[0]))
numpy.random.shuffle(idx)
idx =idx[:test_size]
test =([test[0][n] for n in idx], [test[1][n] for n in idx])
/*如果我们设置了test_size的大小，这个步骤就是从测试集中随机找test_size个作为测试数据，如果没有设置test_size,会用所有的测试集数据做测试。原来的测试数据是根据长度排列的（imdb数据自身特点），这里做了一次打散*/
ydim = numpy.max(train[1]) + 1
model_options['ydim'] = ydim
/*ydim为标签y的维数，因为是从0开始的，所以后面+1，并将它加入模型参数中*/
print('Building model')
# This create the initial parameters as numpy ndarrays.
# Dict name (string) -> numpy ndarray
params = init_params(model_options)
/*模型建立阶段，首先初始化各种参数，调用了全局作用域的函数init_params()*/
definit_params(options):
"""
Global (not LSTM) parameter. For the embedingand the classifier.
"""
/*根据源代码的注释，embedding，claffier和lstm层的参数不是一起初始化的，这里先初始化embedding，claffier的参数*/
params = OrderedDict()
/*将所有的参数放在一个名为params的OrderedDict中*/
# embedding
randn =numpy.random.rand(options['n_words'],
options['dim_proj'])
params['Wemb'] = (0.01 * randn).astype(config.floatX)
/* 随机生成embedding矩阵，这里为10000 * 128维的，因为词典大小是10000，也就是说，词的ID范围是1-10000，我们将每个词转换成一个128维的向量，所以这里生成了一个10000*128的矩阵，每个词转换成它的ID的那一行的128维向量。比如“我”这个词的ID是5，那么“我”这个词就用params['Wemb']矩阵的第5行表示，第5行就是一个128维的向量，这里用随机生成的矩阵表示，作为示例。（这是下边用到的，这里先给出解释）*/

params =get_layer(options['encoder'])[0](options,
params,
prefix=options['encoder'])

/*这里需要调用：
def get_layer(name):
fns = layers[name]
return fns
layers = {'lstm':(param_init_lstm, lstm_layer)}
我们还记得options['encoder']只能为lstm，这里返回了layers['lstm']的第一项param_init_lstm函数：
defparam_init_lstm(options, params, prefix='lstm'):
"""
Init the LSTM parameter:

:see: init_params
"""
W =numpy.concatenate([ortho_weight(options['dim_proj']),
ortho_weight(options['dim_proj']),
ortho_weight(options['dim_proj']),
ortho_weight(options['dim_proj'])],axis=1)

params[_p(prefix, 'W')] = W
U =numpy.concatenate([ortho_weight(options['dim_proj']),
ortho_weight(options['dim_proj']),
ortho_weight(options['dim_proj']),
ortho_weight(options['dim_proj'])], axis=1)
params[_p(prefix,'U')] = U
b = numpy.zeros((4 * options['dim_proj'],))
params[_p(prefix,'b')] = b.astype(config.floatX)

return params

这里初始化了LSTM的参数，_p()这个函数是连接变量名的，
ortho_weight()函数用来生成正交的矩阵，先生成n*n的矩阵，做svd分解，这里不再细述。将4个矩阵列连接起来是为了方便运算，在计算门的时候，一步到位。*/
# classifier
params['U'] = 0.01 *numpy.random.randn(options['dim_proj'],
options['ydim']).astype(config.floatX)
params['b'] =numpy.zeros((options['ydim'],)).astype(config.floatX)
/*初始化softmax分类器的参数 */
return params

# This create Theano Shared Variable from theparameters.
# Dict name (string) -> Theano Tensor SharedVariable
# params and tparams have different copy of theweights.
tparams = init_tparams(params)
/*init_tparams()将上一步初始化的参数转为Theano.shared类型。*/

y, f_pred_prob,f_pred, cost) = build_model(tparams, model_options)
/* 建立模型，代码：*/

def build_model(tparams, options):
trng= RandomStreams(SEED)
#随机数生成器
#Used for dropout.
use_noise= theano.shared(numpy_floatX(0.))
#是否用dropout
x= tensor.matrix('x', dtype='int64')
y= tensor.vector('y', dtype='int64')

n_timesteps= x.shape[0]#x的行代表steps（经过变换）
n_samples= x.shape[1]   #x的列代表不同的样本

emb= tparams['Wemb'][x.flatten()].reshape([n_timesteps,
n_samples, options['dim_proj']])

#将词用向量表示，如前所述
proj= get_layer(options['encoder'])[1](tparams, emb, options,
prefix=options['encoder'],
/*隐藏层的计算，实现代码如下（黑体）：
nsteps= state_below.shape[0]
//state_below是输入x和w，b计算后的输入节点。同上，第一维代表step
if state_below.ndim == 3:
n_samples = state_below.shape[1]
else:
n_samples = 1
//如果输入三维的x，那么样本数就是第二维的长度，否则就是只有一个样本
def_slice(_x, n, dim):
if_x.ndim == 3:
return_x[:, :, n * dim:(n + 1) * dim]
return_x[:, n * dim:(n + 1) * dim]
// 切片，计算的时候是几个门一起计算，切片将各个门的值分开
def_step(m_, x_, h_, c_):
preact= tensor.dot(h_, tparams[_p(prefix, 'U')])
preact+= x_

i= tensor.nnet.sigmoid(_slice(preact, 0, options['dim_proj']))
f= tensor.nnet.sigmoid(_slice(preact, 1, options['dim_proj']))
o= tensor.nnet.sigmoid(_slice(preact, 2, options['dim_proj']))
c= tensor.tanh(_slice(preact, 3, options['dim_proj']))

c= f * c_ + i * c
c= m_[:, None] * c + (1. - m_)[:, None] * c_

h = o * tensor.tanh(c)
h= m_[:, None] * h + (1. - m_)[:, None] * h_

return h, c
/* 隐藏层计算，i：输入门；f：忘记门；o：输出门；c：cell。
根据LSTM隐藏层的计算公式，state_below就是input_node。
c = m_[:, None] * c + (1. -m_)[:, None] * c_
h = m_[:, None] * h + (1. -m_)[:, None] * h_
state_below = (tensor.dot(state_below,tparams[_p(prefix, 'W')]) +
tparams[_p(prefix, 'b')])
//这里相当于计算input_node
dim_proj = options['dim_proj']
outputs_info=[tensor.alloc(numpy_floatX(0.),
n_samples,
dim_proj),
tensor.alloc(numpy_floatX(0.),
n_samples,
dim_proj)],
name=_p(prefix,'_layers'),
n_steps=nsteps)
return rval[0]
//整个函数最后返回rval[0]也就是最后的h
上述即为隐藏层的计算过程，继续分析之前的代码*/

ifoptions['encoder'] == 'lstm':
proj= (proj * mask[:, :, None]).sum(axis=0)
ifoptions['use_dropout']:
proj= dropout_layer(proj, use_noise, trng)
//如果dropout，就调用dropout_layer()随机丢弃一些隐藏层
pred= tensor.nnet.softmax(tensor.dot(proj, tparams['U'])
+ tparams['b'])
//预测就是隐藏层h的均值输入到softmax函数得到的
pred.argmax(axis=1),name='f_pred')

off = 1e-8
ifpred.dtype == 'float16':
off= 1e-6

cost= -tensor.log(pred[tensor.arange(n_samples), y] + off).mean()
// 损失函数
returnuse_noise, x, mask, y, f_pred_prob, f_pred, cost

//模型建立完成，返回，回到train_lstm()函数
if decay_c > 0.:
decay_c = theano.shared(numpy_floatX(decay_c),name='decay_c')
weight_decay = 0.
weight_decay += (tparams['U'] ** 2).sum()
weight_decay *= decay_c
cost += weight_decay
/* 如果加入正则，损失函数加上L2损失。
f_cost = theano.function([x, mask, y], cost,name='f_cost')
/*编译损失函数*/
// 求导，并编译求导函数
lr = tensor.scalar(name='lr')
//优化
print('Optimization')
kf_valid = get_minibatches_idx(len(valid[0]),valid_batch_size)
kf_test = get_minibatches_idx(len(test[0]),valid_batch_size)
/* 将验证集和测试机分成batchs：get_minibatchs_idx,返回batchID和对应的样本序号，省略*/
print("%d train examples" % len(train[0]))
print("%d valid examples" % len(valid[0]))
print("%d test examples" % len(test[0]))

history_errs = []
best_p = None
if validFreq == -1:
validFreq = len(train[0]) // batch_size
if saveFreq == -1:
saveFreq = len(train[0]) // batch_size
/*如果未设置验证频率和保存频率，那么就设置为一个epoch，len（train[0]）/batch_size就是一个epoch*/
uidx = 0  #the number of update done   记录更新的次数
estop = False # early stop

start_time = time.time()

try:
for eidx in range(max_epochs):
n_samples = 0

# Get new shuffled index for thetraining set.
kf = get_minibatches_idx(len(train[0]),batch_size, shuffle=True)
//得到训练数据的mini_batchs
for _, train_index in kf:
uidx += 1
#更新次数+1
use_noise.set_value(1.)
#设置drop_out

# Select the random examplesfor this minibatch
y = [train[1][t] for t in train_index]
x = [train[0][t]for t in train_index]

# Get the data in numpy.ndarrayformat
# This swap the axis!
# Return something of shape(minibatch maxlen, n samples)
x, mask, y = prepare_data(x, y)
/*这里需要注意prepare_data()函数：
defprepare_data(seqs, labels, maxlen=None):
"""Create the matrices from thedatasets.

This pad each sequence to the same length: the lengthof the
longuest sequence or maxlen.

if maxlen is set, we will cut all sequence to thismaximum
length.

This swap the axis!
"""
# x: a listof sentences
lengths = [len(s) for s in seqs]
/*如果设置了maxlen那么将扔掉所有step超过maxlen的数据*/
if maxlen is not None:
new_seqs = []
new_labels= []
new_lengths = []
for l, s, y in zip(lengths, seqs,labels):
if l < maxlen:
new_seqs.append(s)
new_labels.append(y)
new_lengths.append(l)
/*得到经过maxlen过滤的数据集*/
lengths = new_lengths
labels = new_labels
seqs = new_seqs
/*lengths 代表过滤后的数据每一行的长度，也就是每一个样本的steps，seqs保存数据矩阵。Labels保存标签*/
if len(lengths) < 1:
return None, None, None

n_samples = len(seqs)
maxlen = numpy.max(lengths)
/*原始的x（也就是seqs），每一行代表一个样本，同一行不同的列代表他们的先后次序，矩阵seqs的行数len(seqs)代表样本个数，maxlen为step最长的样本的step的长度*/

x = numpy.zeros((maxlen, n_samples)).astype('int64')
.astype(theano.config.floatX)
for idx, s in enumerate(seqs):
x[:lengths[idx], idx] = s

n_samples += x.shape[1]

f_update(lrate)
//更新 ，在后边专门讲

if numpy.isnan(cost) ornumpy.isinf(cost):
return 1., 1., 1.

if numpy.mod(uidx, dispFreq) == 0:
print('Epoch ', eidx, 'Update ', uidx,'Cost ', cost)
//判断是否到了显示频率
if saveto and numpy.mod(uidx, saveFreq)== 0:
print('Saving...')
//判断是否到了保存频率，并保存params
if best_p is notNone:
params = best_p
else:
params = unzip(tparams)
numpy.savez(saveto,history_errs=history_errs, **params)
pickle.dump(model_options, open('%s.pkl'% saveto, 'wb'), -1)
print('Done')
//判断是否到了验证频率，到了就计算各种误差，并更新best_p
if numpy.mod(uidx, validFreq) == 0:

use_noise.set_value(0.)

train_err = pred_error(f_pred,prepare_data, train, kf)

valid_err = pred_error(f_pred,prepare_data, valid,

kf_valid)

test_err = pred_error(f_pred,prepare_data, test, kf_test)

history_errs.append([valid_err, test_err])

if (best_p is None or

valid_err <=numpy.array(history_errs)[:,
0].min()):

best_p = unzip(tparams)

print( ('Train ', train_err, 'Valid ',valid_err,
'Test ', test_err) )

if (len(history_errs) > patience and
valid_err >=numpy.array(history_errs)[:-patience,
0].min()):
print('EarlyStop!')
estop = True
break
print('Seen %d samples' % n_samples)

if estop:
break

except KeyboardInterrupt:
print("Traininginterupted")
end_time = time.time()
if best_p is not None:
zipp(best_p, tparams)
else:
best_p = unzip(tparams)

use_noise.set_value(0.)
kf_train_sorted =get_minibatches_idx(len(train[0]), batch_size)
train_err = pred_error(f_pred,prepare_data, train, kf_train_sorted)
valid_err = pred_error(f_pred,prepare_data, valid, kf_valid)
test_err = pred_error(f_pred, prepare_data,test, kf_test)

print( 'Train ', train_err, 'Valid ',valid_err, 'Test ', test_err )
if saveto:
numpy.savez(saveto,train_err=train_err,
valid_err=valid_err,test_err=test_err,
history_errs=history_errs,**best_p)
print('The code run for %d epochs, with %fsec/epochs' % (
(eidx + 1), (end_time - start_time) /(1. * (eidx + 1))))
print( ('Training took %.1fs' %
(end_time - start_time)),file=sys.stderr)
return train_err, valid_err,test_err

//后边就是计算误差和执行时间了
至此，该LSTM的代码已经解析结束，优化方法就不详细讲解了，代码提供了3种不同的优化函数，基本思想都是通过cost的梯度和learning_rate更新参数啦，其中sgd需要设置learning_rate，另两种方法是自动选择学习率的。

展开全文
• LSTM学士 我的有关LSTM代码和图表的存储库。 不允许复制和粘贴！ 该存储库中的内容不是免费使用的（只有具有适当参考的图才能使用）。 一切都是由@ Poopaye-96创建的
• Matlab多层lstm代码使用具有CNN功能的深度双向LSTM在视频序列中进行动作识别 我们已经使用caffe模式使用matlab脚本“ oneFileFeatures ...”从视频中提取了深层功能。 每个CSV文件代表一个视频的功能。 使用“ ...
• QA系统Match-LSTM代码研读 背景 在QA模型中，Match-LSTM是较早提出的，使用Prt-Net边界模型。本文是对阅读其实现代码的总结。主要思路是对照着论文和代码，对论文中模型的关键结构，查看代码中的具体实现。参考代码...
• ## LSTM代码实现

千次阅读 2020-09-07 22:16:06
单项LSTM 重要的输入参数 input_size: 每一个时步(time_step)输入到lstm单元的维度.(实际输入的数据size为[batch_size, input_size]) hidden_size: 确定了隐含状态hidden_state的维度. 可以简单的看成: 构造了一...
• cvpr2015，LSTM代码的链接为https://github.com/wojzaremba/lstm，作为一个初学Torch的小白，决定直接从代码入手，再根据Torch的说明包，与网上资料来进一步学习Torch。 不多说，直接上代码。 该代码分为三个lua...
• RNN学习笔记（六）-GRU，LSTM 代码实现
• lstm代码，和官方教程解释得不是很详细，故对 theano lstm进行一些分析理解。整体框架理解这份代码实现的功能是利用RNN（LSTM）对IMDB每部电影的评论页面的评论进行情感分类。
• 用 tf.keras.Sequential 实现RNN、LSTM的相关代码实现过程中，一直报在model.fit 那一步， 报这个错误 AttributeError: 'NoneType' object has no attribute 'dtype'，后来整了半天发现是numpy版本问题，于是重新...
• ## 简单LSTM代码讲解

千次阅读 2018-12-16 17:24:32
[lstm_layer_nums, 2 , batch_size, fw_num_units] ( 2 同上) 其他的和tf.nn.dynamic_rnn一样，需要 tf.transpose(outputs, [1,0,2])[-1] 获得最后一层，不同的就是 tf.nn.rnn_cell.MultiRNNCell 创建了多层...
• rnn 与gru区别 两者网络接口相同，只需要在网络定义里替换一下相互名字即可 self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_...rnn与lstm网络接口定义一样，只是换接口名称，但是lstm前向增加了cell_s
• def my_lstm_layer(input_reps, lstm_dim=int(768 / 2), input_lengths=None, scope_name="my_rnn", reuse=False, is_training=True, dropout_rate=0.2, use_cudnn=False): ''' input_reps: [batch_size, seq_...

...