Deploying a Decision Optimization model with Watson Machine Learning

This notebook shows you how to deploy a Decision Optimization model, create and monitor jobs, and get solutions using the Watson Machine Learning Python Client.

This notebook runs on Python.

Set up the Watson Machine Learning client

Before you use the sample code in this notebook, you need to:

Install and then import the Watson Machine Learning client library. This notebook uses the preview Python client based on v4 of Watson Machine Learning APIs.

Important Do not load both Python client libraries into a notebook.

In [ ]:
# Uninstall the Watson Machine Learning client Python client based on v3 APIs

!pip uninstall watson-machine-learning-client -y
In [ ]:
# Install WML client API

!pip install ibm-watson-machine-learning
In [ ]:
from ibm_watson_machine_learning import APIClient

Create a client instance

Use your Watson Machine Learning credentials. You can find infos on how to get your API key and instance's URL here in the second step of the "Before you begin" section.

In [ ]:
# Instantiate a client using credentials
# You may want to change the URL depending on the instance you are using.

api_key = 'PASTE YOUR PLATFORM API KEY HERE'
location = 'PASTE YOUR INSTANCE LOCATION HERE'

wml_credentials = {
      "apikey": api_key,
      "url": 'https://' + location + '.ml.cloud.ibm.com'
}

client = APIClient(wml_credentials)
In [ ]:
client.version

Prepare your model archive

Put the model.py file in a subdirectory and create a tar.gz file. The model consists of two parts:

  • some functions to create an inputs dictionary from files and create files from an outputs dictionary,
  • the real optimization model which uses the inputs and outputs dictionaries.

Use the write_file command to write these models to a main.py file.

Use the tar command to create a tar archive.

In [ ]:
%mkdir model
In [ ]:
%%writefile model/main.py

from docplex.util.environment import get_environment
from os.path import splitext
import pandas
from six import iteritems

def get_all_inputs():
    '''Utility method to read a list of files and return a tuple with all
    read data frames.
    Returns:
        a map { datasetname: data frame }
    '''
    result = {}
    env = get_environment()
    for iname in [f for f in os.listdir('.') if splitext(f)[1] == '.csv']:
        with env.get_input_stream(iname) as in_stream:
            df = pandas.read_csv(in_stream)
            datasetname, _ = splitext(iname)
            result[datasetname] = df
    return result

def write_all_outputs(outputs):
    '''Write all dataframes in ``outputs`` as .csv.

    Args:
        outputs: The map of outputs 'outputname' -> 'output df'
    '''
    for (name, df) in iteritems(outputs):
        csv_file = '%s.csv' % name
        print(csv_file)
        with get_environment().get_output_stream(csv_file) as fp:
            if sys.version_info[0] < 3:
                fp.write(df.to_csv(index=False, encoding='utf8'))
            else:
                fp.write(df.to_csv(index=False).encode(encoding='utf8'))
    if len(outputs) == 0:
        print("Warning: no outputs written")
        
In [ ]:
%%writefile -a model/main.py

# Load CSV files into inputs dictionnary
inputs = get_all_inputs()

food = inputs['diet_food']
nutrients = inputs['diet_nutrients']
food_nutrients = inputs['diet_food_nutrients']
food_nutrients.set_index('Food', inplace=True)
        
from docplex.mp.model import Model

# Model
mdl = Model(name='diet')

# Create decision variables, limited to be >= Food.qmin and <= Food.qmax
qty = food[['name', 'qmin', 'qmax']].copy()
qty['var'] = qty.apply(lambda x: mdl.continuous_var(lb=x['qmin'],
                                                ub=x['qmax'],
                                                name=x['name']),
                   axis=1)
# make the name the index
qty.set_index('name', inplace=True)

# Limit range of nutrients, and mark them as KPIs
for n in nutrients.itertuples():
    amount = mdl.sum(qty.loc[f.name]['var'] * food_nutrients.loc[f.name][n.name]
                     for f in food.itertuples())
    mdl.add_range(n.qmin, amount, n.qmax)
    mdl.add_kpi(amount, publish_name='Total %s' % n.name)

# Minimize cost
obj = mdl.sum(qty.loc[f.name]['var'] * f.unit_cost for f in food.itertuples())
mdl.add_kpi(obj, publish_name="Minimal cost");
mdl.minimize(obj)

mdl.print_information()

# solve
ok = mdl.solve()

mdl.print_solution()

import pandas
import numpy

solution_df = pandas.DataFrame(columns=['Food', 'value'])

