<p>They are not exactly the same, here is why:</p>

<h3>1. Use <code>Convolution2D</code> layers and <code>LSTM</code> layers</h3>

<p>As it is known, <code>Convolution2D</code> serves well for capturing image or spatial features, whilst <code>LSTM</code> are used to detect correlations over time. However, by stacking these kind of layers, the correlation between space and time features may not be captured properly.</p>

<h3>2. Use <code>ConvLSTM2D</code></h3>

<p>To solve this, <a href="https://arxiv.org/abs/1506.04214v1" rel="noreferrer">Xingjian Shi et al.</a> proposed a network structure able to capture spatiotemporal correlations, namely <code>ConvLSTM</code>. In Keras, this is reflected in the <a href="https://keras.io/layers/recurrent/#convlstm2d" rel="noreferrer"><code>ConvLSTM2D</code></a> class, which computes convolutional operations in both the input and the recurrent transformations.</p>

<h3>Keras code</h3>

<p>Too illustrate this, you can see <a href="https://github.com/keras-team/keras/blob/master/keras/layers/recurrent.py" rel="noreferrer">here</a> the <code>LSTM</code> code, if you go to the <code>call</code> method from <code>LSTMCell</code>, you'd only see:</p>

<pre class="lang-python prettyprint-override"><code>    x_i = K.dot(inputs_i, self.kernel_i)
    x_f = K.dot(inputs_f, self.kernel_f)
    x_c = K.dot(inputs_c, self.kernel_c)
    x_o = K.dot(inputs_o, self.kernel_o)
</code></pre>

<p>Instead, the <a href="https://github.com/keras-team/keras/blob/master/keras/layers/convolutional_recurrent.py" rel="noreferrer"><code>ConvLSTM2DCell</code></a> class calls:</p>

<pre class="lang-python prettyprint-override"><code>    x_i = self.input_conv(inputs_i, self.kernel_i, self.bias_i, padding=self.padding)
    x_f = self.input_conv(inputs_f, self.kernel_f, self.bias_f, padding=self.padding)
    x_c = self.input_conv(inputs_c, self.kernel_c, self.bias_c, padding=self.padding)
    x_o = self.input_conv(inputs_o, self.kernel_o, self.bias_o, padding=self.padding)
    h_i = self.recurrent_conv(h_tm1_i, self.recurrent_kernel_i)
    h_f = self.recurrent_conv(h_tm1_f, self.recurrent_kernel_f)
    h_c = self.recurrent_conv(h_tm1_c, self.recurrent_kernel_c)
    h_o = self.recurrent_conv(h_tm1_o, self.recurrent_kernel_o)
</code></pre>

<p>Where:</p>

<pre class="lang-python prettyprint-override"><code>def input_conv(self, x, w, b=None, padding='valid'):
    conv_out = K.conv2d(x, w, strides=self.strides,
                        padding=padding,
                        data_format=self.data_format,
                        dilation_rate=self.dilation_rate)
    if b is not None:
        conv_out = K.bias_add(conv_out, b,
                              data_format=self.data_format)
    return conv_out

def recurrent_conv(self, x, w):
    conv_out = K.conv2d(x, w, strides=(1, 1),
                        padding='same',
                        data_format=self.data_format)
    return conv_out
</code></pre>

<p>In <code>LSTM</code>, the equivalent for <code>h_x</code> (recurrent transformations) would be:</p>

<pre class="lang-python prettyprint-override"><code>K.dot(h_tm1_x, self.recurrent_kernel_x)
</code></pre>

<p>Instead of <code>ConvLSTM2D</code>'s:</p>

<pre class="lang-python prettyprint-override"><code>self.recurrent_conv(h_tm1_x, self.recurrent_kernel_x)
</code></pre>

<p>These kind of transformations could not be computed with stacked <code>Conv2D</code> and <code>LSTM</code> layers.</p>
Convolution2D + LSTM versus ConvLSTM2D
<p>Are <code>1</code> and <code>2</code> the same?</p>

<ol>
<li>Use <code>Convolution2D</code> layers and <code>LSTM</code> layers </li>
<li>Use <code>ConvLSTM2D</code></li>
</ol>

<p>If there is any difference, could you explain it for me?</p>