How do I interop with this Javascript code, from Fable F#? - fable-f#

I want to create a binding of the Plotly.js library to Fable.
I am looking at this js code
import React from 'react';
import Plot from 'react-plotly.js';
class App extends React.Component {
render() {
return (
<Plot
data={[
{
x: [1, 2, 3],
y: [2, 6, 3],
type: 'scatter',
mode: 'lines+points',
marker: {color: 'red'},
},
{type: 'bar', x: [1, 2, 3], y: [2, 5, 3]},
]}
layout={ {width: 320, height: 240, title: 'A Fancy Plot'} }
/>
);
}
}
and my (faulty) attempt of creating a simple test binding looks like this
open Fable.Core
open Fable.Core.JsInterop
open Browser.Types
open Fable.React
// module Props =
type Chart =
|X of int list
|Y of int List
|Type of string
type IProp =
| Data of obj list
let inline plot (props: IProp) : ReactElement =
ofImport "Plot" "react-plotly.js" props []
let myTrace = createObj [
"x" ==> [1,2,3]
"y" ==> [2,6,3]
"type" ==> "scatter"
"mode" ==> "lines"
]
let myData = Data [myTrace]
let testPlot = plot myData
But obviously it does not work. How do I get it to work? Also, what does {[...]} mean? I am new to Javascript, and as far as I know {...} denotes an object which must contain name value pairs, and [...] denotes an array. So {[...]} seems to denote an object with a single nameless member that is an array, but as far as I know, there are no objects with nameless members.

I have been able to reproduce the example you linked. Please note that I don't Plotly and that I went the empiric way and so things can probably be improved :)
I have created the code as I would probably have done it if I had to use it in my production app. So there is a bit more code than in your question because I don't use createObj.
If you don't like the typed DSL you can always simplify it, remove it and use createObj or anonymous record like I did for the marker property :)
You need to install both react-plotly.js plotly.js in your project.
open Fable.Core.JsInterop
open Fable.Core
open Fable.React
// Define props using DUs this helps create a typed version of the React props
// You can then transform a list of props into an object using `keyValueList`
[<RequireQualifiedAccess>]
type LayoutProps =
| Title of string
| Width of int
| Height of int
// GraphType is marked as a `StringEnum` this means
// the value will be replace at compile time with
// their string representation so:
// `Scatter` becomes `"scatter"`
// You can customise the output by using `[<CompiledName("MyCustomName")>]
[<RequireQualifiedAccess; StringEnum>]
type GraphType =
| Scatter
| Bar
[<RequireQualifiedAccess; StringEnum>]
type GraphMode =
| Lines
| Points
| Markers
| Text
| None
[<RequireQualifiedAccess>]
type DataProps =
| X of obj array
| Y of obj array
| Type of GraphType
| Marker of obj
// This is an helpers to generate the `flagList` waited by Plotly, if you don't like it you can just remove
// member and replace it with `| Mode of string` and so you have to pass the string by yourself
static member Mode (modes : GraphMode seq) : DataProps =
let flags =
modes
|> Seq.map unbox<string> // This is safe to do that because GraphMode is a StringEnum
|> String.concat "+"
unbox ("mode", flags)
[<RequireQualifiedAccess>]
type PlotProps =
| Nothing // Should have real props here is there exist more than Data and Layout
// Here notes that we are asking for an `Array` or Data
// Array being the type expected by the JavaScript library
// `DataProps seq` is our way to represents props
static member Data (dataList : (DataProps seq) array) : PlotProps =
let datas =
dataList
|> Array.map (fun v ->
keyValueList CaseRules.LowerFirst v // Transform the list of props into a JavaScript object
)
unbox ("data", datas)
static member Layout (props : LayoutProps seq) : PlotProps =
unbox ("layout", keyValueList CaseRules.LowerFirst props)
// All the example I saw from react-plotly was using this factory function to transform the plotly library into a React component
// Even, the example you shown if you look at the Babel tab in the live example
let createPlotlyComponent (plotly : obj) = import "default" "react-plotly.js/factory"
// Immport the plotly.js library
let plotlyLib : obj = import "default" "plotly.js"
// Apply the factory on the plotly library
let Plot : obj = createPlotlyComponent plotlyLib
// Helper function to instantiate the react components
// This is really low level, in general we use `ofImport` like you did but if I use `ofImport` then I got a React error
let inline renderPlot (plot : obj) (props : PlotProps list) =
ReactBindings.React.createElement(plot, (keyValueList CaseRules.LowerFirst props), [])
let root =
// Here we can render the plot using our Typed DSL
renderPlot
Plot
[
PlotProps.Data
[|
[
DataProps.X [| 1; 2; 3 |]
DataProps.Y [| 2; 6; 3 |]
DataProps.Type GraphType.Scatter
DataProps.Mode
[
GraphMode.Lines
GraphMode.Points
]
DataProps.Marker {| color = "red" |}
]
[
DataProps.Type GraphType.Bar
DataProps.X [| 1; 2; 3 |]
DataProps.Y [| 2; 5; 3 |]
]
|]
PlotProps.Layout
[
LayoutProps.Width 640
LayoutProps.Height 480
LayoutProps.Title "A Fancy Plot"
]
]

