detect_anomalous_spike_fl()
Aplica-se a: ✅Microsoft Fabric✅Azure Data Explorer✅Azure Monitor✅Microsoft Sentinel
Detete o aparecimento de picos anômalos em variáveis numéricas em dados com carimbo de data/hora.
A função detect_anomalous_spike_fl()
é um UDF (função definida pelo usuário) que deteta o aparecimento de picos anômalos em variáveis numéricas - como quantidade de dados exfiltrados ou tentativas de login com falha - em dados com carimbo de data/hora, como logs de tráfego. No contexto da cibersegurança, esses eventos podem ser suspeitos e indicar um potencial ataque ou compromisso.
O modelo de anomalia é baseado em uma combinação de dois escores: Z-score (o número de desvios-padrão acima da média) e Q-score (o número de intervalos interquantis acima de um quantil alto). Z-score é uma métrica atípica simples e comum; O Q-score é baseado nas cercas de Tukey - mas estendemos a definição a quaisquer quantis para maior controle. A escolha de diferentes quantis (por padrão, 95º e 25º quantis são usados) permite detetar valores atípicos mais significativos, melhorando assim a precisão. O modelo é construído em cima de alguma variável numérica e é calculado por escopo - como assinatura ou conta - e por entidade - como usuário ou dispositivo.
Depois de calcular as pontuações para um ponto de dados numérico de variação única e verificar outros requisitos (por exemplo, o número de dias ativos no período de treinamento no escopo está acima de um limite predefinido), verificamos se cada uma das pontuações está acima de seu limite predefinido. Em caso afirmativo, um pico é detetado e o ponto de dados é sinalizado como anômalo. Dois modelos são criados: um para nível de entidade (definido pelo parâmetro entityColumnName) - como usuário ou dispositivo por escopo (definido pelo parâmetro scopeColumnName) - como conta ou assinatura. O segundo modelo é construído para todo o escopo. A lógica de deteção de anomalias é executada para cada modelo e se a anomalia é detetada em um deles - é mostrado. Por padrão, picos ascendentes são detetados; picos descendentes ('dips') também podem ser interessantes em alguns contextos e podem ser detetados adaptando a lógica.
A saída direta do modelo é uma pontuação de anomalia baseada nas pontuações. A pontuação é monótona na faixa de [0, 1], com 1 representando algo anômalo. Além da pontuação de anomalia, há um sinalizador binário para anomalia detetada (controlada por um parâmetro de limite mínimo) e outros campos explicativos.
Note que a função desconsidera a estrutura temporal da variável (principalmente para escalabilidade e explicabilidade). Se a variável tiver componentes temporais significativos - como tendência e sazonalidades - sugerimos considerar a função
Sintaxe
detect_anomalous_spike_fl(
numericColumnName, entityColumnName, scopeColumnName, timeColumnName, startTraining, startDetection, endDetection, [minTrainingDaysThresh], [lowPercentileForQscore], [highPercentileForQscore], [minSlicesPerEntity], [zScoreThreshEntity], [qScoreThreshEntity], [minNumValueThreshEntity], [minSlicesPerScope], [zScoreThreshScope], [qScoreThreshScope], [minNumValueThreshScope])
Saiba mais sobre convenções de sintaxe.
Parâmetros
Designação | Tipo | Necessário | Descrição |
---|---|---|---|
numericColumnName | string |
✔️ | O nome da coluna da tabela de entrada que contém a variável numérica para a qual os modelos de anomalia são calculados. |
entityColumnName | string |
✔️ | O nome da coluna da tabela de entrada que contém os nomes ou IDs das entidades para as quais o modelo de anomalia é calculado. |
scopeColumnName | string |
✔️ | O nome da coluna da tabela de entrada que contém a partição ou o escopo, para que um modelo de anomalia diferente seja criado para cada escopo. |
timeColumnName | string |
✔️ | O nome da coluna da tabela de entrada que contém os carimbos de data/hora, que são usados para definir os períodos de treinamento e deteção. |
start de formação | datetime |
✔️ | O início do período de treinamento para o modelo de anomalia. Seu fim é definido pelo início do período de deteção. |
startDetection | datetime |
✔️ | O início do período de deteção para deteção de anomalias. |
endDetection | datetime |
✔️ | O fim do período de deteção para deteção de anomalias. |
minFormaçãoDiasThresh | int |
O número mínimo de dias no período de treinamento que existe para calcular anomalias. Se estiver abaixo do limite, o escopo é considerado muito novo e desconhecido, então as anomalias não são calculadas. O valor padrão é 14. | |
lowPercentileForQscore | real |
Um número no intervalo [0,0,1,0] que representa o percentil a ser calculado como limite baixo para o Q-score. Nas cercas de Tukey, 0,25 é usado. O valor padrão é 0,25. A escolha de um percentil mais baixo melhora a precisão à medida que são detetadas anomalias mais significativas. | |
highPercentileForQscore | real |
Um número no intervalo [0,0,1,0] que representa o percentil a ser calculado como limite alto para Q-score. Nas cercas de Tukey, 0,75 é usado. O valor padrão é 0,9. A escolha de um percentil mais alto melhora a precisão à medida que anomalias mais significativas são detetadas. | |
minSlicesPerEntity | int |
O limite mínimo de 'fatias' (por exemplo, dias) para existir em uma entidade antes que o modelo de anomalia seja criado para ela. Se o número estiver abaixo do limiar, a entidade é considerada demasiado nova e instável. O valor padrão é 20. | |
zScoreThreshEntity | real |
O limiar mínimo para a pontuação Z ao nível da entidade (número de desvios-padrão acima da média) a assinalar como anomalia. Ao escolher valores mais elevados, apenas são detetadas anomalias mais significativas. O valor padrão é 3.0. | |
qScoreThreshEntity | real |
O limiar mínimo para a pontuação Q ao nível da entidade (número de intervalos interquantitativos acima do quantil elevado) deve ser assinalado como anomalia. Ao escolher valores mais elevados, apenas são detetadas anomalias mais significativas. O valor padrão é 2.0. | |
minNumValueThreshEntity | long |
O limite mínimo para a variável numérica ser sinalizada como anomalia para uma entidade. Isso é útil para filtrar casos em que um valor é anômalo estatisticamente (Z-score alto e Q-score), mas o valor em si é muito pequeno para ser interessante. O valor padrão é 0. | |
minSlicesPerScope | int |
O limite mínimo de "fatias" (por exemplo, dias) para existir em um escopo antes que o modelo de anomalia seja criado para ele. Se o número estiver abaixo do limiar, o âmbito é considerado demasiado novo e instável. O valor padrão é 20. | |
zScoreThreshScope | real |
O limiar mínimo para a pontuação Z ao nível do âmbito de aplicação (número de desvios-padrão acima da média) a assinalar como anomalia. Ao escolher valores mais elevados, apenas são detetadas anomalias mais significativas. O valor padrão é 3.0. | |
qScoreThreshScope | real |
O limiar mínimo para a pontuação Q ao nível do âmbito (número de intervalos interquantitativos acima do quantil elevado) deve ser assinalado como anomalia. Ao escolher valores mais elevados, apenas são detetadas anomalias mais significativas. O valor padrão é 2.0. | |
minNumValueThreshScope | long |
O limite mínimo para a variável numérica ser sinalizada como anomalia para um escopo. Isso é útil para filtrar casos em que um valor é anômalo estatisticamente (Z-score alto e Q-score), mas o valor em si é muito pequeno para ser interessante. O valor padrão é 0. |
Definição de função
Você pode definir a função incorporando seu código como uma função definida por consulta ou criando-a como uma função armazenada em seu banco de dados, da seguinte maneira:
- definido por consulta
- Armazenado
Defina a função usando a seguinte instrução let. Não são necessárias permissões.
Importante
Uma declaração let não pode ser executada sozinha. Ela deve ser seguida por uma instrução de expressão tabular . Para executar um exemplo funcional de detect_anomalous_spike_fl()
, consulte Exemplo.
let detect_anomalous_spike_fl = (T:(*), numericColumnName:string, entityColumnName:string, scopeColumnName:string
, timeColumnName:string, startTraining:datetime, startDetection:datetime, endDetection:datetime, minTrainingDaysThresh:int = 14
, lowPercentileForQscore:real = 0.25, highPercentileForQscore:real = 0.9
, minSlicesPerEntity:int = 20, zScoreThreshEntity:real = 3.0, qScoreThreshEntity:real = 2.0, minNumValueThreshEntity:long = 0
, minSlicesPerScope:int = 20, zScoreThreshScope:real = 3.0, qScoreThreshScope:real = 2.0, minNumValueThreshScope:long = 0)
{
// pre-process the input data by adding standard column names and dividing to datasets
let timePeriodBinSize = 'day'; // we assume a reasonable bin for time is day
let processedData = (
T
| extend scope = column_ifexists(scopeColumnName, '')
| extend entity = column_ifexists(entityColumnName, '')
| extend numVec = tolong(column_ifexists(numericColumnName, 0))
| extend sliceTime = todatetime(column_ifexists(timeColumnName, ''))
| where isnotempty(scope) and isnotempty(sliceTime)
| extend dataSet = case((sliceTime >= startTraining and sliceTime < startDetection), 'trainSet'
, sliceTime >= startDetection and sliceTime <= endDetection, 'detectSet'
, 'other')
| where dataSet in ('trainSet', 'detectSet')
);
let aggregatedCandidateScopeData = (
processedData
| summarize firstSeenScope = min(sliceTime), lastSeenScope = max(sliceTime) by scope
| extend slicesInTrainingScope = datetime_diff(timePeriodBinSize, startDetection, firstSeenScope)
| where slicesInTrainingScope >= minTrainingDaysThresh and lastSeenScope >= startDetection
);
let entityModelData = (
processedData
| join kind = inner (aggregatedCandidateScopeData) on scope
| where dataSet == 'trainSet'
| summarize countSlicesEntity = dcount(sliceTime), avgNumEntity = avg(numVec), sdNumEntity = stdev(numVec)
, lowPrcNumEntity = percentile(numVec, lowPercentileForQscore), highPrcNumEntity = percentile(numVec, highPercentileForQscore)
, firstSeenEntity = min(sliceTime), lastSeenEntity = max(sliceTime)
by scope, entity
| extend slicesInTrainingEntity = datetime_diff(timePeriodBinSize, startDetection, firstSeenEntity)
);
let scopeModelData = (
processedData
| join kind = inner (aggregatedCandidateScopeData) on scope
| where dataSet == 'trainSet'
| summarize countSlicesScope = dcount(sliceTime), avgNumScope = avg(numVec), sdNumScope = stdev(numVec)
, lowPrcNumScope = percentile(numVec, lowPercentileForQscore), highPrcNumScope = percentile(numVec, highPercentileForQscore)
by scope
);
let resultsData = (
processedData
| where dataSet == 'detectSet'
| join kind = inner (aggregatedCandidateScopeData) on scope
| join kind = leftouter (entityModelData) on scope, entity
| join kind = leftouter (scopeModelData) on scope
| extend zScoreEntity = iff(countSlicesEntity >= minSlicesPerEntity, round((toreal(numVec) - avgNumEntity)/(sdNumEntity + 1), 2), 0.0)
, qScoreEntity = iff(countSlicesEntity >= minSlicesPerEntity, round((toreal(numVec) - highPrcNumEntity)/(highPrcNumEntity - lowPrcNumEntity + 1), 2), 0.0)
, zScoreScope = iff(countSlicesScope >= minSlicesPerScope, round((toreal(numVec) - avgNumScope)/(sdNumScope + 1), 2), 0.0)
, qScoreScope = iff(countSlicesScope >= minSlicesPerScope, round((toreal(numVec) - highPrcNumScope)/(highPrcNumScope - lowPrcNumScope + 1), 2), 0.0)
| extend isSpikeOnEntity = iff((slicesInTrainingEntity >= minTrainingDaysThresh and zScoreEntity > zScoreThreshEntity and qScoreEntity > qScoreThreshEntity and numVec >= minNumValueThreshEntity), 1, 0)
, entityHighBaseline= round(max_of((avgNumEntity + sdNumEntity), highPrcNumEntity), 2)
, isSpikeOnScope = iff((countSlicesScope >= minTrainingDaysThresh and zScoreScope > zScoreThreshScope and qScoreScope > qScoreThreshScope and numVec >= minNumValueThreshScope), 1, 0)
, scopeHighBaseline = round(max_of((avgNumEntity + 2 * sdNumEntity), highPrcNumScope), 2)
| extend entitySpikeAnomalyScore = iff(isSpikeOnEntity == 1, round(1.0 - 0.25/(max_of(zScoreEntity, qScoreEntity)),4), 0.00)
, scopeSpikeAnomalyScore = iff(isSpikeOnScope == 1, round(1.0 - 0.25/(max_of(zScoreScope, qScoreScope)), 4), 0.00)
| where isSpikeOnEntity == 1 or isSpikeOnScope == 1
| extend avgNumEntity = round(avgNumEntity, 2), sdNumEntity = round(sdNumEntity, 2)
, avgNumScope = round(avgNumScope, 2), sdNumScope = round(sdNumScope, 2)
| project-away entity1, scope1, scope2, scope3
| extend anomalyType = iff(isSpikeOnEntity == 1, strcat('spike_', entityColumnName), strcat('spike_', scopeColumnName)), anomalyScore = max_of(entitySpikeAnomalyScore, scopeSpikeAnomalyScore)
| extend anomalyExplainability = iff(isSpikeOnEntity == 1
, strcat('The value of numeric variable ', numericColumnName, ' for ', entityColumnName, ' ', entity, ' is ', numVec, ', which is abnormally high for this '
, entityColumnName, ' at this ', scopeColumnName
, '. Based on observations from last ' , slicesInTrainingEntity, ' ', timePeriodBinSize, 's, the expected baseline value is below ', entityHighBaseline, '.')
, strcat('The value of numeric variable ', numericColumnName, ' on ', scopeColumnName, ' ', scope, ' is ', numVec, ', which is abnormally high for this '
, scopeColumnName, '. Based on observations from last ' , slicesInTrainingScope, ' ', timePeriodBinSize, 's, the expected baseline value is below ', scopeHighBaseline, '.'))
| extend anomalyState = iff(isSpikeOnEntity == 1
, bag_pack('avg', avgNumEntity, 'stdev', sdNumEntity, strcat('percentile_', lowPercentileForQscore), lowPrcNumEntity, strcat('percentile_', highPercentileForQscore), highPrcNumEntity)
, bag_pack('avg', avgNumScope, 'stdev', sdNumScope, strcat('percentile_', lowPercentileForQscore), lowPrcNumScope, strcat('percentile_', highPercentileForQscore), highPrcNumScope))
| project-away lowPrcNumEntity, highPrcNumEntity, lowPrcNumScope, highPrcNumScope
);
resultsData
};
// Write your query to use the function here.
Exemplo
O exemplo a seguir usa o operador invoke para executar a função.
- definido por consulta
- Armazenado
Para usar uma função definida por consulta, invoque-a após a definição da função incorporada.
let detect_anomalous_spike_fl = (T:(*), numericColumnName:string, entityColumnName:string, scopeColumnName:string
, timeColumnName:string, startTraining:datetime, startDetection:datetime, endDetection:datetime, minTrainingDaysThresh:int = 14
, lowPercentileForQscore:real = 0.25, highPercentileForQscore:real = 0.9
, minSlicesPerEntity:int = 20, zScoreThreshEntity:real = 3.0, qScoreThreshEntity:real = 2.0, minNumValueThreshEntity:long = 0
, minSlicesPerScope:int = 20, zScoreThreshScope:real = 3.0, qScoreThreshScope:real = 2.0, minNumValueThreshScope:long = 0)
{
// pre-process the input data by adding standard column names and dividing to datasets
let timePeriodBinSize = 'day'; // we assume a reasonable bin for time is day
let processedData = (
T
| extend scope = column_ifexists(scopeColumnName, '')
| extend entity = column_ifexists(entityColumnName, '')
| extend numVec = tolong(column_ifexists(numericColumnName, 0))
| extend sliceTime = todatetime(column_ifexists(timeColumnName, ''))
| where isnotempty(scope) and isnotempty(sliceTime)
| extend dataSet = case((sliceTime >= startTraining and sliceTime < startDetection), 'trainSet'
, sliceTime >= startDetection and sliceTime <= endDetection, 'detectSet'
, 'other')
| where dataSet in ('trainSet', 'detectSet')
);
let aggregatedCandidateScopeData = (
processedData
| summarize firstSeenScope = min(sliceTime), lastSeenScope = max(sliceTime) by scope
| extend slicesInTrainingScope = datetime_diff(timePeriodBinSize, startDetection, firstSeenScope)
| where slicesInTrainingScope >= minTrainingDaysThresh and lastSeenScope >= startDetection
);
let entityModelData = (
processedData
| join kind = inner (aggregatedCandidateScopeData) on scope
| where dataSet == 'trainSet'
| summarize countSlicesEntity = dcount(sliceTime), avgNumEntity = avg(numVec), sdNumEntity = stdev(numVec)
, lowPrcNumEntity = percentile(numVec, lowPercentileForQscore), highPrcNumEntity = percentile(numVec, highPercentileForQscore)
, firstSeenEntity = min(sliceTime), lastSeenEntity = max(sliceTime)
by scope, entity
| extend slicesInTrainingEntity = datetime_diff(timePeriodBinSize, startDetection, firstSeenEntity)
);
let scopeModelData = (
processedData
| join kind = inner (aggregatedCandidateScopeData) on scope
| where dataSet == 'trainSet'
| summarize countSlicesScope = dcount(sliceTime), avgNumScope = avg(numVec), sdNumScope = stdev(numVec)
, lowPrcNumScope = percentile(numVec, lowPercentileForQscore), highPrcNumScope = percentile(numVec, highPercentileForQscore)
by scope
);
let resultsData = (
processedData
| where dataSet == 'detectSet'
| join kind = inner (aggregatedCandidateScopeData) on scope
| join kind = leftouter (entityModelData) on scope, entity
| join kind = leftouter (scopeModelData) on scope
| extend zScoreEntity = iff(countSlicesEntity >= minSlicesPerEntity, round((toreal(numVec) - avgNumEntity)/(sdNumEntity + 1), 2), 0.0)
, qScoreEntity = iff(countSlicesEntity >= minSlicesPerEntity, round((toreal(numVec) - highPrcNumEntity)/(highPrcNumEntity - lowPrcNumEntity + 1), 2), 0.0)
, zScoreScope = iff(countSlicesScope >= minSlicesPerScope, round((toreal(numVec) - avgNumScope)/(sdNumScope + 1), 2), 0.0)
, qScoreScope = iff(countSlicesScope >= minSlicesPerScope, round((toreal(numVec) - highPrcNumScope)/(highPrcNumScope - lowPrcNumScope + 1), 2), 0.0)
| extend isSpikeOnEntity = iff((slicesInTrainingEntity >= minTrainingDaysThresh and zScoreEntity > zScoreThreshEntity and qScoreEntity > qScoreThreshEntity and numVec >= minNumValueThreshEntity), 1, 0)
, entityHighBaseline= round(max_of((avgNumEntity + sdNumEntity), highPrcNumEntity), 2)
, isSpikeOnScope = iff((countSlicesScope >= minTrainingDaysThresh and zScoreScope > zScoreThreshScope and qScoreScope > qScoreThreshScope and numVec >= minNumValueThreshScope), 1, 0)
, scopeHighBaseline = round(max_of((avgNumEntity + 2 * sdNumEntity), highPrcNumScope), 2)
| extend entitySpikeAnomalyScore = iff(isSpikeOnEntity == 1, round(1.0 - 0.25/(max_of(zScoreEntity, qScoreEntity)),4), 0.00)
, scopeSpikeAnomalyScore = iff(isSpikeOnScope == 1, round(1.0 - 0.25/(max_of(zScoreScope, qScoreScope)), 4), 0.00)
| where isSpikeOnEntity == 1 or isSpikeOnScope == 1
| extend avgNumEntity = round(avgNumEntity, 2), sdNumEntity = round(sdNumEntity, 2)
, avgNumScope = round(avgNumScope, 2), sdNumScope = round(sdNumScope, 2)
| project-away entity1, scope1, scope2, scope3
| extend anomalyType = iff(isSpikeOnEntity == 1, strcat('spike_', entityColumnName), strcat('spike_', scopeColumnName)), anomalyScore = max_of(entitySpikeAnomalyScore, scopeSpikeAnomalyScore)
| extend anomalyExplainability = iff(isSpikeOnEntity == 1
, strcat('The value of numeric variable ', numericColumnName, ' for ', entityColumnName, ' ', entity, ' is ', numVec, ', which is abnormally high for this '
, entityColumnName, ' at this ', scopeColumnName
, '. Based on observations from last ' , slicesInTrainingEntity, ' ', timePeriodBinSize, 's, the expected baseline value is below ', entityHighBaseline, '.')
, strcat('The value of numeric variable ', numericColumnName, ' on ', scopeColumnName, ' ', scope, ' is ', numVec, ', which is abnormally high for this '
, scopeColumnName, '. Based on observations from last ' , slicesInTrainingScope, ' ', timePeriodBinSize, 's, the expected baseline value is below ', scopeHighBaseline, '.'))
| extend anomalyState = iff(isSpikeOnEntity == 1
, bag_pack('avg', avgNumEntity, 'stdev', sdNumEntity, strcat('percentile_', lowPercentileForQscore), lowPrcNumEntity, strcat('percentile_', highPercentileForQscore), highPrcNumEntity)
, bag_pack('avg', avgNumScope, 'stdev', sdNumScope, strcat('percentile_', lowPercentileForQscore), lowPrcNumScope, strcat('percentile_', highPercentileForQscore), highPrcNumScope))
| project-away lowPrcNumEntity, highPrcNumEntity, lowPrcNumScope, highPrcNumScope
);
resultsData
};
let detectPeriodStart = datetime(2022-04-30 05:00:00.0000000);
let trainPeriodStart = datetime(2022-03-01 05:00);
let names = pack_array("Admin", "Dev1", "Dev2", "IT-support");
let countNames = array_length(names);
let testData = range t from 1 to 24*60 step 1
| extend timeSlice = trainPeriodStart + 1h * t
| extend countEvents = round(2*rand() + iff((t/24)%7>=5, 10.0, 15.0) - (((t%24)/10)*((t%24)/10)), 2) * 100
| extend userName = tostring(names[toint(rand(countNames))])
| extend deviceId = hash_md5(rand())
| extend accountName = iff(((rand() < 0.2) and (timeSlice < detectPeriodStart)), 'testEnvironment', 'prodEnvironment')
| extend userName = iff(timeSlice == detectPeriodStart, 'H4ck3r', userName)
| extend countEvents = iff(timeSlice == detectPeriodStart, 3*countEvents, countEvents)
| sort by timeSlice desc
;
testData
| invoke detect_anomalous_spike_fl(numericColumnName = 'countEvents'
, entityColumnName = 'userName'
, scopeColumnName = 'accountName'
, timeColumnName = 'timeSlice'
, startTraining = trainPeriodStart
, startDetection = detectPeriodStart
, endDetection = detectPeriodStart
)
Output
t | timeSlice | contarEventos | nome de utilizador | deviceId | nomedaconta; | Âmbito de aplicação | entidade | numVec | fatiaTempo | conjunto de dados | primeiroSeenScope | lastSeenScope | fatiasInTrainingScope | countSlicesEntity | avgNumEntity | sdNumEntity | firstSeenEntity | lastSeenEntity | fatiasInTrainingEntity | countSlicesScope | avgNumScope | sdNumScope | zScoreEntity | qScoreEntity | zScoreScope | qScoreScope | isSpikeOnEntity | entityHighBaseline | isSpikeOnScope | escopoHighBaseline | entitySpikeAnomalyScore | escopoSpikeAnomalyScore | anomalyType | anomaliaScore | anomaliaExplicabilidade | anomaliaEstado |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1440 | 2022-04-30 05:00:00.0000000 | 5079 | H4ck3r | 9e8e151aced5a64938b93ee0c13fe940 | prodAmbiente | prodAmbiente | H4ck3r | 5079 | 2022-04-30 05:00:00.0000000 | detectSet | 2022-03-01 08:00:00.0000000 | 2022-04-30 05:00:00.0000000 | 60 | 1155 | 1363.22 | 267.51 | 0 | 0 | 13.84 | 185.46 | 0 | 1 | 628 | 0 | 0.9987 | spike_accountName | 0.9987 | O valor da variável numérica countEvents em accountName prodEnvironment é 5079, o que é anormalmente alto para este accountName. Com base nas observações dos últimos 60 dias, o valor basal esperado é inferior a 628,0. | {"média": 1363.22,"stdev": 267.51,"percentile_0.25": 605,"percentile_0.9": 628} |
A saída da execução da função são as linhas no conjunto de dados de deteção que foram marcadas como picos anômalos nos níveis de escopo ou entidade. Para maior clareza, são acrescentados alguns outros campos:
-
dataSet
: conjunto de dados atual (é sempredetectSet
). -
firstSeenScope
: carimbo de data/hora quando o escopo foi visto pela primeira vez. -
lastSeenScope
: carimbo de data/hora quando o escopo foi visto pela última vez. -
slicesInTrainingScope
: número de fatias (por exemplo, dias) em que o escopo existe no conjunto de dados de treinamento. -
countSlicesEntity
: número de fatias (por exemplo, dias) que a entidade existe no escopo. -
avgNumEntity
: média da variável numérica no conjunto de treinamento por entidade no escopo. -
sdNumEntity
: desvio padrão da variável numérica no conjunto de treinamento por entidade no escopo. -
firstSeenEntity
: carimbo de data/hora quando a entidade foi vista pela primeira vez no escopo. -
lastSeenEntity
: carimbo de data/hora quando a entidade foi vista pela última vez no escopo. -
slicesInTrainingEntity
: número de fatias (por exemplo, dias) que a entidade existe no escopo no conjunto de dados de treinamento. -
countSlicesScope
: número de fatias (por exemplo, dias) em que o escopo existe. -
avgNumScope
: média da variável numérica no conjunto de treinamento por escopo. -
sdNumScope
: desvio padrão da variável numérica no conjunto de treinamento por escopo. -
zScoreEntity
: Z-score para o valor atual da variável numérica com base no modelo de entidade. -
qScoreEntity
: Q-score para o valor atual da variável numérica com base no modelo de entidade. -
zScoreScope
: Z-score para o valor atual da variável numérica com base no modelo de escopo. -
qScoreScope
: Q-score para o valor atual da variável numérica com base no modelo de escopo. -
isSpikeOnEntity
: sinalizador binário para pico anômalo baseado no modelo de entidade. -
entityHighBaseline
: linha de base elevada esperada para valores de variáveis numéricas com base no modelo de entidade. -
isSpikeOnScope
: sinalizador binário para pico anômalo baseado no modelo de escopo. -
scopeHighBaseline
: linha de base alta esperada para valores de variáveis numéricas com base no modelo de escopo. -
entitySpikeAnomalyScore
: pontuação de anomalia para o pico com base no modelo de entidade; um número no intervalo [0,1], valores mais elevados significando mais anomalia. -
scopeSpikeAnomalyScore
: pontuação de anomalia para o pico com base no modelo de escopo; um número no intervalo [0,1], valores mais elevados significando mais anomalia. -
anomalyType
: mostra o tipo de anomalia (útil ao executar várias lógicas de deteção de anomalias juntas). -
anomalyScore
: pontuação de anomalia para o pico com base no modelo escolhido. -
anomalyExplainability
: invólucro textual para anomalia gerada e sua explicação. -
anomalyState
: conjunto de métricas do modelo escolhido (média, desvio padrão e percentis) descrevendo o modelo.
No exemplo acima, executar essa função na variável countEvents usando usuário como entidade e conta como escopo com parâmetros padrão deteta um pico no nível do escopo. Como o usuário 'H4ck3r' não tem dados suficientes no período de treinamento, a anomalia não é calculada para o nível da entidade e todos os campos relevantes estão vazios. A anomalia do nível de escopo tem uma pontuação de anomalia de 0,998, o que significa que esse pico é anômalo para o escopo.
Se aumentarmos suficientemente qualquer um dos limiares mínimos, não será detetada qualquer anomalia, uma vez que os requisitos seriam demasiado elevados.
A saída mostra as linhas com picos anômalos juntamente com campos de explicação em formato padronizado. Esses campos são úteis para investigar a anomalia e para executar a deteção anômala de picos em várias variáveis numéricas ou executar outros algoritmos juntos.
O uso sugerido no contexto de segurança cibernética é executar a função em variáveis numéricas significativas (quantidades de dados baixados, contagens de arquivos carregados ou tentativas de login com falha) por escopos significativos (como assinatura em contas) e entidades (como usuários ou dispositivos). Um pico anômalo detetado significa que o valor numérico é maior do que o esperado nesse escopo ou entidade e pode ser suspeito.