diff --git a/bindings/pyroot/CMakeLists.txt b/bindings/pyroot/CMakeLists.txt index 85df6f97cf0b2..ba30ce20a8c44 100644 --- a/bindings/pyroot/CMakeLists.txt +++ b/bindings/pyroot/CMakeLists.txt @@ -45,5 +45,9 @@ set( JupyROOTDirName "JupyROOT") install (DIRECTORY ${JupyROOTDirName} DESTINATION ${runtimedir}) file(COPY ${JupyROOTDirName} DESTINATION ${CMAKE_BINARY_DIR}/${runtimedir}) +set( JsMVADirName "JsMVA") +install (DIRECTORY ${JsMVADirName} DESTINATION ${runtimedir}) +file(COPY ${JsMVADirName} DESTINATION ${CMAKE_BINARY_DIR}/${runtimedir}) + #---Install headers---------------------------------------------------------- ROOT_INSTALL_HEADERS() diff --git a/bindings/pyroot/JsMVA/DataLoader.py b/bindings/pyroot/JsMVA/DataLoader.py new file mode 100644 index 0000000000000..4f5d07ed51693 --- /dev/null +++ b/bindings/pyroot/JsMVA/DataLoader.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +## @package JsMVA.DataLoader +# @author Attila Bagoly +# DataLoader module with the functions to be inserted to TMVA::DataLoader class and helper functions + + +from ROOT import TH1F, TMVA, TBufferJSON +import JPyInterface +import ROOT + + +## Creates the input variable histogram and perform the transformations if necessary +# @param dl DataLoader object +# @param className string Signal/Background +# @param variableName string containing the variable name +# @param numBin for creating the histogram +# @param processTrfs string containing the list of transformations to be used on input variable; eg. "I;N;D;P;U;G,D" +def GetInputVariableHist(dl, className, variableName, numBin, processTrfs=""): + dsinfo = dl.GetDefaultDataSetInfo() + vi = 0 + ivar = 0 + for i in range(dsinfo.GetNVariables()): + if dsinfo.GetVariableInfo(i).GetLabel()==variableName: + vi = dsinfo.GetVariableInfo(i) + ivar = i + break + if vi==0: + return 0 + + h = TH1F(className, str(vi.GetExpression()) + " ("+className+")", numBin, vi.GetMin(), vi.GetMax()) + + clsn = dsinfo.GetClassInfo(className).GetNumber() + ds = dsinfo.GetDataSet() + + trfsDef = processTrfs.split(';') + trfs = [] + for trfDef in trfsDef: + trfs.append(TMVA.TransformationHandler(dsinfo, "DataLoader")) + TMVA.CreateVariableTransforms( trfDef, dsinfo, trfs[-1], dl.Log()) + + inputEvents = ds.GetEventCollection() + transformed = 0 + tmp = 0 + for trf in trfs: + if transformed==0: + transformed = trf.CalcTransformations(inputEvents, 1) + else: + tmp = trf.CalcTransformations(transformed, 1) + transformed = tmp + + if transformed!=0: + for event in transformed: + if event.GetClass() != clsn: + continue + h.Fill(event.GetValue(ivar)) + else: + for event in inputEvents: + if event.GetClass() != clsn: + continue + h.Fill(event.GetValue(ivar)) + return (h) + + +## Get correlation matrix in JSON format +# This function is used by OutputTransformer +# @param dl the object pointer +# @param className Signal/Background +def GetCorrelationMatrixInJSON(className, varNames, matrix): + m = ROOT.TMatrixD(len(matrix), len(matrix)) + for i in xrange(len(matrix)): + for j in xrange(len(matrix)): + m[i][j] = matrix[i][j] + th2 = ROOT.TH2D(m) + th2.SetTitle("Correlation matrix ("+className+")") + for i in xrange(len(varNames)): + th2.GetXaxis().SetBinLabel(i+1, varNames[i]) + th2.GetYaxis().SetBinLabel(i+1, varNames[i]) + th2.Scale(100.0) + for i in xrange(len(matrix)): + for j in xrange(len(matrix)): + th2.SetBinContent(i+1, j+1, int(th2.GetBinContent(i+1, j+1))) + th2.SetStats(0) + th2.SetMarkerSize(1.5) + th2.SetMarkerColor(0) + labelSize = 0.040 + th2.GetXaxis().SetLabelSize(labelSize) + th2.GetYaxis().SetLabelSize(labelSize) + th2.LabelsOption("d") + th2.SetLabelOffset(0.011) + th2.SetMinimum(-100.0) + th2.SetMaximum(+100.0) + dat = TBufferJSON.ConvertToJSON(th2) + return str(dat).replace("\n", "") + +## Draw correlation matrix +# This function uses the TMVA::DataLoader::GetCorrelationMatrix function added newly to root +# @param dl the object pointer +# @param className Signal/Background +def DrawCorrelationMatrix(dl, className): + th2 = dl.GetCorrelationMatrix(className) + th2.SetMarkerSize(1.5) + th2.SetMarkerColor(0) + labelSize = 0.040 + th2.GetXaxis().SetLabelSize(labelSize) + th2.GetYaxis().SetLabelSize(labelSize) + th2.LabelsOption("d") + th2.SetLabelOffset(0.011) + JPyInterface.JsDraw.Draw(th2, 'drawTH2') + +## Draw input variables +# This function uses the previously defined GetInputVariableHist function to create the histograms +# @param dl The object pointer +# @param variableName string containing the variable name +# @param numBin for creating the histogram +# @param processTrfs list of transformations to be used on input variable; eg. ["I", "N", "D", "P", "U", "G"]" +def DrawInputVariable(dl, variableName, numBin=100, processTrfs=[]): + processTrfsSTR = "" + if len(processTrfs)>0: + for o in processTrfs: + processTrfsSTR += str(o) + ";" + processTrfsSTR = processTrfsSTR[:-1] + sig = GetInputVariableHist(dl, "Signal", variableName, numBin, processTrfsSTR) + bkg = GetInputVariableHist(dl, "Background", variableName, numBin, processTrfsSTR) + c, l = JPyInterface.JsDraw.sbPlot(sig, bkg, {"xaxis": sig.GetTitle(), + "yaxis": "Number of events", + "plot": "Input variable: "+sig.GetTitle()}) + JPyInterface.JsDraw.Draw(c) + +## Rewrite TMVA::DataLoader::PrepareTrainingAndTestTree +# @param *args positional parameters +# @param **kwargs named parameters: this will be transformed to option string +def ChangeCallOriginalPrepareTrainingAndTestTree(*args, **kwargs): + if len(kwargs)==0: + originalFunction, args = JPyInterface.functions.ProcessParameters(0, *args, **kwargs) + return originalFunction(*args) + try: + args, kwargs = JPyInterface.functions.ConvertSpecKwargsToArgs(["SigCut", "BkgCut"], *args, **kwargs) + except AttributeError: + try: + args, kwargs = JPyInterface.functions.ConvertSpecKwargsToArgs(["Cut"], *args, **kwargs) + except AttributeError: + raise AttributeError + originalFunction, args = JPyInterface.functions.ProcessParameters(3, *args, **kwargs) + return originalFunction(*args) diff --git a/bindings/pyroot/JsMVA/Factory.py b/bindings/pyroot/JsMVA/Factory.py new file mode 100644 index 0000000000000..efe121531cf5d --- /dev/null +++ b/bindings/pyroot/JsMVA/Factory.py @@ -0,0 +1,771 @@ +# -*- coding: utf-8 -*- +## @package JsMVA.Factory +# @author Attila Bagoly +# Factory module with the functions to be inserted to TMVA::Factory class and helper functions and classes + + +import ROOT +from ROOT import TMVA +import JPyInterface +from xml.etree.ElementTree import ElementTree +import json +from IPython.core.display import display, HTML, clear_output +from ipywidgets import widgets +from threading import Thread +import time +from string import Template + + +# This class contains the necessary HTML, JavaScript, CSS codes (templates) +# for the new Factory methods. Some parts of these variables will be replaced and the new string will be the cell output. +class __HTMLJSCSSTemplates: + # stop button + button = """ + + + + """ + # progress bar + inc = Template(""" + + """) + progress_bar = Template(""" + +
+
+
0%
+
+
+ """) + + +## Getting method object from factory +# @param fac the TMVA::Factory object +# @param datasetName selecting the dataset +# @param methodName which method we want to get +def GetMethodObject(fac, datasetName, methodName): + method = [] + for methodMapElement in fac.fMethodsMap: + if methodMapElement[0] != datasetName: + continue + methods = methodMapElement[1] + for m in methods: + m.GetName._threaded = True + if m.GetName() == methodName: + method.append( m ) + break + if len(method) != 1: + print("Factory.GetMethodObject: no method object found") + return None + return (method[0]) + +## Reads deep neural network weights from file and returns it in JSON format +# @param xml_file path to DNN weight file +# @param returnObj if Fakse it will return a JSON string, if True it will return the JSON object itself +def GetDeepNetwork(xml_file, returnObj=False): + tree = ElementTree() + tree.parse(xml_file) + roottree = tree.getroot() + network = {} + network["variables"] = [] + for v in roottree.find("Variables"): + network["variables"].append(v.get('Title')) + layout = roottree.find("Weights").find("Layout") + net = [] + for layer in layout: + net.append({"Connection": layer.get("Connection"), + "Nodes": layer.get("Nodes"), + "ActivationFunction": layer.get("ActivationFunction"), + "OutputMode": layer.get("OutputMode") + }) + network["layers"] = net + Synapses = roottree.find("Weights").find("Synapses") + synapses = { + "InputSize": Synapses.get("InputSize"), + "OutputSize": Synapses.get("OutputSize"), + "NumberSynapses": Synapses.get("NumberSynapses"), + "synapses": [] + } + for i in Synapses.text.split(" "): + tmp = i.replace("\n", "") + if len(tmp)>1: + synapses["synapses"].append(tmp) + network["synapses"] = synapses + if returnObj: + return network + return json.dumps(network) + +## Reads neural network weights from file and returns it in JSON format +# @param xml_file path to weight file +def GetNetwork(xml_file): + tree = ElementTree() + tree.parse(xml_file) + roottree = tree.getroot() + network = {} + network["variables"] = [] + for v in roottree.find("Variables"): + network["variables"].append(v.get('Title')) + layout = roottree.find("Weights").find("Layout") + + net = { "nlayers": layout.get("NLayers") } + for layer in layout: + neuron_num = int(layer.get("NNeurons")) + neurons = { "nneurons": neuron_num } + i = 0 + for neuron in layer: + label = "neuron_"+str(i) + i += 1 + nsynapses = int(neuron.get('NSynapses')) + neurons[label] = {"nsynapses": nsynapses} + if nsynapses == 0: + break + text = str(neuron.text) + wall = text.replace("\n","").split(" ") + weights = [] + for w in wall: + if w!="": + weights.append(float(w)) + neurons[label]["weights"] = weights + net["layer_"+str(layer.get('Index'))] = neurons + network["layout"] = net + return json.dumps(network) + +## Helper class for reading decision tree from XML file +class TreeReader: + + ## Standard Constructor + # @param self object pointer + # @oaran fileName path to XML file + def __init__(self, fileName): + self.__xmltree = ElementTree() + self.__xmltree.parse(fileName) + self.__NTrees = int(self.__xmltree.find("Weights").get('NTrees')) + + ## Returns the number of trees + # @param self object pointer + def getNTrees(self): + return (self.__NTrees) + + # Returns DOM object to selected tree + # @param self object pointer + # @param itree the index of tree + def __getBinaryTree(self, itree): + if self.__NTrees<=itree: + print( "to big number, tree number must be less then %s"%self.__NTrees ) + return 0 + return self.__xmltree.find("Weights").find("BinaryTree["+str(itree+1)+"]") + + ## Reads the tree + # @param self the object pointer + # @param binaryTree the tree DOM object to be read + # @param tree empty object, this will be filled + # @param depth current depth + def __readTree(self, binaryTree, tree={}, depth=0): + nodes = binaryTree.findall("Node") + if len(nodes)==0: + return + if len(nodes)==1 and nodes[0].get("pos")=="s": + info = { + "IVar": nodes[0].get("IVar"), + "Cut" : nodes[0].get("Cut"), + "purity": nodes[0].get("purity"), + "pos": 0 + } + tree["info"] = info + tree["children"] = [] + self.__readTree(nodes[0], tree, 1) + return + for node in nodes: + info = { + "IVar": node.get("IVar"), + "Cut" : node.get("Cut"), + "purity": node.get("purity"), + "pos": node.get("pos") + } + tree["children"].append({ + "info": info, + "children": [] + }) + self.__readTree(node, tree["children"][-1], depth+1) + + ## Public function which returns the specified tree object + # @param self the object pointer + # @param itree selected tree index + def getTree(self, itree): + binaryTree = self.__getBinaryTree(itree) + if binaryTree==0: + return {} + tree = {} + self.__readTree(binaryTree, tree) + return tree + + ## Returns a list with input variable names + # @param self the object pointer + def getVariables(self): + varstree = self.__xmltree.find("Variables").findall("Variable") + variables = [None]*len(varstree) + for v in varstree: + variables[int(v.get('VarIndex'))] = v.get('Expression') + return variables + + +## Draw ROC curve +# @param fac the object pointer +# @param datasetName the dataset name +def DrawROCCurve(fac, datasetName): + canvas = fac.GetROCCurve(datasetName) + JPyInterface.JsDraw.Draw(canvas) + +## Draw output distributions +# @param fac the object pointer +# @param datasetName the dataset name +# @param methodName we want to see the output distribution of this method +def DrawOutputDistribution(fac, datasetName, methodName): + method = GetMethodObject(fac, datasetName, methodName) + if method==None: + return None + mvaRes = method.Data().GetResults(method.GetMethodName(), TMVA.Types.kTesting, TMVA.Types.kMaxAnalysisType) + sig = mvaRes.GetHist("MVA_S") + bgd = mvaRes.GetHist("MVA_B") + c, l = JPyInterface.JsDraw.sbPlot(sig, bgd, {"xaxis": methodName+" response", + "yaxis": "(1/N) dN^{ }/^{ }dx", + "plot": "TMVA response for classifier: "+methodName}) + JPyInterface.JsDraw.Draw(c) + +## Draw output probability distribution +# @param fac the object pointer +# @param datasetName the dataset name +# @param methodName we want to see the output probability distribution of this method +def DrawProbabilityDistribution(fac, datasetName, methodName): + method = GetMethodObject(fac, datasetName, methodName) + if method==0: + return + mvaRes = method.Data().GetResults(method.GetMethodName(), TMVA.Types.kTesting, TMVA.Types.kMaxAnalysisType) + sig = mvaRes.GetHist("Prob_S") + bgd = mvaRes.GetHist("Prob_B") #Rar_S + c, l = JPyInterface.JsDraw.sbPlot(sig, bgd, {"xaxis": "Signal probability", + "yaxis": "(1/N) dN^{ }/^{ }dx", + "plot": "TMVA probability for classifier: "+methodName}) + JPyInterface.JsDraw.Draw(c) + +## Draw cut efficiencies +# @param fac the object pointer +# @param datasetName the dataset name +# @param methodName we want to see the cut efficiencies of this method +def DrawCutEfficiencies(fac, datasetName, methodName): + #reading histograms + method = GetMethodObject(fac, datasetName, methodName) + if method==0: + return + mvaRes = method.Data().GetResults(method.GetMethodName(), TMVA.Types.kTesting, TMVA.Types.kMaxAnalysisType) + sigE = mvaRes.GetHist("MVA_EFF_S") + bgdE = mvaRes.GetHist("MVA_EFF_B") + + fNSignal = 1000 + fNBackground = 1000 + + f = ROOT.TFormula("sigf", "x/sqrt(x+y)") + + pname = "purS_" + methodName + epname = "effpurS_" + methodName + ssigname = "significance_" + methodName + + nbins = sigE.GetNbinsX() + low = sigE.GetBinLowEdge(1) + high = sigE.GetBinLowEdge(nbins+1) + + purS = ROOT.TH1F(pname, pname, nbins, low, high) + sSig = ROOT.TH1F(ssigname, ssigname, nbins, low, high) + effpurS = ROOT.TH1F(epname, epname, nbins, low, high) + + # formating the style of histograms + #chop off useless stuff + sigE.SetTitle( "Cut efficiencies for "+methodName+" classifier") + + TMVA.TMVAGlob.SetSignalAndBackgroundStyle( sigE, bgdE ) + TMVA.TMVAGlob.SetSignalAndBackgroundStyle( purS, bgdE ) + TMVA.TMVAGlob.SetSignalAndBackgroundStyle( effpurS, bgdE ) + sigE.SetFillStyle( 0 ) + bgdE.SetFillStyle( 0 ) + sSig.SetFillStyle( 0 ) + sigE.SetLineWidth( 3 ) + bgdE.SetLineWidth( 3 ) + sSig.SetLineWidth( 3 ) + + purS.SetFillStyle( 0 ) + purS.SetLineWidth( 2 ) + purS.SetLineStyle( 5 ) + effpurS.SetFillStyle( 0 ) + effpurS.SetLineWidth( 2 ) + effpurS.SetLineStyle( 6 ) + sig = 0 + maxSigErr = 0 + for i in range(1,sigE.GetNbinsX()+1): + eS = sigE.GetBinContent( i ) + S = eS * fNSignal + B = bgdE.GetBinContent( i ) * fNBackground + if (S+B)==0: + purS.SetBinContent( i, 0) + else: + purS.SetBinContent( i, S/(S+B) ) + + sSig.SetBinContent( i, f.Eval(S,B) ) + effpurS.SetBinContent( i, eS*purS.GetBinContent( i ) ) + + maxSignificance = sSig.GetMaximum() + maxSignificanceErr = 0 + sSig.Scale(1/maxSignificance) + + c = ROOT.TCanvas( "canvasCutEff","Cut efficiencies for "+methodName+" classifier", JPyInterface.JsDraw.jsCanvasWidth, + JPyInterface.JsDraw.jsCanvasHeight) + + c.SetGrid(1) + c.SetTickx(0) + c.SetTicky(0) + + TMVAStyle = ROOT.gROOT.GetStyle("Plain") + TMVAStyle.SetLineStyleString( 5, "[32 22]" ) + TMVAStyle.SetLineStyleString( 6, "[12 22]" ) + + c.SetTopMargin(.2) + + effpurS.SetTitle("Cut efficiencies and optimal cut value") + if methodName.find("Cuts")!=-1: + effpurS.GetXaxis().SetTitle( "Signal Efficiency" ) + else: + effpurS.GetXaxis().SetTitle( "Cut value applied on " + methodName + " output" ) + effpurS.GetYaxis().SetTitle( "Efficiency (Purity)" ) + TMVA.TMVAGlob.SetFrameStyle( effpurS ) + + c.SetTicks(0,0) + c.SetRightMargin ( 2.0 ) + + effpurS.SetMaximum(1.1) + effpurS.Draw("histl") + + purS.Draw("samehistl") + + sigE.Draw("samehistl") + bgdE.Draw("samehistl") + + signifColor = ROOT.TColor.GetColor( "#00aa00" ) + + sSig.SetLineColor( signifColor ) + sSig.Draw("samehistl") + + effpurS.Draw( "sameaxis" ) + + #Adding labels and other informations to plots. + + legend1 = ROOT.TLegend( c.GetLeftMargin(), 1 - c.GetTopMargin(), + c.GetLeftMargin() + 0.4, 1 - c.GetTopMargin() + 0.12 ) + legend1.SetFillStyle( 1 ) + legend1.AddEntry(sigE,"Signal efficiency","L") + legend1.AddEntry(bgdE,"Background efficiency","L") + legend1.Draw("same") + legend1.SetBorderSize(1) + legend1.SetMargin( 0.3 ) + + + legend2 = ROOT.TLegend( c.GetLeftMargin() + 0.4, 1 - c.GetTopMargin(), + 1 - c.GetRightMargin(), 1 - c.GetTopMargin() + 0.12 ) + legend2.SetFillStyle( 1 ) + legend2.AddEntry(purS,"Signal purity","L") + legend2.AddEntry(effpurS,"Signal efficiency*purity","L") + legend2.AddEntry(sSig, "S/#sqrt{ S+B }","L") + legend2.Draw("same") + legend2.SetBorderSize(1) + legend2.SetMargin( 0.3 ) + + effline = ROOT.TLine( sSig.GetXaxis().GetXmin(), 1, sSig.GetXaxis().GetXmax(), 1 ) + effline.SetLineWidth( 1 ) + effline.SetLineColor( 1 ) + effline.Draw() + + c.Update() + + tl = ROOT.TLatex() + tl.SetNDC() + tl.SetTextSize( 0.033 ) + maxbin = sSig.GetMaximumBin() + line1 = tl.DrawLatex( 0.15, 0.23, "For %1.0f signal and %1.0f background"%(fNSignal, fNBackground)) + tl.DrawLatex( 0.15, 0.19, "events the maximum S/#sqrt{S+B} is") + + if maxSignificanceErr > 0: + line2 = tl.DrawLatex( 0.15, 0.15, "%5.2f +- %4.2f when cutting at %5.2f"%( + maxSignificance, + maxSignificanceErr, + sSig.GetXaxis().GetBinCenter(maxbin)) ) + else: + line2 = tl.DrawLatex( 0.15, 0.15, "%4.2f when cutting at %5.2f"%( + maxSignificance, + sSig.GetXaxis().GetBinCenter(maxbin)) ) + + if methodName.find("Cuts")!=-1: + tl.DrawLatex( 0.13, 0.77, "Method Cuts provides a bundle of cut selections, each tuned to a") + tl.DrawLatex(0.13, 0.74, "different signal efficiency. Shown is the purity for each cut selection.") + + wx = (sigE.GetXaxis().GetXmax()+abs(sigE.GetXaxis().GetXmin()))*0.135 + rightAxis = ROOT.TGaxis( sigE.GetXaxis().GetXmax()+wx, + c.GetUymin()-0.3, + sigE.GetXaxis().GetXmax()+wx, + 0.7, 0, 1.1*maxSignificance,510, "+L") + rightAxis.SetLineColor ( signifColor ) + rightAxis.SetLabelColor( signifColor ) + rightAxis.SetTitleColor( signifColor ) + + rightAxis.SetTitleSize( sSig.GetXaxis().GetTitleSize() ) + rightAxis.SetTitle( "Significance" ) + rightAxis.Draw() + + c.Update() + + JPyInterface.JsDraw.Draw(c) + +## Draw neural network +# @param fac the object pointer +# @param datasetName the dataset name +# @param methodName we want to see the network created by this method +def DrawNeuralNetwork(fac, datasetName, methodName): + m = GetMethodObject(fac, datasetName, methodName) + if m==None: + return None + if (methodName=="DNN"): + net = GetDeepNetwork(str(m.GetWeightFileName())) + else: + net = GetNetwork(str(m.GetWeightFileName())) + JPyInterface.JsDraw.Draw(net, "drawNeuralNetwork", True) + +## Draw deep neural network +# @param fac the object pointer +# @param datasetName the dataset name +# @param methodName we want to see the deep network created by this method +def DrawDecisionTree(fac, datasetName, methodName): + m = GetMethodObject(fac, datasetName, methodName) + if m==None: + return None + tr = TreeReader(str(m.GetWeightFileName())) + + variables = tr.getVariables(); + + def clicked(b): + if treeSelector.value>tr.getNTrees(): + treeSelector.value = tr.getNTrees() + clear_output() + toJs = { + "variables": variables, + "tree": tr.getTree(treeSelector.value) + } + json_str = json.dumps(toJs) + JPyInterface.JsDraw.Draw(json_str, "drawDecisionTree", True) + + mx = str(tr.getNTrees()-1) + + treeSelector = widgets.IntText(value=0, font_weight="bold") + drawTree = widgets.Button(description="Draw", font_weight="bold") + label = widgets.HTML("
Decision Tree [0-"+mx+"]:
") + + drawTree.on_click(clicked) + container = widgets.HBox([label,treeSelector, drawTree]) + display(container) + +## Rewrite function for TMVA::Factory::TrainAllMethods. This function provides interactive training. +# The training will be started on separated thread. The main thread will periodically check for updates and will create +# the JS output which will update the plots and progress bars. The main thread must contain `while True`, because, if not +# it will cause crash (output will be flushed by tornado IOLoop (runs on main thread), but the output streams are +# C++ atomic types) +# @param fac the factory object pointer +def ChangeTrainAllMethods(fac): + clear_output() + #stop button + button = __HTMLJSCSSTemplates.button + #progress bar + inc = __HTMLJSCSSTemplates.inc + progress_bar = __HTMLJSCSSTemplates.progress_bar + progress_bar_idx = 0 + + def exit_supported(mn): + name = str(mn) + es = ["SVM", "Cuts", "Boost", "BDT"] + for e in es: + if name.find(e) != -1: + return True + return False + + wait_times = {"MLP": 0.5, "DNN": 1, "BDT": 0.5} + + for methodMapElement in fac.fMethodsMap: + display(HTML("

Dataset: "+str(methodMapElement[0])+"

")) + for m in methodMapElement[1]: + m.GetName._threaded = True + name = str(m.GetName()) + display(HTML("

Train method: "+name+"

")) + m.InitIPythonInteractive() + t = Thread(target=ROOT.TMVA.MethodBase.TrainMethod, args=[m]) + t.start() + if name in wait_times: + display(HTML(button)) + time.sleep(wait_times[name]) + if m.GetMaxIter() != 0: + display(HTML(progress_bar.substitute({"id": progress_bar_idx}))) + display(HTML(inc.substitute({"id": progress_bar_idx, "progress": 100 * m.GetCurrentIter() / m.GetMaxIter()}))) + JPyInterface.JsDraw.Draw(m.GetInteractiveTrainingError(), "drawTrainingTestingErrors") + try: + while not m.TrainingEnded(): + JPyInterface.JsDraw.InsertData(m.GetInteractiveTrainingError()) + if m.GetMaxIter() != 0: + display(HTML(inc.substitute({ + "id": progress_bar_idx, + "progress": 100 * m.GetCurrentIter() / m.GetMaxIter() + }))) + time.sleep(0.5) + except KeyboardInterrupt: + m.ExitFromTraining() + else: + if exit_supported(name): + display(HTML(button)) + time.sleep(0.5) + if m.GetMaxIter()!=0: + display(HTML(progress_bar.substitute({"id": progress_bar_idx}))) + display(HTML(inc.substitute({"id": progress_bar_idx, "progress": 100*m.GetCurrentIter()/m.GetMaxIter()}))) + else: + display(HTML("Training...")) + if exit_supported(name): + try: + while not m.TrainingEnded(): + if m.GetMaxIter()!=0: + display(HTML(inc.substitute({ + "id": progress_bar_idx, + "progress": 100 * m.GetCurrentIter() / m.GetMaxIter() + }))) + time.sleep(0.5) + except KeyboardInterrupt: + m.ExitFromTraining() + else: + while not m.TrainingEnded(): + if m.GetMaxIter() != 0: + display(HTML(inc.substitute({ + "id": progress_bar_idx, + "progress": 100 * m.GetCurrentIter() / m.GetMaxIter() + }))) + time.sleep(0.5) + if m.GetMaxIter() != 0: + display(HTML(inc.substitute({ + "id": progress_bar_idx, + "progress": 100 * m.GetCurrentIter() / m.GetMaxIter() + }))) + else: + display(HTML("End")) + progress_bar_idx += 1 + t.join() + return + +## Rewrite the constructor of TMVA::Factory +# @param *args positional parameters +# @param **kwargs named parameters: this will be transformed to option string +def ChangeCallOriginal__init__(*args, **kwargs): + hasColor = False + args = list(args) + for arg_idx in xrange(len(args)): + if isinstance(args[arg_idx], basestring) and args[arg_idx].find(":")!=-1: + if args[arg_idx].find("Color")!=-1: + hasColor = True + if args[arg_idx].find("!Color")==-1: + args[arg_idx] = args[arg_idx].replace("Color", "!Color") + else: + kwargs["Color"] = False + args = tuple(args) + if not hasColor: + kwargs["Color"] = False + try: + args, kwargs = JPyInterface.functions.ConvertSpecKwargsToArgs(["JobName", "TargetFile"], *args, **kwargs) + except AttributeError: + try: + args, kwargs = JPyInterface.functions.ConvertSpecKwargsToArgs(["JobName"], *args, **kwargs) + except AttributeError: + raise AttributeError + originalFunction, args = JPyInterface.functions.ProcessParameters(3, *args, **kwargs) + return originalFunction(*args) + +## Rewrite TMVA::Factory::BookMethod +# @param *args positional parameters +# @param **kwargs named parameters: this will be transformed to option string +def ChangeCallOriginalBookMethod(*args, **kwargs): + compositeOpts = False + composite = False + if "Composite" in kwargs: + composite = kwargs["Composite"] + del kwargs["Composite"] + if "CompositeOptions" in kwargs: + compositeOpts = kwargs["CompositeOptions"] + del kwargs["CompositeOptions"] + args, kwargs = JPyInterface.functions.ConvertSpecKwargsToArgs(["DataLoader", "Method", "MethodTitle"], *args, **kwargs) + originalFunction, args = JPyInterface.functions.ProcessParameters(4, *args, **kwargs) + if composite!=False: + args = list(args) + args.append(composite) + args = tuple(args) + if compositeOpts!=False: + o, compositeOptStr = JPyInterface.functions.ProcessParameters(-10, **compositeOpts) + args = list(args) + args.append(compositeOptStr[0]) + args = tuple(args) + return originalFunction(*args) + +## Rewrite the constructor of TMVA::Factory::EvaluateImportance +# @param *args positional parameters +# @param **kwargs named parameters: this will be transformed to option string +def ChangeCallOriginalEvaluateImportance(*args, **kwargs): + if len(kwargs) == 0: + originalFunction, args = JPyInterface.functions.ProcessParameters(0, *args, **kwargs) + return originalFunction(*args) + args, kwargs = JPyInterface.functions.ConvertSpecKwargsToArgs(["DataLoader", "VIType", "Method", "MethodTitle"], *args, **kwargs) + originalFunction, args = JPyInterface.functions.ProcessParameters(5, *args, **kwargs) + hist = originalFunction(*args) + JPyInterface.JsDraw.Draw(hist) + return hist + +## Background booking method for BookDNN +__BookDNNHelper = None + +## Graphical interface for booking DNN +# @param self object pointer +# @param loader the DataLoader object +# @param title classifier title +def BookDNN(self, loader, title="DNN"): + global __BookDNNHelper + def __bookDNN(optString): + self.BookMethod(loader, ROOT.TMVA.Types.kDNN, title, optString) + return + __BookDNNHelper = __bookDNN + clear_output() + JPyInterface.JsDraw.InsertCSS("NetworkDesigner.min.css") + JPyInterface.JsDraw.Draw("", "NetworkDesigner", True) + +## This function gets the classifier information and weights in JSON formats, and the selected layers and it will create +# the weight heat map. +# @param net DNN in JSON format +# @param selectedLayers the selected layers +def CreateWeightHist(net, selectedLayers): + weightStartIndex = 0 + numberOfWeights = 0 + firstLayer=int(selectedLayers.split("->")[0]) + for i in xrange(firstLayer): + n1=int(net["layers"][i-1]["Nodes"]) + n2=int(net["layers"][i]["Nodes"]) + weightStartIndex += int(n1*n2) + n1 = 1 + n2 = 1 + if firstLayer>0: + n1 = int(net["layers"][firstLayer-1]["Nodes"]) + n2 = int(net["layers"][firstLayer]["Nodes"]) + else: + n2 = int(net["layers"][firstLayer]["Nodes"]) + numberOfWeights = n1*n2 + m = ROOT.TMatrixD(n1, n2) + for i in xrange(n1): + for j in xrange(n2): + idx = j+n2*i + if idx>numberOfWeights: + print("Something is wrong...") + continue + m[i][j] = float(net["synapses"]["synapses"][weightStartIndex+idx]) + th2 = ROOT.TH2D(m) + th2.SetTitle("Weight map for DNN") + for i in xrange(n2): + th2.GetXaxis().SetBinLabel(i + 1, str(i)) + for i in xrange(n1): + th2.GetYaxis().SetBinLabel(i + 1, str(i)) + th2.GetXaxis().SetTitle("Layer: "+str(firstLayer+1)) + th2.GetYaxis().SetTitle("Layer: "+str(firstLayer)) + th2.SetStats(0) + th2.SetMarkerSize(1.5) + th2.SetMarkerColor(0) + labelSize = 0.040 + th2.GetXaxis().SetLabelSize(labelSize) + th2.GetYaxis().SetLabelSize(labelSize) + th2.LabelsOption("d") + th2.SetLabelOffset(0.011) + clear_output() + JPyInterface.JsDraw.Draw(th2, 'drawDNNMap') + +## Show DNN weights in a heat map. It will produce an ipywidget element, where the layers can be selected. +# @param fac object pointer +# @oaram datasetName name of current dataset +# @param methodName DNN's name +def DrawDNNWeights(fac, datasetName, methodName="DNN"): + m = GetMethodObject(fac, datasetName, methodName) + if m == None: + return None + net = GetDeepNetwork(str(m.GetWeightFileName()), True) + numOfLayers = len(net["layers"]) + options = [] + vals=[] + for layer in xrange(numOfLayers): + options.append(str(layer)+"->"+str(layer+1)) + vals.append(layer) + selectLayer=widgets.Dropdown( + options=options, + value=options[0], + description='Layer' + ) + def drawWrapper(e): + CreateWeightHist(net, selectLayer.value) + pass + button = widgets.Button(description="Draw", font_weight="bold", font_size="16") + button.on_click(drawWrapper) + box = widgets.HBox([selectLayer, button]) + display(box) \ No newline at end of file diff --git a/bindings/pyroot/JsMVA/JPyInterface.py b/bindings/pyroot/JsMVA/JPyInterface.py new file mode 100644 index 0000000000000..cd5a558f38e82 --- /dev/null +++ b/bindings/pyroot/JsMVA/JPyInterface.py @@ -0,0 +1,321 @@ +# -*- coding: utf-8 -*- +## @package JsMVA.JPyInterface +# @author Attila Bagoly +# JPyInterface is responsible for adding the drawing methods to TMVA +# and for creating the JavaScript outputs from objects. + + +from IPython.core.display import display, HTML +from string import Template +import ROOT +import DataLoader +import Factory +import types +import OutputTransformer + + +## Function inserter class +# This class contains the methods which are invoked by using jsmva magic, and will inject the new methods +# to TMVA::Factory, TMVA::DataLoader +class functions: + + ## Threaded functions + ThreadedFunctions = { + "MethodBase": ["GetInteractiveTrainingError", "ExitFromTraining", "TrainingEnded", "TrainMethod", + "GetMaxIter", "GetCurrentIter"] + } + + ## The method inserter function + # @param target which class to insert + # @param source module which contains the methods to insert + # @param args list of methods to insert + @staticmethod + def __register(target, source, *args): + for arg in args: + if hasattr(target, arg): + continue + setattr(target, arg, getattr(source, arg)) + + ## This method change TMVA methods with new methods + # @param target which class to insert + # @param source module which contains the methods to insert + # @param args list of methods to insert + @staticmethod + def __changeMethod(target, source, *args): + def rewriter(originalFunction, newFunction): + def wrapper(*args, **kwargs): + kwargs["originalFunction"] = originalFunction + return newFunction(*args, **kwargs) + return wrapper + for arg in args: + if arg.find("CallOriginal")!=-1: + originalName = arg.replace("Change", "").replace("CallOriginal", "") + setattr(target, originalName, rewriter(getattr(target, originalName), getattr(source, arg))) + else: + setattr(target, arg.replace("Change", ""), getattr(source, arg)) + + ## Get's special parameters from kwargs and converts to positional parameter + @staticmethod + def ConvertSpecKwargsToArgs(positionalArgumentsToNamed, *args, **kwargs): + # args[0] = self + args = list(args) + idx = 0 + PositionalArgsEnded = False + for argName in positionalArgumentsToNamed: + if not PositionalArgsEnded: + if argName in kwargs: + if (idx + 1) != len(args): + raise AttributeError + PositionalArgsEnded = True + else: + idx += 1 + if PositionalArgsEnded and argName not in kwargs: + raise AttributeError + if argName in kwargs: + args.append(kwargs[argName]) + del kwargs[argName] + args = tuple(args) + return (args, kwargs) + + ## Converts object to TMVA style option string + @staticmethod + def ProcessParameters(optStringStartIndex, *args, **kwargs): + originalFunction = None + if optStringStartIndex != -10: + originalFunction = kwargs["originalFunction"] + del kwargs["originalFunction"] + OptionStringPassed = False + if (len(args) - 1) == optStringStartIndex: + opt = args[optStringStartIndex] + ":" + tmp = list(args) + del tmp[optStringStartIndex] + args = tuple(tmp) + OptionStringPassed = True + else: + opt = "" + for key in kwargs: + if type(kwargs[key]) == types.BooleanType: + if kwargs[key] == True: + opt += key + ":" + else: + opt += "!" + key + ":" + elif type(kwargs[key]) == types.ListType: + ss = "" + for o in kwargs[key]: + if type(o) == types.DictType: + sst = "" + for kk in o: + sst += kk + "=" + str(o[kk]) + "," + ss += sst[:-1] + "|" + elif key=="Layout": + ss += str(o) + "," + else: + ss += str(o) + ";" + opt += key + "=" + ss[:-1] + ":" + else: + opt += key + "=" + str(kwargs[key]) + ":" + tmp = list(args) + if OptionStringPassed or len(kwargs) > 0: + tmp.append(opt[:-1]) + return (originalFunction, tuple(tmp)) + + ## The method removes inserted functions from class + # @param target from which class to remove functions + # @param args list of methods to remove + @staticmethod + def __unregister(target, *args): + for arg in args: + if hasattr(target, arg): + delattr(target, arg) + + ## Reads all methods containing a selector from specified module + # @param module finding methods in this module + # @param selector if method in module contains this string will be selected + @staticmethod + def __getMethods(module, selector): + methods = [] + for method in dir(module): + if method.find(selector)!=-1: + methods.append(method) + return methods + + ## This function will register all functions which name contains "Draw" to TMVA.DataLoader and TMVA.Factory + # from DataLoader and Factory modules + @staticmethod + def register(noOutput=False): + from JupyROOT.utils import transformers + functions.__register(ROOT.TMVA.DataLoader, DataLoader, *functions.__getMethods(DataLoader, "Draw")) + functions.__register(ROOT.TMVA.Factory, Factory, *functions.__getMethods(Factory, "Draw")) + functions.__changeMethod(ROOT.TMVA.Factory, Factory, *functions.__getMethods(Factory, "Change")) + functions.__changeMethod(ROOT.TMVA.DataLoader, DataLoader, *functions.__getMethods(DataLoader, "Change")) + for key in functions.ThreadedFunctions: + for func in functions.ThreadedFunctions[key]: + setattr(getattr(getattr(ROOT.TMVA, key), func), "_threaded", True) + functions.__register(ROOT.TMVA.Factory, Factory, "BookDNN") + if not noOutput: + outputTransformer = OutputTransformer.transformTMVAOutputToHTML() + transformers.append(outputTransformer.transform) + JsDraw.InitJsMVA() + else: + def noOutputFunc(out, err): + return ("", "", "html") + transformers.append(noOutputFunc) + ## This function will remove all functions which name contains "Draw" from TMVA.DataLoader and TMVA.Factory + # if the function was inserted from DataLoader and Factory modules + @staticmethod + def unregister(): + functions.__register(ROOT.TMVA.DataLoader, DataLoader, *functions.__getMethods(DataLoader, "Draw")) + functions.__register(ROOT.TMVA.Factory, Factory, *functions.__getMethods(Factory, "Draw")) + + ## This function captures objects which are declared in noteboko cell. It's used to capture factory and data loader objects. + # @param args classes + @staticmethod + def captureObjects(*args): + ip = get_ipython() + vList = [ip.user_ns[key] for key in ip.user_ns] + res = {} + for ttype in args: + res[ttype.__name__] = [ttype] + for var in vList: + for ttype in args: + if type(var) == ttype and isinstance(var, ttype): + res[ttype.__name__].append(var) + return res + + +## Class for creating the output scripts and inserting them to cell output +class JsDraw: + ## String containing the link to JavaScript files + __jsMVASourceDir = "https://rawgit.com/qati/GSOC16/master/src/js" + + ## String containing the link to CSS files + __jsMVACSSDir = "https://rawgit.com/qati/GSOC16/master/src/css" + + ## Drawing are sizes + jsCanvasWidth = 800 + jsCanvasHeight = 450 + + ## id for drawing area + __divUID = 0 + + ## Template containing HTML code with draw area and drawing JavaScript + __jsCode = Template(""" +
+ +""") + + ## Template containing requirejs configuration with JsMVA location + __jsCodeRequireJSConfig = Template(""" + +""") + + ## Template containing data insertion JavaScript code + __jsCodeForDataInsert = Template("""""") + __jsCodeForDataInsertNoRemove = Template("""""") + + ## Inserts requirejs config script + @staticmethod + def InitJsMVA(): + display(HTML(JsDraw.__jsCodeRequireJSConfig.substitute({ + 'PATH': JsDraw.__jsMVASourceDir + }))) + JsDraw.InsertCSS("TMVAHTMLOutput.min.css") + + ## Inserts the draw area and drawing JavaScript to output + # @param obj ROOT object (will be converted to JSON) or JSON string containing the data to be drawed + # @param jsDrawMethod the JsMVA JavaScrip object method name to be used for drawing + # @param objIsJSON obj is ROOT object or JSON + @staticmethod + def Draw(obj, jsDrawMethod='draw', objIsJSON=False): + if objIsJSON: + dat = obj + else: + dat = ROOT.TBufferJSON.ConvertToJSON(obj) + dat = str(dat).replace("\n", "") + JsDraw.__divUID += 1 + display(HTML(JsDraw.__jsCode.substitute({ + 'funcName': jsDrawMethod, + 'divid':'jstmva_'+str(JsDraw.__divUID), + 'dat': dat, + 'width': JsDraw.jsCanvasWidth, + 'height': JsDraw.jsCanvasHeight + }))) + + ## Inserts CSS file + # @param cssName The CSS file name. File must be in jsMVACSSDir! + @staticmethod + def InsertCSS(cssName): + display(HTML('')) + + ## Inserts the data inserter JavaScript code to output + # @param obj ROOT object (will be converted to JSON) or JSON string containing the data to be inserted + # @param dataInserterMethod the JsMVA JavaScrip object method name to be used for inserting the new data + # @param objIsJSON obj is ROOT object or JSON, default is False + # @param divID custom id, it will be sent to dataInserterMethod, default is JsDraw.__divUID + @staticmethod + def InsertData(obj, dataInserterMethod="updateTrainingTestingErrors", objIsJSON=False, divID=""): + if objIsJSON: + dat = obj + else: + dat = ROOT.TBufferJSON.ConvertToJSON(obj) + dat = str(dat).replace("\n", "") + if len(divID)>1: + divid = divID + jsCode = JsDraw.__jsCodeForDataInsertNoRemove + else: + divid = str(JsDraw.__divUID) + jsCode = JsDraw.__jsCodeForDataInsert + display(HTML(jsCode.substitute({ + 'funcName': dataInserterMethod, + 'divid': 'jstmva_'+divid, + 'dat': dat + }))) + + ## Draws a signal and background histogram to a newly created TCanvas + # @param sig signal histogram + # @param bkg background histogram + # @param title all labels + @staticmethod + def sbPlot(sig, bkg, title): + canvas = ROOT.TCanvas("csbplot", title["plot"], JsDraw.jsCanvasWidth, JsDraw.jsCanvasHeight) + sig.SetMaximum(ROOT.TMath.Max(sig.GetMaximum()*1.1,bkg.GetMaximum()*1.1)) + sig.SetTitle(sig.GetTitle().replace("(Signal)","")) + sig.GetXaxis().SetTitle(title["xaxis"]) + sig.GetYaxis().SetTitle(title["yaxis"]) + sig.SetTitle(title["plot"]) + bkg.SetFillColorAlpha(ROOT.kRed, 0.3) + sig.SetFillColor(ROOT.kBlue) + bkg.SetLineColor(ROOT.kRed) + sig.Draw("hist") + bkg.Draw("histsame") + + legend = ROOT.TLegend(1-canvas.GetLeftMargin()-0.39, 1-canvas.GetTopMargin()-0.15, + 1-canvas.GetLeftMargin()-0.01, 1-canvas.GetTopMargin()-0.01) + legend.SetFillStyle(1) + legend.AddEntry(sig, "Signal", "F") + legend.AddEntry(bkg, "Background", "F") + legend.SetBorderSize(1) + legend.SetMargin(0.3) + legend.Draw() + + return (canvas, legend) \ No newline at end of file diff --git a/bindings/pyroot/JsMVA/JsMVAMagic.py b/bindings/pyroot/JsMVA/JsMVAMagic.py new file mode 100644 index 0000000000000..436ea4139c2c6 --- /dev/null +++ b/bindings/pyroot/JsMVA/JsMVAMagic.py @@ -0,0 +1,38 @@ +# -*- coding:utf-8 -*- +## @package JsMVA.JsMVAMagic +# @author Attila Bagoly +# IPython magic class for JsMVA + +from IPython.core.magic import Magics, magics_class, line_magic +from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring + + +@magics_class +class JsMVAMagic(Magics): + + ## Standard constructor + # @param self pointer to object + # @param shell ipython API + def __init__(self, shell): + super(JsMVAMagic, self).__init__(shell) + + ## jsmva magic + # @param self pointer to object + # @param line jsmva arguments: on/off + @line_magic + @magic_arguments() + @argument('arg', nargs="?", default="on", help='Enable/Disable JavaScript visualisation for TMVA') + def jsmva(self, line): + from JPyInterface import functions + args = parse_argstring(self.jsmva, line) + if args.arg == 'on': + functions.register() + elif args.arg == 'off': + functions.unregister() + elif args.arg == "noOutput": + functions.register(True) + + +## Function for registering the magic class +def load_ipython_extension(ipython): + ipython.register_magics(JsMVAMagic) \ No newline at end of file diff --git a/bindings/pyroot/JsMVA/OutputTransformer.py b/bindings/pyroot/JsMVA/OutputTransformer.py new file mode 100644 index 0000000000000..1857efb7cd00f --- /dev/null +++ b/bindings/pyroot/JsMVA/OutputTransformer.py @@ -0,0 +1,361 @@ +# -*- coding: utf-8 -*- +## @package JsMVA.FormatedOutput +# @author Attila Bagoly +# This class will transform the TMVA original output to HTML formated output. + +import DataLoader +import cgi +import re + +## The output transformer class. This class contains all the methods which are used to transform the C++ style output +# to HTML formatted output. +class transformTMVAOutputToHTML: + + ## Constructor + # @param self object pointer + def __init__(self): + self.__eventsUID = 0 + + # Transform one line of C++ output string to HTML. + # @param self object pointer + # @param line line to transform + def __processGroupContentLine(self, line): + if re.match(r"^\s*:?\s*-*\s*$", line)!=None: + return "" + if len(self.__outputFlagClass) > 1: + line = cgi.escape(str(line)) + self.addClassForOutputFlag(line) + if line.find("Booking method")!=-1: + line = "Booking method: " + line.split(":")[1].replace("\033[1m", "").replace("[0m", "")+"" + if line.find("time")!=-1: + lr = line.split(":") + if len(lr)==2: + line = "" + lr[0] + " : " + lr[1].replace("\033[1;31m", "").replace("[0m", "") +"" + outFlag = self.__outputFlagClass + if line.find("\033[0;36m")!=-1: + line = "" + line.replace("\033[0;36m", "").replace("[0m", "") + "" + if line.find("\033[1m")!=-1: + line = "" + line.replace("\033[1m", "").replace("[0m", "") + "" + lre = re.match(r"(\s*using\s*input\s*file\s*):?\s*(.*)", line, re.I) + if lre: + line = lre.group(1)+":"+""+lre.group(2)+"" + return ""+str(line)+"" + + ## Checks if a line is empty. + # @param self object pointer + # @param line line to check + def __isEmpty(self, line): + return re.match(r"^\s*:?\s*-*$", line)!=None + + ## Transforms all data set specific content. + # @param self object pointer + # @param firstLine first line which mach with header + # @param startIndex it defines where we are in self.lines array + # @param maxlen defines how many iterations we can do from startIndex + def __transformDatasetSpecificContent(self, firstLine, startIndex, maxlen): + tmp_str = "" + count = 0 + for l in xrange(1, maxlen): + nextline = self.lines[startIndex+l] + if self.__isEmpty(nextline): + count += 1 + continue + DatasetName = re.match(r".*(\[.*\])\s*:\s*(.*)", nextline) + if DatasetName: + count += 1 + tmp_str += ""+self.__processGroupContentLine(DatasetName.group(2)) + tmp_str += "" + else: + break + DatasetName = re.match(r".*(\[.*\])\s*:\s*(.*)", firstLine) + self.__lastDataSetName = DatasetName.group(1).replace("[", "").replace("]", "") + tbodyclass = "" + if count > 0: + tbodyclass = " class='tmva_output_tbody_multiple_row'" + rstr = "" + rstr += "" + rstr += self.__processGroupContentLine(DatasetName.group(2)) + "" + rstr += tmp_str + rstr += "
Dataset: "+self.__lastDataSetName+"
" + return (count, rstr) + + ## It transforms number of events related contents. + # @param self object pointer + # @param firstLine first line which mach with header + # @param startIndex it defines where we are in self.lines array + # @param maxlen defines how many iterations we can do from startIndex + def __transformNumEvtSpecificContent(self, firstLine, startIndex, maxlen): + tmp_str = "" + count = 0 + tmpmap = {} + for l in xrange(1, maxlen): + nextline = self.lines[startIndex+l] + if self.__isEmpty(nextline): + count += 1 + continue + NumberOfEvents = re.match(r"\s+:\s*\w+\s*-\s*-\s*((training\sevents)|(testing\sevents)|(training\sand\stesting\sevents))\s*:\s*\d+", nextline) + if NumberOfEvents: + lc = re.findall(r"\w+", nextline) + t = "" + for i in range(1, len(lc) - 1): + t += lc[i] + " " + t = t[:-1] + if lc[0] not in tmpmap: + tmpmap[lc[0]] = [] + count += 1 + tmpmap[lc[0]].append({"name": t, "value": lc[len(lc) - 1]}) + else: + break + rstr = "" + rstr += "" + for key in tmpmap: + rstr += "" + rstr += "" + rstr += "" + rstr += "" + rstr += "" + for i in xrange(1, len(tmpmap[key])): + rstr += "" + rstr += "" + rstr += tmp_str + rstr += "
"+firstLine+"
"+key+""+tmpmap[key][0]["name"]+""+tmpmap[key][0]["value"]+"
"+tmpmap[key][i]["name"]+""+tmpmap[key][i]["value"]+"
" + return (count, rstr) + + ## Transform Variable related informations to table. + # @param self object pointer + # @param headerMatch re.match object for the first line + # @param startIndex it defines where we are in self.lines array + # @param maxlen defines how many iterations we can do from startIndex + def __transformVariableMeanSpecificContent(self, headerMatch, startIndex, maxlen): + count = 0 + table = [[]] + for j in range(1, 6): + table[0].append(headerMatch.group(j)) + for l in xrange(1, maxlen): + nextline = self.lines[startIndex + l] + if self.__isEmpty(nextline): + count += 1 + continue + VariableMean = re.match(r"\s*:\s*([\w\d]+)\s*:\s*(-?\d*\.?\d*)\s*(-?\d*\.?\d*)\s*\[\s*(-?\d*\.?\d*)\s*(-?\d*\.?\d*)\s*\]", nextline, re.I) + if VariableMean: + count += 1 + tmp = [] + for j in range(1, 6): + tmp.append(VariableMean.group(j)) + table.append(tmp) + else: + break + rstr = "" + for i in xrange(len(table)): + rstr += "" + for j in xrange(len(table[i])): + rstr += "" + rstr += "" + rstr += "
" + str(table[i][j]) + rstr += "
" + return (count, rstr) + + ## This function creates a correlation matrix from python matrix. + # @param self object pointer + # @param title first part of the title of histogram + # @param className Signal/Background: it will be on title + # @param varNames array with variable names + # @param matrix the correlation matrix + def __correlationMatrix(self, title, className, varNames, matrix): + id = "jsmva_outputtansformer_events_"+str(self.__eventsUID)+"_onclick" + self.__eventsUID += 1 + json = DataLoader.GetCorrelationMatrixInJSON(className, varNames, matrix) + jsCall = "require(['JsMVA'],function(jsmva){jsmva.outputShowCorrelationMatrix('"+id+"');});" + rstr = "" + rstr += self.__processGroupContentLine("" + title + " (" + className + ")") + return rstr + + ## This function add different flag based on the line formation. It add's a specific class for each output type. + # @param self object pointer + # @param line current line + def addClassForOutputFlag(self, line): + if self.__currentType==None: + self.__outputFlagClass = "" + if line.find("\033[1;31m") != -1: + self.__outputFlagClass = " class='tmva_output_warning'" + elif line.find("\033[31m") !=-1: + self.__outputFlagClass = " class='tmva_output_error'" + elif line.find("\033[37;41;1m") !=-1: + self.__outputFlagClass = " class='tmva_output_fatal'" + elif line.find("\033[37;41;1m") !=-1: + self.__outputFlagClass = " class='tmva_output_silent'" + elif line.find("\033[34m") !=-1: + self.__outputFlagClass = " class='tmva_output_debug'" + return + if self.__currentType.find("WARNING") != -1 or line.find("\033[1;31m")!=-1: + self.__outputFlagClass = " class='tmva_output_warning'" + elif self.__currentType.find("ERROR") !=-1: + self.__outputFlagClass = " class='tmva_output_error'" + elif self.__currentType.find("FATAL") !=-1: + self.__outputFlagClass = " class='tmva_output_fatal'" + elif self.__currentType.find("SILENT") !=-1: + self.__outputFlagClass = " class='tmva_output_silent'" + elif self.__currentType.find("DEBUG") !=-1: + self.__outputFlagClass = " class='tmva_output_debug'" + elif self.__currentType=="impHead": + self.__outputFlagClass = " class='tmva_output_imphead'" + else: + self.__outputFlagClass = "" + + ## This function can transform one group of output. The group has only one header. + # @param self object pointer + # @param firstLine the header line, it defines the start + def __transformOneGroup(self, firstLine): + tmp_str = "" + processed_lines = 0 + lineIter = iter(xrange(len(self.lines) - self.lineIndex)) + for j in lineIter: + if j == 0: + nextline = firstLine + else: + nextline = self.lines[self.lineIndex + j] + Header = re.match(r"^\s*-*\s*(<\w+>\s*)*\s*(\w+.*\s+)(\s+)(:)\s*(.*)", nextline, re.I) + DatasetName = re.match(r".*(\[.*\])\s*:\s*(.*)", nextline) + NumEvents = re.match(r"(.*)(number\sof\straining\sand\stesting\sevents)", nextline, re.I) + CorrelationMatrixHeader = re.match(r".*\s*:?\s*(correlation\s*matrix)\s*\((\w+)\)\s*:\s*", nextline, re.I) + VariableMeanHeader = re.match(r".*\s*:?\s*(variable)\s*(mean)\s*(rms)\s*\[\s*(min)\s*(max)\s*\].*", nextline, re.I) + WelcomeHeader = re.match(r"^\s*:?\s*(.*ROOT\s*version:.*)", nextline, re.I) + if Header == None or j == 0: + if j != 0: + processed_lines += 1 + self.iterLines.next() + tmp_str += "" + if DatasetName or NumEvents or VariableMeanHeader: + if DatasetName: + func = self.__transformDatasetSpecificContent + fLine = nextline + elif NumEvents: + func = self.__transformNumEvtSpecificContent + fLine = NumEvents.group(2) + else: + func = self.__transformVariableMeanSpecificContent + fLine = VariableMeanHeader + count, tmp = func(fLine, self.lineIndex + j, len(self.lines) - self.lineIndex - j) + for x in xrange(count): + lineIter.next() + self.iterLines.next() + tmp_str += self.__processGroupContentLine(tmp) + elif CorrelationMatrixHeader: + self.iterLines.next() + lineIter.next() + ik = 1 + matrixLines = [] + while True: + self.iterLines.next() + lineIter.next() + ik += 1 + if self.__isEmpty(self.lines[self.lineIndex + j + ik]): + break + ltmp = re.match(r"\s*:\s*(.*)", self.lines[self.lineIndex + j + ik]).group(1) + if ltmp.find(":") != -1: + matrixLines.append(ltmp.split(":")) + rmatch = "^\s*" + for ii in xrange(len(matrixLines)): + rmatch += r"(\+\d+\.?\d*|-\d+\.?\d*)\s*" + rmatch += "$" + matrix = [] + varNames = [] + for ii in xrange(len(matrixLines)): + varNames.append(matrixLines[ii][0]) + ll = re.match(rmatch, matrixLines[ii][1]) + mline = [] + for jj in xrange(len(matrixLines)): + mline.append(float(ll.group(jj + 1))) + matrix.append(mline) + tmp_str += self.__correlationMatrix(CorrelationMatrixHeader.group(1), + CorrelationMatrixHeader.group(2), varNames, matrix) + elif WelcomeHeader: + kw = 0 + while True: + nextline = self.lines[self.lineIndex + j + kw] + EndWelcome = re.match(r"\s*:?\s*_*\s*(TMVA\s*Version\s*.*)", nextline, re.I) + if EndWelcome or re.match(r"[\s_/|]*", nextline) == None: + break + kw += 1 + self.iterLines.next() + lineIter.next() + tmp_str += "" + WelcomeHeader.group(1) + "" + tmp_str += "
" + tmp_str += "
" + EndWelcome.group(1) + "
" + else: + lmatch = re.match(r"\s*(<\w+>\s*)*\s*:\s*(.*)", nextline) + if lmatch: + if lmatch.group(1): + self.__currentType = lmatch.group(1) + tmp_str += self.__processGroupContentLine(lmatch.group(2)) + else: + tmp_str += self.__processGroupContentLine(nextline) + tmp_str += "" + else: + break + tbodyclass = "" + if processed_lines > 0: + tbodyclass = " class='tmva_output_tbody_multiple_row'" + extraHeaderClass="" + if self.__currentType=="impHead": + extraHeaderClass = " tmva_output_imphead" + self.out += "" + self.__currentHeaderName + "" + self.out += tmp_str + "" + + ## This is the main function, it will be registered as transformer function to JupyROOT, it will run every time + # when some ROOT function produces output. It get's the C++ style output and it returns it in HTML version. + # @param self object pointer + # @param output the content of C++ output stream + # @param error the content of C++ error stream + def transform(self, output, error): + self.err = "" + if str(error).find(", time left:")==-1: + self.err = error + self.out = "" + self.lines = output.splitlines() + self.iterLines = iter(xrange(len(self.lines))) + for self.lineIndex in self.iterLines: + line = self.lines[self.lineIndex] + Header = re.match(r"^\s*-*\s*(<\w+>\s*)*\s*(\w+.*\s+)(\s+)(:)\s*(.*)", line, re.I) + NewGroup = re.match(r"^\s*:\s*$", line) + + if Header: + if re.match(r"^\s*-+\s*(<\w+>\s*)*\s*(\w+.*\s+)(\s+)(:)\s*(.*)", line, re.I): + self.__currentType = "impHead" + else: + self.__currentType = Header.group(1) + self.addClassForOutputFlag(Header.group(5)) + self.__currentHeaderName = Header.group(2) + self.__transformOneGroup(Header.group(5)) + elif NewGroup: + kw = 1 + lines = [] + while True: + if (self.lineIndex + kw) >=len(self.lines): + break + nextline = self.lines[self.lineIndex + kw] + kw += 1 + if re.match(r"^\s*:\s*$", nextline): + Header = re.match(r"^\s*-*\s*(<\w+>\s*)*\s*(\w+.*\s+)(\s+)(:)\s*(.*)", self.lines[self.lineIndex+kw], re.I) + if Header: + self.iterLines.next() + break + self.iterLines.next() + if self.__isEmpty(nextline): + continue + lre = re.match(r"\s*:\s*(.*)", nextline) + if lre: + lines.append(lre.group(1)) + else: + lines.append(nextline) + if len(lines)==0: + continue + if len(lines) > 1: + tbodyclass = " class='tmva_output_tbody_multiple_row'" + self.out += "" + for ii in xrange(1, len(lines)): + self.out += "" + else: + self.out += line + self.out += "
"+lines[0]+"
"+lines[ii]+"
" + return (self.out, self.err, "html") \ No newline at end of file diff --git a/bindings/pyroot/JsMVA/__init__.py b/bindings/pyroot/JsMVA/__init__.py new file mode 100644 index 0000000000000..d227863decad7 --- /dev/null +++ b/bindings/pyroot/JsMVA/__init__.py @@ -0,0 +1,14 @@ +# -*- coding:utf-8 -*- +## @mainpage +# @package JsMVA +# @author Attila Bagoly + +from IPython.core.extensions import ExtensionManager + +## This function will register JsMVAMagic class to ipython +def loadExtensions(): + ip = get_ipython() + extMgr = ExtensionManager(ip) + extMgr.load_extension("JsMVA.JsMVAMagic") + +loadExtensions() \ No newline at end of file diff --git a/bindings/pyroot/JupyROOT/utils.py b/bindings/pyroot/JupyROOT/utils.py index 476b4246374e8..6c4198053cffb 100644 --- a/bindings/pyroot/JupyROOT/utils.py +++ b/bindings/pyroot/JupyROOT/utils.py @@ -224,6 +224,8 @@ def invokeAclic(cell): else: processCppCode(".L %s+" %fileName) +transformers = [] + class StreamCapture(object): def __init__(self, ip=get_ipython()): # For the registration @@ -284,8 +286,17 @@ def post_execute(self): sys.stderr = self.nbErrStream # Print for the notebook - self.nbOutStream.write(self.ioHandler.GetStdout()) - self.nbErrStream.write(self.ioHandler.GetStderr()) + out = self.ioHandler.GetStdout() + err = self.ioHandler.GetStderr() + if not transformers: + self.nbOutStream.write(out) + self.nbErrStream.write(err) + else: + for t in transformers: + (out, err, otype) = t(out, err) + if otype == 'html': + IPython.display.display(HTML(out)) + IPython.display.display(HTML(err)) return 0 def register(self): diff --git a/bindings/pyroot/ROOT.py b/bindings/pyroot/ROOT.py index 70c7302dc6383..0b5f515cbcb30 100644 --- a/bindings/pyroot/ROOT.py +++ b/bindings/pyroot/ROOT.py @@ -586,6 +586,7 @@ def _finishSchedule( ROOT = self ): ip = get_ipython() if hasattr(ip,"kernel"): import JupyROOT + import JsMVA ### b/c of circular references, the facade needs explicit cleanup --------------- import atexit diff --git a/etc/notebook/JsMVA/css/NetworkDesigner.css b/etc/notebook/JsMVA/css/NetworkDesigner.css new file mode 100644 index 0000000000000..f1fff6e3a75e7 --- /dev/null +++ b/etc/notebook/JsMVA/css/NetworkDesigner.css @@ -0,0 +1,169 @@ +/** + * This file contain all CSS code for the Deep neural network designer. + * Author: Attila Bagoly + * Created: 7/9/16 + */ + + +#nd_menu_div { + position: relative; + margin: 0; +} + +#nd_menu_div ul#nd_menu { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #e7e7e7; + position: clear; + cursor: pointer; +} + +#nd_menu_div ul#nd_menu ul { + list-style-type: none; + margin: 0; + padding: 0; +} + +.nd_menu_element, .nd_menu_dropdown { + float: left; +} + +.nd_menu_element div, .nd_menu_dropdown div { + display: inline-block; + color: #333333;//#777; + text-align: center; + padding: 6px 16px; + text-decoration: none; +} + +.nd_menu_element:hover, .nd_menu_dropdown:hover { + background-color: #337ab7; +} + +.nd_menu_dropdown { + display: inline-block; +} +.nd_menu_dropdown_content { + display: none; + position: absolute; + background-color: #f1f1f1; + //min-width: 100px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 99; + overflow: hidden; +} + +.nd_menu_dropdown_content li div { + text-align: left; + width: 100%; +} + +.nd_menu_dropdown_content li div:hover { + background-color: #fafafa; +} + +.nd_menu_dropdown:hover .nd_menu_dropdown_content { + display: block; +} + +.connection { + border:3px solid #051; + opacity: 0.5; + z-index:1; + border-radius:100%; + pointer-events:none; +} + +#drawConnectionHelper { + position: absolute; + text-indent: -9999px; + width: 0px; + height: 0px; + border-top: 16px solid transparent; + border-bottom: 16px solid transparent; + border-left: 16px solid #051; + z-index: 95; +} + +.layer_box { + border: 4px solid #051; + -webkit-border-radius: 20%; + -moz-border-radius: 20%; + border-radius: 20%; + width: 140px; + height: 70px; + cursor: pointer; +} + +.layer_box span { + position: absolute; + width:100%; + bottom: 0px; + text-align:center; + font-size: 22px; + font-weight: bold; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.layer_box input.button_layer{ + position: absolute; + top: 5px; + left: 23%; + font-weight: bold; + font-size: 16px; +} + +.layer_box input.button_layer_output { + position: absolute; + bottom: 3px; + left: 23%; + font-weight: bold; + font-size: 16px; +} + +.layer_box center { + font-size: 20px; + font-weight: bold; + padding: 5px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +#ts_link { + margin-top: 10px; +} + +#neuronnum_layer_dialog input:not(.ui-button) { + width: 100px; +} + +#neuronnum_layer_dialog label { + padding-right: 5px; +} + +#trainingstrategy_dialog tr td:first-child { + float: right; +} + +#globopts_dialog tr td:first-child { + float: right; +} + +#trainingstrategy_dialog input { + width: 150px; +} + +#trainingstrategy_dialog label { + padding-right: 10px; +} \ No newline at end of file diff --git a/etc/notebook/JsMVA/css/NetworkDesigner.min.css b/etc/notebook/JsMVA/css/NetworkDesigner.min.css new file mode 100644 index 0000000000000..d911c05faabeb --- /dev/null +++ b/etc/notebook/JsMVA/css/NetworkDesigner.min.css @@ -0,0 +1 @@ +#nd_menu_div{position:relative;margin:0}#nd_menu_div ul#nd_menu{list-style-type:none;margin:0;padding:0;overflow:hidden;background-color:#e7e7e7;position:clear;cursor:pointer}#nd_menu_div ul#nd_menu ul{list-style-type:none;margin:0;padding:0}.nd_menu_element,.nd_menu_dropdown{float:left}.nd_menu_element div,.nd_menu_dropdown div{display:inline-block;color:#333;//#777;text-align:center;padding:6px 16px;text-decoration:none}.nd_menu_element:hover,.nd_menu_dropdown:hover{background-color:#337ab7}.nd_menu_dropdown{display:inline-block}.nd_menu_dropdown_content{display:none;position:absolute;background-color:#f1f1f1;//min-width:100px;box-shadow:0 8px 16px 0 rgba(0,0,0,0.2);z-index:99;overflow:hidden}.nd_menu_dropdown_content li div{text-align:left;width:100%}.nd_menu_dropdown_content li div:hover{background-color:#fafafa}.nd_menu_dropdown:hover .nd_menu_dropdown_content{display:block}.connection{border:3px solid #051;opacity:.5;z-index:1;border-radius:100%;pointer-events:none}#drawConnectionHelper{position:absolute;text-indent:-9999px;width:0;height:0;border-top:16px solid transparent;border-bottom:16px solid transparent;border-left:16px solid #051;z-index:95}.layer_box{border:4px solid #051;-webkit-border-radius:20%;-moz-border-radius:20%;border-radius:20%;width:140px;height:70px;cursor:pointer}.layer_box span{position:absolute;width:100%;bottom:0;text-align:center;font-size:22px;font-weight:bold;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.layer_box input.button_layer{position:absolute;top:5px;left:23%;font-weight:bold;font-size:16px}.layer_box input.button_layer_output{position:absolute;bottom:3px;left:23%;font-weight:bold;font-size:16px}.layer_box center{font-size:20px;font-weight:bold;padding:5px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ts_link{margin-top:10px}#neuronnum_layer_dialog input:not(.ui-button){width:100px}#neuronnum_layer_dialog label{padding-right:5px}#trainingstrategy_dialog tr td:first-child{float:right}#globopts_dialog tr td:first-child{float:right}#trainingstrategy_dialog input{width:150px}#trainingstrategy_dialog label{padding-right:10px} \ No newline at end of file diff --git a/etc/notebook/JsMVA/css/TMVAHTMLOutput.css b/etc/notebook/JsMVA/css/TMVAHTMLOutput.css new file mode 100644 index 0000000000000..4cde1206242d6 --- /dev/null +++ b/etc/notebook/JsMVA/css/TMVAHTMLOutput.css @@ -0,0 +1,126 @@ +/** + * This file contains the CSS for the HTML formatted output. + * Author: Attila Bagoly + * Created: 8/25/16 + */ + +.tmva_output_table { + width: 100%; + border-collapse: collapse; + border: 1px solid white !important; +} + +.tmva_output_table td { + border-top: 1px solid white; + border-left: 1px solid white; + border-right: 1px solid white; + border-bottom: 1px solid #595959; + word-break: break-all; +} + +.tmva_output_table tbody:last-child tr:first-child td:first-child, .tmva_output_table tbody:last-child tr:last-child td { + border-bottom: 1px solid white; +} + +.tmva_output_table tbody tr:first-child td:first-child { + border-right: 1px solid #595959; +} + +.tmva_output_table tbody.tmva_output_tbody_multiple_row tr:not(:last-child) td{ + border-bottom: 1px solid #a6a6a6; +} + +.tmva_output_table tbody:not(:last-child).tmva_output_tbody_multiple_row tr:first-child td:first-child { + border-bottom: 1px solid #595959; +} + +.tmva_output_table tbody:hover { + background-color:#f5f5f5; +} + +.tmva_output_table tbody:hover tr:hover td:last-child { + background-color: rgba(0 ,179, 0, 0.15); +} + +.tmva_output_table tr td.tmva_output_header { + vertical-align: top; + width: 160px !important; + font-family: Arial; + font-size: 18px; + font-weight: bold; +} + +.tmva_output_dataset { + border-collapse: collapse; +} + +.tmva_output_dataset tr:first-child td:first-child { + font-size: 16px; + font-weight: bold; + color: rgb(0, 0, 179); +} + +.tmva_output_dataset td { + border: 1px solid #ddd !important; + vertical-align: top; +} + +.tmva_output_dataset tbody:hover { + //background-color: rgba(0, 150, 179, 0.4); + background-color: rgba(0, 179, 0, 0.4); +} + +.tmva_output_dataset tbody:hover tr:hover td { + background-color: rgba(0, 0, 179, 0.1); +} + +.tmva_output_traintestevents td { + border-top: 1px solid white !important; + border-left: 1px solid white !important; + border-right: 1px solid white !important; + border-bottom: 1px solid #ddd !important; +} + +.tmva_output_traintestevents tbody:hover { + background-color: rgba(0, 179, 0, 0.4); +} + +.tmva_output_varmeanrms td { + border: 1px solid #000066 !important; +} + +.tmva_output_varmeanrms tbody:hover tr { + background-color: rgba(0, 179, 0, 0.4); +} + +.tmva_output_hidden_td { + display: none; +} + +.tmva_output_corrmat_link { + cursor: pointer; +} + +.tmva_output_warning { + background-color: rgba(179, 0, 0, 0.2); +} + +.tmva_output_error { + background-color: rgba(179, 0, 0, 0.5); +} + +.tmva_output_fatal { + background-color: rgba(179, 0, 0, 0.9); +} + +.tmva_output_silent { + background-color: rgba(0, 179, 0, 0.2); +} + +.tmva_output_debug { + background-color: rgba(0, 0, 179, 0.2); +} + +.tmva_output_imphead { + background-color: rgba(0, 179, 0, 0.3); +} diff --git a/etc/notebook/JsMVA/css/TMVAHTMLOutput.min.css b/etc/notebook/JsMVA/css/TMVAHTMLOutput.min.css new file mode 100644 index 0000000000000..99c058f4d027b --- /dev/null +++ b/etc/notebook/JsMVA/css/TMVAHTMLOutput.min.css @@ -0,0 +1 @@ +.tmva_output_table{width:100%;border-collapse:collapse;border:1px solid white !important}.tmva_output_table td{border-top:1px solid white;border-left:1px solid white;border-right:1px solid white;border-bottom:1px solid #595959;word-break:break-all}.tmva_output_table tbody:last-child tr:first-child td:first-child,.tmva_output_table tbody:last-child tr:last-child td{border-bottom:1px solid white}.tmva_output_table tbody tr:first-child td:first-child{border-right:1px solid #595959}.tmva_output_table tbody.tmva_output_tbody_multiple_row tr:not(:last-child) td{border-bottom:1px solid #a6a6a6}.tmva_output_table tbody:not(:last-child).tmva_output_tbody_multiple_row tr:first-child td:first-child{border-bottom:1px solid #595959}.tmva_output_table tbody:hover{background-color:#f5f5f5}.tmva_output_table tbody:hover tr:hover td:last-child{background-color:rgba(0,179,0,0.15)}.tmva_output_table tr td.tmva_output_header{vertical-align:top;width:160px !important;font-family:Arial;font-size:18px;font-weight:bold}.tmva_output_dataset{border-collapse:collapse}.tmva_output_dataset tr:first-child td:first-child{font-size:16px;font-weight:bold;color:#0000b3}.tmva_output_dataset td{border:1px solid #ddd !important;vertical-align:top}.tmva_output_dataset tbody:hover{//background-color:rgba(0,150,179,0.4);background-color:rgba(0,179,0,0.4)}.tmva_output_dataset tbody:hover tr:hover td{background-color:rgba(0,0,179,0.1)}.tmva_output_traintestevents td{border-top:1px solid white !important;border-left:1px solid white !important;border-right:1px solid white !important;border-bottom:1px solid #ddd !important}.tmva_output_traintestevents tbody:hover{background-color:rgba(0,179,0,0.4)}.tmva_output_varmeanrms td{border:1px solid #006 !important}.tmva_output_varmeanrms tbody:hover tr{background-color:rgba(0,179,0,0.4)}.tmva_output_hidden_td{display:none}.tmva_output_corrmat_link{cursor:pointer}.tmva_output_warning{background-color:rgba(179,0,0,0.2)}.tmva_output_error{background-color:rgba(179,0,0,0.5)}.tmva_output_fatal{background-color:rgba(179,0,0,0.9)}.tmva_output_silent{background-color:rgba(0,179,0,0.2)}.tmva_output_debug{background-color:rgba(0,0,179,0.2)}.tmva_output_imphead{background-color:rgba(0,179,0,0.3)} \ No newline at end of file diff --git a/etc/notebook/JsMVA/js/DecisionTree.js b/etc/notebook/JsMVA/js/DecisionTree.js new file mode 100644 index 0000000000000..5311974202a5e --- /dev/null +++ b/etc/notebook/JsMVA/js/DecisionTree.js @@ -0,0 +1,533 @@ +/** + * This is submodule produces visualizations for decision trees. The visualization is interactive, and it is made + * with d3js. + * Interactions supported: + * - Mouseover (node, weight): showing decision path + * - Zooming and grab and move supported + * - Reset zoomed tree: double click + * - Expand all closed subtrees, turn off zoom: button in the bottom of the picture + * - Click on node: + * * hiding subtree, if node children are hidden the node will have a green border + * * rescaling: bigger nodes, bigger texts + * * click again to show the subtree + * Author: Attila Bagoly + * Created: 6/11/16 + */ + + +(function(factory){ + var url = ""; + if (require.s.contexts.hasOwnProperty("_")) { + url = require.s.contexts._.config.paths["JsMVA"].replace("src/js/JsMVA.min",""); + } + + define(["d3"], function(d3){ + return factory({}, d3, url); + }); + +})(function(DecisionTree, d3, url){ + + Object.size = function(obj){ + return Object.keys(obj).length; + }; + + var style = { + margin: { + x: 20, + y: 20 + }, + node: { + padding: 10, + yspace: 40, + xspace: 20, + width: 150, + height: 40, + mwidth: 150, + mheight: 60, + colors: { + focus: "#033A00", + closed: "#00A62B", + pureBkg: "red", + pureSig: "blue" + }, + swidth: "4px" + }, + link: { + colors:{ + default: "#ccc", + focus: "#033A00" + }, + width: "4px", + focus_width: "8px" + }, + aduration: 1500, + legend: { + size: 20, + rect_width: 100, + rect_height: 30, + rect_fucus_width: 115 + }, + text: { + color: "#DEDEDE", + padding: "4px" + }, + buttons:{ + reset:{ + width: "36px", + height: "36px", + alpha: "0.5", + img: url+"img/reset.png", + background: "white" + } + } + }; + + + var nodeColor = d3.scale.linear() + .range([style.node.colors.pureBkg, style.node.colors.pureSig]); + + var canvas, svg, roottree, variables; + + var d3tree = d3.layout.tree(); + var d3path = (function(){ + var diagonal = d3.svg.diagonal(); + var forEachData = function(d, i, hidden){ + if (hidden) return diagonal(d); + return diagonal({ + source: { + x: (d.source.x + style.node.width), + y: d.source.y + }, + target: { + x: (d.target.x + style.node.width), + y: d.target.y - style.node.height + } + }); + }; + return forEachData; + })(); + + var clickOnNode = function(d){ + if ("children" in d){ + d._children = d.children; + d.children = null; + } else { + d.children = d._children; + d._children = null; + } + drawTree(d); + }; + + var drawLabels = function(nodeContainer) { + var height = style.node.height; + nodeContainer.append("text") + .attr("dy", height* 0.35) + .attr("class", "label1") + .attr("dx", style.text.padding) + .style("fill-opacity", 1e-6) + .style("font-size", 1e-6+"px") + .style("cursor", "pointer") + .style("fill", style.text.color) + .style("font-weight", "bold") + .text(function (d) { + return "S/(S+B)=" + Number(d.info.purity).toFixed(3); + }); + nodeContainer.append("text") + .attr("class", "label2") + .attr("dx", style.text.padding) + .attr("dy", height*0.75) + .style("fill-opacity", 1e-6) + .style("cursor", "pointer") + .text(function(d){ + return d.info.IVar!=-1 + ? variables[d.info.IVar]+">"+(Number(d.info.Cut).toFixed(3)) + : ""; + }) + .style("font-size", 1e-6+"px") + .style("fill", style.text.color) + .style("font-weight", "bold"); + }; + + var drawNodes = function(nodeSelector, father){ + var nodeContainer = nodeSelector.enter() + .append("g").attr("class", "nodes") + .attr("transform", function(d){return "translate("+father.x0+","+father.y0+")";}) + .style("cursor","pointer"); + + nodeContainer.filter(function(d){ + return d.parent; + }) + .on("click", clickOnNode) + .on("mouseover", path) + .on("contextmenu", function(d, i){ + d3.event.preventDefault(); + makePathNodesBigger(d); + }) + .on("mouseleave", function(d, i){ + if (d.bigger) makePathNodesBigger(d, i, 1); + return path(d, i, 1); + }); + + nodeContainer.append("rect") + .attr("width", 1e-6) + .attr("height", 1e-6); + + drawLabels(nodeContainer); + + nodeSelector.transition().duration(style.aduration) + .attr("transform", function(d){ + return "translate(" + + (d.x+style.node.width*0.5) + "," + + (d.y-style.node.height) + ")"; + }); + + nodeSelector.select("rect").transition().duration(style.aduration) + .attr("width", style.node.width) + .attr("height", style.node.height) + .attr("fill", function(d){return nodeColor(Number(d.info.purity));}) + .style("stroke-width", style.node.swidth) + .style("stroke", function(d){ + return (d._children) ? style.node.colors.closed : ""; + }); + + nodeSelector.selectAll("text") + .transition().duration(style.aduration) + .style("font-size", function(d) { + var l1 = "S/(S+B)=" + Number(d.info.purity).toFixed(3); + var l2 = d.info.IVar!=-1 + ? variables[d.info.IVar]+">"+(Number(d.info.Cut).toFixed(3)) + : ""; + d.font_size = 1.5*(style.node.width-2*Number(style.node.swidth.replace("px","")))/Math.max(l1.length, l2.length); + return d.font_size+"px"; + }) + .attr("dx", style.text.padding) + .attr("dy", function(d){ + return ((d3.select(this).attr("class")=="label1")? (style.node.height * 0.35) : (style.node.height * 0.75))+"px"; }) + .style("fill-opacity", 1); + + var nodeExit = nodeSelector.exit() + .transition().duration(style.aduration) + .attr("transform", function(d){ + return "translate(" + + (father.x+style.node.width) + "," + + father.y + ")"; + }) + .remove(); + + nodeExit.select("rect") + .attr("width", 1e-6) + .attr("height", 1e-6); + + nodeExit.selectAll("text") + .style("font-size", 1e-6+"px") + .style("fill-opacity", 1e-6); + }; + + var drawLinks = function(linkSelector, father){ + linkSelector.enter() + .insert("path", "g") + .attr("class", "link") + .attr("d", function(d, i){ + var o = {x:father.x0, y:father.y0}; + return d3path({source: o, target: o},i, 1); + }); + + linkSelector.transition().duration(style.aduration) + .attr("d", d3path) + .style("fill", "none") + .style("stroke", style.link.colors.default) + .style("stroke-width", style.link.width) + .attr("id", function(d){return "link"+d.target.id;}); + + linkSelector.exit() + .transition().duration(style.aduration) + .attr("d", function(d, i){ + var o = {x:father.x+style.node.width, y:father.y}; + return d3path({source:o, target:o},i, 1); + }) + .remove(); + }; + + var path = function(node, i, clear){ + svg.selectAll("path.link").filter(function(d){return d.target.id==node.id;}) + .style("stroke-width", (clear) ? style.link.width : style.link.focus_width) + .style("stroke", (clear) ? style.link.colors.default : style.link.colors.focus); + + svg.selectAll("g.nodes rect").filter(function(d){return d.id==node.id;}) + .style("stroke-width", style.node.swidth) + .style("stroke", function(d){ + return (clear) + ? (d._children) ? style.node.colors.closed : nodeColor(d.info.purity) + : style.node.colors.focus; + }); + + if (node.parent) path(node.parent, i, clear); + }; + + var makePathNodesBigger = function(node, i, clear){ + var width = (clear) ? style.node.width : 2*style.node.width, + height = (clear) ? style.node.height : 1.5*style.node.height; + console.log("anim height:"+String(height)); + svg.selectAll("g.nodes rect").filter(function(d){d.bigger=(clear) ? false : true; return d.id==node.id;}) + .transition().duration(style.aduration/2) + .attr("width", width+"px") + .attr("height", height+"px"); + + svg.selectAll("g.nodes text").filter(function(d){return d.id==node.id;}) + .transition().duration(style.aduration/2) + .style("font-size", function(d){ + return ((clear) ? d.font_size : 2*d.font_size)+"px"; + }) + .attr("dx", (clear) ? style.text.padding : (2*Number(style.text.padding.replace("px", ""))+"px")) + .attr("dy", function(){ + return ((d3.select(this).attr("class")=="label1")? (height * 0.35) : (height * 0.75))+"px"; + }); + if (node.parent) makePathNodesBigger(node.parent, i, clear); + }; + + var drawTree = function(father, nox0y0Calc){ + updateSizesColors(); + var nodes = d3tree.nodes(roottree), + links = d3tree.links(nodes); + + var maxDepth = 0; + nodes.forEach(function(d){ + if (maxDepthmax) max = pur; + } + return [min, max]; + }; + + var updateSizesColors = function(first){ + var nodes = d3tree.nodes(roottree); + var tree; + for(var i in nodes){ + if (!nodes[i].parent){ + tree = nodes[i]; + break; + } + } + var height = treeHeight(tree); + + style.node.height = canvas.height/(height+1)-style.node.yspace; + if (style.node.height>style.node.mheight) style.node.height = style.node.mheight; + var corr = 0; + while(height!=0){ + if (!(height%4)) corr++; + height /= 4; + } + style.node.width = canvas.width/(treeWidth(nodes)+1-corr)-style.node.xspace; + if (style.node.width>style.node.mwidth) style.node.height = style.node.mwidth; + + d3tree.size([canvas.width, canvas.height]); + + nodeColor.domain(purityToColor(nodes)); + }; + + var drawLegend = function(svgOriginal){ + var labels = [ + {text: "Pure Backg.", id: "label1", color: nodeColor(nodeColor.domain()[0]), x:5,y:5}, + {text: "Pure Signal", id: "label2", color: nodeColor(nodeColor.domain()[1]), x:5,y:40} + ]; + var legend = svgOriginal.append("g") + .attr("transform", "translate(5,5)"); + + var group = legend.selectAll("g") + .data(labels, function(d){return d.id;}) + .enter() + .append("g") + .style("cursor", "pointer") + .attr("transform", function(d){return "translate("+d.x+","+d.y+")";}); + + group.on("mouseover", function(d){ + d3.select("#"+d.id).style("font-weight", "bold"); + d3.select("#"+d.id+"_rect").attr("width", style.legend.rect_fucus_width); + }); + group.on("mouseout", function(d){ + d3.select("#"+d.id).style("font-weight", "normal"); + d3.select("#"+d.id+"_rect").attr("width", style.legend.rect_width); + + }); + + group.append("rect") + .attr("id", function(d){return d.id+"_rect";}) + .attr("width", style.legend.rect_width) + .attr("height", style.legend.rect_height) + .attr("fill", function(d){return d.color;}); + + group.append("text") + .attr("id", function(d){return d.id;}) + .attr("x", function(d){return 5;}) + .attr("y", function(d){return 20;}) + .text(function(d){return d.text;}) + .style("fill", style.text.color); + }; + + var openSubTree = function(node){ + if ("_children" in node && node.children==null){ + node.children = node._children; + node._children = null; + } + if ("children" in node && node.children!=null){ + for(var i in node.children){ + openSubTree(node.children[i]); + } + } + }; + + var findFathers = function(start){ + var fathers = []; + var Q = []; + Q.push(start); + while(Q.length>0){ + var node = Q.shift(); + if ("_children" in node && node._children!=null){ + fathers.push(node); + } + if (node.children!=null) { + for (var i = 0; i < node.children.length; i++) { + Q.push(node.children[i]); + } + } + } + return fathers.length>0 ? fathers : start; + }; + + + DecisionTree.draw = function(divid, pyobj){ + var div = d3.select("#"+divid); + + roottree = pyobj["tree"]; + variables = pyobj["variables"]; + + if (Object.size(roottree)==0){ + div.innerHTML = "Tree empty..."; + return; + } + + canvas = { + width: div.property("style")["width"], + height: div.property("style")["height"] + }; + + svg = div.append("svg") + .attr("width", canvas.width) + .attr("height", canvas.height); + var svgOriginal = svg; + Object.keys(canvas).forEach(function (key) { + canvas[key] = Number(canvas[key].replace("px","")); + canvas[key] -= key=="width" ? 2*style.margin.x+style.node.width : 2*style.margin.y+style.node.height; + }); + + updateSizesColors(1); + + var zoom = d3.behavior.zoom() + .scaleExtent([1, 10]) + .on("zoom", function(){ + svg.attr("transform", + "translate("+(-style.node.width)+", "+style.node.height + +")translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); + }); + svg = svg + .on("dblclick", function(){ + zoom.scale(1); + zoom.translate([0, 0]); + svg.transition().attr("transform", "translate("+(-style.node.width)+", "+style.node.height+")"); + }) + .append("g").call(zoom).append("g") + .attr("transform", "translate("+(-style.node.width)+", "+style.node.height+")"); + + drawLegend(svgOriginal); + + drawTree(roottree); + + div.append("button") + .style("position", "relative") + .style("top", "-"+style.buttons.reset.height) + .style("width", style.buttons.reset.width) + .style("height", style.buttons.reset.height) + .style("opacity", style.buttons.reset.alpha) + .style("background", style.buttons.reset.background) + .style("background-size", "contain") + .style("background-image", "url("+style.buttons.reset.img+")") + .style("cursor", "pointer") + .style("border", "none") + .on("mouseover", function(){ + d3.select(this).style("opacity", "1"); + }) + .on("mouseout", function(){ + d3.select(this).style("opacity", style.buttons.reset.alpha); + }) + .on("click", function(){ + zoom.scale(1); + zoom.translate([0, 0]); + svg.transition().attr("transform", "translate("+(-style.node.width)+", "+style.node.height+")"); + var fathers = findFathers(roottree); + for(var i=0;i"+Number(t.info.Cut).toFixed(3):""}).style("font-size",1e-6+"px").style("fill",r.text.color).style("font-weight","bold")};var f=function(t,n){var o=t.enter().append("g").attr("class","nodes").attr("transform",function(t){return"translate("+n.x0+","+n.y0+")"}).style("cursor","pointer");o.filter(function(t){return t.parent}).on("click",u).on("mouseover",p).on("contextmenu",function(t,n){e.event.preventDefault();y(t)}).on("mouseleave",function(t,e){if(t.bigger)y(t,e,1);return p(t,e,1)});o.append("rect").attr("width",1e-6).attr("height",1e-6);h(o);t.transition().duration(r.aduration).attr("transform",function(t){return"translate("+(t.x+r.node.width*.5)+","+(t.y-r.node.height)+")"});t.select("rect").transition().duration(r.aduration).attr("width",r.node.width).attr("height",r.node.height).attr("fill",function(t){return i(Number(t.info.purity))}).style("stroke-width",r.node.swidth).style("stroke",function(t){return t._children?r.node.colors.closed:""});t.selectAll("text").transition().duration(r.aduration).style("font-size",function(t){var e="S/(S+B)="+Number(t.info.purity).toFixed(3);var n=t.info.IVar!=-1?l[t.info.IVar]+">"+Number(t.info.Cut).toFixed(3):"";t.font_size=1.5*(r.node.width-2*Number(r.node.swidth.replace("px","")))/Math.max(e.length,n.length);return t.font_size+"px"}).attr("dx",r.text.padding).attr("dy",function(t){return(e.select(this).attr("class")=="label1"?r.node.height*.35:r.node.height*.75)+"px"}).style("fill-opacity",1);var a=t.exit().transition().duration(r.aduration).attr("transform",function(t){return"translate("+(n.x+r.node.width)+","+n.y+")"}).remove();a.select("rect").attr("width",1e-6).attr("height",1e-6);a.selectAll("text").style("font-size",1e-6+"px").style("fill-opacity",1e-6)};var g=function(t,e){t.enter().insert("path","g").attr("class","link").attr("d",function(t,n){var r={x:e.x0,y:e.y0};return c({source:r,target:r},n,1)});t.transition().duration(r.aduration).attr("d",c).style("fill","none").style("stroke",r.link.colors.default).style("stroke-width",r.link.width).attr("id",function(t){return"link"+t.target.id});t.exit().transition().duration(r.aduration).attr("d",function(t,n){var i={x:e.x+r.node.width,y:e.y};return c({source:i,target:i},n,1)}).remove()};var p=function(t,e,n){a.selectAll("path.link").filter(function(e){return e.target.id==t.id}).style("stroke-width",n?r.link.width:r.link.focus_width).style("stroke",n?r.link.colors.default:r.link.colors.focus);a.selectAll("g.nodes rect").filter(function(e){return e.id==t.id}).style("stroke-width",r.node.swidth).style("stroke",function(t){return n?t._children?r.node.colors.closed:i(t.info.purity):r.node.colors.focus});if(t.parent)p(t.parent,e,n)};var y=function(t,n,i){var o=i?r.node.width:2*r.node.width,d=i?r.node.height:1.5*r.node.height;console.log("anim height:"+String(d));a.selectAll("g.nodes rect").filter(function(e){e.bigger=i?false:true;return e.id==t.id}).transition().duration(r.aduration/2).attr("width",o+"px").attr("height",d+"px");a.selectAll("g.nodes text").filter(function(e){return e.id==t.id}).transition().duration(r.aduration/2).style("font-size",function(t){return(i?t.font_size:2*t.font_size)+"px"}).attr("dx",i?r.text.padding:2*Number(r.text.padding.replace("px",""))+"px").attr("dy",function(){return(e.select(this).attr("class")=="label1"?d*.35:d*.75)+"px"});if(t.parent)y(t.parent,n,i)};var x=function(t,e){b();var n=s.nodes(d),i=s.links(n);var l=0;n.forEach(function(t){if(ln)n=r}return[e,n]};var b=function(t){var e=s.nodes(d);var n;for(var a in e){if(!e[a].parent){n=e[a];break}}var l=v(n);r.node.height=o.height/(l+1)-r.node.yspace;if(r.node.height>r.node.mheight)r.node.height=r.node.mheight;var c=0;while(l!=0){if(!(l%4))c++;l/=4}r.node.width=o.width/(w(e)+1-c)-r.node.xspace;if(r.node.width>r.node.mwidth)r.node.height=r.node.mwidth;s.size([o.width,o.height]);i.domain(m(e))};var k=function(t){var n=[{text:"Pure Backg.",id:"label1",color:i(i.domain()[0]),x:5,y:5},{text:"Pure Signal",id:"label2",color:i(i.domain()[1]),x:5,y:40}];var o=t.append("g").attr("transform","translate(5,5)");var a=o.selectAll("g").data(n,function(t){return t.id}).enter().append("g").style("cursor","pointer").attr("transform",function(t){return"translate("+t.x+","+t.y+")"});a.on("mouseover",function(t){e.select("#"+t.id).style("font-weight","bold");e.select("#"+t.id+"_rect").attr("width",r.legend.rect_fucus_width)});a.on("mouseout",function(t){e.select("#"+t.id).style("font-weight","normal");e.select("#"+t.id+"_rect").attr("width",r.legend.rect_width)});a.append("rect").attr("id",function(t){return t.id+"_rect"}).attr("width",r.legend.rect_width).attr("height",r.legend.rect_height).attr("fill",function(t){return t.color});a.append("text").attr("id",function(t){return t.id}).attr("x",function(t){return 5}).attr("y",function(t){return 20}).text(function(t){return t.text}).style("fill",r.text.color)};var _=function(t){if("_children"in t&&t.children==null){t.children=t._children;t._children=null}if("children"in t&&t.children!=null){for(var e in t.children){_(t.children[e])}}};var z=function(t){var e=[];var n=[];n.push(t);while(n.length>0){var r=n.shift();if("_children"in r&&r._children!=null){e.push(r)}if(r.children!=null){for(var i=0;i0?e:t};t.draw=function(t,n){var i=e.select("#"+t);d=n["tree"];l=n["variables"];if(Object.size(d)==0){i.innerHTML="Tree empty...";return}o={width:i.property("style")["width"],height:i.property("style")["height"]};a=i.append("svg").attr("width",o.width).attr("height",o.height);var s=a;Object.keys(o).forEach(function(t){o[t]=Number(o[t].replace("px",""));o[t]-=t=="width"?2*r.margin.x+r.node.width:2*r.margin.y+r.node.height});b(1);var c=e.behavior.zoom().scaleExtent([1,10]).on("zoom",function(){a.attr("transform","translate("+-r.node.width+", "+r.node.height+")translate("+e.event.translate+")scale("+e.event.scale+")")});a=a.on("dblclick",function(){c.scale(1);c.translate([0,0]);a.transition().attr("transform","translate("+-r.node.width+", "+r.node.height+")")}).append("g").call(c).append("g").attr("transform","translate("+-r.node.width+", "+r.node.height+")");k(s);x(d);i.append("button").style("position","relative").style("top","-"+r.buttons.reset.height).style("width",r.buttons.reset.width).style("height",r.buttons.reset.height).style("opacity",r.buttons.reset.alpha).style("background",r.buttons.reset.background).style("background-size","contain").style("background-image","url("+r.buttons.reset.img+")").style("cursor","pointer").style("border","none").on("mouseover",function(){e.select(this).style("opacity","1")}).on("mouseout",function(){e.select(this).style("opacity",r.buttons.reset.alpha)}).on("click",function(){c.scale(1);c.translate([0,0]);a.transition().attr("transform","translate("+-r.node.width+", "+r.node.height+")");var t=z(d);for(var e=0;e + * Created: 5/14/16 + */ + +(function(factory){ + + var JSROOT_source_dir = "https://root.cern.ch/js/notebook/scripts/"; + + var url = ""; + if (requirejs.s.contexts.hasOwnProperty("_")) { + url = requirejs.s.contexts._.config.paths["JsMVA"].replace("JsMVA.min",""); + } + if ((console!==undefined) && (typeof console.log == 'function')) { + if (url!=""){ + console.log("JsMVA source dir:" + url.substring(0, url.length-1)); + } else { + console.log("JsMVA source dir can't be resolved, requireJS doesn't have context '_', this will be a problem!"); + } + } + + require.config({ + paths: { + 'd3': JSROOT_source_dir+'d3.v3.min', + 'JsRootCore': JSROOT_source_dir+'JSRootCore.min', + 'nn': url+'NeuralNetwork.min', + 'dtree': url+'DecisionTree.min', + 'NetworkDesigner': url+'NetworkDesigner.min' + } + }); + + define(['JsRootCore'], function(jsroot){ + return factory({}, jsroot); + }); + +}(function(JsMVA, JSROOT){ + + JsMVA.drawTH2 = function(divid, dat_json){ + var obj = JSROOT.parse(dat_json); + JSROOT.draw(divid, obj, "colz;PAL50;text"); + }; + + JsMVA.drawDNNMap = function(divid, dat_json){ + var obj = JSROOT.parse(dat_json); + JSROOT.draw(divid, obj, "colz;PAL50"); + }; + + JsMVA.draw = function(divid, dat_json){ + var obj = JSROOT.parse(dat_json); + JSROOT.draw(divid, obj); + }; + + JsMVA.drawNeuralNetwork = function(divid, dat_json){ + var obj = JSON.parse(dat_json); + require(['nn'], function(nn){ + nn.draw(divid, obj); + }); + }; + + JsMVA.drawDecisionTree = function(divid, dat_json){ + require(['dtree'], function(dtree){ + var obj = JSON.parse(dat_json); + dtree.draw(divid, obj); + }); + }; + + var drawLabel = function(divid, obj){ + require(['d3'], function(d3){ + var csvg = d3.select("#"+divid+">.interactivePlot_Labels")[0][0]; + if (csvg!=null) return; + var div = d3.select("#"+divid).style("position", "relative"); + var svg = div.append("svg").attr("class", "interactivePlot_Labels") + .attr("width", "200px") + .attr("height", "50px") + .style({"position":"absolute", "top": "8px", "right": "8px"}); + var attr = { + "pos": {"x": 150, "y": 0}, + "rect": {"width": 10, "height":10}, + "dy": 20, + "padding": 10 + }; + canvas = { + width: 160, + height: 70 + }; + var container = svg.append("g").attr("id", "legend"); + container.selectAll("g") + .data(obj.fGraphs.arr) + .enter() + .append("g") + .each(function(d, i){ + var g = d3.select(this); + g.append("rect") + .attr("x", canvas.width-attr.pos.x) + .attr("y", attr.pos.y+i*attr.dy) + .attr("width", attr.rect.width) + .attr("height", attr.rect.height) + .style("fill", function(d){return JSROOT.Painter.root_colors[d.fFillColor];}); + g.append("text") + .attr("x", canvas.width-attr.pos.x+attr.rect.width+attr.padding) + .attr("y", attr.pos.y+i*attr.dy+attr.rect.height) + .text(function(d){return d.fTitle;}) + .style("fill", function(d){return JSROOT.Painter.root_colors[d.fFillColor];}); + }); + div.append("svg").attr("width", "55px").attr("height", "20px") + .style({"position":"absolute", "bottom": "15px", "right": "40px"}) + .append("text") + .attr("x", "5px") + .attr("y", "15px") + .text(obj.fGraphs.arr[0].fTitle.indexOf("Error on training set")!=-1 ? "Epoch" : "#tree") + .style({"font-size": "16px"}); + }); + }; + + + JsMVA.drawTrainingTestingErrors = function(divid, dat_json){ + var obj = JSROOT.parse(dat_json); + JSROOT.draw(divid, obj); + drawLabel(divid, obj); + }; + + JsMVA.updateTrainingTestingErrors = function(divid, dat_json){ + var obj = JSROOT.parse(dat_json); + JSROOT.redraw(divid, obj); + drawLabel(divid, obj); + }; + + JsMVA.NetworkDesigner = function(divid, dat_json){ + require(['NetworkDesigner'], function (nd) { + nd.draw(divid); + }); + }; + + JsMVA.outputShowCorrelationMatrix = function(divid){ + require(['jquery', 'jquery-ui'], function($){ + var th2 = JSROOT.parse($("#"+divid).html()); + if (!$("#dialog_"+divid).length || $("#dialog_"+divid).length < 1) { + $("#" + divid).parent().append("
"); + JSROOT.draw("dialog_" + divid, th2, "colz;PAL50;text"); + } + $("#dialog_" + divid).dialog({ + autoOpen: true, + width: 600, + show: { + effect: "blind", + duration: 1000 + }, + hide: { + effect: "explode", + duration: 500 + } + }); + + }); + }; + + return JsMVA; +})); diff --git a/etc/notebook/JsMVA/js/JsMVA.min.js b/etc/notebook/JsMVA/js/JsMVA.min.js new file mode 100644 index 0000000000000..e1bdebef015b0 --- /dev/null +++ b/etc/notebook/JsMVA/js/JsMVA.min.js @@ -0,0 +1 @@ +(function(t){var e="https://root.cern.ch/js/notebook/scripts/";var r="";if(requirejs.s.contexts.hasOwnProperty("_")){r=requirejs.s.contexts._.config.paths["JsMVA"].replace("JsMVA.min","")}if(console!==undefined&&typeof console.log=="function"){if(r!=""){console.log("JsMVA source dir:"+r.substring(0,r.length-1))}else{console.log("JsMVA source dir can't be resolved, requireJS doesn't have context '_', this will be a problem!")}}require.config({paths:{d3:e+"d3.v3.min",JsRootCore:e+"JSRootCore.min",nn:r+"NeuralNetwork.min",dtree:r+"DecisionTree.min",NetworkDesigner:r+"NetworkDesigner.min"}});define(["JsRootCore"],function(e){return t({},e)})})(function(t,e){t.drawTH2=function(t,r){var i=e.parse(r);e.draw(t,i,"colz;PAL50;text")};t.drawDNNMap=function(t,r){var i=e.parse(r);e.draw(t,i,"colz;PAL50")};t.draw=function(t,r){var i=e.parse(r);e.draw(t,i)};t.drawNeuralNetwork=function(t,e){var r=JSON.parse(e);require(["nn"],function(e){e.draw(t,r)})};t.drawDecisionTree=function(t,e){require(["dtree"],function(r){var i=JSON.parse(e);r.draw(t,i)})};var r=function(t,r){require(["d3"],function(i){var n=i.select("#"+t+">.interactivePlot_Labels")[0][0];if(n!=null)return;var o=i.select("#"+t).style("position","relative");var a=o.append("svg").attr("class","interactivePlot_Labels").attr("width","200px").attr("height","50px").style({position:"absolute",top:"8px",right:"8px"});var s={pos:{x:150,y:0},rect:{width:10,height:10},dy:20,padding:10};canvas={width:160,height:70};var d=a.append("g").attr("id","legend");d.selectAll("g").data(r.fGraphs.arr).enter().append("g").each(function(t,r){var n=i.select(this);n.append("rect").attr("x",canvas.width-s.pos.x).attr("y",s.pos.y+r*s.dy).attr("width",s.rect.width).attr("height",s.rect.height).style("fill",function(t){return e.Painter.root_colors[t.fFillColor]});n.append("text").attr("x",canvas.width-s.pos.x+s.rect.width+s.padding).attr("y",s.pos.y+r*s.dy+s.rect.height).text(function(t){return t.fTitle}).style("fill",function(t){return e.Painter.root_colors[t.fFillColor]})});o.append("svg").attr("width","55px").attr("height","20px").style({position:"absolute",bottom:"15px",right:"40px"}).append("text").attr("x","5px").attr("y","15px").text(r.fGraphs.arr[0].fTitle.indexOf("Error on training set")!=-1?"Epoch":"#tree").style({"font-size":"16px"})})};t.drawTrainingTestingErrors=function(t,i){var n=e.parse(i);e.draw(t,n);r(t,n)};t.updateTrainingTestingErrors=function(t,i){var n=e.parse(i);e.redraw(t,n);r(t,n)};t.NetworkDesigner=function(t,e){require(["NetworkDesigner"],function(e){e.draw(t)})};t.outputShowCorrelationMatrix=function(t){require(["jquery","jquery-ui"],function(r){var i=e.parse(r("#"+t).html());if(!r("#dialog_"+t).length||r("#dialog_"+t).length<1){r("#"+t).parent().append("
");e.draw("dialog_"+t,i,"colz;PAL50;text")}r("#dialog_"+t).dialog({autoOpen:true,width:600,show:{effect:"blind",duration:1e3},hide:{effect:"explode",duration:500}})})};return t}); \ No newline at end of file diff --git a/etc/notebook/JsMVA/js/NetworkDesigner.js b/etc/notebook/JsMVA/js/NetworkDesigner.js new file mode 100644 index 0000000000000..ed771fbf8130f --- /dev/null +++ b/etc/notebook/JsMVA/js/NetworkDesigner.js @@ -0,0 +1,764 @@ +/** + * This is the Deep Neural Network Designer: it produces an interactive interface, where we can build easily the + * deep network. This script can transform the graphical representation of the network to an option string. + * By using IPython JavaScript API, this script is able to communicate with the python kernel and book the method, + * and print out the kernel response. + * Used libraries/plugins: + * - jquery + * - jquery-ui + * - jquery.connections + * - jquery-timing + * Author: Attila Bagoly + * Created: 8/3/2016 + */ + +(function (factory) { + var baseURL = require.toUrl("./NetworkDesigner.js"); + baseURL = baseURL.substr(0, baseURL.lastIndexOf("/")+1); + require.config({ + paths: { + "jquery-connections": baseURL + "jquery.connections.min", + "jquery-timing": baseURL + "jquery-timing.min", + "d3": "https://root.cern.ch/js/notebook/scripts/d3.v3.min" + }, + shim: { + "jquery-ui": { + exports: "jquery.ui", + deps: ['jquery'] + }, + "jquery-connections":{ + deps: ['jquery'] + }, + "jquery-timing": { + deps: ['jquery'] + } + } + }); + define(['jquery', 'd3', 'jquery-ui', 'jquery-connections', 'jquery-timing'], function (jQ, d3) { + return factory({}, jQ, d3); + }) +})(function (NetworkDesigner, $, d3) { + + var containerID; + + var globalOptions = { + H: false, + V: false, + VerbosityLevel: "Default", + VarTransform: "Normalize", + ErrorStrategy: "CROSSENTROPY", + WeightInitialization: "XAVIER", + CreateMVAPdfs: false, + IgnoreNegWeightsInTraining: false, + SignalWeightsSum: 1000.0, + BackgroundWeightsSum: 1000.0 + }; + + var layers = Array(); + var layersID; + var layer_ids = { + ReLU: "layer_relu", + TANH: "layer_tanh", + SYMMReLU: "layer_symmrelu", + SOFTSIGN: "layer_SOFTSIGN", + Sigmoid: "layer_sigmoid", + LINEAR: "layer_linear", + GAUSS: "layer_gauss" + }; + + var connection_queue = []; + + var helpMessages = []; + + var colors = { + layer: { + input: "#00A000", + output: "#F6BD00", + hidden: ["steelblue", "red"] + } + }; + + var layer_color = d3.scale.linear() + .domain([0, 100]).range(colors.layer.hidden) + .interpolate(d3.interpolateRgb); + + + var getText = function(key){ + for(var k in layer_ids){ + if (key==layer_ids[k]) return k; + } + }; + + var drawConnection = function(e, id){ + if ($("#drawConnectionHelper")[0]!==undefined){ + $("#drawConnectionHelper").remove(); + } + connection_queue = []; + $("#"+containerID).append("
div_"+id+"
"); + var helper = $("#drawConnectionHelper"); + var offset = $("#"+containerID).offset(); + helper.css({ + top: (e.pageY -offset.top) + "px", + left: (e.pageX -offset.left)+ "px" + }); + helper.draggable(); + var counter = 0; + helper.on("click", function () { + if (counter==2) { + connection_queue.push($(this).text()); + $(this).remove(); + counter = 0; + } else { + counter++; + } + }); + $("#div_"+id).connections({to: "#drawConnectionHelper"}); + }; + + var initLayer = function(i, type){ + layers[ i ] = { + type: type, + neurons: 0, + connected: { + before: null, + after: null + }, + trainingStrategy: { + LearningRate: 1e-5, + Momentum: 0.3, + Repetitions: 3, + ConvergenceSteps: 100, + BatchSize: 30, + TestRepetitions: 7, + WeightDecay: 0.0, + Regularization: "NONE", + DropConfig: "", + DropRepetitions: 3, + Multithreading: true + } + }; + if (type.toLowerCase().indexOf("output")!=-1){ + layers[i]["func"] + } + }; + + var connectLayers = function(target){ + var source; + if (connection_queue.length!=1) { + var helper = $("#drawConnectionHelper"); + source = String(helper.text()); + helper.remove(); + } else { + source = String(connection_queue.pop()); + } + $("#"+source).connections({to: "#"+target}); + updateLayer(source, false, false, target); + }; + + var updateLayer = function (id, neuron_num, connected_before, connected_after) { + var arr = id.split("_"); + var idx = Number(arr[arr.length-1]); + if (neuron_num) layers[idx].neurons = neuron_num; + if (connected_before){ + arr = connected_before.split("_"); + var other_idx = Number(arr[arr.length-1]); + layers[idx].connected.before = other_idx; + layers[other_idx].connected.after = idx; + } + if (connected_after){ + arr = connected_after.split("_"); + var other_idx = Number(arr[arr.length-1]); + layers[other_idx].connected.before = idx; + layers[idx].connected.after = other_idx; + } + }; + + var addLayer = function(id, addButton, connectionSource, connectionTarget, addText){ + var lid = id + "_" + layersID++; + $("#"+containerID).append("
"); + var layer = $("#div_"+lid); + var bkg_color = id.indexOf("input")!=-1 ? colors.layer.input + : id.indexOf("output")!=-1 ? colors.layer.output + : colors.layer.hidden[0]; + layer.css({ + position: "relative", + top: "100px", + left: "100px", + "z-index": 90, + "background-color": bkg_color + }); + layer.draggable(); + if (addButton) { + layer.html("" + + "" + + "" + getText(id) + ""); + layer.on("click", "#options_" + lid, function () { + $("#neuronnum_layer_dialog").data('buttonID', lid); + $("#neuronnum_layer_dialog").dialog("open"); + }); + } + if (connectionSource){ + var counter = 0; + layer.on("click",function(e){ + if (counter>=2) { + drawConnection(e, lid); + counter = 0; + } else { + counter += 1; + } + }); + } + if (connectionTarget) { + layer.droppable({ + drop: function () { + connectLayers("div_" + lid); + } + }); + } + if (addText){ + layer.append(addText); + if (addText.toLowerCase().indexOf("output")!=-1){ + layer.append(""); + layer.on("click", "#options_" + lid, function () { + $("#output_layer_dialog").data('buttonID', lid); + $("#output_layer_dialog").dialog("open"); + }); + } + } + var arr = lid.split("_"); + initLayer( Number(arr[arr.length-1]), arr[arr.length-2]); + }; + + var addNeuronsToLayer = function(){ + $("#"+containerID).append(""); + $("#neuronnum_layer_dialog").dialog({ + autoOpen: false, + show: { + effect: "fade", + duration: 500 + }, + hide: { + effect: "fade", + duration: 500 + }, + open: function(){ + $("#neuronnum_layer_dialog form input").val($("#button_"+$(this).data("buttonID")).val()); + }, + buttons: { + "OK": function(){ + var neuron_num = $("#neuronnum_layer_dialog form input").val(); + var button = $("#button_"+$(this).data("buttonID")); + //button.css({left: String(40-neuron_num.length*5)+"%"}); + neuron_num = Number(neuron_num); + button.val(neuron_num); + button.parent().css({ "background": layer_color(neuron_num)}); + updateLayer($(this).data("buttonID"), neuron_num); + $(this).dialog("close"); + }, + "Close": function() { + $(this).dialog("close"); + } + } + }).data('buttonID', '-1'); + $("#neuronnum_layer_dialog").on("click", "#training_strategy_button", function () { + var d = $("#trainingstrategy_dialog"); + d.data("formID", $("#neuronnum_layer_dialog").data("buttonID")); + d.dialog("open"); + }); + $("#neuronnum_layer_dialog form input").on("keypress keydown keyup", function(e) { + e.stopPropagation(); + }); + }; + + var outputLayerOptionsDialog = function(){ + var layertype = ""; + $("#"+containerID).append(""); + $("#output_layer_dialog").dialog({ + autoOpen: false, + show: { + effect: "fade", + duration: 500 + }, + hide: { + effect: "fade", + duration: 500 + }, + open: function(){ + var arr = $(this).data("buttonID").split("_"); + arr = Number(arr[arr.length-1]); + if (layers[arr].func!==undefined) { + $("#output_layer_type").val(layers[arr].func); + } + }, + buttons: { + "OK": function(){ + var arr = $(this).data("buttonID").split("_"); + arr = Number(arr[arr.length-1]); + layers[arr]["func"] = $("#output_layer_type").val(); + $(this).dialog("close"); + }, + "Close": function() { + $(this).dialog("close"); + } + } + }).data('buttonID', '-1'); + $("#output_layer_dialog").on("click", "#training_strategy_button", function () { + var d = $("#trainingstrategy_dialog"); + d.data("formID", $("#output_layer_dialog").data("buttonID")); + d.dialog("open"); + }); + }; + + var createForm = function(id, form){ + var string = ""; + form.forEach(function(opts, optID){ + string += ""; + if ("title" in opts){ + string += ""; + } else { + string += ""; + } + string += ""; + string += ""; + }); + string += "
"; + if (opts["type"]=="select"){ + string += ""; + } else { + string += ""; + } + string += "
"; + return string; + }; + + var syncForm = function(id, form, options){ + for(var opt in options){ + if (options[opt]===true || options[opt]===false){ + $("#"+id+"_"+opt).prop("checked", options[opt]); + $("#"+id+"_"+opt).change(function(){ + $(this).attr("value", this.checked ? 1 : 0); + }); + if (options[opt]==true){ + $("#"+id+"_" + opt).val(1); + } else { + $("#" + id + "_" + opt).val(0); + } + } else { + $("#"+id+"_" + opt).val(options[opt]); + } + } + }; + + var trainingStrategyForm = function(){ + var form = new Map(); + form.set("LearningRate", { + type: "text" + }); + form.set("Momentum", { + type: "text" + }); + form.set("Repetitions", { + type: "text" + }); + form.set("ConvergenceSteps", { + type: "text" + }); + form.set("BatchSize", { + type: "text" + }); + form.set("TestRepetitions", { + type: "text" + }); + form.set("WeightDecay", { + type: "text" + }); + form.set("Regularization", { + type: "select", + options: ["NONE", "L1", "L2", "L1MAX"] + }); + form.set("DropConfig", { + type: "text" + }); + form.set("DropRepetitions", { + type: "text" + }); + form.set("Multithreading", { + type: "checkbox" + }); + $("#"+containerID).append(""); + $("#trainingstrategy_dialog").dialog({ + autoOpen: false, + width: 400, + show: { + effect: "fade", + duration: 500 + }, + hide: { + effect: "fade", + duration: 500 + }, + open: function(){ + var arr = $(this).data("formID").split("_"); + var i = Number(arr[arr.length-1]); + syncForm("trainingstrategy", form, layers[i].trainingStrategy); + }, + buttons: { + "OK": function(){ + var arr = $(this).data("formID").split("_"); + var idx = Number(arr[arr.length-1]); + arr = $("#trainingstrategy_dialog input, #trainingstrategy_dialog select"); + var id; + for(var i=0;i