diff --git a/.gitignore b/.gitignore index e43b0f9..af56f61 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +.idea/ diff --git a/ml_examples/01_smart_contract_model/model.etch b/ml_examples/01_smart_contract_model/model.etch new file mode 100644 index 0000000..5983d0a --- /dev/null +++ b/ml_examples/01_smart_contract_model/model.etch @@ -0,0 +1,111 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2019 Fetch.AI Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +persistent model_state : Model; +persistent data_state : Tensor; +persistent label_state : Tensor; + +// initial set up creates the model and persistent data +@init +function setup(owner : Address) + use model_state; + use data_state; + use label_state; + + // set up the initial model + var model = model_state.get(Model("sequential")); + model.add("dense", 13u64, 10u64, "relu"); + model.add("dense", 10u64, 10u64, "relu"); + model.add("dense", 10u64, 1u64); + model.compile("mse", "adam"); + model_state.set(model); + + // define the initial input data + var data_shape = Array(2); + data_shape[0] = 13u64; // boston feature size is 13 + data_shape[1] = 1u64; // batch size == 1 + var data = data_state.get(Tensor(data_shape)); + data_state.set(data); + + // define the initial label + var label_shape = Array(2); + label_shape[0] = 1u64; // house price output size is 1 + label_shape[1] = 1u64; // batch size == 1 + var label = label_state.get(Tensor(label_shape)); + label_state.set(label); + +endfunction + +// get the data state +@query +function getData() : String + use data_state; + var data = data_state.get(); + return data.toString(); +endfunction + +// get the label state +@query +function getLabel() : String + use label_state; + var label = label_state.get(); + return label.toString(); +endfunction + +// get the current training loss of the model +@query +function evaluate() : String + use model_state; + var model = model_state.get(); + var loss = model.evaluate(); + var str_loss = toString(loss[0]); + return str_loss; +endfunction + +// pass in some data, train the model with it, save the updated model to state +@action +function train(data_string: String, label_string: String) + use model_state; + var model = model_state.get(); + + use data_state; + var data = data_state.get(); + data.fromString(data_string); + + use label_state; + var label = label_state.get(); + label.fromString(label_string); + + var batch_size = 10u64; + model.fit(data, label, batch_size); + model_state.set(model); +endfunction + +// make a prediction with the model based on input data passed to function +@query +function predict(data_string: String) : String + use model_state; + var model = model_state.get(); + + use data_state; + var data = data_state.get(); + data.fromString(data_string); + + var prediction = model.predict(data); + return prediction.toString(); +endfunction diff --git a/ml_examples/01_smart_contract_model/submit_contract.py b/ml_examples/01_smart_contract_model/submit_contract.py new file mode 100644 index 0000000..981fd97 --- /dev/null +++ b/ml_examples/01_smart_contract_model/submit_contract.py @@ -0,0 +1,67 @@ +from fetchai.ledger.api import LedgerApi +from fetchai.ledger.contract import Contract +from fetchai.ledger.crypto import Entity, Address +import sys +import time + +DATA_FILE = "/Users/khan/fetch/corpora/boston/boston_data.csv" +LABEL_FILE = "/Users/khan/fetch/corpora/boston/boston_label.csv" + +def read_one_line_data_csv_as_string(fname): + + f = open(fname, 'r') + return f.readline() + + +def main(source, train_data, train_labels, test_data, test_labels): + + # Create keypair for the contract owner + entity = Entity() + address = Address(entity) + + # Setting API up + api = LedgerApi('127.0.0.1', 8000) + + # Need funds to deploy contract + api.sync(api.tokens.wealth(entity, 10000000000000)) + + # Create contract + contract = Contract(source, entity) + + # Deploy contract + # api.sync(api.contracts.create(entity, contract, 1000000000)) + api.sync(contract.create(api, entity, 1000000000)) + + # grab the data and label tensor - just to demonstrate usage + data_string = contract.query(api, 'getData') + label_string = contract.query(api, 'getLabel') + + # grab boston data set data + data_string = read_one_line_data_csv_as_string(DATA_FILE) + label_string = read_one_line_data_csv_as_string(LABEL_FILE) + + print("initial data: " + data_string) + print("initial label: " + label_string) + + # train on some input data + fet_tx_fee = 16000000 + api.sync(contract.action(api, 'train', fet_tx_fee, [entity], data_string, label_string)) + + # evaluate the initial loss + initial_loss = contract.query(api, 'evaluate') + + # predict loss after training + prediction = contract.query(api, 'predict', data_string=data_string) + print("model prediction: " + prediction) + +if __name__ == '__main__': + + # Loading contract + if len(sys.argv) != 6: + print("Usage: ", sys.argv[0], "[filename] train_data.csv train_labels.csv test_data.csv test_labels.csv") + exit(-1) + + with open(sys.argv[1], "r") as fb: + source = fb.read() + + main(source, sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]) \ No newline at end of file diff --git a/ml_examples/02_synergetic_contract_model/model.etch b/ml_examples/02_synergetic_contract_model/model.etch new file mode 100644 index 0000000..e839f2f --- /dev/null +++ b/ml_examples/02_synergetic_contract_model/model.etch @@ -0,0 +1,141 @@ +//------------------------------------------------------------------------------ +// +// Copyright 2019 Fetch.AI Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//------------------------------------------------------------------------------ + +persistent model_state : Model; +persistent data_state : Tensor; +persistent label_state : Tensor; +persistent loss_state : Fixed64; + +// TODO : createProblem must return the problem - this should wrap up model and data somehow +// TODO : doWork must take in the problem & nonce, and return the solution - solution must be the new model +// TODO : evaluateWork must take in the solution, and return a value +// TODO : applyWork will save the new model to a state, for querying + +@init +function setup() + use model_state; + use data_state; + use label_state; + + // set up the initial model + var model = model_state.get(Model("sequential")); + model.add("dense", 13u64, 10u64, "relu"); + model.add("dense", 10u64, 10u64, "relu"); + model.add("dense", 10u64, 1u64); + model.compile("mse", "adam"); + model_state.set(model); + + // define the initial input data + var data_shape = Array(2); + data_shape[0] = 13u64; // boston feature size is 13 + data_shape[1] = 1u64; // batch size == 1 + var data = data_state.get(Tensor(data_shape)); + data_state.set(data); + + // define the initial label + var label_shape = Array(2); + label_shape[0] = 1u64; // house price output size is 1 + label_shape[1] = 1u64; // batch size == 1 + var label = label_state.get(Tensor(label_shape)); + label_state.set(label); + +endfunction + +// set up a problem around training a machine learning model +@problem +function createProblem(model: Model, data: Tensor, label: Tensor) + + return {model, data, label} + +endfunction + +// get the data state +@query +function getData() : String + use data_state; + var data = data_state.get(); + return data.toString(); +endfunction + +// get the label state +@query +function getLabel() : String + use label_state; + var label = label_state.get(); + return label.toString(); +endfunction + +// evaluates performance as the loss function of the model after training +@objective +function evaluateWork(in_model: String) + use model_state; + var model = model_state.get(); + model.fromString(in_model); + return model.evaluate(); +endfunction + +// the work of training the model +@objective +function doWork(problem : String, nonce : UInt256) : String + use model_state; + use data_state; + use label_state; + + var model = model_state.get(); + model.fromString(in_model); + + // update the learning rate of the local model + var lr = nonce.toFloat64() % 1.0fp64; + model.setLearningRate(lr); + + // train the model + var batch_size = 10u64; + var data = data_state.get(); + var label = label_state.get(); + model.fit(data, label, batch_size); + + // return the serialised model + return model.toString(); +endfunction + +// set the new model to be the specified winner +@clear +function applyWork(in_model: String) + use model_state; + var model = model_state.get(); + model.fromString(in_model); + model_state.set(model); +endfunction + +// make a prediction with the model based on input data passed to function +@query +function predict(data: Tensor) : Tensor + use model_state; + var model = model_state.get(); + var prediction = model.predict(data); + return prediction; +endfunction + +// query the current model performance +@query +function evaluate() : Tensor + use model_state; + var model = model_state.get(); + return model.evaluate(); +endfunction + diff --git a/ml_examples/02_synergetic_contract_model/submit_contract.py b/ml_examples/02_synergetic_contract_model/submit_contract.py new file mode 100644 index 0000000..efef837 --- /dev/null +++ b/ml_examples/02_synergetic_contract_model/submit_contract.py @@ -0,0 +1,72 @@ +from fetchai.ledger.api import LedgerApi +from fetchai.ledger.contract import Contract +from fetchai.ledger.crypto import Entity, Address +import sys +import time + + +def main(source, train_data, train_labels, test_data, test_labels): + + + # Create keypair for the contract owner + entity = Entity() + address = Address(entity) + + # Setting API up + api = LedgerApi('127.0.0.1', 8000) + + # Need funds to deploy contract + api.sync(api.tokens.wealth(entity, 10000000000000)) + + # Create contract + contract = Contract(source, entity) + + # Deploy contract + api.sync(contract.create(api, entity, 1000000000)) + + # Run the synergetic contract + + # combine the data and label into one string for passing into the createProblem function + data_string = contract.query(api, 'getData') + label_string = contract.query(api, 'getLabel') + print("initial data: " + data_string) + print("initial label: " + label_string) + + # TODO : combine data and label + + # pass the combined input data + api.sync(api.contracts.submit_data(entity, contract.digest, contract.address, value = (data_string + label_string))) + api.wait_for_blocks(10) + result = contract.query(api, 'query_result') + + + + # evaluate the initial loss + initial_loss = contract.query(api, 'evaluate') + print("initial loss: " + initial_loss) + + # grab the data and label tensor + data_string = contract.query(api, 'getData') + label_string = contract.query(api, 'getLabel') + print("initial data: " + data_string) + print("initial label: " + label_string) + + # train on some input data + fet_tx_fee = 16000000 + api.sync(contract.action(api, 'train', fet_tx_fee, [entity], data_string, label_string)) + + # predict loss after training + prediction = contract.query(api, 'predict', data_string=data_string) + print("model prediction: " + prediction) + +if __name__ == '__main__': + + # Loading contract + if len(sys.argv) != 6: + print("Usage: ", sys.argv[0], "[filename] train_data.csv train_labels.csv test_data.csv test_labels.csv") + exit(-1) + + with open(sys.argv[1], "r") as fb: + source = fb.read() + + main(source, sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]) \ No newline at end of file