Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

922 preprocessor acceleration #1004

Merged
merged 72 commits into from
Dec 11, 2023
Merged

922 preprocessor acceleration #1004

merged 72 commits into from
Dec 11, 2023

Conversation

IIaKyJIuH
Copy link
Collaborator

Fixes to increase preprocessor speed.

Optimized preprocessing directory of the project as well as some core subdirectories with preprocessing utils.

@IIaKyJIuH IIaKyJIuH linked an issue Dec 12, 2022 that may be closed by this pull request
fedot/core/data/data_preprocessing.py Outdated Show resolved Hide resolved
fedot/core/data/data_preprocessing.py Outdated Show resolved Hide resolved
fedot/core/data/data_preprocessing.py Outdated Show resolved Hide resolved
nans_number = is_row_has_nan.sum()
if nans_number > 0 and column_id in categorical_ids:
for column_id in range(number_of_columns):
pd_column = pd.Series(input_data.features[:, column_id], copy=True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потенциально ненужное копирование?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А оно и раньше было - column = np.array(input_data.features[:, column_id])
Ну и потом, без этого копирования тесты падают, мб можно от этого копирования избавиться, но не придумал ещё, как.

fedot/preprocessing/categorical.py Outdated Show resolved Hide resolved
@IIaKyJIuH
Copy link
Collaborator Author

Вот тут скрываются пруфы, почему буст результата не дал

@@ -412,58 +412,42 @@ def _into_numeric_features_transformation_for_predict(self, data: InputData):
features_types[column_id] = NAME_CLASS_FLOAT


def define_column_types(table: np.array):
def define_column_types(table: np.ndarray):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а есть в профалере измерения конкретно по этой функции?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Выглядит это так
image

А есть тут adult_medium.zip

Comment on lines 429 to 386
for column_id in range(n_columns):
current_column = table[:, column_id]

# Check every element in numpy array - it can take a long time!
column_types = list(map(type_ignoring_nans, current_column))
column_types = np.where(pd.isna(current_column), str(type(None)), vto_type(current_column))
unique_column_types = np.unique(column_types)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

может, будет чуть быстрее, если сразу по всей таблице функцию один раз прогнать, а потом уже проитерироваться по колонкам?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот как это выглядит с такой правкой:
image

И вот результаты в pstats
image

Вот файл adult_medium_improved.zip
Получается, что теперь время на векторизацию вообще почти не тратится, с другой стороны, unique метод стал почему-то сразу больше времени занимать ни с того ни с сего.

Copy link
Collaborator

@gkirgizov gkirgizov Dec 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я имел ввиду np.unique прогнать один раз: np.unique(... , axis=1), кроме того можно сразу один раз посчитать количество элементов всех типов np.unique(..., return_counts=True)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

еще пара мыслей:

  • вместо строчек-имен типов может быть эффективнее хранить численные id типов или их int хэш
  • вместо сложной логики подмены nan и писаиваний в массив может быть имеет смысл просто вычесть из числа float-типов число nan значений

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я имел ввиду np.unique прогнать один раз: np.unique(... , axis=1), кроме того можно сразу один раз посчитать количество элементов всех типов np.unique(..., return_counts=True)

Не получится axis задать для dtype=object, поэтому всё ещё внутри цикла это происходит.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

еще пара мыслей:

  • вместо строчек-имен типов может быть эффективнее хранить численные id типов или их int хэш
  • вместо сложной логики подмены nan и писаиваний в массив может быть имеет смысл просто вычесть из числа float-типов число nan значений

С первым пунктом согласен, так и сделал, создав словарь TYPE_TO_ID.
А вот второй пункт вообще, если честно, не понял.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тогда, может, и комментарий выше насчет np.unique сработает для dtype=int ?

Copy link
Collaborator

@gkirgizov gkirgizov Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А вот второй пункт вообще, если честно, не понял.

Я имел ввиду следующее. количество не-nan-значений можно посчитать как (количество всех float-значений) - (количество nan). Это сработает так как nan-значение имеет тип float. А кол-во nan можно посчитать с помощью np.isna. Так можно обойтись без поиндексного присваивания в массив и сравниваний значений типа object -- а это все заметно дольше.

@codecov
Copy link

codecov bot commented Dec 29, 2022

Codecov Report

Attention: 42 lines in your changes are missing coverage. Please review.

Comparison is base (c38c22f) 79.27% compared to head (304e29f) 79.08%.
Report is 3 commits behind head on master.

❗ Current head 304e29f differs from pull request most recent head 0c48f7f. Consider uploading reports for the commit 0c48f7f to get more accurate results

Files Patch % Lines
fedot/api/api_utils/api_data.py 57.50% 17 Missing ⚠️
fedot/preprocessing/data_types.py 92.97% 13 Missing ⚠️
...edot/core/repository/operation_types_repository.py 64.70% 6 Missing ⚠️
fedot/api/main.py 80.00% 1 Missing ⚠️
fedot/core/data/supplementary_data.py 75.00% 1 Missing ⚠️
...on_implementations/models/discriminant_analysis.py 50.00% 1 Missing ⚠️
fedot/core/pipelines/pipeline.py 83.33% 1 Missing ⚠️
fedot/core/repository/json_evaluation.py 93.33% 1 Missing ⚠️
fedot/preprocessing/preprocessing.py 97.43% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1004      +/-   ##
==========================================
- Coverage   79.27%   79.08%   -0.20%     
==========================================
  Files         145      145              
  Lines       10047     9957      -90     
==========================================
- Hits         7965     7874      -91     
- Misses       2082     2083       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@IIaKyJIuH IIaKyJIuH force-pushed the 922-preproc-acceleration branch 3 times, most recently from 6da30b3 to f3befb2 Compare February 15, 2023 08:57
@IIaKyJIuH IIaKyJIuH force-pushed the 922-preproc-acceleration branch 4 times, most recently from bc38230 to 8f05890 Compare February 22, 2023 07:28
@aim-pep8-bot
Copy link

aim-pep8-bot commented Mar 30, 2023

Hello @IIaKyJIuH! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2023-12-11 16:52:39 UTC

@IIaKyJIuH IIaKyJIuH force-pushed the 922-preproc-acceleration branch 2 times, most recently from 29d27a2 to a346643 Compare April 27, 2023 09:08
@IIaKyJIuH IIaKyJIuH force-pushed the 922-preproc-acceleration branch 2 times, most recently from ada3c49 to 177cd28 Compare July 5, 2023 16:33
@IIaKyJIuH
Copy link
Collaborator Author

Новое соотношение по скорости на использование пайплайнов:
image

Это значения получаются усреднением по следующим датасетам на задачу классификации:
['adult', 'airlines', 'Amazon_employee_access', 'Australian', 'bank-marketing', 'blood-transfusion-service-center', 'car', 'christine', 'cnae-9', 'connect-4', 'credit-g', 'fabert', 'helena', 'jannis', 'jasmine', 'jungle_chess_2pcs_raw_endgame_complete', 'kc1', 'kr-vs-kp', 'mfeat-factors', 'nomao', 'numerai286', 'phoneme', 'segment', 'shuttle', 'sylvine', 'vehicle']

То есть моя модификация всё-таки чуть-чуть лучше.
Чёткая проблема виднеется в "среднем" пайплайне, создаю я его для FEDOT и sklearn следующим образом:
image
В ADDITIONAL_PARAMS просто задаётся отключение препроцессинга.

Эквивалентны ли эти пайплайны по логике обработки?

@aPovidlo
Copy link
Collaborator

aPovidlo commented Aug 31, 2023

@IIaKyJIuH
Не совсем понимаю, а зачем сравнивать препроцессинг через работу пайплайнов? Может быть лучше засекать время для препроцессинга каждого датасета по отдельности. Также время обработки препроца было бы полезно добавить в логирование. Еще полезно думаю знать объем занимаемой памяти до и после.

В ADDITIONAL_PARAMS просто задаётся отключение препроцессинга.

А FedotPipe это что?

@kasyanovse kasyanovse self-requested a review November 29, 2023 17:59
need_label = True
break
return need_label
uniques = np.unique(input_data.features[:, categorical_ids].astype(str))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Зачем приводить к str?
  2. В предыдущем варианте учитывалось, что в разных столбцах могут быть одинаковые значения. В новом варианте это не учитывается, хотя по логике должно.
  3. Если отказаться от приведения к str, то надо будет добавить аргумент equal_nan, это должно быть быстрее, чем приводить к str.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем приводить к str?

Так это же категориальные признаки. Думаю, что там могут быть и числа, например, 1, 2, 3 и тд. Наверное, в этом и была идея переводить к str

В предыдущем варианте учитывалось, что в разных столбцах могут быть одинаковые значения. В новом варианте это не учитывается, хотя по логике должно. Если отказаться от приведения к str, то надо будет добавить аргумент equal_nan, это должно быть быстрее, чем приводить к str.

Не понял. Можешь более детальнее?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так это же категориальные признаки. Думаю, что там могут быть и числа, например, 1, 2, 3 и тд. Наверное, в этом и была идея переводить к str

Странно, потому что '1' и 1 в таком случае получатся одной категорией.

Не понял. Можешь более детальнее?

Для нового кода 1 в первом столбце и 1 в любом другом - это одна уникальная категория. В старом коде 1 в первом столбце - это уникальное значение для первого столбца, а 1 во втором - для второго.

@@ -118,11 +115,5 @@ def control_categorical(self, input_data: InputData) -> bool:
"""

categorical_ids, _ = find_categorical_columns(input_data.features)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поиск категориальных столбцов требует времени, поэтому лучше брать индексы категориальных столбцов из input_data либо сохранять их там после определения.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Соглашусь с тобой. Думаю, что можно было бы проследить вызовы этой функции. Сохранение сделано для извлечения незакодированных категориальных признаков, добавлял такой признак в InputData, который сохраняет на одном из этапов предобработки.

Однако думаю, что это мог бы быть оформлен в виде issue и выполнен последующим шагом, а не в этом PR.

Comment on lines +159 to +160
if isinstance(self.train_data, InputData) and self.params.get('use_auto_preprocessing'):
self.train_data = self.data_processor.fit_transform(self.train_data)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А почему только для InputData?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделал так, потому что в MultiModal данных отсутствует supplementary_data. Из-за этого падали тесты. Думаю, что для них нужно сделать как-то по другому, и авто предобработать только если в них содержатся табличные данные. Пока не знаю как это можно лучше всего это сделать.

Comment on lines +535 to +536
if not feature_ids:
return None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Может лучше ошибку кинуть?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Думаю, что None не просто так. Если посмотреть на использование функции, то от нее ожидается такое поведение. Если таких индексов нет, например, категориальных, то и данные должны быть пустыми, то есть None.

Comment on lines +11 to +12
def data_type_is_suitable_for_preprocessing(data: InputData) -> bool:
return data_type_is_table(data) or data_type_is_ts(data) or data_type_is_multi_ts(data)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем нужны эти функции если можно через data_type in List[DataTypesEnum]?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не знаю, это уже было давно так сделано

fedot/core/pipelines/pipeline.py Show resolved Hide resolved
fedot/preprocessing/base_preprocessing.py Outdated Show resolved Hide resolved
fedot/preprocessing/categorical.py Show resolved Hide resolved
fedot/preprocessing/categorical.py Show resolved Hide resolved
fedot/preprocessing/preprocessing.py Outdated Show resolved Hide resolved
fedot/preprocessing/preprocessing.py Outdated Show resolved Hide resolved
fedot/core/data/data_preprocessing.py Show resolved Hide resolved
need_label = True
break
return need_label
uniques = np.unique(input_data.features[:, categorical_ids].astype(str))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Так это же категориальные признаки. Думаю, что там могут быть и числа, например, 1, 2, 3 и тд. Наверное, в этом и была идея переводить к str

Странно, потому что '1' и 1 в таком случае получатся одной категорией.

Не понял. Можешь более детальнее?

Для нового кода 1 в первом столбце и 1 в любом другом - это одна уникальная категория. В старом коде 1 в первом столбце - это уникальное значение для первого столбца, а 1 во втором - для второго.

Copy link
Collaborator

@valer1435 valer1435 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы сделал флаг true по умолчанию. Потому что иначе никто не станет им пользоваться и мы не узнаем насколько изменения хороши в бою

fedot/api/api_utils/api_data.py Show resolved Hide resolved
fedot/api/main.py Outdated Show resolved Hide resolved
@aPovidlo
Copy link
Collaborator

aPovidlo commented Dec 8, 2023

Я бы сделал флаг true по умолчанию. Потому что иначе никто не станет им пользоваться и мы не узнаем насколько изменения хороши в бою

@valer1435 Кажется нам нужно сначала самим также ее протестировать. Хоть я и постарался покрыть код тестами, но хотелось бы увидеть и на качественные изменения, если они есть. Вливаю в мастер, так как сейчас есть актуальные эксперименты с необходимостью такого функционала, на которых и посмотрим. Далее, если нужно будет доработать, внесем изменения и поставим по умолчанию.

@@ -131,3 +132,13 @@ def df_to_html(df: pd.DataFrame, save_path: Union[str, os.PathLike], name: str =
if table.parent.name != 'div':
table = table.wrap(doc.new_tag('div', style='overflow: auto;'))
file.write_text(doc.prettify())


def convert_memory_size(size_bytes):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь бы как-то поинформативнее назвать переменные

try:
return item.strip()
except AttributeError:
# not an str object
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поправь на "not a str object"

@aPovidlo aPovidlo merged commit da94b3e into master Dec 11, 2023
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants