Creating Probability-To-Interact Prediction Model
Hello guys! Today, I will be showing you on how to create a retention-based model that could predict the likelihood that the players will interact with certain enemies / items.
Currently, you need these to produce the model:
-
Logictic regression / One-sigmoid-output-layer neural network model
-
A player data that is stored in matrix
Setting Up
Before we train our model, we will first need to construct a regression model as shown below.
local DataPredict = require(DataPredict)
-- For single data point purposes, set the maximumNumberOfIterations to 1 to avoid overfitting. Additionally, the more number of maximumNumberOfIterations you have, the lower the learningRate it should be to avoid "inf" and "nan" issues.
local InteractionPredictionModel = DataPredict.Models.LogisticRegression.new({maximumNumberOfIterations = 1, learningRate = 0.3})
Upon Player Join
In here, what you need to do is:
-
Store initial player data as a vector of numbers.
-
Store the initial time that the player joined.
Below, we will show you how to create this:
-- We're just adding 1 here to add "bias".
local playerContextDataVector = {
{
1,
numberOfCurrencyAmount,
numberOfItemsAmount,
timePlayedInCurrentSession,
timePlayedInAllSessions,
healthAmount,
enemyMaximumHealthAmount,
enemyMaximumDamageAmount,
enemyCurrencyAmount,
}
}
local recordedTime = os.time()
If you want to add more data instead of relying on the initial data point, you actually can and this will improve the prediction accuracy. But keep in mind that this means you have to store more data. I recommend that for every 30 seconds, you store a new entry. Below, I will show how it is done.
local playerContextDataMatrix = {}
local recordedTimeArray = {}
local snapshotIndex = 1
local function snapshotData()
playerContextDataMatrix[snapshotIndex] = {
1,
numberOfCurrencyAmount,
numberOfItemsAmount,
timePlayedInCurrentSession,
timePlayedInAllSessions,
healthAmount,
enemyMaximumHealthAmount,
enemyMaximumDamageAmount,
enemyCurrencyAmount,
}
recordedTimeArray[snapshotIndex] = os.time()
snapshotIndex = snapshotIndex + 1
end
If you’re concerned about that the model may produce wrong result heavily upon first start up, then you can use a randomized dataset to heavily skew the prediction to the “0” probability value. Then use this randomized dataset to pretrain the model before doing any real-time training and prediction. Below, we will show you how it is done.
local numberOfData = 100
local randomPlayerContextDataMatrix = TensorL:createRandomUniformTensor({numberOfData, 9}, -100, 100) -- 100 random data with 9 features (including one "bias").
local labelContextDataMatrix = TensorL:createTensor({numberOfData, 1}, 1) -- Making sure that at all values, it predicts 100% probability of interacting.
However, this require setting the model’s parameters to these settings temporarily so that it can be biased to “0” at start up as shown below.
InteractionPredictionModel.maximumNumberOfIterations = 100
InteractionPredictionModel.learningRate = 0.3
Upon Player Leave
By the time the player leaves, it is time for us to train the model. But first, we need to calculate the difference.
local durationOfNoInteraction = os.time() - recordedTime
Currently, there are two ways to scale the probability.
-
Pure scaling
-
Sigmoid scaling
Method 1: Pure Scaling
local probabilityToInteract = 1 / durationOfNoInteraction
Method 2: Sigmoid Scaling
-- Large scaleFactor means slower growth. scaleFactor should be based on empirical average session length.
local probabilityToInteract = math.exp(-durationOfNoInteraction / scaleFactor)
Once you have chosen to scale your values, we must do this:
local wrappedProbabilityToInteract = {
{probabilityToInteract}
} -- Need to wrap this as our models can only accept matrices.
local costArray = InteractionPredictionModel:train(playerContextDataVector, wrappedProbabilityToInteract)
This should give you a model that predicts a rough estimate when they’ll leave.
Then, you must save the model parameters to Roblox’s DataStores for future use.
local ModelParameters = InteractionPredictionModel:getModelParameters()
Model Parameters Loading
In here, we will use our model parameters so that it can be used to load out models. There are three cases in here:
-
The player is a first-time player.
-
The player is a returning player.
-
Every player uses the same global model.
Case 1: The Player Is A First-Time Player
Under this case, this is a new player that plays the game for the first time. In this case, we do not know how this player would act.
We have a multiple way to handle this issue:
-
We create a “global” model that trains from every player, and then make a deep copy of the model parameters and load it into our models.
-
We take from other players’ existing model parameters and load it into our models.
Case 2: The Player Is A Returning Player
Under this case, you can continue using the existing model parameters that was saved in Roblox’s Datastores.
InteractionPredictionModel:setModelParameters(ModelParameters)
Case 3: Every Player Uses The Same Global Model
Under this case, the procedure is the same to case 2 except that you need to:
-
Load model parameters upon server start.
-
Perform auto-save with the optional ability of merging with saved model parameters from other servers.
Prediction Handling
In other to produce predictions from our model, we must perform this operation:
local currentPlayerContextDataVector = 1
local predictedLabelVector = InteractionPredictionModel:predict(currentPlayerContextDataVector)
Once you receive the predicted label vector, you can grab the pure number output by doing this:
local probabilityToInteract = predictedLabelVector[1][1]
We can do this for every 10 seconds and use this to extend the players’ playtime by doing something like this:
local playerDataVector = getPlayerContextDataVector()
local enemyDataVector = generateEnemyDataVector()
local currentPlayerContextDataVector = TensorL:concatenate(playerDataVector, enemyDataVector, 2)
local predictedLabelVector = InteractionPredictionModel:predict(currentPlayerContextDataVector)
local probabilityToInteract = predictedLabelVector[1][1]
if (probabilityToInteract >= 0.7) then
spawnEnemy(enemyDataVector)
end
Conclusion
This tutorial showed you on how to create “probability to interact” prediction model that allows you to extend your players’ playtime. All you need is some data, some models and a bit of practice to get this right!
That’s all for today and see you later!