          logger.debug("model_files: %s, retval: %s", model_files, retval)
          return retval
  
      @property
      def output_shape(self):
          """ Return the output shapes from the main AutoEncoder """
          out = list()
          for predictor in self.predictors.values():
              out.extend([K.int_shape(output)[-3:] for output in predictor.outputs])
              break  # Only get output from one autoencoder. Shapes are the same
 
</s> remove         return tuple(out[0])
 </s> add         return [tuple(shape) for shape in out]

    @property
    def output_shape(self):
        """ The output shape of the model (shape of largest face output) """
        return self.output_shapes[self.largest_face_index]

    @property
    def largest_face_index(self):
        """ Return the index from model.outputs of the largest face
            Required for multi-output model prediction. The largest face
            is assumed to be the final output
        """
        sizes = [shape[1] for shape in self.output_shapes if shape[2] == 3]
        if not sizes:
            return None
        max_face = max(sizes)
        retval = [idx for idx, shape in enumerate(self.output_shapes)
                  if shape[1] == max_face and shape[2] == 3][0]
        logger.debug(retval)
        return retval

    @property
    def largest_mask_index(self):
        """ Return the index from model.outputs of the largest mask
            Required for multi-output model prediction. The largest face
            is assumed to be the final output
        """
        sizes = [shape[1] for shape in self.output_shapes if shape[2] == 1]
        if not sizes:
            return None
        max_mask = max(sizes)
        retval = [idx for idx, shape in enumerate(self.output_shapes)
                  if shape[1] == max_mask and shape[2] == 1][0]
        logger.debug(retval)
        return retval </s> remove         if self.is_output and not any(name == "face_out" for name in output_names):
 </s> add         if self.is_output and not any(name.startswith("face_out") for name in output_names): </s> remove         target_image = cv2.resize(  # pylint:disable=no-member
            target_image[slices, slices, :], (self.output_size, self.output_size),
            cv2.INTER_AREA)  # pylint:disable=no-member
        logger.trace("Target image shape: %s", target_image.shape)

        warped_image, warped_mask = self.separate_mask(warped_image)
        target_image, target_mask = self.separate_mask(target_image)

        if target_mask is None:
            logger.trace("Randomly warped image")
            return [warped_image, target_image]

        logger.trace("Target mask shape: %s", target_mask.shape)
        logger.trace("Randomly warped image and mask")
        return [warped_image, target_image, target_mask]
 </s> add         target_images = [cv2.resize(target_image[slices, slices, :],  # pylint:disable=no-member
                                    (size, size),
                                    cv2.INTER_AREA)  # pylint:disable=no-member
                         for size in self.output_sizes]

        logger.trace("Target image shapea: %s", [img.shape for img in target_images])
        return self.compile_images(warped_image, target_images)

    def compile_images(self, warped_image, target_images):
        """ Compile the warped images, target images and mask for feed """
        warped_image, _ = self.separate_mask(warped_image)
        final_target_images = list()
        target_mask = None
        for target_image in target_images:
            image, mask = self.separate_mask(target_image)
            final_target_images.append(image)
            # Add the mask if it exists and is the same size as our largest output
            if mask is not None and mask.shape[1] == max(self.output_sizes):
                target_mask = mask

        retval = [warped_image] + final_target_images
        if target_mask is not None:
            logger.trace("Target mask shape: %s", target_mask.shape)
            retval.append(target_mask)

        logger.trace("Final shapes: %s", [img.shape for img in retval])
        return retval </s> remove         output = [", ".join(["{}_{}: {:.5f}".format(self.model.state.loss_names[side][idx],
                                                    side.capitalize(),
                                                    this_loss)
                             for idx, this_loss in enumerate(loss[side])])
 </s> add         output = ["Loss {}: {:.5f}".format(side.capitalize(), loss[side][0])
    def output_shapes(self):
          logger.debug("model_files: %s, retval: %s", model_files, retval)
          return retval
  
      @property
    def output_shapes(self):
          """ Return the output shapes from the main AutoEncoder """
          out = list()
          for predictor in self.predictors.values():
              out.extend([K.int_shape(output)[-3:] for output in predictor.outputs])
              break  # Only get output from one autoencoder. Shapes are the same
keep keep keep keep replace keep keep keep keep keep
 Training: Add Multi-Output Support

- Name output nodes of models
- Add support for multiple outputs in models and in training
- Update loss output format for cli and gui
- Sort graphs tabs on training
- Fix analysis graph for new loss names
- Multi output support in convert 
plugins/train/model/_base.py