/*************************************************************** * * Copyright (C) University of Southampton. All rights reserved * ****************************************************************/ // Author: Matteo Venanzi // Source code of the models for the Community-based Bayesian aggregation of Crowdsourced Estimates presented in the paper: // // "Bayesian Modelling of Community-Based Multidimensional Trust in Participatory Sensing under Data Sparsity" // by Venanzi, Matteo, Teacy, W. T. L., Rogers, Alex and Jennings, Nicholas R. (2015) // In, 24th International Joint Conference on Artificial Intelligenge (IJCAI) using MicrosoftResearch.Infer; using MicrosoftResearch.Infer.Distributions; using MicrosoftResearch.Infer.Models; using MicrosoftResearch.Infer.Utils; using System; using System.Linq; namespace IJCAI15 { /// /// The BACE model. /// /// This model includes the core functionalities of the models CBACE and CBACEBias presented in Venanzi et al. (IJCAI 2015) /// /// This model is the the MaxTrust model used as benchmark in Venanzi et al. (IJCAI 2015) /// public class BACE { public int ItemCount { get { return N == null ? 0 : N.SizeAsInt; } } public int UserCount { get { return K == null ? 0 : K.SizeAsInt; } } // Ranges protected Range N; protected Range K; protected Range KN; // Variables in the model protected VariableArray ItemTrueValue; protected VariableArray UserItemCount; protected VariableArray UserTruePrecision; protected VariableArray, int[][]> UserItemIndex; protected VariableArray, double[][]> UserObservedValue; protected VariableArray, double[][]> UserObservedPrecision; // Prior distributions protected VariableArray UserTrustPrior; protected VariableArray ItemTrueValuePrior; // Model evidence protected Variable Evidence; // Inference engine protected InferenceEngine Engine; public int NumberOfIterations { get; set; } public BACE() { NumberOfIterations = 35; } public virtual void CreateModel(int itemCount, int userCount) { Evidence = Variable.Bernoulli(0.5).Named("Evidence"); var evidenceBlock = Variable.If(Evidence); DefineVariablesAndRanges(itemCount, userCount); DefineGenerativeProcess(); DefineInferenceEngine(); evidenceBlock.CloseBlock(); } protected virtual void DefineVariablesAndRanges(int taskCount, int userCount) { N = new Range(taskCount).Named("n"); K = new Range(userCount).Named("k"); UserItemCount = Variable.Array(K).Named("UserItemCount"); KN = new Range(UserItemCount[K]).Named("kn"); UserItemIndex = Variable.Array(Variable.Array(KN), K).Named("UserItemIndex"); UserItemIndex.SetValueRange(N); UserObservedValue = Variable.Array(Variable.Array(KN), K).Named("UserObservedValue"); UserObservedPrecision = Variable.Array(Variable.Array(KN), K).Named("UserObservedPrecision"); ItemTrueValuePrior = Variable.Array(N).Named("TrueItemValuePrior"); ItemTrueValue = Variable.Array(N).Named("TrueItemValue"); ItemTrueValue[N] = Variable.Random(ItemTrueValuePrior[N]); UserTrustPrior = Variable.Array(K).Named("UserTrustPrior"); UserTruePrecision = Variable.Array(K).Named("UserTruePrecision"); } protected virtual void DefineGenerativeProcess() { // The process that generates the user's reports UserTruePrecision[K] = Variable.Random(UserTrustPrior[K]); using (Variable.ForEach(K)) { var trueValue = Variable.Subarray(ItemTrueValue, UserItemIndex[K]).Named("TrueItemValueSub"); using (Variable.ForEach(KN)) { UserObservedValue[K][KN] = Variable.GaussianFromMeanAndPrecision(trueValue[KN], (UserObservedPrecision[K][KN] * UserTruePrecision[K]).Named("ScaledPrecision")); } } } protected virtual void DefineInferenceEngine() { Engine = new InferenceEngine(new VariationalMessagePassing()); Engine.Compiler.UseParallelForLoops = true; Engine.ShowProgress = false; Engine.ShowFactorGraph = false; Engine.Compiler.WriteSourceFiles = true; Engine.ShowSchedule = false; } protected virtual void SetPriors() { ItemTrueValuePrior.ObservedValue = Util.ArrayInit(ItemCount, t => Gaussian.FromMeanAndPrecision(0, 1)); UserTrustPrior.ObservedValue = Util.ArrayInit(UserCount, k => Gamma.FromMeanAndVariance(1, 1)); } protected virtual void AttachData(int[][] itemIndices, double[][] userValues, double[][] userPrecisions) { UserItemCount.ObservedValue = itemIndices.Select(items => items.Length).ToArray(); UserItemIndex.ObservedValue = itemIndices; UserObservedValue.ClearObservedValue(); } public virtual BACEPosteriors Infer(int[][] itemIndices, double[][] userValues, double[][] userPrecisions) { SetPriors(); AttachData(itemIndices, userValues, userPrecisions); var result = new BACEPosteriors(); Engine.NumberOfIterations = NumberOfIterations; result.TrueItemValuePosterior = Engine.Infer(ItemTrueValue); result.UserTrustPosterior = Engine.Infer(UserTruePrecision); result.LogEvidence = Engine.Infer(Evidence).LogOdds; return result; } } /// /// The BACE posteriors with accuracy metrics (Root mean square error and energy score). /// [Serializable] public class BACEPosteriors { public Gaussian[] TrueItemValuePosterior; public Gamma[] UserTrustPosterior; public Gaussian[][] UserPrediction; public double LogEvidence; public double ComputeRMSE(double[] TrueValue) { double[] Accuracy = new double[TrueValue.Length]; for (int i = 0; i < TrueValue.Length; i++) { Accuracy[i] = Math.Abs(TrueItemValuePosterior[i].GetMean() - TrueValue[i]); } return Accuracy.Average(); } public double ComputeEnergyScore(double[] TrueValue) { // Energy score double[] EnergyScore = new double[TrueValue.Length]; int num_samples = 10000; for (int i = 0; i < TrueValue.Length; i++) { double[] samples = Util.ArrayInit(num_samples, t => TrueItemValuePosterior[i].Sample()); double[] m_term_arr = new double[num_samples]; for (int j = 0; j < num_samples; j++) { m_term_arr[j] = Math.Sqrt(Math.Pow(samples[j] - TrueValue[i], 2)); // Norm } double m_term = m_term_arr.Average(); double[] v_term_arr = new double[num_samples-1]; for (int j=0; j < num_samples-1; j++) v_term_arr[j] = Math.Abs(samples[j] - samples[j+1]); double v_term = v_term_arr.Sum() / (2 * (num_samples-1)); EnergyScore[i] = m_term - v_term; } return EnergyScore.Average(); } } /// /// The CBACEBias model implemented as a sub-class of BACE /// class CBACEBias : BACE { // Additional ranges protected Range M; // Additional variables protected Variable CommunityCount; protected VariableArray UserBias; protected VariableArray UserPrecisionLog; protected VariableArray Community; protected VariableArray CommunityInitializer; protected Variable CommunityProbs; protected VariableArray CommunityBias; protected VariableArray CommunityTrust; // Additional priors protected Variable CommunityProbsPrior; protected VariableArray CommunityBiasPrior; protected VariableArray CommunityTrustPrior; // Additional parameters protected int BiasPrecision; protected int TrustPrecision; protected int CommunityPseudoCount; public CBACEBias() : base() { BiasPrecision = 1; TrustPrecision = 1; CommunityPseudoCount = 10; } protected override void DefineVariablesAndRanges(int taskCount, int userCount) { base.DefineVariablesAndRanges(taskCount, userCount); CommunityCount = Variable.New().Named("CommunityCount"); M = new Range(CommunityCount).Named("m"); // Community memberships CommunityProbsPrior = Variable.New().Named("CommunityProbPrior"); CommunityProbs = Variable.Random(CommunityProbsPrior).Named("CommunityProb"); CommunityProbs.SetValueRange(M); Community = Variable.Array(K).Named("Community"); Community[K] = Variable.Discrete(CommunityProbs).ForEach(K); // Initialiser to break symmetry for community membership CommunityInitializer = Variable.Array(K).Named("CommunityInitializer"); Community[K].InitialiseTo(CommunityInitializer[K]); // Community bias CommunityBiasPrior = Variable.Array(M).Named("CommunityBiasPrior"); CommunityBias = Variable.Array(M).Named("CommunityBias"); CommunityBias[M] = Variable.Random(CommunityBiasPrior[M]); UserBias = Variable.Array(K).Named("UserBias"); // Community trust CommunityTrustPrior = Variable.Array(M).Named("CommunityTrustPrior"); CommunityTrust = Variable.Array(M).Named("CommunityTrust"); CommunityTrust[M] = Variable.Random(CommunityTrustPrior[M]); // User log trust UserTrustLog = Variable.Array(K).Named("UserTrustLog"); } protected override void DefineGenerativeProcess() { // The process that generates the user's reports using (Variable.ForEach(K)) { using (Variable.Switch(Community[K])) { UserBias[K] = Variable.GaussianFromMeanAndPrecision(CommunityBias[Community[K]], BiasPrecision); UserTrustLog[K] = Variable.GaussianFromMeanAndPrecision(CommunityTrust[Community[K]], TrustPrecision); } UserTruePrecision[K] = Variable.Exp(UserTrustLog[K]); var trueValue = Variable.Subarray(ItemTrueValue, UserItemIndex[K]).Named("ItemTrueValueSub"); using (Variable.ForEach(KN)) { UserObservedValue[K][KN] = Variable.GaussianFromMeanAndPrecision((trueValue[KN] + UserBias[K]).Named("BiasedMean"), (UserObservedPrecision[K][KN] * UserTruePrecision[K]).Named("ScaledPrecision")); } } } protected void SetPriors(int userCount, int communityCount) { base.SetPriors(); UserTrustPrior.ClearObservedValue(); CommunityCount.ObservedValue = communityCount; CommunityBiasPrior.ObservedValue = Util.ArrayInit(communityCount, t => Gaussian.FromMeanAndPrecision(0, 1)); CommunityTrustPrior.ObservedValue = Util.ArrayInit(communityCount, k => Gaussian.FromMeanAndPrecision(1, 1)); CommunityProbsPrior.ObservedValue = Dirichlet.Symmetric(communityCount, CommunityPseudoCount); CommunityInitializer.ObservedValue = Util.ArrayInit(userCount, user => Discrete.PointMass(Rand.Int(communityCount), communityCount)); } public BACEPosteriors Infer(int[][] itemIndices, double[][] userValues, double[][] userPrecisions, int communityCount) { int workerCount = userValues.Length; SetPriors(workerCount, communityCount); AttachData(itemIndices, userValues, userPrecisions); var result = new CBACEPosteriors(); Engine.NumberOfIterations = NumberOfIterations; result.TrueItemValuePosterior = Engine.Infer(ItemTrueValue); result.UserTrustLogPosterior = Engine.Infer(UserTrustLog); result.UserBiasPosterior = Engine.Infer(UserBias); result.CommunityBiasPosterior = Engine.Infer(CommunityBias); result.CommunityTrustPosterior = Engine.Infer(CommunityTrust); result.CommunityPosterior = Engine.Infer(Community); result.CommunityProbsPosterior = Engine.Infer(CommunityProbs); result.LogEvidence = Engine.Infer(Evidence).LogOdds; return result; } } /// /// The CBACE posteriors /// [Serializable] public class CBACEPosteriors : BACEPosteriors { public Gaussian[] UserBiasPosterior; public Gaussian[] UserTrustLogPosterior; public Gaussian[] CommunityBiasPosterior; public Gaussian[] CommunityTrustPosterior; public Discrete[] CommunityPosterior; public Dirichlet CommunityProbsPosterior; } /// /// The CBACE model implemented as a sub-class of CBACE bias /// class CBACE : CBACEBias { /// /// The generative process of CBACE /// protected override void DefineGenerativeProcess() { // The process that generates the user's reports using (Variable.ForEach(K)) { using (Variable.Switch(Community[K])) { UserBias[K] = Variable.GaussianFromMeanAndPrecision(CommunityBias[Community[K]], BiasPrecision); UserTrustLog[K] = Variable.GaussianFromMeanAndPrecision(CommunityTrust[Community[K]], TrustPrecision); } UserTruePrecision[K] = Variable.Exp(UserTrustLog[K]); var trueValue = Variable.Subarray(ItemTrueValue, UserItemIndex[K]).Named("ItemTrueValueSub"); using (Variable.ForEach(KN)) { UserObservedValue[K][KN] = Variable.GaussianFromMeanAndPrecision((trueValue[KN]), (UserObservedPrecision[K][KN] * UserTruePrecision[K]).Named("ScaledPrecision")); } } } } }