Skip to content

Remove sample and prediction key #399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func describeAPI(name string, resourcesRes *schema.GetResourcesResponse, flagVer

out := "\n" + console.Bold("url: ") + apiEndpoint + "\n"
out += "\n" + console.Bold("debug url: ") + apiEndpoint + "?debug=true" + "\n\n"
out += fmt.Sprintf("%s curl -X POST -H \"Content-Type: application/json\" %s -d @samples.json\n", console.Bold("curl:"), apiEndpoint)
out += fmt.Sprintf("%s curl -X POST -H \"Content-Type: application/json\" %s -d @sample.json\n", console.Bold("curl:"), apiEndpoint)
out += fmt.Sprintf(console.Bold("updated at:")+" %s\n\n", libtime.LocalTimestamp(updatedAt))

statusTable := table.Table{
Expand Down
20 changes: 8 additions & 12 deletions cli/cmd/predict.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,14 @@ func init() {
predictCmd.Flags().BoolVar(&predictDebug, "debug", false, "Predict with debug mode")
}

type PredictResponse struct {
Predictions []interface{} `json:"predictions"`
}

var predictCmd = &cobra.Command{
Use: "predict API_NAME SAMPLES_FILE",
Use: "predict API_NAME SAMPLE_FILE",
Short: "make predictions",
Long: "Make predictions.",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
apiName := args[0]
samplesJSONPath := args[1]
sampleJSONPath := args[1]

resourcesRes, err := getResourcesResponse()
if err != nil {
Expand Down Expand Up @@ -86,7 +82,7 @@ var predictCmd = &cobra.Command{
if predictDebug {
apiURL += "?debug=true"
}
predictResponse, err := makePredictRequest(apiURL, samplesJSONPath)
predictResponse, err := makePredictRequest(apiURL, sampleJSONPath)
if err != nil {
if strings.Contains(err.Error(), "503 Service Temporarily Unavailable") || strings.Contains(err.Error(), "502 Bad Gateway") {
errors.Exit(ErrorAPINotReady(apiName, resource.StatusCreating.Message()))
Expand All @@ -102,12 +98,12 @@ var predictCmd = &cobra.Command{
},
}

func makePredictRequest(apiURL string, samplesJSONPath string) (*PredictResponse, error) {
samplesBytes, err := files.ReadFileBytes(samplesJSONPath)
func makePredictRequest(apiURL string, sampleJSONPath string) (interface{}, error) {
sampleBytes, err := files.ReadFileBytes(sampleJSONPath)
if err != nil {
errors.Exit(err)
}
payload := bytes.NewBuffer(samplesBytes)
payload := bytes.NewBuffer(sampleBytes)
req, err := http.NewRequest("POST", apiURL, payload)
if err != nil {
return nil, errors.Wrap(err, errStrCantMakeRequest)
Expand All @@ -119,11 +115,11 @@ func makePredictRequest(apiURL string, samplesJSONPath string) (*PredictResponse
return nil, err
}

var predictResponse PredictResponse
var predictResponse interface{}
err = json.DecodeWithNumber(httpResponse, &predictResponse)
if err != nil {
return nil, errors.Wrap(err, "prediction response")
}

return &predictResponse, nil
return predictResponse, nil
}
6 changes: 1 addition & 5 deletions dev/load.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ SLEEP="0" # seconds

# Iris
URL="https://a5a5afc3ba1bb11e9a4d20ab58369843-145237363.us-west-2.elb.amazonaws.com/iris/tensorflow"
DATA='{ "samples": [ { "sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3 } ] }'

# Insurance
# URL="https://ac47cdf2e99cb11e9ade10693263c378-1155928797.us-west-2.elb.amazonaws.com/insurance/cost"
# DATA='{ "samples": [ { "age": 22, "bmi": 25, "children": 0, "region": "northeast", "sex": "female", "smoker": "no" } ] }'
DATA='{ "sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3 }'

trap ctrl_c INT
function ctrl_c() {
Expand Down
8 changes: 4 additions & 4 deletions docs/cluster/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ The `refresh` command behaves similarly to the `deploy` command. The key differe
## predict

```text
Make predictions.
Usage:
cortex predict API_NAME SAMPLES_FILE [flags]
cortex predict API_NAME SAMPLE_FILE [flags]

Flags:
--debug Predict with debug mode
-d, --deployment string deployment name
-e, --env string environment (default "dev")
-h, --help help for predict
-j, --json print the raw json response
```

The `predict` command converts samples from a JSON file into prediction requests and displays the response. This command is useful for quickly testing predictions.
The `predict` command converts a sample from a JSON file into a prediction request and displays the response. This command is useful for quickly testing predictions.

## delete

Expand Down
2 changes: 1 addition & 1 deletion docs/cluster/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ cortex get tensorflow

# Classify a sample
curl -X POST -H "Content-Type: application/json" \
-d '{ "samples": [ { "sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3 } ] }' \
-d '{ "sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3 }' \
<API endpoint>
```

Expand Down
3 changes: 3 additions & 0 deletions examples/image-classifier/sample_image_encoded.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions examples/image-classifier/sample_image_url.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"url": "https://bowwowinsurance.com.au/wp-content/uploads/2018/10/akita-700x700.jpg"
}
10 changes: 0 additions & 10 deletions examples/image-classifier/samples.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/iris-classifier/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ url: http://***.amazonaws.com/iris/classifier

$ curl http://***.amazonaws.com/iris/classifier \
-X POST -H "Content-Type: application/json" \
-d '{ "samples": [{"sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3}]}'
-d '{"sepal_length": 5.2, "sepal_width": 3.6, "petal_length": 1.4, "petal_width": 0.3}'

iris-setosa
```
Expand Down
6 changes: 6 additions & 0 deletions examples/iris-classifier/sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sepal_length": 5.2,
"sepal_width": 3.6,
"petal_length": 1.4,
"petal_width": 0.3
}
10 changes: 0 additions & 10 deletions examples/iris-classifier/samples.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/sentiment-analysis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ url: http://***.amazonaws.com/sentiment/analysis

$ curl http://***.amazonaws.com/sentiment/analysis \
-X POST -H "Content-Type: application/json" \
-d '{ "samples": [{"input": "The movie was great!"}]}'
-d '{"input": "The movie was great!"}'

positive
```
Expand Down
3 changes: 3 additions & 0 deletions examples/sentiment-analysis/sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"input": "The movie was great!"
}
7 changes: 0 additions & 7 deletions examples/sentiment-analysis/samples.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/text-generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The output above indicates that one replica of the API was requested and one rep
```bash
$ curl http://***.amazonaws.com/text/generator \
-X POST -H "Content-Type: application/json" \
-d '{"samples": [{"text": "machine learning"}]}'
-d '{"text": "machine learning"}'

Machine learning, with more than one thousand researchers around the world today, are looking to create computer-driven machine learning algorithms that can also be applied to human and social problems, such as education, health care, employment, medicine, politics, or the environment...
```
Expand Down
3 changes: 3 additions & 0 deletions examples/text-generator/sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"text": "machine learning"
}
7 changes: 0 additions & 7 deletions examples/text-generator/samples.json

This file was deleted.

123 changes: 52 additions & 71 deletions pkg/workloads/cortex/lib/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,100 +62,81 @@ def status_code_metric(dimensions, status_code):
return [{"MetricName": "StatusCode", "Dimensions": status_code_dimensions, "Value": 1}]


def predictions_per_request_metric(dimensions, prediction_count):
return [
{"MetricName": "PredictionsPerRequest", "Dimensions": dimensions, "Value": prediction_count}
]


def extract_predicted_values(api, predictions):
predicted_values = []

def extract_prediction(api, prediction):
tracker = api.get("tracker")
for prediction in predictions:
if tracker.get("key") is not None:
key = tracker["key"]
if type(prediction) != dict:
raise ValueError(
"failed to track key '{}': expected prediction to be of type dict but found '{}'".format(
key, type(prediction)
)
)
if prediction.get(key) is None:
raise ValueError(
"failed to track key '{}': not found in prediction".format(tracker["key"])
if tracker.get("key") is not None:
key = tracker["key"]
if type(prediction) != dict:
raise ValueError(
"failed to track key '{}': expected prediction to be of type dict but found '{}'".format(
key, type(prediction)
)
predicted_value = prediction[key]
else:
predicted_value = prediction

if tracker["model_type"] == "classification":
if type(predicted_value) != str and type(predicted_value) != int:
raise ValueError(
"failed to track classification prediction: expected type 'str' or 'int' but encountered '{}'".format(
type(predicted_value)
)
)
if prediction.get(key) is None:
raise ValueError(
"failed to track key '{}': not found in prediction".format(tracker["key"])
)
predicted_value = prediction[key]
else:
predicted_value = prediction

if tracker["model_type"] == "classification":
if type(predicted_value) != str and type(predicted_value) != int:
raise ValueError(
"failed to track classification prediction: expected type 'str' or 'int' but encountered '{}'".format(
type(predicted_value)
)
else:
if type(predicted_value) != float and type(predicted_value) != int: # allow ints
raise ValueError(
"failed to track regression prediction: expected type 'float' or 'int' but encountered '{}'".format(
type(predicted_value)
)
)
else:
if type(predicted_value) != float and type(predicted_value) != int: # allow ints
raise ValueError(
"failed to track regression prediction: expected type 'float' or 'int' but encountered '{}'".format(
type(predicted_value)
)
predicted_values.append(predicted_value)
)
return predicted_value

return predicted_values


def prediction_metrics(dimensions, api, predicted_values):
def prediction_metrics(dimensions, api, prediction):
metric_list = []
tracker = api.get("tracker")
for predicted_value in predicted_values:
if tracker["model_type"] == "classification":
dimensions_with_class = dimensions + [{"Name": "Class", "Value": str(predicted_value)}]
metric = {
"MetricName": "Prediction",
"Dimensions": dimensions_with_class,
"Unit": "Count",
"Value": 1,
}

metric_list.append(metric)
else:
metric = {
"MetricName": "Prediction",
"Dimensions": dimensions,
"Value": float(predicted_value),
}
metric_list.append(metric)
if tracker["model_type"] == "classification":
dimensions_with_class = dimensions + [{"Name": "Class", "Value": str(prediction)}]
metric = {
"MetricName": "Prediction",
"Dimensions": dimensions_with_class,
"Unit": "Count",
"Value": 1,
}

metric_list.append(metric)
else:
metric = {"MetricName": "Prediction", "Dimensions": dimensions, "Value": float(prediction)}
metric_list.append(metric)
return metric_list


def cache_classes(ctx, api, predicted_values, class_set):
for predicted_value in predicted_values:
if predicted_value not in class_set:
upload_class(ctx, api["name"], predicted_value)
class_set.add(predicted_value)
def cache_classes(ctx, api, prediction, class_set):
if prediction not in class_set:
upload_class(ctx, api["name"], prediction)
class_set.add(prediction)


def post_request_metrics(ctx, api, response, predictions, class_set):
def post_request_metrics(ctx, api, response, prediction_payload, class_set):
api_name = api["name"]
api_dimensions = api_metric_dimensions(ctx, api_name)
metrics_list = []
metrics_list += status_code_metric(api_dimensions, response.status_code)

if predictions is not None:
metrics_list += predictions_per_request_metric(api_dimensions, len(predictions))

if prediction_payload is not None:
if api.get("tracker") is not None:
try:
predicted_values = extract_predicted_values(api, predictions)
prediction = extract_prediction(api, prediction_payload)

if api["tracker"]["model_type"] == "classification":
cache_classes(ctx, api, predicted_values, class_set)
cache_classes(ctx, api, prediction, class_set)

metrics_list += prediction_metrics(api_dimensions, api, predicted_values)
metrics_list += prediction_metrics(api_dimensions, api, prediction)
except Exception as e:
logger.warn(str(e), exc_info=True)

Expand Down
Loading