I'm a bit late to the party here, but wanted to give you a different option if you're still looking to use plotly.js with Fable.
I've been working on bindings for plotly.js for the past month or so, and it's in a pretty usable state as of now. That being said, I wouldn't say it's production ready.
This is what the example you want to convert would look like written with Feliz.Plotly:
open Feliz
open Feliz.Plotly
let chart () =
Plotly.plot [
plot.traces [
traces.scatter [
scatter.x [ 1; 2; 3 ]
scatter.y [ 2; 6; 3 ]
scatter.mode [
scatter.mode.lines
scatter.mode.markers
]
scatter.marker [
marker.color color.red
]
]
traces.bar [
bar.x [ 1; 2; 3 ]
bar.y [ 2; 5; 3 ]
]
]
plot.layout [
layout.width 320
layout.height 240
layout.title [
title.text "A Fancy Plot"
]
]
]
You can find more information out here.

Related

Assigning the result of corrplot to a variable

I am using the function corrplot from corrplot package to generate a plot of a correlation matrix created with cor.test (psych package).
When I try to save the result into a variable, the variable is NULL.
Anyone could advice, please?
library(corrplot)
library(psych)
library(ggpubr)
data(iris)
res_pearson.c_setosa<-iris%>%
filter(Species=="setosa")%>%
select(Sepal.Length:Petal.Width)%>%
corr.test(., y = NULL, use = "complete",method="pearson",adjust="bonferroni", alpha=.05,ci=TRUE,minlength=5)
corr.a<-corrplot(res_pearson.c_setosa$r[,1:3],
type="lower",
order="original",
p.mat = res_pearson.c_setosa$p[,1:3],
sig.level = 0.05,
insig = "blank",
col=col4(10),
tl.pos = "ld",
tl.cex = .8,
tl.srt=45,
tl.col = "black",
cl.cex = .8)+
my.theme #this is a theme() piece, but if I take this away, the result is a list rather than a plot
You can create your own function where you put recordPlot at the end to save the plot. After that you can save the output of the function in a variable. Here is a reproducible example:
library(corrplot)
library(psych)
library(ggpubr)
library(dplyr)
data(iris)
res_pearson.c_setosa<-iris%>%
filter(Species=="setosa")%>%
select(Sepal.Length:Petal.Width)%>%
corr.test(., y = NULL, use = "complete",method="pearson",adjust="bonferroni", alpha=.05,ci=TRUE,minlength=5)
your_function <- function(ff){
corr.a<-corrplot(ff$r[,1:3],
type="lower",
order="original",
p.mat = ff$p[,1:3],
sig.level = 0.05,
insig = "blank",
#col=col4(10),
tl.pos = "ld",
tl.cex = .8,
tl.srt=45,
tl.col = "black",
cl.cex = .8)
#my.theme #this is a theme() piece, but if I take this away, the result is a list rather than a plot
recordPlot() # save the latest plot
}
your_function(res_pearson.c_setosa)
p <- your_function(res_pearson.c_setosa)
p
Created on 2022-07-13 by the reprex package (v2.0.1)
As you can see, the variable p outputs the plot.

feed data to fitDataset()

