Base class for biased MF plus attribute-to-factor mapping
Inheritance: MyMediaLite.RatingPrediction.BiasedMatrixFactorization
    public static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(Handlers.UnhandledExceptionHandler);

        // check number of command line parameters
        if (args.Length < 4)
            Usage("Not enough arguments.");

        // read command line parameters
        RecommenderParameters parameters = null;
        try	{ parameters = new RecommenderParameters(args, 4);	}
        catch (ArgumentException e)	{ Usage(e.Message); 		}

        // other parameters
        string data_dir             = parameters.GetRemoveString( "data_dir");
        //Console.Error.WriteLine("data_dir " + data_dir);
        string item_attributes_file = parameters.GetRemoveString( "item_attributes");
        string user_attributes_file = parameters.GetRemoveString( "user_attributes");
        //string save_mapping_file    = parameters.GetRemoveString( "save_model");
        int random_seed             = parameters.GetRemoveInt32(  "random_seed", -1);
        bool no_eval                = parameters.GetRemoveBool(   "no_eval", false);
        bool compute_fit            = parameters.GetRemoveBool(   "compute_fit", false);
        string prediction_file      = parameters.GetRemoveString( "prediction_file");

        if (random_seed != -1)
            MyMediaLite.Util.Random.InitInstance(random_seed);

        // main data files and method
        string trainfile = args[0].Equals("-") ? "-" : Path.Combine(data_dir, args[0]);
        string testfile  = args[1].Equals("-") ? "-" : Path.Combine(data_dir, args[1]);
        string load_model_file = args[2];
        string method    = args[3];

        // set correct recommender
        switch (method)
        {
            case "MF-ItemMapping":
                recommender = Recommender.Configure(mf_map, parameters, Usage);
                break;
        //				case "MF-ItemMapping-Optimal":
        //					recommender = Recommender.Configure(mf_map_opt, parameters, Usage);
        //					break;
        //				case "BPR-MF-ItemMapping-kNN":
        //					recommender = Recommender.Configure(mf_map_knn, parameters, Usage);
        //					break;
        //				case "BPR-MF-ItemMapping-SVR":
        //					recommender = Recommender.Configure(mf_map_svr, parameters, Usage);
        //					break;
            default:
                Usage(string.Format("Unknown method: '{0}'", method));
                break;
        }

        if (parameters.CheckForLeftovers())
            Usage(-1);

        // TODO move loading into its own method

        // ID mapping objects
        EntityMapping user_mapping = new EntityMapping();
        EntityMapping item_mapping = new EntityMapping();

        // training data
        training_data = MyMediaLite.IO.RatingPrediction.Read(Path.Combine(data_dir, trainfile), user_mapping, item_mapping);
        recommender.Ratings = training_data;

        // user attributes
        if (recommender is IUserAttributeAwareRecommender)
        {
            if (user_attributes_file.Equals(string.Empty))
                Usage("Recommender expects user_attributes=FILE.");
            else
                ((IUserAttributeAwareRecommender)recommender).UserAttributes = AttributeData.Read(Path.Combine(data_dir, user_attributes_file), user_mapping);
        }

        // item attributes
        if (recommender is IItemAttributeAwareRecommender)
        {
            if (item_attributes_file.Equals(string.Empty))
                Usage("Recommender expects item_attributes=FILE.");
            else
                ((IItemAttributeAwareRecommender)recommender).ItemAttributes = AttributeData.Read(Path.Combine(data_dir, item_attributes_file), item_mapping);
        }

        // test data
        test_data = MyMediaLite.IO.RatingPrediction.Read( Path.Combine(data_dir, testfile), user_mapping, item_mapping );

        TimeSpan seconds;

        Recommender.LoadModel(recommender, load_model_file);

        // set the maximum user and item IDs in the recommender - this is important for the cold start use case
        recommender.MaxUserID = user_mapping.InternalIDs.Max();
        recommender.MaxItemID = item_mapping.InternalIDs.Max();

        Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, "ratings range: [{0}, {1}]", recommender.MinRating, recommender.MaxRating));

        DisplayDataStats();

        Console.Write(recommender.ToString() + " ");

        if (compute_fit)
        {
            seconds = Utils.MeasureTime( delegate() {
                int num_iter = recommender.NumIterMapping;
                recommender.NumIterMapping = 0;
                recommender.LearnAttributeToFactorMapping();
                Console.Error.WriteLine();
                Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, "iteration {0} fit {1}", -1, recommender.ComputeFit()));

                recommender.NumIterMapping = 1;
                for (int i = 0; i < num_iter; i++, i++)
                {
                    recommender.IterateMapping();
                    Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, "iteration {0} fit {1}", i, recommender.ComputeFit()));
                }
                recommender.NumIterMapping = num_iter; // restore
            } );
        }
        else
        {
            seconds = Utils.MeasureTime( delegate() {
                recommender.LearnAttributeToFactorMapping();
            } );
        }
        Console.Write("mapping_time " + seconds + " ");

        if (!no_eval)
            seconds = EvaluateRecommender(recommender);
        Console.WriteLine();

        if (prediction_file != string.Empty)
        {
            Console.WriteLine();
            seconds = Utils.MeasureTime(
                delegate() {
                    Prediction.WritePredictions(recommender, test_data, user_mapping, item_mapping, prediction_file);
                }
            );
            Console.Error.WriteLine("predicting_time " + seconds);
        }
    }
    static TimeSpan EvaluateRecommender(MF_Mapping recommender)
    {
        Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, "fit {0}", recommender.ComputeFit()));

        TimeSpan seconds = Utils.MeasureTime( delegate()
            {
                var result = MyMediaLite.Eval.Ratings.Evaluate(recommender, test_data);
                MyMediaLite.Eval.Ratings.DisplayResults(result);
            } );
        Console.Write(" testing " + seconds);

        return seconds;
    }