convolution-test.cc
Go to the documentation of this file.
1 // nnet3/convolution-test.cc
2 
3 // Copyright 2017 Johns Hopkins University (author: Daniel Povey)
4 
5 // See ../../COPYING for clarification regarding multiple authors
6 //
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
10 //
11 // http://www.apache.org/licenses/LICENSE-2.0
12 //
13 // THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
15 // WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
16 // MERCHANTABLITY OR NON-INFRINGEMENT.
17 // See the Apache 2 License for the specific language governing permissions and
18 // limitations under the License.
19 
20 #include "nnet3/convolution.h"
21 #include "util/common-utils.h"
22 
23 namespace kaldi {
24 namespace nnet3 {
25 namespace time_height_convolution {
26 
27 // for testing purposes, create a random ConvolutionModel.
29 start:
30  {
31  model->num_filters_in = RandInt(1, 10);
32  model->num_filters_out = RandInt(1, 10);
33  model->height_in = RandInt(1, 10);
34  int32 min_height_offset = RandInt(-2, 0),
35  max_height_offset = RandInt(0, 2),
36  min_time_offset = RandInt(-2, 0),
37  max_time_offset = RandInt(0, 2);
38 
39  model->height_out = RandInt(1, model->height_in);
40  model->height_subsample_out = 1;
41  if (RandInt(0, 1) == 0) {
42  if (model->height_out % 2 == 0) {
43  model->height_out /= 2;
44  model->height_subsample_out = 2;
45  } else if (model->height_out % 3 == 0) {
46  model->height_out /= 3;
47  model->height_subsample_out = 3;
48  }
49  }
50  std::vector<int32> all_time_offsets;
51  int32 max_offsets = RandInt(1, 10);
52  model->offsets.clear();
53  model->required_time_offsets.clear();
54  for (int32 i = 0; i < max_offsets; i++) {
56  o.time_offset = RandInt(min_time_offset, max_time_offset);
57  o.height_offset = RandInt(min_height_offset, max_height_offset);
58  all_time_offsets.push_back(o.time_offset);
59  model->offsets.push_back(o);
60  }
61  SortAndUniq(&(model->offsets));
62  SortAndUniq(&all_time_offsets);
63  std::random_shuffle(all_time_offsets.begin(), all_time_offsets.end());
64  int32 num_required_offsets = RandInt(1, all_time_offsets.size());
65  for (int32 i = 0; i < num_required_offsets; i++)
66  model->required_time_offsets.insert(all_time_offsets[i]);
67  model->ComputeDerived();
68  }
69  if (!model->Check()) {
70  KALDI_WARN << "Regenerating model because it didn't pass the check: "
71  << model->Info();
72  goto start;
73  }
74 }
75 
76 // for testing purposes, create a set of input and output indexes for
77 // a convolution computation that are computable given this model.
79  std::vector<Index> *input_indexes,
80  std::vector<Index> *output_indexes) {
81  KALDI_ASSERT(model.Check());
82 
83  std::vector<std::pair<int32, int32> > n_x_pairs;
84  int32 num_n_x_pairs = RandInt(1, 3);
85  for (int32 i = 0; i < num_n_x_pairs; i++) {
86  int32 n = RandInt(0, 3), x = RandInt(0, 1);
87  n_x_pairs.push_back(std::pair<int32, int32>(n, x));
88  }
89  SortAndUniq(&n_x_pairs);
90  num_n_x_pairs = n_x_pairs.size();
91 
92 
93  // 'output_t_values' is the set of *possible* output
94  // t values; we'll later sub-sample from these.
95  std::vector<int32> output_t_values;
96 
97  {
98  int32 out_t_start = RandInt(-5, 5), out_t_step = RandInt(1, 3),
99  num_t_out = RandInt(1, 4);
100  for (int32 i = 0; i < num_t_out; i++)
101  output_t_values.push_back(out_t_start + i * out_t_step);
102  }
103 
104  input_indexes->clear();
105  output_indexes->clear();
106  for (size_t i = 0; i < n_x_pairs.size(); i++) {
107  std::vector<int32> chosen_output_t_values;
108  while (chosen_output_t_values.empty()) {
109  for (size_t j = 0; j < output_t_values.size(); j++)
110  if (RandInt(0, 1) != 0)
111  chosen_output_t_values.push_back(output_t_values[j]);
112  }
113  KALDI_ASSERT(IsSortedAndUniq(chosen_output_t_values));
114 
115  std::set<int32> required_input_t_values,
116  usable_input_t_values;
117  for (size_t j = 0; j < chosen_output_t_values.size(); j++) {
118  std::set<int32>::const_iterator iter;
119  int32 t_out = chosen_output_t_values[j];
120  for (iter = model.required_time_offsets.begin();
121  iter != model.required_time_offsets.end(); iter++) {
122  int32 offset = *iter;
123  required_input_t_values.insert(t_out + offset);
124  }
125  for (iter = model.all_time_offsets.begin();
126  iter != model.all_time_offsets.end(); iter++) {
127  int32 offset = *iter;
128  usable_input_t_values.insert(t_out + offset);
129  }
130  }
131 
132  // add to output_indexes
133  for (size_t j = 0; j < chosen_output_t_values.size(); j++) {
134  int32 t_out = chosen_output_t_values[j];
135  Index index;
136  index.n = n_x_pairs[i].first;
137  index.x = n_x_pairs[i].second;
138  index.t = t_out;
139  output_indexes->push_back(index);
140  }
141 
142  std::vector<int32> chosen_input_t_values(required_input_t_values.begin(),
143  required_input_t_values.end());
144  for (std::set<int32>::const_iterator iter = usable_input_t_values.begin();
145  iter != usable_input_t_values.end(); ++iter) {
146  int32 t = *iter;
147  if (RandInt(0, 1) == 0)
148  chosen_input_t_values.push_back(t);
149  }
150  SortAndUniq(&chosen_input_t_values);
151 
152  // add to input_indexes
153  for (size_t j = 0; j < chosen_input_t_values.size(); j++) {
154  int32 t_in = chosen_input_t_values[j];
155  Index index;
156  index.n = n_x_pairs[i].first;
157  index.x = n_x_pairs[i].second;
158  index.t = t_in;
159  input_indexes->push_back(index);
160  }
161  }
162 }
163 
164 
166  for (int32 i = 0; i < 10; i++) {
167  KALDI_LOG << "iter = " << i;
168  // Create a ConvolutionModel and test its I/O.
169  ConvolutionModel conv_model;
170  GetRandomConvolutionModel(&conv_model);
171  std::ostringstream os1, os2;
172  bool binary = (RandInt(0, 1) == 0);
173  conv_model.Write(os1, binary);
174  std::istringstream is(os1.str());
175  ConvolutionModel conv_model2;
176  conv_model2.Read(is, binary);
177  conv_model2.Write(os2, binary);
178  KALDI_ASSERT(os1.str() == os2.str() && conv_model2.Check());
179  }
180 }
181 
182 void TestComputationIo(const ConvolutionComputation &computation) {
183  std::ostringstream os1, os2;
184  bool binary = (RandInt(0, 1) == 0);
185  computation.Write(os1, binary);
186  std::istringstream is(os1.str());
187  ConvolutionComputation computation2;
188  computation2.Read(is, binary);
189  computation2.Write(os2, binary);
190  KALDI_ASSERT(os1.str() == os2.str());
191  computation2.Check();
192 }
193 
194 
195 // This function exects indexes.size() == matrix->NumRows();
196 // it sets to zero any row i of the matrix for which
197 // indexes[i].t == kNoTime.
198 void ZeroBlankRows(const std::vector<Index> &indexes,
199  CuMatrix<BaseFloat> *matrix) {
200  KALDI_ASSERT(static_cast<int32>(indexes.size()) == matrix->NumRows());
201  int32 num_rows = matrix->NumRows();
202  if (num_rows == 0) return;
203  Vector<BaseFloat> mask(num_rows, kUndefined);
204  mask.Set(1.0);
205  const Index *indexes_ptr = &(indexes[0]);
206  BaseFloat *mask_ptr = mask.Data();
207  for (int32 r = 0; r < num_rows; r++) {
208  if (indexes_ptr[r].t == kNoTime)
209  mask_ptr[r] = 0.0;
210  }
211  CuVector<BaseFloat> cu_mask;
212  cu_mask.Swap(&mask);
213  matrix->MulRowsVec(cu_mask);
214 }
215 
216 // This is a 'dumb' implementation of convolution, created to compare
217 // with ConvolveForward.
219  const ConvolutionModel &model,
220  const std::vector<Index> &input_indexes,
221  const std::vector<Index> &output_indexes,
222  const CuMatrixBase<BaseFloat> &input_cu,
223  const CuMatrixBase<BaseFloat> &params_cu,
224  CuMatrixBase<BaseFloat> *output_cu) {
225  // these loops will be very slow on GPU, so do it all on CPU.
226  Matrix<BaseFloat> input(input_cu), params(params_cu),
227  output(*output_cu);
228  std::unordered_map<Index, int32, IndexHasher> index_to_row;
229  int32 input_rows = input.NumRows(),
230  output_rows = output.NumRows();
231  for (int32 r_in = 0; r_in < input_rows; r_in++) {
232  if (input_indexes[r_in].t != kNoTime) {
233  index_to_row[input_indexes[r_in]] = r_in;
234  }
235  }
236  int32 num_offsets = model.offsets.size(),
237  num_filters_in = model.num_filters_in,
238  num_filters_out = model.num_filters_out,
239  height_in = model.height_in,
240  height_out = model.height_out,
241  height_subsample_out = model.height_subsample_out;
242  for (int32 r_out = 0; r_out < output_rows; r_out++) {
243  Index index_out = output_indexes[r_out];
244  if (index_out.t == kNoTime)
245  continue;
246  SubVector<BaseFloat> output_row(output, r_out);
247  for (int32 o = 0; o < num_offsets; o++) {
248  int32 time_offset = model.offsets[o].time_offset,
249  height_offset = model.offsets[o].height_offset;
250  Index index_in(index_out);
251  index_in.t += time_offset;
252  std::unordered_map<Index, int32, IndexHasher>::const_iterator iter =
253  index_to_row.find(index_in);
254  if (iter != index_to_row.end()) {
255  SubMatrix<BaseFloat> params_part(params, 0, params.NumRows(),
256  o * num_filters_in, num_filters_in);
257  int32 r_in = iter->second;
258  SubVector<BaseFloat> input_row(input, r_in);
259  for (int32 h_out_subsampled = 0;
260  h_out_subsampled < height_out;
261  h_out_subsampled++) {
262  int32 h_out = h_out_subsampled * height_subsample_out,
263  h_in = h_out + height_offset;
264  if (h_in < 0 || h_in >= height_in)
265  continue;
266  SubVector<BaseFloat> output_part(output_row,
267  h_out_subsampled * num_filters_out,
268  num_filters_out),
269  input_part(input_row, h_in * num_filters_in, num_filters_in);
270  output_part.AddMatVec(1.0, params_part, kNoTrans, input_part, 1.0);
271  }
272  }
273  }
274  }
275  output_cu->CopyFromMat(output);
276 }
277 
278 
279 
281  const std::vector<Index> &input_indexes,
282  const std::vector<Index> &output_indexes,
283  const ConvolutionComputation &computation) {
284  CuMatrix<BaseFloat> input(input_indexes.size(), conv_model.InputDim(),
286  output(output_indexes.size(), conv_model.OutputDim(),
288  output2(output),
289  params(conv_model.ParamRows(), conv_model.ParamCols());
290  input.SetRandn();
291  params.SetRandn();
292  ZeroBlankRows(input_indexes, &input);
293  ConvolveForward(computation, input, params, &output);
294  ZeroBlankRows(output_indexes, &output);
295 
296  ConvolveForwardSimple(conv_model, input_indexes, output_indexes,
297  input, params, &output2);
298  KALDI_LOG << "Tested convolution for model: "
299  << conv_model.Info();
300  if (!output.ApproxEqual(output2, 0.001)) {
301  KALDI_LOG << "Output is: " << output;
302  KALDI_LOG << "Output2 is: " << output2;
303  KALDI_ERR << "Convolution test failure.";
304  }
305 }
306 
307 
308 void TestDataBackprop(const ConvolutionModel &conv_model,
309  const std::vector<Index> &input_indexes,
310  const std::vector<Index> &output_indexes,
311  const ConvolutionComputation &computation) {
313  input_deriv(input_indexes.size(), conv_model.InputDim(),
315  input(input_indexes.size(), conv_model.InputDim(),
317  output(output_indexes.size(), conv_model.OutputDim(),
319  output_deriv(output_indexes.size(), conv_model.OutputDim(),
321  params(conv_model.ParamRows(), conv_model.ParamCols());
322 
323  input.SetRandn();
324  params.SetRandn();
325  output_deriv.SetRandn();
326 
327  ZeroBlankRows(output_indexes, &output_deriv);
328  ConvolveBackwardData(computation, params, output_deriv, &input_deriv);
329  ZeroBlankRows(input_indexes, &input_deriv);
330  ZeroBlankRows(input_indexes, &input);
331 
332  // define the objf as TraceMatMat(output_deriv, output, kTrans).
333  // we can work it out from the backpropagated data-derivative.
334  BaseFloat expected_objf = TraceMatMat(input_deriv, input, kTrans);
335 
336  ConvolveForward(computation, input, params, &output);
337  ZeroBlankRows(output_indexes, &output);
338 
339  BaseFloat observed_objf = TraceMatMat(output, output_deriv, kTrans);
340 
341  KALDI_LOG << "Expected objf = " << expected_objf
342  << ", observed objf = " << observed_objf;
343  if (!ApproxEqual(expected_objf, observed_objf, 0.1) &&
344  fabs(expected_objf) < 1.0) {
345  KALDI_ERR << "Difference in objf too large.";
346  }
347 }
348 
349 
350 void TestParamsBackprop(const ConvolutionModel &conv_model,
351  const std::vector<Index> &input_indexes,
352  const std::vector<Index> &output_indexes,
353  const ConvolutionComputation &computation) {
355  input(input_indexes.size(), conv_model.InputDim(),
357  output(output_indexes.size(), conv_model.OutputDim(),
359  output_deriv(output_indexes.size(), conv_model.OutputDim(),
361  params(conv_model.ParamRows(), conv_model.ParamCols()),
362  params_deriv(conv_model.ParamRows(), conv_model.ParamCols());
363 
364  input.SetRandn();
365  params.SetRandn();
366  output_deriv.SetRandn();
367 
368  BaseFloat alpha = 0.5 * RandInt(1, 3);
369 
370  ZeroBlankRows(output_indexes, &output_deriv);
371  ZeroBlankRows(input_indexes, &input);
372 
373  ConvolveBackwardParams(computation, input, output_deriv, alpha,
374  &params_deriv);
375 
376  BaseFloat expected_objf = TraceMatMat(params_deriv, params, kTrans) / alpha;
377 
378  ConvolveForward(computation, input, params, &output);
379 
380  ZeroBlankRows(output_indexes, &output);
381 
382  BaseFloat observed_objf = TraceMatMat(output, output_deriv, kTrans);
383 
384  KALDI_LOG << "Expected objf = " << expected_objf
385  << ", observed objf = " << observed_objf;
386  if (!ApproxEqual(expected_objf, observed_objf, 0.1) &&
387  fabs(expected_objf) < 1.0) {
388  KALDI_ERR << "Difference in objf too large.";
389  }
390 }
391 
392 
393 
395  for (int32 i = 0; i < 10; i++) {
396  KALDI_LOG << "iter = " << i;
397  // Create a ConvolutionModel
398  ConvolutionModel conv_model;
399  GetRandomConvolutionModel(&conv_model);
400  std::vector<Index> input_indexes, output_indexes;
401  GetRandomConvolutionIndexes(conv_model, &input_indexes, &output_indexes);
402 
404  ConvolutionComputation computation;
405  std::vector<Index> input_indexes_modified, output_indexes_modified;
406  CompileConvolutionComputation(conv_model, input_indexes, output_indexes,
407  opts, &computation,
408  &input_indexes_modified,
409  &output_indexes_modified);
410  TestComputationIo(computation);
411  TestRunningComputation(conv_model,
412  input_indexes_modified,
413  output_indexes_modified,
414  computation);
415  TestDataBackprop(conv_model,
416  input_indexes_modified,
417  output_indexes_modified,
418  computation);
419  TestParamsBackprop(conv_model,
420  input_indexes_modified,
421  output_indexes_modified,
422  computation);
423  std::ostringstream os;
424  os << "\nInput-indexes: ";
425  WriteIndexVector(os, false, input_indexes);
426  os << "\nInput-indexes-modified: ";
427  WriteIndexVector(os, false, input_indexes_modified);
428  os << "\nOutput-indexes: ";
429  WriteIndexVector(os, false, output_indexes);
430  os << "\nOutput-indexes-modified: ";
431  WriteIndexVector(os, false, output_indexes_modified);
432  KALDI_LOG << os.str();
433  }
434 }
435 
436 
440 }
441 
442 
443 
444 } // namespace time_height_convolution
445 } // namespace nnet3
446 } // namespace kaldi
447 
448 
449 int main() {
450  using namespace kaldi;
451  using namespace kaldi::nnet3;
453  for (int32 loop = 0; loop < 2; loop++) {
454 #if HAVE_CUDA == 1
455  CuDevice::Instantiate().SetDebugStrideMode(true);
456  if (loop == 0)
457  CuDevice::Instantiate().SelectGpuId("no"); // -1 means no GPU
458  else
459  CuDevice::Instantiate().SelectGpuId("optional"); // -2 .. automatic selection
460 #endif
461  for (int32 i = 0; i < 5; i++) {
463  }
464  }
465 }
void CopyFromMat(const MatrixBase< OtherReal > &src, MatrixTransposeType trans=kNoTrans)
Definition: cu-matrix.cc:344
This code computes Goodness of Pronunciation (GOP) and extracts phone-level pronunciation feature for...
Definition: chain.dox:20
void WriteIndexVector(std::ostream &os, bool binary, const std::vector< Index > &vec)
Definition: nnet-common.cc:126
void Write(std::ostream &os, bool binary) const
Definition: convolution.cc:225
void ConvolveBackwardParams(const ConvolutionComputation &cc, const CuMatrixBase< BaseFloat > &input, const CuMatrixBase< BaseFloat > &output_deriv, BaseFloat alpha, CuMatrixBase< BaseFloat > *params_deriv)
This does the part of the backward derivative computation of convolution, that computes derivatives w...
Definition: convolution.cc:840
bool Check(bool check_heights_used=true, bool allow_height_padding=true) const
Definition: convolution.cc:130
This comment explains the basic framework used for everything related to time-height convolution...
Definition: convolution.h:125
kaldi::int32 int32
This class represents a matrix that&#39;s stored on the GPU if we have one, and in memory if not...
Definition: matrix-common.h:71
static void GetRandomConvolutionIndexes(const ConvolutionModel &model, std::vector< Index > *input_indexes, std::vector< Index > *output_indexes)
void SortAndUniq(std::vector< T > *vec)
Sorts and uniq&#39;s (removes duplicates) from a vector.
Definition: stl-utils.h:39
struct Index is intended to represent the various indexes by which we number the rows of the matrices...
Definition: nnet-common.h:44
This file contains some fairly low-level utilities for implementing convolutional neural networks and...
void ConvolveBackwardData(const ConvolutionComputation &cc, const CuMatrixBase< BaseFloat > &params, const CuMatrixBase< BaseFloat > &output_deriv, CuMatrixBase< BaseFloat > *input_deriv)
This does the part of the backward derivative computation of convolution, that propagates derivatives...
Definition: convolution.cc:682
void ZeroBlankRows(const std::vector< Index > &indexes, CuMatrix< BaseFloat > *matrix)
struct rnnlm::@11::@12 n
void TestRunningComputation(const ConvolutionModel &conv_model, const std::vector< Index > &input_indexes, const std::vector< Index > &output_indexes, const ConvolutionComputation &computation)
void TestComputationIo(const ConvolutionComputation &computation)
void ConvolveForwardSimple(const ConvolutionModel &model, const std::vector< Index > &input_indexes, const std::vector< Index > &output_indexes, const CuMatrixBase< BaseFloat > &input_cu, const CuMatrixBase< BaseFloat > &params_cu, CuMatrixBase< BaseFloat > *output_cu)
#define KALDI_ERR
Definition: kaldi-error.h:147
int main()
#define KALDI_WARN
Definition: kaldi-error.h:150
void CompileConvolutionComputation(const ConvolutionModel &model, const std::vector< Index > &input_indexes, const std::vector< Index > &output_indexes, const ConvolutionComputationOptions &opts, ConvolutionComputation *computation, std::vector< Index > *input_indexes_modified, std::vector< Index > *output_indexes_modified)
This function does the compilation for a convolution computation; it&#39;s a wrapper for the functions be...
Real TraceMatMat(const MatrixBase< Real > &A, const MatrixBase< Real > &B, MatrixTransposeType trans)
We need to declare this here as it will be a friend function.
Real * Data()
Returns a pointer to the start of the vector&#39;s data.
Definition: kaldi-vector.h:70
This struct represents the structure of a convolution computation.
Definition: convolution.h:252
Matrix for CUDA computing.
Definition: matrix-common.h:69
A class representing a vector.
Definition: kaldi-vector.h:406
#define KALDI_ASSERT(cond)
Definition: kaldi-error.h:185
MatrixIndexT NumRows() const
Returns number of rows (or zero for empty matrix).
Definition: kaldi-matrix.h:64
void Set(Real f)
Set all members of a vector to a specified value.
void TestParamsBackprop(const ConvolutionModel &conv_model, const std::vector< Index > &input_indexes, const std::vector< Index > &output_indexes, const ConvolutionComputation &computation)
This struct contains options for compiling the convolutional computation.
Definition: convolution.h:362
void Write(std::ostream &os, bool binary) const
Definition: convolution.cc:283
static void GetRandomConvolutionModel(ConvolutionModel *model)
void Swap(CuVector< Real > *vec)
Definition: cu-vector.cc:1019
void TestDataBackprop(const ConvolutionModel &conv_model, const std::vector< Index > &input_indexes, const std::vector< Index > &output_indexes, const ConvolutionComputation &computation)
MatrixIndexT NumRows() const
Dimensions.
Definition: cu-matrix.h:215
bool IsSortedAndUniq(const std::vector< T > &vec)
Returns true if the vector is sorted and contains each element only once.
Definition: stl-utils.h:63
#define KALDI_LOG
Definition: kaldi-error.h:153
void MulRowsVec(const CuVectorBase< Real > &scale)
scale i&#39;th row by scale[i]
Definition: cu-matrix.cc:792
Sub-matrix representation.
Definition: kaldi-matrix.h:988
void ConvolveForward(const ConvolutionComputation &cc, const CuMatrixBase< BaseFloat > &input, const CuMatrixBase< BaseFloat > &params, CuMatrixBase< BaseFloat > *output)
This does the forward computation of convolution.
Definition: convolution.cc:524
Represents a non-allocating general vector which can be defined as a sub-vector of higher-level vecto...
Definition: kaldi-vector.h:501
static bool ApproxEqual(float a, float b, float relative_tolerance=0.001)
return abs(a - b) <= relative_tolerance * (abs(a)+abs(b)).
Definition: kaldi-math.h:265
const int kNoTime
Definition: nnet-common.cc:573
int32 RandInt(int32 min_val, int32 max_val, struct RandomState *state)
Definition: kaldi-math.cc:95