From 79f4e4fd52f61bc4ba4e57582cc3281f40aebb3e Mon Sep 17 00:00:00 2001 From: bxdd Date: Fri, 7 Apr 2023 16:34:08 +0800 Subject: [PATCH] [ENH] Add import learnware methods --- examples/example_market_db/example_db.py | 6 ++- examples/examples2/example_learnware.py | 3 +- images/(README)-pic_1680488105261.png | Bin 27166 -> 0 bytes learnware/learnware/__init__.py | 65 ++++++++++++++++++++++- learnware/learnware/base.py | 34 +----------- learnware/learnware/utils.py | 51 ++++++++++++++++++ learnware/market/database_ops.py | 2 + learnware/market/easy.py | 33 +++++++----- learnware/specification/base.py | 5 +- setup.py | 2 + 10 files changed, 151 insertions(+), 50 deletions(-) delete mode 100644 images/(README)-pic_1680488105261.png create mode 100644 learnware/learnware/utils.py diff --git a/examples/example_market_db/example_db.py b/examples/example_market_db/example_db.py index cc9e544..7ac2303 100644 --- a/examples/example_market_db/example_db.py +++ b/examples/example_market_db/example_db.py @@ -63,7 +63,9 @@ def test_search(): for i in range(10): user_spec = specification.rkme.RKMEStatSpecification() user_spec.load(f"./learnware_pool/svm{i}/spec.json") - user_info = BaseUserInfo(id="user_0", semantic_spec={"desc": "test_user_number_0"}, stat_info={"RKME": user_spec}) + user_info = BaseUserInfo( + id="user_0", semantic_spec={"desc": "test_user_number_0"}, stat_info={"RKME": user_spec} + ) sorted_dist_list, single_learnware_list, mixture_learnware_list = easy_market.search_learnware(user_info) print(f"search result of user{i}:") @@ -71,7 +73,7 @@ def test_search(): print(f"dist: {dist}, learnware_id: {learnware.id}, learnware_name: {learnware.name}") mixture_id = " ".join([learnware.id for learnware in mixture_learnware_list]) print(f"mixture_learnware: {mixture_id}\n") - + if __name__ == "__main__": test_market() diff --git a/examples/examples2/example_learnware.py b/examples/examples2/example_learnware.py index 811ef2a..6ea7065 100644 --- a/examples/examples2/example_learnware.py +++ b/examples/examples2/example_learnware.py @@ -17,7 +17,8 @@ def prepare_learnware(): clf.fit(data_X, data_y) joblib.dump(clf, "./svm/svm.pkl") - spec = specification.utils.generate_rkme_spec(X=data_X, gamma=0.1, cuda_idx=0) + spec = specification.utils.generate_rkme_spec(X=data_X, gamma=0.1) + spec.save("./svm/spec.json") diff --git a/images/(README)-pic_1680488105261.png b/images/(README)-pic_1680488105261.png deleted file mode 100644 index c3368de537a4b35e5b0f393b85b739fb64c1e5ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27166 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 diff --git a/learnware/learnware/__init__.py b/learnware/learnware/__init__.py index 9bedb2a..9a2de34 100644 --- a/learnware/learnware/__init__.py +++ b/learnware/learnware/__init__.py @@ -1,2 +1,65 @@ from .base import Learnware -from .reuse import BaseReuse +from .utils import get_stat_spec_from_config, get_model_from_config +from ..specification import RKMEStatSpecification, Specification +from ..utils import get_module_by_module_path +from ..logger import get_module_logger + +from typing import Tuple + +from .base import Learnware + +logger = get_module_logger("learnware.learnware") + + +def get_learnware_from_config(id: int, file_config: dict, semantic_spec: dict) -> Learnware: + """Get the learnware object from config, and provide the manage interface tor Learnware class + + Parameters + ---------- + id : int + The learnware id that is given by learnware market + file_config : dict + The learnware file config that demonstrates the name, model, and statistic specification config of learnware + semantic_spec : dict + The learnware semantice specifactions + + Returns + ------- + Learnware + The contructed learnware object, return None if build failed + """ + learnware_config = { + "name": "None", + "model": { + "class_name": "Model", + "kwargs": {}, + }, + "stat_specifications": [ + { + "module_name": "learnware.specification", + "class_name": "RKMEStatSpecification", + "kwargs": {}, + }, + ], + } + if "name" in file_config: + learnware_config["name"] = file_config["name"] + if "model" in file_config: + learnware_config["model"].update(file_config["model"]) + if "stats_specifications" in file_config: + learnware_config["stat_specifications"] = file_config["stat_specifications"] + + try: + learnware_spec = Specification() + for _stat_spec in learnware_config["stat_specifications"]: + stat_spac_name, stat_spec_inst = get_stat_spec_from_config(_stat_spec) + learnware_spec.update_stat_spec(**{stat_spac_name: stat_spec_inst}) + + learnware_spec.upload_semantic_spec(semantic_spec) + learnware_model = get_model_from_config(learnware_config["model"]) + + except Exception: + logger.warning(f"Load Learnware {id} failed!") + return None + + return Learnware(id=id, name=learnware_config["name"], model=learnware_model, specification=learnware_spec) diff --git a/learnware/learnware/base.py b/learnware/learnware/base.py index 8578f91..c696fc2 100644 --- a/learnware/learnware/base.py +++ b/learnware/learnware/base.py @@ -8,42 +8,12 @@ from ..utils import get_module_by_module_path class Learnware: - def __init__(self, id: str, name: str, model: Union[BaseModel, str], specification: Specification): + def __init__(self, id: str, name: str, model: BaseModel, specification: Specification): self.id = id self.name = name - self.model = self._import_model(model) + self.model = model self.specification = specification - def _import_model(self, model: Union[BaseModel, str]) -> BaseModel: - """_summary_ - - Parameters - ---------- - model : Union[BaseModel, dict] - - If isinstance(model, str), model is the path of the python file - - If isinstance(model, BaseModel), return model directly - Returns - ------- - BaseModel - The model that is given by user - Raises - ------ - TypeError - The type of model must be str or BaseModel, else raise error - """ - if isinstance(model, BaseModel): - return model - elif isinstance(model, str): - model_dict = { - "module_path": model, # path of python file - "class_name": "Model" # the name of class in python file, default is "Model", can be changed by yaml - } - # TODO: test yaml file, change model_dict["class_name"] - model_module = get_module_by_module_path(model_dict["module_path"]) - return getattr(model_module, model_dict["class_name"])() - else: - raise TypeError("model must be BaseModel or str") - def predict(self, X: np.ndarray) -> np.ndarray: return self.model.predict(X) diff --git a/learnware/learnware/utils.py b/learnware/learnware/utils.py new file mode 100644 index 0000000..4dacc33 --- /dev/null +++ b/learnware/learnware/utils.py @@ -0,0 +1,51 @@ +from .base import Learnware +from .reuse import BaseReuse + + +from typing import Tuple, Union + +from .base import Learnware +from ..model import BaseModel +from ..specification import BaseStatSpecification +from ..utils import get_module_by_module_path +import learnware.specification as specification + + +def get_model_from_config(model: Union[BaseModel, dict]) -> BaseModel: + """_summary_ + + Parameters + ---------- + model : Union[BaseModel, dict] + - If isinstance(model, dict), model is must be the following format: + model_dict = { + "module_path": str, # path of python file + "class_name": str, # the name of class in python file + } + - If isinstance(model, BaseModel), return model directly + Returns + ------- + BaseModel + The model that is given by user + Raises + ------ + TypeError + The type of model must be dict or BaseModel, else raise error + """ + if isinstance(model, BaseModel): + return model + elif isinstance(model, dict): + model_module = get_module_by_module_path(model["module_path"]) + return getattr(model_module, model["class_name"])(**model["kwargs"]) + else: + raise TypeError("model must be type of BaseModel or str") + + +def get_stat_spec_from_config(stat_spec: dict) -> BaseStatSpecification: + stat_spec_module = get_module_by_module_path(stat_spec["module_path"]) + stat_spec_inst = getattr(stat_spec_module, stat_spec["class_name"])(**stat_spec["kwargs"]) + if not isinstance(stat_spec_inst, BaseStatSpecification): + raise TypeError( + f"Statistic specification must be type of BaseStatSpecification, not {BaseStatSpecification.__class__.__name__}" + ) + return stat_spec["class_name"], stat_spec_inst diff --git a/learnware/market/database_ops.py b/learnware/market/database_ops.py index 03009af..e7aa6a8 100644 --- a/learnware/market/database_ops.py +++ b/learnware/market/database_ops.py @@ -36,6 +36,7 @@ def init_empty_db(func): return wrapper + # Clear Learnware Database # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # !!!!! !!!!! @@ -47,6 +48,7 @@ def clear_learnware_table(cur): LOGGER.warning("!!! Drop Learnware Table !!!") cur.execute("DROP TABLE LEARNWARE") + @init_empty_db def add_learnware_to_db(id: str, name: str, model_path: str, stat_spec_path: str, semantic_spec: dict, cur): semantic_spec_str = json.dumps(semantic_spec) diff --git a/learnware/market/easy.py b/learnware/market/easy.py index a752081..1ecf1b3 100644 --- a/learnware/market/easy.py +++ b/learnware/market/easy.py @@ -12,7 +12,7 @@ from ..specification import RKMEStatSpecification, Specification from ..logger import get_module_logger from ..config import C -LOGGER = get_module_logger("market", "INFO") +logger = get_module_logger("market", "INFO") class EasyMarket(BaseMarket): @@ -22,7 +22,7 @@ class EasyMarket(BaseMarket): self.count = 0 self.semantic_spec_list = C.semantic_specs self.reload_market() - LOGGER.info("Market Initialized!") + logger.info("Market Initialized!") def reload_market(self) -> bool: self.learnware_list, self.count = load_market_from_db() @@ -42,10 +42,11 @@ class EasyMarket(BaseMarket): try: spec_data = learnware.specification.stat_spec["RKME"].get_z() pred_spec = learnware.predict(spec_data) - return True - except: + except Exception: + logger.warning(f"The learnware [{learnware.id}-{learnware.name}] is not avaliable!") return False - + return True + def add_learnware( self, learnware_name: str, model_path: str, stat_spec_path: str, semantic_spec: dict ) -> Tuple[str, bool]: @@ -88,14 +89,18 @@ class EasyMarket(BaseMarket): rkme_stat_spec.load(stat_spec_path) stat_spec = {"RKME": rkme_stat_spec} specification = Specification(semantic_spec=semantic_spec, stat_spec=stat_spec) - + id = "%08d" % (self.count) new_learnware = Learnware(id=id, name=learnware_name, model=model_path, specification=specification) - if self.check_learnware(new_learnware): + if self.check_learnware(new_learnware): self.learnware_list[id] = new_learnware self.count += 1 add_learnware_to_db( - id, name=learnware_name, model_path=model_path, stat_spec_path=stat_spec_path, semantic_spec=semantic_spec + id, + name=learnware_name, + model_path=model_path, + stat_spec_path=stat_spec_path, + semantic_spec=semantic_spec, ) return id, True else: @@ -303,11 +308,13 @@ class EasyMarket(BaseMarket): if match_semantic_spec(learnware_semantic_spec, user_semantic_spec): match_learnwares.append(learnware) return match_learnwares - + learnware_list = [self.learnware_list[key] for key in self.learnware_list] return learnware_list - - def search_learnware(self, user_info: BaseUserInfo, search_num=3) -> Tuple[List[float], List[Learnware], List[Learnware]]: + + def search_learnware( + self, user_info: BaseUserInfo, search_num=3 + ) -> Tuple[List[float], List[Learnware], List[Learnware]]: """Search learnwares based on user_info Parameters @@ -331,7 +338,9 @@ class EasyMarket(BaseMarket): else: user_rkme = user_info.stat_info["RKME"] sorted_dist_list, single_learnware_list = self._search_by_rkme_spec_single(learnware_list, user_rkme) - weight_list, mixture_learnware_list = self._search_by_rkme_spec_mixture(learnware_list, user_rkme, search_num) + weight_list, mixture_learnware_list = self._search_by_rkme_spec_mixture( + learnware_list, user_rkme, search_num + ) return sorted_dist_list, single_learnware_list, mixture_learnware_list def delete_learnware(self, id: str) -> bool: diff --git a/learnware/specification/base.py b/learnware/specification/base.py index 12b61c6..53e9c9f 100644 --- a/learnware/specification/base.py +++ b/learnware/specification/base.py @@ -29,8 +29,9 @@ class Specification: def upload_semantic_spec(self, new_semantic_spec: dict): self.semantic_spec = new_semantic_spec - def update_stat_spec(self, name, new_stat_spec: BaseStatSpecification): - self.stat_spec[name] = new_stat_spec + def update_stat_spec(self, **kwargs): + for _k, _v in kwargs: + self.stat_spec[_k] = _v def get_stat_spec_by_name(self, name: str): return self.stat_spec.get(name, None) diff --git a/setup.py b/setup.py index 03a356c..99daa89 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,8 @@ REQUIRED = [ # "mkl-service>=2.3.0", "cvxopt>=1.3.0", "tqdm>=4.65.0", + "scikit-learn>=1.2.2", + "joblib>=1.2.0", ] here = os.path.abspath(os.path.dirname(__file__))