test_lstmp_op.py 11.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
#  Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserve.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
import unittest
import numpy as np
from op_test import OpTest

SIGMOID_THRESHOLD_MIN = -40.0
SIGMOID_THRESHOLD_MAX = 13.0
EXP_MAX_INPUT = 40.0


def identity(x):
    return x


def sigmoid(x):
    y = np.copy(x)
    y[x < SIGMOID_THRESHOLD_MIN] = SIGMOID_THRESHOLD_MIN
    y[x > SIGMOID_THRESHOLD_MAX] = SIGMOID_THRESHOLD_MAX
    return 1. / (1. + np.exp(-y))


def tanh(x):
    y = -2. * x
    y[y > EXP_MAX_INPUT] = EXP_MAX_INPUT
    return (2. / (1. + np.exp(y))) - 1.


def relu(x):
    return np.maximum(x, 0)


44
ACTIVATION = {
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
    'identity': identity,
    'sigmoid': sigmoid,
    'tanh': tanh,
    'relu': relu
}


# LSTM with recurrent projection Layer
def lstmp(
        input,  # T x 4D
        lod,  # 1 x N
        h0=None,  # N x D
        c0=None,  # N x D
        w_r=None,  # P x 5D
        w_rh=None,  # D x P
        w_b=None,  # 1 x 4D
        w_c=None,  # 1 x 3D
        is_reverse=False,
        act_gate=None,
        act_cell=None,
65
        act_cand=None,
66 67 68
        act_proj=None):
    def _step(x, w_r, w_rh, w_c, r_pre, c_pre, act_gate, act_cell, act_cand,
              act_proj):
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
        g = np.dot(r_pre, w_r)  # 1 x 4D
        g = g + x
        g = np.reshape(g, (1, g.size))
        c, g_i, g_f, g_o = np.split(g, 4, axis=1)
        if w_c is None:
            g_i = act_gate(g_i)  # 1 x D
            g_f = act_gate(g_f)  # 1 x D
        else:
            w_ic, w_fc, _ = np.split(w_c, 3, axis=1)
            g_i = act_gate(g_i + w_ic * c_pre)  # 1 x D
            g_f = act_gate(g_f + w_fc * c_pre)  # 1 x D
        c = g_f * c_pre + g_i * act_cand(c)  # 1 x D

        if w_c is None:
            g_o = act_gate(g_o)  # 1 x D
        else:
            _, _, w_oc = np.split(w_c, 3, axis=1)
            g_o = act_gate(g_o + w_oc * c)  # 1 x D
        h = g_o * act_cell(c)
        # projection
        r = np.dot(h, w_rh)
90
        r = act_proj(r)
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        return r, c

    def _reverse(x, lod):
        y = np.zeros_like(x)
        for i in range(len(lod) - 1):
            b, e = lod[i], lod[i + 1]
            y[b:e, :] = np.flip(x[b:e, :], 0)
        return y

    offset = lod[0]
    batch_size = len(offset) - 1
    # recurrent projection state
    projection = []
    cell = []
    input = _reverse(input, offset) if is_reverse else input
    if w_b is not None:
        input = input + np.tile(w_b, (offset[-1], 1))
    for i in range(batch_size):
        # compute one sequence
        seq_len = offset[i + 1] - offset[i]
        x = input[offset[i]:offset[i + 1], :]
        r_pre = np.dot(h0[i], w_rh)  # 1 x P
113
        r_pre = act_proj(r_pre)
114 115 116 117
        c_pre = c0[i]  # 1 x D
        for j in range(seq_len):
            # compute one step
            r_pre, c_pre = _step(x[j], w_r, w_rh, w_c, r_pre, c_pre, act_gate,
118
                                 act_cell, act_cand, act_proj)
119 120 121 122 123 124 125 126 127 128 129 130 131 132
            projection.append(r_pre.flatten())
            cell.append(c_pre.flatten())

    projection = np.array(projection).astype('float64')
    cell = np.array(cell).astype('float64')

    projection = _reverse(projection, offset) if is_reverse else projection
    cell = _reverse(cell, offset) if is_reverse else cell

    assert projection.shape == (input.shape[0], w_r.shape[0])  # T x P
    assert cell.shape == (input.shape[0], input.shape[1] / 4)  # T x D
    return projection, cell


133
class TestLstmpOp(OpTest):
Y
Yibing Liu 已提交
134 135 136 137
    def reset_argument(self):
        pass

    def setUp(self):
138 139 140 141 142 143 144 145 146
        self.lod = [[0, 2, 5, 7]]
        # hidden size
        self.D = 16
        # projection size
        self.P = 10

        self.act_gate = 'sigmoid'
        self.act_cell = 'tanh'
        self.act_cand = 'tanh'
147
        self.act_proj = self.act_cell
148 149 150 151 152

        self.has_initial_state = False
        self.is_reverse = False
        self.use_peepholes = True

