# Path: deeppy/base.py
# class Model(ParamMixin):
#     def setup(self, *array_shapes):
#         pass
# 
#     def update(self, *arrays):
#         raise NotImplementedError()
# 
# class ParamMixin(object):
#     @property
#     def params(self):
#         """ List of Parameter objects. """
#         raise NotImplementedError()
# 
#     @params.setter
#     def params(self, params):
#         raise NotImplementedError()
# 
# class CollectionMixin(ParamMixin, PhaseMixin):
#     collection = []
# 
#     @property
#     def params(self):
#         params = []
#         for obj in self.collection:
#             if isinstance(obj, ParamMixin):
#                 params.extend(obj.params)
#         return params
# 
#     @params.setter
#     def params(self, params):
#         idx = 0
#         for obj in self.collection:
#             if isinstance(obj, ParamMixin):
#                 n_params = len(obj.params)
#                 obj.params = params[idx:idx+n_params]
#                 idx += n_params
# 
#     @property
#     def phase(self):
#         return self._phase
# 
#     @phase.setter
#     def phase(self, phase):
#         if self._phase == phase:
#             return
#         self._phase = phase
#         for obj in self.collection:
#             if isinstance(obj, PhaseMixin):
#                 obj.phase = phase
# 
# Path: deeppy/feed.py
# class Feed(object):
#     def __init__(self, x, batch_size=None, epoch_size=None):
#         self.x = x
#         self.n_samples = x.shape[0]
#         if batch_size is None:
#             batch_size = 128 if self.n_samples > 512 else self.n_samples
#         if epoch_size is None:
#             epoch_size = int(np.ceil(float(self.n_samples) / batch_size))
#         self.epoch_size = epoch_size
#         self.batch_size = batch_size
#         self.epoch_idx = 0
# 
#     @classmethod
#     def from_any(cls, arg):
#         if isinstance(arg, Feed):
#             arg.reset()
#             return arg
#         elif isinstance(arg, np.ndarray):
#             return cls(arg)
#         elif isinstance(arg, ca.ndarray):
#             return cls(np.array(arg))
#         elif isinstance(arg, tuple):
#             return SupervisedFeed(arg[0], arg[1])
#         raise ValueError('Invalid arguments.')
# 
#     def reset(self):
#         self.epoch_idx = 0
# 
#     def _batch_slices(self):
#         for b in range(self.epoch_size):
#             start = (self.epoch_idx * self.epoch_size + b) * self.batch_size
#             start = start % self.n_samples
#             stop = (start + self.batch_size) % self.n_samples
#             yield start, stop
#         self.epoch_idx += 1
# 
#     def batches(self):
#         x = ca.empty(self.x_shape, dtype=self.x.dtype)
#         for start, stop in self._batch_slices():
#             if stop > start:
#                 x_np = self.x[start:stop]
#             else:
#                 x_np = np.concatenate((self.x[start:], self.x[:stop]))
#             ca.copyto(x, x_np)
#             yield x,
# 
#     @property
#     def x_shape(self):
#         return (self.batch_size,) + self.x.shape[1:]
# 
#     @property
#     def shapes(self):
#         return self.x_shape,
# 
# Path: deeppy/loss.py
# class SoftmaxCrossEntropy(Loss):
#     """
#     Softmax + cross entropy (aka. multinomial logistic loss)
#     """
# 
#     def __init__(self):
#         self._tmp_x = None
#         self._tmp_y = None
#         self.n_classes = None
# 
#     def setup(self, pred_shape, target_shape=None):
#         self.n_classes = pred_shape[1]
# 
#     def _softmax(self, x):
#         # caching wrapper
#         if self._tmp_x is not x:
#             self._tmp_y = ca.nnet.softmax(x)
#             self._tmp_x = x
#         return self._tmp_y
# 
#     def loss(self, pred, target):
#         pred = self._softmax(pred)
#         target = ca.nnet.one_hot_encode(target, self.n_classes)
#         return ca.nnet.categorical_cross_entropy(y_pred=pred, y_true=target)
# 
#     def grad(self, pred, target):
#         pred = self._softmax(pred)
#         target = ca.nnet.one_hot_encode(target, self.n_classes)
#         return -(target - pred)
# 
#     def fprop(self, x):
#         return ca.nnet.one_hot_decode(self._softmax(x))
# 
#     def y_shape(self, x_shape):
#         return (x_shape[0],)


import numpy as np
from ..base import Model, ParamMixin, CollectionMixin
from ..feed import Feed
from ..loss import SoftmaxCrossEntropy
        # Backward propagation
        grad = self.loss.grad(y_pred, y)
        for layer in reversed(self.layers[self.bprop_until:]):
            grad = layer.bprop(grad)
        return self.loss.loss(y_pred, y)

    def fprop(self, x):
        for layer in self.layers:
            x = layer.fprop(x)
        return x

    def y_shape(self, x_shape):
        for layer in self.layers:
            x_shape = layer.y_shape(x_shape)
        return x_shape

    def predict(self, feed):
        """ Calculate the output for the given input x. """
        feed = Feed.from_any(feed)
        self.phase = 'test'

        if isinstance(self.loss, SoftmaxCrossEntropy):
            # Add softmax from SoftmaxCrossEntropy
            self.layers += [self.loss]

        y = []
        for x_batch, in feed.batches():
            y.append(np.array(self.fprop(x_batch)))
        y = np.concatenate(y)[:feed.n_samples]


