LIMA
Libre Multilingual Analyzer — C++ API
Loading...
Searching...
No Matches
bilstm.h
Go to the documentation of this file.
1// Copyright 2021 CEA LIST
2// SPDX-FileCopyrightText: 2022 CEA LIST <gael.de-chalendar@cea.fr>
3//
4// SPDX-License-Identifier: MIT
5
6#ifndef DEEPLIMA_SRC_INFERENCE_EIGEN_BILSTM_H
7#define DEEPLIMA_SRC_INFERENCE_EIGEN_BILSTM_H
8
9#include <iostream>
10#include <vector>
11#include <eigen3/Eigen/Dense>
12
13#include "op_base.h"
14
15namespace deeplima
16{
17namespace eigen_impl
18{
19
20template<class M=Eigen::MatrixXf, class V=Eigen::VectorXf>
22{
27
29 {
30 weight_ih = arg.weight_ih.cast<float>();
31 weight_hh = arg.weight_hh.cast<float>();
32 bias_ih = arg.bias_ih.cast<float>();
33 bias_hh = arg.bias_hh.cast<float>();
34 }
35
36 std::ostream& operator<< (std::ostream& out) const
37 {
38 out << weight_ih << std::endl;
39 out << weight_hh << std::endl;
40 out << bias_ih << std::endl;
41 out << bias_hh << std::endl;
42 return out;
43 }
44};
45
46template<class M=Eigen::MatrixXf, class V=Eigen::VectorXf>
48{
49 typedef M matrix_t;
50
53
59
60 std::ostream& operator<< (std::ostream& out) const
61 {
62 out << fw << std::endl;
63 out << bw << std::endl;
64 return out;
65 }
66};
67
68template<class M=Eigen::MatrixXf, class V=Eigen::VectorXf>
70{
71 typedef M matrix_t;
73
74 std::vector<layer_params_t> layers;
75
77 params_multilayer_bilstm_t(const std::vector<layer_params_t>& arg)
78 : layers(arg)
79 {}
80
82 {
83 layers.resize(1);
84 layers[0].fw.init_from_double(arg.fw);
85 layers[0].bw.init_from_double(arg.bw);
86 }
87
88 std::ostream& operator<< (std::ostream& out) const
89 {
90 for (size_t i = 0; i < layers.size(); ++i)
91 {
92 out << layers[i].fw << std::endl;
93 out << layers[i].bw << std::endl;
94 }
95 return out;
96 }
97};
98
99template<class M, class V, class T>
100class Op_BiLSTM : public Op_Base
101{
102public:
104
106 {
107 workbench_t(const params_t& p, uint32_t input_size, bool precomputed_input)
108 : temps(p.layers.size()),
109 outputs(p.layers.size()),
110 fw_h(p.layers.size()),
111 fw_c(p.layers.size()),
112 bw_h(p.layers.size()),
113 bw_c(p.layers.size()),
114 zeros(p.layers.size()),
115 m_precomputed_input(precomputed_input)
116 {
117 for (size_t i = 0; i < p.layers.size(); ++i)
118 {
119 uint32_t hidden_size = p.layers[i].fw.weight_ih.rows() / 4;
120 temps[i] = M::Zero((0 == i && precomputed_input) ? 0 : hidden_size * 8, input_size);
121 outputs[i] = M::Zero(hidden_size * 2, input_size);
122 zeros[i] = V::Zero(hidden_size);
123 fw_h[i] = V::Zero(hidden_size);
124 fw_c[i] = V::Zero(hidden_size);
125 bw_h[i] = V::Zero(hidden_size);
126 bw_c[i] = V::Zero(hidden_size);
127 }
128 }
129
131 {
132 return outputs[outputs.size() - 1];
133 }
134
135 virtual ~workbench_t() {}
136
137 std::vector<M> temps;
138 std::vector<M> outputs;
139 std::vector<V> fw_h, fw_c, bw_h, bw_c;
140 std::vector<V> zeros;
142 };
143
144public:
145 typedef V Vector;
146
147 virtual std::shared_ptr<Op_Base::workbench_t> create_workbench(uint32_t input_size, const std::shared_ptr<param_base_t> params, bool precomputed_input=false) const override
148 {
149 assert(input_size > 0);
150 assert(nullptr != params);
151 auto p = std::dynamic_pointer_cast<const params_t>(params);
152
153 return std::make_shared<workbench_t>(*p, input_size, precomputed_input);
154 }
155
156 virtual bool supports_precomputing() const
157 {
158 return true;
159 }
160
161 virtual void precompute_inputs(const std::shared_ptr<param_base_t> params, const M& inputs, M& outputs, int64_t first_column)
162 {
163 assert(nullptr != params);
164 auto p = std::dynamic_pointer_cast<const params_t>(params);
165 const auto& layer = p->layers[0];
166
167 size_t hidden_size = layer.fw.weight_ih.rows() / 4;
168
169 auto output_block = outputs.block(0, first_column, outputs.rows(), inputs.cols());
170 // Top rows - forward pass
171 output_block.topRows(hidden_size * 4) = (layer.fw.weight_ih * inputs).colwise() + layer.fw.bias_ih;
172 // Bottom rows - backward pass
173 output_block.bottomRows(hidden_size * 4) = (layer.bw.weight_ih * inputs).colwise() + layer.bw.bias_ih;
174 }
175
176 virtual size_t execute(std::shared_ptr<Op_Base::workbench_t> pwb,
177 const M& input_matrix,
178 const std::shared_ptr<param_base_t> params,
179 size_t input_begin,
180 size_t input_end,
181 Vector& fw_h,
182 Vector& fw_c,
183 Vector& bw_h,
184 Vector& bw_c)
185 {
186 assert(nullptr != pwb);
187 assert(nullptr != params);
188 const auto& p = *std::dynamic_pointer_cast<const params_t>(params);
189 const typename params_t::layer_params_t& layer = p.layers[0];
190
191 auto wb = std::dynamic_pointer_cast<workbench_t>(pwb);
192 //M& temp = wb->temps[0];
193 M& output = wb->outputs[0];
194// TODO should it be used?
195 // const V& zero = wb->zeros[0];
196
197 size_t hidden_size = layer.fw.weight_ih.rows() / 4;
198 V c = fw_c; //V::Zero(hidden_size);
199
200 // 4th block() arg is the column COUNT: the sentence width, not input_end.
201 M input = input_matrix.block(0, input_begin, input_matrix.rows(),
202 input_end - input_begin);
203 // TODO: rewrite this!!!
204
205 V s, g_u, g_o, g_if;
206 M temp = M::Zero(hidden_size * 8, input_end - input_begin);
207
208 if (wb->m_precomputed_input)
209 {
210 // precomputed inputs
211 temp = input;
212 }
213 else
214 {
215 // Top rows - forward pass
216 temp.topLeftCorner(hidden_size * 4, input_end - input_begin) = (layer.fw.weight_ih * input).colwise() + layer.fw.bias_ih;
217
218 // Bottom rows - backward pass
219 temp.bottomLeftCorner(hidden_size * 4, input_end - input_begin) = (layer.bw.weight_ih * input).colwise() + layer.bw.bias_ih;
220 }
221
222 // Forward pass
223 s = temp.col(0).topRows(hidden_size * 4) + layer.fw.bias_hh + layer.fw.weight_hh * fw_h;
224 step_fw(hidden_size, 0, s, g_u, g_o, g_if, c, output);
225
226 for (int t = 1; t < input.cols(); t++)
227 {
228 s = temp.col(t).topRows(hidden_size * 4) + layer.fw.bias_hh + layer.fw.weight_hh * output.col(t-1).topRows(hidden_size);
229 step_fw(hidden_size, t, s, g_u, g_o, g_if, c, output);
230 }
231
232 int t = input.cols() - 1;
233 //V c_fw = c;
234 wb->fw_c[0] = c;
235 wb->fw_h[0] = output.col(t).topRows(hidden_size);
236 //t--;
237
238 // Backward pass
239 c = bw_c; //V::Zero(hidden_size);
240 s = temp.col(t).bottomRows(hidden_size * 4) + layer.bw.bias_hh + layer.bw.weight_hh * bw_h; //zero;
241 step_bw(hidden_size, t, s, g_u, g_o, g_if, c, output);
242
243 for (t = input.cols() - 2; t >= 0; t--)
244 {
245 s = temp.col(t).bottomRows(hidden_size * 4) + layer.bw.bias_hh + layer.bw.weight_hh * output.col(t+1).bottomRows(hidden_size);
246 step_bw(hidden_size, t, s, g_u, g_o, g_if, c, output);
247 }
248 wb->bw_c[0] = c;
249 wb->bw_h[0] = output.col(0).bottomRows(hidden_size);
250
251 fw_h = wb->fw_h[0];
252 fw_c = wb->fw_c[0];
253 bw_h = wb->bw_h[0];
254 bw_c = wb->bw_c[0];
255
256 return 0;
257 }
258
259 virtual size_t execute(std::shared_ptr<Op_Base::workbench_t> pwb,
260 const M& input_matrix,
261 const std::shared_ptr<param_base_t> params,
262 const size_t input_begin,
263 const size_t input_end)
264 {
265 assert(nullptr != pwb);
266 assert(nullptr != params);
267 const auto& p = *std::dynamic_pointer_cast<const params_t>(params);
268 auto wb = std::dynamic_pointer_cast<workbench_t>(pwb);
269
270 for (size_t i = 0; i < p.layers.size(); ++i)
271 {
272 V s, g_u, g_o, g_if;
273 M& temp = wb->temps[i]; //.leftCols(input_end - input_begin);
274 M& output = wb->outputs[i]; //.leftCols(input_end - input_begin);
275 const typename params_t::layer_params_t& layer = p.layers[i];
276
277 size_t hidden_size = layer.fw.weight_ih.rows() / 4;
278 V c = V::Zero(hidden_size);
279 const V& zero = wb->zeros[i];
280
281 if (0 == i && wb->m_precomputed_input)
282 {
283 // precomputed inputs. The 4th block() arg is the column COUNT, so it must
284 // be the sentence width (input_end - input_begin), not input_end. With the
285 // latter, any sentence after the first (input_begin > 0) read the wrong
286 // columns, corrupting multi-sentence parses.
287 temp = input_matrix.block(0, input_begin, input_matrix.rows(),
288 input_end - input_begin);
289 }
290 else
291 {
292 if (0 == i)
293 {
294 // Copy the input block into a dense, contiguous matrix BEFORE the
295 // product. input_matrix is the shared vectorizer tensor whose leading
296 // dimension is wider than the feature count, so a lazily-evaluated
297 // block (input_matrix.block(...)) in the Eigen product reads columns
298 // >0 at the wrong stride -> only column 0 is correct. Forcing a dense
299 // copy respects the stride. (Deeper layers use outputs[i-1], which is
300 // tightly allocated, so they were unaffected.)
301 const M input = input_matrix.block(0, input_begin, input_matrix.rows(),
302 input_end - input_begin);
303 // Top rows - forward pass
304 temp.topLeftCorner(hidden_size * 4, input_end - input_begin)
305 = (layer.fw.weight_ih * input).colwise()
306 + layer.fw.bias_ih;
307
308 // Bottom rows - backward pass
309 temp.bottomLeftCorner(hidden_size * 4, input_end - input_begin)
310 = (layer.bw.weight_ih * input).colwise()
311 + layer.bw.bias_ih;
312 }
313 else
314 {
315 //const M input = wb->outputs[i-1].leftCols(input_end - input_begin);
316 // Top rows - forward pass
317 temp.topLeftCorner(hidden_size * 4, input_end - input_begin)
318 = (layer.fw.weight_ih * wb->outputs[i-1].leftCols(input_end - input_begin)).colwise()
319 + layer.fw.bias_ih;
320 // Bottom rows - backward pass
321 temp.bottomLeftCorner(hidden_size * 4, input_end - input_begin)
322 = (layer.bw.weight_ih * wb->outputs[i-1].leftCols(input_end - input_begin)).colwise()
323 + layer.bw.bias_ih;
324 }
325 }
326
327 // Forward pass
328 forward_pass(hidden_size, layer.fw, temp, s, g_u, g_o, g_if, c, output, zero, 0, input_end - input_begin);
329 wb->fw_c[i] = c;
330 wb->fw_h[i] = output.col(input_end - input_begin - 1).topRows(hidden_size);
331
332 // Backward pass
333 c = V::Zero(hidden_size);
334 backward_pass(hidden_size, layer.bw, temp, s, g_u, g_o, g_if, c, output, zero, 0, input_end - input_begin);
335 wb->bw_c[i] = c;
336 // output is written at LOCAL columns [0, input_end - input_begin); the
337 // backward hidden state at the first position is column 0, not the global
338 // accumulated offset input_begin (which overruns the matrix once several
339 // sentences have been processed). Matches the single-layer overload.
340 wb->bw_h[i] = output.col(0).bottomRows(hidden_size);
341 }
342
343 return 0;
344 }
345
346
347protected:
348 inline void forward_pass(
349 size_t hidden_size, // [in] LSTM parameter
350 const params_lstm_t<M, V>& fw, // [in] forward LSTM params
351 M& input, // [in] precomputed input
352 V& s, // [in] result before gating
353 V& g_u, // [temp] update gate
354 V& g_o, // [temp] output gate
355 V& g_if, // [temp] input and forget gates
356 V& c, // [in/out] cell state
357 M& output, // [out] matrix of output states
358 const V& initial_h, // [in] initial cell hidden state
359 int begin, // [in] first column
360 int end // [in] the column after the last one
361 )
362 {
363 s = input.col(begin).topRows(hidden_size * 4) + fw.bias_hh + fw.weight_hh * initial_h;
364 step_fw(hidden_size, 0, s, g_u, g_o, g_if, c, output);
365
366 for (auto t = begin + 1; t < end; t++)
367 {
368 s = input.col(t).topRows(hidden_size * 4) + fw.bias_hh + fw.weight_hh * output.col(t-1).topRows(hidden_size);
369 step_fw(hidden_size, t, s, g_u, g_o, g_if, c, output);
370 }
371 }
372
373 inline void backward_pass(
374 size_t hidden_size, // [in] LSTM parameter
375 const params_lstm_t<M, V>& bw, // [in] backward LSTM params
376 M& input, // [in] precomputed input
377 V& s, // [in] result before gating
378 V& g_u, // [temp] update gate
379 V& g_o, // [temp] output gate
380 V& g_if, // [temp] input and forget gates
381 V& c, // [in/out] cell state
382 M& output, // [out] matrix of output states
383 const V& initial_h, // [in] initial cell hidden state
384 int begin, // [in] first column
385 int end // [in] the column after the last one
386 )
387 {
388 int t = end - 1;
389 s = input.col(t).bottomRows(hidden_size * 4) + bw.bias_hh + bw.weight_hh * initial_h;
390 step_bw(hidden_size, t, s, g_u, g_o, g_if, c, output);
391
392 for (t = end - 2; t >= begin; t--)
393 {
394 s = input.col(t).bottomRows(hidden_size * 4) + bw.bias_hh + bw.weight_hh * output.col(t+1).bottomRows(hidden_size);
395 step_bw(hidden_size, t, s, g_u, g_o, g_if, c, output);
396 }
397 }
398
399 inline void step_fw(
400 size_t hidden_size, // [in] LSTM parameter
401 size_t t, // [in] step (position in output)
402 const V& s, // [in] result before gating
403 V& g_u, // [temp] update gate
404 V& g_o, // [temp] output gate
405 V& g_if, // [temp] input and forget gates
406 V& c, // [in/out] cell state
407 M& output // [out] matrix of output states
408 )
409 {
410 g_if = 1 / (1 + Eigen::exp( 0 - s.segment(0, hidden_size * 2).array() ) );
411 g_u = 2 / (1 + Eigen::exp( 0 - 2 * s.segment(hidden_size * 2, hidden_size).array() ) ) - 1; // tanh
412 g_o = 1 / (1 + Eigen::exp( 0 - s.segment(hidden_size * 3, hidden_size).array() ) );
413
414 c = g_if.segment(0, hidden_size).cwiseProduct(g_u) + g_if.segment(hidden_size, hidden_size).cwiseProduct(c);
415
416 output.col(t).topRows(hidden_size) = g_o.cwiseProduct(c.unaryExpr( [](float x) { return my_tanh(x); } ));
417 }
418
419 inline void step_bw(
420 size_t hidden_size, // [in] LSTM parameter
421 size_t t, // [in] step (position in output)
422 const V& s, // [in] result before gating
423 V& g_u, // [temp] update gate
424 V& g_o, // [temp] output gate
425 V& g_if, // [temp] input and forget gates
426 V& c, // [in/out] cell state
427 M& output // [out] matrix of output states
428 )
429 {
430 g_if = 1 / (1 + Eigen::exp( 0 - s.segment(0, hidden_size * 2).array() ) );
431 g_u = 2 / (1 + Eigen::exp( 0 - 2 * s.segment(hidden_size * 2, hidden_size).array() ) ) - 1; // tanh
432 g_o = 1 / (1 + Eigen::exp( 0 - s.segment(hidden_size * 3, hidden_size).array() ) );
433
434 c = g_if.segment(0, hidden_size).cwiseProduct(g_u) + g_if.segment(hidden_size, hidden_size).cwiseProduct(c);
435
436 output.col(t).bottomRows(hidden_size) = g_o.cwiseProduct(c.unaryExpr( [](float x) { return my_tanh(x); } ));
437 }
438
439 inline static float my_tanh(float x)
440 {
441 return ::tanhf(x);
442 }
443};
444
445} // namespace eigen_impl
446} // namespace deeplima
447
448#endif
void step_fw(size_t hidden_size, size_t t, const V &s, V &g_u, V &g_o, V &g_if, V &c, M &output)
Definition bilstm.h:399
static float my_tanh(float x)
Definition bilstm.h:439
void forward_pass(size_t hidden_size, const params_lstm_t< M, V > &fw, M &input, V &s, V &g_u, V &g_o, V &g_if, V &c, M &output, const V &initial_h, int begin, int end)
Definition bilstm.h:348
virtual std::shared_ptr< Op_Base::workbench_t > create_workbench(uint32_t input_size, const std::shared_ptr< param_base_t > params, bool precomputed_input=false) const override
Definition bilstm.h:147
virtual size_t execute(std::shared_ptr< Op_Base::workbench_t > pwb, const M &input_matrix, const std::shared_ptr< param_base_t > params, const size_t input_begin, const size_t input_end)
Definition bilstm.h:259
virtual bool supports_precomputing() const
Definition bilstm.h:156
params_multilayer_bilstm_t< M, V > params_t
Definition bilstm.h:103
void step_bw(size_t hidden_size, size_t t, const V &s, V &g_u, V &g_o, V &g_if, V &c, M &output)
Definition bilstm.h:419
virtual size_t execute(std::shared_ptr< Op_Base::workbench_t > pwb, const M &input_matrix, const std::shared_ptr< param_base_t > params, size_t input_begin, size_t input_end, Vector &fw_h, Vector &fw_c, Vector &bw_h, Vector &bw_c)
Definition bilstm.h:176
void backward_pass(size_t hidden_size, const params_lstm_t< M, V > &bw, M &input, V &s, V &g_u, V &g_o, V &g_if, V &c, M &output, const V &initial_h, int begin, int end)
Definition bilstm.h:373
virtual void precompute_inputs(const std::shared_ptr< param_base_t > params, const M &inputs, M &outputs, int64_t first_column)
Definition bilstm.h:161
workbench_t(const params_t &p, uint32_t input_size, bool precomputed_input)
Definition bilstm.h:107
params_lstm_t< M, V > fw
Definition bilstm.h:51
params_lstm_t< M, V > bw
Definition bilstm.h:52
std::ostream & operator<<(std::ostream &out) const
Definition bilstm.h:60
void init_from_double(const params_bilstm_t< Eigen::MatrixXd, Eigen::VectorXd > &arg)
Definition bilstm.h:54
std::ostream & operator<<(std::ostream &out) const
Definition bilstm.h:36
void init_from_double(const params_lstm_t< Eigen::MatrixXd, Eigen::VectorXd > &arg)
Definition bilstm.h:28
std::ostream & operator<<(std::ostream &out) const
Definition bilstm.h:88
void init_from_double(const params_bilstm_t< Eigen::MatrixXd, Eigen::VectorXd > &arg)
Definition bilstm.h:81
params_multilayer_bilstm_t(const std::vector< layer_params_t > &arg)
Definition bilstm.h:77
std::vector< layer_params_t > layers
Definition bilstm.h:74