From b77887b7ec40434e57e9e7ff5380f056fdd1145c Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Tue, 4 Jun 2019 12:07:40 -0700 Subject: [PATCH 01/12] train in pyspark, export to mleap, and score in sql. --- .../features/sql-big-data-cluster/spark/mleap_sql/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/README.md diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md b/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md new file mode 100644 index 00000000..5eb8f803 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md @@ -0,0 +1,6 @@ +# MLeap on SQL Server Big Data cluster +This folder shows how we can build a model with Spark ML and then score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions) + +## Model training with Spark ML + +## Model scoring with SQL Server From 69cf5e405f4386a3fbcd3d14f9dd943425edbce7 Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Tue, 4 Jun 2019 14:48:33 -0700 Subject: [PATCH 02/12] initial check-in of the mleap_sql sample code. --- .../spark/mleap_sql/README.md | 10 +- .../spark/mleap_sql/jars/JavaTestPackage.jar | Bin 0 -> 12709 bytes .../jars/mssql_java_lang_extension.jar | Bin 0 -> 4748 bytes .../spark/mleap_sql/mleap_sql_test/cleanup.sh | 6 + .../mleap_sql/mleap_sql_test/mleap_pyspark.py | 237 +++++++++++++ .../mleap_sql_test/mleap_sql_tests.py | 164 +++++++++ .../spark/mleap_sql/mleap_sql_test/setup.sh | 17 + .../spark/mleap_sql/mleap_sql_test/test.sh | 6 + .../spark/mleap_sql/mssql-mleap-app/Makefile | 16 + .../spark/mleap_sql/mssql-mleap-app/build.sbt | 23 ++ .../lib/mssql_java_lang_extension.jar | Bin 0 -> 4748 bytes .../mssql-mleap-app/project/build.properties | 1 + .../mssql-mleap-app/project/plugins.sbt | 1 + .../sqlserver/mleap/PrimitiveDataset.java | 85 +++++ .../com/microsoft/sqlserver/mleap/Scorer.java | 314 ++++++++++++++++++ .../main/resources/adult_census_income.csv | 4 + .../microsoft/sqlserver/mleap/Predictor.scala | 66 ++++ .../com/microsoft/sqlserver/mleap/Score.scala | 235 +++++++++++++ .../microsoft/sqlserver/mleap/ScorerTest.java | 98 ++++++ .../sqlserver/mleap/PredictorTest.scala | 112 +++++++ 20 files changed, 1394 insertions(+), 1 deletion(-) create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/jars/JavaTestPackage.jar create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/jars/mssql_java_lang_extension.jar create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/cleanup.sh create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_pyspark.py create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_sql_tests.py create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/setup.sh create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/test.sh create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/Makefile create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/build.sbt create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/lib/mssql_java_lang_extension.jar create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/build.properties create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/plugins.sbt create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/resources/adult_census_income.csv create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java create mode 100644 samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md b/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md index 5eb8f803..80497f51 100644 --- a/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md @@ -1,6 +1,14 @@ # MLeap on SQL Server Big Data cluster -This folder shows how we can build a model with Spark ML and then score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions) +This folder shows how we can build a model with [Spark ML](https://spark.apache.org/docs/latest/ml-guide.html), export the model to [MLeap](https://github.com/combust/mleap), and score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions) ## Model training with Spark ML +In this sample code, AdultCensusIncome.csv is used to build a Spark ML pipeline model. We can [download the dataset from internet](mleap_sql_test/setup.sh#L11) and [put it on HDFS on a SQL BDC cluster](mleap_sql_test/setup.sh#L12) so that it can be accessed by Spark. + +The data is first [read into Spark](mleap_sql_test/mleap_pyspark.py#L25) and [split into training and testing datasets](mleap_sql_test/mleap_pyspark.py#L64). We then [train a pipeline mode with the training data](mleap_sql_test/mleap_pyspark.py#L87) and [export the model to a mleap bundle](mleap_sql_test/mleap_pyspark.py#L204). ## Model scoring with SQL Server +Now that we have the Spark ML pipeline model in a common serialization [MLeap bundle](http://mleap-docs.combust.ml/core-concepts/mleap-bundles.html) format, we can score the model in Java without the presence of Spark. + +In order to score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions), we need first build a Java application that can load the model into Java and score it. The [mssql-mleap-app folder](mssql-mleap-app/build.sbt) shows how that can be done. + +Then in T-SQL we can [call the Java application and score the model with some database table](mleap_sql_test/mleap_sql_tests.py#L101). diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/jars/JavaTestPackage.jar b/samples/features/sql-big-data-cluster/spark/mleap_sql/jars/JavaTestPackage.jar new file mode 100644 index 0000000000000000000000000000000000000000..296a16de73455f8c967c663e84315701cb0fdc1b GIT binary patch literal 12709 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 literal 0 HcmV?d00001 diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/jars/mssql_java_lang_extension.jar b/samples/features/sql-big-data-cluster/spark/mleap_sql/jars/mssql_java_lang_extension.jar new file mode 100644 index 0000000000000000000000000000000000000000..85cb713358b76adf4aed4542ceb5329840bd4508 GIT binary patch literal 4748 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 literal 0 HcmV?d00001 diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/cleanup.sh b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/cleanup.sh new file mode 100644 index 00000000..fb8dcd10 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/cleanup.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +echo "Cleaning up mleap_sql tests" + +hadoop fs -rm /user/root/AdultCensusIncome.csv +rm AdultCensusIncome.csv diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_pyspark.py b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_pyspark.py new file mode 100644 index 00000000..c450d166 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_pyspark.py @@ -0,0 +1,237 @@ +## train a pyspark model and export it as a mleap bundle +import os + +# parse command line arguments +import argparse +parser = argparse.ArgumentParser(description = 'train pyspark model and export mleap bundle') +parser.add_argument('hdfs_path', nargs='?', default = "/spark_ml", type = str) +parser.add_argument('model_name_export', nargs='?', default = "adult_census_pipeline.zip", type = str) +args = parser.parse_args() + +hdfs_path = args.hdfs_path +model_name_export = args.model_name_export + +# create spark session (needed only if this file is submitted as a spark jobs) +from pyspark.sql import SparkSession + +spark = SparkSession\ + .builder\ + .appName(os.path.basename(__file__))\ + .getOrCreate() + +############################################################################### +## prepare data + +# read the data into a spark data frame. +cwd = os.getcwd() +filename = "AdultCensusIncome.csv" + +## NOTE: reading text file from local file path seems flaky! +#import urllib.request +#url = "https://amldockerdatasets.azureedge.net/" + filename +#local_filename, headers = urllib.request.urlretrieve(url, filename) +#datafile = "file://" + os.path.join(cwd, filename) + +data_all = spark.read.format('csv')\ + .options( + header='true', + inferSchema='true', + ignoreLeadingWhiteSpace='true', + ignoreTrailingWhiteSpace='true')\ + .load(filename) #.load(datafile) for local file + +print("Number of rows: {}, Number of coulumns : {}".format(data_all.count(), len(data_all.columns))) + +#replace "-" with "_" in column names +columns_new = [col.replace("-", "_") for col in data_all.columns] +data_all = data_all.toDF(*columns_new) + +data_all.printSchema() +data_all.show(5) + +# choose feature columns and the label column for training. +label = "income" +#xvars = ["age", "hours_per_week"] #all numeric +xvars = ["age", "hours_per_week", "education"] #numeric + string + +print("label: {}, features: {}".format(label, xvars)) + +select_cols = xvars +select_cols.append(label) +data = data_all.select(select_cols) + +############################################################################### +## split data into train and test. + +train, test = data.randomSplit([0.75, 0.25], seed=123) + +print("train ({}, {})".format(train.count(), len(train.columns))) +print("test ({}, {})".format(test.count(), len(test.columns))) + +train_data_path = os.path.join(hdfs_path, "AdultCensusIncomeTrain") +test_data_path = os.path.join(hdfs_path, "AdultCensusIncomeTest") + +# write the train and test data sets to intermediate storage and then read +train.write.mode('overwrite').orc(train_data_path) +test.write.mode('overwrite').orc(test_data_path) + +print("train and test datasets saved to {} and {}".format(train_data_path, test_data_path)) + +train_read = spark.read.orc(train_data_path) +test_read = spark.read.orc(test_data_path) + +assert train_read.schema == train.schema and train_read.count() == train.count() +assert test_read.schema == test.schema and test_read.count() == test.count() + +############################################################################### +## train model + +from pyspark.ml import Pipeline, PipelineModel +from pyspark.ml.feature import OneHotEncoderEstimator, StringIndexer, IndexToString, VectorAssembler +from pyspark.ml.classification import LogisticRegression + +# create a new Logistic Regression model, which by default uses "features" and "label" columns for training. +reg = 0.1 +lr = LogisticRegression(regParam=reg) + +# encode string columns +dtypes = dict(train.dtypes) +dtypes.pop(label) + +si_xvars = [] +ohe_xvars = [] +featureCols = [] +for idx,key in enumerate(dtypes): + if dtypes[key] == "string": + featureCol = "-".join([key, "encoded"]) + featureCols.append(featureCol) + + tmpCol = "-".join([key, "tmp"]) + si_xvars.append(StringIndexer(inputCol=key, outputCol=tmpCol, handleInvalid="skip")) #, handleInvalid="keep" + ohe_xvars.append(OneHotEncoderEstimator(inputCols=[tmpCol], outputCols=[featureCol])) + else: + featureCols.append(key) + +# string-index the label column into a column named "label" +si_label = StringIndexer(inputCol=label, outputCol='label') +#si_label._resetUid("si_label") # try to name the transformer, which seems not carried over to the fitted pipeline. + +# assemble the encoded feature columns in to a column named "features" +assembler = VectorAssembler(inputCols=featureCols, outputCol="features") + +# put together the pipeline +stages = [] +stages.extend(si_xvars) +stages.extend(ohe_xvars) +stages.append(si_label) +stages.append(assembler) +stages.append(lr) + +pipe = Pipeline(stages=stages) +print("Pipeline Created") + +# train the model +model = pipe.fit(train) +print("Model Trained") +print("Model is ", model) +print("Model Stages", model.stages) + +# name the string-index stage for the label so it can be identified easier later +model.stages[2]._resetUid("si_label") + +############################################################################### +## evaluate model + +from pyspark.ml.evaluation import BinaryClassificationEvaluator + +# make prediction +pred = model.transform(test) + +# evaluate. note only 2 metrics are supported out of the box by Spark ML. +bce = BinaryClassificationEvaluator(rawPredictionCol='rawPrediction') +au_roc = bce.setMetricName('areaUnderROC').evaluate(pred) +au_prc = bce.setMetricName('areaUnderPR').evaluate(pred) + +print("Area under ROC: {}".format(au_roc)) +print("Area Under PR: {}".format(au_prc)) + +############################################################################### +## save and load the model with ML persistence +# https://spark.apache.org/docs/latest/ml-pipeline.html#ml-persistence-saving-and-loading-pipelines + +##NOTE: by default the model is saved to and loaded from hdfs +model_name = "AdultCensus.mml" +model_fs = os.path.join(hdfs_path, model_name) + +model.write().overwrite().save(model_fs) +print("saved model to {}".format(model_fs)) + +# load the model file (from hdfs) +print("load pyspark model from hdfs") +model_loaded = PipelineModel.load(model_fs) +assert str(model_loaded) == str(model) + +print("loaded model from {}".format(model_fs)) +print("Model is " , model_loaded) +print("Model stages", model_loaded.stages) + +############################################################################### +## export and import model with mleap + +import mleap.pyspark +from mleap.pyspark.spark_support import SimpleSparkSerializer + +# serialize the model to a local zip file in JSON format +#model_name_export = "adult_census_pipeline.zip" +model_name_path = cwd +model_file = os.path.join(model_name_path, model_name_export) + +# remove an old model file, if needed. +if os.path.isfile(model_file): + os.remove(model_file) + +model_file_path = "jar:file:{}".format(model_file) +model.serializeToBundle(model_file_path, model.transform(train)) + +## import mleap model +model_deserialized = PipelineModel.deserializeFromBundle(model_file_path) +assert str(model_deserialized) == str(model) + +print("The deserialized model is ", model_deserialized) +print("The deserialized model stages are", model_deserialized.stages) + +############################################################################## +## export the final model with mleap + +## remove the stringIndexer for the label column so it won't be required for prediction +model_final = model.copy() + +si_label_index = -3 +model_final.stages.pop(si_label_index) #si_label + +## append an IndexToString transformer to the model pipeline to get the original labels +#labelReverse = IndexToString(inputCol = "label", outputCol = "predIncome") #no need to provide labels +labelReverse = IndexToString( + inputCol = "prediction", + outputCol = "predictedIncome", + labels = model.stages[si_label_index].labels) #must provide labels (from si_label) otherwise will fail +model_final.stages.append(labelReverse) + +pred_final = model_final.transform(test) +pred_final.printSchema() +pred_final.show(5) + +# remove an old model file, if needed. +if os.path.isfile(model_file): + os.remove(model_file) +model_final.serializeToBundle(model_file_path, model_final.transform(train)) + +print("persist the mleap bundle from local to hdfs") +from subprocess import Popen, PIPE +hdfs_fs_put = ["hadoop", "fs", "-put", "-f", model_file, os.path.join(hdfs_path, model_name_export)] +proc = Popen(hdfs_fs_put, stdout=PIPE, stderr=PIPE) +s_output, s_err = proc.communicate() +if (s_err): + print("s_output: {s_output}\ns_err: {s_err}".format(s_output=s_output, s_err=s_err)) + +############################################################################### diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_sql_tests.py b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_sql_tests.py new file mode 100644 index 00000000..a1b3e1c8 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_sql_tests.py @@ -0,0 +1,164 @@ +import os +dir_path = os.path.dirname(os.path.realpath(__file__)) + +import sys +sys.path.append(os.path.join(dir_path, os.pardir, os.pardir, os.pardir)) + +from spark_submit import * + +from subprocess import run, PIPE +import pytest +import pyodbc + + +@pytest.fixture(scope="module") +def setup_mod(): + print("setting up module ...") + + odbcDriver = "ODBC Driver 13 for SQL Server" + databaseName = "tempdb" + headNode = "master-0.master-svc" + + # Read sql username and password from environment variable. + username = os.environ["EXTENSIBILITY_TEST_SQL_USER"] + password = os.environ["EXTENSIBILITY_TEST_SQL_PASSWORD"] + if not username or not password: + raise Exception("Environment variable EXTENSIBILITY_TEST_SQL_USER or EXTENSIBILITY_TEST_SQL_PASSWORD cannot not be found") + + # enable SPEES + conn = pyodbc.connect('DRIVER={0};SERVER={1};DATABASE={2};UID={3};PWD={4}'.format( + odbcDriver, headNode, databaseName, username, password), autocommit=True) + cursor = conn.cursor() + cursor.execute("""EXEC sp_configure 'external scripts enabled', 1""") + assert(-1 == cursor.rowcount) + + cursor.execute("""RECONFIGURE""") + assert(-1 == cursor.rowcount) + + yield dict(cursor=cursor) + + print("tearing down module ...") + + +def test_java_spees(setup_mod): + # exectue a Java SPEES query to create external libraries + cursor = setup_mod['cursor'] + cursor.execute(""" + --SELECT @@SERVERNAME AS 'Server Name', @@VERSION AS 'Server Version', @@SERVICENAME AS 'Service Name' + + IF NOT EXISTS (SELECT * FROM sys.external_languages WHERE language = 'Java') + --DROP EXTERNAL LANGUAGE Java; + CREATE EXTERNAL LANGUAGE Java + FROM (CONTENT = N'/opt/mssql/lib/extensibility/java-lang-extension.tar.gz', file_name = 'javaextension.so'); + + IF EXISTS (SELECT * FROM sys.external_libraries WHERE name = 'SdkPackage') + DROP EXTERNAL LIBRARY SdkPackage; + CREATE EXTERNAL LIBRARY SdkPackage + FROM (CONTENT = '/opt/mssql/lib/mssql-java-lang-extension.jar') WITH (LANGUAGE = 'Java'); + + IF EXISTS (SELECT * FROM sys.external_libraries WHERE name = 'TestPackage') + DROP EXTERNAL LIBRARY TestPackage + CREATE EXTERNAL LIBRARY TestPackage + FROM (CONTENT = '/opt/mssql/java/jars/JavaTestPackage.jar') WITH (LANGUAGE = 'Java'); + + DECLARE @script NVARCHAR(max) = N'JavaTestPackage.PassThrough' --no space allowed in the string! + EXEC sp_execute_external_script + @language = N'Java' + , @script = @script + , @input_data_1 = N'SELECT 1' + """) + + rows = cursor.fetchall() + assert(1 == len(rows)) + assert(1 == rows[0][0]) + + +def dictfetchall(cursor): + '''fetch all rows from a cursor and return them as a dict''' + colnames = [col[0] for col in cursor.description] + return [dict(zip(colnames, row)) for row in cursor.fetchall()] + + +def test_mleap_pyspark(setup_mod): + # train a pyspark model and export it as a mleap bundle + hdfs_path = "/spark_ml" + model_name_export = "adult_census_pipeline.zip" + + file_path = 'mleap_pyspark.py' + file_args = [hdfs_path, model_name_export] + ret = spark_submit(file_path, file_args) + assert 0 == ret + + # get the mleap bundle from hdfs and copy it to the mssql-server container of the master-0 pod + hdfs_file_path = os.path.join(hdfs_path, model_name_export) + ret = run(["hdfs", "dfs", "-get", "-f", hdfs_file_path], stdout=PIPE, stderr=PIPE).returncode + assert 0 == ret + + local_file_path = os.path.join("master-0:", "tmp") + ret = run(["kubectl", "cp", model_name_export, local_file_path, "-c", "mssql-server"], stdout=PIPE, stderr=PIPE).returncode + assert 0 == ret + + # exectue a Java SPEES query to serve the mleap bundle + cursor = setup_mod['cursor'] + cursor.execute(""" + --suppresses the record count values generated by DML statements + --like UPDATE and allows the result set to be retrieved directly. + SET NOCOUNT ON; + + IF EXISTS (SELECT * FROM sys.external_libraries WHERE name = 'MleapApp') + DROP EXTERNAL LIBRARY MleapApp; + CREATE EXTERNAL LIBRARY MleapApp + FROM (CONTENT = '/opt/mssql/java/jars/mssql-mleap-app-assembly-1.0.jar') WITH (LANGUAGE = 'Java') + + DROP TABLE IF EXISTS ##test + CREATE TABLE ##test ( + income nvarchar(10) + , age int + , hours_per_week int + , education nvarchar(10) + , sex nvarchar(10) + ); + INSERT INTO ##test values ('<=50K', 39, 40, 'Bachelors', 'Male'); + INSERT INTO ##test values ('<=50K', 50, 13, 'Bachelors', 'Male'); + INSERT INTO ##test values ('<=50K', 38, 40, 'HS-grad', 'Male'); + --SELECT * FROM ##test + + DECLARE @script NVARCHAR(max) = N'com.microsoft.sqlserver.mleap.Scorer' --no space allowed in the string! + DECLARE @language nvarchar(4) = N'Java' + DECLARE @parallel bit = 0 + DECLARE @input_data_1 nvarchar(97) = N'select age, hours_per_week, education, sex, income from ##test' + DECLARE @params nvarchar(200) = N'@modelPath nvarchar(100), @outputFields nvarchar(100), @logLevel nvarchar(100)' + DECLARE @modelPath nvarchar(100) = N'/tmp/adult_census_pipeline.zip' + DECLARE @outputFields nvarchar(100) = N'prediction,probability,education,sex,income,predictedIncome' + DECLARE @logLevel nvarchar(100) = N'INFO' + EXEC sp_execute_external_script @language = @language, @script = @script, @parallel = @parallel + , @input_data_1 = @input_data_1 + , @params = @params, @modelPath = @modelPath, @outputFields = @outputFields, @logLevel = @logLevel + WITH RESULT SETS ((prediction int, probability0 float, probability1 float, education nvarchar(20), sex nvarchar(20), income nvarchar(20), predictedIncome nvarchar(20))) + """) + + rows = dictfetchall(cursor) + #pandas.DataFrame(rows) + + assert rows == [ + {'education': 'Bachelors', + 'income': '<=50K', + 'predictedIncome': '<=50K', + 'prediction': 0, + 'probability0': 0.6544871023375456, + 'probability1': 0.3455128976624544, + 'sex': 'Male'}, + {'education': 'Bachelors', + 'income': '<=50K', + 'predictedIncome': '<=50K', + 'prediction': 0, + 'probability0': 0.7363751868447964, + 'probability1': 0.2636248131552036, + 'sex': 'Male'}, + {'education': 'HS-grad', + 'income': '<=50K', + 'predictedIncome': '<=50K', + 'prediction': 0, + 'probability0': 0.8324466132959966, + 'probability1': 0.16755338670400344, + 'sex': 'Male'}] diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/setup.sh b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/setup.sh new file mode 100644 index 00000000..80f376c3 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/setup.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e + +echo "Setting up mleap_sql tests" + +export PYSPARK_PYTHON=python3 + +export EXTENSIBILITY_TEST_SQL_USER=sa +export EXTENSIBILITY_TEST_SQL_PASSWORD=Yukon900 + +hadoop fs -mkdir -p /user/root +wget https://amldockerdatasets.azureedge.net/AdultCensusIncome.csv +hadoop fs -copyFromLocal AdultCensusIncome.csv /user/root + +# Copy java ext jars to mssql-server container in master pod +#kubectl cp -c mssql-server ../jars/mssql_java_lang_extension.jar master-0:/opt/mssql/java/jars/ +kubectl cp -c mssql-server ../jars/JavaTestPackage.jar master-0:/opt/mssql/java/jars/ +kubectl cp -c mssql-server ../mssql-mleap-app/target/scala-2.11/mssql-mleap-app-assembly-1.0.jar master-0:/opt/mssql/java/jars/ diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/test.sh b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/test.sh new file mode 100644 index 00000000..7308e25f --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +source ./setup.sh + +# Generate Junit results +python3 -m pytest -v --junitxml /tests/junit/mleap_sql.xml -o junit_suite_name=mleap_sql --durations=0 mleap_sql_tests.py diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/Makefile b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/Makefile new file mode 100644 index 00000000..5f7abc4e --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/Makefile @@ -0,0 +1,16 @@ +.DEFAULT_GOAL = all + +all: assembly + +assembly: + @sbt assembly + +package: + @sbt package + +clean: + @rm -rf project/project + @rm -rf project/target + @rm -rf target + @rm -rf .idea + diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/build.sbt b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/build.sbt new file mode 100644 index 00000000..ef27c126 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/build.sbt @@ -0,0 +1,23 @@ +name := "mssql-mleap-app" + +version := "1.0" + +scalaVersion := "2.11.12" + +libraryDependencies ++= Seq( + "ml.combust.mleap" %% "mleap-runtime" % "0.13.0" % "provided", + "org.apache.commons" % "commons-csv" % "1.5", + "commons-cli" % "commons-cli" % "1.4", + "org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test, + "org.scalacheck" %% "scalacheck" % "1.14.0" % Test, + "com.novocode" % "junit-interface" % "0.11" % Test +) + +// Exclude scala-library from this fat jar. The scala library is already there in spark package. +assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false) + +// exclude specific jars +assemblyExcludedJars in assembly := { + val cp = (fullClasspath in assembly).value + cp filter {_.data.getName == "mssql_java_lang_extension.jar"} +} diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/lib/mssql_java_lang_extension.jar b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/lib/mssql_java_lang_extension.jar new file mode 100644 index 0000000000000000000000000000000000000000..85cb713358b76adf4aed4542ceb5329840bd4508 GIT binary patch literal 4748 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 literal 0 HcmV?d00001 diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/build.properties b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/build.properties new file mode 100644 index 00000000..e9a676f7 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.1.5 \ No newline at end of file diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/plugins.sbt b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/plugins.sbt new file mode 100644 index 00000000..652a3b93 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java new file mode 100644 index 00000000..3e9a269d --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java @@ -0,0 +1,85 @@ +package com.microsoft.sqlserver.mleap; + +import java.sql.JDBCType; +import java.sql.Types; +import java.util.Arrays; + +public class PrimitiveDataset extends com.microsoft.sqlserver.javalangextension.PrimitiveDataset { + public String[] getColumnNames() { + int nCols = getColumnCount(); + String[] columnNames = new String[nCols]; + + for (int iCol = 0; iCol < nCols; iCol++) { + columnNames[iCol] = getColumnName(iCol); + } + + return columnNames; + } + + public int[] getColumnTypes() { + int nCols = getColumnCount(); + int[] columnTypes = new int[nCols]; + + for (int iCol = 0; iCol < nCols; iCol++) { + columnTypes[iCol] = getColumnType(iCol); + } + + return columnTypes; + } + + public int getColumnIndex(String columnName) { + String[] columnNames = getColumnNames(); + int index = Arrays.asList(columnNames).indexOf(columnName); + return index; + } + + public int getRowCount(int iCol) { + int sqlType = getColumnType(iCol); + int columnLength; + + switch(sqlType) { + case Types.BIT: + columnLength = getBooleanColumn(iCol).length; + break; + case Types.SMALLINT: + columnLength = getShortColumn(iCol).length; + break; + case Types.INTEGER: + columnLength = getIntColumn(iCol).length; + break; + case Types.BIGINT: + columnLength = getLongColumn(iCol).length; + break; + case Types.FLOAT: + columnLength = getFloatColumn(iCol).length; + break; + case Types.DOUBLE: + columnLength = getDoubleColumn(iCol).length; + break; + case Types.NVARCHAR: + columnLength = getStringColumn(iCol).length; + break; + case Types.VARBINARY: + columnLength = getBinaryColumn(iCol).length; + break; + case Types.DATE: + columnLength = getDateColumn(iCol).length; + break; + default: + throw new IllegalArgumentException("unsupported sql type: " + JDBCType.valueOf(sqlType).getName()); + } + + return columnLength; + } + + public int[] getRowCounts() { + int nCols = getColumnCount(); + int[] rowCounts = new int[nCols]; + + for (int iCol = 0; iCol < nCols; iCol++) { + rowCounts[iCol] = getRowCount(iCol); + } + + return rowCounts; + } +} diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java new file mode 100644 index 00000000..a7f60117 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java @@ -0,0 +1,314 @@ +package com.microsoft.sqlserver.mleap; + +import com.microsoft.sqlserver.javalangextension.AbstractSqlServerExtensionExecutor; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.cli.*; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.Reader; + +import java.sql.JDBCType; +import java.sql.Types; + +import java.util.Arrays; +import java.util.List; +import java.util.LinkedHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Scorer extends AbstractSqlServerExtensionExecutor { + + private static final Logger LOGGER = Logger.getLogger(Scorer.class.getName()); + + public Scorer() { + executorExtensionVersion = SQLSERVER_JAVA_LANG_EXTENSION_V1; + executorInputDatasetClassName = PrimitiveDataset.class.getName(); + executorOutputDatasetClassName = PrimitiveDataset.class.getName(); + } + + public void init(String sessionId, int taskId, int numTasks) { + System.out.println("init SessionID: " + sessionId + " taskId: " + taskId + " numTasks: " + numTasks); + } + + public PrimitiveDataset execute(PrimitiveDataset input, LinkedHashMap params) { + List logLevels = Arrays.asList("OFF", "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST", "ALL"); + String logLevel = params.getOrDefault("logLevel", "WARNING").toString(); + if (!logLevels.contains(logLevel)) { + throw new IllegalArgumentException("logLevel (" + logLevel + ") must be one of " + logLevels.toString()); + } + LOGGER.setLevel(Level.parse(logLevel)); + + LOGGER.info("Logger Name: " + LOGGER.getName() + "; Logger Level:" + LOGGER.getLevel()); + + // load model + String modelPath; + try { + modelPath = params.get("modelPath").toString(); + } catch (NullPointerException e) { + throw new IllegalArgumentException("modelPath parameter is required but not set."); + } + + long startTime = System.nanoTime(); + Predictor scorer = new Predictor(); + scorer.init(modelPath); + long endTime = System.nanoTime(); + long duration = (endTime - startTime); //divide by 10^6 to get milliseconds. + LOGGER.info("model loading time: " + duration/1e6 + " ms"); + + // convert PrimitiveDataset to DefaultLeapFrame + startTime = System.nanoTime(); + scorer.primitiveDataset2leapFrame(input); + endTime = System.nanoTime(); + duration = (endTime - startTime); //divide by 10^6 to get milliseconds. + LOGGER.info("PrimitiveDataset to DefaultLeapFrame conversion time: " + duration/1e6 + " ms"); + + // do prediction + startTime = System.nanoTime(); + scorer.run(); + endTime = System.nanoTime(); + duration = (endTime - startTime); //divide by 10^6 to get milliseconds. + LOGGER.info("model scoring time: " + duration/1e6 + " ms"); + + //select output fields specified + startTime = System.nanoTime(); + String[] outputFields; + outputFields = params.getOrDefault("outputFields", "").toString().split(","); + scorer.select(outputFields); + endTime = System.nanoTime(); + duration = (endTime - startTime); //divide by 10^6 to get milliseconds. + LOGGER.info("data selection time: " + duration/1e6 + " ms"); + + // convert DefaultLeapFrame to PrimitiveDataset + startTime = System.nanoTime(); + PrimitiveDataset output = new PrimitiveDataset(); + scorer.leapFrame2primitiveDataset(output); + endTime = System.nanoTime(); + duration = (endTime - startTime); //divide by 10^6 to get milliseconds. + LOGGER.info("DefaultLeapFrame to PrimitiveDataset conversion time: " + duration/1e6 + " ms"); + + return output; + } + + public void cleanup() { + System.out.println("\n* cleanup"); + } + + /** + * + * @param args commandline options for model path and input file. + * @throws Exception + *
+     * {@code
+     *
+     * -- ex: Linux
+     * java -cp mssql-mleap-app-assembly-1.0.jar:mssql_java_lang_extension.jar:mssql-mleap-lib-assembly-1.0.jar:commons-csv-1.5.jar:commons-cli-1.4.jar com.microsoft.sqlserver.mleap.Scorer
+     *  -m /tmp/adult_census_pipeline.zip
+     *  -i /tmp/adult_census_income.csv
+     *
+     * java -cp "*" -m /tmp/adult_census_pipeline.zip -i /tmp/adult_census_income.csv
+     *
+     * -- ex: Windows
+     * java -cp mssql-mleap-app-assembly-1.0.jar;mssql_java_lang_extension.jar;mssql-mleap-lib-assembly-1.0.jar;commons-csv-1.5.jar:commons-cli-1.4.jar com.microsoft.sqlserver.mleap.Scorer
+     *  -m C:\\Users\\lgong\\Work\\git\\aml-databricks\\examples\\mleapsql2\\src\\main\\resources\\sqlqueries\\adult_census_pipeline.zip
+     *  -i C:\\Users\\lgong\\Work\\git\\aml-databricks\\examples\\mleapsql2\\src\\main\\resources\\sqlqueries\\adult_census_income.csv
+     *
+     * java -cp "*"
+     *  -m C:\\Users\\lgong\\Work\\git\\aml-databricks\\examples\\mleapsql2\\src\\main\\resources\\sqlqueries\\adult_census_pipeline.zip
+     *  -i C:\\Users\\lgong\\Work\\git\\aml-databricks\\examples\\mleapsql2\\src\\main\\resources\\sqlqueries\\adult_census_income.csv
+     * }
+     * 
+ */ + public static void main(String[] args) throws Exception { + // get model and testing data + Options options = new Options(); + + Option input = new Option("i", "input", true, "input file"); + input.setRequired(true); + options.addOption(input); + + Option model = new Option("m", "model", true, "model path"); + model.setRequired(true); + options.addOption(model); + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.out.println(e.getMessage()); + formatter.printHelp("Scorer", options); + + System.exit(1); + } + + String modelPath = cmd.getOptionValue("model"); + String scoreFile = cmd.getOptionValue("input"); + + LOGGER.info("os.name: " + System.getProperty("os.name")); + LOGGER.info("isWindows: " + System.getProperty("os.name").startsWith("Windows")); + LOGGER.info("args: " + Arrays.toString(args)); + + LOGGER.info("modelPath: " + modelPath); + LOGGER.info("scoreFile: " + scoreFile); + + // read in the testing data + BufferedReader bufferedReader = new BufferedReader(new FileReader(scoreFile)); + int nRows = -1; //account for the header row + while(bufferedReader.readLine() != null) { + nRows++; + } + + LinkedHashMap inputFields = new LinkedHashMap(); + inputFields.put("age", Types.INTEGER); + inputFields.put("workclass", Types.NVARCHAR); + inputFields.put("fnlwgt", Types.INTEGER); + inputFields.put("education", Types.NVARCHAR); + inputFields.put("education_num", Types.INTEGER); + inputFields.put("marital_status", Types.NVARCHAR); + inputFields.put("occupation", Types.NVARCHAR); + inputFields.put("relationship", Types.NVARCHAR); + inputFields.put("race", Types.NVARCHAR); + inputFields.put("sex", Types.NVARCHAR); + inputFields.put("capital_gain", Types.INTEGER); + inputFields.put("capital_loss", Types.INTEGER); + inputFields.put("hours_per_week", Types.INTEGER); + inputFields.put("native_country", Types.NVARCHAR); + inputFields.put("income", Types.NVARCHAR); + + String[] columnNames = {"age", "hours_per_week", "education", "sex", "income"}; //choose the input variables + int[] columnTypes = new int[columnNames.length]; + for (int iCol = 0; iCol < columnNames.length; iCol++) { + try { + columnTypes[iCol] = inputFields.get(columnNames[iCol]); + } catch (NullPointerException e) { + throw new IllegalArgumentException("invalid input field: " + columnNames[iCol]); + } + } + int nCols = columnNames.length; + + Object[] columns = new Object[nCols]; + for (int iCol = 0; iCol < nCols; iCol++) { + int columnType = columnTypes[iCol]; + switch (columnType) { + case Types.INTEGER: + columns[iCol] = new int[nRows]; + break; + case Types.NVARCHAR: + columns[iCol] = new String[nRows]; + break; + default: + throw new IllegalArgumentException("unsupported sql type: " + JDBCType.valueOf(columnType).getName()); + } + } + + Reader in = new FileReader(scoreFile); + Iterable records = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in); + int iRow = 0; + for (CSVRecord record : records) { + for (int iCol = 0; iCol < nCols; iCol++) { + int columnType = columnTypes[iCol]; + switch (columnType) { + case Types.INTEGER: + ((int[])(columns[iCol]))[iRow] = Integer.parseInt(record.get(columnNames[iCol])); + break; + case Types.NVARCHAR: + ((String[])(columns[iCol]))[iRow] = record.get(columnNames[iCol]); + break; + default: + throw new IllegalArgumentException("unsupported sql type: " + JDBCType.valueOf(columnType).getName()); + } + } + iRow++; + } + + // form the primitive dataset + PrimitiveDataset inputds = new PrimitiveDataset(); + + for (int iCol = 0; iCol < nCols; iCol++) { + int columnType = columnTypes[iCol]; + switch (columnType) { + case Types.INTEGER: + inputds.addColumnMetadata(iCol, columnNames[iCol], Types.INTEGER, 0, 0); + inputds.addIntColumn(iCol, (int[])(columns[iCol]), null); + break; + case Types.NVARCHAR: + inputds.addColumnMetadata(iCol, columnNames[iCol], Types.NVARCHAR, 0, 0); + inputds.addStringColumn(iCol, (String[])(columns[iCol])); + break; + default: + throw new IllegalArgumentException("unsupported sql type: " + JDBCType.valueOf(columnType).getName()); + } + } + + // specify some params + LinkedHashMap params = new LinkedHashMap<>(); + params.put("logLevel", "INFO"); //default WARN + params.put("modelPath", modelPath); + params.put("outputFields", "prediction,probability,education,sex,income,predictedIncome"); + //params.put("outputFields", "features,education-encoded"); //SparseTensor + + // perform scoring + Scorer scorer = new Scorer(); + scorer.init("session0", 0, 1); + PrimitiveDataset output = scorer.execute(inputds, params); + + // display output + int nOutputCols = output.getColumnCount(); + System.out.println("\nnOutputCols: " + nOutputCols); + + for (int iCol = 0; iCol < nOutputCols; iCol++) { + System.out.println("\nColumnName: " + output.getColumnName(iCol)); + + int columnType = output.getColumnType(iCol); + System.out.println("ColumnType: " + JDBCType.valueOf(columnType).getName()); + + switch(columnType) { + case Types.INTEGER: + int[] intColumn = output.getIntColumn(iCol); + System.out.println("Column.length: " + intColumn.length); + System.out.println("Column: " + Arrays.toString(intColumn)); + break; + case Types.DOUBLE: + double[] doubleColumn = output.getDoubleColumn(iCol); + System.out.println("Column.length: " + doubleColumn.length); + System.out.println("Column: " + Arrays.toString(doubleColumn)); + break; + case Types.BIGINT: + long[] longColumn = output.getLongColumn(iCol); + System.out.println("Column.length: " + longColumn.length); + System.out.println("Column: " + Arrays.toString(longColumn)); + break; + case Types.BIT: + boolean[] booleanColumn = output.getBooleanColumn(iCol); + System.out.println("Column.length: " + booleanColumn.length); + System.out.println("Column: " + Arrays.toString(booleanColumn)); + break; + case Types.FLOAT: + float[] floatColumn = output.getFloatColumn(iCol); + System.out.println("Column.length: " + floatColumn.length); + System.out.println("Column: " + Arrays.toString(floatColumn)); + break; + case Types.SMALLINT: + short[] shortColumn = output.getShortColumn(iCol); + System.out.println("Column.length: " + shortColumn.length); + System.out.println("Column: " + Arrays.toString(shortColumn)); + break; + case Types.NVARCHAR: + String[] stringColumn = output.getStringColumn(iCol); + System.out.println("Column.length: " + stringColumn.length); + System.out.println("Column: " + Arrays.toString(stringColumn)); + break; + default: + System.out.println("No columnType " + JDBCType.valueOf(columnType).getName()); + } + } + + // cleanup + scorer.cleanup(); + } +} diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/resources/adult_census_income.csv b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/resources/adult_census_income.csv new file mode 100644 index 00000000..48a1b390 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/resources/adult_census_income.csv @@ -0,0 +1,4 @@ +age,workclass,fnlwgt,education,education_num,marital_status,occupation,relationship,race,sex,capital_gain,capital_loss,hours_per_week,native_country,income +39,State-gov,77516,Bachelors,13,Never-married,Adm-clerical,Not-in-family,White,Male,2174,0,40,United-States,<=50K +50,Self-emp-not-inc,83311,Bachelors,13,Married-civ-spouse,Exec-managerial,Husband,White,Male,0,0,13,United-States,<=50K +38,Private,215646,HS-grad,9,Divorced,Handlers-cleaners,Not-in-family,White,Male,0,0,40,United-States,<=50K diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala new file mode 100644 index 00000000..c319a510 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala @@ -0,0 +1,66 @@ +package com.microsoft.sqlserver.mleap + +import java.io.File +import java.util.logging.Logger +import java.util.logging.Level + +import ml.combust.bundle.BundleFile +import ml.combust.mleap.runtime.MleapSupport._ +import ml.combust.mleap.runtime.frame.Transformer +import resource._ + +class Predictor extends Score { + var model: Transformer = null + private val LOGGER = Logger.getLogger(classOf[Scorer].getName) + + def init(model_path: String) { + LOGGER.info(s"init($model_path)") + + model = (for(bf <- managed(BundleFile(new File(model_path)))) yield { + bf.loadMleapBundle() + }).tried.flatMap(identity).get.root + + if (LOGGER.getLevel.intValue() <= Level.INFO.intValue()) { + println("\nmodel schema fields:") + model.schema.fields.zipWithIndex.foreach { + case (field, idx) => println(s"$idx $field") + } + + println("\nmodel inputSchema fields:") + model.inputSchema.fields.zipWithIndex.foreach { + case (field, idx) => println(s"$idx $field") + } + + println("\nmodel outputSchema fields:") + model.outputSchema.fields.zipWithIndex.foreach { + case (field, idx) => println(s"$idx $field") + } + } + + LOGGER.info(s"model loaded...\n") + } + + def run(): Unit = { + frame_out = model.transform(frame_in).get + + if (LOGGER.getLevel.intValue() <= Level.INFO.intValue()) { + println("\noutput schema fields:") + frame_out.schema.fields.zipWithIndex.foreach { + case (field, idx) => println(s"$idx $field") + } + } + + //leapFrame2json(frame_out) + } + + def select(fieldNames: Array[String]) { + if (fieldNames.nonEmpty && fieldNames.length != 1 && fieldNames(0) != "") { + val allFieldNames = frame_out.schema.fields.map(_.name) + if (!fieldNames.forall(allFieldNames.contains)) { + throw new IllegalArgumentException(s"${fieldNames.toList} must be a subset of $allFieldNames") + } + + frame_out = frame_out.select(fieldNames: _*).get + } + } +} diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala new file mode 100644 index 00000000..342dcfa7 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala @@ -0,0 +1,235 @@ +package com.microsoft.sqlserver.mleap + +import java.io.File +import java.sql.{JDBCType, Types} + +import ml.combust.mleap.runtime.MleapSupport._ +import ml.combust.mleap.runtime.frame.{DefaultLeapFrame, Row} +import ml.combust.mleap.runtime.serialization.{BuiltinFormats, FrameReader} +import ml.combust.mleap.core.types._ +import ml.combust.mleap.tensor.{ByteString, DenseTensor, SparseTensor} + +trait Score { + + var frame_in: DefaultLeapFrame = null + var frame_out: DefaultLeapFrame = null + + def getScalaType(sqlType: Int): ScalarType = { + sqlType match { + case Types.BIT => ScalarType.Boolean + case Types.TINYINT => ScalarType.Byte + case Types.SMALLINT => ScalarType.Short + case Types.INTEGER => ScalarType.Int + case Types.BIGINT => ScalarType.Long + case Types.FLOAT => ScalarType.Float + case Types.DOUBLE => ScalarType.Double + case Types.NVARCHAR => ScalarType.String + case Types.BINARY => ScalarType.ByteString + case _ => throw new IllegalArgumentException("unsupported sql type: " + JDBCType.valueOf(sqlType).getName) + } + } + + def getSqlType(mleapType: BasicType): Int = { + mleapType match { + case BasicType.Boolean => Types.BIT + case BasicType.Byte => Types.TINYINT + case BasicType.Short => Types.SMALLINT + case BasicType.Int => Types.INTEGER + case BasicType.Long => Types.BIGINT + case BasicType.Float => Types.FLOAT + case BasicType.Double => Types.DOUBLE + case BasicType.String => Types.NVARCHAR + case BasicType.ByteString => Types.BINARY + case _ => throw new IllegalArgumentException("unsupported mleap type: " + mleapType) + } + } + + def primitiveDataset2leapFrame(input: PrimitiveDataset) { + val nCols = input.getColumnCount() + val nRows = input.getRowCount(0) // assuming columns have the same length + + // Create a schema. + val fields = List.newBuilder[StructField] + for (iCol <- 0 until nCols) { + fields += StructField(input.getColumnName(iCol), getScalaType(input.getColumnType(iCol))) + } + val schema = StructType(fields.result).get + + // Create a dataset to contain all of our values + val seqBuilder = Seq.newBuilder[Row] + for (iRow <- 0 until nRows) { + val values = List.newBuilder[Any] + for (iCol <- 0 until nCols) { + val columnType = input.getColumnType(iCol) + values += (columnType match { + case Types.BIT => input.getBooleanColumn(iCol)(iRow) + case Types.SMALLINT => input.getShortColumn(iCol)(iRow) + case Types.INTEGER => input.getIntColumn(iCol)(iRow) + case Types.BIGINT => input.getLongColumn(iCol)(iRow) + case Types.FLOAT => input.getFloatColumn(iCol)(iRow) + case Types.DOUBLE => input.getDoubleColumn(iCol)(iRow) + case Types.NVARCHAR => input.getStringColumn(iCol)(iRow) + case Types.VARBINARY => input.getBinaryColumn(iCol)(iRow) + case Types.DATE => input.getDateColumn(iCol)(iRow) + case _ => throw new IllegalArgumentException(s"No BasicType $columnType") + }) + } + seqBuilder += Row(values.result: _*) + } + + val dataset = seqBuilder.result + + // Create a LeapFrame from the schema and dataset + frame_in = DefaultLeapFrame(schema, dataset) + } + + def leapFrame2primitiveDataset(output: PrimitiveDataset) { + + val nRows = frame_out.dataset.length + var nCols = 0 + + val schema = frame_out.schema + val fields = schema.fields + + println("\nouput columns:") + for (iField <- 0 until fields.length) { + val field: StructField = fields(iField) + val name = field.name + val dataType = field.dataType + val base = dataType.base + val shape = dataType.shape + + if (shape.isTensor) { + val nDims = shape.asInstanceOf[TensorShape].dimensions.get.length + frame_out.dataset(0).getTensor(iField) match { + case dense: DenseTensor[_] => { + println(s"\t$name: DenseTensor[$base]") + } + case sparse: SparseTensor[_] => { + println(s"\t$name: SparseTensor[$base]") + } + } + + for (iDim <- 0 until nDims) { + val nSlots = field.dataType.shape.asInstanceOf[TensorShape].dimensions.get(iDim) + + for (iSlot <- 0 until nSlots) { + output.addColumnMetadata(nCols, name + iSlot, getSqlType(base), 0, 0) + base match { + case BasicType.Boolean => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Boolean](iField).toDense.values(iSlot)).toArray + output.addBooleanColumn(nCols, outputDataCol, null) + } + case BasicType.Byte => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Byte](iField).toDense.values(iSlot).toShort).toArray + output.addShortColumn(nCols, outputDataCol, null) + } + case BasicType.Short => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Short](iField).toDense.values(iSlot)).toArray + output.addShortColumn(nCols, outputDataCol, null) + } + case BasicType.Int => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Int](iField).toDense.values(iSlot)).toArray + output.addIntColumn(nCols, outputDataCol, null) + } + case BasicType.Long => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Long](iField).toDense.values(iSlot)).toArray + output.addLongColumn(nCols, outputDataCol, null) + } + case BasicType.Float => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Float](iField).toDense.values(iSlot)).toArray + output.addFloatColumn(nCols, outputDataCol, null) + } + case BasicType.Double => { + val outputDataCol = frame_out.dataset.map(_.getTensor[Double](iField).toDense.values(iSlot)).toArray + output.addDoubleColumn(nCols, outputDataCol, null) + } + case BasicType.String => { + val outputDataCol = frame_out.dataset.map(_.getTensor[String](iField).toDense.values(iSlot)).toArray + output.addStringColumn(nCols, outputDataCol) + } + case BasicType.ByteString => { + val outputDataCol = frame_out.dataset.map(_.getTensor[ByteString](iField).toDense.values(iSlot).bytes).toArray + output.addBinaryColumn(nCols, outputDataCol) + } + case _ => throw new IllegalArgumentException(s"No BasicType $base") + } + + nCols += 1 + } + } + } else { + println(s"\t$name: ScalarType.$base") + + output.addColumnMetadata(nCols, name, getSqlType(base), 0, 0) + base match { + case BasicType.Boolean => { + val outputDataCol = frame_out.dataset.map(_.getBool(iField)).toArray + output.addBooleanColumn(nCols, outputDataCol, null) + } + case BasicType.Byte => { + val outputDataCol = frame_out.dataset.map(_.getByte(iField).toShort).toArray + output.addShortColumn(nCols, outputDataCol, null) + } + case BasicType.Short => { + val outputDataCol = frame_out.dataset.map(_.getShort(iField)).toArray + output.addShortColumn(nCols, outputDataCol, null) + } + case BasicType.Int => { + val outputDataCol = frame_out.dataset.map(_.getInt(iField)).toArray + output.addIntColumn(nCols, outputDataCol, null) + } + case BasicType.Long => { + val outputDataCol = frame_out.dataset.map(_.getLong(iField)).toArray + output.addLongColumn(nCols, outputDataCol, null) + } + case BasicType.Float => { + val outputDataCol = frame_out.dataset.map(_.getFloat(iField)).toArray + output.addFloatColumn(nCols, outputDataCol, null) + } + case BasicType.Double => { + val outputDataCol = frame_out.dataset.map(_.getDouble(iField)).toArray + output.addDoubleColumn(nCols, outputDataCol, null) + } + case BasicType.String => { + val outputDataCol = frame_out.dataset.map(_.getString(iField)).toArray + output.addStringColumn(nCols, outputDataCol) + } + case BasicType.ByteString => { + val outputDataCol = frame_out.dataset.map(_.getByteString(iField).bytes).toArray + output.addBinaryColumn(nCols, outputDataCol) + } + case _ => throw new IllegalArgumentException(s"No BasicType $base") + } + nCols += 1 + } + } + } + + def json2leapFrame(frame_path: String) { + println (s"run($frame_path)") + + val f = new File (frame_path) + if (f.exists () && ! f.isDirectory () ) { + // get input from file + frame_in = FrameReader (BuiltinFormats.json).read (f).get + } else { + // get input from string + frame_in = FrameReader (BuiltinFormats.json).fromBytes (frame_path.getBytes () ).get + } + } + + def leapFrame2json(frame: DefaultLeapFrame): String = { + var json_str: String = null + for(bytes <- frame.writer("ml.combust.mleap.json").toBytes(); + frame2 <- FrameReader("ml.combust.mleap.json").fromBytes(bytes)) { + json_str = new String(bytes) + assert(frame == frame2) + } + + println() + println(json_str) + + return json_str + } +} diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java new file mode 100644 index 00000000..9f4e4455 --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java @@ -0,0 +1,98 @@ +package com.microsoft.sqlserver.mleap; + +import org.junit.*; + +import java.sql.Types; +import java.util.*; + +import static org.junit.Assert.*; + +public class ScorerTest { + + private static PrimitiveDataset input = new PrimitiveDataset(); + private static LinkedHashMap params = new LinkedHashMap<>(); + private static PrimitiveDataset output; + + @BeforeClass + public static void score() { + // get model and testing data + String modelPath = "src/main/resources/adult_census_pipeline.zip"; + + int columnId = 0; + input.addColumnMetadata(columnId, "age", java.sql.Types.INTEGER, 0, 0); + input.addIntColumn(columnId, new int[]{39, 50, 38}, null); + + columnId++; + input.addColumnMetadata(columnId, "hours_per_week", java.sql.Types.INTEGER, 0, 0); + input.addIntColumn(columnId, new int[]{40, 13, 40}, null); + + columnId++; + input.addColumnMetadata(columnId, "education", Types.NVARCHAR, 0, 0); + input.addStringColumn(columnId, new String[]{"Bachelors", "Bachelors", "HS-grad"}); + + columnId++; + input.addColumnMetadata(columnId, "sex", Types.NVARCHAR, 0, 0); + input.addStringColumn(columnId, new String[]{"Male", "Male", "Male"}); + + columnId++; + input.addColumnMetadata(columnId, "income", Types.NVARCHAR, 0, 0); + input.addStringColumn(columnId, new String[]{"<=50K", "<=50K", "<=50K"}); + + // specify some params + params.put("logLevel", "INFO"); //default WARN + params.put("modelPath", modelPath); + params.put("outputFields", "prediction,probability,education,sex,income,predictedIncome"); + //params.put("outputFields", "features,education-encoded"); //SparseTensor + + // perform scoring + Scorer scorer = new Scorer(); + scorer.init("session0", 0, 1); + output = scorer.execute(input, params); + + // cleanup + scorer.cleanup(); + } + + @Test + public void outputColumnCountShouldMatch() { + // display output + int nOutputCols = output.getColumnCount(); + int nOutputFields = params.getOrDefault("outputFields", "").toString().split(",").length; + assertEquals(nOutputFields + 1, nOutputCols); // "probability" is a vector field of size 2 in this case + } + + @Test + public void outputColumnNamesShouldMatch() { + int nOutputCols = output.getColumnCount(); + + Set columnNames = new HashSet<>(); + for (int iCol = 0; iCol < nOutputCols; iCol++) { + columnNames.add(output.getColumnName(iCol)); + } + Set fieldNames = new HashSet<>(Arrays.asList("prediction","probability0","probability1","education","sex","income","predictedIncome")); + assertEquals(fieldNames, columnNames); + } + + @Test + public void outputColumnValuesShouldMatch() { + String columnName = "education"; + + int outputIndex = output.getColumnIndex(columnName); + String[] outputStringColumn = output.getStringColumn(outputIndex); + + int inputIndex = input.getColumnIndex(columnName); + String[] inputStringColumn = input.getStringColumn(inputIndex); + + assertArrayEquals(inputStringColumn, outputStringColumn); + } + + @Test + public void outputRowCountShouldMatch() { + int rowCount0 = input.getRowCount(0); + int[] rowCounts = output.getRowCounts(); + + for (int rowCount: rowCounts) { + assertEquals(rowCount, rowCount0); + } + } +} diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala new file mode 100644 index 00000000..b98ddcbe --- /dev/null +++ b/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala @@ -0,0 +1,112 @@ +package com.microsoft.sqlserver.mleap + +import java.sql.Types + +import org.scalatest.fixture + +class PredictorTest extends fixture.FlatSpec { + + case class FixtureParam(input: PrimitiveDataset, scorer: Predictor, output: PrimitiveDataset) + + def withFixture(test: OneArgTest) = { + val input: PrimitiveDataset = new PrimitiveDataset + val scorer = new Predictor + val output: PrimitiveDataset = new PrimitiveDataset + val theFixture = FixtureParam(input, scorer, output) + + try { + var columnId = 0 + input.addColumnMetadata(columnId, "age", java.sql.Types.INTEGER, 0, 0) + input.addIntColumn(columnId, Array[Int](39, 50, 38), null) + + columnId += 1 + input.addColumnMetadata(columnId, "hours_per_week", java.sql.Types.INTEGER, 0, 0) + input.addIntColumn(columnId, Array[Int](40, 13, 40), null) + + columnId += 1 + input.addColumnMetadata(columnId, "education", Types.NVARCHAR, 0, 0) + input.addStringColumn(columnId, Array[String]("Bachelors", "Bachelors", "HS-grad")) + + columnId += 1 + input.addColumnMetadata(columnId, "sex", Types.NVARCHAR, 0, 0) + input.addStringColumn(columnId, Array[String]("Male", "Male", "Male")) + + columnId += 1 + input.addColumnMetadata(columnId, "income", Types.NVARCHAR, 0, 0) + input.addStringColumn(columnId, Array[String]("<=50K", "<=50K", "<=50K")) + + scorer.primitiveDataset2leapFrame(input) + scorer.frame_out = scorer.frame_in + scorer.leapFrame2primitiveDataset(output) + + withFixture(test.toNoArgTest(theFixture)) // "loan" the fixture to the test + } + finally () // clean up the fixture, nothing in this case + } + + "A Predictor" should "be able to convert PrimitiveDataset to DefaultLeapFrame with same field names" in { f => + + val columnNames = f.input.getColumnNames() + val fieldNames = f.scorer.frame_in.schema.fields.map(_.name).toArray + assert(fieldNames.deep == columnNames.deep) + } + + it should "be able to convert DefaultLeapFrame to PrimitiveDataset with same column names" in { f => + + val columnNames = f.output.getColumnNames() + val fieldNames = f.scorer.frame_out.schema.fields.map(_.name).toArray + assert(fieldNames.deep == columnNames.deep) + } + + it should "be able to convert PrimitiveDataset to DefaultLeapFrame with same int field values" in { f => + + val columnName = "age" + val columnIndex = f.input.getColumnIndex(columnName) + val columnValues = f.input.getIntColumn(columnIndex) + + val fieldNames = f.scorer.frame_in.schema.fields.map(_.name).toArray + val iField = fieldNames.indexOf(columnName) + val fieldValues = f.scorer.frame_in.dataset.map(_.getInt(iField)).toArray + + assert(fieldValues.deep == columnValues.deep) + } + + it should "be able to convert DefaultLeapFrame to PrimitiveDataset with same int column values" in { f => + + val columnName = "age" + val columnIndex = f.input.getColumnIndex(columnName) + val columnValues = f.input.getIntColumn(columnIndex) + + val fieldNames = f.scorer.frame_out.schema.fields.map(_.name).toArray + val iField = fieldNames.indexOf(columnName) + val fieldValues = f.scorer.frame_out.dataset.map(_.getInt(iField)).toArray + + assert(fieldValues.deep == columnValues.deep) + } + + it should "be able to convert PrimitiveDataset to DefaultLeapFrame with same string field values" in { f => + + val columnName = "education" + val columnIndex = f.input.getColumnIndex(columnName) + val columnValues = f.input.getStringColumn(columnIndex) + + val fieldNames = f.scorer.frame_in.schema.fields.map(_.name).toArray + val iField = fieldNames.indexOf(columnName) + val fieldValues = f.scorer.frame_in.dataset.map(_.getString(iField)).toArray + + assert(fieldValues.deep == columnValues.deep) + } + + it should "be able to convert DefaultLeapFrame to PrimitiveDataset with same string column values" in { f => + + val columnName = "education" + val columnIndex = f.input.getColumnIndex(columnName) + val columnValues = f.input.getStringColumn(columnIndex) + + val fieldNames = f.scorer.frame_out.schema.fields.map(_.name).toArray + val iField = fieldNames.indexOf(columnName) + val fieldValues = f.scorer.frame_out.dataset.map(_.getString(iField)).toArray + + assert(fieldValues.deep == columnValues.deep) + } +} From fefe1c54e23dedb3d4712d8a79c439007dcb201e Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Tue, 4 Jun 2019 15:28:28 -0700 Subject: [PATCH 03/12] move the content of mleap_sql to the old sparkml folder. --- .../spark/{mleap_sql => sparkml}/README.md | 0 .../{mleap_sql => sparkml}/jars/JavaTestPackage.jar | Bin .../jars/mssql_java_lang_extension.jar | Bin .../mleap_sql_test/cleanup.sh | 0 .../mleap_sql_test/mleap_pyspark.py | 0 .../mleap_sql_test/mleap_sql_tests.py | 0 .../{mleap_sql => sparkml}/mleap_sql_test/setup.sh | 0 .../{mleap_sql => sparkml}/mleap_sql_test/test.sh | 0 .../{mleap_sql => sparkml}/mssql-mleap-app/Makefile | 0 .../mssql-mleap-app/build.sbt | 0 .../lib/mssql_java_lang_extension.jar | Bin .../mssql-mleap-app/project/build.properties | 0 .../mssql-mleap-app/project/plugins.sbt | 0 .../microsoft/sqlserver/mleap/PrimitiveDataset.java | 0 .../java/com/microsoft/sqlserver/mleap/Scorer.java | 0 .../src/main/resources/adult_census_income.csv | 0 .../com/microsoft/sqlserver/mleap/Predictor.scala | 0 .../scala/com/microsoft/sqlserver/mleap/Score.scala | 0 .../com/microsoft/sqlserver/mleap/ScorerTest.java | 0 .../microsoft/sqlserver/mleap/PredictorTest.scala | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/README.md (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/jars/JavaTestPackage.jar (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/jars/mssql_java_lang_extension.jar (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mleap_sql_test/cleanup.sh (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mleap_sql_test/mleap_pyspark.py (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mleap_sql_test/mleap_sql_tests.py (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mleap_sql_test/setup.sh (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mleap_sql_test/test.sh (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/Makefile (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/build.sbt (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/lib/mssql_java_lang_extension.jar (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/project/build.properties (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/project/plugins.sbt (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/main/resources/adult_census_income.csv (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java (100%) rename samples/features/sql-big-data-cluster/spark/{mleap_sql => sparkml}/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala (100%) diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/README.md b/samples/features/sql-big-data-cluster/spark/sparkml/README.md similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/README.md rename to samples/features/sql-big-data-cluster/spark/sparkml/README.md diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/jars/JavaTestPackage.jar b/samples/features/sql-big-data-cluster/spark/sparkml/jars/JavaTestPackage.jar similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/jars/JavaTestPackage.jar rename to samples/features/sql-big-data-cluster/spark/sparkml/jars/JavaTestPackage.jar diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/jars/mssql_java_lang_extension.jar b/samples/features/sql-big-data-cluster/spark/sparkml/jars/mssql_java_lang_extension.jar similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/jars/mssql_java_lang_extension.jar rename to samples/features/sql-big-data-cluster/spark/sparkml/jars/mssql_java_lang_extension.jar diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/cleanup.sh b/samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/cleanup.sh similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/cleanup.sh rename to samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/cleanup.sh diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_pyspark.py b/samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/mleap_pyspark.py similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_pyspark.py rename to samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/mleap_pyspark.py diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_sql_tests.py b/samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/mleap_sql_tests.py similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/mleap_sql_tests.py rename to samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/mleap_sql_tests.py diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/setup.sh b/samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/setup.sh similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/setup.sh rename to samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/setup.sh diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/test.sh b/samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/test.sh similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mleap_sql_test/test.sh rename to samples/features/sql-big-data-cluster/spark/sparkml/mleap_sql_test/test.sh diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/Makefile b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/Makefile similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/Makefile rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/Makefile diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/build.sbt b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/build.sbt similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/build.sbt rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/build.sbt diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/lib/mssql_java_lang_extension.jar b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/lib/mssql_java_lang_extension.jar similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/lib/mssql_java_lang_extension.jar rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/lib/mssql_java_lang_extension.jar diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/build.properties b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/project/build.properties similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/build.properties rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/project/build.properties diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/plugins.sbt b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/project/plugins.sbt similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/project/plugins.sbt rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/project/plugins.sbt diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/PrimitiveDataset.java diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/java/com/microsoft/sqlserver/mleap/Scorer.java diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/resources/adult_census_income.csv b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/resources/adult_census_income.csv similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/resources/adult_census_income.csv rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/resources/adult_census_income.csv diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Predictor.scala diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/main/scala/com/microsoft/sqlserver/mleap/Score.scala diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/test/java/com/microsoft/sqlserver/mleap/ScorerTest.java diff --git a/samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala similarity index 100% rename from samples/features/sql-big-data-cluster/spark/mleap_sql/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala rename to samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/src/test/scala/com/microsoft/sqlserver/mleap/PredictorTest.scala From 3d5009b5e49c36a24d3730740da0437656dc302b Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Wed, 5 Jun 2019 10:46:16 -0700 Subject: [PATCH 04/12] update the jupyter notebook --- .../spark/sparkml/README.md | 4 +- ...in_score_export_ml_models_with_spark.ipynb | 60 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/README.md b/samples/features/sql-big-data-cluster/spark/sparkml/README.md index 80497f51..7d81c6f3 100644 --- a/samples/features/sql-big-data-cluster/spark/sparkml/README.md +++ b/samples/features/sql-big-data-cluster/spark/sparkml/README.md @@ -1,11 +1,13 @@ # MLeap on SQL Server Big Data cluster -This folder shows how we can build a model with [Spark ML](https://spark.apache.org/docs/latest/ml-guide.html), export the model to [MLeap](https://github.com/combust/mleap), and score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions) +This folder shows how we can build a model with [Spark ML](https://spark.apache.org/docs/latest/ml-guide.html), export the model to [MLeap](mleap-docs.combust.ml/), and score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions) ## Model training with Spark ML In this sample code, AdultCensusIncome.csv is used to build a Spark ML pipeline model. We can [download the dataset from internet](mleap_sql_test/setup.sh#L11) and [put it on HDFS on a SQL BDC cluster](mleap_sql_test/setup.sh#L12) so that it can be accessed by Spark. The data is first [read into Spark](mleap_sql_test/mleap_pyspark.py#L25) and [split into training and testing datasets](mleap_sql_test/mleap_pyspark.py#L64). We then [train a pipeline mode with the training data](mleap_sql_test/mleap_pyspark.py#L87) and [export the model to a mleap bundle](mleap_sql_test/mleap_pyspark.py#L204). +An equivalent Jupyter notebook is also included [here](train_score_export_ml_models_with_spark.ipynb) if it is preferred over pure Python code. + ## Model scoring with SQL Server Now that we have the Spark ML pipeline model in a common serialization [MLeap bundle](http://mleap-docs.combust.ml/core-concepts/mleap-bundles.html) format, we can score the model in Java without the presence of Spark. diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb b/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb index df4bd268..1ea35b27 100644 --- a/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb +++ b/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "markdown", - "source": "## Step 1 - Explore your data\r\n### Load the data\r\nFor this example we'll use **AdultCensusIncome** data from [here]( https://amldockerdatasets.azureedge.net/AdultCensusIncome.csv ). From your Azure Data Studio connect to the HDFS/Spark gateway and create a directory called spark_data under HDFS. \r\nDownload [AdultCensusIncome.csv]( https://amldockerdatasets.azureedge.net/AdultCensusIncome.csv ) to your local machine and upload to HDFS.Upload AdultCensusIncome.csv to the folder we created.\r\n\r\n### Exploratory Analysis\r\n- Baisc exploration on the data\r\n- Labels & Features\r\n1. **Label** - This refers to predicted value. This is represented as a column in the data. Label is **income** \r\n2. **Features** - This refers to the characteristics that are used to predict. **age** and **hours_per_week**\r\n\r\nNote : In reality features are chosen by applying some correlations techniques to understand what best characterize the Label we are predicting.\r\n\r\n### The Model we will build\r\nIn AdultCensusIncome.csv contains several columsn like Income range, age, hours-per-week, education, occupation etc. We'll build a model that can predict income range would be >50K or <50K.\r\n", + "source": "## Step 1 - Explore your data\r\n### Load the data\r\nFor this example we'll use **AdultCensusIncome** data from [here]( https://amldockerdatasets.azureedge.net/AdultCensusIncome.csv ). From your Azure Data Studio connect to the HDFS/Spark gateway and create a directory called spark_data under HDFS. \r\nDownload [AdultCensusIncome.csv]( https://amldockerdatasets.azureedge.net/AdultCensusIncome.csv ) to your local machine and upload to HDFS.Upload AdultCensusIncome.csv to the folder we created.\r\n\r\n### Exploratory Analysis\r\n- Baisc exploration on the data\r\n- Labels & Features\r\n1. **Label** - This refers to predicted value. This is represented as a column in the data. Label is **income** \r\n2. **Features** - This refers to the characteristics that are used to predict. **age**, **hours_per_week**, and **education**\r\n\r\nNote : In reality features are chosen by applying some correlations techniques to understand what best characterize the Label we are predicting.\r\n\r\n### The Model we will build\r\nIn AdultCensusIncome.csv contains several columsn like Income range, age, hours-per-week, education, occupation etc. We'll build a model that can predict income range would be >50K or <50K.\r\n", "metadata": {} }, { @@ -33,35 +33,53 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Number of rows: 32561, Number of coulumns : 15\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education-num: integer (nullable = true)\n |-- marital-status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital-gain: integer (nullable = true)\n |-- capital-loss: integer (nullable = true)\n |-- hours-per-week: integer (nullable = true)\n |-- native-country: string (nullable = true)\n |-- income: string (nullable = true)\n\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education_num: integer (nullable = true)\n |-- marital_status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital_gain: integer (nullable = true)\n |-- capital_loss: integer (nullable = true)\n |-- hours_per_week: integer (nullable = true)\n |-- native_country: string (nullable = true)\n |-- income: string (nullable = true)" + "text": "Starting Spark application\n", + "output_type": "stream" + }, + { + "data": { + "text/plain": "", + "text/html": "\n
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
19application_1559313998190_0085pyspark3idleLinkLink
" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "text": "SparkSession available as 'spark'.\n", + "output_type": "stream" + }, + { + "name": "stdout", + "text": "Number of rows: 32561, Number of coulumns : 15\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education-num: integer (nullable = true)\n |-- marital-status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital-gain: integer (nullable = true)\n |-- capital-loss: integer (nullable = true)\n |-- hours-per-week: integer (nullable = true)\n |-- native-country: string (nullable = true)\n |-- income: string (nullable = true)\n\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education_num: integer (nullable = true)\n |-- marital_status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital_gain: integer (nullable = true)\n |-- capital_loss: integer (nullable = true)\n |-- hours_per_week: integer (nullable = true)\n |-- native_country: string (nullable = true)\n |-- income: string (nullable = true)", + "output_type": "stream" } ], "execution_count": 3 }, { "cell_type": "code", - "source": "#Basic data exploration\r\n\r\n##1. Sub set the data and print some important columns\r\nprint(\"Select few columns to see the data\")\r\ndata_all.select(['income','age','hours_per_week']).show(10)\r\n\r\n## Find the number of distict values\r\nprint(\"Number of distinct values for income\")\r\nds_sub = data_all.select('income').distinct()\r\nds_sub.show()\r\n\r\n##Add a numberic column(income_code) derived from income column\r\nprint(\"Added numeric column(income_code) derived from income column\")\r\nfrom pyspark.sql.functions import expr\r\n\r\ndf_new = data_all.withColumn(\"income_code\", expr(\"case \\\r\n when income like '%<=50K%' then 0 \\\r\n when income like '%>50K%' then 1 \\\r\n else 2 end \"))\r\n\r\ndf_new.select(['income','age','hours_per_week','income_code']).show(10)\r\n\r\n##Summary statistical operations on dataframe\r\nprint(\"Print a statistical summary of a few columns\")\r\ndf_new.select(['income','age','hours_per_week','income_code']).describe().show()\r\n\r\nprint(\"Calculate Co variance between a few columns to understand features to use\")\r\nmycov = df_new.stat.cov('income_code','hours_per_week')\r\nprint(\"Covariance between income and hours_per_week is\", round(mycov,1))\r\n\r\nmycov = df_new.stat.cov('income_code','age')\r\nprint(\"Covariance between income and age is\", round(mycov,1))\r\n\r\n", + "source": "#Basic data exploration\r\n\r\n##1. Sub set the data and print some important columns\r\nprint(\"Select few columns to see the data\")\r\ndata_all.select(['income','age','hours_per_week', 'education']).show(10)\r\n\r\n## Find the number of distict values\r\nprint(\"Number of distinct values for income\")\r\nds_sub = data_all.select('income').distinct()\r\nds_sub.show()\r\n\r\n##Add a numberic column(income_code) derived from income column\r\nprint(\"Added numeric column(income_code) derived from income column\")\r\nfrom pyspark.sql.functions import expr\r\n\r\ndf_new = data_all.withColumn(\"income_code\", expr(\"case \\\r\n when income like '%<=50K%' then 0 \\\r\n when income like '%>50K%' then 1 \\\r\n else 2 end \"))\r\n\r\ndf_new.select(['income', 'age', 'hours_per_week', 'education', 'income_code']).show(10)\r\n\r\n##Summary statistical operations on dataframe\r\nprint(\"Print a statistical summary of a few columns\")\r\ndf_new.select(['income','age','hours_per_week', 'education','income_code']).describe().show()\r\n\r\nprint(\"Calculate Co variance between a few columns to understand features to use\")\r\nmycov = df_new.stat.cov('income_code','hours_per_week')\r\nprint(\"Covariance between income and hours_per_week is\", round(mycov,1))\r\n\r\nmycov = df_new.stat.cov('income_code','age')\r\nprint(\"Covariance between income and age is\", round(mycov,1))\r\n\r\n", "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Select few columns to see the data\n+------+---+--------------+\n|income|age|hours_per_week|\n+------+---+--------------+\n| <=50K| 39| 40|\n| <=50K| 50| 13|\n| <=50K| 38| 40|\n| <=50K| 53| 40|\n| <=50K| 28| 40|\n| <=50K| 37| 40|\n| <=50K| 49| 16|\n| >50K| 52| 45|\n| >50K| 31| 50|\n| >50K| 42| 40|\n+------+---+--------------+\nonly showing top 10 rows\n\nNumber of distinct values for income\n+------+\n|income|\n+------+\n| <=50K|\n| >50K|\n+------+\n\nAdded numeric column(income_code) derived from income column\n+------+---+--------------+-----------+\n|income|age|hours_per_week|income_code|\n+------+---+--------------+-----------+\n| <=50K| 39| 40| 0|\n| <=50K| 50| 13| 0|\n| <=50K| 38| 40| 0|\n| <=50K| 53| 40| 0|\n| <=50K| 28| 40| 0|\n| <=50K| 37| 40| 0|\n| <=50K| 49| 16| 0|\n| >50K| 52| 45| 1|\n| >50K| 31| 50| 1|\n| >50K| 42| 40| 1|\n+------+---+--------------+-----------+\nonly showing top 10 rows\n\nPrint a statistical summary of a few columns\n+-------+------+------------------+------------------+-------------------+\n|summary|income| age| hours_per_week| income_code|\n+-------+------+------------------+------------------+-------------------+\n| count| 32561| 32561| 32561| 32561|\n| mean| null| 38.58164675532078|40.437455852092995| 0.2408095574460244|\n| stddev| null|13.640432553581356|12.347428681731838|0.42758148856469247|\n| min| <=50K| 17| 1| 0|\n| max| >50K| 90| 99| 1|\n+-------+------+------------------+------------------+-------------------+\n\nCalculate Co variance between a few columns to understand features to use\nCovariance between income and hours_per_week is 1.2\nCovariance between income and age is 1.4" + "text": "Select few columns to see the data\n+------+---+--------------+---------+\n|income|age|hours_per_week|education|\n+------+---+--------------+---------+\n| <=50K| 39| 40|Bachelors|\n| <=50K| 50| 13|Bachelors|\n| <=50K| 38| 40| HS-grad|\n| <=50K| 53| 40| 11th|\n| <=50K| 28| 40|Bachelors|\n| <=50K| 37| 40| Masters|\n| <=50K| 49| 16| 9th|\n| >50K| 52| 45| HS-grad|\n| >50K| 31| 50| Masters|\n| >50K| 42| 40|Bachelors|\n+------+---+--------------+---------+\nonly showing top 10 rows\n\nNumber of distinct values for income\n+------+\n|income|\n+------+\n| <=50K|\n| >50K|\n+------+\n\nAdded numeric column(income_code) derived from income column\n+------+---+--------------+---------+-----------+\n|income|age|hours_per_week|education|income_code|\n+------+---+--------------+---------+-----------+\n| <=50K| 39| 40|Bachelors| 0|\n| <=50K| 50| 13|Bachelors| 0|\n| <=50K| 38| 40| HS-grad| 0|\n| <=50K| 53| 40| 11th| 0|\n| <=50K| 28| 40|Bachelors| 0|\n| <=50K| 37| 40| Masters| 0|\n| <=50K| 49| 16| 9th| 0|\n| >50K| 52| 45| HS-grad| 1|\n| >50K| 31| 50| Masters| 1|\n| >50K| 42| 40|Bachelors| 1|\n+------+---+--------------+---------+-----------+\nonly showing top 10 rows\n\nPrint a statistical summary of a few columns\n+-------+------+------------------+------------------+------------+-------------------+\n|summary|income| age| hours_per_week| education| income_code|\n+-------+------+------------------+------------------+------------+-------------------+\n| count| 32561| 32561| 32561| 32561| 32561|\n| mean| null| 38.58164675532078|40.437455852092995| null| 0.2408095574460244|\n| stddev| null|13.640432553581356|12.347428681731838| null|0.42758148856469247|\n| min| <=50K| 17| 1| 10th| 0|\n| max| >50K| 90| 99|Some-college| 1|\n+-------+------+------------------+------------------+------------+-------------------+\n\nCalculate Co variance between a few columns to understand features to use\nCovariance between income and hours_per_week is 1.2\nCovariance between income and age is 1.4", + "output_type": "stream" } ], "execution_count": 4 }, { "cell_type": "code", - "source": "# Choose feature columns and the label column.\r\nlabel = \"income\"\r\nxvars = [\"age\", \"hours_per_week\"] #all numeric\r\n\r\nprint(\"label = {}\".format(label))\r\nprint(\"features = {}\".format(xvars))\r\n\r\n#Check label counts to check data bias\r\nprint(\"Count of rows that are <=50K\", data_all[data_all.income==\"<=50K\"].count())\r\nprint(\"Count of rows that are >50K\", data_all[data_all.income==\">50K\"].count())\r\n\r\n\r\nselect_cols = xvars\r\nselect_cols.append(label)\r\ndata = data_all.select(select_cols)", + "source": "# Choose feature columns and the label column.\r\nlabel = \"income\"\r\nxvars = [\"age\", \"hours_per_week\", 'education'] #numeric and string\r\n\r\nprint(\"label = {}\".format(label))\r\nprint(\"features = {}\".format(xvars))\r\n\r\n#Check label counts to check data bias\r\nprint(\"Count of rows that are <=50K\", data_all[data_all.income==\"<=50K\"].count())\r\nprint(\"Count of rows that are >50K\", data_all[data_all.income==\">50K\"].count())\r\n\r\n\r\nselect_cols = xvars\r\nselect_cols.append(label)\r\ndata = data_all.select(select_cols)", "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "label = income\nfeatures = ['age', 'hours_per_week']\nCount of rows that are <=50K 24720\nCount of rows that are >50K 7841" + "text": "label = income\nfeatures = ['age', 'hours_per_week', 'education']\nCount of rows that are <=50K 24720\nCount of rows that are >50K 7841", + "output_type": "stream" } ], "execution_count": 5 @@ -77,27 +95,27 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "train (24469, 3)\ntest (8092, 3)\ntrain and test datasets saved to /spark_ml/AdultCensusIncomeTrain and /spark_ml/AdultCensusIncomeTest" + "text": "train (24469, 4)\ntest (8092, 4)\ntrain and test datasets saved to /spark_ml/AdultCensusIncomeTrain and /spark_ml/AdultCensusIncomeTest", + "output_type": "stream" } ], "execution_count": 6 }, { "cell_type": "markdown", - "source": "## Step 3 - Train a model\r\n[Spark ML pipeline] ( https://spark.apache.org/docs/2.3.1/ml-pipeline.html ) allow to sequence all steps as a workflow and make it easier to experiment with various algorithms and their parameters. The following code first constructs the stages and then puts these stages together in Ml pipeline. LogisticRegression is used to create the model.\r\n\r\n", + "source": "## Step 3 - Train a model\r\n[Spark ML pipelines](https://spark.apache.org/docs/latest/ml-pipeline.html) allow to sequence all steps as a workflow and make it easier to experiment with various algorithms and their parameters. The following code first constructs the stages and then puts these stages together in Ml pipeline. LogisticRegression is used to create the model.", "metadata": {} }, { "cell_type": "code", - "source": "from pyspark.ml import Pipeline, PipelineModel\r\nfrom pyspark.ml.feature import OneHotEncoder, StringIndexer, VectorAssembler\r\nfrom pyspark.ml.classification import LogisticRegression\r\n\r\nreg = 0.1\r\nprint(\"Using LogisticRegression model with Regularization Rate of {}.\".format(reg))\r\n\r\n# create a new Logistic Regression model.\r\nlr = LogisticRegression(regParam=reg)\r\n\r\ndtypes = dict(train.dtypes)\r\ndtypes.pop(label)\r\n\r\nsi_xvars = []\r\nohe_xvars = []\r\nfeatureCols = []\r\nfor idx,key in enumerate(dtypes):\r\n if dtypes[key] == \"string\":\r\n featureCol = \"-\".join([key, \"encoded\"])\r\n featureCols.append(featureCol)\r\n \r\n tmpCol = \"-\".join([key, \"tmp\"])\r\n si_xvars.append(StringIndexer(inputCol=key, outputCol=tmpCol, handleInvalid=\"skip\")) #, handleInvalid=\"keep\"\r\n ohe_xvars.append(OneHotEncoder(inputCol=tmpCol, outputCol=featureCol))\r\n else:\r\n featureCols.append(key)\r\n\r\n# string-index the label column into a column named \"label\"\r\nsi_label = StringIndexer(inputCol=label, outputCol='label')\r\n\r\n# assemble the encoded feature columns in to a column named \"features\"\r\nassembler = VectorAssembler(inputCols=featureCols, outputCol=\"features\")\r\n\r\n\r\nstages = []\r\nstages.extend(si_xvars)\r\nstages.extend(ohe_xvars)\r\nstages.append(si_label)\r\nstages.append(assembler)\r\nstages.append(lr)\r\npipe = Pipeline(stages=stages)\r\nprint(\"Pipeline Created\")\r\n\r\nmodel = pipe.fit(train)\r\nprint(\"Model Trained\")\r\nprint(\"Model is \", model)\r\nprint(\"Model Stages\", model.stages)", + "source": "from pyspark.ml import Pipeline, PipelineModel\r\nfrom pyspark.ml.feature import OneHotEncoderEstimator, StringIndexer, VectorAssembler\r\nfrom pyspark.ml.classification import LogisticRegression\r\n\r\nreg = 0.1\r\nprint(\"Using LogisticRegression model with Regularization Rate of {}.\".format(reg))\r\n\r\n# create a new Logistic Regression model.\r\nlr = LogisticRegression(regParam=reg)\r\n\r\ndtypes = dict(train.dtypes)\r\ndtypes.pop(label)\r\n\r\nsi_xvars = []\r\nohe_xvars = []\r\nfeatureCols = []\r\nfor idx,key in enumerate(dtypes):\r\n if dtypes[key] == \"string\":\r\n featureCol = \"-\".join([key, \"encoded\"])\r\n featureCols.append(featureCol)\r\n \r\n tmpCol = \"-\".join([key, \"tmp\"])\r\n si_xvars.append(StringIndexer(inputCol=key, outputCol=tmpCol, handleInvalid=\"skip\")) #, handleInvalid=\"keep\"\r\n ohe_xvars.append(OneHotEncoderEstimator(inputCols=[tmpCol], outputCols=[featureCol]))\r\n else:\r\n featureCols.append(key)\r\n\r\n# string-index the label column into a column named \"label\"\r\nsi_label = StringIndexer(inputCol=label, outputCol='label')\r\n\r\n# assemble the encoded feature columns in to a column named \"features\"\r\nassembler = VectorAssembler(inputCols=featureCols, outputCol=\"features\")\r\n\r\n\r\nstages = []\r\nstages.extend(si_xvars)\r\nstages.extend(ohe_xvars)\r\nstages.append(si_label)\r\nstages.append(assembler)\r\nstages.append(lr)\r\npipe = Pipeline(stages=stages)\r\nprint(\"Pipeline Created\")\r\n\r\nmodel = pipe.fit(train)\r\nprint(\"Model Trained\")\r\nprint(\"Model is \", model)\r\nprint(\"Model Stages\", model.stages)", "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Using LogisticRegression model with Regularization Rate of 0.1.\nPipeline Created\nModel Trained\nModel is PipelineModel_e5284bc61285\nModel Stages [StringIndexer_1ecf86c8d2ae, VectorAssembler_450ee37e6955, LogisticRegressionModel: uid = LogisticRegression_deb52c17940d, numClasses = 2, numFeatures = 2]" + "text": "Using LogisticRegression model with Regularization Rate of 0.1.\nPipeline Created\nModel Trained\nModel is PipelineModel_8c7a4fdc6110\nModel Stages [StringIndexer_7d244350f55d, OneHotEncoderEstimator_559780c5ce92, StringIndexer_53280e6349e6, VectorAssembler_1051507100cb, LogisticRegressionModel: uid = LogisticRegression_5c0eda4eab78, numClasses = 2, numFeatures = 17]", + "output_type": "stream" } ], "execution_count": 7 @@ -113,9 +131,9 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Area under ROC: 0.7363559303440261\nArea Under PR: 0.39475773290351296\n+------+-----+----------+\n|income|label|prediction|\n+------+-----+----------+\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n+------+-----+----------+\nonly showing top 20 rows" + "text": "Area under ROC: 0.7964496884726682\nArea Under PR: 0.5358180243123482\n+------+-----+----------+\n|income|label|prediction|\n+------+-----+----------+\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n+------+-----+----------+\nonly showing top 20 rows", + "output_type": "stream" } ], "execution_count": 8 @@ -131,9 +149,9 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "saved model to /spark_ml/AdultCensus.mml\nSuccessfully loaded from /spark_ml/AdultCensus.mml" + "text": "saved model to /spark_ml/AdultCensus.mml\nSuccessfully loaded from /spark_ml/AdultCensus.mml", + "output_type": "stream" } ], "execution_count": 9 @@ -149,9 +167,9 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "persist the mleap bundle from local to hdfs" + "text": "persist the mleap bundle from local to hdfs", + "output_type": "stream" } ], "execution_count": 10 From 56f5e1167912ddb828af5030a980f8f497901a0c Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Wed, 5 Jun 2019 11:55:17 -0700 Subject: [PATCH 05/12] update image and md file. --- .../spark/sparkml/README.md | 2 ++ .../sparkml/Train_Score_Export_with_Spark.jpg | Bin 135628 -> 95989 bytes 2 files changed, 2 insertions(+) diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/README.md b/samples/features/sql-big-data-cluster/spark/sparkml/README.md index 7d81c6f3..7965e6fc 100644 --- a/samples/features/sql-big-data-cluster/spark/sparkml/README.md +++ b/samples/features/sql-big-data-cluster/spark/sparkml/README.md @@ -1,6 +1,8 @@ # MLeap on SQL Server Big Data cluster This folder shows how we can build a model with [Spark ML](https://spark.apache.org/docs/latest/ml-guide.html), export the model to [MLeap](mleap-docs.combust.ml/), and score the model in SQL Server with its [Java Language Extension](https://docs.microsoft.com/en-us/sql/language-extensions/language-extensions-overview?view=sqlallproducts-allversions) +![Train_Score_Export_with_Spark.jpg](Train_Score_Export_with_Spark.jpg) + ## Model training with Spark ML In this sample code, AdultCensusIncome.csv is used to build a Spark ML pipeline model. We can [download the dataset from internet](mleap_sql_test/setup.sh#L11) and [put it on HDFS on a SQL BDC cluster](mleap_sql_test/setup.sh#L12) so that it can be accessed by Spark. diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/Train_Score_Export_with_Spark.jpg b/samples/features/sql-big-data-cluster/spark/sparkml/Train_Score_Export_with_Spark.jpg index 0cc016f8a001ce83d4a5c64f8ee650eba991b7ce..5893e55c51c17f4cf95c223e47688e89982dd9f1 100644 GIT binary patch literal 95989 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.>JMf-3y@Ku6kX%-D3>y@yUr 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

Wn7vEYWSIriGt{*|kl)S$4jyeII!4l1p`Da5 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

~gMRY81P~M)4J;fU#%r#*MMbt~`GO_vo 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 From 168bcb3c2eda2ab2c76e4bc2f5f60f48167bebac Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Wed, 5 Jun 2019 12:11:06 -0700 Subject: [PATCH 06/12] update notebook with the correct link to the image. --- ...in_score_export_ml_models_with_spark.ipynb | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb b/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb index 1ea35b27..988a9725 100644 --- a/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb +++ b/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb @@ -19,7 +19,7 @@ "cells": [ { "cell_type": "markdown", - "source": "# Machine learning with SPARK in SQL Server 2019 Big Data Cluster\r\nSpark in Unified Big data compute engine that enables big data processing, Machine learning and AI\r\n\r\nKey Spark advantages are \r\n1. Distributed compute enging \r\n2. Choice of langauge (Python, R, Scala, Java)\r\n3. Single engine for Batch and Streaming jobs\r\n\r\nIn this tutorial we'll cover how we can use Spark to create and deploy machine learning models. The example is a python(PySpark) sample. The same can also be done using Scala and R ( SparkR) in Spark.\r\n\r\n\"drawing\"\r\n\r\n## Steps\r\n1. Explore your Data\r\n2. Data Prep and split Data as Training and Test set\r\n3. Model Training\r\n4. Model Scoring \r\n5. Persist as Spark Model\r\n6. Persist as Portable Model\r\n\r\nE2E machine learning involves several additional step e.g data exploration, feature selection and principal component analysis,model selection etc. Many of these steps are ignored here for brevity.\r\n\r\n\r\n\r\n", + "source": "# Machine learning with SPARK in SQL Server 2019 Big Data Cluster\r\nSpark in Unified Big data compute engine that enables big data processing, Machine learning and AI\r\n\r\nKey Spark advantages are \r\n1. Distributed compute enging \r\n2. Choice of langauge (Python, R, Scala, Java)\r\n3. Single engine for Batch and Streaming jobs\r\n\r\nIn this tutorial we'll cover how we can use Spark to create and deploy machine learning models. The example is a python(PySpark) sample. The same can also be done using Scala and R ( SparkR) in Spark.\r\n\r\n\"drawing\"\r\n\r\n## Steps\r\n1. Explore your Data\r\n2. Data Prep and split Data as Training and Test set\r\n3. Model Training\r\n4. Model Scoring \r\n5. Persist as Spark Model\r\n6. Persist as Portable Model\r\n\r\nE2E machine learning involves several additional step e.g data exploration, feature selection and principal component analysis,model selection etc. Many of these steps are ignored here for brevity.\r\n\r\n\r\n\r\n", "metadata": {} }, { @@ -29,31 +29,31 @@ }, { "cell_type": "code", - "source": "datafile = \"/spark_data/AdultCensusIncome.csv\"\r\n\r\n#Read the data to a spark data frame.\r\ndata_all = spark.read.format('csv').options(header='true', inferSchema='true', ignoreLeadingWhiteSpace='true', ignoreTrailingWhiteSpace='true').load(datafile)\r\nprint(\"Number of rows: {}, Number of coulumns : {}\".format(data_all.count(), len(data_all.columns)))\r\ndata_all.printSchema() \r\n\r\n#Replace \"-\" with \"_\" in column names\r\ncolumns_new = [col.replace(\"-\", \"_\") for col in data_all.columns]\r\ndata_all = data_all.toDF(*columns_new)\r\ndata_all.printSchema()\r\n", + "source": "![title](datafile = \"/spark_data/AdultCensusIncome.csv\"\r\n\r\n#Read the data to a spark data frame.\r\ndata_all = spark.read.format('csv').options(header='true', inferSchema='true', ignoreLeadingWhiteSpace='true', ignoreTrailingWhiteSpace='true').load(datafile)\r\nprint(\"Number of rows: {}, Number of coulumns : {}\".format(data_all.count(), len(data_all.columns)))\r\ndata_all.printSchema() \r\n\r\n#Replace \"-\" with \"_\" in column names\r\ncolumns_new = [col.replace(\"-\", \"_\") for col in data_all.columns]\r\ndata_all = data_all.toDF(*columns_new)\r\ndata_all.printSchema()\r\n", "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "Starting Spark application\n", - "output_type": "stream" + "text": "Starting Spark application\n" }, { + "output_type": "display_data", "data": { "text/plain": "", "text/html": "\n
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
19application_1559313998190_0085pyspark3idleLinkLink
" }, - "metadata": {}, - "output_type": "display_data" + "metadata": {} }, { + "output_type": "stream", "name": "stdout", - "text": "SparkSession available as 'spark'.\n", - "output_type": "stream" + "text": "SparkSession available as 'spark'.\n" }, { + "output_type": "stream", "name": "stdout", - "text": "Number of rows: 32561, Number of coulumns : 15\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education-num: integer (nullable = true)\n |-- marital-status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital-gain: integer (nullable = true)\n |-- capital-loss: integer (nullable = true)\n |-- hours-per-week: integer (nullable = true)\n |-- native-country: string (nullable = true)\n |-- income: string (nullable = true)\n\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education_num: integer (nullable = true)\n |-- marital_status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital_gain: integer (nullable = true)\n |-- capital_loss: integer (nullable = true)\n |-- hours_per_week: integer (nullable = true)\n |-- native_country: string (nullable = true)\n |-- income: string (nullable = true)", - "output_type": "stream" + "text": "Number of rows: 32561, Number of coulumns : 15\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education-num: integer (nullable = true)\n |-- marital-status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital-gain: integer (nullable = true)\n |-- capital-loss: integer (nullable = true)\n |-- hours-per-week: integer (nullable = true)\n |-- native-country: string (nullable = true)\n |-- income: string (nullable = true)\n\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education_num: integer (nullable = true)\n |-- marital_status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital_gain: integer (nullable = true)\n |-- capital_loss: integer (nullable = true)\n |-- hours_per_week: integer (nullable = true)\n |-- native_country: string (nullable = true)\n |-- income: string (nullable = true)" } ], "execution_count": 3 @@ -64,9 +64,9 @@ "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "Select few columns to see the data\n+------+---+--------------+---------+\n|income|age|hours_per_week|education|\n+------+---+--------------+---------+\n| <=50K| 39| 40|Bachelors|\n| <=50K| 50| 13|Bachelors|\n| <=50K| 38| 40| HS-grad|\n| <=50K| 53| 40| 11th|\n| <=50K| 28| 40|Bachelors|\n| <=50K| 37| 40| Masters|\n| <=50K| 49| 16| 9th|\n| >50K| 52| 45| HS-grad|\n| >50K| 31| 50| Masters|\n| >50K| 42| 40|Bachelors|\n+------+---+--------------+---------+\nonly showing top 10 rows\n\nNumber of distinct values for income\n+------+\n|income|\n+------+\n| <=50K|\n| >50K|\n+------+\n\nAdded numeric column(income_code) derived from income column\n+------+---+--------------+---------+-----------+\n|income|age|hours_per_week|education|income_code|\n+------+---+--------------+---------+-----------+\n| <=50K| 39| 40|Bachelors| 0|\n| <=50K| 50| 13|Bachelors| 0|\n| <=50K| 38| 40| HS-grad| 0|\n| <=50K| 53| 40| 11th| 0|\n| <=50K| 28| 40|Bachelors| 0|\n| <=50K| 37| 40| Masters| 0|\n| <=50K| 49| 16| 9th| 0|\n| >50K| 52| 45| HS-grad| 1|\n| >50K| 31| 50| Masters| 1|\n| >50K| 42| 40|Bachelors| 1|\n+------+---+--------------+---------+-----------+\nonly showing top 10 rows\n\nPrint a statistical summary of a few columns\n+-------+------+------------------+------------------+------------+-------------------+\n|summary|income| age| hours_per_week| education| income_code|\n+-------+------+------------------+------------------+------------+-------------------+\n| count| 32561| 32561| 32561| 32561| 32561|\n| mean| null| 38.58164675532078|40.437455852092995| null| 0.2408095574460244|\n| stddev| null|13.640432553581356|12.347428681731838| null|0.42758148856469247|\n| min| <=50K| 17| 1| 10th| 0|\n| max| >50K| 90| 99|Some-college| 1|\n+-------+------+------------------+------------------+------------+-------------------+\n\nCalculate Co variance between a few columns to understand features to use\nCovariance between income and hours_per_week is 1.2\nCovariance between income and age is 1.4", - "output_type": "stream" + "text": "Select few columns to see the data\n+------+---+--------------+---------+\n|income|age|hours_per_week|education|\n+------+---+--------------+---------+\n| <=50K| 39| 40|Bachelors|\n| <=50K| 50| 13|Bachelors|\n| <=50K| 38| 40| HS-grad|\n| <=50K| 53| 40| 11th|\n| <=50K| 28| 40|Bachelors|\n| <=50K| 37| 40| Masters|\n| <=50K| 49| 16| 9th|\n| >50K| 52| 45| HS-grad|\n| >50K| 31| 50| Masters|\n| >50K| 42| 40|Bachelors|\n+------+---+--------------+---------+\nonly showing top 10 rows\n\nNumber of distinct values for income\n+------+\n|income|\n+------+\n| <=50K|\n| >50K|\n+------+\n\nAdded numeric column(income_code) derived from income column\n+------+---+--------------+---------+-----------+\n|income|age|hours_per_week|education|income_code|\n+------+---+--------------+---------+-----------+\n| <=50K| 39| 40|Bachelors| 0|\n| <=50K| 50| 13|Bachelors| 0|\n| <=50K| 38| 40| HS-grad| 0|\n| <=50K| 53| 40| 11th| 0|\n| <=50K| 28| 40|Bachelors| 0|\n| <=50K| 37| 40| Masters| 0|\n| <=50K| 49| 16| 9th| 0|\n| >50K| 52| 45| HS-grad| 1|\n| >50K| 31| 50| Masters| 1|\n| >50K| 42| 40|Bachelors| 1|\n+------+---+--------------+---------+-----------+\nonly showing top 10 rows\n\nPrint a statistical summary of a few columns\n+-------+------+------------------+------------------+------------+-------------------+\n|summary|income| age| hours_per_week| education| income_code|\n+-------+------+------------------+------------------+------------+-------------------+\n| count| 32561| 32561| 32561| 32561| 32561|\n| mean| null| 38.58164675532078|40.437455852092995| null| 0.2408095574460244|\n| stddev| null|13.640432553581356|12.347428681731838| null|0.42758148856469247|\n| min| <=50K| 17| 1| 10th| 0|\n| max| >50K| 90| 99|Some-college| 1|\n+-------+------+------------------+------------------+------------+-------------------+\n\nCalculate Co variance between a few columns to understand features to use\nCovariance between income and hours_per_week is 1.2\nCovariance between income and age is 1.4" } ], "execution_count": 4 @@ -77,9 +77,9 @@ "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "label = income\nfeatures = ['age', 'hours_per_week', 'education']\nCount of rows that are <=50K 24720\nCount of rows that are >50K 7841", - "output_type": "stream" + "text": "label = income\nfeatures = ['age', 'hours_per_week', 'education']\nCount of rows that are <=50K 24720\nCount of rows that are >50K 7841" } ], "execution_count": 5 @@ -91,13 +91,13 @@ }, { "cell_type": "code", - "source": "train, test = data.randomSplit([0.75, 0.25], seed=123)\r\n\r\nprint(\"train ({}, {})\".format(train.count(), len(train.columns)))\r\nprint(\"test ({}, {})\".format(test.count(), len(test.columns)))\r\n\r\ntrain_data_path = \"/spark_ml/AdultCensusIncomeTrain\"\r\ntest_data_path = \"/spark_ml/AdultCensusIncomeTest\"\r\n\r\ntrain.write.mode('overwrite').orc(train_data_path)\r\ntest.write.mode('overwrite').orc(test_data_path)\r\nprint(\"train and test datasets saved to {} and {}\".format(train_data_path, test_data_path))", + "source": "![title](train, test = data.randomSplit([0.75, 0.25], seed=123)\r\n\r\nprint(\"train ({}, {})\".format(train.count(), len(train.columns)))\r\nprint(\"test ({}, {})\".format(test.count(), len(test.columns)))\r\n\r\ntrain_data_path = \"/spark_ml/AdultCensusIncomeTrain\"\r\ntest_data_path = \"/spark_ml/AdultCensusIncomeTest\"\r\n\r\ntrain.write.mode('overwrite').orc(train_data_path)\r\ntest.write.mode('overwrite').orc(test_data_path)\r\nprint(\"train and test datasets saved to {} and {}\".format(train_data_path, test_data_path))", "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "train (24469, 4)\ntest (8092, 4)\ntrain and test datasets saved to /spark_ml/AdultCensusIncomeTrain and /spark_ml/AdultCensusIncomeTest", - "output_type": "stream" + "text": "train (24469, 4)\ntest (8092, 4)\ntrain and test datasets saved to /spark_ml/AdultCensusIncomeTrain and /spark_ml/AdultCensusIncomeTest" } ], "execution_count": 6 @@ -113,9 +113,9 @@ "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "Using LogisticRegression model with Regularization Rate of 0.1.\nPipeline Created\nModel Trained\nModel is PipelineModel_8c7a4fdc6110\nModel Stages [StringIndexer_7d244350f55d, OneHotEncoderEstimator_559780c5ce92, StringIndexer_53280e6349e6, VectorAssembler_1051507100cb, LogisticRegressionModel: uid = LogisticRegression_5c0eda4eab78, numClasses = 2, numFeatures = 17]", - "output_type": "stream" + "text": "Using LogisticRegression model with Regularization Rate of 0.1.\nPipeline Created\nModel Trained\nModel is PipelineModel_8c7a4fdc6110\nModel Stages [StringIndexer_7d244350f55d, OneHotEncoderEstimator_559780c5ce92, StringIndexer_53280e6349e6, VectorAssembler_1051507100cb, LogisticRegressionModel: uid = LogisticRegression_5c0eda4eab78, numClasses = 2, numFeatures = 17]" } ], "execution_count": 7 @@ -131,9 +131,9 @@ "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "Area under ROC: 0.7964496884726682\nArea Under PR: 0.5358180243123482\n+------+-----+----------+\n|income|label|prediction|\n+------+-----+----------+\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n+------+-----+----------+\nonly showing top 20 rows", - "output_type": "stream" + "text": "Area under ROC: 0.7964496884726682\nArea Under PR: 0.5358180243123482\n+------+-----+----------+\n|income|label|prediction|\n+------+-----+----------+\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n+------+-----+----------+\nonly showing top 20 rows" } ], "execution_count": 8 @@ -149,9 +149,9 @@ "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "saved model to /spark_ml/AdultCensus.mml\nSuccessfully loaded from /spark_ml/AdultCensus.mml", - "output_type": "stream" + "text": "saved model to /spark_ml/AdultCensus.mml\nSuccessfully loaded from /spark_ml/AdultCensus.mml" } ], "execution_count": 9 @@ -167,9 +167,9 @@ "metadata": {}, "outputs": [ { + "output_type": "stream", "name": "stdout", - "text": "persist the mleap bundle from local to hdfs", - "output_type": "stream" + "text": "persist the mleap bundle from local to hdfs" } ], "execution_count": 10 From 995d4ab8b339169729931128720c5e37a2206834 Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Wed, 5 Jun 2019 12:54:35 -0700 Subject: [PATCH 07/12] fix some typos --- ...in_score_export_ml_models_with_spark.ipynb | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb b/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb index 988a9725..c3219ad8 100644 --- a/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb +++ b/samples/features/sql-big-data-cluster/spark/sparkml/train_score_export_ml_models_with_spark.ipynb @@ -29,34 +29,34 @@ }, { "cell_type": "code", - "source": "![title](datafile = \"/spark_data/AdultCensusIncome.csv\"\r\n\r\n#Read the data to a spark data frame.\r\ndata_all = spark.read.format('csv').options(header='true', inferSchema='true', ignoreLeadingWhiteSpace='true', ignoreTrailingWhiteSpace='true').load(datafile)\r\nprint(\"Number of rows: {}, Number of coulumns : {}\".format(data_all.count(), len(data_all.columns)))\r\ndata_all.printSchema() \r\n\r\n#Replace \"-\" with \"_\" in column names\r\ncolumns_new = [col.replace(\"-\", \"_\") for col in data_all.columns]\r\ndata_all = data_all.toDF(*columns_new)\r\ndata_all.printSchema()\r\n", + "source": "datafile = \"/spark_data/AdultCensusIncome.csv\"\r\n\r\n#Read the data to a spark data frame.\r\ndata_all = spark.read.format('csv').options(header='true', inferSchema='true', ignoreLeadingWhiteSpace='true', ignoreTrailingWhiteSpace='true').load(datafile)\r\nprint(\"Number of rows: {}, Number of coulumns : {}\".format(data_all.count(), len(data_all.columns)))\r\ndata_all.printSchema() \r\n\r\n#Replace \"-\" with \"_\" in column names\r\ncolumns_new = [col.replace(\"-\", \"_\") for col in data_all.columns]\r\ndata_all = data_all.toDF(*columns_new)\r\ndata_all.printSchema()\r\n", "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Starting Spark application\n" + "text": "Starting Spark application\n", + "output_type": "stream" }, { - "output_type": "display_data", "data": { "text/plain": "", - "text/html": "\n
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
19application_1559313998190_0085pyspark3idleLinkLink
" + "text/html": "\n
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
20application_1559313998190_0086pyspark3idleLinkLink
" }, - "metadata": {} + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "stream", "name": "stdout", - "text": "SparkSession available as 'spark'.\n" + "text": "SparkSession available as 'spark'.\n", + "output_type": "stream" }, { - "output_type": "stream", "name": "stdout", - "text": "Number of rows: 32561, Number of coulumns : 15\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education-num: integer (nullable = true)\n |-- marital-status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital-gain: integer (nullable = true)\n |-- capital-loss: integer (nullable = true)\n |-- hours-per-week: integer (nullable = true)\n |-- native-country: string (nullable = true)\n |-- income: string (nullable = true)\n\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education_num: integer (nullable = true)\n |-- marital_status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital_gain: integer (nullable = true)\n |-- capital_loss: integer (nullable = true)\n |-- hours_per_week: integer (nullable = true)\n |-- native_country: string (nullable = true)\n |-- income: string (nullable = true)" + "text": "Number of rows: 32561, Number of coulumns : 15\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education-num: integer (nullable = true)\n |-- marital-status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital-gain: integer (nullable = true)\n |-- capital-loss: integer (nullable = true)\n |-- hours-per-week: integer (nullable = true)\n |-- native-country: string (nullable = true)\n |-- income: string (nullable = true)\n\nroot\n |-- age: integer (nullable = true)\n |-- workclass: string (nullable = true)\n |-- fnlwgt: integer (nullable = true)\n |-- education: string (nullable = true)\n |-- education_num: integer (nullable = true)\n |-- marital_status: string (nullable = true)\n |-- occupation: string (nullable = true)\n |-- relationship: string (nullable = true)\n |-- race: string (nullable = true)\n |-- sex: string (nullable = true)\n |-- capital_gain: integer (nullable = true)\n |-- capital_loss: integer (nullable = true)\n |-- hours_per_week: integer (nullable = true)\n |-- native_country: string (nullable = true)\n |-- income: string (nullable = true)", + "output_type": "stream" } ], - "execution_count": 3 + "execution_count": 2 }, { "cell_type": "code", @@ -64,12 +64,12 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Select few columns to see the data\n+------+---+--------------+---------+\n|income|age|hours_per_week|education|\n+------+---+--------------+---------+\n| <=50K| 39| 40|Bachelors|\n| <=50K| 50| 13|Bachelors|\n| <=50K| 38| 40| HS-grad|\n| <=50K| 53| 40| 11th|\n| <=50K| 28| 40|Bachelors|\n| <=50K| 37| 40| Masters|\n| <=50K| 49| 16| 9th|\n| >50K| 52| 45| HS-grad|\n| >50K| 31| 50| Masters|\n| >50K| 42| 40|Bachelors|\n+------+---+--------------+---------+\nonly showing top 10 rows\n\nNumber of distinct values for income\n+------+\n|income|\n+------+\n| <=50K|\n| >50K|\n+------+\n\nAdded numeric column(income_code) derived from income column\n+------+---+--------------+---------+-----------+\n|income|age|hours_per_week|education|income_code|\n+------+---+--------------+---------+-----------+\n| <=50K| 39| 40|Bachelors| 0|\n| <=50K| 50| 13|Bachelors| 0|\n| <=50K| 38| 40| HS-grad| 0|\n| <=50K| 53| 40| 11th| 0|\n| <=50K| 28| 40|Bachelors| 0|\n| <=50K| 37| 40| Masters| 0|\n| <=50K| 49| 16| 9th| 0|\n| >50K| 52| 45| HS-grad| 1|\n| >50K| 31| 50| Masters| 1|\n| >50K| 42| 40|Bachelors| 1|\n+------+---+--------------+---------+-----------+\nonly showing top 10 rows\n\nPrint a statistical summary of a few columns\n+-------+------+------------------+------------------+------------+-------------------+\n|summary|income| age| hours_per_week| education| income_code|\n+-------+------+------------------+------------------+------------+-------------------+\n| count| 32561| 32561| 32561| 32561| 32561|\n| mean| null| 38.58164675532078|40.437455852092995| null| 0.2408095574460244|\n| stddev| null|13.640432553581356|12.347428681731838| null|0.42758148856469247|\n| min| <=50K| 17| 1| 10th| 0|\n| max| >50K| 90| 99|Some-college| 1|\n+-------+------+------------------+------------------+------------+-------------------+\n\nCalculate Co variance between a few columns to understand features to use\nCovariance between income and hours_per_week is 1.2\nCovariance between income and age is 1.4" + "text": "Select few columns to see the data\n+------+---+--------------+---------+\n|income|age|hours_per_week|education|\n+------+---+--------------+---------+\n| <=50K| 39| 40|Bachelors|\n| <=50K| 50| 13|Bachelors|\n| <=50K| 38| 40| HS-grad|\n| <=50K| 53| 40| 11th|\n| <=50K| 28| 40|Bachelors|\n| <=50K| 37| 40| Masters|\n| <=50K| 49| 16| 9th|\n| >50K| 52| 45| HS-grad|\n| >50K| 31| 50| Masters|\n| >50K| 42| 40|Bachelors|\n+------+---+--------------+---------+\nonly showing top 10 rows\n\nNumber of distinct values for income\n+------+\n|income|\n+------+\n| <=50K|\n| >50K|\n+------+\n\nAdded numeric column(income_code) derived from income column\n+------+---+--------------+---------+-----------+\n|income|age|hours_per_week|education|income_code|\n+------+---+--------------+---------+-----------+\n| <=50K| 39| 40|Bachelors| 0|\n| <=50K| 50| 13|Bachelors| 0|\n| <=50K| 38| 40| HS-grad| 0|\n| <=50K| 53| 40| 11th| 0|\n| <=50K| 28| 40|Bachelors| 0|\n| <=50K| 37| 40| Masters| 0|\n| <=50K| 49| 16| 9th| 0|\n| >50K| 52| 45| HS-grad| 1|\n| >50K| 31| 50| Masters| 1|\n| >50K| 42| 40|Bachelors| 1|\n+------+---+--------------+---------+-----------+\nonly showing top 10 rows\n\nPrint a statistical summary of a few columns\n+-------+------+------------------+------------------+------------+-------------------+\n|summary|income| age| hours_per_week| education| income_code|\n+-------+------+------------------+------------------+------------+-------------------+\n| count| 32561| 32561| 32561| 32561| 32561|\n| mean| null| 38.58164675532078|40.437455852092995| null| 0.2408095574460244|\n| stddev| null|13.640432553581356|12.347428681731838| null|0.42758148856469247|\n| min| <=50K| 17| 1| 10th| 0|\n| max| >50K| 90| 99|Some-college| 1|\n+-------+------+------------------+------------------+------------+-------------------+\n\nCalculate Co variance between a few columns to understand features to use\nCovariance between income and hours_per_week is 1.2\nCovariance between income and age is 1.4", + "output_type": "stream" } ], - "execution_count": 4 + "execution_count": 3 }, { "cell_type": "code", @@ -77,12 +77,12 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "label = income\nfeatures = ['age', 'hours_per_week', 'education']\nCount of rows that are <=50K 24720\nCount of rows that are >50K 7841" + "text": "label = income\nfeatures = ['age', 'hours_per_week', 'education']\nCount of rows that are <=50K 24720\nCount of rows that are >50K 7841", + "output_type": "stream" } ], - "execution_count": 5 + "execution_count": 4 }, { "cell_type": "markdown", @@ -91,16 +91,16 @@ }, { "cell_type": "code", - "source": "![title](train, test = data.randomSplit([0.75, 0.25], seed=123)\r\n\r\nprint(\"train ({}, {})\".format(train.count(), len(train.columns)))\r\nprint(\"test ({}, {})\".format(test.count(), len(test.columns)))\r\n\r\ntrain_data_path = \"/spark_ml/AdultCensusIncomeTrain\"\r\ntest_data_path = \"/spark_ml/AdultCensusIncomeTest\"\r\n\r\ntrain.write.mode('overwrite').orc(train_data_path)\r\ntest.write.mode('overwrite').orc(test_data_path)\r\nprint(\"train and test datasets saved to {} and {}\".format(train_data_path, test_data_path))", + "source": "train, test = data.randomSplit([0.75, 0.25], seed=123)\r\n\r\nprint(\"train ({}, {})\".format(train.count(), len(train.columns)))\r\nprint(\"test ({}, {})\".format(test.count(), len(test.columns)))\r\n\r\ntrain_data_path = \"/spark_ml/AdultCensusIncomeTrain\"\r\ntest_data_path = \"/spark_ml/AdultCensusIncomeTest\"\r\n\r\ntrain.write.mode('overwrite').orc(train_data_path)\r\ntest.write.mode('overwrite').orc(test_data_path)\r\nprint(\"train and test datasets saved to {} and {}\".format(train_data_path, test_data_path))", "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "train (24469, 4)\ntest (8092, 4)\ntrain and test datasets saved to /spark_ml/AdultCensusIncomeTrain and /spark_ml/AdultCensusIncomeTest" + "text": "train (24469, 4)\ntest (8092, 4)\ntrain and test datasets saved to /spark_ml/AdultCensusIncomeTrain and /spark_ml/AdultCensusIncomeTest", + "output_type": "stream" } ], - "execution_count": 6 + "execution_count": 5 }, { "cell_type": "markdown", @@ -113,12 +113,12 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Using LogisticRegression model with Regularization Rate of 0.1.\nPipeline Created\nModel Trained\nModel is PipelineModel_8c7a4fdc6110\nModel Stages [StringIndexer_7d244350f55d, OneHotEncoderEstimator_559780c5ce92, StringIndexer_53280e6349e6, VectorAssembler_1051507100cb, LogisticRegressionModel: uid = LogisticRegression_5c0eda4eab78, numClasses = 2, numFeatures = 17]" + "text": "Using LogisticRegression model with Regularization Rate of 0.1.\nPipeline Created\nModel Trained\nModel is PipelineModel_1adfacc01e7a\nModel Stages [StringIndexer_ee8506a28443, OneHotEncoderEstimator_cb5dbefb5cce, StringIndexer_38769cda5ab3, VectorAssembler_a3c2d358bd55, LogisticRegressionModel: uid = LogisticRegression_18837c9488f5, numClasses = 2, numFeatures = 17]", + "output_type": "stream" } ], - "execution_count": 7 + "execution_count": 6 }, { "cell_type": "markdown", @@ -131,12 +131,12 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "Area under ROC: 0.7964496884726682\nArea Under PR: 0.5358180243123482\n+------+-----+----------+\n|income|label|prediction|\n+------+-----+----------+\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n+------+-----+----------+\nonly showing top 20 rows" + "text": "Area under ROC: 0.7964496884726682\nArea Under PR: 0.5358180243123482\n+------+-----+----------+\n|income|label|prediction|\n+------+-----+----------+\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n| <=50K| 0.0| 1.0|\n| >50K| 1.0| 1.0|\n+------+-----+----------+\nonly showing top 20 rows", + "output_type": "stream" } ], - "execution_count": 8 + "execution_count": 7 }, { "cell_type": "markdown", @@ -149,12 +149,12 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "saved model to /spark_ml/AdultCensus.mml\nSuccessfully loaded from /spark_ml/AdultCensus.mml" + "text": "saved model to /spark_ml/AdultCensus.mml\nSuccessfully loaded from /spark_ml/AdultCensus.mml", + "output_type": "stream" } ], - "execution_count": 9 + "execution_count": 8 }, { "cell_type": "markdown", @@ -167,12 +167,12 @@ "metadata": {}, "outputs": [ { - "output_type": "stream", "name": "stdout", - "text": "persist the mleap bundle from local to hdfs" + "text": "persist the mleap bundle from local to hdfs", + "output_type": "stream" } ], - "execution_count": 10 + "execution_count": 9 } ] } \ No newline at end of file From d39f7bf1ef5bcde4612408da45b3798c9f5dd503 Mon Sep 17 00:00:00 2001 From: Lixin Gong Date: Thu, 6 Jun 2019 11:50:46 -0700 Subject: [PATCH 08/12] remove jars due to policy. --- .../spark/sparkml/jars/JavaTestPackage.jar | Bin 12709 -> 0 bytes .../sparkml/jars/mssql_java_lang_extension.jar | Bin 4748 -> 0 bytes .../lib/mssql_java_lang_extension.jar | Bin 4748 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 samples/features/sql-big-data-cluster/spark/sparkml/jars/JavaTestPackage.jar delete mode 100644 samples/features/sql-big-data-cluster/spark/sparkml/jars/mssql_java_lang_extension.jar delete mode 100644 samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/lib/mssql_java_lang_extension.jar diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/jars/JavaTestPackage.jar b/samples/features/sql-big-data-cluster/spark/sparkml/jars/JavaTestPackage.jar deleted file mode 100644 index 296a16de73455f8c967c663e84315701cb0fdc1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12709 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
85UT^c!VZU- zFp2GUiW)q z)tW8JBh=NfRBb&KZ!4}5XW_>?^mLy$(#b9Cpv%H)X*t~aY*RS{Rxe-p4qwGU8faCr z(E5U*^Q%YKqhomci00bCm?>KAV;$?yz`H{NEPV$N5A%BR!JJcnHr%H($Hv3KGW$;Guj){-xs-cq887 z6;%RAQu#5xtzFaJOWmC+1&rmdQR>U>!JsN;(g$VORiWxK`cdx1KtaY*Y#=Jt*An^} zuQgie98g#DV~b~gId51A$Hk0L@pGH#+Ia3BFSY_FToOK+rECi#I-`cWr1{$7fPJ;&)~y$d4v0O) zlQ{cnNz3U`D>-!kp(g`5IJ^6(S-5%F zyQ?{Qx&2RHw&qF#!WVOpCzmDn0m{VTnHRl|u@H*W34xsi;I0jrb%RkPS2v2K6-1-C z7tHpMw(jAd-VzxFS22RrQn7Z>?g`1^+GW0~HAXSNDP+zS33&gdQ~&PqaU41dQ@3A@ z{zGM+GkXMNu*8`@z?ZVLXMs>(a>Z=4;4JozSB|N>?!X__iSfH7suNRZf;d7nkXA+Q zvMq!T;R~a;_5cQgfO^KViUmNYMNFTa>xRh66NszXM+JbaI4IjoU*sX5`p-})N|2ce z8XNRMvAoxIjhYRTgFo}JlvT3wxhU%-mK49jUt!y3d*4QIU-+w)HQXy#cKD6f(g4b|&V#~)RLme+hjIn+^XL&~tiH9K+KCU%4cz8-N%{{`_zY4@ z7H?M0%SJ8mRc8AhIm9{gYL}U&mbb{j;}i4EWo5c?J4TD9V~al-8;02QThBM6Hj=_c zsAEVw_k&1`)XjzSB=dHS1-l!GCf@2dd9G9rTWQ6$Y3;>51jkv_aI*fqqvhS4zarQ2 z$k181db!M7bGF(~Z`fHb@6FSc3qb}*FXpl?N>vC>9fpO(vDvE|qe*o+@e4;`0T9uQ z4whw!n3piM_!iw4uL|P~{Y&=EHlMYbmrGZfAu|O2!5l#L+Qcti$cOwN$+qwh%jG6G zsk%UdbrW^SDJCZLVkOBt)tFf0ukBDf#|@YA_dcwh4RHDlS^L($@kGNX@%j*BBt1g` zbO1G#FO4x3Lsd}Ijk+o3qNhEM?Tz_{lZO~P_Wc_N% zO$wA5=3IvI5LA?EVG?kzO!CBLQ89y;yr@^Iep$*V<}j(ic+gaYaUSJjGR zrxQC}YPK*#Exdfa_o^&izEw#36wj&JBaf44SV7Q0%_|xujS-W)9S;S8D4gGJkQr=R z6`p6m#=bCGa+@+i<8N9LyW@k4u|5;6mrvuy(Z~wEAr+pwXGKTHS>gO_BvClo5f!+O zRQD9MNK@kU>h%b~UNXP5FOhDnoy#13VU#wYg=>D~I5J#;v@}>ZCh-%uQ9-NeBPKE8 z8+I{?5XM-=B9d(7n;8bTaf3Y>g_*;o=1y~xBhwGVg5>+(`RRL=oARH2m|a+z9!EbK z#H@`Z4!$@{V8|H~9S2S9gVx83zMx3+8Gz4~Q#c7odZnloDbgLQ^t*WJYz>YRhY3vc zsTH~S^Zq$Q{`~0wRQ`M#Gx>*@0t<%^^M_&j&&KQDcJTil|IHk(A`c6P3iIDh+JB%l z{0Bby{p~;R;m@YO-=^(9V(aWa+@I^+|GuLIH2)6w uUyfCO1^sg>{VtV%1m5$l`0L;F9}B07Ji_zC2Lprl{0e!_N(}bjSN{iBqjJaq diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/jars/mssql_java_lang_extension.jar b/samples/features/sql-big-data-cluster/spark/sparkml/jars/mssql_java_lang_extension.jar deleted file mode 100644 index 85cb713358b76adf4aed4542ceb5329840bd4508..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4748 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 diff --git a/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/lib/mssql_java_lang_extension.jar b/samples/features/sql-big-data-cluster/spark/sparkml/mssql-mleap-app/lib/mssql_java_lang_extension.jar deleted file mode 100644 index 85cb713358b76adf4aed4542ceb5329840bd4508..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4748 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 From 35a36a25a5e31f51493460d38805345c9247571e Mon Sep 17 00:00:00 2001 From: Mihaela Blendea Date: Mon, 22 Jul 2019 09:21:51 -0700 Subject: [PATCH 09/12] Update push-bdc-images-to-custom-private-repo.py --- .../offline/push-bdc-images-to-custom-private-repo.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/samples/features/sql-big-data-cluster/deployment/offline/push-bdc-images-to-custom-private-repo.py b/samples/features/sql-big-data-cluster/deployment/offline/push-bdc-images-to-custom-private-repo.py index 28890249..0697de10 100644 --- a/samples/features/sql-big-data-cluster/deployment/offline/push-bdc-images-to-custom-private-repo.py +++ b/samples/features/sql-big-data-cluster/deployment/offline/push-bdc-images-to-custom-private-repo.py @@ -30,9 +30,6 @@ images = [ 'mssql-appdeploy-init', 'mssql-monitor-collectd', 'mssql-server-data', 'mssql-hadoop', - 'mssql-java', - 'mssql-mlservices-pythonserver', - 'mssql-mlservices-rserver', 'mssql-monitor-elasticsearch', 'mssql-monitor-influxdb', 'mssql-security-knox', From 31613470ceb8c36c3ff58bacf0935334350ac4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sr=C4=91an=20Bo=C5=BEovi=C4=87?= Date: Tue, 23 Jul 2019 03:43:29 +0200 Subject: [PATCH 10/12] Add prepare subnet for SQL MI delegation --- .../delegate-subnet/README.md | 86 ++++ .../delegate-subnet/delegateSubnet.ps1 | 452 ++++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 samples/manage/azure-sql-db-managed-instance/delegate-subnet/README.md create mode 100644 samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 diff --git a/samples/manage/azure-sql-db-managed-instance/delegate-subnet/README.md b/samples/manage/azure-sql-db-managed-instance/delegate-subnet/README.md new file mode 100644 index 00000000..ac317441 --- /dev/null +++ b/samples/manage/azure-sql-db-managed-instance/delegate-subnet/README.md @@ -0,0 +1,86 @@ +# Delegate subnet for Managed Instance deployment + +Script that validates and prepares virtual network and subnet for Managed Instance creation to comply with [networking requirements](https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance-vnet-configuration#requirements). + +### Contents + +[About this sample](#about-this-sample)
+[Before you begin](#before-you-begin)
+[Run this sample](#run-this-sample)
+[Sample details](#sample-details)
+[Disclaimers](#disclaimers)
+[Related links](#related-links)
+ + +
+ +## About this sample + +- **Applies to:** Azure SQL Database +- **Key features:** Managed Instance +- **Workload:** n/a +- **Programming Language:** PowerShell +- **Authors:** Srdan Bozovic +- **Update history:** n/a + + + +## Before you begin + +To run this sample, you need the following prerequisites. + +**Software prerequisites:** + +1. PowerShell 5.1 or PowerShell Core 6.0 +2. Azure PowerShell Az module + +**Azure prerequisites:** + +1. Permission to manage Azure virtual network + + + +## Run this sample + +Run the script below from either Windows or Azure Cloud Shell + +```powershell + +$scriptUrlBase = 'https://raw.githubusercontent.com/Microsoft/sql-server-samples/master/samples/manage/azure-sql-db-managed-instance/delegate-subnet' + +$parameters = @{ + subscriptionId = '' + resourceGroupName = '' + virtualNetworkName = '' + subnetName = '' + } + +Invoke-Command -ScriptBlock ([Scriptblock]::Create((iwr ($scriptUrlBase+'/delegateSubnet.ps1?t='+ [DateTime]::Now.Ticks)).Content)) -ArgumentList $parameters + +``` + + + +## Sample details + +This sample shows how to prepare Azure virtual network and subnet for Managed Instance deployment using PowerShell + +This is done in three simple steps: +- Validate - Selected virtual netwok and subnet are validated for Managed Instance networking requirements +- Confirm - User is shown a set of changes that need to be made to prepare subnet for Managed Instance deployment and asked for consent +- Prepare - Virtual network and subnet are configured properly + + + +## Disclaimers +The scripts and this guide are copyright Microsoft Corporations and are provided as samples. They are not part of any Azure service and are not covered by any SLA or other Azure-related agreements. They are provided as-is with no warranties express or implied. Microsoft takes no responsibility for the use of the scripts or the accuracy of this document. Familiarize yourself with the scripts before using them. + + + +## Related Links + + +For more information, see these articles: + +- [What is a Managed Instance?](https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance) +- [Configure a VNet for Azure SQL Database Managed Instance](https://docs.microsoft.com/azure/sql-database/sql-database-managed-instance-vnet-configuration) \ No newline at end of file diff --git a/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 b/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 new file mode 100644 index 00000000..2bc51314 --- /dev/null +++ b/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 @@ -0,0 +1,452 @@ +#$parameters = $args[0] + +$parameters = @{ + subscriptionId = 'a8c9a924-06c0-4bde-9788-e7b1370969e1' + resourceGroupName = 'srbozovi_delegation_test' + virtualNetworkName = 'vnet-subnetdelegation-westus' + subnetName = 'default' + } + +$subscriptionId = $parameters['subscriptionId'] +$resourceGroupName = $parameters['resourceGroupName'] +$virtualNetworkName = $parameters['virtualNetworkName'] +$subnetName = $parameters['subnetName'] +$force = $parameters['force'] + +$NSnetworkModels = "Microsoft.Azure.Commands.Network.Models" +$NScollections = "System.Collections.Generic" + +function VerifyPSVersion { + Write-Host "Verifying PowerShell version." + if ($PSVersionTable.PSEdition -eq "Desktop") { + if (($PSVersionTable.PSVersion.Major -ge 6) -or + (($PSVersionTable.PSVersion.Major -eq 5) -and ($PSVersionTable.PSVersion.Minor -ge 1))) { + Write-Host "PowerShell version verified." -ForegroundColor Green + } + else { + Write-Host "You need to install PowerShell version 5.1 or heigher." -ForegroundColor Red + Break; + } + } + else { + if ($PSVersionTable.PSVersion.Major -ge 6) { + Write-Host "PowerShell version verified." -ForegroundColor Green + } + else { + Write-Host "You need to install PowerShell version 6.0 or heigher." -ForegroundColor Red + Break; + } + } +} + +function EnsureAzModule { + Write-Host "Checking if Az module is imported." + $module = Get-Module Az + If ($null -eq $module) { + try { + Import-Module Az -ErrorAction Stop + Write-Host "Module Az imported." -ForegroundColor Green + } + catch { + Install-Module Az -AllowClobber + Write-Host "Module Az installed." -ForegroundColor Green + } + } else { + Write-Host "Module Az imported." -ForegroundColor Green + } +} + +function EnsureLogin () { + $context = Get-AzContext + If ($null -eq $context.Subscription) { + Write-Host "Sign-in..." + If ($null -eq (Connect-AzAccount -ErrorAction SilentlyContinue -ErrorVariable Errors)) { + Write-Host ("Sign-in failed: {0}" -f $Errors[0].Exception.Message) -ForegroundColor Red + Break + } + } + Write-Host "Sign-in successful." -ForegroundColor Green +} + +function SelectSubscriptionId { + param ( + $subscriptionId + ) + Write-Host "Selecting subscription '$subscriptionId'." + $context = Get-AzContext + If($context.Subscription.Id -ne $subscriptionId) + { + Try + { + Select-AzSubscription -SubscriptionId $subscriptionId -ErrorAction Stop | Out-null + } + Catch + { + Write-Host "Subscription selection failed: $_" -ForegroundColor Red + Break + } + } + Write-Host "Subscription selected." -ForegroundColor Green +} + +function LoadVirtualNetwork { + param ( + $resourceGroupName, + $virtualNetworkName + ) + Write-Host("Loading virtual network '{0}' in resource group '{1}'." -f $virtualNetworkName, $resourceGroupName) + $virtualNetwork = Get-AzVirtualNetwork -ResourceGroupName $resourceGroupName -Name $virtualNetworkName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + If($null -ne $virtualNetwork.Id) + { + Write-Host "Virtual network loaded." -ForegroundColor Green + return $virtualNetwork + } + else + { + Write-Host "Virtual network not found." -ForegroundColor Red + Break + } +} + +function LoadVirtualNetworkSubnet { + param ( + $virtualNetwork, + $subnetName + ) + Write-Host("Loading subnet '{0}'." -f $subnetName) + $subnets = $virtualNetwork.Subnets.Name + If($true -eq $subnets.Contains($subnetName)) + { + $subnetIndex = $subnets.IndexOf($subnetName) + $subnet = $virtualNetwork.Subnets[$subnetIndex] + Write-Host "Subnet loaded." -ForegroundColor Green + return $subnet + } + else + { + Write-Host "Subnet not found." -ForegroundColor Red + Break + } +} + +function VerifySubnet { + param ( + $subnet + ) + Write-Host("Verifying subnet '{0}'." -f $subnet.Name) + If($subnet.AddressPrefix.Split('/')[1] -le 28) + { + Write-Host "Passed Validation - Subnet is of enough size." -ForegroundColor Green + } + Else + { + Write-Host "Failed Validation - Minimum supported subnet size is /28." -ForegroundColor Red + Break + } + If( + ($subnet.IpConfigurations.Count -eq 0) -and + ( + ($subnet.ResourceNavigationLinks.Count -eq 0) -or + ($subnet.ResourceNavigationLinks[0].LinkedResourceType -eq 'Microsoft.Sql/virtualClusters') + ) + ) + { + Write-Host "Passed Validation - There are no conflicting resources inside the subnet." -ForegroundColor Green + } + Else + { + Write-Host "Failed Validation - Subnet is already in use." -ForegroundColor Red + Break + } +} + +function VerifyDelegation { + param ( + $subnet + ) + + $result = @{ + isDelegatedToManagedInstance = $false; + isDelegated = $false; + success = $false; + } + + $delegation = Get-AzDelegation -Subnet $subnet + + If($delegation -ne $null) + { + $result['isDelegated'] = $true + $result['isDelegatedToManagedInstance'] = $delegation.ServiceName -eq "Microsoft.Sql/managedInstances" + } + + $result['success'] = -not $result['isDelegated'] -or $result['isDelegatedToManagedInstance'] + + return $result +} + +function LoadNetworkSecurityGroup { + param ( + $subnet + ) + Write-Host("Loading Network security group for subnet '{0}'." -f $subnet.Name) + If( + $null -ne $subnet.NetworkSecurityGroup + ) + { + $nsgSegments = ($subnet.NetworkSecurityGroup.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries) + $nsgName = $nsgSegments[-1].Trim() + $nsgResourceGroup = $nsgSegments[3].Trim() + $networkSecurityGroup = Get-AzNetworkSecurityGroup -ResourceGroupName $nsgResourceGroup -Name $nsgName + Write-Host "Network security group security group loaded." -ForegroundColor Green + return $networkSecurityGroup + } + Else + { + return $null + } +} + +function HasNSG { + param ( + $subnet + ) + + $nsg = LoadNetworkSecurityGroup $subnet + return $nsg -ne $null +} + +function LoadRouteTable { + param ( + $subnet + ) + Write-Host("Loading Route table for subnet '{0}'." -f $subnet.Name) + If( + $null -ne $subnet.RouteTable + ) + { + $rtSegments = ($subnet.RouteTable.Id).Split("/", [System.StringSplitOptions]::RemoveEmptyEntries) + $rtName = $rtSegments[-1].Trim() + $rtResourceGroup = $rtSegments[3].Trim() + $routeTable = Get-AzRouteTable -ResourceGroupName $rtResourceGroup -Name $rtName + Write-Host "Route table loaded." -ForegroundColor Green + return $routeTable + } + return $null +} + +function HasRouteTable { + param ( + $subnet + ) + + $routeTable = LoadRouteTable $subnet + return $routeTable -ne $null +} + +function CreateNSG +{ + param( + $virtualNetwork, + $subnet + ) + + Write-Host "Creating Network security group." + $networkSecurityGroupName = "nsgManagedInstance" + (Get-Random -Maximum 1000) + + $securityRules = New-Object "$NScollections.List``1[$NSnetworkModels.PSSecurityRule]" + + $rule = New-AzNetworkSecurityRuleConfig ` + -Name prepare-allow_tds_inbound ` + -Description "Allow access to data" ` + -Direction Inbound -Priority 1000 -Access Allow -Protocol Tcp ` + -SourceAddressPrefix VirtualNetwork -DestinationAddressPrefix $subnet.AddressPrefix ` + -SourcePortRange * -DestinationPortRange @("1433","11000-11999") + $securityRules.Add($rule) + + + $rule = New-AzNetworkSecurityRuleConfig ` + -Name prepare-deny_all_inbound ` + -Description "Deny all other inbound traffic" ` + -Direction Inbound -Priority 4096 -Access Deny -Protocol * ` + -SourceAddressPrefix * -DestinationAddressPrefix * ` + -SourcePortRange * -DestinationPortRange * + $securityRules.Add($rule) + + $rule = New-AzNetworkSecurityRuleConfig ` + -Name prepare-deny_all_outbound ` + -Description "Deny all other outbound traffic" ` + -Direction Outbound -Priority 4096 -Access Deny -Protocol * ` + -SourceAddressPrefix * -DestinationAddressPrefix * ` + -SourcePortRange * -DestinationPortRange * + $securityRules.Add($rule) + + Try + { + $networkSecurityGroup = New-AzNetworkSecurityGroup -Name $networkSecurityGroupName -ResourceGroupName $virtualNetwork.ResourceGroupName -Location $virtualNetwork.Location -SecurityRules $securityRules + } + Catch + { + Write-Host "Failed: $_" -ForegroundColor Red + } + + Write-Host "Associating Network security group." + $subnet.NetworkSecurityGroup = $networkSecurityGroup +} + +function CreateRouteTable +{ + param( + $virtualNetwork, + $subnet + ) + Write-Host "Creating Route table." + $routeTableName = "rtManagedInstance" + (Get-Random -Maximum 1000) + + Try + { + $routeTable = New-AzRouteTable -Name $routeTableName -ResourceGroupName $virtualNetwork.ResourceGroupName -Location $virtualNetwork.Location + } + Catch + { + Write-Host "Failed: $_" -ForegroundColor Red + } + + Write-Host "Associating Route table." + $subnet.RouteTable = $routeTable +} + +function DelegateSubnet +{ + param( + $subnet + ) + + Write-Host "Creating Subnet Delegation for Managed Instance." + + + $subnet.Delegations = New-Object "$NScollections.List``1[$NSnetworkModels.PSDelegation]" + $delegationName = "dgManagedInstance" + (Get-Random -Maximum 1000) + + Try + { + $delegation = New-AzDelegation -Name $delegationName -ServiceName "Microsoft.Sql/managedInstances" + } + Catch + { + Write-Host "Failed: $_" -ForegroundColor Red + } + + Write-Host "Associating Subnet Delegation for Managed Instance." + $subnet.Delegations.Add($delegation) +} + +function SetVirtualNetwork +{ + param($virtualNetwork) + + Write-Host "Applying changes to the virtual network." + Try + { + Set-AzVirtualNetwork -VirtualNetwork $virtualNetwork -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null + } + Catch + { + Write-Host "Failed: $_" -ForegroundColor Red + } + +} + +VerifyPSVersion +EnsureAzModule +EnsureLogin +SelectSubscriptionId -subscriptionId $subscriptionId + +$virtualNetwork = LoadVirtualNetwork -resourceGroupName $resourceGroupName -virtualNetworkName $virtualNetworkName +$subnet = LoadVirtualNetworkSubnet -virtualNetwork $virtualNetwork -subnetName $subnetName + +Write-Host + +$delegationVerificationResult = VerifyDelegation $subnet + +If($delegationVerificationResult['success']) +{ + VerifySubnet $subnet + $hasNsg = HasNSG $subnet + $hasRouteTable = HasRouteTable $subnet + $isValid = $delegationVerificationResult['isDelegatedToManagedInstance'] -and $hasNsg -and $hasRouteTable + + If($isValid -ne $true) + { + Write-Host + Write-Host("---------- To delegate the virtual network subnet for Managed Instance this script will: --------------- ") -ForegroundColor Yellow + Write-Host + + If(-not $hasNsg) + { + Write-Host "Create Network security group and associate it to the subnet." -ForegroundColor Yellow + } + + If(-not $hasRouteTable) + { + Write-Host "Create Route table and associate it to the subnet." -ForegroundColor Yellow + } + + If(-not $delegationVerificationResult['isDelegatedToManagedInstance']) + { + Write-Host "Delegate subnet to Managed Instance." -ForegroundColor Yellow + } + + Write-Host + Write-Host("-------------------------------------------------------------------------------------------------------- ") -ForegroundColor Yellow + Write-Host + + + $applyChanges = $force + + If($applyChanges -ne $true) + { + $reply = Read-Host -Prompt "Do you want to make these changes? [y/n]" + $applyChanges = $reply -match "[yY]" + Write-Host + } + + If ($applyChanges) + { + If(-not $hasNsg) + { + CreateNSG $virtualNetwork $subnet + } + + If(-not $hasRouteTable) + { + CreateRouteTable $virtualNetwork $subnet + } + + If(-not $delegationVerificationResult['isDelegatedToManagedInstance']) + { + DelegateSubnet $subnet + } + + SetVirtualNetwork $virtualNetwork + + Write-Host + Write-Host "Subnet delegated to the Managed Instance." -ForegroundColor Green + Write-Host "https://portal.azure.com/#create/Microsoft.SQLManagedInstance" + } + Else + { + Write-Host + Write-Host "Subnet delegation canceled." -ForegroundColor Yellow + } + } + Else + { + Write-Host "Subnet is already delegated to the Managed Instance." -ForegroundColor Green + Write-Host "https://portal.azure.com/#create/Microsoft.SQLManagedInstance" + } +} +Else +{ + Write-Host + Write-Host "Subnet is already delegated to other service." -ForegroundColor Red +} From 7103655c738b87ddfe9c46174a9bf320e1974e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sr=C4=91an=20Bo=C5=BEovi=C4=87?= Date: Tue, 23 Jul 2019 03:46:00 +0200 Subject: [PATCH 11/12] Update delegateSubnet.ps1 --- .../delegate-subnet/delegateSubnet.ps1 | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 b/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 index 2bc51314..0616853a 100644 --- a/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 +++ b/samples/manage/azure-sql-db-managed-instance/delegate-subnet/delegateSubnet.ps1 @@ -1,11 +1,4 @@ -#$parameters = $args[0] - -$parameters = @{ - subscriptionId = 'a8c9a924-06c0-4bde-9788-e7b1370969e1' - resourceGroupName = 'srbozovi_delegation_test' - virtualNetworkName = 'vnet-subnetdelegation-westus' - subnetName = 'default' - } +$parameters = $args[0] $subscriptionId = $parameters['subscriptionId'] $resourceGroupName = $parameters['resourceGroupName'] From 4f42994be6e6e2bd1bf7cfbf72fee0a49ed77658 Mon Sep 17 00:00:00 2001 From: Pedro Lopes Date: Mon, 22 Jul 2019 22:57:18 -0700 Subject: [PATCH 12/12] Update register.md --- samples/manage/sql-server-extended-security-updates/register.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/manage/sql-server-extended-security-updates/register.md b/samples/manage/sql-server-extended-security-updates/register.md index d8dd2df5..1711dee6 100644 --- a/samples/manage/sql-server-extended-security-updates/register.md +++ b/samples/manage/sql-server-extended-security-updates/register.md @@ -9,7 +9,7 @@ - [Formatting requirements for a CSV file](#csv) ## Register a single SQL Server instance -It's required that at least one SQL Server instance is registered in the scope of your SQL Server Registry, in order to download an ESU package (if and when available). +It's required that at least one SQL Server instance is registered in the scope of your SQL Server Registry, in order to download an ESU package (if and when available). If you have not yet created the SQL Server Registry in your Azre subscription, refer to the page [Create the SQL Server Registry](./registry.md). **Important**: It's not required to register a SQL Server instance for ESUs when running an Azure Virtual Machine that is configured for Automatic Updates. For more information, see [Manage Windows updates by using Azure Automation](https://docs.microsoft.com/azure/automation/automation-tutorial-update-management).