# Path: deeppy/base.py
# class Model(ParamMixin):
#     def setup(self, *array_shapes):
#         pass
# 
#     def update(self, *arrays):
#         raise NotImplementedError()
# 
# class ParamMixin(object):
#     @property
#     def params(self):
#         """ List of Parameter objects. """
#         raise NotImplementedError()
# 
#     @params.setter
#     def params(self, params):
#         raise NotImplementedError()
# 
# class CollectionMixin(ParamMixin, PhaseMixin):
#     collection = []
# 
#     @property
#     def params(self):
#         params = []
#         for obj in self.collection:
#             if isinstance(obj, ParamMixin):
#                 params.extend(obj.params)
#         return params
# 
#     @params.setter
#     def params(self, params):
#         idx = 0
#         for obj in self.collection:
#             if isinstance(obj, ParamMixin):
#                 n_params = len(obj.params)
#                 obj.params = params[idx:idx+n_params]
#                 idx += n_params
# 
#     @property
#     def phase(self):
#         return self._phase
# 
#     @phase.setter
#     def phase(self, phase):
#         if self._phase == phase:
#             return
#         self._phase = phase
#         for obj in self.collection:
#             if isinstance(obj, PhaseMixin):
#                 obj.phase = phase
# 
# Path: deeppy/feed.py
# class Feed(object):
#     def __init__(self, x, batch_size=None, epoch_size=None):
#         self.x = x
#         self.n_samples = x.shape[0]
#         if batch_size is None:
#             batch_size = 128 if self.n_samples > 512 else self.n_samples
#         if epoch_size is None:
#             epoch_size = int(np.ceil(float(self.n_samples) / batch_size))
#         self.epoch_size = epoch_size
#         self.batch_size = batch_size
#         self.epoch_idx = 0
# 
#     @classmethod
#     def from_any(cls, arg):
#         if isinstance(arg, Feed):
#             arg.reset()
#             return arg
#         elif isinstance(arg, np.ndarray):
#             return cls(arg)
#         elif isinstance(arg, ca.ndarray):
#             return cls(np.array(arg))
#         elif isinstance(arg, tuple):
#             return SupervisedFeed(arg[0], arg[1])
#         raise ValueError('Invalid arguments.')
# 
#     def reset(self):
#         self.epoch_idx = 0
# 
#     def _batch_slices(self):
#         for b in range(self.epoch_size):
#             start = (self.epoch_idx * self.epoch_size + b) * self.batch_size
#             start = start % self.n_samples
#             stop = (start + self.batch_size) % self.n_samples
#             yield start, stop
#         self.epoch_idx += 1
# 
#     def batches(self):
#         x = ca.empty(self.x_shape, dtype=self.x.dtype)
#         for start, stop in self._batch_slices():
#             if stop > start:
#                 x_np = self.x[start:stop]
#             else:
#                 x_np = np.concatenate((self.x[start:], self.x[:stop]))
#             ca.copyto(x, x_np)
#             yield x,
# 
#     @property
#     def x_shape(self):
#         return (self.batch_size,) + self.x.shape[1:]
# 
#     @property
#     def shapes(self):
#         return self.x_shape,
# 
# Path: deeppy/loss.py
# class SoftmaxCrossEntropy(Loss):
#     """
#     Softmax + cross entropy (aka. multinomial logistic loss)
#     """
# 
#     def __init__(self):
#         self._tmp_x = None
#         self._tmp_y = None
#         self.n_classes = None
# 
#     def setup(self, pred_shape, target_shape=None):
#         self.n_classes = pred_shape[1]
# 
#     def _softmax(self, x):
#         # caching wrapper
#         if self._tmp_x is not x:
#             self._tmp_y = ca.nnet.softmax(x)
#             self._tmp_x = x
#         return self._tmp_y
# 
#     def loss(self, pred, target):
#         pred = self._softmax(pred)
#         target = ca.nnet.one_hot_encode(target, self.n_classes)
#         return ca.nnet.categorical_cross_entropy(y_pred=pred, y_true=target)
# 
#     def grad(self, pred, target):
#         pred = self._softmax(pred)
#         target = ca.nnet.one_hot_encode(target, self.n_classes)
#         return -(target - pred)
# 
#     def fprop(self, x):
#         return ca.nnet.one_hot_decode(self._softmax(x))
# 
#     def y_shape(self, x_shape):
#         return (x_shape[0],)


# Path: deeppy/feedforward/neural_network.py
import numpy as np
from ..base import Model, ParamMixin, CollectionMixin
from ..feed import Feed
from ..loss import SoftmaxCrossEntropy

        # Backward propagation
        grad = self.loss.grad(y_pred, y)
        for layer in reversed(self.layers[self.bprop_until:]):
            grad = layer.bprop(grad)
        return self.loss.loss(y_pred, y)

    def fprop(self, x):
        for layer in self.layers:
            x = layer.fprop(x)
        return x

    def y_shape(self, x_shape):
        for layer in self.layers:
            x_shape = layer.y_shape(x_shape)
        return x_shape

    def predict(self, feed):
        """ Calculate the output for the given input x. """
        feed = Feed.from_any(feed)
        self.phase = 'test'

        if isinstance(self.loss, SoftmaxCrossEntropy):
            # Add softmax from SoftmaxCrossEntropy
            self.layers += [self.loss]

        y = []
        for x_batch, in feed.batches():
            y.append(np.array(self.fprop(x_batch)))
        y = np.concatenate(y)[:feed.n_samples]


        if isinstance(self.loss, SoftmaxCrossEntropy):