-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
174 lines (140 loc) · 5.37 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#!/usr/bin/env python3
# app
# A quick Flask app to demonstrate Machine Learning decision space.
#
# Author: Benjamin Bengfort <benjamin@bengfort.com>
# Created: Sun Jul 21 06:22:47 2019 -0400
#
# Copyright (C) 2019 Georgetown Data Analytics (CCPE)
# For license information, see LICENSE.txt
#
# ID: app.py [] benjamin@bengfort.com $
"""
A quick Flask app to demonstrate Machine Learning decision space.
"""
##########################################################################
## Imports
##########################################################################
import json
import numpy as np
from flask import Flask
from flask import render_template, jsonify, request
from numpy import asarray
from functools import partial
from datetime import datetime
from sklearn.svm import SVC
from sklearn.preprocessing import MinMaxScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_recall_fscore_support as prfs
from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB, ComplementNB
from sklearn.datasets import make_blobs, make_circles, make_moons, make_classification
##########################################################################
## Data Generators
##########################################################################
make_moons = partial(make_moons, n_samples=256, noise=0.075)
make_blobs = partial(make_blobs, n_samples=256, n_features=2, centers=2)
make_circles = partial(make_circles, n_samples=256, noise=0.075, factor=0.5)
make_binary = partial(make_classification, n_samples=256, n_features=2, n_redundant=0, n_classes=2)
make_multiclass = partial(make_classification, n_samples=256, n_features=2, n_redundant=0, n_clusters_per_class=1, n_classes=4)
##########################################################################
## Flask Application Definition
##########################################################################
app = Flask(__name__)
version = "v1.1.0"
started = datetime.now()
##########################################################################
## Routes
##########################################################################
@app.route('/')
@app.route('/index')
def index():
return render_template('index.html', title='Home')
@app.route("/generate", methods=["POST"])
def generate():
# TODO: test content type and send 400 if not JSON
data = request.get_json()
generator = {
'…': None, None: None, "": None,
'binary': make_binary,
'multiclass': make_multiclass,
'blobs': make_blobs,
'circles': make_circles,
'moons': make_moons,
}[data.get("generator", None)]
if generator is None:
return "invalid generate request: unspecified data generator", 400
X, y = generator()
X = MinMaxScaler().fit_transform(X)
data = [
{"x": float(x[0]), "y": float(x[1]), "c": int(y)}
for x, y in zip(X, y)
]
return jsonify(data)
@app.route("/fit", methods=["POST"])
def fit():
# TODO: test content type and send 400 if not JSON
# Construct the model fit request
data = request.get_json()
params = data.get("model", {})
dataset = data.get("dataset", [])
grid = data.get("grid", [])
model = {
'gaussiannb': GaussianNB(),
'multinomialnb': MultinomialNB(),
'bernoullinb': BernoulliNB(),
'complementnb': ComplementNB(),
'svm': SVC(),
'logit': LogisticRegression(),
}.get(params.pop("model", None), None)
# Validate the request is correct and sane
if model is None or len(dataset) == 0:
return "invalid fit request: please specify model and data", 400
# Parse the JSON hyperparameters or leave as string for type detection
for key in params.keys():
try:
params[key] = json.loads(params[key])
except json.decoder.JSONDecodeError:
continue
# Set the hyperparameters on the model
try:
model.set_params(**params)
except ValueError as e:
return str(e), 400
# Construct the dataset
X, y = [], []
for point in dataset:
X.append([point["x"], point["y"]])
y.append(point["c"])
X, y = asarray(X), asarray(y)
# Fit the model to the dataset and get the training score
model.fit(X, y)
yhat = model.predict(X)
metrics = prfs(y, yhat, average="macro")
# Make probability predictions on the grid to implement contours
# The returned value is the class index + the probability
# To get the selected class in JavaScript, use Math.floor(p)
# Where p is the probability returned by the grid. Note that this
# method guarantees that no P(c) == 1 to prevent class misidentification
Xp = asarray([
[point["x"], point["y"]] for point in grid
])
preds = []
for proba in model.predict_proba(Xp):
c = np.argmax(proba)
preds.append(float(c + proba[c]) - 0.000001)
return jsonify({
"metrics": dict(zip(["precision", "recall", "f1", "support"], metrics)),
"grid": preds,
})
@app.route("/status", methods=["GET"])
def status():
return jsonify({
"status": "ok",
"version": version,
"uptime": (datetime.now() - started).total_seconds(),
})
##########################################################################
## Run the Web App
##########################################################################
if __name__ == "__main__":
app.run()