Smart Placement Systems - Creating Personalized Object Placement Prediction Model
Hello guys! Today, I will be showing you on how to create a placement-based model that could predict the next object to place.
Setting Up
local DataPredict = require(DataPredict)
--[[
We are choosing Factorization Machine models due to sparsity from placing a number of objects that could be selected from hundreds of potential objects to be placed.
For incremental learning purposes, set the maximumNumberOfIterations to 1 to avoid fully train on the current data.
Additionally, the more number of maximumNumberOfIterations you have, the lower the learningRate it should be to avoid "inf" and "nan" issues.
Also please to make sure to set latentFactorCount to be less than the number of features to avoid overfitting.
--]]
local PlacementPredictionModel = DataPredict.Models.FactorizationMachine.new({maximumNumberOfIterations = 1, learningRate = 0.3, latentFactorCount = 5, binaryFunction = "Logistic"})
Feature Matrix Format
local objectPlacementFeatureMatrix = {
{
1, -- We're just adding 1 here to add "bias".
positionXPlacement, -- This is the previous object placement position made by the player.
positionYPlacement,
positionZPlacement,
changeInPositionX, -- This is calculated based on previous object placement position and the target placement position. In other words, targetPositionX - positionXPlacement
changeInPositionY,
changeInPositionZ,
objectRarityValue,
objectCost,
isInteractable, -- This only accepts 1 and 0. Additionally, you can also use 1 and -1.
isAWallObject, -- Same as above.
isALivingRoomObject, -- Same as above.
isAKitchenObject, -- Same as above.
isABathroomObject, -- Same as above.
isABedroomObject, -- Same as above.
isAGardenObject, -- Same as above.
objectLength,
objectWidth,
objectHeight,
previousObjectRarityValue,
previousObjectCost,
previousIsInteractable, -- This only accepts 1 and 0. Additionally, you can also use 1 and -1.
previousIsAWallObject, -- Same as above.
previousIsALivingRoomObject, -- Same as above.
previousIsAKitchenObject, -- Same as above.
previousIsABathroomObject, -- Same as above.
previousIsABedroomObject, -- Same as above.
previousIsAGardenObject, -- Same as above.
previousObjectLength,
previousObjectWidth,
previousObjectHeight,
currentPlayerCashAmount, -- The mount of cash that the player is currently holding.
consecutiveNumberOfTimesPlayerPlacedThisObject, -- The number of time that the player have placed this object in a row. Not to be confused with total number of this object placed by the player.
}
}
Upon Player Placement
By the time the player places an object, it is time for us to train the model. But first, we need to calculate the difference.
local objectPlacementFeatureMatrixToTrain = {}
local objectPlacementLabelVectorToTrain = {}
local function appendPlacementData(Player, positionXPlacement, positionYPlacement, positionZPlacement, changeInPositionX, changeInPositionY, changeInPositionZ, objectID, previousObjectID, hasPlacedBoolean)
local currentPlayerCashAmount, consecutiveNumberOfTimesPlayerPlacedThisObject = getPlayerPlacementData(Player)
local objectRarityValue, objectCost, isInteractable, isAWallObject, isALivingRoomObject, isAKitchenObject, isABathroomObject, isABedroomObject, isAGardenObject, objectLength, objectWidth, objectHeight = getObjectDataFromID(objectID)
local previousObjectRarityValue, previousObjectCost, previousIsInteractable, previousIsAWallObject, previousIsALivingRoomObject, previousIsAKitchenObject, previousIsABathroomObject, previousIsABedroomObject, previousIsAGardenObject, previousObjectLength, previousObjectWidth, previousObjectHeight = getObjectDataFromID(previousObjectID)
local objectPlacementUnwrappedFeatureVector = {
1, -- We're just adding 1 here to add "bias".
positionXPlacement, -- This is the previous object placement position made by the player.
positionYPlacement,
positionZPlacement,
changeInPositionX, -- This is calculated based on previous object placement position and the target placement position.
changeInPositionY,
changeInPositionZ,
objectRarityValue,
objectCost,
isInteractable,
isAWallObject,
isALivingRoomObject,
isAKitchenObject,
isABathroomObject,
isABedroomObject,
isAGardenObject,
objectLength,
objectWidth,
objectHeight,
previousObjectRarityValue,
previousObjectCost,
previousIsInteractable, -- This only accepts 1 and 0. Additionally, you can also use 1 and -1.
previousIsAWallObject, -- Same as above.
previousIsALivingRoomObject, -- Same as above.
previousIsAKitchenObject, -- Same as above.
previousIsABathroomObject, -- Same as above.
previousIsABedroomObject, -- Same as above.
previousIsAGardenObject, -- Same as above.
previousObjectLength,
previousObjectWidth,
previousObjectHeight,
currentPlayerCashAmount,
consecutiveNumberOfTimesPlayerPlacedThisObject,
}
table.insert(objectPlacementFeatureMatrixToTrain, objectPlacementUnwrappedFeatureVector)
local hasPlacedInteger = (hasPlacedBoolean and 1) or 0
table.insert(objectPlacementLabelVectorToTrain, {hasPlacedInteger})
end
local function onPlacement(Player, positionXPlacement, positionYPlacement, positionZPlacement, changeInPositionX, changeInPositionY, changeInPositionZ, suggestedObjectID, placedObjectID, previousObjectID) -- All the features from the previous feature matrix.
appendPlacementData(Player, positionXPlacement, positionYPlacement, positionZPlacement, changeInPositionX, changeInPositionY, changeInPositionZ, placedObjectID, previousObjectID, true)
if (suggestedObjectID ~= placedObjectID) then appendPlacementData(Player, positionXPlacement, positionYPlacement, positionZPlacement, changeInPositionX, changeInPositionY, changeInPositionZ, suggestedObjectID, false) end
PlacementPredictionModel:train(objectPlacementFeatureMatrixToTrain, objectPlacementLabelVectorToTrain) -- Theoretically, you can use single data points per train() function call instead of accumulating data points. However, factorization machines works best in batches of data in order to handle sparsity better.
end
To save the model parameters to Roblox’s DataStores for future use, you must do:
local ModelParameters = PlacementPredictionModel: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.
PlacementPredictionModel: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.
Upon Player Placement Target
In order to produce recommendations from our model, we must perform this operation:
local function displayPredictedObject(Player, positionXPlacement, positionYPlacement, positionZPlacement, changeInPositionX, changeInPositionY, changeInPositionZ, previousObjectID)
local currentPlayerCashAmount, consecutiveNumberOfTimesPlayerPlacedThisObject = getPlayerPlacementData(Player)
local previousObjectRarityValue, previousObjectCost, previousIsInteractable, previousIsAWallObject, previousIsALivingRoomObject, previousIsAKitchenObject, previousIsABathroomObject, previousIsABedroomObject, previousIsAGardenObject, previousObjectLength, previousObjectWidth, previousObjectHeight = getObjectDataFromID(previousObjectID)
local objectPlacementFeatureMatrixToPredict = createFeatureMatrixByConcatenatingWithObjectData(positionXPlacement, positionYPlacement, positionZPlacement, changeInPositionX, changeInPositionY, changeInPositionZ, previousObjectRarityValue, previousObjectCost, previousIsInteractable, previousIsAWallObject, previousIsALivingRoomObject, previousIsAKitchenObject, previousIsABathroomObject, previousIsABedroomObject, previousIsAGardenObject, previousObjectLength, previousObjectWidth, previousObjectHeight, currentPlayerCashAmount, consecutiveNumberOfTimesPlayerPlacedThisObject)
local probabilityVector = PlacementPredictionModel:predict(objectPlacementFeatureMatrixToPredict, true) -- This consists of all objects available to the player for predicting object placement, which can be arranged based on ID.
local maximumValueDimensionIndexArray = TensorL2D:findMaximumValueDimensionIndexArray(probabilityVector)
local topID = maximumValueDimensionIndexArray[1] -- We only need the maximum from rows since there is only one column generated here.
displayObject(Player, topID)
end
Conclusion
This tutorial showed you on how to create object placement prediction model that allows you to aid your players’ construction process. 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!