From 6ce39ab925076de2588b98963b0408836f1e1dbe Mon Sep 17 00:00:00 2001 From: xuewanqi <36396136+xuewanqi@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:49:55 +0800 Subject: [PATCH 01/53] Add the running script for the TED_CT_Detection --- .../application/TED_CT_Detection/run.sh | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/healthcare/application/TED_CT_Detection/run.sh diff --git a/examples/healthcare/application/TED_CT_Detection/run.sh b/examples/healthcare/application/TED_CT_Detection/run.sh new file mode 100644 index 000000000..9182810c8 --- /dev/null +++ b/examples/healthcare/application/TED_CT_Detection/run.sh @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +### command +python train.py -dir pathToDataset From 98c262742cdc461f91096c252858d4051cb7837e Mon Sep 17 00:00:00 2001 From: Xie Zhongle Date: Tue, 31 Dec 2024 10:50:44 +0800 Subject: [PATCH 02/53] Add the implementation of model for the TED CT --- examples/healthcare/models/tedct_net.py | 95 +++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 examples/healthcare/models/tedct_net.py diff --git a/examples/healthcare/models/tedct_net.py b/examples/healthcare/models/tedct_net.py new file mode 100644 index 000000000..c6e4db1ce --- /dev/null +++ b/examples/healthcare/models/tedct_net.py @@ -0,0 +1,95 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from singa import layer +from singa import model +import singa.tensor as tensor +from singa import autograd +from singa.tensor import Tensor + + +class CNN(model.Model): + + def __init__(self, num_classes=10, num_channels=1): + super(CNN, self).__init__() + self.num_classes = num_classes + self.input_size = 28 + self.dimension = 4 + self.conv1 = layer.Conv2d(num_channels, 20, 5, padding=0, activation="RELU") + self.conv2 = layer.Conv2d(20, 50, 5, padding=0, activation="RELU") + self.linear1 = layer.Linear(500) + self.linear2 = layer.Linear(num_classes) + self.pooling1 = layer.MaxPool2d(2, 2, padding=0) + self.pooling2 = layer.MaxPool2d(2, 2, padding=0) + self.relu = layer.ReLU() + self.flatten = layer.Flatten() + self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() + + def forward(self, x): + y = self.conv1(x) + y = self.pooling1(y) + y = self.conv2(y) + y = self.pooling2(y) + y = self.flatten(y) + y = self.linear1(y) + y = self.relu(y) + y = self.linear2(y) + return y + + def train_one_batch(self, x, y, dist_option, spars): + out = self.forward(x) + loss = self.softmax_cross_entropy(out, y) + + if dist_option == 'plain': + self.optimizer(loss) + elif dist_option == 'half': + self.optimizer.backward_and_update_half(loss) + elif dist_option == 'partialUpdate': + self.optimizer.backward_and_partial_update(loss) + elif dist_option == 'sparseTopK': + self.optimizer.backward_and_sparse_update(loss, + topK=True, + spars=spars) + elif dist_option == 'sparseThreshold': + self.optimizer.backward_and_sparse_update(loss, + topK=False, + spars=spars) + return out, loss + + def set_optimizer(self, optimizer): + self.optimizer = optimizer + +def create_cnn_model(pretrained=False, **kwargs): + """Constructs a CNN model. + + Args: + pretrained (bool): If True, returns a pre-trained model. + + Returns: + The created CNN model. + """ + model = CNN(**kwargs) + + return model + +def create_model(backbone, prototype_count=2, lamb=0.5, temp=10.0): + model = CPL(backbone, prototype_count=prototype_count, lamb=lamb, temp=temp) + return model + + +__all__ = ["CPL", "CNN", "create_cnn_model", "create_model"] From ef6fa25184eb0c5a55f364e2286182e2f684209d Mon Sep 17 00:00:00 2001 From: Zrealshadow <704309740@qq.com> Date: Sat, 11 Jan 2025 18:36:38 +0800 Subject: [PATCH 03/53] Add the running script for the diabetic readmission --- .../Diabetic_Readmission_Prediction/run.sh | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/healthcare/application/Diabetic_Readmission_Prediction/run.sh diff --git a/examples/healthcare/application/Diabetic_Readmission_Prediction/run.sh b/examples/healthcare/application/Diabetic_Readmission_Prediction/run.sh new file mode 100644 index 000000000..0cb9797df --- /dev/null +++ b/examples/healthcare/application/Diabetic_Readmission_Prediction/run.sh @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +### diabetic dataset +python train.py mlp diabetic From 5585426ff9d235ed8f17055ef1bd42655bb5f96e Mon Sep 17 00:00:00 2001 From: working <57171759+NLGithubWP@users.noreply.github.com> Date: Mon, 13 Jan 2025 21:26:48 +0800 Subject: [PATCH 04/53] Add the implementation forthe training file of the diabetic readmission prediction --- .../Diabetic_Readmission_Prediction/train.py | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 examples/healthcare/application/Diabetic_Readmission_Prediction/train.py diff --git a/examples/healthcare/application/Diabetic_Readmission_Prediction/train.py b/examples/healthcare/application/Diabetic_Readmission_Prediction/train.py new file mode 100644 index 000000000..a51b4b059 --- /dev/null +++ b/examples/healthcare/application/Diabetic_Readmission_Prediction/train.py @@ -0,0 +1,265 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from singa import device +from singa import tensor +from singa import opt +import numpy as np +import time +import argparse +from healthcare.data import diabetic +from healthcare.models import diabetic_net + +np_dtype = {"float16": np.float16, "float32": np.float32} + +singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} + + +# Calculate accuracy +def accuracy(pred, target): + # y is network output to be compared with ground truth (int) + y = np.argmax(pred, axis=1) + a = y == target + correct = np.array(a, "int").sum() + return correct + + +# Data partition according to the rank +def partition(global_rank, world_size, train_x, train_y, val_x, val_y): + # Partition training data + data_per_rank = train_x.shape[0] // world_size + idx_start = global_rank * data_per_rank + idx_end = (global_rank + 1) * data_per_rank + train_x = train_x[idx_start:idx_end] + train_y = train_y[idx_start:idx_end] + + # Partition evaluation data + data_per_rank = val_x.shape[0] // world_size + idx_start = global_rank * data_per_rank + idx_end = (global_rank + 1) * data_per_rank + val_x = val_x[idx_start:idx_end] + val_y = val_y[idx_start:idx_end] + return train_x, train_y, val_x, val_y + + +# Function to all reduce NUMPY accuracy and loss from multiple devices +def reduce_variable(variable, dist_opt, reducer): + reducer.copy_from_numpy(variable) + dist_opt.all_reduce(reducer.data) + dist_opt.wait() + output = tensor.to_numpy(reducer) + return output + + +def run(global_rank, + world_size, + local_rank, + max_epoch, + batch_size, + model, + data, + sgd, + graph, + verbosity, + dist_option='plain', + spars=None, + precision='float32'): + dev = device.create_cpu_device() # now CPU version only, could change to GPU device for GPU-support machines + dev.SetRandSeed(0) + np.random.seed(0) + + # Load data based on specified dataset + if data == 'diabetic': + train_x, train_y, val_x, val_y = diabetic.load() + elif data == 'mnist' or data == 'cifar10' or data == 'cifar100': + raise ValueError("Only 'diabetic' dataset (2D table data) is supported with MLP model.") + + # Ensure the data is already 2D (train_x.shape[1:] should have only one dimension) + data_size = train_x.shape[1] + num_classes = int(np.max(train_y) + 1) + + # Initialize MLP model + if model == 'mlp': + model = diabetic_net.create_model(data_size=data_size, + num_classes=num_classes) + else: + print( + 'Wrong model!' + ) + sys.exit(0) + # Setup distributed training flags + if hasattr(sgd, "communicator"): + DIST = True + sequential = True + else: + DIST = False + sequential = False + + # Partition data if distributed training is used + if DIST: + train_x, train_y, val_x, val_y = partition(global_rank, world_size, + train_x, train_y, val_x, + val_y) + + # Define tensors for inputs and labels + tx = tensor.Tensor((batch_size, data_size), dev, singa_dtype[precision]) + ty = tensor.Tensor((batch_size,), dev, tensor.int32) + + num_train_batch = train_x.shape[0] // batch_size + num_val_batch = val_x.shape[0] // batch_size + idx = np.arange(train_x.shape[0], dtype=np.int32) + + # Attach optimizer to model + model.set_optimizer(sgd) + model.compile([tx], is_train=True, use_graph=graph, sequential=sequential) + dev.SetVerbosity(verbosity) + + # Training and evaluation loop + for epoch in range(max_epoch): + start_time = time.time() + np.random.shuffle(idx) + + if global_rank == 0: + print('Starting Epoch %d:' % epoch) + + # Training phase + train_correct = np.zeros(shape=[1], dtype=np.float32) + test_correct = np.zeros(shape=[1], dtype=np.float32) + train_loss = np.zeros(shape=[1], dtype=np.float32) + + model.train() + for b in range(num_train_batch): + x = train_x[idx[b * batch_size:(b + 1) * batch_size]] + y = train_y[idx[b * batch_size:(b + 1) * batch_size]] + + x = x.astype(np_dtype[precision]) # Ensure correct precision + tx.copy_from_numpy(x) + ty.copy_from_numpy(y) + + # Train the model + out, loss = model(tx, ty, dist_option, spars) + train_correct += accuracy(tensor.to_numpy(out), y) + train_loss += tensor.to_numpy(loss)[0] + + if DIST: + # Reduce training stats across distributed devices + reducer = tensor.Tensor((1,), dev, tensor.float32) + train_correct = reduce_variable(train_correct, sgd, reducer) + train_loss = reduce_variable(train_loss, sgd, reducer) + + if global_rank == 0: + print('Training loss = %f, training accuracy = %f' % + (train_loss, train_correct / + (num_train_batch * batch_size * world_size)), + flush=True) + + # Evaluation phase + model.eval() + for b in range(num_val_batch): + x = val_x[b * batch_size:(b + 1) * batch_size] + y = val_y[b * batch_size:(b + 1) * batch_size] + + x = x.astype(np_dtype[precision]) + tx.copy_from_numpy(x) + ty.copy_from_numpy(y) + + out_test = model(tx) + test_correct += accuracy(tensor.to_numpy(out_test), y) + + if DIST: + # Reduce evaluation stats across distributed devices + test_correct = reduce_variable(test_correct, sgd, reducer) + + if global_rank == 0: + print('Evaluation accuracy = %f, Elapsed Time = %fs' % + (test_correct / (num_val_batch * batch_size * world_size), + time.time() - start_time), + flush=True) + + dev.PrintTimeProfiling() + + + +if __name__ == '__main__': + # Use argparse to get command config: max_epoch, model, data, etc., for single gpu training + parser = argparse.ArgumentParser( + description='Training using the autograd and graph.') + parser.add_argument( + 'model', + choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet'], + default='mlp') + parser.add_argument('data', + choices=['mnist', 'cifar10', 'cifar100', 'diabetic'], + default='mnist') + parser.add_argument('-p', + choices=['float32', 'float16'], + default='float32', + dest='precision') + parser.add_argument('-m', + '--max-epoch', + default=100, + type=int, + help='maximum epochs', + dest='max_epoch') + parser.add_argument('-b', + '--batch-size', + default=64, + type=int, + help='batch size', + dest='batch_size') + parser.add_argument('-l', + '--learning-rate', + default=0.005, + type=float, + help='initial learning rate', + dest='lr') + # Determine which gpu to use + parser.add_argument('-i', + '--device-id', + default=0, + type=int, + help='which GPU to use', + dest='device_id') + parser.add_argument('-g', + '--disable-graph', + default='True', + action='store_false', + help='disable graph', + dest='graph') + parser.add_argument('-v', + '--log-verbosity', + default=0, + type=int, + help='logging verbosity', + dest='verbosity') + + args = parser.parse_args() + + sgd = opt.SGD(lr=args.lr, momentum=0.9, weight_decay=1e-5, dtype=singa_dtype[args.precision]) + run(0, + 1, + args.device_id, + args.max_epoch, + args.batch_size, + args.model, + args.data, + sgd, + args.graph, + args.verbosity, + precision=args.precision) From d85496d321c8e19f4c6ce2c8d3a44e1a2b66761b Mon Sep 17 00:00:00 2001 From: Cai Shaofeng Date: Wed, 22 Jan 2025 21:45:14 +0800 Subject: [PATCH 05/53] Update the implementation of model for the TED CT Update the implementation of model for the TED CT --- examples/healthcare/models/tedct_net.py | 88 +++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/examples/healthcare/models/tedct_net.py b/examples/healthcare/models/tedct_net.py index c6e4db1ce..2758487bd 100644 --- a/examples/healthcare/models/tedct_net.py +++ b/examples/healthcare/models/tedct_net.py @@ -23,6 +23,94 @@ from singa.tensor import Tensor +class CPLayer(layer.Layer): + def __init__(self, prototype_count=2, temp=10.0): + super(CPLayer, self).__init__() + self.prototype_count = prototype_count + self.temp = temp + + def initialize(self, x): + self.feature_dim = x.shape[1] + self.prototype = tensor.random( + (self.feature_dim, self.prototype_count), device=x.device + ) + + def forward(self, feat): + self.device_check(feat, self.prototype) + self.dtype_check(feat, self.prototype) + + feat_sq = autograd.mul(feat, feat) + feat_sq_sum = autograd.reduce_sum(feat_sq, axes=[1], keepdims=1) + feat_sq_sum_tile = autograd.tile(feat_sq_sum, repeats=[1, self.feature_dim]) + + prototype_sq = autograd.mul(self.prototype, self.prototype) + prototype_sq_sum = autograd.reduce_sum(prototype_sq, axes=[0], keepdims=1) + prototype_sq_sum_tile = autograd.tile(prototype_sq_sum, repeats=feat.shape[0]) + + cross_term = autograd.matmul(feat, self.prototype) + cross_term_scale = Tensor( + shape=cross_term.shape, device=cross_term.device, requires_grad=False + ).set_value(-2) + cross_term_scaled = autograd.mul(cross_term, cross_term_scale) + + dist = autograd.add(feat_sq_sum_tile, prototype_sq_sum_tile) + dist = autograd.add(dist, cross_term_scaled) + + logits_coeff = ( + tensor.ones((feat.shape[0], self.prototype.shape[1]), device=feat.device) + * -1.0 + / self.temp + ) + logits_coeff.requires_grad = False + logits = autograd.mul(logits_coeff, dist) + + return logits + + def get_params(self): + return {self.prototype.name: self.prototype} + + def set_params(self, parameters): + self.prototype.copy_from(parameters[self.prototype.name]) + + +class CPL(model.Model): + + def __init__( + self, + backbone: model.Model, + prototype_count=2, + lamb=0.5, + temp=10, + label=None, + prototype_weight=None, + ): + super(CPL, self).__init__() + # config + self.lamb = lamb + self.prototype_weight = prototype_weight + self.prototype_label = label + + # layer + self.backbone = backbone + self.cplayer = CPLayer(prototype_count=prototype_count, temp=temp) + # optimizer + self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() + + def forward(self, x): + feat = self.backbone.forward(x) + logits = self.cplayer(feat) + return logits + + def train_one_batch(self, x, y, dist_option, spars): + out = self.forward(x) + loss = self.softmax_cross_entropy(out, y) + self.optimizer(loss) + return out, loss + + def set_optimizer(self, optimizer): + self.optimizer = optimizer + + class CNN(model.Model): def __init__(self, num_classes=10, num_channels=1): From 6a56c8efbaa5bbd86273cf06384be21021fbee1b Mon Sep 17 00:00:00 2001 From: zmeihui Date: Sat, 25 Jan 2025 17:49:02 +0800 Subject: [PATCH 06/53] Add the running script for the diabetic retinopathy classification Add the running script for the diabetic retinopathy classification --- .../run.sh | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/healthcare/application/Diabetic_Retinopathy_Classification/run.sh diff --git a/examples/healthcare/application/Diabetic_Retinopathy_Classification/run.sh b/examples/healthcare/application/Diabetic_Retinopathy_Classification/run.sh new file mode 100644 index 000000000..afadcb138 --- /dev/null +++ b/examples/healthcare/application/Diabetic_Retinopathy_Classification/run.sh @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +### diabetic retinopathy dataset +python train_cnn.py cnn diaret -dir pathToDataset From 06de1b3b5f983a9181fe72d72d76a3a39097aaf7 Mon Sep 17 00:00:00 2001 From: prometheus <57171759+NLGithubWP@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:57:58 +0800 Subject: [PATCH 07/53] Update the readme file for the diabetic readmission prediction example --- .../application/Diabetic_Readmission_Prediction/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Readmission_Prediction/README.md b/examples/healthcare/application/Diabetic_Readmission_Prediction/README.md index c58e6375a..4bca2070a 100644 --- a/examples/healthcare/application/Diabetic_Readmission_Prediction/README.md +++ b/examples/healthcare/application/Diabetic_Readmission_Prediction/README.md @@ -25,7 +25,7 @@ Diabetic readmission is a significant concern in healthcare, with a substantial Although diabetes is a manageable condition, early identification of patients at high risk of readmission remains a challenge. A reliable and efficient predictive model can help identify these patients, enabling healthcare providers to intervene early and prevent unnecessary readmissions. -To address this issue, we use Singa to implement a machine learning model for predicting diabetic readmission. The dataset is from [BMC Medical Informatics and Decision-Making](https://bmcmedinformdecismak.biomedcentral.com/articles/10.1186/s12911-021-01423-y). Please download the dataset before running the scripts. +To address this issue, we use Singa to implement a machine learning model for predicting diabetic readmission. The dataset is from [Diabetes 130-US Hospitals for Years 1999-2008](https://archive.ics.uci.edu/ml/datasets/diabetes+130-us+hospitals+for+years+1999-2008). Please download the dataset before running the scripts. ## Structure @@ -41,5 +41,5 @@ To address this issue, we use Singa to implement a machine learning model for pr ## Command ```bash -python train.py mlp diabetic +python train_mlp.py mlp diabetic ``` From 72cd1dd5b43b72438bc30b7f7a0cd9ae6447813f Mon Sep 17 00:00:00 2001 From: npcmaci <779568335@qq.com> Date: Sat, 8 Feb 2025 18:06:00 +0800 Subject: [PATCH 08/53] Restructure the Diabetic Disease Folder --- .../Diabetic_Readmission_Prediction/README.md | 2 +- .../Diabetic_Readmission_Prediction/run.sh | 0 .../Diabetic_Readmission_Prediction/train.py | 2 ++ .../Diabetic_Retinopathy_Classification/README.md | 0 .../Diabetic_Retinopathy_Classification/run.sh | 0 .../Diabetic_Retinopathy_Classification/train.py | 2 +- 6 files changed, 4 insertions(+), 2 deletions(-) rename examples/healthcare/application/{ => Diabetic_Disease}/Diabetic_Readmission_Prediction/README.md (98%) rename examples/healthcare/application/{ => Diabetic_Disease}/Diabetic_Readmission_Prediction/run.sh (100%) rename examples/healthcare/application/{ => Diabetic_Disease}/Diabetic_Readmission_Prediction/train.py (99%) rename examples/healthcare/application/{ => Diabetic_Disease}/Diabetic_Retinopathy_Classification/README.md (100%) rename examples/healthcare/application/{ => Diabetic_Disease}/Diabetic_Retinopathy_Classification/run.sh (100%) rename examples/healthcare/application/{ => Diabetic_Disease}/Diabetic_Retinopathy_Classification/train.py (99%) diff --git a/examples/healthcare/application/Diabetic_Readmission_Prediction/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md similarity index 98% rename from examples/healthcare/application/Diabetic_Readmission_Prediction/README.md rename to examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md index 4bca2070a..f0cea4738 100644 --- a/examples/healthcare/application/Diabetic_Readmission_Prediction/README.md +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md @@ -41,5 +41,5 @@ To address this issue, we use Singa to implement a machine learning model for pr ## Command ```bash -python train_mlp.py mlp diabetic +python train.py mlp diabetic ``` diff --git a/examples/healthcare/application/Diabetic_Readmission_Prediction/run.sh b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/run.sh similarity index 100% rename from examples/healthcare/application/Diabetic_Readmission_Prediction/run.sh rename to examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/run.sh diff --git a/examples/healthcare/application/Diabetic_Readmission_Prediction/train.py b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py similarity index 99% rename from examples/healthcare/application/Diabetic_Readmission_Prediction/train.py rename to examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py index a51b4b059..694b08a35 100644 --- a/examples/healthcare/application/Diabetic_Readmission_Prediction/train.py +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py @@ -23,6 +23,8 @@ import numpy as np import time import argparse +import sys +sys.path.append("../../../..") from healthcare.data import diabetic from healthcare.models import diabetic_net diff --git a/examples/healthcare/application/Diabetic_Retinopathy_Classification/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/README.md similarity index 100% rename from examples/healthcare/application/Diabetic_Retinopathy_Classification/README.md rename to examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/README.md diff --git a/examples/healthcare/application/Diabetic_Retinopathy_Classification/run.sh b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/run.sh similarity index 100% rename from examples/healthcare/application/Diabetic_Retinopathy_Classification/run.sh rename to examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/run.sh diff --git a/examples/healthcare/application/Diabetic_Retinopathy_Classification/train.py b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py similarity index 99% rename from examples/healthcare/application/Diabetic_Retinopathy_Classification/train.py rename to examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py index 5ef41851a..9f17cb495 100644 --- a/examples/healthcare/application/Diabetic_Retinopathy_Classification/train.py +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py @@ -6,7 +6,7 @@ import time import argparse import sys -sys.path.append("../../..") +sys.path.append("../../../..") from PIL import Image From 4a33d2cb76537c5cd464c79cd8de65348ca9b436 Mon Sep 17 00:00:00 2001 From: Zrealshadow <704309740@qq.com> Date: Tue, 11 Feb 2025 19:06:45 +0800 Subject: [PATCH 09/53] update script name in doc --- .../Diabetic_Disease/Diabetic_Readmission_Prediction/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md index f0cea4738..4bca2070a 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md @@ -41,5 +41,5 @@ To address this issue, we use Singa to implement a machine learning model for pr ## Command ```bash -python train.py mlp diabetic +python train_mlp.py mlp diabetic ``` From 268caaab3ad755ec3f60252817701c5566bf185a Mon Sep 17 00:00:00 2001 From: Cai Shaofeng Date: Sun, 16 Feb 2025 18:20:40 +0800 Subject: [PATCH 10/53] Update the readme file for the Diabetic Readmission Prediction Update the readme file --- .../Diabetic_Readmission_Prediction/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md index 4bca2070a..ba2038f4b 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md @@ -36,10 +36,10 @@ To address this issue, we use Singa to implement a machine learning model for pr a subclass of `Module` to wrap the neural network operations of each model. -* `train_mlp.py` is the training script, which controls the training flow by +* `train.py` is the training script, which controls the training flow by doing BackPropagation and SGD update. ## Command ```bash -python train_mlp.py mlp diabetic +python train.py mlp diabetic ``` From 4286570aaff4d82c19d4c3d496f46355950a7ca3 Mon Sep 17 00:00:00 2001 From: zhangruipeng Date: Mon, 17 Feb 2025 18:36:42 +0800 Subject: [PATCH 11/53] Add the readme file for the peft. --- examples/singa_peft/README.md | 67 ++++++++++++++++++++++++++++++ examples/singa_peft/docs/peft.png | Bin 0 -> 74792 bytes 2 files changed, 67 insertions(+) create mode 100644 examples/singa_peft/README.md create mode 100644 examples/singa_peft/docs/peft.png diff --git a/examples/singa_peft/README.md b/examples/singa_peft/README.md new file mode 100644 index 000000000..76da6b690 --- /dev/null +++ b/examples/singa_peft/README.md @@ -0,0 +1,67 @@ + + +# Singa PEFT + +## Code structure design + +The code structure design is shown in the following figure. For details, please refer to the next section, which will give a detailed explanation. + +img.png + + +## Code structure specification + +The code structure is shown in the tree structure below, which is specified as follows: + +``` +_singa-peft/ +├── __init__.py +├── tuner/ +│ ├── __init__.py +│ ├── base_tuner.py +│ └── linear_lora/ +│ ├── __init__.py +│ ├── config.py +│ ├── layer.py +│ └── tuner.py +├── peft_config.py +├── peft_registry.py +└── peft_model.py +``` + +`peft_config.py` contains the peft configuration base class **PeftConfig**, which defines some common parameters. All other peft configuration classes must inherit from this class. + + +`peft_registry.py` contains the **PeftRegistry** class. You can register a new peft tuner by using the annotation `@PeftRegistry.register("xxx")`. + + +`base_tuner.py` contains the **BaseTuner** class, which is the base class for all tuners. All peft methods must inherit from this class and implement `inject` and `merge_weights` abstract methods. + + +`linear_lora/config.py` contains **LinearLoraConfig** class, and inherits from **PeftConfig** class, which includes the necessary parameters for lora method. + + +`linear_lora/layer.py` contains the **LinearLoRALayer** class, which is the implementation of the Lora method in a linear layer. The tuner's `inject` method replaces the linear layer in the base model with this layer. + + +`linear_lora/tuner.py` contains **LinearLoraTuner** class, which inherits from **BaseTuner** class. First you need to register the peft method with the annotation `@PeftRegistry.register("linear_lora")` on the class. Next, you need to implement abstract methods that inherit from **BaseTuner**, including `inject` and `merge_weights` abstraction methods. The `inject` method implementation specifies the layers to replace, for example, the normal `linear layer` instead of the `linearLora layer`. The `merge_weights` method merges the parameters of the replaced layers to speed up the inference. + + +Finally, if you want to extend the new peft method, you can follow the script in the `linear_lora` directory. In addition, you need to expose the corresponding classes and methods in the `__init__.py` file. Also see the scripts in the `examples` directory for how to use the library. diff --git a/examples/singa_peft/docs/peft.png b/examples/singa_peft/docs/peft.png new file mode 100644 index 0000000000000000000000000000000000000000..fa0e1e60ba7e0d50dc5e41c1a141ef90a6a2569f GIT binary patch literal 74792 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 Date: Fri, 28 Feb 2025 18:30:50 +0800 Subject: [PATCH 12/53] Add the folder for kidney disease prediction. --- .../application/Kidney_Disease/README.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 examples/healthcare/application/Kidney_Disease/README.md diff --git a/examples/healthcare/application/Kidney_Disease/README.md b/examples/healthcare/application/Kidney_Disease/README.md new file mode 100644 index 000000000..5ec9a667e --- /dev/null +++ b/examples/healthcare/application/Kidney_Disease/README.md @@ -0,0 +1,43 @@ + + +# Singa for Kidney disease Prediction + +## Kidney disease Prediction Task + +Kidney disease prediction is an important tool that uses data science and machine learning techniques to predict the likelihood of a patient suffering from Kidney disease. The core goal of this technology is to judge whether a patient suffers from kidney disease by analyzing multiple data such as a patient’s medical history, physiological indicators, diagnostic information, treatment options, and socioeconomic factors, so as to take appropriate interventions in advance to provide treatment. + +The dataset used in this task is MIMIC-III after preprocessed. The features are data containing 6 visit windows, with 2549 frequent diagnoses, procedures and drugs for each window. Each item in features are data for one patient, and these features are encoded by one-hot code. The labels are corresponding flags to mark whether the patient suffered from kidney disease, where the label equals "1" if the patient had kidn disease, the label equals "0" if not. + + +## Structure + +* `data` includes the load of mimic-iii data to be utilized. + +* `model` includes the MLP model construction codes by creating + a subclass of `Module` to wrap the neural network operations + of each model. + +* `train_kidney_mlp.py` is the training script, which controls the training flow by + doing BackPropagation and SGD update. + +## Command +```bash +python train_kidney_mlp.py mlp kidney-disease -dir pathToDataset +``` \ No newline at end of file From 7f934d1405ddbdd3bb3dcacee9ea2b1f60e8054f Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:43:00 +0800 Subject: [PATCH 13/53] Add the folder for Cardiovascular Disease Prediction --- .../Cardiovascular_Disease/README.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/healthcare/application/Cardiovascular_Disease/README.md diff --git a/examples/healthcare/application/Cardiovascular_Disease/README.md b/examples/healthcare/application/Cardiovascular_Disease/README.md new file mode 100644 index 000000000..cc68d4106 --- /dev/null +++ b/examples/healthcare/application/Cardiovascular_Disease/README.md @@ -0,0 +1,42 @@ + + +# Singa for Cardiovascular Disease Detection Task + +## Cardiovascular Disease + +Cardiovascular disease is primarily caused by risk factors like high blood pressure, unhealthy diet, and physical inactivity. As the leading cause of death globally, it accounts for approximately 17.9 million fatalities annually, representing 31% of all global deaths. This makes cardiovascular disease the most significant threat to human health worldwide. + +Although early detection can significantly improve outcomes, insufficient screening methods and delayed diagnosis often lead to preventable complications. Therefore, developing rapid and accurate diagnostic tools is crucial for effective prevention and treatment of cardiovascular conditions. + +To address this challenge, we utilize Singa to develop a machine learning model for cardiovascular disease risk prediction. The training dataset is sourced from Kaggle https://www.kaggle.com/datasets/sulianova/cardiovascular-disease-dataset. Please download the dataset before running the scripts. + +## Structure + +* `cardiovascular.py` in the `healthcare/data` directory is the scripts for preprocessing Cardiovascular Disease datasets. + +* `cardiovascular_net.py` in the `healthcare/models` directory includes the MLP model construction codes. + +* `train_cnn.py` is the training script, which controls the training flow by + doing BackPropagation and SGD update. + +## Command +```bash +python train_cnn.py mlp cardiovascular +``` From 4ec096dc3527a709daefd2198c88e1806293dc82 Mon Sep 17 00:00:00 2001 From: zmeihui Date: Tue, 4 Mar 2025 12:05:48 +0800 Subject: [PATCH 14/53] Add the dataset for the kidney disease prediction Add the dataset for the kidney disease prediction --- examples/healthcare/data/kidneydata.py | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/healthcare/data/kidneydata.py diff --git a/examples/healthcare/data/kidneydata.py b/examples/healthcare/data/kidneydata.py new file mode 100644 index 000000000..4144acf70 --- /dev/null +++ b/examples/healthcare/data/kidneydata.py @@ -0,0 +1,57 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +import numpy as np +import torch +from tqdm import tqdm +import pickle + + +def load_dataset(): + with open('/home/kidney_disease/kidney_features.pkl','rb') as f: # change the path to load dataset + features = pickle.load(f) + with open('/home/kidney_disease/kidney_labels.pkl','rb') as f: # change the path to load dataset + labels = pickle.load(f) + + + split_train_point = int(len(features) * 8/ 10) + train_x, train_y = features[:split_train_point], labels[:split_train_point] + val_x, val_y = features[split_train_point:], labels[split_train_point:] + + return train_x,train_y,val_x,val_y + +def process_label(data): + new_labels = [] + for i in tqdm(data, total=len(data)): + label = torch.squeeze(i, dim=0) + new_labels.append(label) + return new_labels + + + +def load(): + train_x,train_y,val_x,val_y = load_dataset() + + + train_x = train_x.astype(np.float32) + val_x = val_x.astype(np.float32) + train_y = train_y.astype(np.int32) + val_y = val_y.astype(np.int32) + + return train_x,train_y,val_x,val_y \ No newline at end of file From 59c447cbcac398bd7858c3b0ad9a095f49c86b49 Mon Sep 17 00:00:00 2001 From: Cai Shaofeng Date: Tue, 4 Mar 2025 19:31:01 +0800 Subject: [PATCH 15/53] Add the training file for the kidney disease prediction Add the training file for the kidney disease prediction --- .../Kidney_Disease/train_kidney_mlp.py | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py diff --git a/examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py b/examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py new file mode 100644 index 000000000..474858066 --- /dev/null +++ b/examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py @@ -0,0 +1,319 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from singa import singa_wrap as singa +from singa import device +from singa import tensor +from singa import opt +import numpy as np +import time +import argparse +from PIL import Image + +np_dtype = {"float16": np.float16, "float32": np.float32} + +singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} + + +# Data augmentation +def augmentation(x, batch_size): + xpad = np.pad(x, [[0, 0], [0, 0], [4, 4], [4, 4]], 'symmetric') + for data_num in range(0, batch_size): + offset = np.random.randint(8, size=2) + x[data_num, :, :, :] = xpad[data_num, :, + offset[0]:offset[0] + x.shape[2], + offset[1]:offset[1] + x.shape[2]] + if_flip = np.random.randint(2) + if (if_flip): + x[data_num, :, :, :] = x[data_num, :, :, ::-1] + return x + + +# Calculate accuracy +def accuracy(pred, target): + # y is network output to be compared with ground truth (int) + y = np.argmax(pred, axis=1) + #print('y:',y) + #print('tar:',target) + a = y == target + #print(np.array(a, "int")) + correct = np.array(a, "int").sum() + return correct + + +# Data partition according to the rank +def partition(global_rank, world_size, train_x, train_y, val_x, val_y): + # Partition training data + data_per_rank = train_x.shape[0] // world_size + idx_start = global_rank * data_per_rank + idx_end = (global_rank + 1) * data_per_rank + train_x = train_x[idx_start:idx_end] + train_y = train_y[idx_start:idx_end] + + # Partition evaluation data + data_per_rank = val_x.shape[0] // world_size + idx_start = global_rank * data_per_rank + idx_end = (global_rank + 1) * data_per_rank + val_x = val_x[idx_start:idx_end] + val_y = val_y[idx_start:idx_end] + return train_x, train_y, val_x, val_y + + +# Function to all reduce NUMPY accuracy and loss from multiple devices +def reduce_variable(variable, dist_opt, reducer): + reducer.copy_from_numpy(variable) + dist_opt.all_reduce(reducer.data) + dist_opt.wait() + output = tensor.to_numpy(reducer) + return output + + +def resize_dataset(x, image_size): + num_data = x.shape[0] + dim = x.shape[1] + X = np.zeros(shape=(num_data, dim, image_size, image_size), + dtype=np.float32) + for n in range(0, num_data): + for d in range(0, dim): + X[n, d, :, :] = np.array(Image.fromarray(x[n, d, :, :]).resize( + (image_size, image_size), Image.BILINEAR), + dtype=np.float32) + return X + + +def run(global_rank, + world_size, + local_rank, + max_epoch, + batch_size, + model, + data, + sgd, + graph, + verbosity, + dist_option='plain', + spars=None, + precision='float32'): + #dev = device.create_cuda_gpu_on(local_rank) # need to change to CPU device for CPU-only machines + dev = device.get_default_device() + dev.SetRandSeed(0) + np.random.seed(0) + + if data == 'kidney-disease': + from data import load_kidneydata + train_x, train_y, val_x, val_y = load_kidneydata.load() + else: + print('Wrong Dataset!') + sys.exit(0) + + + num_channels = train_x.shape[1] + image_size = train_x.shape[2] + data_size = np.prod(train_x.shape[1:train_x.ndim]).item() + num_classes = (np.max(train_y) + 1).item() + print(num_channels,image_size) + + + if model == 'mlp': + import os, sys, inspect + current = os.path.dirname( + os.path.abspath(inspect.getfile(inspect.currentframe()))) + parent = os.path.dirname(current) + sys.path.insert(0, parent) + from mlp import model + model = model.create_model(data_size=data_size, + num_classes=num_classes) + else: + print('Wrong model!') + sys.exit(0) + + # For distributed training, sequential has better performance + if hasattr(sgd, "communicator"): + DIST = True + sequential = True + else: + DIST = False + sequential = False + + if DIST: + train_x, train_y, val_x, val_y = partition(global_rank, world_size, + train_x, train_y, val_x, + val_y) + + if model.dimension == 4: + tx = tensor.Tensor( + (batch_size, num_channels, model.input_size, model.input_size), dev, + singa_dtype[precision]) + elif model.dimension == 2: + tx = tensor.Tensor((batch_size, data_size), dev, singa_dtype[precision]) + np.reshape(train_x, (train_x.shape[0], -1)) + np.reshape(val_x, (val_x.shape[0], -1)) + + ty = tensor.Tensor((batch_size,), dev, tensor.int32) + num_train_batch = train_x.shape[0] // batch_size + num_val_batch = val_x.shape[0] // batch_size + idx = np.arange(train_x.shape[0], dtype=np.int32) + + # Attach model to graph + model.set_optimizer(sgd) + model.compile([tx], is_train=True, use_graph=graph, sequential=sequential) + dev.SetVerbosity(verbosity) + + # Training and evaluation loop + for epoch in range(max_epoch): + start_time = time.time() + np.random.shuffle(idx) + + if global_rank == 0: + print('Starting Epoch %d:' % (epoch)) + + # Training phase + train_correct = np.zeros(shape=[1], dtype=np.float32) + test_correct = np.zeros(shape=[1], dtype=np.float32) + train_loss = np.zeros(shape=[1], dtype=np.float32) + + model.train() + for b in range(num_train_batch): + # if b % 100 == 0: + # print ("b: \n", b) + # Generate the patch data in this iteration + x = train_x[idx[b * batch_size:(b + 1) * batch_size]] + if model.dimension == 4: + x = augmentation(x, batch_size) + if (image_size != model.input_size): + x = resize_dataset(x, model.input_size) + x = x.astype(np_dtype[precision]) + y = train_y[idx[b * batch_size:(b + 1) * batch_size]] + + # Copy the patch data into input tensors + tx.copy_from_numpy(x) + ty.copy_from_numpy(y) + + # Train the model + out, loss = model(tx, ty, dist_option, spars) + train_correct += accuracy(tensor.to_numpy(out), y) + train_loss += tensor.to_numpy(loss)[0] + + if DIST: + # Reduce the evaluation accuracy and loss from multiple devices + reducer = tensor.Tensor((1,), dev, tensor.float32) + train_correct = reduce_variable(train_correct, sgd, reducer) + train_loss = reduce_variable(train_loss, sgd, reducer) + + if global_rank == 0: + print('Training loss = %f, training accuracy = %f' % + (train_loss, train_correct / + (num_train_batch * batch_size * world_size)), + flush=True) + + # Evaluation phase + model.eval() + for b in range(num_val_batch): + x = val_x[b * batch_size:(b + 1) * batch_size] + if model.dimension == 4: + if (image_size != model.input_size): + x = resize_dataset(x, model.input_size) + x = x.astype(np_dtype[precision]) + y = val_y[b * batch_size:(b + 1) * batch_size] + tx.copy_from_numpy(x) + ty.copy_from_numpy(y) + out_test = model(tx) + test_correct += accuracy(tensor.to_numpy(out_test), y) + + if DIST: + # Reduce the evaulation accuracy from multiple devices + test_correct = reduce_variable(test_correct, sgd, reducer) + + # Output the evaluation accuracy + if global_rank == 0: + print('Evaluation accuracy = %f, Elapsed Time = %fs' % + (test_correct / (num_val_batch * batch_size * world_size), + time.time() - start_time), + flush=True) + + dev.PrintTimeProfiling() + + +if __name__ == '__main__': + # Use argparse to get command config: max_epoch, model, data, etc., for single gpu training + parser = argparse.ArgumentParser( + description='Training using the autograd and graph.') + parser.add_argument( + 'model', + choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet'], + default='cnn') + parser.add_argument('data', + choices=['mnist', 'cifar10', 'cifar100','mimic-iii','kidney-disease'], + default='kidney-disease') + parser.add_argument('-p', + choices=['float32', 'float16'], + default='float32', + dest='precision') + parser.add_argument('-m', + '--max-epoch', + default=20, + type=int, + help='maximum epochs', + dest='max_epoch') + parser.add_argument('-b', + '--batch-size', + default=64, + type=int, + help='batch size', + dest='batch_size') + parser.add_argument('-l', + '--learning-rate', + default=0.005, + type=float, + help='initial learning rate', + dest='lr') + # Determine which gpu to use + parser.add_argument('-i', + '--device-id', + default=0, + type=int, + help='which GPU to use', + dest='device_id') + parser.add_argument('-g', + '--disable-graph', + default='True', + action='store_false', + help='disable graph', + dest='graph') + parser.add_argument('-v', + '--log-verbosity', + default=0, + type=int, + help='logging verbosity', + dest='verbosity') + + args = parser.parse_args() + + sgd = opt.SGD(lr=args.lr, momentum=0.9, weight_decay=1e-5, dtype=singa_dtype[args.precision]) + run(0, + 1, + args.device_id, + args.max_epoch, + args.batch_size, + args.model, + args.data, + sgd, + args.graph, + args.verbosity, + precision=args.precision) From a1f292c2ec78430601a8d188afb6871ac475211d Mon Sep 17 00:00:00 2001 From: Cai Shaofeng Date: Tue, 4 Mar 2025 19:32:16 +0800 Subject: [PATCH 16/53] Delete Malaria_Detection Delete Malaria_Detection --- .../healthcare/Malaria_Detection/README.md | 44 --- .../Malaria_Detection/data/malaria.py | 122 -------- .../healthcare/Malaria_Detection/model/cnn.py | 94 ------ .../healthcare/Malaria_Detection/model/mlp.py | 85 ----- examples/healthcare/Malaria_Detection/run.sh | 20 -- .../healthcare/Malaria_Detection/train_cnn.py | 294 ------------------ 6 files changed, 659 deletions(-) delete mode 100644 examples/healthcare/Malaria_Detection/README.md delete mode 100644 examples/healthcare/Malaria_Detection/data/malaria.py delete mode 100644 examples/healthcare/Malaria_Detection/model/cnn.py delete mode 100644 examples/healthcare/Malaria_Detection/model/mlp.py delete mode 100644 examples/healthcare/Malaria_Detection/run.sh delete mode 100644 examples/healthcare/Malaria_Detection/train_cnn.py diff --git a/examples/healthcare/Malaria_Detection/README.md b/examples/healthcare/Malaria_Detection/README.md deleted file mode 100644 index b9dcbf239..000000000 --- a/examples/healthcare/Malaria_Detection/README.md +++ /dev/null @@ -1,44 +0,0 @@ - - -# Singa for Malaria Detection Task - -## Malaria - -Malaria is caused by parasites and could be transmitted through infected mosquitoes. There are about 200 million cases worldwide, and about 400,000 deaths per year, therefore, malaria does lots of harm to global health. - -Although Malaria is a curable disease, inadequate diagnostics make it harder to reduce mortality, as a result, a fast and reliable diagnostic test is a promising and effective way to fight malaria. - -To mitigate the problem, we use Singa to implement a machine learning model to help with Malaria diagnosis. The dataset is from Kaggle https://www.kaggle.com/datasets/miracle9to9/files1?resource=download. Please download the dataset before running the scripts. - -## Structure - -* `data` includes the scripts for preprocessing Malaria image datasets. - -* `model` includes the CNN model construction codes by creating - a subclass of `Module` to wrap the neural network operations - of each model. - -* `train_cnn.py` is the training script, which controls the training flow by - doing BackPropagation and SGD update. - -## Command -```bash -python train_cnn.py cnn malaria -dir pathToDataset -``` \ No newline at end of file diff --git a/examples/healthcare/Malaria_Detection/data/malaria.py b/examples/healthcare/Malaria_Detection/data/malaria.py deleted file mode 100644 index 46422b739..000000000 --- a/examples/healthcare/Malaria_Detection/data/malaria.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -try: - import pickle -except ImportError: - import cPickle as pickle - -import numpy as np -import os -import sys -from PIL import Image - - -# need to save to specific local directories -def load_train_data(dir_path="/tmp/malaria", resize_size=(128, 128)): - dir_path = check_dataset_exist(dirpath=dir_path) - path_train_label_1 = os.path.join(dir_path, "training_set/Parasitized") - path_train_label_0 = os.path.join(dir_path, "training_set/Uninfected") - train_label_1 = load_image_path(os.listdir(path_train_label_1)) - train_label_0 = load_image_path(os.listdir(path_train_label_0)) - labels = [] - Images = np.empty((len(train_label_1) + len(train_label_0), - 3, resize_size[0], resize_size[1]), dtype=np.uint8) - for i in range(len(train_label_0)): - image_path = os.path.join(path_train_label_0, train_label_0[i]) - temp_image = np.array(Image.open(image_path).resize( - resize_size).convert("RGB")).transpose(2, 0, 1) - Images[i] = temp_image - labels.append(0) - for i in range(len(train_label_1)): - image_path = os.path.join(path_train_label_1, train_label_1[i]) - temp_image = np.array(Image.open(image_path).resize( - resize_size).convert("RGB")).transpose(2, 0, 1) - Images[i + len(train_label_0)] = temp_image - labels.append(1) - - Images = np.array(Images, dtype=np.float32) - labels = np.array(labels, dtype=np.int32) - return Images, labels - - -# need to save to specific local directories -def load_test_data(dir_path='/tmp/malaria', resize_size=(128, 128)): - dir_path = check_dataset_exist(dirpath=dir_path) - path_test_label_1 = os.path.join(dir_path, "testing_set/Parasitized") - path_test_label_0 = os.path.join(dir_path, "testing_set/Uninfected") - test_label_1 = load_image_path(os.listdir(path_test_label_1)) - test_label_0 = load_image_path(os.listdir(path_test_label_0)) - labels = [] - Images = np.empty((len(test_label_1) + len(test_label_0), - 3, resize_size[0], resize_size[1]), dtype=np.uint8) - for i in range(len(test_label_0)): - image_path = os.path.join(path_test_label_0, test_label_0[i]) - temp_image = np.array(Image.open(image_path).resize( - resize_size).convert("RGB")).transpose(2, 0, 1) - Images[i] = temp_image - labels.append(0) - for i in range(len(test_label_1)): - image_path = os.path.join(path_test_label_1, test_label_1[i]) - temp_image = np.array(Image.open(image_path).resize( - resize_size).convert("RGB")).transpose(2, 0, 1) - Images[i + len(test_label_0)] = temp_image - labels.append(1) - - Images = np.array(Images, dtype=np.float32) - labels = np.array(labels, dtype=np.int32) - return Images, labels - - -def load_image_path(list): - new_list = [] - for image_path in list: - if (image_path.endswith(".png") or image_path.endswith(".jpg")): - new_list.append(image_path) - return new_list - - -def check_dataset_exist(dirpath): - if not os.path.exists(dirpath): - print( - 'Please download the malaria dataset first' - ) - sys.exit(0) - return dirpath - - -def normalize(train_x, val_x): - mean = [0.5339, 0.4180, 0.4460] # mean for malaria dataset - std = [0.3329, 0.2637, 0.2761] # std for malaria dataset - train_x /= 255 - val_x /= 255 - for ch in range(0, 2): - train_x[:, ch, :, :] -= mean[ch] - train_x[:, ch, :, :] /= std[ch] - val_x[:, ch, :, :] -= mean[ch] - val_x[:, ch, :, :] /= std[ch] - return train_x, val_x - - -def load(dir_path): - train_x, train_y = load_train_data(dir_path=dir_path) - val_x, val_y = load_test_data(dir_path=dir_path) - train_x, val_x = normalize(train_x, val_x) - train_y = train_y.flatten() - val_y = val_y.flatten() - return train_x, train_y, val_x, val_y diff --git a/examples/healthcare/Malaria_Detection/model/cnn.py b/examples/healthcare/Malaria_Detection/model/cnn.py deleted file mode 100644 index 856adb7e7..000000000 --- a/examples/healthcare/Malaria_Detection/model/cnn.py +++ /dev/null @@ -1,94 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -from singa import layer -from singa import model - - -class CNN(model.Model): - - def __init__(self, num_classes=10, num_channels=1): - super(CNN, self).__init__() - self.num_classes = num_classes - self.input_size = 128 - self.dimension = 4 - self.conv1 = layer.Conv2d(num_channels, 32, 3, padding=0, activation="RELU") - self.conv2 = layer.Conv2d(32, 64, 3, padding=0, activation="RELU") - self.conv3 = layer.Conv2d(64, 64, 3, padding=0, activation="RELU") - self.linear1 = layer.Linear(128) - self.linear2 = layer.Linear(num_classes) - self.pooling1 = layer.MaxPool2d(2, 2, padding=0) - self.pooling2 = layer.MaxPool2d(2, 2, padding=0) - self.pooling3 = layer.MaxPool2d(2, 2, padding=0) - self.relu = layer.ReLU() - self.flatten = layer.Flatten() - self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() - self.sigmoid = layer - - def forward(self, x): - y = self.conv1(x) - y = self.pooling1(y) - y = self.conv2(y) - y = self.pooling2(y) - y = self.conv3(y) - y = self.pooling3(y) - y = self.flatten(y) - y = self.linear1(y) - y = self.relu(y) - y = self.linear2(y) - return y - - def train_one_batch(self, x, y, dist_option, spars): - out = self.forward(x) - loss = self.softmax_cross_entropy(out, y) - - if dist_option == 'plain': - self.optimizer(loss) - elif dist_option == 'half': - self.optimizer.backward_and_update_half(loss) - elif dist_option == 'partialUpdate': - self.optimizer.backward_and_partial_update(loss) - elif dist_option == 'sparseTopK': - self.optimizer.backward_and_sparse_update(loss, - topK=True, - spars=spars) - elif dist_option == 'sparseThreshold': - self.optimizer.backward_and_sparse_update(loss, - topK=False, - spars=spars) - return out, loss - - def set_optimizer(self, optimizer): - self.optimizer = optimizer - - -def create_model(**kwargs): - """Constructs a CNN model. - - Args: - pretrained (bool): If True, returns a pre-trained model. - - Returns: - The created CNN model. - """ - model = CNN(**kwargs) - - return model - - -__all__ = ['CNN', 'create_model'] diff --git a/examples/healthcare/Malaria_Detection/model/mlp.py b/examples/healthcare/Malaria_Detection/model/mlp.py deleted file mode 100644 index 5f46bc321..000000000 --- a/examples/healthcare/Malaria_Detection/model/mlp.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -from singa import layer -from singa import model -from singa import tensor -from singa import opt -from singa import device -import argparse -import numpy as np - -np_dtype = {"float16": np.float16, "float32": np.float32} - -singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} - - -class MLP(model.Model): - - def __init__(self, perceptron_size=100, num_classes=10): - super(MLP, self).__init__() - self.num_classes = num_classes - self.dimension = 2 - - self.relu = layer.ReLU() - self.linear1 = layer.Linear(perceptron_size) - self.linear2 = layer.Linear(num_classes) - self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() - - def forward(self, inputs): - y = self.linear1(inputs) - y = self.relu(y) - y = self.linear2(y) - return y - - def train_one_batch(self, x, y, dist_option, spars): - out = self.forward(x) - loss = self.softmax_cross_entropy(out, y) - - if dist_option == 'plain': - self.optimizer(loss) - elif dist_option == 'half': - self.optimizer.backward_and_update_half(loss) - elif dist_option == 'partialUpdate': - self.optimizer.backward_and_partial_update(loss) - elif dist_option == 'sparseTopK': - self.optimizer.backward_and_sparse_update(loss, - topK=True, - spars=spars) - elif dist_option == 'sparseThreshold': - self.optimizer.backward_and_sparse_update(loss, - topK=False, - spars=spars) - return out, loss - - def set_optimizer(self, optimizer): - self.optimizer = optimizer - - -def create_model(**kwargs): - """Constructs a CNN model. - - Returns: - The created CNN model. - """ - model = MLP(**kwargs) - - return model - - -__all__ = ['MLP', 'create_model'] diff --git a/examples/healthcare/Malaria_Detection/run.sh b/examples/healthcare/Malaria_Detection/run.sh deleted file mode 100644 index 14718208b..000000000 --- a/examples/healthcare/Malaria_Detection/run.sh +++ /dev/null @@ -1,20 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -### malaria dataset -python train_cnn.py cnn malaria -dir pathToDataset diff --git a/examples/healthcare/Malaria_Detection/train_cnn.py b/examples/healthcare/Malaria_Detection/train_cnn.py deleted file mode 100644 index bfe810d4f..000000000 --- a/examples/healthcare/Malaria_Detection/train_cnn.py +++ /dev/null @@ -1,294 +0,0 @@ -from singa import singa_wrap as singa -from singa import device -from singa import tensor -from singa import opt -import numpy as np -import time -import argparse -import sys -from PIL import Image - -np_dtype = {"float16": np.float16, "float32": np.float32} - -singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} - - -# Data augmentation -def augmentation(x, batch_size): - xpad = np.pad(x, [[0, 0], [0, 0], [4, 4], [4, 4]], 'symmetric') - for data_num in range(0, batch_size): - offset = np.random.randint(8, size=2) - x[data_num, :, :, :] = xpad[data_num, :, - offset[0]:offset[0] + x.shape[2], - offset[1]:offset[1] + x.shape[2]] - if_flip = np.random.randint(2) - if (if_flip): - x[data_num, :, :, :] = x[data_num, :, :, ::-1] - return x - - -# Calculate accuracy -def accuracy(pred, target): - # y is network output to be compared with ground truth (int) - y = np.argmax(pred, axis=1) - a = y == target - correct = np.array(a, "int").sum() - return correct - - -# Data partition according to the rank -def partition(global_rank, world_size, train_x, train_y, val_x, val_y): - # Partition training data - data_per_rank = train_x.shape[0] // world_size - idx_start = global_rank * data_per_rank - idx_end = (global_rank + 1) * data_per_rank - train_x = train_x[idx_start:idx_end] - train_y = train_y[idx_start:idx_end] - - # Partition evaluation data - data_per_rank = val_x.shape[0] // world_size - idx_start = global_rank * data_per_rank - idx_end = (global_rank + 1) * data_per_rank - val_x = val_x[idx_start:idx_end] - val_y = val_y[idx_start:idx_end] - return train_x, train_y, val_x, val_y - - -# Function to all reduce NUMPY accuracy and loss from multiple devices -def reduce_variable(variable, dist_opt, reducer): - reducer.copy_from_numpy(variable) - dist_opt.all_reduce(reducer.data) - dist_opt.wait() - output = tensor.to_numpy(reducer) - return output - - -def resize_dataset(x, image_size): - num_data = x.shape[0] - dim = x.shape[1] - X = np.zeros(shape=(num_data, dim, image_size, image_size), - dtype=np.float32) - for n in range(0, num_data): - for d in range(0, dim): - X[n, d, :, :] = np.array(Image.fromarray(x[n, d, :, :]).resize( - (image_size, image_size), Image.BILINEAR), - dtype=np.float32) - return X - - -def run(global_rank, - world_size, - dir_path, - max_epoch, - batch_size, - model, - data, - sgd, - graph, - verbosity, - dist_option='plain', - spars=None, - precision='float32'): - # now CPU version only, could change to GPU device for GPU-support machines - dev = device.get_default_device() - dev.SetRandSeed(0) - np.random.seed(0) - if data == 'malaria': - from data import malaria - train_x, train_y, val_x, val_y = malaria.load(dir_path=dir_path) - else: - print( - 'Wrong dataset!' - ) - sys.exit(0) - - num_channels = train_x.shape[1] - image_size = train_x.shape[2] - data_size = np.prod(train_x.shape[1:train_x.ndim]).item() - num_classes = (np.max(train_y) + 1).item() - - if model == 'cnn': - from model import cnn - model = cnn.create_model(num_channels=num_channels, - num_classes=num_classes) - else: - print( - 'Wrong model!' - ) - sys.exit(0) - - # For distributed training, sequential has better performance - if hasattr(sgd, "communicator"): - DIST = True - sequential = True - else: - DIST = False - sequential = False - - if DIST: - train_x, train_y, val_x, val_y = partition(global_rank, world_size, - train_x, train_y, val_x, - val_y) - - if model.dimension == 4: - tx = tensor.Tensor( - (batch_size, num_channels, model.input_size, model.input_size), dev, - singa_dtype[precision]) - elif model.dimension == 2: - tx = tensor.Tensor((batch_size, data_size), - dev, singa_dtype[precision]) - np.reshape(train_x, (train_x.shape[0], -1)) - np.reshape(val_x, (val_x.shape[0], -1)) - - ty = tensor.Tensor((batch_size,), dev, tensor.int32) - num_train_batch = train_x.shape[0] // batch_size - num_val_batch = val_x.shape[0] // batch_size - idx = np.arange(train_x.shape[0], dtype=np.int32) - - # Attach model to graph - model.set_optimizer(sgd) - model.compile([tx], is_train=True, use_graph=graph, sequential=sequential) - dev.SetVerbosity(verbosity) - - # Training and evaluation loop - for epoch in range(max_epoch): - start_time = time.time() - np.random.shuffle(idx) - - if global_rank == 0: - print('Starting Epoch %d:' % (epoch)) - - # Training phase - train_correct = np.zeros(shape=[1], dtype=np.float32) - test_correct = np.zeros(shape=[1], dtype=np.float32) - train_loss = np.zeros(shape=[1], dtype=np.float32) - - model.train() - for b in range(num_train_batch): - # if b % 100 == 0: - # print ("b: \n", b) - # Generate the patch data in this iteration - x = train_x[idx[b * batch_size:(b + 1) * batch_size]] - if model.dimension == 4: - x = augmentation(x, batch_size) - if (image_size != model.input_size): - x = resize_dataset(x, model.input_size) - x = x.astype(np_dtype[precision]) - y = train_y[idx[b * batch_size:(b + 1) * batch_size]] - - # Copy the patch data into input tensors - tx.copy_from_numpy(x) - ty.copy_from_numpy(y) - - # Train the model - out, loss = model(tx, ty, dist_option, spars) - train_correct += accuracy(tensor.to_numpy(out), y) - train_loss += tensor.to_numpy(loss)[0] - - if DIST: - # Reduce the evaluation accuracy and loss from multiple devices - reducer = tensor.Tensor((1,), dev, tensor.float32) - train_correct = reduce_variable(train_correct, sgd, reducer) - train_loss = reduce_variable(train_loss, sgd, reducer) - - if global_rank == 0: - print('Training loss = %f, training accuracy = %f' % - (train_loss, train_correct / - (num_train_batch * batch_size * world_size)), - flush=True) - - # Evaluation phase - model.eval() - for b in range(num_val_batch): - x = val_x[b * batch_size:(b + 1) * batch_size] - if model.dimension == 4: - if (image_size != model.input_size): - x = resize_dataset(x, model.input_size) - x = x.astype(np_dtype[precision]) - y = val_y[b * batch_size:(b + 1) * batch_size] - tx.copy_from_numpy(x) - ty.copy_from_numpy(y) - out_test = model(tx) - test_correct += accuracy(tensor.to_numpy(out_test), y) - - if DIST: - # Reduce the evaulation accuracy from multiple devices - test_correct = reduce_variable(test_correct, sgd, reducer) - - # Output the evaluation accuracy - if global_rank == 0: - print('Evaluation accuracy = %f, Elapsed Time = %fs' % - (test_correct / (num_val_batch * batch_size * world_size), - time.time() - start_time), - flush=True) - - dev.PrintTimeProfiling() - - -if __name__ == '__main__': - # Use argparse to get command config: max_epoch, model, data, etc., for single gpu training - parser = argparse.ArgumentParser( - description='Training using the autograd and graph.') - parser.add_argument( - 'model', - choices=['cnn'], - default='cnn') - parser.add_argument('data', - choices=['malaria'], - default='malaria') - parser.add_argument('-p', - choices=['float32', 'float16'], - default='float32', - dest='precision') - parser.add_argument('-dir', - '--dir-path', - default="/tmp/malaria", - type=str, - help='the directory to store the malaria dataset', - dest='dir_path') - parser.add_argument('-m', - '--max-epoch', - default=100, - type=int, - help='maximum epochs', - dest='max_epoch') - parser.add_argument('-b', - '--batch-size', - default=64, - type=int, - help='batch size', - dest='batch_size') - parser.add_argument('-l', - '--learning-rate', - default=0.005, - type=float, - help='initial learning rate', - dest='lr') - parser.add_argument('-g', - '--disable-graph', - default='True', - action='store_false', - help='disable graph', - dest='graph') - parser.add_argument('-v', - '--log-verbosity', - default=0, - type=int, - help='logging verbosity', - dest='verbosity') - - args = parser.parse_args() - - sgd = opt.SGD(lr=args.lr, momentum=0.9, weight_decay=1e-5, - dtype=singa_dtype[args.precision]) - run(0, - 1, - args.dir_path, - args.max_epoch, - args.batch_size, - args.model, - args.data, - sgd, - args.graph, - args.verbosity, - precision=args.precision) From 081e417013ebbe47449b4687fc2a582054be5e62 Mon Sep 17 00:00:00 2001 From: lemonviv Date: Tue, 4 Mar 2025 23:36:04 +0800 Subject: [PATCH 17/53] Restructure the healthcare folder --- .../Hematologic_Disease/ClassDemo.py | 270 ------------------ .../healthcare/Hematologic_Disease/Readme.md | 45 --- .../Hematologic_Disease/transforms.py | 166 ----------- .../readme.md | 0 .../run.sh | 0 .../train_cnn.py | 0 .../README.md | 0 .../run.sh | 0 .../train.py | 0 9 files changed, 481 deletions(-) delete mode 100644 examples/healthcare/Hematologic_Disease/ClassDemo.py delete mode 100644 examples/healthcare/Hematologic_Disease/Readme.md delete mode 100644 examples/healthcare/Hematologic_Disease/transforms.py rename examples/healthcare/application/{Malaria_Detection => Malaria_Disease}/readme.md (100%) rename examples/healthcare/application/{Malaria_Detection => Malaria_Disease}/run.sh (100%) rename examples/healthcare/application/{Malaria_Detection => Malaria_Disease}/train_cnn.py (100%) rename examples/healthcare/application/{TED_CT_Detection => Thyroid_Eye_Disease}/README.md (100%) rename examples/healthcare/application/{TED_CT_Detection => Thyroid_Eye_Disease}/run.sh (100%) rename examples/healthcare/application/{TED_CT_Detection => Thyroid_Eye_Disease}/train.py (100%) diff --git a/examples/healthcare/Hematologic_Disease/ClassDemo.py b/examples/healthcare/Hematologic_Disease/ClassDemo.py deleted file mode 100644 index a6872f8cb..000000000 --- a/examples/healthcare/Hematologic_Disease/ClassDemo.py +++ /dev/null @@ -1,270 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -import json -import os -import time -from glob import glob - -import numpy as np -from PIL import Image -from singa import device, layer, model, opt, tensor -from tqdm import tqdm - -from transforms import Compose, Normalize, ToTensor - -np_dtype = {"float16": np.float16, "float32": np.float32} -singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} - - -class ClassDataset(object): - """Fetch data from file and generate batches. - - Load data from folder as PIL.Images and convert them into batch array. - - Args: - img_folder (Str): Folder path of the training/validation images. - transforms (Transform): Preprocess transforms. - """ - def __init__(self, img_folder, transforms): - super(ClassDataset, self).__init__() - - self.img_list = list() - self.transforms = transforms - - classes = os.listdir(img_folder) - for i in classes: - images = glob(os.path.join(img_folder, i, "*")) - for img in images: - self.img_list.append((img, i)) - - def __len__(self) -> int: - return len(self.img_list) - - def __getitem__(self, index: int): - img_path, label_str = self.img_list[index] - img = Image.open(img_path) - img = self.transforms.forward(img) - label = np.array(label_str, dtype=np.int32) - - return img, label - - def batchgenerator(self, indexes, batch_size, data_size): - """Generate batch arrays from transformed image list. - - Args: - indexes (Sequence): current batch indexes list, e.g. [n, n + 1, ..., n + batch_size] - batch_size (int): - data_size (Tuple): input image size of shape (C, H, W) - - Return: - batch_x (Numpy ndarray): batch array of input images (B, C, H, W) - batch_y (Numpy ndarray): batch array of ground truth lables (B,) - """ - batch_x = np.zeros((batch_size,) + data_size) - batch_y = np.zeros((batch_size,) + (1,), dtype=np.int32) - for idx, i in enumerate(indexes): - sample_x, sample_y = self.__getitem__(i) - batch_x[idx, :, :, :] = sample_x - batch_y[idx, :] = sample_y - - return batch_x, batch_y - - -class CNNModel(model.Model): - def __init__(self, num_classes): - super(CNNModel, self).__init__() - self.input_size = 28 - self.dimension = 4 - self.num_classes = num_classes - - self.layer1 = layer.Conv2d(16, kernel_size=3, activation="RELU") - self.bn1 = layer.BatchNorm2d() - self.layer2 = layer.Conv2d(16, kernel_size=3, activation="RELU") - self.bn2 = layer.BatchNorm2d() - self.pooling2 = layer.MaxPool2d(kernel_size=2, stride=2) - self.layer3 = layer.Conv2d(64, kernel_size=3, activation="RELU") - self.bn3 = layer.BatchNorm2d() - self.layer4 = layer.Conv2d(64, kernel_size=3, activation="RELU") - self.bn4 = layer.BatchNorm2d() - self.layer5 = layer.Conv2d(64, kernel_size=3, padding=1, activation="RELU") - self.bn5 = layer.BatchNorm2d() - self.pooling5 = layer.MaxPool2d(kernel_size=2, stride=2) - - self.flatten = layer.Flatten() - - self.linear1 = layer.Linear(128) - self.linear2 = layer.Linear(128) - self.linear3 = layer.Linear(self.num_classes) - - self.relu = layer.ReLU() - - self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() - self.dropout = layer.Dropout(ratio=0.3) - - def forward(self, x): - x = self.layer1(x) - x = self.bn1(x) - x = self.layer2(x) - x = self.bn2(x) - x = self.pooling2(x) - - x = self.layer3(x) - x = self.bn3(x) - x = self.layer4(x) - x = self.bn4(x) - x = self.layer5(x) - x = self.bn5(x) - x = self.pooling5(x) - x = self.flatten(x) - x = self.linear1(x) - x = self.relu(x) - x = self.linear2(x) - x = self.relu(x) - x = self.linear3(x) - return x - - def set_optimizer(self, optimizer): - self.optimizer = optimizer - - def train_one_batch(self, x, y, dist_option, spars): - out = self.forward(x) - loss = self.softmax_cross_entropy(out, y) - - if dist_option == 'plain': - self.optimizer(loss) - elif dist_option == 'half': - self.optimizer.backward_and_update_half(loss) - elif dist_option == 'partialUpdate': - self.optimizer.backward_and_partial_update(loss) - elif dist_option == 'sparseTopK': - self.optimizer.backward_and_sparse_update(loss, - topK=True, - spars=spars) - elif dist_option == 'sparseThreshold': - self.optimizer.backward_and_sparse_update(loss, - topK=False, - spars=spars) - return out, loss - - -def accuracy(pred, target): - """Compute recall accuracy. - - Args: - pred (Numpy ndarray): Prediction array, should be in shape (B, C) - target (Numpy ndarray): Ground truth array, should be in shape (B, ) - - Return: - correct (Float): Recall accuracy - """ - # y is network output to be compared with ground truth (int) - y = np.argmax(pred, axis=1) - a = (y[:,None]==target).sum() - correct = np.array(a, "int").sum() - return correct - - -# Define pre-processing methods (transforms) -transforms = Compose([ - ToTensor(), - Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) -]) - -# Dataset loading -dataset_path = "./bloodmnist" -train_path = os.path.join(dataset_path, "train") -val_path = os.path.join(dataset_path, "val") -cfg_path = os.path.join(dataset_path, "param.json") - -with open(cfg_path,'r') as load_f: - num_class = json.load(load_f)["num_classes"] - -train_dataset = ClassDataset(train_path, transforms) -val_dataset = ClassDataset(val_path, transforms) - -batch_size = 256 - -# Model configuration for CNN -model = CNNModel(num_classes=num_class) -criterion = layer.SoftMaxCrossEntropy() -optimizer_ft = opt.Adam(lr=1e-3) - -# Start training -dev = device.create_cpu_device() -dev.SetRandSeed(0) -np.random.seed(0) - -tx = tensor.Tensor( - (batch_size, 3, model.input_size, model.input_size), dev, - singa_dtype['float32']) -ty = tensor.Tensor((batch_size,), dev, tensor.int32) - -num_train_batch = train_dataset.__len__() // batch_size -num_val_batch = val_dataset.__len__() // batch_size -idx = np.arange(train_dataset.__len__(), dtype=np.int32) - -model.set_optimizer(optimizer_ft) -model.compile([tx], is_train=True, use_graph=False, sequential=False) -dev.SetVerbosity(0) - -max_epoch = 100 -for epoch in range(max_epoch): - print(f'Epoch {epoch}:') - - start_time = time.time() - - train_correct = np.zeros(shape=[1], dtype=np.float32) - test_correct = np.zeros(shape=[1], dtype=np.float32) - train_loss = np.zeros(shape=[1], dtype=np.float32) - - # Training part - model.train() - for b in tqdm(range(num_train_batch)): - # Extract batch from image list - x, y = train_dataset.batchgenerator(idx[b * batch_size:(b + 1) * batch_size], - batch_size=batch_size, data_size=(3, model.input_size, model.input_size)) - x = x.astype(np_dtype['float32']) - - tx.copy_from_numpy(x) - ty.copy_from_numpy(y) - - out, loss = model(tx, ty, dist_option="plain", spars=None) - train_correct += accuracy(tensor.to_numpy(out), y) - train_loss += tensor.to_numpy(loss)[0] - print('Training loss = %f, training accuracy = %f' % - (train_loss, train_correct / - (num_train_batch * batch_size))) - - # Validation part - model.eval() - for b in tqdm(range(num_val_batch)): - x, y = train_dataset.batchgenerator(idx[b * batch_size:(b + 1) * batch_size], - batch_size=batch_size, data_size=(3, model.input_size, model.input_size)) - x = x.astype(np_dtype['float32']) - - tx.copy_from_numpy(x) - ty.copy_from_numpy(y) - - out = model(tx) - test_correct += accuracy(tensor.to_numpy(out), y) - - print('Evaluation accuracy = %f, Elapsed Time = %fs' % - (test_correct / (num_val_batch * batch_size), - time.time() - start_time)) diff --git a/examples/healthcare/Hematologic_Disease/Readme.md b/examples/healthcare/Hematologic_Disease/Readme.md deleted file mode 100644 index c519e9d7d..000000000 --- a/examples/healthcare/Hematologic_Disease/Readme.md +++ /dev/null @@ -1,45 +0,0 @@ - -# CNN demo model on BloodMnist dataset - -## About dataset -Download address: https://drive.google.com/drive/folders/1Ze9qri1UtAsIRoI0SJ4YRpdt5kUUMBEn?usp=sharing - -The BloodMNIST , as a sub set of [MedMNIST](https://medmnist.com/), is based on a dataset of individual normal cells, captured from individuals without infection, hematologic or oncologic disease and free of any pharmacologic treatment at the moment of blood collection. -It contains a total of 17,092 images and is organized into 8 classes. -it is split with a ratio of 7:1:2 into training, validation and test set. -The source images with resolution 3×360×363 pixels are center-cropped into 3×200×200, and then resized into 3×28×28. - -8 classes of the dataset: -```python -"0": "basophil", -"1": "eosinophil", -"2": "erythroblast", -"3": "ig (immature granulocytes)", -"4": "lymphocyte", -"5": "monocyte", -"6": "neutrophil", -"7": "platelet" -``` - -# Run the demo -Run -``` -python ClassDemo.py -``` \ No newline at end of file diff --git a/examples/healthcare/Hematologic_Disease/transforms.py b/examples/healthcare/Hematologic_Disease/transforms.py deleted file mode 100644 index 5b5111798..000000000 --- a/examples/healthcare/Hematologic_Disease/transforms.py +++ /dev/null @@ -1,166 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - - -import numpy as np -from PIL import Image - - -class Compose(object): - """Compose several transforms together. - - Args: - transforms: list of transforms to compose. - - Example: - >>> transforms.Compose([ - >>> transforms.ToTensor(), - >>> transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) - >>> ]) - - """ - def __init__(self, transforms): - self.transforms = transforms - - def forward(self, img): - """ - Args: - img (PIL Image or numpy array): Image to be processed. - - Returns: - PIL Image or numpy array: Processed image. - """ - for t in self.transforms: - img = t.forward(img) - return img - - def __repr__(self): - format_string = self.__class__.__name__ + '(' - for t in self.transforms: - format_string += '\n' - format_string += ' {0}'.format(t) - format_string += '\n)' - return format_string - - -class ToTensor(object): - """Convert a ``PIL Image`` to ``numpy.ndarray``. - - Converts a PIL Image (H x W x C) in the range [0, 255] to a ``numpy.array`` of shape - (C x H x W) in the range [0.0, 1.0] - if the PIL Image belongs to one of the modes (L, LA, P, I, F, RGB, YCbCr, RGBA, CMYK, 1). - - In the other cases, tensors are returned without scaling. - - .. note:: - Because the input image is scaled to [0.0, 1.0], this transformation should not be used when - transforming target image masks. - """ - - def forward(self, pic): - """ - Args: - pic (PIL Image): Image to be converted to array. - - Returns: - Array: Converted image. - """ - if not isinstance(pic, Image.Image): - raise TypeError('pic should be PIL Image. Got {}'.format(type(pic))) - - # Handle PIL Image - mode_to_nptype = {'I': np.int32, 'I;16': np.int16, 'F': np.float32} - img = np.array(pic, mode_to_nptype.get(pic.mode, np.uint8), copy=True) - - if pic.mode == '1': - img = 255 * img - - # Put it from HWC to CHW format - img = np.transpose(img, (2, 0, 1)) - - if img.dtype == np.uint8: - return np.array(np.float32(img)/255.0, dtype=np.float) - else: - return np.float(img) - - def __repr__(self): - return self.__class__.__name__ + '()' - - -class Normalize(object): - """Normalize a ``numpy.array`` image with mean and standard deviation. - - This transform does not support PIL Image. - Given mean: ``(mean[1],...,mean[n])`` and std: ``(std[1],..,std[n])`` for ``n`` - channels, this transform will normalize each channel of the input - ``numpy.array`` i.e., - ``output[channel] = (input[channel] - mean[channel]) / std[channel]`` - - .. note:: - This transform acts out of place, i.e., it does not mutate the input array. - - Args: - mean (Sequence): Sequence of means for each channel. - std (Sequence): Sequence of standard deviations for each channel. - inplace(bool, optional): Bool to make this operation in-place. - - """ - - def __init__(self, mean, std, inplace=False): - super().__init__() - self.mean = mean - self.std = std - self.inplace = inplace - - def forward(self, img: np.ndarray): - """ - Args: - img (Numpy ndarray): Array image to be normalized. - - Returns: - d_res (Numpy ndarray): Normalized Tensor image. - """ - if not isinstance(img, np.ndarray): - raise TypeError('Input img should be a numpy array. Got {}.'.format(type(img))) - - if not img.dtype == np.float: - raise TypeError('Input array should be a float array. Got {}.'.format(img.dtype)) - - if img.ndim < 3: - raise ValueError('Expected array to be an array image of size (..., C, H, W). Got img.shape = ' - '{}.'.format(img.shape)) - - if not self.inplace: - img = img.copy() - - dtype = img.dtype - mean = np.array(self.mean, dtype=dtype) - std = np.array(self.std, dtype=dtype) - if (std == 0).any(): - raise ValueError('std evaluated to zero after conversion to {}, leading to division by zero.'.format(dtype)) - s_res = np.subtract(img, mean[:, None, None]) - d_res = np.divide(s_res, std[:, None, None]) - - return d_res - - - def __repr__(self): - return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std) - - diff --git a/examples/healthcare/application/Malaria_Detection/readme.md b/examples/healthcare/application/Malaria_Disease/readme.md similarity index 100% rename from examples/healthcare/application/Malaria_Detection/readme.md rename to examples/healthcare/application/Malaria_Disease/readme.md diff --git a/examples/healthcare/application/Malaria_Detection/run.sh b/examples/healthcare/application/Malaria_Disease/run.sh similarity index 100% rename from examples/healthcare/application/Malaria_Detection/run.sh rename to examples/healthcare/application/Malaria_Disease/run.sh diff --git a/examples/healthcare/application/Malaria_Detection/train_cnn.py b/examples/healthcare/application/Malaria_Disease/train_cnn.py similarity index 100% rename from examples/healthcare/application/Malaria_Detection/train_cnn.py rename to examples/healthcare/application/Malaria_Disease/train_cnn.py diff --git a/examples/healthcare/application/TED_CT_Detection/README.md b/examples/healthcare/application/Thyroid_Eye_Disease/README.md similarity index 100% rename from examples/healthcare/application/TED_CT_Detection/README.md rename to examples/healthcare/application/Thyroid_Eye_Disease/README.md diff --git a/examples/healthcare/application/TED_CT_Detection/run.sh b/examples/healthcare/application/Thyroid_Eye_Disease/run.sh similarity index 100% rename from examples/healthcare/application/TED_CT_Detection/run.sh rename to examples/healthcare/application/Thyroid_Eye_Disease/run.sh diff --git a/examples/healthcare/application/TED_CT_Detection/train.py b/examples/healthcare/application/Thyroid_Eye_Disease/train.py similarity index 100% rename from examples/healthcare/application/TED_CT_Detection/train.py rename to examples/healthcare/application/Thyroid_Eye_Disease/train.py From 8367d22d20a2ea33f6d93370d55f8c38fecff2f1 Mon Sep 17 00:00:00 2001 From: xuewanqi <36396136+xuewanqi@users.noreply.github.com> Date: Wed, 5 Mar 2025 17:15:49 +0800 Subject: [PATCH 18/53] Add the dataset for the diabetic readmission --- examples/healthcare/data/diabetic.py | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 examples/healthcare/data/diabetic.py diff --git a/examples/healthcare/data/diabetic.py b/examples/healthcare/data/diabetic.py new file mode 100644 index 000000000..6a48b4bd6 --- /dev/null +++ b/examples/healthcare/data/diabetic.py @@ -0,0 +1,78 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from ucimlrepo import fetch_ucirepo +from sklearn.model_selection import train_test_split +import pandas as pd +import numpy as np + + +def load_dataset(columns_to_encode=None, flag=True): + """ + Load the dataset and apply one-hot encoding to features (all columns or specific columns). + Targets will first be one-hot encoded and then converted to categorical integer labels. + + Parameters: + columns_to_encode (list or None): List of column names to be one-hot encoded. + If None and `flag=True`, all columns are encoded. + flag (bool): Whether to apply one-hot encoding to all columns. + If True, `columns_to_encode` will be ignored, and all columns will be processed. + + Returns: + train_x, train_y, test_x, test_y (numpy.ndarray): + Train features, train labels, test features, and test labels in NumPy array format. + """ + # Load the dataset + diabetes_data = fetch_ucirepo(id=296) + + # Extract features and targets + features = diabetes_data.data.features + targets = diabetes_data.data.targets + + # Apply one-hot encoding to features + if flag or columns_to_encode is None: + features_encoded = pd.get_dummies(features, drop_first=True) + else: + features_encoded = pd.get_dummies(features, columns=columns_to_encode, drop_first=True) + + # One-hot encode targets and convert to a single categorical variable + targets_encoded = pd.get_dummies(targets, drop_first=False) + targets_categorical = targets_encoded.idxmax(axis=1) # Get the column name with the max value (One-Hot index) + targets_categorical = targets_categorical.astype('category').cat.codes # Convert to integer codes + + # Convert to NumPy arrays + features_np = features_encoded.to_numpy(dtype=np.float32) + targets_np = targets_categorical.to_numpy(dtype=np.float32) + + # Split the data + train_x, test_x, train_y, test_y = train_test_split( + features_np, targets_np, test_size=0.2, random_state=42 + ) + + return train_x, train_y, test_x, test_y + + + +def load(): + train_x, train_y, val_x, val_y = load_dataset() + train_x = train_x.astype(np.float32) + val_x = val_x.astype(np.float32) + train_y = train_y.astype(np.int32) + val_y = val_y.astype(np.int32) + return train_x, train_y, val_x, val_y From 971461be232d7aa9f493fce414f33e171c18bba7 Mon Sep 17 00:00:00 2001 From: Zrealshadow <704309740@qq.com> Date: Wed, 5 Mar 2025 18:00:33 +0800 Subject: [PATCH 19/53] add the implementation of the model for the kidney disease --- examples/healthcare/models/kidney_net.py | 149 +++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 examples/healthcare/models/kidney_net.py diff --git a/examples/healthcare/models/kidney_net.py b/examples/healthcare/models/kidney_net.py new file mode 100644 index 000000000..c4c49764b --- /dev/null +++ b/examples/healthcare/models/kidney_net.py @@ -0,0 +1,149 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from singa import layer +from singa import model +from singa import tensor +from singa import opt +from singa import device +import argparse +import numpy as np + +np_dtype = {"float16": np.float16, "float32": np.float32} + +singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} + + +class MLP(model.Model): + + def __init__(self, data_size=10, perceptron_size=100, num_classes=10): + super(MLP, self).__init__() + self.num_classes = num_classes + self.dimension = 2 + + self.relu = layer.ReLU() + self.linear1 = layer.Linear(perceptron_size) + self.linear2 = layer.Linear(num_classes) + self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() + + def forward(self, inputs): + y = self.linear1(inputs) + y = self.relu(y) + y = self.linear2(y) + return y + + def train_one_batch(self, x, y, dist_option, spars): + out = self.forward(x) + loss = self.softmax_cross_entropy(out, y) + + if dist_option == 'plain': + self.optimizer(loss) + elif dist_option == 'half': + self.optimizer.backward_and_update_half(loss) + elif dist_option == 'partialUpdate': + self.optimizer.backward_and_partial_update(loss) + elif dist_option == 'sparseTopK': + self.optimizer.backward_and_sparse_update(loss, + topK=True, + spars=spars) + elif dist_option == 'sparseThreshold': + self.optimizer.backward_and_sparse_update(loss, + topK=False, + spars=spars) + return out, loss + + def set_optimizer(self, optimizer): + self.optimizer = optimizer + + +def create_model(pretrained=False, **kwargs): + """Constructs a CNN model. + + Args: + pretrained (bool): If True, returns a pre-trained model. + + Returns: + The created CNN model. + """ + model = MLP(**kwargs) + + return model + + +__all__ = ['MLP', 'create_model'] + +if __name__ == "__main__": + np.random.seed(0) + + parser = argparse.ArgumentParser() + parser.add_argument('-p', + choices=['float32', 'float16'], + default='float32', + dest='precision') + parser.add_argument('-g', + '--disable-graph', + default='True', + action='store_false', + help='disable graph', + dest='graph') + parser.add_argument('-m', + '--max-epoch', + default=1001, + type=int, + help='maximum epochs', + dest='max_epoch') + args = parser.parse_args() + + # generate the boundary + f = lambda x: (5 * x + 1) + bd_x = np.linspace(-1.0, 1, 200) + bd_y = f(bd_x) + + # generate the training data + x = np.random.uniform(-1, 1, 400) + y = f(x) + 2 * np.random.randn(len(x)) + + # choose one precision + precision = singa_dtype[args.precision] + np_precision = np_dtype[args.precision] + + # convert training data to 2d space + label = np.asarray([5 * a + 1 > b for (a, b) in zip(x, y)]).astype(np.int32) + data = np.array([[a, b] for (a, b) in zip(x, y)], dtype=np_precision) + + dev = device.create_cuda_gpu_on(0) + sgd = opt.SGD(0.1, 0.9, 1e-5, dtype=singa_dtype[args.precision]) + tx = tensor.Tensor((400, 2), dev, precision) + ty = tensor.Tensor((400,), dev, tensor.int32) + model = MLP(data_size=2, perceptron_size=3, num_classes=2) + + # attach model to graph + model.set_optimizer(sgd) + model.compile([tx], is_train=True, use_graph=args.graph, sequential=True) + model.train() + + for i in range(args.max_epoch): + tx.copy_from_numpy(data) + ty.copy_from_numpy(label) + out, loss = model(tx, ty, 'fp32', spars=None) + + if i % 100 == 0: + print("training loss = ", tensor.to_numpy(loss)[0]) + + From 6b82190e641c2098116e19223a29e0d3acfecd4b Mon Sep 17 00:00:00 2001 From: Zhu Lei Date: Wed, 5 Mar 2025 20:38:33 +0800 Subject: [PATCH 20/53] Add the implementation of the model for the diabetic readmission --- examples/healthcare/models/diabetic_net.py | 147 +++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 examples/healthcare/models/diabetic_net.py diff --git a/examples/healthcare/models/diabetic_net.py b/examples/healthcare/models/diabetic_net.py new file mode 100644 index 000000000..83874c896 --- /dev/null +++ b/examples/healthcare/models/diabetic_net.py @@ -0,0 +1,147 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from singa import layer +from singa import model +from singa import tensor +from singa import opt +from singa import device +import argparse +import numpy as np + +np_dtype = {"float16": np.float16, "float32": np.float32} + +singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} + + +class MLP(model.Model): + + def __init__(self, data_size=10, perceptron_size=100, num_classes=10): + super(MLP, self).__init__() + self.num_classes = num_classes + self.dimension = 2 + + self.relu = layer.ReLU() + self.linear1 = layer.Linear(perceptron_size) + self.linear2 = layer.Linear(num_classes) + self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() + + def forward(self, inputs): + y = self.linear1(inputs) + y = self.relu(y) + y = self.linear2(y) + return y + + def train_one_batch(self, x, y, dist_option, spars): + out = self.forward(x) + loss = self.softmax_cross_entropy(out, y) + + if dist_option == 'plain': + self.optimizer(loss) + elif dist_option == 'half': + self.optimizer.backward_and_update_half(loss) + elif dist_option == 'partialUpdate': + self.optimizer.backward_and_partial_update(loss) + elif dist_option == 'sparseTopK': + self.optimizer.backward_and_sparse_update(loss, + topK=True, + spars=spars) + elif dist_option == 'sparseThreshold': + self.optimizer.backward_and_sparse_update(loss, + topK=False, + spars=spars) + return out, loss + + def set_optimizer(self, optimizer): + self.optimizer = optimizer + + +def create_model(pretrained=False, **kwargs): + """Constructs a CNN model. + + Args: + pretrained (bool): If True, returns a pre-trained model. + + Returns: + The created CNN model. + """ + model = MLP(**kwargs) + + return model + + +__all__ = ['MLP', 'create_model'] + +if __name__ == "__main__": + np.random.seed(0) + + parser = argparse.ArgumentParser() + parser.add_argument('-p', + choices=['float32', 'float16'], + default='float32', + dest='precision') + parser.add_argument('-g', + '--disable-graph', + default='True', + action='store_false', + help='disable graph', + dest='graph') + parser.add_argument('-m', + '--max-epoch', + default=1001, + type=int, + help='maximum epochs', + dest='max_epoch') + args = parser.parse_args() + + # generate the boundary + f = lambda x: (5 * x + 1) + bd_x = np.linspace(-1.0, 1, 200) + bd_y = f(bd_x) + + # generate the training data + x = np.random.uniform(-1, 1, 400) + y = f(x) + 2 * np.random.randn(len(x)) + + # choose one precision + precision = singa_dtype[args.precision] + np_precision = np_dtype[args.precision] + + # convert training data to 2d space + label = np.asarray([5 * a + 1 > b for (a, b) in zip(x, y)]).astype(np.int32) + data = np.array([[a, b] for (a, b) in zip(x, y)], dtype=np_precision) + + dev = device.create_cuda_gpu_on(0) + sgd = opt.SGD(0.1, 0.9, 1e-5, dtype=singa_dtype[args.precision]) + tx = tensor.Tensor((400, 2), dev, precision) + ty = tensor.Tensor((400,), dev, tensor.int32) + model = MLP(data_size=2, perceptron_size=3, num_classes=2) + + # attach model to graph + model.set_optimizer(sgd) + model.compile([tx], is_train=True, use_graph=args.graph, sequential=True) + model.train() + + for i in range(args.max_epoch): + tx.copy_from_numpy(data) + ty.copy_from_numpy(label) + out, loss = model(tx, ty, 'fp32', spars=None) + + if i % 100 == 0: + print("training loss = ", tensor.to_numpy(loss)[0]) \ No newline at end of file From 689e0d129d08eee12bfc21c4e0ce6d238e0a323e Mon Sep 17 00:00:00 2001 From: Zrealshadow <704309740@qq.com> Date: Thu, 6 Mar 2025 14:03:09 +0800 Subject: [PATCH 21/53] Add the running script for the kidney disease prediction --- .../application/Kidney_Disease/run.sh | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/healthcare/application/Kidney_Disease/run.sh diff --git a/examples/healthcare/application/Kidney_Disease/run.sh b/examples/healthcare/application/Kidney_Disease/run.sh new file mode 100644 index 000000000..27de7eae3 --- /dev/null +++ b/examples/healthcare/application/Kidney_Disease/run.sh @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +### kidney disease dataset +python train_kidney_mlp.py mlp kidney-disease -dir pathToDataset \ No newline at end of file From 3509235f73fbd92b314e090ff98fb967190cc4c8 Mon Sep 17 00:00:00 2001 From: joddiy Date: Thu, 6 Mar 2025 23:54:35 +0800 Subject: [PATCH 22/53] Add the implementation of the model for cardiovascular disease --- .../healthcare/models/cardiovascular_net.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 examples/healthcare/models/cardiovascular_net.py diff --git a/examples/healthcare/models/cardiovascular_net.py b/examples/healthcare/models/cardiovascular_net.py new file mode 100644 index 000000000..b8ce49018 --- /dev/null +++ b/examples/healthcare/models/cardiovascular_net.py @@ -0,0 +1,88 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +from singa import layer +from singa import model +from singa import tensor +from singa import opt +from singa import device +import argparse +import numpy as np + +np_dtype = {"float16": np.float16, "float32": np.float32} + +singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} + + +class MLP(model.Model): + + def __init__(self, data_size=10, perceptron_size=20, num_classes=10): + super(MLP, self).__init__() + self.num_classes = num_classes + self.dimension = 2 + + self.relu = layer.ReLU() + self.linear1 = layer.Linear(perceptron_size) + self.linear2 = layer.Linear(num_classes) + self.softmax_cross_entropy = layer.SoftMaxCrossEntropy() + + def forward(self, inputs): + y = self.linear1(inputs) + y = self.relu(y) + y = self.linear2(y) + return y + + def train_one_batch(self, x, y, dist_option, spars): + out = self.forward(x) + loss = self.softmax_cross_entropy(out, y) + + if dist_option == 'plain': + self.optimizer(loss) + elif dist_option == 'half': + self.optimizer.backward_and_update_half(loss) + elif dist_option == 'partialUpdate': + self.optimizer.backward_and_partial_update(loss) + elif dist_option == 'sparseTopK': + self.optimizer.backward_and_sparse_update(loss, + topK=True, + spars=spars) + elif dist_option == 'sparseThreshold': + self.optimizer.backward_and_sparse_update(loss, + topK=False, + spars=spars) + return out, loss + + def set_optimizer(self, optimizer): + self.optimizer = optimizer + + +def create_model(pretrained=False, **kwargs): + """Constructs a CNN model. + + Args: + pretrained (bool): If True, returns a pre-trained model. + + Returns: + The created MLP model. + """ + model = MLP(**kwargs) + + return model + +__all__ = ['MLP', 'create_model'] \ No newline at end of file From 1a73cb73c434e0f875e72af4620dec9ad6e44100 Mon Sep 17 00:00:00 2001 From: prometheus <57171759+NLGithubWP@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:51:49 +0800 Subject: [PATCH 23/53] Add the running script for the cardiovascular disease prediction --- .../application/Cardiovascular_Disease/run.sh | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/healthcare/application/Cardiovascular_Disease/run.sh diff --git a/examples/healthcare/application/Cardiovascular_Disease/run.sh b/examples/healthcare/application/Cardiovascular_Disease/run.sh new file mode 100644 index 000000000..a0e6b9767 --- /dev/null +++ b/examples/healthcare/application/Cardiovascular_Disease/run.sh @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +### cardiovascular dataset +python train_cnn.py mlp cardiovascular From df29b2a4cd135ce126df3eeca167f6aa060e3b21 Mon Sep 17 00:00:00 2001 From: working <57171759+NLGithubWP@users.noreply.github.com> Date: Sun, 9 Mar 2025 13:42:34 +0800 Subject: [PATCH 24/53] Add the implementation of the model for cardiovascular disease detection --- .../Cardiovascular_Disease/train_cnn.py | 316 ++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 examples/healthcare/application/Cardiovascular_Disease/train_cnn.py diff --git a/examples/healthcare/application/Cardiovascular_Disease/train_cnn.py b/examples/healthcare/application/Cardiovascular_Disease/train_cnn.py new file mode 100644 index 000000000..40dd8d016 --- /dev/null +++ b/examples/healthcare/application/Cardiovascular_Disease/train_cnn.py @@ -0,0 +1,316 @@ +from singa import singa_wrap as singa +from singa import device +from singa import tensor +from singa import opt +import numpy as np +import time +import argparse +from PIL import Image +from healthcare.data import cardiovascular +from healthcare.models import cardiovascular_net + +np_dtype = {"float16": np.float16, "float32": np.float32} + +singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} + + +# Data augmentation +def augmentation(x, batch_size): + xpad = np.pad(x, [[0, 0], [0, 0], [4, 4], [4, 4]], 'symmetric') + for data_num in range(0, batch_size): + offset = np.random.randint(8, size=2) + x[data_num, :, :, :] = xpad[data_num, :, + offset[0]:offset[0] + x.shape[2], + offset[1]:offset[1] + x.shape[2]] + if_flip = np.random.randint(2) + if (if_flip): + x[data_num, :, :, :] = x[data_num, :, :, ::-1] + return x + + +# Calculate accuracy +def accuracy(pred, target): + # y is network output to be compared with ground truth (int) + y = np.argmax(pred, axis=1) + a = y == target + correct = np.array(a, "int").sum() + return correct + + +# Data partition according to the rank +def partition(global_rank, world_size, train_x, train_y, val_x, val_y): + # Partition training data + data_per_rank = train_x.shape[0] // world_size + idx_start = global_rank * data_per_rank + idx_end = (global_rank + 1) * data_per_rank + train_x = train_x[idx_start:idx_end] + train_y = train_y[idx_start:idx_end] + + # Partition evaluation data + data_per_rank = val_x.shape[0] // world_size + idx_start = global_rank * data_per_rank + idx_end = (global_rank + 1) * data_per_rank + val_x = val_x[idx_start:idx_end] + val_y = val_y[idx_start:idx_end] + return train_x, train_y, val_x, val_y + + +# Function to all reduce NUMPY accuracy and loss from multiple devices +def reduce_variable(variable, dist_opt, reducer): + reducer.copy_from_numpy(variable) + dist_opt.all_reduce(reducer.data) + dist_opt.wait() + output = tensor.to_numpy(reducer) + return output + + +def resize_dataset(x, image_size): + num_data = x.shape[0] + dim = x.shape[1] + X = np.zeros(shape=(num_data, dim, image_size, image_size), + dtype=np.float32) + for n in range(0, num_data): + for d in range(0, dim): + X[n, d, :, :] = np.array(Image.fromarray(x[n, d, :, :]).resize( + (image_size, image_size), Image.BILINEAR), + dtype=np.float32) + return X + + +def run(global_rank, + world_size, + local_rank, + max_epoch, + batch_size, + model, + data, + sgd, + graph, + verbosity, + dist_option='plain', + spars=None, + precision='float32'): + #dev = device.get_default_device() + dev = device.create_cuda_gpu_on(local_rank) + # need to change to CPU device for CPU-only machines + dev.SetRandSeed(0) + np.random.seed(0) + + if data == 'cifar10': + from data import cifar10 + train_x, train_y, val_x, val_y = cifar10.load() + elif data == 'cifar100': + from data import cifar100 + train_x, train_y, val_x, val_y = cifar100.load() + elif data == 'mnist': + from data import mnist + train_x, train_y, val_x, val_y = mnist.load() + elif data == 'cardiovascular': + train_x, train_y, val_x, val_y = cardiovascular.load() + + + num_channels = 1 + image_size = 1 + data_size = train_x.shape[1] + + num_classes = 2 + + if model == 'resnet': + from model import resnet + model = resnet.resnet50(num_channels=num_channels, + num_classes=num_classes) + elif model == 'xceptionnet': + from model import xceptionnet + model = xceptionnet.create_model(num_channels=num_channels, + num_classes=num_classes) + elif model == 'cnn': + from model import cnn + model = cnn.create_model(num_channels=num_channels, + num_classes=num_classes) + elif model == 'alexnet': + from model import alexnet + model = alexnet.create_model(num_channels=num_channels, + num_classes=num_classes) + elif model == 'mlp': + import os, sys, inspect + current = os.path.dirname( + os.path.abspath(inspect.getfile(inspect.currentframe()))) + parent = os.path.dirname(current) + sys.path.insert(0, parent) + model = cardiovascular_net.create_model(data_size=data_size, perceptron_size=1000, num_classes=num_classes) + + # For distributed training, sequential has better performance + if hasattr(sgd, "communicator"): + DIST = True + sequential = True + else: + DIST = False + sequential = False + + if DIST: + train_x, train_y, val_x, val_y = partition(global_rank, world_size, + train_x, train_y, val_x, + val_y) + + if model.dimension == 4: + tx = tensor.Tensor( + (batch_size, num_channels, model.input_size, model.input_size), dev, + singa_dtype[precision]) + elif model.dimension == 2: + tx = tensor.Tensor((batch_size, data_size), dev, singa_dtype[precision]) + # np.reshape(train_x, (train_x.shape[0], -1)) + # np.reshape(val_x, (val_x.shape[0], -1)) + + ty = tensor.Tensor((batch_size,), dev, tensor.int32) + num_train_batch = train_x.shape[0] // batch_size + num_val_batch = val_x.shape[0] // batch_size + idx = np.arange(train_x.shape[0], dtype=np.int32) + + # Attach model to graph + model.set_optimizer(sgd) + model.compile([tx], is_train=True, use_graph=graph, sequential=sequential) + dev.SetVerbosity(verbosity) + + # Training and evaluation loop + for epoch in range(max_epoch): + start_time = time.time() + np.random.shuffle(idx) + + if global_rank == 0: + print('Starting Epoch %d:' % (epoch)) + + + # Training phase + train_correct = np.zeros(shape=[1], dtype=np.float32) + test_correct = np.zeros(shape=[1], dtype=np.float32) + train_loss = np.zeros(shape=[1], dtype=np.float32) + + model.train() + for b in range(num_train_batch): + # if b % 100 == 0: + # print ("b: \n", b) + # Generate the patch data in this iteration + x = train_x[idx[b * batch_size:(b + 1) * batch_size]] + if model.dimension == 4: + x = augmentation(x, batch_size) + if (image_size != model.input_size): + x = resize_dataset(x, model.input_size) + x = x.astype(np_dtype[precision]) + y = train_y[idx[b * batch_size:(b + 1) * batch_size]] + + # Copy the patch data into input tensors + tx.copy_from_numpy(x) + ty.copy_from_numpy(y) + + # Train the model + out, loss = model(tx, ty, dist_option, spars) + train_correct += accuracy(tensor.to_numpy(out), y) + train_loss += tensor.to_numpy(loss)[0] + + if DIST: + # Reduce the evaluation accuracy and loss from multiple devices + reducer = tensor.Tensor((1,), dev, tensor.float32) + train_correct = reduce_variable(train_correct, sgd, reducer) + train_loss = reduce_variable(train_loss, sgd, reducer) + + if global_rank == 0: + print('Training loss = %f, training accuracy = %f' % + (train_loss, train_correct / + (num_train_batch * batch_size * world_size)), + flush=True) + + # Evaluation phase + model.eval() + for b in range(num_val_batch): + x = val_x[b * batch_size:(b + 1) * batch_size] + if model.dimension == 4: + if (image_size != model.input_size): + x = resize_dataset(x, model.input_size) + x = x.astype(np_dtype[precision]) + y = val_y[b * batch_size:(b + 1) * batch_size] + tx.copy_from_numpy(x) + ty.copy_from_numpy(y) + out_test = model(tx) + test_correct += accuracy(tensor.to_numpy(out_test), y) + + if DIST: + # Reduce the evaulation accuracy from multiple devices + test_correct = reduce_variable(test_correct, sgd, reducer) + + # Output the evaluation accuracy + if global_rank == 0: + print('Evaluation accuracy = %f, Elapsed Time = %fs' % + (test_correct / (num_val_batch * batch_size * world_size), + time.time() - start_time), + flush=True) + + dev.PrintTimeProfiling() + + +if __name__ == '__main__': + # Use argparse to get command config: max_epoch, model, data, etc., for single gpu training + parser = argparse.ArgumentParser( + description='Training using the autograd and graph.') + parser.add_argument( + 'model', + choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet'], + default='cnn') + parser.add_argument('data', + choices=['mnist', 'cifar10', 'cifar100', 'cardiovascular'], + default='cardiovascular') + parser.add_argument('-p', + choices=['float32', 'float16'], + default='float32', + dest='precision') + parser.add_argument('-m', + '--max-epoch', + default=100, + type=int, + help='maximum epochs', + dest='max_epoch') + parser.add_argument('-b', + '--batch-size', + default=64, + type=int, + help='batch size', + dest='batch_size') + parser.add_argument('-l', + '--learning-rate', + default=0.001, + type=float, + help='initial learning rate', + dest='lr') + # Determine which gpu to use + parser.add_argument('-i', + '--device-id', + default=0, + type=int, + help='which GPU to use', + dest='device_id') + parser.add_argument('-g', + '--disable-graph', + default='True', + action='store_false', + help='disable graph', + dest='graph') + parser.add_argument('-v', + '--log-verbosity', + default=0, + type=int, + help='logging verbosity', + dest='verbosity') + + args = parser.parse_args() + + sgd = opt.SGD(lr=args.lr, momentum=0.9, weight_decay=1e-5, dtype=singa_dtype[args.precision]) + run(0, + 1, + args.device_id, + args.max_epoch, + args.batch_size, + args.model, + args.data, + sgd, + args.graph, + args.verbosity, + precision=args.precision) From b411fb78b57905ac08df5f5906a2822f1cf81ec2 Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Sun, 9 Mar 2025 14:51:32 +0800 Subject: [PATCH 25/53] Add the dataset for the cardiovascular disease detection --- examples/healthcare/data/cardiovascular.py | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 examples/healthcare/data/cardiovascular.py diff --git a/examples/healthcare/data/cardiovascular.py b/examples/healthcare/data/cardiovascular.py new file mode 100644 index 000000000..d4fd6c715 --- /dev/null +++ b/examples/healthcare/data/cardiovascular.py @@ -0,0 +1,63 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +import numpy as np +import os +import sys + +def load_cardiovascular_data(file_path): + data = np.loadtxt(file_path, delimiter=',') + + + X = data[:, :-1] + y = data[:, -1] + + + # Split the data into training and validation sets + train_size = int(0.8 * data.shape[0]) + train_x, val_x = X[:train_size], X[train_size:] + train_y, val_y = y[:train_size], y[train_size:] + + # Normalize the data + mean = np.mean(train_x, axis=0) + std = np.std(train_x, axis=0) + train_x = (train_x - mean) / std + val_x = (val_x - mean) / std + + return train_x, train_y, val_x, val_y + +def load(): + file_path = 'cardio_train.csv' #need to change + + train_x, train_y, val_x, val_y = load_cardiovascular_data(file_path) + + train_x = np.array(train_x, dtype=np.float32) + val_x = np.array(val_x, dtype=np.float32) + train_y = np.array(train_y, dtype=np.int32) + val_y = np.array(val_y, dtype=np.int32) + + return train_x, train_y, val_x, val_y + +if __name__ == '__main__': + train_x, train_y, val_x, val_y = load() + print("Training data shape:", train_x.shape) + print("Training labels shape:", train_y.shape) + print("Validation data shape:", val_x.shape) + print("Validation labels shape:", val_y.shape) + From 0de64e81ac4b6ae4ce2369492eb19fb18eab51a8 Mon Sep 17 00:00:00 2001 From: zhangruipeng Date: Thu, 13 Mar 2025 15:48:47 +0800 Subject: [PATCH 26/53] Update the readme files for healthcare applications --- .../Diabetic_Readmission_Prediction/README.md | 2 +- .../application/Kidney_Disease/README.md | 10 ++++----- .../application/Thyroid_Eye_Disease/README.md | 21 ++++++++++++++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md index ba2038f4b..98f07ec3c 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md @@ -17,7 +17,7 @@ under the License. --> -# Singa for Diabetic Readmission Prediction task +# Singa for Diabetic Readmission Prediction Task ## Diabetic Readmission diff --git a/examples/healthcare/application/Kidney_Disease/README.md b/examples/healthcare/application/Kidney_Disease/README.md index 5ec9a667e..05a97bc32 100644 --- a/examples/healthcare/application/Kidney_Disease/README.md +++ b/examples/healthcare/application/Kidney_Disease/README.md @@ -17,13 +17,13 @@ under the License. --> -# Singa for Kidney disease Prediction +# Singa for Kidney Disease Prediction -## Kidney disease Prediction Task +## Kidney Disease Prediction Task -Kidney disease prediction is an important tool that uses data science and machine learning techniques to predict the likelihood of a patient suffering from Kidney disease. The core goal of this technology is to judge whether a patient suffers from kidney disease by analyzing multiple data such as a patient’s medical history, physiological indicators, diagnostic information, treatment options, and socioeconomic factors, so as to take appropriate interventions in advance to provide treatment. +Kidney disease prediction is an important tool that uses data science and machine learning techniques to predict the likelihood of a patient suffering from Kidney disease. The goal is to judge whether a patient suffers from kidney disease by analyzing multiple data such as a patient’s medical history, physiological indicators, diagnostic information, treatment options, and socioeconomic factors, so as to take appropriate interventions in advance to provide treatment. -The dataset used in this task is MIMIC-III after preprocessed. The features are data containing 6 visit windows, with 2549 frequent diagnoses, procedures and drugs for each window. Each item in features are data for one patient, and these features are encoded by one-hot code. The labels are corresponding flags to mark whether the patient suffered from kidney disease, where the label equals "1" if the patient had kidn disease, the label equals "0" if not. +The dataset used in this task is MIMIC-III. The features are data containing 6 visit windows, with 2549 frequent diagnoses, procedures and drugs for each window. These features are encoded by one-hot. The labels are corresponding flags to mark whether the patient suffered from kidney disease, where the label equals "1" if the patient had kidney disease, and the label equals "0" if not. ## Structure @@ -35,7 +35,7 @@ The dataset used in this task is MIMIC-III after preprocessed. The features are of each model. * `train_kidney_mlp.py` is the training script, which controls the training flow by - doing BackPropagation and SGD update. + doing BackPropagation and the SGD update. ## Command ```bash diff --git a/examples/healthcare/application/Thyroid_Eye_Disease/README.md b/examples/healthcare/application/Thyroid_Eye_Disease/README.md index f23e2404a..2a20f75c9 100644 --- a/examples/healthcare/application/Thyroid_Eye_Disease/README.md +++ b/examples/healthcare/application/Thyroid_Eye_Disease/README.md @@ -1,6 +1,25 @@ + + # Convolutional Prototype Learning -We have successfully applied the idea of prototype loss in various medical image classification task to improve performance, for example detection thyroid eye disease from CT images. Here we provide the implementation of the convolution prototype model in Singa. Due to data privacy, we are not able to release the CT image dataset used. The training scripts `./train.py` demonstrate how to apply this model on cifar-10 dataset. +We have successfully applied the idea of prototype loss in various medical image classification task to improve performance, for example, detecting thyroid eye disease from CT images. Here we provide the implementation of the convolution prototype model in Singa. Due to data privacy, we are not able to release the CT image dataset used. The training script `./train.py` demonstrates how to apply this model on the cifar-10 dataset. ## run From b45d258fbffad0571e86ae8ed792ad398ffe615e Mon Sep 17 00:00:00 2001 From: Wu Junran Date: Sat, 15 Mar 2025 20:26:05 +0800 Subject: [PATCH 27/53] Revise the model file for diabetic retinopthy classification --- .../healthcare/models/diabetic_retinopthy_net.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/healthcare/models/diabetic_retinopthy_net.py b/examples/healthcare/models/diabetic_retinopthy_net.py index 856adb7e7..e922733a9 100644 --- a/examples/healthcare/models/diabetic_retinopthy_net.py +++ b/examples/healthcare/models/diabetic_retinopthy_net.py @@ -20,10 +20,10 @@ from singa import model -class CNN(model.Model): +class DRNet(model.Model): def __init__(self, num_classes=10, num_channels=1): - super(CNN, self).__init__() + super(DRNet, self).__init__() self.num_classes = num_classes self.input_size = 128 self.dimension = 4 @@ -78,17 +78,17 @@ def set_optimizer(self, optimizer): def create_model(**kwargs): - """Constructs a CNN model. + """Constructs a DRNet model. Args: pretrained (bool): If True, returns a pre-trained model. Returns: - The created CNN model. + The created DRNet model. """ - model = CNN(**kwargs) + model = DRNet(**kwargs) return model -__all__ = ['CNN', 'create_model'] +__all__ = ['DRNet', 'create_model'] From 4fc892a4bc0fbb54bf4c9f75c9f524e6e3f68340 Mon Sep 17 00:00:00 2001 From: Wu Junran Date: Sat, 15 Mar 2025 20:29:30 +0800 Subject: [PATCH 28/53] Revise the training file for diabetic retinopthy classification --- .../Diabetic_Retinopathy_Classification/train.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py index 9f17cb495..793bfbc66 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py @@ -111,7 +111,7 @@ def run(global_rank, data_size = np.prod(train_x.shape[1:train_x.ndim]).item() num_classes = (np.max(train_y) + 1).item() - if model == 'cnn': + if model == 'DRNet': model = diabetic_retinopthy_net.create_model(num_channels=num_channels, num_classes=num_classes) else: @@ -234,8 +234,8 @@ def run(global_rank, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['cnn'], - default='cnn') + choices=['DRNet'], + default='DRNet') parser.add_argument('data', choices=['diaret'], default='diaret') From 680b9c8e68da76864bd84f14311450274b7dbe25 Mon Sep 17 00:00:00 2001 From: Wu Junran Date: Sat, 15 Mar 2025 20:33:02 +0800 Subject: [PATCH 29/53] Revise the training script for diabetic retinopthy classification --- .../Diabetic_Retinopathy_Classification/run.sh | 2 +- .../Diabetic_Retinopathy_Classification/train.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/run.sh b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/run.sh index afadcb138..1773b067f 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/run.sh +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/run.sh @@ -17,4 +17,4 @@ # ### diabetic retinopathy dataset -python train_cnn.py cnn diaret -dir pathToDataset +python train.py drnet -dir pathToDataset diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py index 793bfbc66..bab78c23f 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py @@ -111,7 +111,7 @@ def run(global_rank, data_size = np.prod(train_x.shape[1:train_x.ndim]).item() num_classes = (np.max(train_y) + 1).item() - if model == 'DRNet': + if model == 'drnet': model = diabetic_retinopthy_net.create_model(num_channels=num_channels, num_classes=num_classes) else: @@ -234,8 +234,8 @@ def run(global_rank, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['DRNet'], - default='DRNet') + choices=['drnet'], + default='drnet') parser.add_argument('data', choices=['diaret'], default='diaret') From 1d9af97336b47c4696b18a74d172c77e5fda1bf2 Mon Sep 17 00:00:00 2001 From: Wu Junran Date: Sat, 15 Mar 2025 20:33:25 +0800 Subject: [PATCH 30/53] Revise README for diabetic retinopthy classification --- .../Diabetic_Retinopathy_Classification/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/README.md index dfa88fd50..ef0194bcb 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/README.md +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/README.md @@ -42,10 +42,10 @@ To mitigate the problem, we use Singa to implement a machine learning model to h a subclass of `Module` to wrap the neural network operations of each model. -* `train_cnn.py` is the training script, which controls the training flow by +* `train.py` is the training script, which controls the training flow by doing BackPropagation and SGD update. ## Command ```bash -python train_cnn.py cnn diaret -dir pathToDataset +python train.py drnet -dir pathToDataset ``` From 6935d292b5dd5f39a922e5a3efb85db533373dc4 Mon Sep 17 00:00:00 2001 From: zhangruipeng Date: Sat, 15 Mar 2025 21:57:05 +0800 Subject: [PATCH 31/53] Update hematologic disease application --- .../application/Hematologic_Disease/readme.md | 16 ++++++--- .../application/Hematologic_Disease/run.sh | 2 +- .../{train_cnn.py => train.py} | 36 +++++++------------ examples/healthcare/data/bloodmnist.py | 2 +- examples/healthcare/models/hematologic_net.py | 15 ++++---- 5 files changed, 31 insertions(+), 40 deletions(-) rename examples/healthcare/application/Hematologic_Disease/{train_cnn.py => train.py} (87%) diff --git a/examples/healthcare/application/Hematologic_Disease/readme.md b/examples/healthcare/application/Hematologic_Disease/readme.md index d0f4902b9..26564c0d2 100644 --- a/examples/healthcare/application/Hematologic_Disease/readme.md +++ b/examples/healthcare/application/Hematologic_Disease/readme.md @@ -16,10 +16,11 @@ specific language governing permissions and limitations under the License. --> -# CNN demo model on BloodMnist dataset +# Train a hematologic net model on BloodMnist dataset + +This example is to train a hematologic net model over the BloodMnist dataset. ## About dataset -Download address: https://drive.google.com/drive/folders/1Ze9qri1UtAsIRoI0SJ4YRpdt5kUUMBEn?usp=sharing The BloodMNIST , as a sub set of [MedMNIST](https://medmnist.com/), is based on a dataset of individual normal cells, captured from individuals without infection, hematologic or oncologic disease and free of any pharmacologic treatment at the moment of blood collection. It contains a total of 17,092 images and is organized into 8 classes. @@ -27,7 +28,7 @@ it is split with a ratio of 7:1:2 into training, validation and test set. The source images with resolution 3×360×363 pixels are center-cropped into 3×200×200, and then resized into 3×28×28. 8 classes of the dataset: -```python +``` "0": "basophil", "1": "eosinophil", "2": "erythroblast", @@ -38,7 +39,12 @@ The source images with resolution 3×360×363 pixels are center-cropped into 3× "7": "platelet" ``` -## Command +## Running instructions + +1. Download the pre-processed [BloodMnist dataset](https://github.com/lzjpaul/singa-healthcare/blob/main/data/bloodmnist/bloodmnist.tar.gz) to a folder(pathToDataset), which contains a few training samples and test samples. For the complete BloodMnist dataset, please download it via this [link](https://github.com/gzrp/bloodmnist/blob/master/bloodmnist.zip). + +2. Start the training + ```bash -python train_cnn.py cnn bloodmnist -dir pathToDataset +python train.py hematologicnet -dir pathToDataset ``` diff --git a/examples/healthcare/application/Hematologic_Disease/run.sh b/examples/healthcare/application/Hematologic_Disease/run.sh index c4a321ede..9b8d777ec 100644 --- a/examples/healthcare/application/Hematologic_Disease/run.sh +++ b/examples/healthcare/application/Hematologic_Disease/run.sh @@ -17,4 +17,4 @@ # ### bloodminist dataset -python train_cnn.py cnn bloodminist -dir pathToDataset +python train.py hematologicnet -dir pathToDataset diff --git a/examples/healthcare/application/Hematologic_Disease/train_cnn.py b/examples/healthcare/application/Hematologic_Disease/train.py similarity index 87% rename from examples/healthcare/application/Hematologic_Disease/train_cnn.py rename to examples/healthcare/application/Hematologic_Disease/train.py index 0f267cd5a..a0523f0e8 100644 --- a/examples/healthcare/application/Hematologic_Disease/train_cnn.py +++ b/examples/healthcare/application/Hematologic_Disease/train.py @@ -23,7 +23,6 @@ from singa import tensor from singa import opt import numpy as np -from tqdm import tqdm import argparse import sys sys.path.append("../../..") @@ -55,7 +54,6 @@ def run(dir_path, max_epoch, batch_size, model, - data, lr, graph, verbosity, @@ -66,15 +64,10 @@ def run(dir_path, dev = device.create_cpu_device() dev.SetRandSeed(0) np.random.seed(0) - if data == 'bloodmnist': - train_dataset, val_dataset, num_class = bloodmnist.load(dir_path=dir_path) - else: - print( - 'Wrong dataset!' - ) - sys.exit(0) - if model == 'cnn': + train_dataset, val_dataset, num_class = bloodmnist.load(dir_path=dir_path) + + if model == 'hematologicnet': model = hematologic_net.create_model(num_classes=num_class) else: print( @@ -112,7 +105,7 @@ def run(dir_path, # Training part model.train() - for b in tqdm(range(num_train_batch)): + for b in range(num_train_batch): # Extract batch from image list x, y = train_dataset.batchgenerator(idx[b * batch_size:(b + 1) * batch_size], batch_size=batch_size, data_size=(3, model.input_size, model.input_size)) @@ -124,13 +117,13 @@ def run(dir_path, out, loss = model(tx, ty, dist_option, spars) train_correct += accuracy(tensor.to_numpy(out), y) train_loss += tensor.to_numpy(loss)[0] - print('Training loss = %f, training accuracy = %f' % - (train_loss, train_correct / + print('Training loss = %f, training accuracy = %.2f %%' % + (train_loss, 100.0 * train_correct / (num_train_batch * batch_size))) # Validation part model.eval() - for b in tqdm(range(num_val_batch)): + for b in range(num_val_batch): x, y = train_dataset.batchgenerator(idx[b * batch_size:(b + 1) * batch_size], batch_size=batch_size, data_size=(3, model.input_size, model.input_size)) x = x.astype(np_dtype[precision]) @@ -141,8 +134,8 @@ def run(dir_path, out = model(tx) test_correct += accuracy(tensor.to_numpy(out), y) - print('Evaluation accuracy = %f, Elapsed Time = %fs' % - (test_correct / (num_val_batch * batch_size), + print('Evaluation accuracy = %.2f%%, Elapsed Time = %fs' % + (100.0*test_correct / (num_val_batch * batch_size), time.time() - start_time)) @@ -152,18 +145,14 @@ def run(dir_path, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['cnn'], - default='cnn') - parser.add_argument('data', - choices=['bloodmnist'], - default='bloodmnist') + choices=['hematologicnet'], + default='hematologicnet') parser.add_argument('-p', choices=['float32', 'float16'], default='float32', dest='precision') parser.add_argument('-dir', '--dir-path', - default="/tmp/bloodmnist", type=str, help='the directory to store the bloodmnist dataset', dest='dir_path') @@ -175,7 +164,7 @@ def run(dir_path, dest='max_epoch') parser.add_argument('-b', '--batch-size', - default=256, + default=8, type=int, help='batch size', dest='batch_size') @@ -204,7 +193,6 @@ def run(dir_path, args.max_epoch, args.batch_size, args.model, - args.data, args.lr, args.graph, args.verbosity, diff --git a/examples/healthcare/data/bloodmnist.py b/examples/healthcare/data/bloodmnist.py index 1fe3e5cc3..4042a0c7d 100644 --- a/examples/healthcare/data/bloodmnist.py +++ b/examples/healthcare/data/bloodmnist.py @@ -221,7 +221,7 @@ def batchgenerator(self, indexes, batch_size, data_size): return batch_x, batch_y -def load(dir_path="tmp/bloodmnist"): +def load(dir_path): # Dataset loading train_path = os.path.join(dir_path, "train") val_path = os.path.join(dir_path, "val") diff --git a/examples/healthcare/models/hematologic_net.py b/examples/healthcare/models/hematologic_net.py index fadd050e9..d295b0cfe 100644 --- a/examples/healthcare/models/hematologic_net.py +++ b/examples/healthcare/models/hematologic_net.py @@ -30,9 +30,9 @@ singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} -class CNNModel(model.Model): +class HematologicNet(model.Model): def __init__(self, num_classes): - super(CNNModel, self).__init__() + super(HematologicNet, self).__init__() self.input_size = 28 self.dimension = 4 self.num_classes = num_classes @@ -108,17 +108,14 @@ def train_one_batch(self, x, y, dist_option, spars): def create_model(**kwargs): - """Constructs a CNN model. - - Args: - pretrained (bool): If True, returns a pre-trained model. + """Constructs a HematologicNet model. Returns: - The created CNN model. + The created HematologicNet model. """ - model = CNNModel(**kwargs) + model = HematologicNet(**kwargs) return model -__all__ = ['CNNModel', 'create_model'] \ No newline at end of file +__all__ = ['HematologicNet', 'create_model'] \ No newline at end of file From d91a44ee5fae330e0b63914653bfab1fb6364d9b Mon Sep 17 00:00:00 2001 From: zhangruipeng Date: Sat, 15 Mar 2025 23:51:01 +0800 Subject: [PATCH 32/53] Update thyroid eye disease application --- .../application/Thyroid_Eye_Disease/README.md | 10 ++++--- .../application/Thyroid_Eye_Disease/run.sh | 2 +- .../application/Thyroid_Eye_Disease/train.py | 30 +++++++++++++------ examples/healthcare/data/cifar10.py | 4 +-- examples/healthcare/models/tedct_net.py | 18 +++++------ 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/examples/healthcare/application/Thyroid_Eye_Disease/README.md b/examples/healthcare/application/Thyroid_Eye_Disease/README.md index 2a20f75c9..755ef96d1 100644 --- a/examples/healthcare/application/Thyroid_Eye_Disease/README.md +++ b/examples/healthcare/application/Thyroid_Eye_Disease/README.md @@ -22,12 +22,14 @@ We have successfully applied the idea of prototype loss in various medical image classification task to improve performance, for example, detecting thyroid eye disease from CT images. Here we provide the implementation of the convolution prototype model in Singa. Due to data privacy, we are not able to release the CT image dataset used. The training script `./train.py` demonstrates how to apply this model on the cifar-10 dataset. -## run +## Running instructions + +1. Download the [CIFAR-10 python version](https://www.cs.toronto.edu/~kriz/cifar.html) to a folder(pathToDataset). + +2. Start the training -1. Download `healthcare` directory then change to the `healthcare/application/TED_CT_Detection` directory. -2. Command. ```bash -python train.py -dir pathToDataset +python train.py tedctnet -dir pathToDataset ``` ## reference diff --git a/examples/healthcare/application/Thyroid_Eye_Disease/run.sh b/examples/healthcare/application/Thyroid_Eye_Disease/run.sh index 9182810c8..eef7bd480 100644 --- a/examples/healthcare/application/Thyroid_Eye_Disease/run.sh +++ b/examples/healthcare/application/Thyroid_Eye_Disease/run.sh @@ -17,4 +17,4 @@ # ### command -python train.py -dir pathToDataset +python train.py tedctnet -dir pathToDataset diff --git a/examples/healthcare/application/Thyroid_Eye_Disease/train.py b/examples/healthcare/application/Thyroid_Eye_Disease/train.py index 2b045fd93..270740091 100644 --- a/examples/healthcare/application/Thyroid_Eye_Disease/train.py +++ b/examples/healthcare/application/Thyroid_Eye_Disease/train.py @@ -60,6 +60,7 @@ def run( dir_path, max_epoch, batch_size, + model, sgd, graph, verbosity, @@ -77,8 +78,14 @@ def run( data_size = np.prod(train_x.shape[1 : train_x.ndim]).item() num_classes = (np.max(train_y) + 1).item() - backbone = tedct_net.create_cnn_model(num_channels=num_channels, num_classes=num_classes) - model = tedct_net.create_model(backbone, prototype_count=10, lamb=0.5, temp=10) + if model == 'tedctnet': + backbone = tedct_net.create_backbone(num_channels=num_channels, num_classes=num_classes) + model = tedct_net.create_model(backbone, prototype_count=10, lamb=0.5, temp=10) + else: + print( + 'Wrong model!' + ) + sys.exit(0) if backbone.dimension == 4: tx = tensor.Tensor( @@ -105,7 +112,6 @@ def run( np.random.shuffle(idx) train_correct = np.zeros(shape=[1], dtype=np.float32) - test_correct = np.zeros(shape=[1], dtype=np.float32) train_loss = np.zeros(shape=[1], dtype=np.float32) model.train() @@ -119,11 +125,12 @@ def run( train_correct += accuracy(tensor.to_numpy(out), y) train_loss += tensor.to_numpy(loss)[0] print( - "Training loss = %f, training accuracy = %f" - % (train_loss, train_correct / (num_train_batch * batch_size)), + "Training loss = %f, training accuracy = %.2f %%" + % (train_loss, 100.0 * train_correct / (num_train_batch * batch_size)), flush=True, ) + test_correct = np.zeros(shape=[1], dtype=np.float32) model.eval() for b in range(num_val_batch): x = val_x[b * batch_size : (b + 1) * batch_size] @@ -132,22 +139,26 @@ def run( tx.copy_from_numpy(x) ty.copy_from_numpy(y) - out_test = model(tx, ty, dist_option="fp32", spars=None) + out_test = model(tx) test_correct += accuracy(tensor.to_numpy(out_test), y) - + print('Evaluation accuracy = %.2f %%' % + (100.0 * test_correct / (num_val_batch * batch_size))) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Train a CPL model") + parser.add_argument( + 'model', + choices=['tedctnet'], + default='tedctnet') parser.add_argument('-dir', '--dir-path', - default="/tmp/cifar-10-batches-py", type=str, help='the directory to store the dataset', dest='dir_path') parser.add_argument( "-m", "--max-epoch", - default=20, + default=10, type=int, help="maximum epochs", dest="max_epoch", @@ -196,6 +207,7 @@ def run( args.dir_path, args.max_epoch, args.batch_size, + args.model, sgd, args.graph, args.verbosity diff --git a/examples/healthcare/data/cifar10.py b/examples/healthcare/data/cifar10.py index 8e6c3f9ac..1ee6a0bb3 100644 --- a/examples/healthcare/data/cifar10.py +++ b/examples/healthcare/data/cifar10.py @@ -40,7 +40,7 @@ def load_dataset(filepath): return image, label -def load_train_data(dir_path='/tmp/cifar-10-batches-py', num_batches=5): # need to save to specific local directories +def load_train_data(dir_path, num_batches=5): # need to save to specific local directories labels = [] batchsize = 10000 images = np.empty((num_batches * batchsize, 3, 32, 32), dtype=np.uint8) @@ -54,7 +54,7 @@ def load_train_data(dir_path='/tmp/cifar-10-batches-py', num_batches=5): # need return images, labels -def load_test_data(dir_path='/tmp/cifar-10-batches-py'): # need to save to specific local directories +def load_test_data(dir_path): # need to save to specific local directories images, labels = load_dataset(check_dataset_exist(dir_path + "/test_batch")) return np.array(images, dtype=np.float32), np.array(labels, dtype=np.int32) diff --git a/examples/healthcare/models/tedct_net.py b/examples/healthcare/models/tedct_net.py index 2758487bd..40662e869 100644 --- a/examples/healthcare/models/tedct_net.py +++ b/examples/healthcare/models/tedct_net.py @@ -73,7 +73,7 @@ def set_params(self, parameters): self.prototype.copy_from(parameters[self.prototype.name]) -class CPL(model.Model): +class TEDctNet(model.Model): def __init__( self, @@ -84,7 +84,7 @@ def __init__( label=None, prototype_weight=None, ): - super(CPL, self).__init__() + super(TEDctNet, self).__init__() # config self.lamb = lamb self.prototype_weight = prototype_weight @@ -111,10 +111,10 @@ def set_optimizer(self, optimizer): self.optimizer = optimizer -class CNN(model.Model): +class Backbone(model.Model): def __init__(self, num_classes=10, num_channels=1): - super(CNN, self).__init__() + super(Backbone, self).__init__() self.num_classes = num_classes self.input_size = 28 self.dimension = 4 @@ -162,7 +162,7 @@ def train_one_batch(self, x, y, dist_option, spars): def set_optimizer(self, optimizer): self.optimizer = optimizer -def create_cnn_model(pretrained=False, **kwargs): +def create_backbone(pretrained=False, **kwargs): """Constructs a CNN model. Args: @@ -171,13 +171,13 @@ def create_cnn_model(pretrained=False, **kwargs): Returns: The created CNN model. """ - model = CNN(**kwargs) + model = Backbone(**kwargs) return model -def create_model(backbone, prototype_count=2, lamb=0.5, temp=10.0): - model = CPL(backbone, prototype_count=prototype_count, lamb=lamb, temp=temp) +def create_model(backbone, prototype_count=2, lamb=0.5, temp=10): + model = TEDctNet(backbone, prototype_count=prototype_count, lamb=lamb, temp=temp) return model -__all__ = ["CPL", "CNN", "create_cnn_model", "create_model"] +__all__ = ["TEDctNet", "Backbone", "create_backbone", "create_model"] From 88f8bc078f1c9513eb1e2318e344c2054dba73db Mon Sep 17 00:00:00 2001 From: liuchangshiye Date: Sun, 16 Mar 2025 14:03:35 +0800 Subject: [PATCH 33/53] update malaria model --- .../Malaria_Disease/{train_cnn.py => train.py} | 8 ++++---- examples/healthcare/models/malaria_net.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) rename examples/healthcare/application/Malaria_Disease/{train_cnn.py => train.py} (98%) diff --git a/examples/healthcare/application/Malaria_Disease/train_cnn.py b/examples/healthcare/application/Malaria_Disease/train.py similarity index 98% rename from examples/healthcare/application/Malaria_Disease/train_cnn.py rename to examples/healthcare/application/Malaria_Disease/train.py index a58ae802c..78fbd31de 100644 --- a/examples/healthcare/application/Malaria_Disease/train_cnn.py +++ b/examples/healthcare/application/Malaria_Disease/train.py @@ -130,8 +130,8 @@ def run(global_rank, data_size = np.prod(train_x.shape[1:train_x.ndim]).item() num_classes = (np.max(train_y) + 1).item() - if model == 'cnn': - model = malaria_net.create_model(model_option='cnn', num_channels=num_channels, + if model == 'malarianet': + model = malaria_net.create_model(model_option='MalariaNet', num_channels=num_channels, num_classes=num_classes) else: print( @@ -255,8 +255,8 @@ def run(global_rank, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['cnn'], - default='cnn') + choices=['malarianet'], + default='malarianet') parser.add_argument('data', choices=['malaria'], default='malaria') diff --git a/examples/healthcare/models/malaria_net.py b/examples/healthcare/models/malaria_net.py index 2a10a7078..b7f14dbc5 100644 --- a/examples/healthcare/models/malaria_net.py +++ b/examples/healthcare/models/malaria_net.py @@ -28,10 +28,10 @@ singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} -class CNN(model.Model): +class MalariaNet(model.Model): def __init__(self, num_classes=10, num_channels=1): - super(CNN, self).__init__() + super(MalariaNet, self).__init__() self.num_classes = num_classes self.input_size = 128 self.dimension = 4 @@ -127,20 +127,20 @@ def set_optimizer(self, optimizer): self.optimizer = optimizer -def create_model(model_option='cnn', **kwargs): - """Constructs a CNN model. +def create_model(model_option='MalariaNet', **kwargs): + """Constructs a MalariaNet model. Args: pretrained (bool): If True, returns a pre-trained model. Returns: - The created CNN model. + The created MalariaNet model. """ - model = CNN(**kwargs) + model = MalariaNet(**kwargs) if model_option=='mlp': model = MLP(**kwargs) return model -__all__ = ['CNN', 'MLP', 'create_model'] \ No newline at end of file +__all__ = ['MalariaNet', 'MLP', 'create_model'] \ No newline at end of file From 9f499eb216d293da7adc9c64b8b2dc9a1a0bb41c Mon Sep 17 00:00:00 2001 From: npcmaci <779568335@qq.com> Date: Mon, 17 Mar 2025 21:47:15 +0800 Subject: [PATCH 34/53] Update diabetic readmission application --- .../Diabetic_Readmission_Prediction/README.md | 4 ++-- .../Diabetic_Readmission_Prediction/run.sh | 2 +- .../Diabetic_Readmission_Prediction/train.py | 8 ++++---- examples/healthcare/models/diabetic_net.py | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md index 98f07ec3c..d4378466b 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/README.md @@ -25,7 +25,7 @@ Diabetic readmission is a significant concern in healthcare, with a substantial Although diabetes is a manageable condition, early identification of patients at high risk of readmission remains a challenge. A reliable and efficient predictive model can help identify these patients, enabling healthcare providers to intervene early and prevent unnecessary readmissions. -To address this issue, we use Singa to implement a machine learning model for predicting diabetic readmission. The dataset is from [Diabetes 130-US Hospitals for Years 1999-2008](https://archive.ics.uci.edu/ml/datasets/diabetes+130-us+hospitals+for+years+1999-2008). Please download the dataset before running the scripts. +To address this issue, we use Singa to implement a machine learning model for predicting diabetic readmission. The dataset is from [Diabetes 130-US Hospitals for Years 1999-2008](https://archive.ics.uci.edu/ml/datasets/diabetes+130-us+hospitals+for+years+1999-2008). ## Structure @@ -41,5 +41,5 @@ To address this issue, we use Singa to implement a machine learning model for pr ## Command ```bash -python train.py mlp diabetic +python train.py diabeticnet ``` diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/run.sh b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/run.sh index 0cb9797df..0edd94d6e 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/run.sh +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/run.sh @@ -17,4 +17,4 @@ # ### diabetic dataset -python train.py mlp diabetic +python train.py diabeticnet diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py index 694b08a35..30a6de78e 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Readmission_Prediction/train.py @@ -97,7 +97,7 @@ def run(global_rank, num_classes = int(np.max(train_y) + 1) # Initialize MLP model - if model == 'mlp': + if model == 'diabeticnet': model = diabetic_net.create_model(data_size=data_size, num_classes=num_classes) else: @@ -204,11 +204,11 @@ def run(global_rank, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet'], - default='mlp') + choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet', 'diabeticnet'], + default='diabeticnet') parser.add_argument('data', choices=['mnist', 'cifar10', 'cifar100', 'diabetic'], - default='mnist') + default='diabetic') parser.add_argument('-p', choices=['float32', 'float16'], default='float32', diff --git a/examples/healthcare/models/diabetic_net.py b/examples/healthcare/models/diabetic_net.py index 83874c896..a9bffe1a1 100644 --- a/examples/healthcare/models/diabetic_net.py +++ b/examples/healthcare/models/diabetic_net.py @@ -30,10 +30,10 @@ singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} -class MLP(model.Model): +class diabeticnet(model.Model): def __init__(self, data_size=10, perceptron_size=100, num_classes=10): - super(MLP, self).__init__() + super(diabeticnet, self).__init__() self.num_classes = num_classes self.dimension = 2 @@ -81,12 +81,12 @@ def create_model(pretrained=False, **kwargs): Returns: The created CNN model. """ - model = MLP(**kwargs) + model = diabeticnet(**kwargs) return model -__all__ = ['MLP', 'create_model'] +__all__ = ['diabeticnet', 'create_model'] if __name__ == "__main__": np.random.seed(0) @@ -131,7 +131,7 @@ def create_model(pretrained=False, **kwargs): sgd = opt.SGD(0.1, 0.9, 1e-5, dtype=singa_dtype[args.precision]) tx = tensor.Tensor((400, 2), dev, precision) ty = tensor.Tensor((400,), dev, tensor.int32) - model = MLP(data_size=2, perceptron_size=3, num_classes=2) + model = diabeticnet(data_size=2, perceptron_size=3, num_classes=2) # attach model to graph model.set_optimizer(sgd) From ddfe97c005b1d5b006e5c007d21d68d7c0000dbe Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:31:49 +0800 Subject: [PATCH 35/53] Update the data file for cardiovascular disease prediction --- examples/healthcare/data/cardiovascular.py | 96 ++++++++++++++-------- 1 file changed, 62 insertions(+), 34 deletions(-) diff --git a/examples/healthcare/data/cardiovascular.py b/examples/healthcare/data/cardiovascular.py index d4fd6c715..b1e5b98fb 100644 --- a/examples/healthcare/data/cardiovascular.py +++ b/examples/healthcare/data/cardiovascular.py @@ -18,46 +18,74 @@ # import numpy as np -import os -import sys +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) +from sklearn.model_selection import train_test_split +from sklearn.compose import ColumnTransformer +from sklearn.preprocessing import StandardScaler, OneHotEncoder +from sklearn.impute import SimpleImputer +from sklearn.pipeline import Pipeline -def load_cardiovascular_data(file_path): - data = np.loadtxt(file_path, delimiter=',') +def load_cardio_data(file_path): + data = np.genfromtxt(file_path, delimiter=',', skip_header=0) + + continuous_cols = [0, 2, 3, 4, 5] + binary1_col = [1] + ternary_cols = [6, 7] + binary2_cols = [8, 9, 10] - X = data[:, :-1] - y = data[:, -1] + X = data[:, :-1] + y = data[:, -1] + + X_train, X_val, y_train, y_val = train_test_split( + X, y, test_size=0.2, random_state=42, shuffle=False + ) - # Split the data into training and validation sets - train_size = int(0.8 * data.shape[0]) - train_x, val_x = X[:train_size], X[train_size:] - train_y, val_y = y[:train_size], y[train_size:] - - # Normalize the data - mean = np.mean(train_x, axis=0) - std = np.std(train_x, axis=0) - train_x = (train_x - mean) / std - val_x = (val_x - mean) / std - - return train_x, train_y, val_x, val_y - -def load(): - file_path = 'cardio_train.csv' #need to change - - train_x, train_y, val_x, val_y = load_cardiovascular_data(file_path) + preprocessor = ColumnTransformer( + transformers=[ + ('cont', Pipeline([ + ('imputer', SimpleImputer(strategy='mean')), + ('scaler', StandardScaler()) + ]), continuous_cols), + ('binary1', Pipeline([ + ('imputer', SimpleImputer(strategy='most_frequent')), + ('onehot', OneHotEncoder(sparse_output=False, drop=None)) + ]), binary1_col), + ('ternary', Pipeline([ + ('imputer', SimpleImputer(strategy='most_frequent')), + ('onehot', OneHotEncoder(sparse_output=False, drop=None)) + ]), ternary_cols), + ('binary2', Pipeline([ + ('imputer', SimpleImputer(strategy='most_frequent')), + ('onehot', OneHotEncoder(sparse_output=False, drop=None)) + ]), binary2_cols) + ], + remainder='drop' + ) + - train_x = np.array(train_x, dtype=np.float32) - val_x = np.array(val_x, dtype=np.float32) - train_y = np.array(train_y, dtype=np.int32) - val_y = np.array(val_y, dtype=np.int32) + X_train_processed = preprocessor.fit_transform(X_train) + X_val_processed = preprocessor.transform(X_val) + + return X_train_processed, y_train, X_val_processed, y_val + +def load(file_path): + + + try: + X_train, y_train, X_val, y_val = load_cardio_data(file_path) + except FileNotFoundError: + raise SystemExit(f"Error:File {file_path} is not found.") + - return train_x, train_y, val_x, val_y + X_train = X_train.astype(np.float32) + X_val = X_val.astype(np.float32) + y_train = y_train.astype(np.int32) + y_val = y_val.astype(np.int32) + + + return X_train, y_train, X_val, y_val -if __name__ == '__main__': - train_x, train_y, val_x, val_y = load() - print("Training data shape:", train_x.shape) - print("Training labels shape:", train_y.shape) - print("Validation data shape:", val_x.shape) - print("Validation labels shape:", val_y.shape) From ca9bab05ce9a67c27d63fcb0a8b77c0ac79816ce Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:33:41 +0800 Subject: [PATCH 36/53] Update the model file for cardiovascular disease prediction --- .../{cardiovascular_net.py => cardionet.py} | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) rename examples/healthcare/models/{cardiovascular_net.py => cardionet.py} (89%) diff --git a/examples/healthcare/models/cardiovascular_net.py b/examples/healthcare/models/cardionet.py similarity index 89% rename from examples/healthcare/models/cardiovascular_net.py rename to examples/healthcare/models/cardionet.py index b8ce49018..8adbecc6e 100644 --- a/examples/healthcare/models/cardiovascular_net.py +++ b/examples/healthcare/models/cardionet.py @@ -30,10 +30,10 @@ singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} -class MLP(model.Model): +class CardioNet(model.Model): - def __init__(self, data_size=10, perceptron_size=20, num_classes=10): - super(MLP, self).__init__() + def __init__(self, data_size=10, perceptron_size=100, num_classes=10): + super(CardioNet, self).__init__() self.num_classes = num_classes self.dimension = 2 @@ -73,16 +73,19 @@ def set_optimizer(self, optimizer): def create_model(pretrained=False, **kwargs): - """Constructs a CNN model. + """Constructs a CardioNet model. Args: pretrained (bool): If True, returns a pre-trained model. - + Returns: - The created MLP model. + The created CardioNet model. """ - model = MLP(**kwargs) + model = CardioNet(**kwargs) return model -__all__ = ['MLP', 'create_model'] \ No newline at end of file + +__all__ = ['CardioNet', 'create_model'] + + From 399703f64fba8a54f0a243af8cf35be92a8188ca Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:35:32 +0800 Subject: [PATCH 37/53] Update the train file for cardiovascular disease prediction --- .../{train_cnn.py => train.py} | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) rename examples/healthcare/application/Cardiovascular_Disease/{train_cnn.py => train.py} (90%) diff --git a/examples/healthcare/application/Cardiovascular_Disease/train_cnn.py b/examples/healthcare/application/Cardiovascular_Disease/train.py similarity index 90% rename from examples/healthcare/application/Cardiovascular_Disease/train_cnn.py rename to examples/healthcare/application/Cardiovascular_Disease/train.py index 40dd8d016..4d3d07557 100644 --- a/examples/healthcare/application/Cardiovascular_Disease/train_cnn.py +++ b/examples/healthcare/application/Cardiovascular_Disease/train.py @@ -6,8 +6,8 @@ import time import argparse from PIL import Image -from healthcare.data import cardiovascular -from healthcare.models import cardiovascular_net +from data import cardiovascular +from model import cardionet np_dtype = {"float16": np.float16, "float32": np.float32} @@ -83,30 +83,21 @@ def run(global_rank, max_epoch, batch_size, model, - data, sgd, graph, verbosity, + path, dist_option='plain', spars=None, precision='float32'): - #dev = device.get_default_device() - dev = device.create_cuda_gpu_on(local_rank) + dev = device.get_default_device() + #dev = device.create_cuda_gpu_on(local_rank) # need to change to CPU device for CPU-only machines dev.SetRandSeed(0) np.random.seed(0) - if data == 'cifar10': - from data import cifar10 - train_x, train_y, val_x, val_y = cifar10.load() - elif data == 'cifar100': - from data import cifar100 - train_x, train_y, val_x, val_y = cifar100.load() - elif data == 'mnist': - from data import mnist - train_x, train_y, val_x, val_y = mnist.load() - elif data == 'cardiovascular': - train_x, train_y, val_x, val_y = cardiovascular.load() + + train_x, train_y, val_x, val_y = cardiovascular.load(path) num_channels = 1 @@ -131,13 +122,13 @@ def run(global_rank, from model import alexnet model = alexnet.create_model(num_channels=num_channels, num_classes=num_classes) - elif model == 'mlp': + elif model == 'cardionet': import os, sys, inspect current = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) parent = os.path.dirname(current) sys.path.insert(0, parent) - model = cardiovascular_net.create_model(data_size=data_size, perceptron_size=1000, num_classes=num_classes) + model = cardionet.create_model(data_size=data_size, perceptron_size=1000, num_classes=num_classes) # For distributed training, sequential has better performance if hasattr(sgd, "communicator"): @@ -253,11 +244,8 @@ def run(global_rank, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet'], - default='cnn') - parser.add_argument('data', - choices=['mnist', 'cifar10', 'cifar100', 'cardiovascular'], - default='cardiovascular') + choices=['cnn', 'resnet', 'xceptionnet', 'cardionet', 'alexnet'], + default='cardionet') parser.add_argument('-p', choices=['float32', 'float16'], default='float32', @@ -299,6 +287,12 @@ def run(global_rank, type=int, help='logging verbosity', dest='verbosity') + parser.add_argument('-dir', + '--path-to-dataset', + default=None, + help='path to dataset', + dest='path') + args = parser.parse_args() @@ -309,8 +303,8 @@ def run(global_rank, args.max_epoch, args.batch_size, args.model, - args.data, sgd, args.graph, args.verbosity, + args.path, precision=args.precision) From 673f4b74982fcdc9b6d1df8f3e1ec46633caf5cb Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:52:27 +0800 Subject: [PATCH 38/53] Update the training script for cardiovascular disease prediction --- examples/healthcare/application/Cardiovascular_Disease/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/healthcare/application/Cardiovascular_Disease/run.sh b/examples/healthcare/application/Cardiovascular_Disease/run.sh index a0e6b9767..0ecb8231a 100644 --- a/examples/healthcare/application/Cardiovascular_Disease/run.sh +++ b/examples/healthcare/application/Cardiovascular_Disease/run.sh @@ -17,4 +17,4 @@ # ### cardiovascular dataset -python train_cnn.py mlp cardiovascular +python train.py cardionet -dir pathToDataset From ac55bb49c84409439617f0bad6d88ce3fdaf4d29 Mon Sep 17 00:00:00 2001 From: serakiepiphany <142875362+serakiepiphany@users.noreply.github.com> Date: Tue, 18 Mar 2025 15:54:15 +0800 Subject: [PATCH 39/53] Update README for cardiovascular disease prediction --- .../application/Cardiovascular_Disease/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/healthcare/application/Cardiovascular_Disease/README.md b/examples/healthcare/application/Cardiovascular_Disease/README.md index cc68d4106..a7ed42d2a 100644 --- a/examples/healthcare/application/Cardiovascular_Disease/README.md +++ b/examples/healthcare/application/Cardiovascular_Disease/README.md @@ -25,18 +25,17 @@ Cardiovascular disease is primarily caused by risk factors like high blood press Although early detection can significantly improve outcomes, insufficient screening methods and delayed diagnosis often lead to preventable complications. Therefore, developing rapid and accurate diagnostic tools is crucial for effective prevention and treatment of cardiovascular conditions. -To address this challenge, we utilize Singa to develop a machine learning model for cardiovascular disease risk prediction. The training dataset is sourced from Kaggle https://www.kaggle.com/datasets/sulianova/cardiovascular-disease-dataset. Please download the dataset before running the scripts. +To address this challenge, we utilize Singa to develop a machine learning model for cardiovascular disease risk prediction. The training dataset is sourced from Kaggle https://www.kaggle.com/datasets/sulianova/cardiovascular-disease-dataset. You can download the dataset, pass the path to the script, and then you can run the program by using the script. ## Structure * `cardiovascular.py` in the `healthcare/data` directory is the scripts for preprocessing Cardiovascular Disease datasets. -* `cardiovascular_net.py` in the `healthcare/models` directory includes the MLP model construction codes. +* `cardionet.py` in the `healthcare/models` directory includes the MLP model construction codes. -* `train_cnn.py` is the training script, which controls the training flow by - doing BackPropagation and SGD update. +* `train.py` is the training script, which controls the training flow by doing BackPropagation and SGD update. ## Command ```bash -python train_cnn.py mlp cardiovascular +python train.py cardionet -dir pathToDataset ``` From 90fcd3b7cec9098b9ac3c9bf0df24a8f3df4c959 Mon Sep 17 00:00:00 2001 From: Zhaojing Luo Date: Wed, 19 Mar 2025 15:45:34 +0800 Subject: [PATCH 40/53] Update CMakeLists.txt for v 5.0.0 Update CMakeLists.txt for v 5.0.0 --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7dc1d2c4..ce0f0e33e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,10 @@ LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Thirdparty) #string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" VERSION_PATCH "${VERSION}") -SET(PACKAGE_VERSION 4.3.0) # ${VERSION}) -SET(VERSION 4.3.0) -SET(SINGA_MAJOR_VERSION 4) -SET(SINGA_MINOR_VERSION 3) +SET(PACKAGE_VERSION 5.0.0) # ${VERSION}) +SET(VERSION 5.0.0) +SET(SINGA_MAJOR_VERSION 5) +SET(SINGA_MINOR_VERSION 0) SET(SINGA_PATCH_VERSION 0) #SET(SINGA_MAJOR_VERSION ${VERSION_MAJOR}) # 0 - #SET(SINGA_MINOR_VERSION ${VERSION_MINOR}) # 0 - 9 From e7c3b92c82a863520f38626026f873a265a4ab1a Mon Sep 17 00:00:00 2001 From: Zhaojing Luo Date: Wed, 19 Mar 2025 15:47:27 +0800 Subject: [PATCH 41/53] Update setup.py for V5.0.0 Update setup.py for V5.0.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a7811c77f..5d0d2f579 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ from datetime import date # stable version -VERSION = '4.3.0' +VERSION = '5.0.0' # get the git hash # git_hash = subprocess.check_output(["git", "describe"]).strip().split('-')[-1][1:] # comment the next line to build wheel for stable version From 8c41a4d61d6bfd7f7da91210f151ee970ed6368c Mon Sep 17 00:00:00 2001 From: Zhaojing Luo Date: Wed, 19 Mar 2025 15:49:01 +0800 Subject: [PATCH 42/53] Update meta.yaml for V5.0.0 Update meta.yaml for V5.0.0 --- tool/conda/singa/meta.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/conda/singa/meta.yaml b/tool/conda/singa/meta.yaml index e8e2fcc60..6fe568d21 100644 --- a/tool/conda/singa/meta.yaml +++ b/tool/conda/singa/meta.yaml @@ -20,7 +20,7 @@ # https://docs.conda.io/projects/conda-build/en/latest/resources/define-metadata.html#templating-with-jinja # {% set data = load_setup_py_data(setup_file='../../../python/singa/setup.py', from_recipe_dir=True) %} -{% set version = "4.3.0" %} +{% set version = "5.0.0" %} package: name: singa From f8fa8ce33d9a259f25883fc841dd6dfe4b3869aa Mon Sep 17 00:00:00 2001 From: L0FX <111472480+L0FX@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:23:42 +0800 Subject: [PATCH 43/53] Update README.md --- .../application/Kidney_Disease/README.md | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/healthcare/application/Kidney_Disease/README.md b/examples/healthcare/application/Kidney_Disease/README.md index 05a97bc32..0a3979e79 100644 --- a/examples/healthcare/application/Kidney_Disease/README.md +++ b/examples/healthcare/application/Kidney_Disease/README.md @@ -19,25 +19,28 @@ # Singa for Kidney Disease Prediction -## Kidney Disease Prediction Task +## Kidney disease Prediction Task -Kidney disease prediction is an important tool that uses data science and machine learning techniques to predict the likelihood of a patient suffering from Kidney disease. The goal is to judge whether a patient suffers from kidney disease by analyzing multiple data such as a patient’s medical history, physiological indicators, diagnostic information, treatment options, and socioeconomic factors, so as to take appropriate interventions in advance to provide treatment. +Kidney disease prediction is an important tool that uses data science and machine learning techniques to predict the likelihood of a patient suffering from Kidney disease. The core goal of this technology is to judge whether a patient suffers from kidney disease by analyzing multiple data such as a patient’s medical history, physiological indicators, diagnostic information, treatment options, and socioeconomic factors, so as to take appropriate interventions in advance to provide treatment. + +The dataset used in this task is MIMIC-III after preprocessed. The features are data containing 6 visit windows, with 2549 frequent diagnoses, procedures and drugs for each window. Each item in features are data for one patient, and these features are encoded by one-hot code. The labels are corresponding flags to mark whether the patient suffered from kidney disease, where the label equals "1" if the patient had kidn disease, the label equals "0" if not. -The dataset used in this task is MIMIC-III. The features are data containing 6 visit windows, with 2549 frequent diagnoses, procedures and drugs for each window. These features are encoded by one-hot. The labels are corresponding flags to mark whether the patient suffered from kidney disease, where the label equals "1" if the patient had kidney disease, and the label equals "0" if not. ## Structure -* `data` includes the load of mimic-iii data to be utilized. +* `kidney.py` in floder `healthcare/data` includes the load of pre-processed kidney data to be utilized. + +* `kidney_net.py` in folder `healthcare/models` includes the construction codes of the KidneyNet model to be applied for kidney disease prediction. + +* `train.py` is the training script, which controls the training flow bydoing BackPropagation and SGD update. -* `model` includes the MLP model construction codes by creating - a subclass of `Module` to wrap the neural network operations - of each model. +## Instruction +Before starting to use this model for kidney disease prediction, download the sample dataset for kidney disease prediction: https://github.com/lzjpaul/singa-healthcare/tree/main/data/kidney -* `train_kidney_mlp.py` is the training script, which controls the training flow by - doing BackPropagation and the SGD update. +The provided dataset is from MIMIC-III, which has been pre-processed. And the dataset contains 100 samples for model testing. -## Command +Please download the dataset to a folder(pathToDataset), and then pass the path to run the codes using the following command: ```bash -python train_kidney_mlp.py mlp kidney-disease -dir pathToDataset -``` \ No newline at end of file +python train.py kidneynet -dir pathToDataset +``` From d11d01bb74a105c3e72e697c95b713491fef2e8f Mon Sep 17 00:00:00 2001 From: L0FX <111472480+L0FX@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:24:03 +0800 Subject: [PATCH 44/53] Update run.sh --- examples/healthcare/application/Kidney_Disease/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/healthcare/application/Kidney_Disease/run.sh b/examples/healthcare/application/Kidney_Disease/run.sh index 27de7eae3..d86c83bf4 100644 --- a/examples/healthcare/application/Kidney_Disease/run.sh +++ b/examples/healthcare/application/Kidney_Disease/run.sh @@ -17,4 +17,4 @@ # ### kidney disease dataset -python train_kidney_mlp.py mlp kidney-disease -dir pathToDataset \ No newline at end of file +python train.py kidneynet -dir pathToDataset From b706afbd2541384b3f052fb4b17032ba783eb075 Mon Sep 17 00:00:00 2001 From: L0FX <111472480+L0FX@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:24:47 +0800 Subject: [PATCH 45/53] Update and rename train_kidney_mlp.py to train.py --- .../{train_kidney_mlp.py => train.py} | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) rename examples/healthcare/application/Kidney_Disease/{train_kidney_mlp.py => train.py} (93%) diff --git a/examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py b/examples/healthcare/application/Kidney_Disease/train.py similarity index 93% rename from examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py rename to examples/healthcare/application/Kidney_Disease/train.py index 474858066..1af8c24ef 100644 --- a/examples/healthcare/application/Kidney_Disease/train_kidney_mlp.py +++ b/examples/healthcare/application/Kidney_Disease/train.py @@ -25,6 +25,10 @@ import time import argparse from PIL import Image +import sys +sys.path.append("../../..") +from healthcare.data import kidney +from healthcare.models import kidney_net np_dtype = {"float16": np.float16, "float32": np.float32} @@ -107,6 +111,7 @@ def run(global_rank, sgd, graph, verbosity, + dir_path, dist_option='plain', spars=None, precision='float32'): @@ -115,9 +120,9 @@ def run(global_rank, dev.SetRandSeed(0) np.random.seed(0) - if data == 'kidney-disease': - from data import load_kidneydata - train_x, train_y, val_x, val_y = load_kidneydata.load() + if data == 'kidney': + + train_x, train_y, val_x, val_y = kidney.load(dir_path) else: print('Wrong Dataset!') sys.exit(0) @@ -130,14 +135,14 @@ def run(global_rank, print(num_channels,image_size) - if model == 'mlp': + if model == 'kidneynet': import os, sys, inspect current = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) parent = os.path.dirname(current) sys.path.insert(0, parent) - from mlp import model - model = model.create_model(data_size=data_size, + + model = kidney_net.create_model(data_size=data_size, num_classes=num_classes) else: print('Wrong model!') @@ -256,11 +261,11 @@ def run(global_rank, description='Training using the autograd and graph.') parser.add_argument( 'model', - choices=['cnn', 'resnet', 'xceptionnet', 'mlp', 'alexnet'], - default='cnn') - parser.add_argument('data', - choices=['mnist', 'cifar10', 'cifar100','mimic-iii','kidney-disease'], - default='kidney-disease') + choices=[ 'cardionet', 'diabeticnet', 'drnet', 'hematologicnet', 'kidneynet', 'malarianet', 'tedctnet'], + default='kidneynet') + parser.add_argument('-data', + choices=['mnist', 'cifar10', 'cifar100','kidney'], + default='kidney') parser.add_argument('-p', choices=['float32', 'float16'], default='float32', @@ -302,7 +307,12 @@ def run(global_rank, type=int, help='logging verbosity', dest='verbosity') - + parser.add_argument('-dir', + '--dir-path', + default="/tmp/kidney", + type=str, + help='the directory to store the kidney dataset', + dest='dir_path') args = parser.parse_args() sgd = opt.SGD(lr=args.lr, momentum=0.9, weight_decay=1e-5, dtype=singa_dtype[args.precision]) @@ -316,4 +326,5 @@ def run(global_rank, sgd, args.graph, args.verbosity, + args.dir_path, precision=args.precision) From a715fd6e6198e6a2aa6d6dda5183cc50c9a672d1 Mon Sep 17 00:00:00 2001 From: L0FX <111472480+L0FX@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:25:26 +0800 Subject: [PATCH 46/53] Update and rename kidneydata.py to kidney.py --- examples/healthcare/data/kidney.py | 41 ++++++++++++++++++ examples/healthcare/data/kidneydata.py | 57 -------------------------- 2 files changed, 41 insertions(+), 57 deletions(-) create mode 100644 examples/healthcare/data/kidney.py delete mode 100644 examples/healthcare/data/kidneydata.py diff --git a/examples/healthcare/data/kidney.py b/examples/healthcare/data/kidney.py new file mode 100644 index 000000000..5956aae4a --- /dev/null +++ b/examples/healthcare/data/kidney.py @@ -0,0 +1,41 @@ +import numpy as np + +import pickle +import sys +import os + +def load_dataset(dir_path="/tmp/kidney"): + dir_path = check_dataset_exist(dir_path=dir_path) + feature_path = os.path.join(dir_path, "kidney_features.pkl") + label_path = os.path.join(dir_path, "kidney_labels.pkl") + with open(feature_path,'rb') as f: + features = pickle.load(f) + with open(label_path,'rb') as f: + labels = pickle.load(f) + + + split_train_point = int(len(features) * 8/ 10) + train_x, train_y = features[:split_train_point], labels[:split_train_point] + val_x, val_y = features[split_train_point:], labels[split_train_point:] + + return train_x,train_y,val_x,val_y + +def check_dataset_exist(dir_path): + if not os.path.exists(dir_path): + print( + 'Please download the kidney dataset first' + ) + sys.exit(0) + return dir_path + + +def load(dir_path): + train_x,train_y,val_x,val_y = load_dataset(dir_path) + + train_x = train_x.astype(np.float32) + val_x = val_x.astype(np.float32) + train_y = train_y.astype(np.int32) + val_y = val_y.astype(np.int32) + + return train_x,train_y,val_x,val_y + diff --git a/examples/healthcare/data/kidneydata.py b/examples/healthcare/data/kidneydata.py deleted file mode 100644 index 4144acf70..000000000 --- a/examples/healthcare/data/kidneydata.py +++ /dev/null @@ -1,57 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -import numpy as np -import torch -from tqdm import tqdm -import pickle - - -def load_dataset(): - with open('/home/kidney_disease/kidney_features.pkl','rb') as f: # change the path to load dataset - features = pickle.load(f) - with open('/home/kidney_disease/kidney_labels.pkl','rb') as f: # change the path to load dataset - labels = pickle.load(f) - - - split_train_point = int(len(features) * 8/ 10) - train_x, train_y = features[:split_train_point], labels[:split_train_point] - val_x, val_y = features[split_train_point:], labels[split_train_point:] - - return train_x,train_y,val_x,val_y - -def process_label(data): - new_labels = [] - for i in tqdm(data, total=len(data)): - label = torch.squeeze(i, dim=0) - new_labels.append(label) - return new_labels - - - -def load(): - train_x,train_y,val_x,val_y = load_dataset() - - - train_x = train_x.astype(np.float32) - val_x = val_x.astype(np.float32) - train_y = train_y.astype(np.int32) - val_y = val_y.astype(np.int32) - - return train_x,train_y,val_x,val_y \ No newline at end of file From 652edd865bfad063068397ece57966378ac41f7f Mon Sep 17 00:00:00 2001 From: L0FX <111472480+L0FX@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:25:44 +0800 Subject: [PATCH 47/53] Update kidney_net.py --- examples/healthcare/models/kidney_net.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/healthcare/models/kidney_net.py b/examples/healthcare/models/kidney_net.py index c4c49764b..67d472412 100644 --- a/examples/healthcare/models/kidney_net.py +++ b/examples/healthcare/models/kidney_net.py @@ -30,10 +30,10 @@ singa_dtype = {"float16": tensor.float16, "float32": tensor.float32} -class MLP(model.Model): +class KidneyNet(model.Model): def __init__(self, data_size=10, perceptron_size=100, num_classes=10): - super(MLP, self).__init__() + super(KidneyNet, self).__init__() self.num_classes = num_classes self.dimension = 2 @@ -73,20 +73,13 @@ def set_optimizer(self, optimizer): def create_model(pretrained=False, **kwargs): - """Constructs a CNN model. - Args: - pretrained (bool): If True, returns a pre-trained model. - - Returns: - The created CNN model. - """ - model = MLP(**kwargs) + model = KidneyNet(**kwargs) return model -__all__ = ['MLP', 'create_model'] +__all__ = ['KidneyNet', 'create_model'] if __name__ == "__main__": np.random.seed(0) @@ -131,7 +124,7 @@ def create_model(pretrained=False, **kwargs): sgd = opt.SGD(0.1, 0.9, 1e-5, dtype=singa_dtype[args.precision]) tx = tensor.Tensor((400, 2), dev, precision) ty = tensor.Tensor((400,), dev, tensor.int32) - model = MLP(data_size=2, perceptron_size=3, num_classes=2) + model = KidneyNet(data_size=2, perceptron_size=3, num_classes=2) # attach model to graph model.set_optimizer(sgd) @@ -145,5 +138,3 @@ def create_model(pretrained=False, **kwargs): if i % 100 == 0: print("training loss = ", tensor.to_numpy(loss)[0]) - - From 943646d0f9c5f4987c99d26dc3214a8077ff28d3 Mon Sep 17 00:00:00 2001 From: Zhaojing Luo Date: Fri, 21 Mar 2025 21:54:24 +0800 Subject: [PATCH 48/53] Add the release notes for V 5.0.0 Add the release notes for V 5.0.0 --- RELEASE_NOTES | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/RELEASE_NOTES b/RELEASE_NOTES index fe9384656..c8c75349b 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,3 +1,31 @@ +Release Notes - SINGA - Version singa-5.0.0 + +SINGA is a distributed deep learning library. + +This release includes following changes: + + * Add the implementations of the healthcare model zoo. + * Add the implementation for the cardiovascular disease. + * Add the implementation for the diabetic disease. + * Add the implementation for the hematologic disease. + * Add the implementation for the kidney disease. + * Add the implementation for the malaria disease. + * Add the implementation for the thyroid eye disease. + + * Optimize the distributed training by updating the MSOptimizer and MSSGD. + + * Improve the efficiency of the transformer example. + + * Add the sparsification version of the model for the model selection example. + + * Update data processing for the benchmark dataset. + + * Update the pom.xml file to include paths for datasets. + + * Update the online documentations for the healthcare model zoo. + +---------------------------------------------------------------------------------------------- + Release Notes - SINGA - Version singa-4.3.0 SINGA is a distributed deep learning library. From 864a26bbb70b580c7ee87a9e3dfb2c128b2e7f8e Mon Sep 17 00:00:00 2001 From: zhangruipeng Date: Sat, 22 Mar 2025 09:52:28 +0800 Subject: [PATCH 49/53] Add Apache license header for V 5.0.0 --- .../Cardiovascular_Disease/train.py | 18 ++++++++++++++++++ .../train.py | 18 ++++++++++++++++++ examples/healthcare/data/kidney.py | 18 ++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/examples/healthcare/application/Cardiovascular_Disease/train.py b/examples/healthcare/application/Cardiovascular_Disease/train.py index 4d3d07557..2caa68998 100644 --- a/examples/healthcare/application/Cardiovascular_Disease/train.py +++ b/examples/healthcare/application/Cardiovascular_Disease/train.py @@ -1,3 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + from singa import singa_wrap as singa from singa import device from singa import tensor diff --git a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py index bab78c23f..e3f3649f2 100644 --- a/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py +++ b/examples/healthcare/application/Diabetic_Disease/Diabetic_Retinopathy_Classification/train.py @@ -1,3 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + from singa import singa_wrap as singa from singa import device from singa import tensor diff --git a/examples/healthcare/data/kidney.py b/examples/healthcare/data/kidney.py index 5956aae4a..6ed0d7dc2 100644 --- a/examples/healthcare/data/kidney.py +++ b/examples/healthcare/data/kidney.py @@ -1,3 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + import numpy as np import pickle From 9f94f6c5946f562f9b3c22f9c6bbc0b3fd3708b5 Mon Sep 17 00:00:00 2001 From: shicong Date: Mon, 24 Mar 2025 08:20:04 -0400 Subject: [PATCH 50/53] Add the apache license header for the malaria example --- examples/malaria_cnn/train_cnn.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/examples/malaria_cnn/train_cnn.py b/examples/malaria_cnn/train_cnn.py index bfe810d4f..4a54e6c83 100644 --- a/examples/malaria_cnn/train_cnn.py +++ b/examples/malaria_cnn/train_cnn.py @@ -1,3 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + from singa import singa_wrap as singa from singa import device from singa import tensor From f2611c91ecf4933d308b82d4d99f01496d552d45 Mon Sep 17 00:00:00 2001 From: zmeihui Date: Tue, 25 Mar 2025 16:35:08 +0800 Subject: [PATCH 51/53] Update readme.md --- examples/healthcare/application/Malaria_Disease/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/healthcare/application/Malaria_Disease/readme.md b/examples/healthcare/application/Malaria_Disease/readme.md index 00100b77f..45a532db8 100644 --- a/examples/healthcare/application/Malaria_Disease/readme.md +++ b/examples/healthcare/application/Malaria_Disease/readme.md @@ -40,5 +40,5 @@ To mitigate the problem, we use Singa to implement a machine learning model to h ## Command ```bash -python train_cnn.py cnn malaria -dir pathToDataset -``` \ No newline at end of file +python train.py malarianet -dir pathToDataset +``` From daf0f97c2fd67045d203d8ca94752575a6bcafbc Mon Sep 17 00:00:00 2001 From: zmeihui Date: Tue, 25 Mar 2025 16:35:28 +0800 Subject: [PATCH 52/53] Update run.sh --- examples/healthcare/application/Malaria_Disease/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/healthcare/application/Malaria_Disease/run.sh b/examples/healthcare/application/Malaria_Disease/run.sh index 8e10e9924..abf2fbc9d 100644 --- a/examples/healthcare/application/Malaria_Disease/run.sh +++ b/examples/healthcare/application/Malaria_Disease/run.sh @@ -17,4 +17,4 @@ # ### malaria dataset -python train_cnn.py cnn malaria -dir pathToDataset \ No newline at end of file +python train.py malarianet -dir pathToDataset From 39bd78493dc15d40203e462d712f7a3bf522f7b3 Mon Sep 17 00:00:00 2001 From: Zhaojing Luo Date: Wed, 26 Mar 2025 17:10:48 +0800 Subject: [PATCH 53/53] Upgrade the .asf.yaml file Upgrade the .asf.yaml file --- .asf.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index 1e0b37f9f..df9684076 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -24,5 +24,3 @@ github: wiki: true # Enable issues on github issues: true - # Enable settings on github - settings: true