for index, dvar in enumerate(mdl.solution.iter_variables()):
    solution_df.loc[index,'Food'] = dvar.to_string()
    solution_df.loc[index,'value'] = dvar.solution_value
    
outputs = {}
outputs['solution'] = solution_df
        
# Generate output files
write_all_outputs(outputs)
In [ ]:
import tarfile
def reset(tarinfo):
    tarinfo.uid = tarinfo.gid = 0
    tarinfo.uname = tarinfo.gname = "root"
    return tarinfo
tar = tarfile.open("model.tar.gz", "w:gz")
tar.add("model/main.py", arcname="main.py", filter=reset)
tar.close()

Upload your model on Watson Machine Learning

Store model in Watson Machine Learning with:

  • the tar archive previously created,
  • metadata including the model type and runtime

Get the model_uid.

In [ ]:
# All available meta data properties 

client.repository.ModelMetaNames.show()

Information about the CRNs can be found here in the third and fourth step of the "Before you begin" section.

Use the following code to list deployment spaces and delete unneeded ones:

client.spaces.list()

client.spaces.delete("")

client.spaces.get_details("")

In [ ]:
client.set.default_space('PASTE YOUR SPACE ID HERE')
In [ ]:
mnist_metadata = {
    client.repository.ModelMetaNames.NAME: "Diet",
    client.repository.ModelMetaNames.DESCRIPTION: "Model for Diet",
    client.repository.ModelMetaNames.TYPE: "do-docplex_20.1",
    client.repository.ModelMetaNames.SOFTWARE_SPEC_UID: client.software_specifications.get_uid_by_name("do_20.1")
}

model_details = client.repository.store_model(model='/home/wsuser/work/model.tar.gz', meta_props=mnist_metadata)

model_uid = client.repository.get_model_id(model_details)

# print model uid if needed
# print( model_uid )

Create a deployment

Create a batch deployment for the model, providing information such as:

  • the maximum number of compute nodes
  • the T-shirt size of the compute nodes

Get the deployment_uid.

In [ ]:
meta_props = {
    client.deployments.ConfigurationMetaNames.NAME: "Diet Deployment",
    client.deployments.ConfigurationMetaNames.DESCRIPTION: "Diet Deployment",
    client.deployments.ConfigurationMetaNames.BATCH: {},
    client.deployments.ConfigurationMetaNames.HARDWARE_SPEC: {'name': 'S', 'nodes': 1}
}

deployment_details = client.deployments.create(model_uid, meta_props=meta_props)

deployment_uid = client.deployments.get_uid(deployment_details)

# print deployment id if needed
# print( deployment_uid )
In [ ]:
# List all existing deployments

client.deployments.list()

Create and monitor a job with inline data for your deployed model

Create a payload containing inline input data.

Create a new job with this payload and the deployment.

Get the job_uid.

In [ ]:
# Import pandas library 
import pandas as pd 
  
# initialize list of lists 
diet_food = pd.DataFrame([ ["Roasted Chicken", 0.84, 0, 10],
                ["Spaghetti W/ Sauce", 0.78, 0, 10],
                ["Tomato,Red,Ripe,Raw", 0.27, 0, 10],
                ["Apple,Raw,W/Skin", 0.24, 0, 10],
                ["Grapes", 0.32, 0, 10],
                ["Chocolate Chip Cookies", 0.03, 0, 10],
                ["Lowfat Milk", 0.23, 0, 10],
                ["Raisin Brn", 0.34, 0, 10],
                ["Hotdog", 0.31, 0, 10]] , columns = ["name","unit_cost","qmin","qmax"])

diet_food_nutrients = pd.DataFrame([
                ["Spaghetti W/ Sauce", 358.2, 80.2, 2.3, 3055.2, 11.6, 58.3, 8.2],
                ["Roasted Chicken", 277.4, 21.9, 1.8, 77.4, 0, 0, 42.2],
                ["Tomato,Red,Ripe,Raw", 25.8, 6.2, 0.6, 766.3, 1.4, 5.7, 1],
                ["Apple,Raw,W/Skin", 81.4, 9.7, 0.2, 73.1, 3.7, 21, 0.3],
                ["Grapes", 15.1, 3.4, 0.1, 24, 0.2, 4.1, 0.2],
                ["Chocolate Chip Cookies", 78.1, 6.2, 0.4, 101.8, 0, 9.3, 0.9],
                ["Lowfat Milk", 121.2, 296.7, 0.1, 500.2, 0, 11.7, 8.1],
                ["Raisin Brn", 115.1, 12.9, 16.8, 1250.2, 4, 27.9, 4],
                ["Hotdog", 242.1, 23.5, 2.3, 0, 0, 18, 10.4 ]
            ] , columns = ["Food","Calories","Calcium","Iron","Vit_A","Dietary_Fiber","Carbohydrates","Protein"])

