/*
* Copyright (C) 2016 RankSys http://ranksys.org
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.ranksys.fm.data;
import es.uam.eps.ir.ranksys.fast.preference.FastPreferenceData;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArrays;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
import org.ranksys.javafm.FMInstance;
import org.ranksys.javafm.data.FMData;
/**
* Samples user preferences with a number of negative preferences for one class prediction
* for collaborative filtering.
*
* @author Saúl Vargas (Saul@VargasSandoval.es)
*/
public class OneClassPreferenceFMData implements FMData {
private static final double[] UI_VALUES = {1.0, 1.0};
private final FastPreferenceData<?, ?> prefs;
private final Random rnd;
private final IntArrayList uidxs;
private final IntArrayList iidxs;
private final double negativeProp;
/**
* Constructor.
*
* @param prefs preference data
* @param negativeProp proportion of negative instances wrt positive preferences by user
*/
public OneClassPreferenceFMData(FastPreferenceData<?, ?> prefs, double negativeProp) {
this(prefs, negativeProp, new Random());
}
/**
* Constructor.
*
* @param prefs preference data
* @param negativeProp proportion of negative instances wrt positive preferences by user
* @param rnd random number generator
*/
public OneClassPreferenceFMData(FastPreferenceData<?, ?> prefs, double negativeProp, Random rnd) {
this.prefs = prefs;
this.negativeProp = negativeProp;
this.rnd = rnd;
this.uidxs = new IntArrayList();
prefs.getUidxWithPreferences().forEach(uidxs::add);
this.iidxs = new IntArrayList();
prefs.getIidxWithPreferences().forEach(iidxs::add);
}
@Override
public int numFeatures() {
return prefs.numUsers() + prefs.numItems();
}
@Override
public int numInstances() {
return prefs.numPreferences();
}
@Override
public void shuffle() {
IntArrays.shuffle(uidxs.elements(), 0, uidxs.size(), rnd);
}
private FMInstance getInstance(int uidx, int iidx, double v) {
return new FMInstance(v, new int[]{uidx, iidx + prefs.numUsers()}, UI_VALUES);
}
@Override
public Stream<? extends FMInstance> stream() {
return uidxs.stream()
.flatMap(uidx -> {
IntSet uidxIidxs = new IntOpenHashSet();
prefs.getUidxIidxs(uidx).forEachRemaining(uidxIidxs::add);
List<FMInstance> instances = new ArrayList<>();
// adding positive examples
uidxIidxs
.forEach(iidx -> instances.add(getInstance(uidx, iidx, 1.0)));
// adding negative examples
rnd.ints(iidxs.size(), 0, iidxs.size()).map(iidxs::getInt)
.filter(jidx -> !uidxIidxs.contains(jidx))
.distinct()
.limit((int) (negativeProp * uidxIidxs.size()))
.forEach(jidx -> instances.add(getInstance(uidx, jidx, 0.0)));
Collections.shuffle(instances);
return instances.stream();
});
}
}