I'm trying to fit a model using fitDataset(). I can train using the "normal" approach, with a for loop and getting random batches of data (20000 data points).
I'd like to use the fitDataset() and be able to use the entire dataset and not rely on "randomness" of my getBatch function.
I'm getting closer, using the API docs and the example on tfjs-data but, i'm stuck on a probably dumb data manipulation...
So here's how i'm doing it:
const [trainX, trainY] = await bigData
const model = await cnnLSTM // gru performing well
const BATCH_SIZE = 32
const dataSet = flattenDataset(trainX.slice(200), trainY.slice(200))
model.compile({
loss: 'categoricalCrossentropy',
optimizer: tf.train.adam(0.001),
metrics: ['accuracy']
})
await model.fitDataset(dataSet.train.batch(32), {
epochs: C.trainSteps,
validationData: dataSet.validation,
callbacks: {
onBatchEnd: async (batch, logs) => (await tf.nextFrame()),
onEpochEnd: (epoch, logs) => {
let i = epoch + 1
lossValues.push({'epoch': i, 'loss': logs.loss, 'val_loss': logs.val_loss, 'set': 'train'})
accuracyValues.push({'epoch': i, 'accuracy': logs.acc, 'val_accuracy': logs.val_acc, 'set': 'train'})
// await md `${await plotLosses(train.lossValues)} ${await plotAccuracy(train.accuracyValues)}`
}
}
})
here's my interpretation of the dataset creation:
flattenDataset = (features, labels, split = 0.35) => {
return tf.tidy(() => {
let slice =features.length - Math.floor(features.length * split)
const featuresTrain = features.slice(0, slice)
const featuresVal = features.slice(slice)
const labelsTrain = labels.slice(0, slice)
const labelsVal = labels.slice(slice)
const data = {
train: tf.data.array(featuresTrain, labelsTrain),
validation: tf.data.array(featuresVal, labelsVal)
}
return data
})
}
I'm getting an error:
Error: Dataset iterator for fitDataset() is expected to generate an Array of length 2: `[xs, ys]`, but instead generates Tensor
[[0.4106583, 0.5408, 0.4885066, 0.9021732, 0.1278526],
[0.3711334, 0.5141, 0.4848816, 0.9021571, 0.2688071],
[0.4336613, 0.5747, 0.4822159, 0.9021728, 0.3694479],
...,
[0.4123166, 0.4553, 0.478438 , 0.9020132, 0.8797594],
[0.3963479, 0.3714, 0.4871198, 0.901996 , 0.7170534],
[0.4832076, 0.3557, 0.4892016, 0.9019232, 0.9999322]],Tensor
[[0.3711334, 0.5141, 0.4848816, 0.9021571, 0.2688071],
[0.4336613, 0.5747, 0.4822159, 0.9021728, 0.3694479],
[0.4140858, 0.5985, 0.4789927, 0.9022084, 0.1912155],
...,
The input data is 6 timesteps with 5 dimensions and the labels are just one-hot encoded classes [0,0,1], [0,1,0] and [1, 0, 0]. I guess the flattenDataset() is not sending the data in the correct way.
Does data.train needs to output for each data point [6 timesteps with 5 dims, label] ? I get this error when i tried that:
Error: The feature data generated by the dataset lacks the required input key 'conv1d_Conv1D5_input'.
Could really use some pro insight...
--------------------
Edit #1:
I feel i'm close to an answer.
const X = tf.data.array(trainX.slice(0, 100))//.map(x => x)
const Y = tf.data.array(trainY.slice(0, 100))//.map(x => x)
const zip = tf.data.zip([X, Y])
const dataSet = {
train: zip
}
dataSet.train.forEach(x => console.log(x))
With this i get on the console:
[Array(6), Array(3)]
[Array(6), Array(3)]
[Array(6), Array(3)]
...
[Array(6), Array(3)]
[Array(6), Array(3)]
but the fitDataset is giving me: Error: The feature data generated by the dataset lacks the required input key 'conv1d_Conv1D5_input'.
my model look like this:
const model = tf.sequential()
model.add(tf.layers.conv1d({
inputShape: [6, 5],
kernelSize: (3),
filters: 64,
strides: 1,
padding: 'same',
activation: 'elu',
kernelInitializer: 'varianceScaling',
}))
model.add(tf.layers.maxPooling1d({poolSize: (2)}))
model.add(tf.layers.conv1d({
kernelSize: (1),
filters: 64,
strides: 1,
padding: 'same',
activation: 'elu'
}))
model.add(tf.layers.maxPooling1d({poolSize: (2)}))
model.add(tf.layers.lstm({
units: 18,
activation: 'elu'
}))
model.add(tf.layers.dense({units: 3, activation: 'softmax'}))
model.compile({
loss: 'categoricalCrossentropy',
optimizer: tf.train.adam(0.001),
metrics: ['accuracy']
})
return model
What is wrong here?
What model.fitDataset expects are a Dataset, each element inside this dataset is a tuple of two items, [feature, label].
So in your case, you need to create featureDataset and labelDataset, then merge then with tf.data.zip to create trainDataset. Same for validation dataset.
Solved it
so after a lot of trial an error i found a way to make it work.
So, i had an input shape of [6, 5], meaning an array with 6 arrays of 5 floats each.
[[[0.3467378, 0.3737, 0.4781905, 0.90665, 0.68142351],
[0.44003019602788285, 0.3106, 0.4864576, 0.90193448, 0.5841830879700972],
[0.30672944860847245, 0.3404, 0.490295674, 0.90720676, 0.8331748581920732],
[0.37475716007758336, 0.265, 0.4847249, 0.902056932, 0.6611207914113887],
[0.5639427928616854, 0.2423002, 0.483168235, 0.9020202294447865, 0.82823],
[0.41581425627336555, 0.4086, 0.4721923, 0.902094287, 0.914699]], ... 20k more]
What i did was to flatten the array becoming an array of 5 dimensions arrays. Then applied the .batch(6) to it.
const BATCH_SIZE = 20 //batch size fed to the NN
const X = tf.data.array([].concat(...trainX)).batch(6).batch(BATCH_SIZE)
const Y = tf.data.array(trainY).batch(BATCH_SIZE)
const zip = tf.data.zip([X, Y])
const dataSet = {
train: zip
}
Hope it can help others on complex data!!

Unable to predict when loading a Tensorflow model in Go

I've loaded a Tensorflow model in Go and cannot get predictions - it keeps complaining about shape mismatch - a simple 2d array. Would appreciate an idea here, thank you so much in advance.
Error running the session with input, err: You must feed a value for placeholder tensor 'theoutput_target' with dtype float
[[Node: theoutput_target = Placeholder[_output_shapes=[[?,?]], dtype=DT_FLOAT, shape=[], _device="/job:localhost/replica:0/task:0/cpu:0"]()]]
Input tensor being sent is a [][]float32{ {1.0}, }
a := [][]float32{ {1.0}, }
tensor, terr := tf.NewTensor(a)
if terr != nil {
fmt.Printf("Error creating input tensor: %s\n", terr.Error())
return
}
result, runErr := model.Session.Run(
map[tf.Output]*tf.Tensor{
model.Graph.Operation("theinput").Output(0): tensor,
},
[]tf.Output{
model.Graph.Operation("theoutput_target").Output(0),
},
nil,
)
and the model is generated via Keras and exported to TF using SavedModelBuilder after:
layer_name_input = "theinput"
layer_name_output = "theoutput"
def get_encoder():
model = Sequential()
model.add(Dense(5, input_dim=1))
model.add(Activation("relu"))
model.add(Dense(5, input_dim=1))
return model
inputs = Input(shape=(1, ), name=layer_name_input)
encoder = get_encoder()
model = encoder(inputs)
model = Activation("relu")(model)
objective = Dense(1, name=layer_name_output)(model)
model = Model(inputs=[inputs], outputs=objective)
model.compile(loss='mean_squared_error', optimizer='sgd')
EDIT - fixed, it was a problem with exporting from Keras to TF (layer names). Pasting the export here, hopefully helpful for someone else:
def export_to_tf(keras_model_path, export_path, export_version, is_functional=False):
sess = tf.Session()
K.set_session(sess)
K.set_learning_phase(0)
export_path = os.path.join(export_path, str(export_version))
model = load_model(keras_model_path)
config = model.get_config()
weights = model.get_weights()
if is_functional == True:
model = Model.from_config(config)
else:
model = Sequential.from_config(config)
model.set_weights(weights)
with K.get_session() as sess:
inputs = [ (model_input.name.split(":")[0], model_input) for model_input in model.inputs]
outputs = [ (model_output.name.split(":")[0], model_output) for model_output in model.outputs]
signature = predict_signature_def(inputs=dict(inputs),
outputs=dict(outputs))
input_descriptor = [ { 'name': item[0], 'shape': item[1].shape.as_list() } for item in inputs]
output_descriptor = [ { 'name': item[0], 'shape': item[1].shape.as_list() } for item in outputs]
builder = saved_model_builder.SavedModelBuilder(export_path)
builder.add_meta_graph_and_variables(
sess=sess,
tags=[tag_constants.SERVING],
signature_def_map={signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature})
builder.save()
descriptor = dict()
descriptor["inputs"] = input_descriptor
descriptor["outputs"] = output_descriptor
pprint.pprint(descriptor)
That's something strange in your code and error. Tensorflow is complaining about a missing value for the placeholder with name 'theoutput_target', whilst this placeholder is never defined in the code you posted. Instead, your code defines a placeholder whose name is 'theinput'.
Also, I suggest you to use a more complete and easy to use wrapper around the tensorflow API: tfgo

How can I execute a TensorFlow graph from a protobuf in C++?

I got a simple code form tutorial and output it to .pb file as below:
mnist_softmax_train.py
x = tf.placeholder("float", shape=[None, 784], name='input_x')
y_ = tf.placeholder("float", shape=[None, 10], name='input_y')
W = tf.Variable(tf.zeros([784, 10]), name='W')
b = tf.Variable(tf.zeros([10]), name='b')
tf.initialize_all_variables().run()
y = tf.nn.softmax(tf.matmul(x,W)+b, name='softmax')
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy, name='train_step')
train_step.run(feed_dict={x:input_x, y_:input_y})
In C++, I load the same graph, and feed in fake data for testing:
Tensor input_x(DT_FLOAT, TensorShape({10,784}));
Tensor input_y(DT_FLOAT, TensorShape({10,10}));
Tensor W(DT_FLOAT, TensorShape({784,10}));
Tensor b(DT_FLOAT, TensorShape({10,10}));
Tensor input_test_x(DT_FLOAT, TensorShape({1,784}));
for(int i=0;i<10;i++){
for(int j=0;j<10;j++)
input_x.matrix<float>()(i,i+j) = 1.0;
input_y.matrix<float>()(i,i) = 1.0;
input_test_x.matrix<float>()(0,i) = 1.0;
}
std::vector<std::pair<string, tensorflow::Tensor>> inputs = {
{ "input_x", input_x },
{ "input_y", input_y },
{ "W", W },
{ "b", b },
{ "input_test_x", input_test_x },
};
std::vector<tensorflow::Tensor> outputs;
status = session->Run(inputs, {}, {"train_step"}, &outputs);
std::cout << outputs[0].DebugString() << "\n";
However, this fails with the error:
Invalid argument: Input 0 of node train_step/update_W/ApplyGradientDescent was passed float from _recv_W_0:0 incompatible with expected float_ref.
The graph runs correctly in Python. How can I run it correctly in C++?
The issue here is that you are running the "train_step" target, which performs much more work than just inference. In particular, it attempts to update the variables W and b with the result of the gradient descent step. The error message
Invalid argument: Input 0 of node train_step/update_W/ApplyGradientDescent was passed float from _recv_W_0:0 incompatible with expected float_ref.
...means that one of the nodes you attempted to run ("train_step/update_W/ApplyGradientDescent") expected a mutable input (with type float_ref) but it got an immutable input (with type float) because the value was fed in.
There are (at least) two possible solutions:
If you only want to see predictions for a given input and given weights, fetch "softmax:0" instead of "train_step" in the call to Session::Run().
If you want to perform training in C++, do not feed W and b, but instead assign values to those variables, then continue to execute "train_step". You may find it easier to create a tf.train.Saver when you build the graph in Python, and then invoke the operations that it produces to save and restore values from a checkpoint.