diet_nutrients = pd.DataFrame([
                ["Calories", 2000, 2500],
                ["Calcium", 800, 1600],
                ["Iron", 10, 30],
                ["Vit_A", 5000, 50000],
                ["Dietary_Fiber", 25, 100],
                ["Carbohydrates", 0, 300],
                ["Protein", 50, 100]
            ], columns = ["name","qmin","qmax"])
In [ ]:
solve_payload = {
    client.deployments.DecisionOptimizationMetaNames.INPUT_DATA: [
        {
            "id":"diet_food.csv",
            "values" : diet_food
        },
        {
            "id":"diet_food_nutrients.csv",
            "values" : diet_food_nutrients
        },
        {
            "id":"diet_nutrients.csv",
            "values" : diet_nutrients
        }
    ],
    client.deployments.DecisionOptimizationMetaNames.OUTPUT_DATA: [
    {
        "id":".*\.csv"
    }
    ]
}

job_details = client.deployments.create_job(deployment_uid, solve_payload)
job_uid = client.deployments.get_job_uid(job_details)

# print job id if needed
# print( job_uid )

Display job status until it is completed.

The first job of a new deployment might take some time as a compute node must be started.

In [ ]:
from time import sleep

while job_details['entity']['decision_optimization']['status']['state'] not in ['completed', 'failed', 'canceled']:
    print(job_details['entity']['decision_optimization']['status']['state'] + '...')
    sleep(5)
    job_details=client.deployments.get_job_details(job_uid)

print( job_details['entity']['decision_optimization']['status']['state'])

Extract and display solution

Display the output solution.

Display the KPI Total Calories value.

In [ ]:
# Create a dataframe for the solution
solution = pd.DataFrame(job_details['entity']['decision_optimization']['output_data'][0]['values'], 
                        columns = job_details['entity']['decision_optimization']['output_data'][0]['fields'])
solution.head()
In [ ]:
print( job_details['entity']['decision_optimization']['solve_state']['details']['KPI.Total Calories'] )

Solve another problem using the same deployment

Create a new payload with modified input data.

In [ ]:
# Change the input data
diet_nutrients.at[0,'qmin'] = 1500
diet_nutrients.at[0,'qmax'] = 2000

solve_payload = {
    client.deployments.DecisionOptimizationMetaNames.INPUT_DATA: [
        {
            "id":"diet_food.csv",
            "values" : diet_food         
        },
        {
            "id":"diet_food_nutrients.csv",
             "values" : diet_food_nutrients            
        },
        {
            "id":"diet_nutrients.csv",
            "values" : diet_nutrients
        }
    ],
    client.deployments.DecisionOptimizationMetaNames.OUTPUT_DATA: [
    {
        "id":".*\.csv"
    }
    ]
}

Create a new job.

In [ ]:
job_details = client.deployments.create_job(deployment_uid, solve_payload)
job_uid = client.deployments.get_job_uid(job_details)

# print job id if needed
# print( job_uid )

Display job status until it is completed.

In [ ]:
while job_details['entity']['decision_optimization']['status']['state'] not in ['completed', 'failed', 'canceled']:
    print(job_details['entity']['decision_optimization']['status']['state'] + '...')
    sleep(5)
    job_details=client.deployments.get_job_details(job_uid)

print( job_details['entity']['decision_optimization']['status']['state'])

Display the KPI Total Calories value for this modified data.

In [ ]:
print( job_details['entity']['decision_optimization']['solve_state']['details']['KPI.Total Calories'] )
In [ ]:
print(client.deployments.get_job_details(job_uid)['entity']['decision_optimization']['status'])

Delete the deployment

Use the following method to delete the deployment.

In [ ]:
client.deployments.delete(deployment_uid)

Summary and next steps

You successfully completed this notebook!

You've learned how to:

  • work with the Watson Machine Learning client
  • prepare your model archive and upload your model on Watson Machine Learning
  • create a deployment
  • create and monitor a job with inline data for your deployed model
  • display the solution

Check out our online documentation at https://dataplatform.cloud.ibm.com/docs for more samples, tutorials and documentation.


Copyright © 2019, 2021. This notebook and its source code are released under the terms of the MIT License.

Love this notebook? Don't have an account yet?
Share it with your colleagues and help them discover the power of Watson Studio! Sign Up