blob: c99873209281b3afb8da33a622fb746ccf923484 [file]
#-------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#-------------------------------------------------------------
# This function cleans top-K item (where K is given as input) for a given list of users.
# metaData[3, ncol(X)] : metaData[1] stores mask, metaData[2] stores schema, metaData[3] stores FD mask
#
# INPUT:
# ------------------------------------------------------------------------------
# dataTrain Training set
# dataTest Test set ignored when cv is set to True
# metaData 3×n frame with schema, categorical mask, and FD mask for dataTrain
# primitives Library of primitive cleaning operators
# parameters Hyperparameter search space that matches the primitives
# refSol Reference solution
# evaluationFunc Name of a SystemDS DML function that scores a pipeline
# evalFunHp Hyperparameter matrix for the above evaluation function
# topK Number of best pipelines to return
# resource_val Maximum resource R for the Bandit search
# max_iter Maximum iterations while enumerating logical pipelines
# lq Lower quantile used by utils::doErrorSample when triggered
# uq Upper quantile used by utils::doErrorSample when triggered
# sample Fraction of rows to subsample from dataTrain
# expectedIncrease Minimum improvement over dirtyScore that a candidate must deliver
# seed Seed number
# cv TRUE means k-fold CV, FALSE means hold-out split
# cvk Number of folds if cv = TRUE
# isLastLabel TRUE if the last column is the label
# rowCount Row-count threshold above which doErrorSample may replace uniform sampling
# correctTypos Run spelling correction in the string preprocessing step
# enablePruning Enable pruning inside the Bandit phase
# ------------------------------------------------------------------------------
#
# OUTPUT:
#-------------------------------------------------------------------------------
# topKPipelines K cleaned-data pipelines
# topKHyperParams Hyperparameter matrix with rows aligning with topKPipelines
# topKScores Evaluation scores with rows aligning with topKPipelines
# dirtyScore Baseline score on the unclean data
# evalFunHp Updated evaluation function hyperparameters
# applyFunc Frame of “apply” functions for deploying each of the top-K pipelines
#-------------------------------------------------------------------------------
source("scripts/pipelines/scripts/utils.dml") as utils;
source("scripts/pipelines/scripts/enumerateLogical.dml") as lg;
source("scripts/builtin/bandit.dml") as bandit;
f_topk_cleaning = function(Frame[Unknown] dataTrain, Frame[Unknown] dataTest = as.frame("NULL"), Frame[Unknown] metaData = as.frame("NULL"), Frame[Unknown] primitives,
Frame[Unknown] parameters, Frame[String] refSol = as.frame("NaN"), String evaluationFunc, Matrix[Double] evalFunHp, Integer topK = 5, Integer resource_val = 20,
Integer max_iter = 10, Double lq = 0.1, Double uq=0.7, Double sample = 1.0, Double expectedIncrease=1.0, Integer seed = -1, Boolean cv=TRUE, Integer cvk = 2,
Boolean isLastLabel = TRUE, Integer rowCount = 3700,
Boolean correctTypos=FALSE, Boolean enablePruning = FALSE)
return (Frame[Unknown] topKPipelines, Matrix[Double] topKHyperParams, Matrix[Double] topKScores,
Double dirtyScore, Matrix[Double] evalFunHp, Frame[Unknown] applyFunc)
{
t1 = time(); print("TopK-Cleaning:");
Xtest = as.frame("0")
Ytest = as.frame("0")
ctx = list(prefix="----"); #TODO include seed
# prepare meta data
# # keeping the meta list format if we decide to add more stuff in metadata
[schema, mask, fdMask, maskY] = prepareMeta(dataTrain, metaData)
metaList = list(mask=mask, schema=schema, fd=fdMask, applyFunc=as.frame("null"), distY=0, minFold=0)
t2 = time(); print("-- Cleaning - Prepare Metadata: "+(t2-t1)/1e9+"s");
# separate the label
[Xtrain, Ytrain] = getLabel(dataTrain, isLastLabel)
if(!cv)
[Xtest, Ytest] = getLabel(dataTest, isLastLabel)
# always recode the label
if(maskY == 1) {
[eYtrain, M] = transformencode(target=Ytrain, spec= "{ids:true, recode:[1]}");
eYtest = transformapply(target=Ytest, spec= "{ids:true, recode:[1]}", meta=M);
}
else {
eYtrain = as.matrix(Ytrain)
eYtest = as.matrix(Ytest)
}
t3 = time(); print("-- Cleaning - Prepare Labels: "+(t3-t2)/1e9+"s");
# # # when the evaluation function is called first we also compute and keep hyperparams of target application
print("-- Cleaning - Get Dirty Score: ");
[dirtyScore, evalFunHp] = getDirtyScore(X=Xtrain, Y=eYtrain, Xtest=Xtest, Ytest=eYtest, evaluationFunc=evaluationFunc,
metaList=metaList, cv=cv, cvk=cvk, evalFunHp=evalFunHp, ctx=ctx)
t4 = time(); print("---- finalized in: "+(t4-t3)/1e9+"s");
# # do the string processing
print("-- Cleaning - Data Preparation (strings, transform, sample): ");
[Xtrain, Xtest] = runStringPipeline(Xtrain, Xtest, schema, mask, cv, correctTypos, ctx)
# # if mask has 1s then there are categorical features
print("---- feature transformations to numeric matrix");
[eXtrain, eXtest, metaR] = recodeData(Xtrain, Xtest, mask, cv, "recode")
# # # do the early dropping
# [eXtrain, eXtest, metaList] = featureDrop(eXtrain, eXtest, metaList, cv)
# apply sampling on training data for pipeline enumeration
# TODO why recoding/sampling twice (within getDirtyScore)
print("---- class-stratified sampling of feature matrix w/ f="+sample);
# if(nrow(eYtrain) >= rowCount & sample == 1.0 & sum(mask) > ncol(mask)/2) # &
# [eXtrain, eYtrain ] = utils::doErrorSample(eXtrain, eYtrain, lq, uq, rowCount)
# else
[eXtrain, eYtrain] = utils::doSample(eXtrain, eYtrain, sample, mask, metaR, TRUE)
t5 = time(); print("---- finalized in: "+(t5-t4)/1e9+"s");
# # # create logical pipeline seeds
logicalSeedCI = frame([
"MVI",
"OTLR",
"CI",
"SCALE"
], rows=4, cols=1)
logicalSeedNoCI = frame([
"MVI",
"OTLR",
"SCALE"
], rows=3, cols=1)
dist = 0
if(min(eYtrain) >= 1) {
tab = table(eYtrain, 1)
dist = nrow(tab)
}
if(nrow(eYtrain) > 0 & min(eYtrain) >= 1 & dist <= 15)
logical = logicalSeedCI
else {
logical = logicalSeedNoCI
}
metaList['distY'] = dist
print("-- Cleaning - Enum Logical Pipelines: ");
print("---- Data Dimension before Cleaning: "+ nrow(eXtrain) + ", " + ncol(eXtrain));
[bestLogical, bestHp, con, refChanges, acc] = lg::enumerateLogical(X=eXtrain, y=eYtrain, Xtest=eXtest, ytest=eYtest,
initial_population=logical, refSol=refSol, seed = seed, max_iter=max_iter, metaList = metaList,
evaluationFunc=evaluationFunc, evalFunHp=evalFunHp, primitives=primitives, param=parameters,
dirtyScore = (dirtyScore + expectedIncrease), cv=cv, cvk=cvk, verbose=TRUE, ctx=ctx)
t6 = time(); print("---- finalized in: "+(t6-t5)/1e9+"s");
topKPipelines = as.frame("NULL"); topKHyperParams = matrix(0,0,0); topKScores = matrix(0,0,0); applyFunc = as.frame("NULL")
# write(acc, output+"/acc.csv", format="csv")
# stop("end of enumlp")
[topKPipelines, topKHyperParams, topKScores, applyFunc] = bandit(X_train=eXtrain, Y_train=eYtrain, X_test=eXtest, Y_test=eYtest, metaList=metaList,
evaluationFunc=evaluationFunc, evalFunHp=evalFunHp, lp=bestLogical, lpHp=bestHp, primitives=primitives, param=parameters, baseLineScore=dirtyScore,
k=topK, R=resource_val, cv=cv, cvk=cvk, ref=refChanges, seed=seed, enablePruning = enablePruning, verbose=TRUE);
t7 = time(); print("-- Cleaning - Enum Physical Pipelines: "+(t7-t6)/1e9+"s");
}
prepareMeta = function(Frame[Unknown] data, Frame[Unknown] metaData)
return(Frame[String] schema, Matrix[Double] mask, Matrix[Double] fdMask, Integer maskY)
{
if(as.scalar(metaData[1, 1]) == "NULL")
{
r1 = detectSchema(data)
r2 = matrix(0, rows=1, cols=ncol(data))
for(i in 1 : ncol(r1))
{
if(as.scalar(r1[1, i]) == "STRING" | as.scalar(r1[1, i]) == "BOOLEAN")
r2[1, i] = 1
}
schema = r1[, 1:ncol(r1) - 1]
mask = r2[, 1:ncol(r2) - 1]
fdMask = r2[, 1:ncol(r2) - 1]
maskY = as.integer(as.scalar(r2[,ncol(r2)]))
}
else {
schema = metaData[1, 1:ncol(metaData) - 1]
mask = as.matrix(metaData[2, 1:ncol(metaData) - 1])
fdMask = as.matrix(metaData[3, 1:ncol(metaData) - 1])
maskY = as.integer(as.scalar(metaData[2, ncol(metaData)]))
}
}
getLabel = function(Frame[Unknown] data, Boolean isLastLabel)
return(Frame[Unknown] X, Frame[Unknown] Y)
{
if(isLastLabel) {
X = data[, 1:ncol(data) - 1]
Y = data[, ncol(data)]
}
else
{
X = data
Y = as.frame("0")
}
}
runStringPipeline = function(Frame[Unknown] Xtrain, Frame[Unknown] Xtest, Frame[String] schema,
Matrix[Double] mask, Boolean cv, Boolean correctTypos = FALSE, List[Unknown] ctx)
return(Frame[Unknown] Xtrain, Frame[Unknown] Xtest)
{
if(cv)
Xtrain = utils::stringProcessing(data=Xtrain, mask=mask, schema=schema, CorrectTypos=correctTypos, ctx=ctx)
else
{
# # # binding train and test to use same dictionary for both
[Xtrain, distMatrix, dict, dateCol] = utils::stringProcessing(data=Xtrain, mask=mask, schema=schema, CorrectTypos=correctTypos, ctx=ctx)
Xtest = utils::stringProcessingApply(data=Xtest, mask=mask, schema=schema, CorrectTypos=correctTypos, distanceMatrix=distMatrix, dictionary=dict, dateColIdx=dateCol)
}
}
getDirtyScore = function(Frame[Unknown] X, Matrix[Double] Y, Frame[Unknown] Xtest, Matrix[Double] Ytest, String evaluationFunc, List[Unknown] metaList,
Matrix[Double] evalFunHp, Boolean cv = FALSE, Integer cvk = 3, List[Unknown] ctx=list() )
return(Double dirtyScore, Matrix[Double] evalFunHp)
{
dirtyScore = 100
dschema = detectSchema(X)
dmask = matrix(0, rows=1, cols=ncol(dschema))
for(i in 1:ncol(dschema))
if(as.scalar(dschema[1, i]) == "STRING" | as.scalar(dschema[1, i]) == "BOOLEAN")
dmask[1, i] = 1
prefix = as.scalar(ctx["prefix"]);
mask = as.matrix(metaList['mask'])
mask = ifelse(sum(mask == dmask) < ncol(mask), matrix(1, rows=1, cols=ncol(mask)), mask)
[eXtrain, eXtest] = recodeData(X, Xtest, mask, cv, "recode")
eXtrain = replace(target=eXtrain, pattern=NaN, replacement = 1)
eXtest = replace(target=eXtest, pattern=NaN, replacement = 1)
[eXtrain, eXtest] = recodeData(as.frame(eXtrain), as.frame(eXtest), mask, cv, "dummycode")
pipList = list(lp = as.frame("NULL"), ph = as.frame("NULL"), hp = as.matrix(0), flags = 0)
print(prefix+" hyper-parameter tuning and dirtyscore computation");
if(cv) {
[dirtyScore, evalFunHp] = bandit::crossV(X=eXtrain, y=Y, cvk=cvk, evalFunHp=evalFunHp,
pipList=pipList, metaList=metaList, evalFunc=evaluationFunc)
print("dirtyScore cv: "+dirtyScore)
}
else {
res = eval(evaluationFunc, list(X=eXtrain, Y=Y, Xtest=eXtest, Ytest=Ytest, Xorig=as.matrix(0), evalFunHp=evalFunHp))
dirtyScore = as.scalar(res[1, 1])
evalFunHp = res[1, 2:ncol(res)]
print("Dirty Accuracy holdout: "+dirtyScore)
}
}
recodeData = function(Frame[Unknown] Xtrain, Frame[Unknown] Xtest, Matrix[Double] mask, Boolean cv, String code)
return(Matrix[Double] eXtrain, Matrix[Double] eXtest, Frame[Unknown] X_meta)
{
if(sum(mask) > 0)
{
index = vectorToCsv(mask)
jspecR = "{ids:true, "+code+":["+index+"]}"
[eXtrain, X_meta] = transformencode(target=Xtrain, spec=jspecR);
if(!cv)
eXtest = transformapply(target=Xtest, spec=jspecR, meta=X_meta);
else eXtest = as.matrix(Xtest)
}
# if no categorical value exist then just cast the frame into matrix
else {
eXtrain = as.matrix(Xtrain)
eXtest = as.matrix(Xtest)
X_meta = as.frame('NULL')
}
}
# featureDrop = function(Matrix[Double] eXtrain, Matrix[Double] eXtest, List[Unknown] metaList, Boolean cv)
# return(Matrix[Double] eXtrain, Matrix[Double] eXtest, List[Unknown] metaList)
# {
# mask = as.matrix(metaList['mask'])
# fdMask = as.matrix(metaList['fd'])
# schema = as.frame(metaList['schema'])
# # # 1. if 90% of the column is empty
# # # # 2. if the column has only single value
# # # # have all unique values
# Xtmp = replace(target = eXtrain, pattern = NaN, replacement = 0)
# nullMask = is.na(eXtrain)
# singleValuesCol = ((colMins(Xtmp) == 0) & (colMaxs(Xtmp) == 1)) | (colMaxs(Xtmp) == colMins(Xtmp))
# allmostEmpty = colSums(nullMask)
# allmostEmptyRatio = allmostEmpty >= (nrow(Xtmp) * 0.9)
# allSum = singleValuesCol | allmostEmptyRatio
# if(sum(allSum) > 0) {
# eXtrain = removeEmpty(target=eXtrain, margin="cols", select = (allSum == 0))
# if(!cv)
# eXtest = removeEmpty(target=eXtest, margin="cols", select = (allSum == 0))
# mask = removeEmpty(target=mask, margin="cols", select = (allSum == 0))
# fdMask = removeEmpty(target=fdMask, margin="cols", select = (allSum == 0))
# schema = removeEmpty(target=schema, margin="cols", select = (allSum == 0))
# metaList['mask'] = mask
# metaList['schema'] = schema
# metaList['fd'] = fdMask
# }
# }