<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def predict(self, eval_data, num_batch=None, merge_batches=True, reset=True, always_output_list=False, sparse_row_id_fn=None):
 """Runs prediction and collects the outputs. When `merge_batches` is ``True`` (by default), the return value will be a list ``[out1, out2, out3]``, where each element is formed by concatenating the outputs for all the mini-batches. When `always_output_list` is ``False`` (as by default), then in the case of a single output, `out1` is returned instead of ``[out1]``. When `merge_batches` is ``False``, the return value will be a nested list like in some cases (e.g. bucketing), the module does not necessarily produce the same number of outputs. The objects in the results have type `NDArray`. If you need to work with a numpy array, just call ``.asnumpy()`` on each `NDArray`. Parameters eval_data : DataIter or NDArray or numpy array Evaluation data to run prediction on. num_batch : int Defaults to ``None``, indicates running all the batches in the data iterator. merge_batches : bool Defaults to ``True``, see above for return values. reset : bool Defaults to ``True``, indicates whether we should reset the data iter before doing prediction. always_output_list : bool Defaults to ``False``, see above for return values. sparse_row_id_fn : A callback function The function takes `data_batch` as an input and returns a dict of str -> NDArray. The resulting dict is used for pulling row_sparse parameters from the kvstore, where the str key is the name of the param, and the value is the row id of the param to pull. Returns ------- list of NDArray or list of list of NDArray Prediction results. Examples -------- """

        assert self.binded and self.params_initialized

        if isinstance(eval_data, (ndarray.NDArray, np.ndarray)):
            if isinstance(eval_data, np.ndarray):
                eval_data = ndarray.array(eval_data)
            self.forward(DataBatch([eval_data]))
            return self.get_outputs()[0]

        if not isinstance(eval_data, DataIter):
            raise ValueError('eval_data must be of type NDArray or DataIter')

        if reset:
            eval_data.reset()

        output_list = []

        for nbatch, eval_batch in enumerate(eval_data):
            if num_batch is not None and nbatch == num_batch:
                break
            self.prepare(eval_batch, sparse_row_id_fn=sparse_row_id_fn)
            self.forward(eval_batch, is_train=False)
            pad = eval_batch.pad
            outputs = [out[0:out.shape[0]-pad].copy() for out in self.get_outputs()]

            output_list.append(outputs)

        if len(output_list) == 0:
            return output_list

        if merge_batches:
            num_outputs = len(output_list[0])
            for out in output_list:
                assert len(out) == num_outputs, \
                       'Cannot merge batches, as num of outputs is not the same ' + \
                       'in mini-batches. Maybe bucketing is used?'
            output_list2 = [ndarray.concatenate([out[i] for out in output_list])
                            for i in range(num_outputs)]

            if num_outputs == 1 and not always_output_list:
                return output_list2[0]
            return output_list2

        return output_list