Browse Source

Add of probs to redirect user if new scenes available (balacing improvment)

Jérôme BUISINE 9 months ago
parent
commit
5c542ad93d
7 changed files with 180 additions and 4 deletions
  1. 1 0
      .gitignore
  2. 3 0
      config.js
  3. 104 0
      expeStats.js
  4. 5 1
      index.js
  5. 31 2
      src/views/ExperimentValidated.vue
  6. 35 0
      utils/extract_experiment.py
  7. 1 1
      utils/extract_stats_freq_and_min_all.py

+ 1 - 0
.gitignore

@@ -28,3 +28,4 @@ yarn-error.log*
 /data/*
 /doc
 /experimentConfig.js
+/results

+ 3 - 0
config.js

@@ -38,5 +38,8 @@ export const sceneFileNameBlackList = ['config', 'seuilExpe', extractsDirName]
 // Cron time for extracts deletion (every day at 03:00 AM)
 export const deleteExtractsCronTime = '0 3 * * *'
 
+// Cron time for stats estimation (every day at 03:00 AM)
+export const expeStatsCronTime = '0 3 * * *'
+
 // Logger configurations (Default application, WebSocket, Database)
 export { logger, dbLogger }

+ 104 - 0
expeStats.js

@@ -0,0 +1,104 @@
+'use strict'
+
+// import { experiments } from './experimentConfig'
+const config = require('./experimentConfig')
+
+const fs = require('fs-extra')
+
+const winston = require('winston')
+const execSync = require('child_process').execSync
+
+// get whitelist scene for MatchExtractsWithReference experiment
+const scenes = config.experiments.MatchExtractsWithReference.availableScenes.whitelist
+
+// File logger configuration
+const fileLogger = winston.createLogger({
+  level: 'info',
+  format: winston.format.json(),
+  transports: [
+    new winston.transports.File({ filename: 'logs/expeStats.log' }),
+    new winston.transports.File({ filename: 'logs/expeStats.error.log', level: 'error' }),
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      format: winston.format.json()
+    })
+  ],
+  exitOnError: false
+})
+
+const setup = async (logToFile = false) => {
+  if (logToFile) fileLogger.info({ log: 'Start extraction of data from mongo for `MatchExtractsExperiments`.', date: new Date() })
+
+  execSync('python utils/extract_experiment.py', { encoding: 'utf-8' })
+  if (logToFile) fileLogger.info({ log: 'Mongo extraction done', date: new Date() })
+  execSync('python utils/extract_stats_freq_and_min_all.py --file results/experiments_results.json --output results/match_extracts_stats.csv', { encoding: 'utf-8' })
+  if (logToFile) fileLogger.info({ log: 'Stats computation done, need to create probability for each scene', date: new Date() })
+
+  // read extracted stats in order to compute probabilities
+  let statsPath = 'results/match_extracts_stats.csv'
+  let buffer = fs.readFileSync(statsPath)
+  let lines = buffer.toString().split('\n')
+
+  let stats = {}
+  let nUsers = 0
+
+  for (let l of lines) {
+    if (l.length > 0) {
+      // extract data from csv file
+      let data = l.split(';')
+
+      // data[0] contains scene name
+      // data[1] contains number of users who do this scene
+      let u = Number(data[1])
+      stats[String(data[0])] = u
+      nUsers += u
+    }
+  }
+
+  // start computing probabilities
+  let probabilities = {}
+  let probsArr = []
+  let nUnknownScenes = 0
+
+  // based on white list
+  for (let s of scenes) {
+    if (s in stats) {
+      probabilities[s] = stats[s] / nUsers
+
+      probsArr.push(probabilities[s])
+    }
+    else {
+      nUnknownScenes += 1
+    }
+  }
+
+  // normalize probabilities
+  let currentMax = Math.max(...probsArr)
+
+  for (let s of scenes) {
+    // if new scene
+    if (!(s in stats)) {
+      // multiply prob criteria based on number of unknown scene
+      // => increase chance for user to pass this scene
+      probabilities[s] = (1 + (1 - (nUnknownScenes / scenes.length))) * currentMax
+      probsArr.push(probabilities[s])
+    }
+  }
+
+  // get sum of current probs
+  let sum = probsArr.reduce((a, b) => a + b, 0)
+
+  for (let s of scenes) {
+    probabilities[s] /= sum
+  }
+
+  if (logToFile) fileLogger.info({ log: 'New probabilities extracted:' + JSON.stringify(probabilities, null, 3), date: new Date() })
+
+  fs.writeFile('results/match_extracts_probs.json', JSON.stringify(probabilities, null, 3))
+}
+
+// Execute setup command
+setup()
+
+module.exports = { setup, expeStatsServiceLogger: fileLogger }

+ 5 - 1
index.js

@@ -4,10 +4,14 @@ import { CronJob } from 'cron'
 
 import server from './server'
 import { setup as cleanExtracts, extractsRemoverServiceLogger } from './cleanExtracts'
-import { imagesPath, deleteExtractsCronTime } from './config'
+import { setup as expeStats, expeStatsServiceLogger } from './expeStats'
+import { imagesPath, deleteExtractsCronTime, expeStatsCronTime } from './config'
 
 const argv = process.argv.slice(2)
 
+new CronJob(expeStatsCronTime, () => expeStats(true), null, true, null, null, false)
+expeStatsServiceLogger.info('Started the expe stats service.')
+
 // Start the extracts remover service
 if (!argv.includes('--no-delete')) { /* eslint no-new: 0 */
   new CronJob(deleteExtractsCronTime, () => cleanExtracts(imagesPath, false, true), null, true, null, null, false)

