Decouple dev workflow from Jupyter, create hot debugging environment (#25)
* 2 build targets for webpack - jupyter and dev * First revision of Flask dev server hacked together * Refactored code to maximize reuse. Changed webpack dev build to export a separate artifact to widget/resources directory instead of dist/ * Remove widget-build-dev from checkin, add to gitignore * Setup webpack-dev-server and React hot loader. 'npm start' runs the web server. Debugger setup for VSCode. Integrated front end and backend. * Code cleanup - remove unnecessary files, move .flaskenv to project root, cleanup linter errors * widget.css has to be exported for Jupyter notebook - fix webpack * Refactored compatibility_analysis.py and app.py to separate code more appropriately. Webpack now exports dev widget to dev-artifacts folder. Todo: Use FlaskHelper in dev mode, include Jupyter styles in dev widget build * Merge webpack configs into 1 config that changes dynamically. Fix React hot loading. Use HtmlWebpackPlugin to generate index.html from template, removing dev build artifacts on file system. Linting & simplification of python code. * Remove dev-artifacts folder from .gitignore * Mode dev harness code to development/ folder outside of the widget code. Update readme with dev harness usage instructions. Update release.sh with new build command. * Fix CompatibilityAnalysis import in app.py * Include all CSS from Jupyter notebook * Remove .dev-container CSS class * Move development artifacts to development/ directory * Change dev API port to 5000 * Revert a deletion in training.py, add info about non-localhost usage in Readme Co-authored-by: Nicholas King <v-nicki@microsoft.com>
This commit is contained in:
Родитель
4b878b69a7
Коммит
af18242748
|
@ -0,0 +1,2 @@
|
|||
[flake8]
|
||||
max-line-length = 160
|
|
@ -0,0 +1,3 @@
|
|||
FLASK_APP=development/app.py
|
||||
FLASK_ENV=development
|
||||
FLASK_RUN_PORT=5000
|
|
@ -123,6 +123,7 @@ tests/sweeps/*state
|
|||
node_modules/
|
||||
|
||||
backwardcompatibilityml/widget/resources/widget-build.js
|
||||
backwardcompatibilityml/widget/resources/widget.css
|
||||
|
||||
package-lock.json
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}/widget"
|
||||
}
|
||||
]
|
||||
}
|
14
README.md
14
README.md
|
@ -24,7 +24,7 @@ The Backward Compatibility ML project has two components:
|
|||
1. Setup a Python virtual environment or Conda environment and activate it.
|
||||
2. From within the root folder of this project do `pip install -r requirements.txt`
|
||||
3. From within the root folder do `npm install`
|
||||
4. From within the root folder of this project do `npx webpack && pip install -e .`
|
||||
4. From within the root folder of this project do `npm run build && pip install -e .` or `NODE_ENV=production npx webpack && pip install -e .`
|
||||
5. You should now be able to import the `backwardcompatibilityml` module and use it.
|
||||
|
||||
# Examples
|
||||
|
@ -41,6 +41,18 @@ To run tests, make sure that you are in the project root folder and do:
|
|||
1. `pip install -r dev-requirements.txt`
|
||||
2. `pytest tests/`
|
||||
|
||||
# Development environment
|
||||
|
||||
To run the widget and APIs in a development environment:
|
||||
- Open a new terminal, then from within the root folder do `npm start`. This will host the widget with `webpack-dev-server`.
|
||||
- To customize the host IP, run `webpack-dev-server --hot --mode development --host <ip>` instead of `npm start`.
|
||||
- `npm start` uses `0.0.0.0` which makes the server accessible externally.
|
||||
- Open a new terminal, then from within the root folder do `flask run`. This will start the Flask server for the APIs used by the widget.
|
||||
|
||||
The widget can be loaded in the web browser at `localhost:3000`. It will be loaded independently from a Jupyter notebook. The APIs will be hosted at `localhost:5000`. If you are running the server within a VM, you might need to use your machine's local IP instead of `localhost`.
|
||||
|
||||
Changes to the CSS or TypeScript code will be hot loaded automatically in the browser. Flask will run in debug mode and automatically restart whenever the Python code is changed.
|
||||
|
||||
# Contributing
|
||||
|
||||
Check [CONTRIBUTING](CONTRIBUTING.md) page.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class MLPClassifier(nn.Module):
|
||||
|
||||
def __init__(self, input_size, num_classes, hidden_sizes=[50, 10]):
|
||||
super(MLPClassifier, self).__init__()
|
||||
layer_sizes = [input_size] + hidden_sizes + [num_classes]
|
||||
self.layers = [nn.Linear(layer_sizes[i], layer_sizes[i + 1]) for i in range(len(layer_sizes) - 1)]
|
||||
|
||||
for i, layer in enumerate(self.layers):
|
||||
self.add_module("layer-%d" % i, layer)
|
||||
|
||||
def forward(self, data, sample_weight=None):
|
||||
x = data
|
||||
out = x
|
||||
num_layers = len(self.layers)
|
||||
|
||||
for i in range(num_layers):
|
||||
out = self.layers[i](out)
|
||||
if i < num_layers - 1:
|
||||
out = F.relu(out)
|
||||
|
||||
out_softmax = F.softmax(out, dim=-1)
|
||||
out_log_softmax = F.log_softmax(out, dim=-1)
|
||||
|
||||
return out, out_softmax, out_log_softmax
|
||||
|
||||
|
||||
class LogisticRegression(nn.Module):
|
||||
|
||||
def __init__(self, input_dim, output_dim):
|
||||
super(LogisticRegression, self).__init__()
|
||||
self.linear = nn.Linear(input_dim, output_dim)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.linear(x)
|
||||
out_softmax = F.softmax(out, dim=-1)
|
||||
out_log_softmax = F.log_softmax(out, dim=-1)
|
||||
|
||||
return out, out_softmax, out_log_softmax
|
||||
|
|
@ -57,6 +57,72 @@ def build_environment_params(flask_service_env):
|
|||
}
|
||||
|
||||
|
||||
def render_widget_html(api_service_environment):
|
||||
"""
|
||||
Renders the HTML for the compatibility analysis widget.
|
||||
|
||||
Args:
|
||||
api_service_environment: A dictionary of the environment
|
||||
type, the base URL, and the port for the Flask service.
|
||||
|
||||
Returns:
|
||||
The widget HTML rendered as a string.
|
||||
"""
|
||||
|
||||
resource_package = __name__
|
||||
javascript_path = '/'.join(('resources', 'widget-build.js'))
|
||||
css_path = '/'.join(('resources', 'widget.css'))
|
||||
html_template_path = '/'.join(('resources', 'widget.html'))
|
||||
widget_javascript = pkg_resources.resource_string(
|
||||
resource_package, javascript_path).decode("utf-8")
|
||||
widget_css = pkg_resources.resource_string(
|
||||
resource_package, css_path).decode("utf-8")
|
||||
app_html_template_string = pkg_resources.resource_string(
|
||||
resource_package, html_template_path).decode("utf-8")
|
||||
|
||||
app_html_template = Template(app_html_template_string)
|
||||
return app_html_template.render(
|
||||
widget_css=widget_css,
|
||||
widget_javascript=widget_javascript,
|
||||
api_service_environment=json.dumps(api_service_environment),
|
||||
data=json.dumps(None))
|
||||
|
||||
|
||||
def init_app_routes(app, sweep_manager):
|
||||
"""
|
||||
Defines the API for the Flask app.
|
||||
|
||||
Args:
|
||||
app: The Flask app to use for the API.
|
||||
sweep_manager: The SweepManager that will be controlled by the API.
|
||||
"""
|
||||
|
||||
@app.route("/api/v1/start_sweep", methods=["POST"])
|
||||
def start_sweep():
|
||||
sweep_manager.start_sweep()
|
||||
return {
|
||||
"running": sweep_manager.sweep_thread.is_alive(),
|
||||
"percent_complete": sweep_manager.get_sweep_status()
|
||||
}
|
||||
|
||||
@app.route("/api/v1/sweep_status", methods=["GET"])
|
||||
def get_sweep_status():
|
||||
return {
|
||||
"running": sweep_manager.sweep_thread.is_alive(),
|
||||
"percent_complete": sweep_manager.get_sweep_status()
|
||||
}
|
||||
|
||||
@app.route("/api/v1/sweep_summary", methods=["GET"])
|
||||
def get_data():
|
||||
return Response(
|
||||
json.dumps(sweep_manager.get_sweep_summary()),
|
||||
mimetype="application/json")
|
||||
|
||||
@app.route("/api/v1/evaluation_data/<int:evaluation_id>")
|
||||
def get_evaluation(evaluation_id):
|
||||
return sweep_manager.get_evaluation(evaluation_id)
|
||||
|
||||
|
||||
class CompatibilityAnalysis(object):
|
||||
"""
|
||||
The CompatibilityAnalysis class is an interactive widget intended for use
|
||||
|
@ -118,8 +184,6 @@ class CompatibilityAnalysis(object):
|
|||
NewErrorLossClass=None, StrictImitationLossClass=None,
|
||||
port=None, new_error_loss_kwargs=None,
|
||||
strict_imitation_loss_kwargs=None, device="cpu"):
|
||||
self.flask_service = FlaskHelper(ip="0.0.0.0", port=port)
|
||||
|
||||
if OptimizerClass is None:
|
||||
OptimizerClass = optim.SGD
|
||||
|
||||
|
@ -149,51 +213,10 @@ class CompatibilityAnalysis(object):
|
|||
strict_imitation_loss_kwargs=strict_imitation_loss_kwargs,
|
||||
device=device)
|
||||
|
||||
resource_package = __name__
|
||||
javascript_path = '/'.join(('resources', 'widget-build.js'))
|
||||
css_path = '/'.join(('resources', 'widget.css'))
|
||||
html_template_path = '/'.join(('resources', 'widget.html'))
|
||||
widget_javascript = pkg_resources.resource_string(
|
||||
resource_package, javascript_path).decode("utf-8")
|
||||
widget_css = pkg_resources.resource_string(
|
||||
resource_package, css_path).decode("utf-8")
|
||||
app_html_template_string = pkg_resources.resource_string(
|
||||
resource_package, html_template_path).decode("utf-8")
|
||||
|
||||
self.flask_service = FlaskHelper(ip="0.0.0.0", port=port)
|
||||
init_app_routes(FlaskHelper.app, self.sweep_manager)
|
||||
api_service_environment = build_environment_params(self.flask_service.env)
|
||||
api_service_environment["port"] = self.flask_service.port
|
||||
|
||||
app_html_template = Template(app_html_template_string)
|
||||
html_string = app_html_template.render(
|
||||
widget_css=widget_css,
|
||||
widget_javascript=widget_javascript,
|
||||
api_service_environment=json.dumps(api_service_environment),
|
||||
data=json.dumps(None))
|
||||
|
||||
html_string = render_widget_html(api_service_environment)
|
||||
self.html_widget = HTML(html_string)
|
||||
display(self.html_widget)
|
||||
|
||||
@FlaskHelper.app.route("/api/v1/start_sweep", methods=["POST"])
|
||||
def start_sweep():
|
||||
self.sweep_manager.start_sweep()
|
||||
return {
|
||||
"running": self.sweep_manager.sweep_thread.is_alive(),
|
||||
"percent_complete": self.sweep_manager.get_sweep_status()
|
||||
}
|
||||
|
||||
@FlaskHelper.app.route("/api/v1/sweep_status", methods=["GET"])
|
||||
def get_sweep_status():
|
||||
return {
|
||||
"running": self.sweep_manager.sweep_thread.is_alive(),
|
||||
"percent_complete": self.sweep_manager.get_sweep_status()
|
||||
}
|
||||
|
||||
@FlaskHelper.app.route("/api/v1/sweep_summary", methods=["GET"])
|
||||
def get_data():
|
||||
return Response(
|
||||
json.dumps(self.sweep_manager.get_sweep_summary()),
|
||||
mimetype="application/json")
|
||||
|
||||
@FlaskHelper.app.route("/api/v1/evaluation_data/<int:evaluation_id>")
|
||||
def get_evaluation(evaluation_id):
|
||||
return self.sweep_manager.get_evaluation(evaluation_id)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pytest==5.0.1
|
||||
|
||||
python-dotenv==0.14.0
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
# Copyright (c) Microsoft Corporation
|
||||
# Licensed under the MIT License.
|
||||
|
||||
import torch
|
||||
import torch.optim as optim
|
||||
import torch.nn.functional as F
|
||||
import random
|
||||
from backwardcompatibilityml import loss as bcloss
|
||||
from backwardcompatibilityml.helpers import training
|
||||
from backwardcompatibilityml.helpers.models import LogisticRegression, MLPClassifier
|
||||
from backwardcompatibilityml.widget.compatibility_analysis import CompatibilityAnalysis
|
||||
from rai_core_flask.flask_helper import FlaskHelper
|
||||
|
||||
folder_name = "tests/sweeps"
|
||||
number_of_epochs = 10
|
||||
batch_size_train = 70
|
||||
batch_size_test = 139
|
||||
learning_rate = 0.01
|
||||
momentum = 0.5
|
||||
|
||||
dataset_file = open("tests/datasets/breast-cancer-wisconsin.data", "r")
|
||||
raw_data = dataset_file.read()
|
||||
dataset_file.close()
|
||||
data = list(map(lambda l: l.split(",")[1:], filter(lambda l: len(l) > 0, map(lambda l: l.strip(), raw_data.split("\n")))))
|
||||
for i in range(len(data)):
|
||||
row = data[i]
|
||||
row = list(map(lambda e: 0 if (e == "?") else int(e), row))
|
||||
if row[9] == 2:
|
||||
row[9] = 0
|
||||
elif row[9] == 4:
|
||||
row[9] = 1
|
||||
data[i] = row
|
||||
|
||||
data = list(map(lambda r: (torch.tensor(r[:9], dtype=torch.float32), torch.tensor(r[9])), data))
|
||||
|
||||
random.shuffle(data)
|
||||
|
||||
training_set = data[:560]
|
||||
testing_set = data[560:]
|
||||
|
||||
training_set_torch = []
|
||||
prev = 0
|
||||
for i in range((batch_size_train - 1), len(training_set), batch_size_train):
|
||||
training_data = list(map(lambda r: r[0], training_set[prev:(i + 1)]))
|
||||
training_labels = list(map(lambda r: r[1], training_set[prev:(i + 1)]))
|
||||
prev = i
|
||||
training_set_torch.append([torch.stack(training_data, dim=0), torch.stack(training_labels, dim=0)])
|
||||
|
||||
testing_set_torch = []
|
||||
prev = 0
|
||||
for i in range((batch_size_test - 1), len(testing_set), batch_size_test):
|
||||
testing_data = list(map(lambda r: r[0], testing_set[prev:(i + 1)]))
|
||||
testing_labels = list(map(lambda r: r[1], testing_set[prev:(i + 1)]))
|
||||
prev = i
|
||||
testing_set_torch.append([torch.stack(testing_data, dim=0), torch.stack(testing_labels, dim=0)])
|
||||
|
||||
training_set = training_set_torch
|
||||
partial_training_set = training_set[:int(0.5 * (len(training_set)))]
|
||||
testing_set = testing_set_torch
|
||||
|
||||
h1 = LogisticRegression(9, 2)
|
||||
optimizer = optim.SGD(h1.parameters(), lr=learning_rate, momentum=momentum)
|
||||
train_counter, test_counter, train_losses, test_losses = training.train(
|
||||
number_of_epochs, h1, optimizer, F.nll_loss, partial_training_set, testing_set,
|
||||
batch_size_train, batch_size_test)
|
||||
h1.eval()
|
||||
|
||||
with torch.no_grad():
|
||||
_, _, output = h1(testing_set[0][0])
|
||||
|
||||
h2 = MLPClassifier(9, 2)
|
||||
|
||||
analysis = CompatibilityAnalysis(
|
||||
folder_name,
|
||||
number_of_epochs,
|
||||
h1,
|
||||
h2,
|
||||
training_set,
|
||||
testing_set,
|
||||
batch_size_train,
|
||||
batch_size_test,
|
||||
lambda_c_stepsize=0.05,
|
||||
OptimizerClass=optim.SGD,
|
||||
optimizer_kwargs={"lr": learning_rate, "momentum": momentum},
|
||||
NewErrorLossClass=bcloss.BCCrossEntropyLoss,
|
||||
StrictImitationLossClass=bcloss.StrictImitationCrossEntropyLoss)
|
||||
|
||||
app = FlaskHelper.app
|
||||
app.logger.info('initialization complete')
|
|
@ -0,0 +1,19 @@
|
|||
/* ===========================================================
|
||||
# bootstrap-tour - v0.9.0
|
||||
# http://bootstraptour.com
|
||||
# ==============================================================
|
||||
# Copyright 2012-2013 Ulrich Sossou
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
*/
|
||||
.tour-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1100;background-color:#000;opacity:.8}.tour-step-backdrop{position:relative;z-index:1101;background:inherit}.tour-step-background{position:absolute;z-index:1100;background:inherit;border-radius:6px}.popover[class*=tour-]{z-index:1100}.popover[class*=tour-] .popover-navigation{padding:9px 14px}.popover[class*=tour-] .popover-navigation [data-role=end]{float:right}.popover[class*=tour-] .popover-navigation [data-role=prev],.popover[class*=tour-] .popover-navigation [data-role=next],.popover[class*=tour-] .popover-navigation [data-role=end]{cursor:pointer}.popover[class*=tour-] .popover-navigation [data-role=prev].disabled,.popover[class*=tour-] .popover-navigation [data-role=next].disabled,.popover[class*=tour-] .popover-navigation [data-role=end].disabled{cursor:default}.popover[class*=tour-].orphan{position:fixed;margin-top:0}.popover[class*=tour-].orphan .arrow{display:none}
|
|
@ -0,0 +1,349 @@
|
|||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px; margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre.CodeMirror-line,
|
||||
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
Placeholder for custom user CSS
|
||||
|
||||
mainly to be overridden in profile/static/custom/custom.css
|
||||
|
||||
This will always be an empty file
|
||||
*/
|
||||
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,7 @@
|
|||
/*This file contains any manual css for this page that needs to override the global styles.
|
||||
This is only required when different pages style the same element differently. This is just
|
||||
a hack to deal with our current css styles and no new styling should be added in this file.*/
|
||||
|
||||
#ipython-main-app {
|
||||
position: relative;
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,41 @@
|
|||
<!--
|
||||
Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License.
|
||||
-->
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="css/bootstrap-tour.min.css">
|
||||
<link rel="stylesheet" href="css/codemirror.css">
|
||||
<link rel="stylesheet" href="css/custom.css">
|
||||
<link rel="stylesheet" href="css/jquery.typeahead.min.css">
|
||||
<link rel="stylesheet" href="css/jquery-ui.min.css">
|
||||
<link rel="stylesheet" href="css/override.css">
|
||||
<link rel="stylesheet" href="css/style.min.css">
|
||||
<title>BCML widget</title>
|
||||
</head>
|
||||
|
||||
<div class="container">
|
||||
<div id="widget">
|
||||
</div>
|
||||
<script>
|
||||
var data = null;
|
||||
window.API_SERVICE_ENVIRONMENT = {environment_type: "local", base_url: "", port: 5000};
|
||||
window.WIDGET_STATE = {
|
||||
data: data,
|
||||
sweepStatus: null,
|
||||
selectedDataPoint: null,
|
||||
training: true,
|
||||
testing: true,
|
||||
newError: true,
|
||||
strictImitation: true,
|
||||
error: null,
|
||||
loading: false
|
||||
};
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</html>
|
13
package.json
13
package.json
|
@ -18,6 +18,7 @@
|
|||
"@babel/core": "^7.2.2",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.2",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
|
@ -28,11 +29,21 @@
|
|||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^4.3.0",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"mini-css-extract-plugin": "^0.12.0",
|
||||
"react-refresh": "^0.8.3",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"ts-loader": "^8.0.1",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.2.1",
|
||||
"webpack-dev-server": "^3.1.14"
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --hot --mode development --host 0.0.0.0",
|
||||
"build": "cross-env NODE_ENV=production webpack"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# Build the Typescript app and place the javascript
|
||||
# build artefact in a plae where the Python package
|
||||
# expects it.
|
||||
npx webpack
|
||||
NODE_ENV=production npx webpack
|
||||
|
||||
# Install the Python dependencies and the package itself
|
||||
python -m pip install -r requirements.txt
|
||||
|
|
|
@ -13,46 +13,7 @@ from sklearn.metrics import accuracy_score
|
|||
import random
|
||||
from backwardcompatibilityml import loss as bcloss
|
||||
from backwardcompatibilityml.helpers import training
|
||||
|
||||
|
||||
class MLPClassifier(nn.Module):
|
||||
|
||||
def __init__(self, input_size, num_classes, hidden_sizes=[50, 10]):
|
||||
super(MLPClassifier, self).__init__()
|
||||
layer_sizes = [input_size] + hidden_sizes + [num_classes]
|
||||
self.layers = [nn.Linear(layer_sizes[i], layer_sizes[i + 1]) for i in range(len(layer_sizes) - 1)]
|
||||
|
||||
for i, layer in enumerate(self.layers):
|
||||
self.add_module("layer-%d" % i, layer)
|
||||
|
||||
def forward(self, data, sample_weight=None):
|
||||
x = data
|
||||
out = x
|
||||
num_layers = len(self.layers)
|
||||
|
||||
for i in range(num_layers):
|
||||
out = self.layers[i](out)
|
||||
if i < num_layers - 1:
|
||||
out = F.relu(out)
|
||||
|
||||
out_softmax = F.softmax(out, dim=-1)
|
||||
out_log_softmax = F.log_softmax(out, dim=-1)
|
||||
|
||||
return out, out_softmax, out_log_softmax
|
||||
|
||||
|
||||
class LogisticRegression(nn.Module):
|
||||
|
||||
def __init__(self, input_dim, output_dim):
|
||||
super(LogisticRegression, self).__init__()
|
||||
self.linear = nn.Linear(input_dim, output_dim)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.linear(x)
|
||||
out_softmax = F.softmax(out, dim=-1)
|
||||
out_log_softmax = F.log_softmax(out, dim=-1)
|
||||
|
||||
return out, out_softmax, out_log_softmax
|
||||
from backwardcompatibilityml.helpers.models import LogisticRegression, MLPClassifier
|
||||
|
||||
|
||||
class TestLossFunctions(object):
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
var webpack = require('webpack');
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
|
||||
module.exports = {
|
||||
mode: isDevelopment ? 'development' : 'production',
|
||||
entry: ['./widget/index.tsx'],
|
||||
devtool: 'eval-source-map',
|
||||
performance: {
|
||||
|
@ -11,33 +18,56 @@ module.exports = {
|
|||
new webpack.ProvidePlugin({
|
||||
$: "jquery",
|
||||
jQuery: "jquery"
|
||||
})
|
||||
],
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'widget.css'
|
||||
}),
|
||||
isDevelopment && new HtmlWebpackPlugin({
|
||||
template: './development/index.html'
|
||||
}),
|
||||
isDevelopment && new ReactRefreshPlugin()
|
||||
].filter(Boolean),
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts(x?)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "ts-loader"
|
||||
}
|
||||
]
|
||||
},
|
||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||
{
|
||||
enforce: "pre",
|
||||
test: /\.js$/,
|
||||
loader: "source-map-loader"
|
||||
}
|
||||
{
|
||||
test: /\.ts(x?)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
isDevelopment && {
|
||||
loader: 'babel-loader',
|
||||
options: { plugins: ['react-refresh/babel'] }
|
||||
},
|
||||
{
|
||||
loader: "ts-loader"
|
||||
}
|
||||
].filter(Boolean)
|
||||
},
|
||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||
{
|
||||
enforce: "pre",
|
||||
test: /\.js$/,
|
||||
loader: "source-map-loader"
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
isDevelopment && 'style-loader',
|
||||
!isDevelopment && MiniCssExtractPlugin.loader,
|
||||
'css-loader'
|
||||
].filter(Boolean)
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.jsx', '*.ts', '*.tsx']
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/backwardcompatibilityml/widget/resources',
|
||||
path: path.resolve(__dirname, 'backwardcompatibilityml/widget/resources'),
|
||||
publicPath: '/',
|
||||
filename: 'widget-build.js'
|
||||
},
|
||||
devServer: {
|
||||
contentBase: path.resolve(__dirname, "development"),
|
||||
port: 3000
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import rootReducer from './reducers.ts'
|
|||
import MainContainer from "./MainContainer.tsx";
|
||||
import { createLogger } from 'redux-logger';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import "./widget.css";
|
||||
|
||||
const loggerMiddleware = createLogger()
|
||||
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware, loggerMiddleware));
|
||||
|
|
Загрузка…
Ссылка в новой задаче