blob: 0b49e33178faf2b8c9a21baa1481d2c9d2987ded [file] [log] [blame]
#
# 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 time
from src.controller.core.sample import Sampler
from src.search_space.core.model_params import ModelMicroCfg
class ModelScore:
def __init__(self, model_id, score):
self.model_id = model_id
self.score = score
def __repr__(self):
return "m_{}_s_{}".format(self.model_id, self.score)
# for binary insert
def binary_insert_get_rank(rank_list: list, new_item: ModelScore) -> int:
"""
Insert the new_item to rank_list, then get the rank of it.
:param rank_list:
:param new_item:
:return:
"""
index = search_position(rank_list, new_item)
# search the position to insert into
rank_list.insert(index, new_item)
return index
# O(logN) search the position to insert into
def search_position(rank_list_m: list, new_item: ModelScore):
if len(rank_list_m) == 0:
return 0
left = 0
right = len(rank_list_m) - 1
while left + 1 < right:
mid = int((left + right) / 2)
if rank_list_m[mid].score <= new_item.score:
left = mid
else:
right = mid
# consider the time.
if rank_list_m[right].score <= new_item.score:
return right + 1
elif rank_list_m[left].score <= new_item.score:
return left + 1
else:
return left
class SampleController(object):
"""
Controller control the sample-score flow in the 1st phase.
It records the results in the history.
"""
def __init__(self, search_strategy: Sampler):
# Current ea is better than others.
self.search_strategy = search_strategy
# the large the index, the better the model
self.ranked_models = []
# when simple_score_sum=False, records the model's score of each algorithm,
# use when simple_score_sum=True, record the model's sum score
self.history = {}
def sample_next_arch(self) -> (str, ModelMicroCfg):
"""
Return a generator
:return:
"""
return self.search_strategy.sample_next_arch(self.ranked_models)
def fit_sampler(self, arch_id: str, alg_score: dict, simple_score_sum: bool = False,
is_sync: bool = True, arch_micro=None) -> float:
"""
:param arch_id:
:param alg_score: {alg_name1: score1, alg_name2: score2}
:param simple_score_sum: if simply sum multiple scores (good performing),
or sum over their rank (worse performing)
:return:
"""
if simple_score_sum or len(alg_score.keys()) == 1:
score = self._use_pure_score_as_final_res(arch_id, alg_score)
else:
score = self._use_vote_rank_as_final_res(arch_id, alg_score)
if is_sync:
self.search_strategy.fit_sampler(score)
else:
self.search_strategy.async_fit_sampler(arch_id, arch_micro, score)
return score
def _use_vote_rank_as_final_res(self, model_id: str, alg_score: dict):
"""
:param model_id:
:param alg_score: {alg_name1: score1, alg_name2: score2}
"""
# todo: bug: only all scores' under all arg is greater than previous one, then treat it as greater.
for alg in alg_score:
if alg not in self.history:
self.history[alg] = []
# add model and score to local list
for alg, score in alg_score.items():
binary_insert_get_rank(self.history[alg], ModelScore(model_id, score))
new_rank_score = self._re_rank_model_id(model_id, alg_score)
return new_rank_score
def _use_pure_score_as_final_res(self, model_id: str, alg_score: dict):
# get the key and sum the score of various alg
score_sum_key = "_".join(list(alg_score.keys()))
if score_sum_key not in self.history:
self.history[score_sum_key] = []
final_score = 0
for alg in alg_score:
final_score += float(alg_score[alg])
# insert and get rank
index = binary_insert_get_rank(self.history[score_sum_key], ModelScore(model_id, final_score))
self.ranked_models.insert(index, model_id)
return final_score
def _re_rank_model_id(self, model_id: str, alg_score: dict):
# todo: re-rank everything, to make it self.ranked_models more accurate.
model_new_rank_score = {}
current_explored_models = 0
for alg, score in alg_score.items():
for rank_index in range(len(self.history[alg])):
current_explored_models = len(self.history[alg])
ms_ins = self.history[alg][rank_index]
# rank = index + 1, since index can be 0
if ms_ins.model_id in model_new_rank_score:
model_new_rank_score[ms_ins.model_id] += rank_index + 1
else:
model_new_rank_score[ms_ins.model_id] = rank_index + 1
for ele in model_new_rank_score.keys():
model_new_rank_score[ele] = model_new_rank_score[ele] / current_explored_models
self.ranked_models = [k for k, v in sorted(model_new_rank_score.items(), key=lambda item: item[1])]
new_rank_score = model_new_rank_score[model_id]
return new_rank_score
def get_current_top_k_models(self, k=-1):
"""
The model is already scored by: low -> high
:param k:
:return:
"""
if k == -1:
# retur all models
return self.ranked_models
else:
return self.ranked_models[-k:]
if __name__ == "__main__":
rank_list = []
begin = time.time()
score_list = [1, 2, 3, 1, 2]
for i in range(5):
ms = ModelScore(i, score_list[i])
binary_insert_get_rank(rank_list, ms)
print(rank_list)
print(time.time() - begin)
rank_list = []
begin = time.time()
score_list = [1, 1, 1, 1, 1]
for i in range(5):
ms = ModelScore(i, score_list[i])
binary_insert_get_rank(rank_list, ms)
print(rank_list)
print(time.time() - begin)