+ 31 - 2
src/views/ExperimentValidated.vue

@@ -47,8 +47,9 @@ import Loader from '@/components/Loader.vue'
 import { mapActions, mapGetters } from 'vuex'
 import Experiments from '@/router/experiments'
 import { getExperimentSceneList, getCalibrationScene, getCalibrationFrequency } from '@/config.utils'
-import { rand } from '@/functions'
+// import { rand } from '@/functions'
 import Newsletter from '@/components/ExperimentsComponents/Newsletter.vue'
+import stats from './../../results/match_extracts_probs.json'
 
 export default {
   name: 'ExperimentValidated',
@@ -82,7 +83,35 @@ export default {
       return this.availableScenes.length > 0
     },
     getRandomScene() {
-      return this.availableScenes[rand(0, this.availableScenes.length - 1)]
+      // need to get only data from available data
+      let availableStats = {}
+      let sumProbs = 0
+
+      for (let scene of this.availableScenes) {
+        availableStats[scene] = stats[scene]
+        sumProbs += stats[scene]
+      }
+
+      // renormalize probs for specific available scenes
+      for (let scene of this.availableScenes) {
+        availableStats[scene] /= sumProbs
+      }
+
+      let sceneChoice = this.availableScenes[0] // default choice
+      let sum = 0 // store sum prob during choice selection
+      let p = Math.random() // random number between 0 and 1
+
+      for (let scene of this.availableScenes) {
+        sum += availableStats[scene]
+
+        // get choice selection
+        if (sum >= p) {
+          sceneChoice = scene
+          break
+        }
+      }
+
+      return sceneChoice
     }
   },
   async mounted() {

+ 35 - 0
utils/extract_experiment.py

@@ -0,0 +1,35 @@
+# db connection
+from pymongo import MongoClient
+import json, os
+
+connection = MongoClient()
+
+db = connection['sin3d']
+data_collection = db['datas']
+
+output_results_folder = 'results'
+experiments_identifier = ['sin3d-PrISE-3D']
+
+experiment_results = data_collection.find({
+    'data.msg.experimentName': 'MatchExtractsWithReference', 
+    'data.msgId': 'EXPERIMENT_VALIDATED',
+    'data.experimentId':{
+        '$in': experiments_identifier
+    }
+    # '$not': { '$gt': 1.99 }
+})
+
+if not os.path.exists(output_results_folder):
+    os.makedirs(output_results_folder)
+
+results_filename = 'experiments_results.json'
+results_filepath = os.path.join(output_results_folder, results_filename)
+
+export_data = []
+
+for result in experiment_results:
+    export_data.append(result['data'])
+
+print('Save results into', results_filepath)
+with open(results_filepath, 'w') as f:
+    f.write(json.dumps(export_data, indent=4))

+ 1 - 1
utils/extract_stats_freq_and_min_all.py

@@ -41,7 +41,7 @@ def main():
             
     
     output_file = open(p_output, 'w')
-    output_file.write('scene;n_users;min_scene;\n')
+    # output_file.write('scene;n_users;min_scene;\n')
 
     for scene in dict_data:
         output_file.write(scene + ';')