Y
Yibing Liu 已提交
153
        self.reset_argument()
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
        self.op_type = 'lstmp'

        T = self.lod[0][-1]
        N = len(self.lod[0]) - 1

        x = np.random.normal(size=(T, 4 * self.D)).astype('float64')
        if self.has_initial_state:
            h0 = np.random.normal(size=(N, self.D)).astype('float64')
            c0 = np.random.normal(size=(N, self.D)).astype('float64')
        else:
            h0 = np.zeros((N, self.D)).astype('float64')
            c0 = np.zeros((N, self.D)).astype('float64')
        w = np.random.normal(size=(self.P, 4 * self.D)).astype('float64')
        if self.use_peepholes:
            b = np.random.normal(size=(1, 7 * self.D)).astype('float64')
        else:
            b = np.random.normal(size=(1, 4 * self.D)).astype('float64')

        w_b = b[:, 0:4 * self.D]
        w_c = b[:, 4 * self.D:] if self.use_peepholes else None
        w_rh = np.random.normal(size=(self.D, self.P)).astype('float64')
        r, c = lstmp(x, self.lod, h0, c0, w, w_rh, w_b, w_c, self.is_reverse,
176 177
                     ACTIVATION[self.act_gate], ACTIVATION[self.act_cell],
                     ACTIVATION[self.act_cand], ACTIVATION[self.act_proj])
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

        self.inputs = {'Input': (x, self.lod), 'Weight': w, 'ProjWeight': w_rh}

        self.inputs['Bias'] = b

        if self.has_initial_state:
            self.inputs['H0'] = h0
            self.inputs['C0'] = c0

        self.outputs = {
            'Projection': (r, self.lod),
            'Cell': (c, self.lod),
        }
        self.attrs = {
            'use_peepholes': self.use_peepholes,
            'is_reverse': self.is_reverse,
            'gate_activation': self.act_gate,
            'cell_activation': self.act_cell,
Y
Yibing Liu 已提交
196
            'candidate_activation': self.act_cand,
197
            'proj_activation': self.act_proj
198 199 200 201 202 203 204 205
        }

    def test_check_output(self):
        self.check_output(atol=1e-8)

    def test_check_grad(self):
        # TODO(qingqing) remove folowing lines after the check_grad is refined.
        N = len(self.lod[0]) - 1
206
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
207
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
208
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
209 210 211
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
212 213
            ['Input', 'Weight', 'ProjWeight', 'Bias'], ['Projection'],
            max_relative_error=1e-2)
214 215


216
class TestLstmpOpHasInitial(TestLstmpOp):
Y
Yibing Liu 已提交
217
    def reset_argument(self):
218 219 220 221 222
        self.has_initial_state = True

    def test_check_grad(self):
        # TODO(qingqing) remove folowing lines after the check_grad is refined.
        N = len(self.lod[0]) - 1
223
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
224
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
225
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
226 227 228
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
229 230 231
            ['Input', 'Weight', 'ProjWeight', 'Bias', 'H0', 'C0'],
            ['Projection'],
            max_relative_error=1e-2)
232 233 234

    def test_check_grad_ingore_bias(self):
        N = len(self.lod[0]) - 1
235
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
236
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
237
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
238 239 240
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
241 242
            ['Input', 'ProjWeight', 'Weight'], ['Projection'],
            max_relative_error=1e-2,
243 244 245 246
            no_grad_set=set('Bias'))

    def test_check_grad_ingore_weight(self):
        N = len(self.lod[0]) - 1
247
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
248
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
249
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
250 251 252
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
253 254
            ['Input', 'ProjWeight', 'Bias'], ['Projection'],
            max_relative_error=1e-2,
255 256
            no_grad_set=set('Weight'))

257 258 259 260 261 262 263 264 265 266 267 268
    def test_check_grad_ingore_proj_weight(self):
        N = len(self.lod[0]) - 1
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
            ['Input', 'Weight', 'Bias'], ['Projection'],
            max_relative_error=1e-2,
            no_grad_set=set('ProjWeight'))

269 270
    def test_check_grad_ingore_input(self):
        N = len(self.lod[0]) - 1
271
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
272
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
273
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
274 275 276
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
277 278
            ['Weight', 'ProjWeight', 'Bias'], ['Projection'],
            max_relative_error=1e-2,
279 280 281 282
            no_grad_set=set('Input'))

    def test_check_grad_ingore_h0(self):
        N = len(self.lod[0]) - 1
283
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
284
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
285
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
286 287 288
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
289 290
            ['Input', 'Weight', 'ProjWeight', 'Bias', 'C0'], ['Projection'],
            max_relative_error=1e-2,
291 292 293 294
            no_grad_set=set('H0'))

    def test_check_grad_ingore_c0(self):
        N = len(self.lod[0]) - 1
295
        self.outputs['OrderedP0'] = np.zeros((N, self.P)).astype('float64')
296
        self.outputs['BatchGate'] = np.zeros((N, 4 * self.D)).astype('float64')
297
        self.outputs['BatchHidden'] = np.zeros((N, self.D)).astype('float64')
298 299 300
        self.outputs['BatchCellPreAct'] = np.zeros(
            (N, self.D)).astype('float64')
        self.check_grad(
301 302
            ['Input', 'Weight', 'ProjWeight', 'Bias', 'H0'], ['Projection'],
            max_relative_error=1e-2,
303 304 305
            no_grad_set=set('C0'))


306
class TestLstmpOpRerverse(TestLstmpOp):
Y
Yibing Liu 已提交
307
    def reset_argument(self):
308 309 310
        self.is_reverse = True


311
class TestLstmpOpNotUsePeepholes(TestLstmpOp):
Y
Yibing Liu 已提交
312
    def reset_argument(self):
313 314 315
        self.use_peepholes = False


316
class TestLstmpOpLinearProjection(TestLstmpOp):
Y
Yibing Liu 已提交
317
    def reset_argument(self):
318
        self.act_proj = 'identity'
Y
Yibing Liu 已提交
319 320


321 322
if __name__ == '__main__':
    unittest.main()