Tutorial: Criar modelos baseados em partição no R no SQL Server
Aplica-se a: SQL Server 2016 (13.x) e versões posteriores
No SQL Server 2019, a modelagem baseada em partição é a capacidade de criar e treinar modelos em dados particionados. Para dados estratificados que são naturalmente segmentados em um esquema de classificação especificado – como regiões geográficas, data e hora, idade ou sexo, você pode executar um script em todo o conjunto de dados, com a capacidade de modelar, treinar e pontuar as partições que permanecem intactas em todas essas operações.
A modelagem baseada em partição é habilitada por meio de dois novos parâmetros em sp_execute_external_script:
input_data_1_partition_by_columns
, que especifica uma coluna pela qual particionar.input_data_1_order_by_columns
especifica por quais colunas é feita a ordenação.
Neste tutorial, aprenda a usar a modelagem baseada em partição usando os dados clássicos de exemplo de táxis de Nova York e o script R. A coluna de partição é a forma de pagamento.
- As partições são baseadas em tipos de pagamento (5).
- Crie e treine modelos em cada partição e armazene os objetos no banco de dados.
- Preveja a probabilidade de resultados de gorjeta em cada modelo de partição usando dados de exemplo reservados para essa finalidade.
Pré-requisitos
Para concluir este tutorial, é necessário ter o seguinte:
Recursos do sistema suficientes. O conjunto de dados é grande e as operações de treinamento fazem uso intensivo de recursos. Se possível, use um sistema que tenha, pelo menos, 8 GB de RAM. Como alternativa, você pode usar conjuntos de dados menores para resolver problemas de restrições de recursos. As instruções para reduzir o conjunto de dados são embutidas.
Uma ferramenta para a execução da consulta T-SQL, como o SQL Server Management Studio (SSMS).
NYCTaxi_Sample.bak, que pode ser baixado e restaurado na instância do SQL Server. O tamanho do arquivo é aproximadamente 90 MB.
Instância do mecanismo de banco de dados do SQL Server 2019, com integração entre os Serviços de Machine Learning e o R.
O tutorial usa uma conexão de loopback com o SQL Server de um script do R sobre ODBC. Portanto, você precisa criar um logon para SQLRUserGroup.
Verifique a disponibilidade de pacotes R retornando uma lista bem formatada de todos os pacotes R atualmente instalados com a instância do mecanismo de banco de dados:
EXECUTE sp_execute_external_script
@language=N'R',
@script = N'str(OutputDataSet);
packagematrix <- installed.packages();
Name <- packagematrix[,1];
Version <- packagematrix[,3];
OutputDataSet <- data.frame(Name, Version);',
@input_data_1 = N''
WITH RESULT SETS ((PackageName nvarchar(250), PackageVersion nvarchar(max) ))
Conectar-se ao banco de dados
Inicie o SSMS e conecte-se à instância do mecanismo de banco de dados. No Pesquisador de Objetos, verifique se o banco de dados NYCTaxi_Sample existe.
Criar CalculateDistance
O banco de dados de demonstração é fornecido com uma função escalar para calcular a distância, mas nosso procedimento armazenado funciona melhor com uma função com valor de tabela. Execute o script a seguir para criar a função CalculateDistance
usada na etapa de treinamento mais adiante.
Para confirmar que a função foi criada, no Pesquisador de Objetos, confira o \Programmability\Functions\Table-valued Functions
no banco de dados NYCTaxi_Sample
.
USE NYCTaxi_sample
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[CalculateDistance] (
@Lat1 FLOAT
,@Long1 FLOAT
,@Lat2 FLOAT
,@Long2 FLOAT
)
-- User-defined function calculates the direct distance between two geographical coordinates.
RETURNS TABLE
AS
RETURN
SELECT COALESCE(3958.75 * ATAN(SQRT(1 - POWER(t.distance, 2)) / nullif(t.distance, 0)), 0) AS direct_distance
FROM (
VALUES (CAST((SIN(@Lat1 / 57.2958) * SIN(@Lat2 / 57.2958)) + (COS(@Lat1 / 57.2958) * COS(@Lat2 / 57.2958) * COS((@Long2 / 57.2958) - (@Long1 / 57.2958))) AS DECIMAL(28, 10)))
) AS t(distance)
GO
Definir um procedimento para criar e treinar modelos por partição
Este tutorial encapsula o script R em um procedimento armazenado. Nesta etapa, você criará um procedimento armazenado que usa o R para criar um conjunto de dados de entrada, criar um modelo de classificação para prever os resultados de gorjeta e, em seguida, armazena o modelo no banco de dados.
Entre as entradas de parâmetro usadas por esse script, você verá input_data_1_partition_by_columns
e input_data_1_order_by_columns
. Lembre-se de que esses parâmetros são o mecanismo pelo qual a modelagem particionada ocorre. Os parâmetros são passados como entradas para sp_execute_external_script para processar partições com o script externo executado uma vez para cada partição.
Para esse procedimento armazenado, use o paralelismo para obter um tempo de conclusão mais rápido.
Depois que você executar este script, no Pesquisador de Objetos, deverá ver train_rxLogIt_per_partition
no \Programmability\Stored Procedures
no banco de dados NYCTaxi_Sample
. Você também deverá ver uma nova tabela usada para armazenar modelos: dbo.nyctaxi_models
.
USE NYCTaxi_Sample
GO
CREATE
OR
ALTER PROCEDURE [dbo].[train_rxLogIt_per_partition] (@input_query NVARCHAR(max))
AS
BEGIN
DECLARE @start DATETIME2 = SYSDATETIME()
,@model_generation_duration FLOAT
,@model VARBINARY(max)
,@instance_name NVARCHAR(100) = @@SERVERNAME
,@database_name NVARCHAR(128) = db_name();
EXEC sp_execute_external_script @language = N'R'
,@script =
N'
# Make sure InputDataSet is not empty. In parallel mode, if one thread gets zero data, an error occurs
if (nrow(InputDataSet) > 0) {
# Define the connection string
connStr <- paste("Driver=SQL Server;Server=", instance_name, ";Database=", database_name, ";Trusted_Connection=true;", sep="");
# build classification model to predict a tip outcome
duration <- system.time(logitObj <- rxLogit(tipped ~ passenger_count + trip_distance + trip_time_in_secs + direct_distance, data = InputDataSet))[3];
# First, serialize a model to and put it into a database table
modelbin <- as.raw(serialize(logitObj, NULL));
# Create the data source. To reduce data size, add rowsPerRead=500000 to cut the dataset by half.
ds <- RxOdbcData(table="ml_models", connectionString=connStr);
# Store the model in the database
model_name <- paste0("nyctaxi.", InputDataSet[1,]$payment_type);
rxWriteObject(ds, model_name, modelbin, version = "v1",
keyName = "model_name", valueName = "model_object", versionName = "model_version", overwrite = TRUE, serialize = FALSE);
}
'
,@input_data_1 = @input_query
,@input_data_1_partition_by_columns = N'payment_type'
,@input_data_1_order_by_columns = N'passenger_count'
,@parallel = 1
,@params = N'@instance_name nvarchar(100), @database_name nvarchar(128)'
,@instance_name = @instance_name
,@database_name = @database_name
WITH RESULT SETS NONE
END;
GO
Execução paralela
Observe que as entradas de sp_execute_external_script incluem @parallel=1
, usado para habilitar o processamento paralelo. Em contraste com as versões anteriores, a partir do SQL Server 2019, a configuração @parallel=1
fornece uma dica mais forte ao otimizador de consulta, tornando a execução paralela um resultado muito mais provável.
Por padrão, o otimizador de consulta tende a operar em @parallel=1
nas tabelas com mais de 256 linhas, mas se você puder, trate isso explicitamente definindo @parallel=1
, conforme mostrado neste script.
Dica
Para treinar cargas de trabalho, use @parallel
com qualquer script de treinamento arbitrário, mesmo aqueles que usam algoritmos não Microsoft Rx. Normalmente, somente os algoritmos do RevoScaleR (com o prefixo rx) oferecem paralelismo em cenários de treinamento no SQL Server. Mas com o novo parâmetro, você pode paralelizar um script que chama funções, incluindo funções open-source do R, não especificamente projetadas com essa capacidade. Isso funciona porque as partições têm afinidade com threads específicos. Portanto, todas as operações chamadas em um script são executadas por partição na thread especificada.
Executar o procedimento e treinar o modelo
Nesta seção, o script treina o modelo criado e salvo na etapa anterior. Os exemplos abaixo demonstram duas abordagens para treinar o modelo: uso de um conjunto de dados inteiro ou dados parciais.
Espere que essa etapa leve algum tempo. O treinamento é computacionalmente intensivo, levando vários minutos para ser concluído. Se os recursos do sistema, especialmente a memória, forem insuficientes para a carga, use um subconjunto dos dados. O segundo exemplo fornece a sintaxe.
--Example 1: train on entire dataset
EXEC train_rxLogIt_per_partition N'
SELECT payment_type, tipped, passenger_count, trip_time_in_secs, trip_distance, d.direct_distance
FROM dbo.nyctaxi_sample CROSS APPLY [CalculateDistance](pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as d
';
GO
--Example 2: Train on 20 percent of the dataset to expedite processing.
EXEC train_rxLogIt_per_partition N'
SELECT tipped, payment_type, passenger_count, trip_time_in_secs, trip_distance, d.direct_distance
FROM dbo.nyctaxi_sample TABLESAMPLE (20 PERCENT) REPEATABLE (98074)
CROSS APPLY [CalculateDistance](pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as d
';
GO
Observação
Se estiver executando outras cargas de trabalho, acrescente OPTION(MAXDOP 2)
à instrução SELECT, caso deseje limitar o processamento de consulta a apenas dois núcleos.
Verificar os resultados
O resultado na tabela de modelos deve ser cinco modelos diferentes, baseado em cinco partições segmentadas pelos cinco tipos de pagamento. Os modelos estão na fonte de dados ml_models
.
SELECT *
FROM ml_models
Definir um procedimento para prever os resultados
Use os mesmos parâmetros para pontuação. A amostra a seguir contém um script R que será pontuado com o uso do modelo correto para a partição que está sendo atualmente processada.
Como antes, crie um procedimento armazenado para encapsular o código R.
USE NYCTaxi_Sample
GO
-- Stored procedure that scores per partition.
-- Depending on the partition being processed, a model specific to that partition will be used
CREATE
OR
ALTER PROCEDURE [dbo].[predict_per_partition]
AS
BEGIN
DECLARE @predict_duration FLOAT
,@instance_name NVARCHAR(100) = @@SERVERNAME
,@database_name NVARCHAR(128) = db_name()
,@input_query NVARCHAR(max);
SET @input_query = 'SELECT tipped, passenger_count, trip_time_in_secs, trip_distance, d.direct_distance, payment_type
FROM dbo.nyctaxi_sample TABLESAMPLE (1 PERCENT) REPEATABLE (98074)
CROSS APPLY [CalculateDistance](pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as d'
EXEC sp_execute_external_script @language = N'R'
,@script =
N'
if (nrow(InputDataSet) > 0) {
#Get the partition that is currently being processed
current_partition <- InputDataSet[1,]$payment_type;
#Create the SQL query to select the right model
query_getModel <- paste0("select model_object from ml_models where model_name = ", "''", "nyctaxi.",InputDataSet[1,]$payment_type,"''", ";")
# Define the connection string
connStr <- paste("Driver=SQL Server;Server=", instance_name, ";Database=", database_name, ";Trusted_Connection=true;", sep="");
#Define data source to use for getting the model
ds <- RxOdbcData(sqlQuery = query_getModel, connectionString = connStr)
# Load the model
modelbin <- rxReadObject(ds, deserialize = FALSE)
# unserialize model
logitObj <- unserialize(modelbin);
# predict tipped or not based on model
predictions <- rxPredict(logitObj, data = InputDataSet, overwrite = TRUE, type = "response", writeModelVars = TRUE
, extraVarsToWrite = c("payment_type"));
OutputDataSet <- predictions
} else {
OutputDataSet <- data.frame(integer(), InputDataSet[,]);
}
'
,@input_data_1 = @input_query
,@parallel = 1
,@input_data_1_partition_by_columns = N'payment_type'
,@params = N'@instance_name nvarchar(100), @database_name nvarchar(128)'
,@instance_name = @instance_name
,@database_name = @database_name
WITH RESULT SETS((
tipped_Pred INT
,payment_type VARCHAR(5)
,tipped INT
,passenger_count INT
,trip_distance FLOAT
,trip_time_in_secs INT
,direct_distance FLOAT
));
END;
GO
Criar uma tabela para armazenar as previsões
CREATE TABLE prediction_results (
tipped_Pred INT
,payment_type VARCHAR(5)
,tipped INT
,passenger_count INT
,trip_distance FLOAT
,trip_time_in_secs INT
,direct_distance FLOAT
);
TRUNCATE TABLE prediction_results
GO
Executar o procedimento e salvar as previsões
INSERT INTO prediction_results (
tipped_Pred
,payment_type
,tipped
,passenger_count
,trip_distance
,trip_time_in_secs
,direct_distance
)
EXECUTE [predict_per_partition]
GO
Exibir as previsões
Como as previsões são armazenadas, execute uma consulta simples para retornar um conjunto de resultados.
SELECT *
FROM prediction_results;
Próximas etapas
- Neste tutorial, você usou sp_execute_external_script para iterar operações em dados particionados. Para obter uma análise mais detalhada de como chamar scripts externos em procedimentos armazenados e usar as funções do RevoScaleR, prossiga com o tutorial a seguir.