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:
Nicholas King 2020-10-16 15:09:25 -07:00 коммит произвёл Xavier Fernandes
Родитель 4b878b69a7
Коммит af18242748
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 1B011D38C073A7F2
23 изменённых файлов: 13743 добавлений и 109 удалений

2
.flake8 Normal file
Просмотреть файл

@ -0,0 +1,2 @@
[flake8]
max-line-length = 160

3
.flaskenv Normal file
Просмотреть файл

@ -0,0 +1,3 @@
FLASK_APP=development/app.py
FLASK_ENV=development
FLASK_RUN_PORT=5000

1
.gitignore поставляемый
Просмотреть файл

@ -123,6 +123,7 @@ tests/sweeps/*state
node_modules/
backwardcompatibilityml/widget/resources/widget-build.js
backwardcompatibilityml/widget/resources/widget.css
package-lock.json

15
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -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"
}
]
}

Просмотреть файл

@ -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

89
development/app.py Normal file
Просмотреть файл

@ -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')

19
development/css/bootstrap-tour.min.css поставляемый Normal file
Просмотреть файл

@ -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
*/

7
development/css/jquery-ui.min.css поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

1
development/css/jquery.typeahead.min.css поставляемый Normal 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;
}

13011
development/css/style.min.css поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

41
development/index.html Normal file
Просмотреть файл

@ -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>

Просмотреть файл

@ -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));

Просмотреть файл