Count number of dictionarys in dictionary in swift

I have a buch of accounts stored in a string dictionary and i would like to count the number of accounts existing, so basicly a ".count" but to find the number of dictionaries created.
var dictionary: [String : [ String ]] = ["" : []]
let storeString = "StoreString"
func addUpdateArray(strings: [String], index: Int) {
let locator = storeString + index.description
dictionary[locator] = strings
}
addUpdateArray(["Account1", "Hy"], 1)
addUpdateArray(["Account2", "Hey"], 3)
and now I would like to see how many accounts are have created of the kind dictionary, is ther a way?
Something like this?
var accounts = [String:[String:String]]() // or whatever your structure is
accounts["Edmund"] = [
"amount": "23.87",
"curreny": "dollars"
]
accounts["Baldrick"] = [
"amount": "23.87",
"curreny": "dollars"
]
accounts["Percy"] = [
"amount": "87.00",
"curreny": "peso"
]
println(accounts.keys.array.count) // 3
If you have dictionary of dictionaries and you want to count the number of actual values inside, you can do it like this:
var accounts = [
"accountsGroup1" : ["account1", "account2", "account3", "account4"],
"accountsGroup2" : ["account1", "account2"],
"accountsGroup3" : ["account1", "account2", "account3", "account4"]
]
let accountsCount = accounts.values.map { $0.count }
let numberOfAllAccounts = reduce(accountsCount, 0) { $0 + $1 }
println(numberOfAllAccounts)

Resources