From 86c80487d7bee90d2125bc5cd243f1c1ba87ad8b Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Mon, 8 Jun 2020 22:38:10 +0300 Subject: [PATCH 1/9] increase py version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 03087dc..adc893d 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,6 @@ 'Topic :: Scientific/Engineering :: Artificial Intelligence', 'Topic :: Scientific/Engineering :: Information Analysis', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], ) From 6e2315a7df51609e745f0dd3354424ab9209e524 Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 00:32:04 +0300 Subject: [PATCH 2/9] add new stuff for the release --- .gitignore | 2 + docs/cooking_machine/config_parser.html | 394 +++-- docs/cooking_machine/cubes/base_cube.html | 68 +- .../cubes/controller_cube.html | 1091 ++++++++---- docs/cooking_machine/cubes/cube_creator.html | 34 +- .../cubes/greedy_strategy.html | 38 +- docs/cooking_machine/cubes/index.html | 65 +- .../cubes/perplexity_strategy.html | 46 +- .../cubes/regularizer_cube.html | 105 +- docs/cooking_machine/cubes/strategy.html | 46 +- docs/cooking_machine/dataset.html | 187 +- docs/cooking_machine/dataset_cooc.html | 34 +- docs/cooking_machine/experiment.html | 274 ++- docs/cooking_machine/index.html | 69 +- docs/cooking_machine/model_constructor.html | 46 +- docs/cooking_machine/model_tracking.html | 88 +- docs/cooking_machine/models/base_model.html | 140 +- .../models/base_regularizer.html | 34 +- docs/cooking_machine/models/base_score.html | 478 +++++- .../models/blei_lafferty_score.html | 45 +- .../models/dummy_topic_model.html | 52 +- .../cooking_machine/models/example_score.html | 57 +- docs/cooking_machine/models/frozen_score.html | 77 +- docs/cooking_machine/models/index.html | 88 +- .../models/intratext_coherence_score.html | 137 +- docs/cooking_machine/models/scores.html | 135 +- .../models/scores_wrapper.html | 46 +- .../models/semantic_radius_score.html | 78 +- .../models/thetaless_regularizer.html | 565 +++++- docs/cooking_machine/models/topic_model.html | 506 ++++-- .../models/topic_prior_regularizer.html | 40 +- docs/cooking_machine/pretty_output.html | 84 +- .../recipes/artm_baseline_pipeline.html | 47 +- .../recipes/exploratory_search_pipeline.html | 24 +- docs/cooking_machine/recipes/index.html | 36 +- .../recipes/intratext_coherence_pipeline.html | 62 +- ...ultimodal_exploratory_search_pipeline.html | 285 +++- .../recipes/recipe_wrapper.html | 169 +- docs/cooking_machine/recipes/wntm.html | 82 +- docs/cooking_machine/rel_toolbox_lite.html | 82 +- docs/cooking_machine/routine.html | 233 ++- docs/dataset_manager/api.html | 76 +- docs/dataset_manager/index.html | 14 +- docs/index.html | 33 +- docs/viewers/base_viewer.html | 40 +- docs/viewers/document_cluster.html | 40 +- docs/viewers/index.html | 106 +- docs/viewers/initial_doc_to_topic_viewer.html | 28 +- docs/viewers/spectrum.html | 100 +- docs/viewers/top_documents_viewer.html | 82 +- .../viewers/top_similar_documents_viewer.html | 54 +- docs/viewers/top_tokens_viewer.html | 159 +- docs/viewers/topic_flow_viewer.html | 52 +- docs/viewers/topic_mapping.html | 54 +- topicnet/README-rus.md | 23 - topicnet/bitbucket-pipelines.yml | 2 +- topicnet/cooking_machine/config_parser.py | 173 +- .../cooking_machine/cubes/controller_cube.py | 231 ++- .../cubes/perplexity_strategy.py | 2 +- .../cooking_machine/cubes/regularizer_cube.py | 25 +- topicnet/cooking_machine/dataset.py | 19 +- topicnet/cooking_machine/experiment.py | 28 +- topicnet/cooking_machine/models/__init__.py | 1 + topicnet/cooking_machine/models/base_score.py | 132 +- .../models/blei_lafferty_score.py | 13 +- .../models/dummy_topic_model.py | 3 + .../cooking_machine/models/example_score.py | 13 +- .../models/intratext_coherence_score.py | 6 +- .../cooking_machine/models/scores_wrapper.py | 6 +- .../models/thetaless_regularizer.py | 230 ++- .../cooking_machine/models/topic_model.py | 117 +- .../recipes/artm_baseline_pipeline.py | 16 + .../recipes/exploratory_search_pipeline.py | 4 + .../intratext_coherence_maximization.yml | 1 + .../recipes/intratext_coherence_pipeline.py | 12 + .../multimodal_exploratory_search_pipeline.py | 90 +- .../cooking_machine/recipes/recipe_wrapper.py | 52 +- topicnet/cooking_machine/routine.py | 32 +- topicnet/dataset_manager/api.py | 18 +- topicnet/demos/README.md | 1 + .../demos/Topic-Thetaless-Regularizer.ipynb | 1290 ++++++++++++++ .../Visualizing-Your-Model-Documents.ipynb | 16 +- .../demos/{ => images}/topic_clusters.html | 0 .../demos/topic_thetaless_regularizer.ipynb | 1512 ----------------- topicnet/tests/test_cube_controller.py | 4 +- topicnet/tests/test_cube_utils.py | 5 +- topicnet/tests/test_cubes.py | 155 +- topicnet/tests/test_dataset.py | 1 - topicnet/tests/test_experiment.py | 6 +- topicnet/tests/test_experiment_restore.py | 295 ++++ topicnet/tests/test_experiment_select.py | 5 +- topicnet/tests/test_pipeline.py | 43 +- topicnet/tests/test_topic_model.py | 146 ++ 93 files changed, 7704 insertions(+), 4031 deletions(-) delete mode 100644 topicnet/README-rus.md create mode 100644 topicnet/demos/Topic-Thetaless-Regularizer.ipynb rename topicnet/demos/{ => images}/topic_clusters.html (100%) delete mode 100644 topicnet/demos/topic_thetaless_regularizer.ipynb create mode 100644 topicnet/tests/test_experiment_restore.py diff --git a/.gitignore b/.gitignore index c77c71b..a17a4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ build/ dist/ +topicnet/dataset_manager/*__internals +topicnet/tests/test_data/*__internals diff --git a/docs/cooking_machine/config_parser.html b/docs/cooking_machine/config_parser.html index 85b3f16..84df31b 100644 --- a/docs/cooking_machine/config_parser.html +++ b/docs/cooking_machine/config_parser.html @@ -3,15 +3,15 @@ - + topicnet.cooking_machine.config_parser API documentation - - + + @@ -43,7 +43,7 @@

Module topicnet.cooking_machine.config_parser

Our process consists of three stages: -1) we check the high-level structure using base_schema. +1) we check the high-level structure using BASE_SCHEMA. The presence of each required key is ensured. After this stage we could be sure than we can create a valid model using specified parameters.

@@ -59,7 +59,9 @@

Module topicnet.cooking_machine.config_parser

-Source code + +Expand source code +
"""
 Parsing text file into Experiment instance using strictyaml
 (github.com/crdoconnor/strictyaml/)
@@ -88,7 +90,7 @@ 

Module topicnet.cooking_machine.config_parser

Module topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parserModule topicnet.cooking_machine.config_parser
@@ -655,20 +730,22 @@

Functions

def build_cube_settings(elemtype, elem_args)
-

Parameters

+

Parameters

elemtype : str
name of regularizer
-
elem_args : strictyaml.YAML object
+
elem_args : strictyaml.YAML object
(contains dict inside)

Returns

list of dict
 
-
+
-Source code + +Expand source code +
def build_cube_settings(elemtype, elem_args):
     """
     Parameters
@@ -698,7 +775,7 @@ 

Returns

def build_experiment_environment_from_yaml_config(yaml_string, experiment_id, save_path, force_separate_thread=False)
-

Wraps up parameter extraction and class instances creation +

Wraps up parameter extraction and class instances creation from yaml formatted string together with the method that builds experiment pipeline from given experiment parameters (model, cubes, regularizers, etc)

@@ -711,20 +788,26 @@

Parameters

path to the folder to save experiment logs and models
experiment_id : str
name of the experiment folder
-
force_separate_thread : bool default = False
+
force_separate_thread : bool default = False
experimental feature that packs model training into separate process which is killed upon training completion by default is not used

Returns

-
tuple experiment, dataset instances of corresponding classes from topicnet
+
tuple experiment, dataset instances of corresponding classes from topicnet
 
-
+
-Source code -
def build_experiment_environment_from_yaml_config(yaml_string, experiment_id,
-                                                  save_path, force_separate_thread=False):
+
+Expand source code
+
+
def build_experiment_environment_from_yaml_config(
+    yaml_string,
+    experiment_id,
+    save_path,
+    force_separate_thread=False,
+):
     """
     Wraps up parameter extraction and class instances creation
     from yaml formatted string
@@ -747,13 +830,14 @@ 

Returns

Returns ------- - tuple experiment, dataset instances of corresponding classes from topicnet + """ settings, regs, model, dataset = parse(yaml_string, force_separate_thread) # TODO: handle dynamic addition of regularizers experiment = Experiment(experiment_id=experiment_id, save_path=save_path, topic_model=model) experiment.build(settings) + return experiment, dataset
@@ -761,22 +845,24 @@

Returns

def build_regularizer(elemtype, elem_args, specific_topic_names, background_topic_names)
-

Parameters

+

Parameters

elemtype : str
name of regularizer
elem_args : dict
 
-
parsed : strictyaml.YAML object
+
parsed : strictyaml.YAML object
 

Returns

instance of artm.Regularizer
 
-
+
-Source code + +Expand source code +
def build_regularizer(elemtype, elem_args, specific_topic_names, background_topic_names):
     """
     Parameters
@@ -807,15 +893,17 @@ 

Returns

def build_schema_for_cubes()
-

Returns

+

Returns

dict
each element is str -> strictyaml.Map where key is name of cube, value is a schema used for validation and type-coercion
-
+
-Source code + +Expand source code +
def build_schema_for_cubes():
     """
     Returns
@@ -863,13 +951,15 @@ 

Returns

def build_schema_for_regs()
-

Returns

+

Returns

strictyaml.Map
schema used for validation and type-coercion
-
+
-Source code + +Expand source code +
def build_schema_for_regs():
     """
     Returns
@@ -897,13 +987,15 @@ 

Returns

def build_schema_for_scores()
-

Returns

+

Returns

strictyaml.Map
schema used for validation and type-coercion
-
+
-Source code + +Expand source code +
def build_schema_for_scores():
     """
     Returns
@@ -935,11 +1027,34 @@ 

Returns

return schemas
+
+def build_schema_from_function(func: Callable) -> dict +
+
+
+
+ +Expand source code + +
def build_schema_from_function(func: Callable) -> dict:
+    from docstring_parser import parse as docstring_parse
+
+    func_params = signature(func).parameters
+    func_params_schema = dict()
+
+    for elem in docstring_parse(func.__doc__).params:
+        if elem.arg_name in func_params:
+            key = choose_key(func_params[elem.arg_name])
+            func_params_schema[key] = TYPE_VALIDATORS[elem.type_name]
+
+    return func_params_schema
+
+
def build_schema_from_signature(class_of_object, use_optional=True)
-

Parameters

+

Parameters

class_of_object : class
 
@@ -948,9 +1063,11 @@

Returns

dict
each element is either str -> Validator or Optional(str) -> Validator
-
+
-Source code + +Expand source code +
def build_schema_from_signature(class_of_object, use_optional=True):
     """
     Parameters
@@ -972,7 +1089,7 @@ 

Returns

def build_score(elemtype, elem_args, is_artm_score)
-

Parameters

+

Parameters

elemtype : str
name of score
@@ -983,11 +1100,13 @@

Returns

Returns

-
instance of artm.scores.BaseScore or topicnet.cooking_machine.models.base_score
+
instance of artm.scores.BaseScore or topicnet.cooking_machine.models.base_score
 
-
+
-Source code + +Expand source code +
def build_score(elemtype, elem_args, is_artm_score):
     """
     Parameters
@@ -1013,7 +1132,7 @@ 

Returns

def choose_key(param)
-

Parameters

+

Parameters

param : inspect.Parameter
 
@@ -1022,9 +1141,11 @@

Returns

str or strictyaml.Optional
 
-
+
-Source code + +Expand source code +
def choose_key(param):
     """
     Parameters
@@ -1037,6 +1158,7 @@ 

Returns

""" if param.default is not Parameter.empty: return Optional(param.name) + return param.name
@@ -1044,7 +1166,7 @@

Returns

def choose_validator(param)
-

Parameters

+

Parameters

param : inspect.Parameter
 
@@ -1053,9 +1175,11 @@

Returns

instance of strictyaml.Validator
 
-
+
-Source code + +Expand source code +
def choose_validator(param):
     """
     Parameters
@@ -1076,6 +1200,7 @@ 

Returns

return Str() if param.name in ARTM_TYPES: return ARTM_TYPES[param.name] + return Any()
@@ -1083,7 +1208,7 @@

Returns

def handle_special_cases(elem_args, kwargs)
-

In-place fixes kwargs, handling special cases and shortcuts +

In-place fixes kwargs, handling special cases and shortcuts (only strategy for now) Parameters


@@ -1092,9 +1217,11 @@

Returns

 
kwargs : dict
 
-
+
-Source code + +Expand source code +
def handle_special_cases(elem_args, kwargs):
     """
     In-place fixes kwargs, handling special cases and shortcuts
@@ -1126,9 +1253,11 @@ 

Returns

def is_key_in_schema(key, schema)
-
+
-Source code + +Expand source code +
def is_key_in_schema(key, schema):
     if key in schema:
         return True
@@ -1139,10 +1268,10 @@ 

Returns

-def parse(yaml_string, force_separate_thread=False, dataset_class=) +def parse(yaml_string: str, force_separate_thread: bool = False, dataset_class: Type[Dataset] = topicnet.cooking_machine.dataset.Dataset)
-

Parameters

+

Parameters

yaml_string : str
 
@@ -1161,9 +1290,11 @@

Returns

 
dataset : Dataset
 
-
+
-Source code + +Expand source code +
def parse(
     yaml_string: str,
     force_separate_thread: bool = False,
@@ -1182,8 +1313,9 @@ 

Returns

regularizers: list topic_model: TopicModel dataset: Dataset + """ - parsed = dirty_load(yaml_string, base_schema, allow_flow_style=True) + parsed = dirty_load(yaml_string, BASE_SCHEMA, allow_flow_style=True) specific_topic_names, background_topic_names = create_default_topics( parsed.data["topics"]["specific_topics"], @@ -1192,12 +1324,22 @@

Returns

revalidate_section(parsed, "stages") revalidate_section(parsed, "regularizers") + if "scores" in parsed: revalidate_section(parsed, "scores") - cube_settings = [] + dataset = dataset_class( + data_path=parsed.data["model"]["dataset_path"], + keep_in_memory=parsed.data["model"].get("keep_in_memory", True), + internals_folder_path=parsed.data["model"].get("internals_folder_path", None), + ) + filter_parameters = parsed.data["model"].get( + KEY_DICTIONARY_FILTER_PARAMETERS, dict() + ) - dataset = dataset_class(parsed.data["model"]["dataset_path"]) + if len(filter_parameters) > 0: + filtered_dictionary = dataset.get_dictionary().filter(**filter_parameters) + dataset._cached_dict = filtered_dictionary modalities_to_use = parse_modalities_data(parsed) @@ -1216,11 +1358,12 @@

Returns

topic_model = TopicModel(model) _add_parsed_scores(parsed, topic_model) + cube_settings = list() + for stage in parsed['stages']: for elemtype, elem_args in stage.items(): settings = build_cube_settings(elemtype.data, elem_args) - if force_separate_thread: - settings[elemtype]["separate_thread"] = False + settings[elemtype]["separate_thread"] = force_separate_thread cube_settings.append(settings) return cube_settings, regularizers, topic_model, dataset
@@ -1230,9 +1373,11 @@

Returns

def parse_modalities_data(parsed)
-
+
-Source code + +Expand source code +
def parse_modalities_data(parsed):
     has_modalities_to_use = is_key_in_schema("modalities_to_use", parsed["model"])
     has_weights = is_key_in_schema("modalities_weights", parsed["model"])
@@ -1240,7 +1385,7 @@ 

Returns

# exactly one should be specified if has_modalities_to_use == has_weights: - raise ValueError(f"Either 'modalities_to_use' or 'modalities_weights' should be specified") + raise ValueError("Either 'modalities_to_use' or 'modalities_weights' should be specified") if has_weights: modalities_to_use = list(parsed["model"]["modalities_weights"].data) @@ -1261,7 +1406,7 @@

Returns

def preprocess_parameters_for_cube_creator(elem_args)
-

This function does two things: +

This function does two things: 1) convert class_ids from name: class_ids@text, values: [0, 1, 2, 3] to @@ -1269,16 +1414,18 @@

Returns

2) type conversion for "values" field.

Parameters

-
elem_args : strictyaml.YAML object
+
elem_args : strictyaml.YAML object
(contains dict inside)

Returns

new_elem_args : dict
 
-
+
-Source code + +Expand source code +
def preprocess_parameters_for_cube_creator(elem_args):
     """
     This function does two things:
@@ -1315,16 +1462,18 @@ 

Returns

def revalidate_section(parsed, section)
-

Perofrms in-place type coercion and validation

+

Perofrms in-place type coercion and validation

Parameters

-
parsed : strictyaml.YAML object
+
parsed : strictyaml.YAML object
(half-parsed, half-validated chunk of config)
section : str
 
-
+
-Source code + +Expand source code +
def revalidate_section(parsed, section):
     """
     Perofrms in-place type coercion and validation
@@ -1359,9 +1508,11 @@ 

Parameters

def wrap_in_map(dictionary)
-
+
-Source code + +Expand source code +
def wrap_in_map(dictionary):
     could_be_empty = all(isinstance(key, Optional) for key in dictionary)
     if could_be_empty:
@@ -1393,6 +1544,7 @@ 

Index

  • build_schema_for_cubes
  • build_schema_for_regs
  • build_schema_for_scores
  • +
  • build_schema_from_function
  • build_schema_from_signature
  • build_score
  • choose_key
  • @@ -1410,7 +1562,7 @@

    Index

    diff --git a/docs/cooking_machine/cubes/base_cube.html b/docs/cooking_machine/cubes/base_cube.html index 08fc64c..9c2787a 100644 --- a/docs/cooking_machine/cubes/base_cube.html +++ b/docs/cooking_machine/cubes/base_cube.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes.base_cube API documentation - - + + @@ -21,7 +21,9 @@

    Module topicnet.cooking_machine.cubes.base_cube
    -Source code + +Expand source code +
    import os
     from tqdm import tqdm
     import warnings
    @@ -384,7 +386,7 @@ 

    Functions

    def check_experiment_existence(topic_model)
    -

    Checks if topic_model has experiment.

    +

    Checks if topic_model has experiment.

    Parameters

    topic_model : TopicModel
    @@ -394,9 +396,11 @@

    Returns

    bool
    True if experiment exists, in other case False.
    -
    +
    -Source code + +Expand source code +
    def check_experiment_existence(topic_model):
         """
         Checks if topic_model has experiment.
    @@ -421,9 +425,11 @@ 

    Returns

    def get_from_queue_till_fail(queue, error_message='')
    -
    +
    -Source code + +Expand source code +
    def get_from_queue_till_fail(queue,  error_message='',):
         return queue.get()
    @@ -432,9 +438,11 @@

    Returns

    def put_to_queue(queue, puttable)
    -
    +
    -Source code + +Expand source code +
    def put_to_queue(queue, puttable):
         queue.put(puttable)
    @@ -443,9 +451,11 @@

    Returns

    def retrieve_score_for_strategy(score_name=None)
    -
    +
    -Source code + +Expand source code +
    def retrieve_score_for_strategy(score_name=None):
         if not score_name:
             score_name = 'PerplexityScore@all'
    @@ -468,7 +478,7 @@ 

    Classes

    (num_iter, action=None, reg_search='grid', strategy=None, tracked_score_function=None, verbose=False, separate_thread=True)
    -

    Abstract class for all cubes.

    +

    Abstract class for all cubes.

    Initialize stage. Checks params and update .parameters attribute.

    Parameters

    @@ -489,9 +499,11 @@

    Parameters

    visualization flag
    separate_thread : bool
    will train models inside a separate thread if True
    -
    +
    -Source code + +Expand source code +
    class BaseCube:
         """
         Abstract class for all cubes.
    @@ -785,9 +797,9 @@ 

    Parameters

    Subclasses

    Methods

    @@ -795,12 +807,12 @@

    Methods

    def apply(self, topic_model, one_cube_parameter, dictionary=None, model_id=None)
    -

    "apply" method changes topic_model in way that is defined by one_cube_parameter.

    +

    "apply" method changes topic_model in way that is defined by one_cube_parameter.

    Parameters

    topic_model : TopicModel
    topic model
    -
    one_cube_parameter : optional
    +
    one_cube_parameter : optional
    parameters of one experiment
    dictionary : dict
    dictionary so that the it can be used @@ -808,9 +820,11 @@

    Parameters

    model_id : str
    id of created model if necessary (Default value = None)
    -

    Returns

    +

    Returns

    -Source code + +Expand source code +
    def apply(self, topic_model, one_cube_parameter, dictionary=None, model_id=None):
         """
         "apply" method changes topic_model in way that is defined by one_cube_parameter.
    @@ -838,15 +852,17 @@ 

    Returns

    def get_jsonable_from_parameters(self)
    -

    Transform self.parameters to something that can be downloaded as json.

    +

    Transform self.parameters to something that can be downloaded as json.

    Parameters

    Returns

    -
    optional
    +
    optional
    something jsonable
    -
    +
    -Source code + +Expand source code +
    def get_jsonable_from_parameters(self):
         """
         Transform self.parameters to something that can be downloaded as json.
    @@ -902,7 +918,7 @@ 

    -

    Generated by pdoc 0.6.3.

    +

    Generated by pdoc 0.8.1.

    diff --git a/docs/cooking_machine/cubes/controller_cube.html b/docs/cooking_machine/cubes/controller_cube.html index 11c70ff..4488146 100644 --- a/docs/cooking_machine/cubes/controller_cube.html +++ b/docs/cooking_machine/cubes/controller_cube.html @@ -3,15 +3,15 @@ - + topicnet.cooking_machine.cubes.controller_cube API documentation - - + + @@ -21,104 +21,125 @@

    Module topicnet.cooking_machine.cubes.controller_cube

    -

    Allows to add ControllerAgent (with unknown parameters) to the model, which enables user to +

    Allows to add ControllerAgent (with unknown parameters) to the model, which enables user to change tau during the _fit method.

    parameters is a dict with four fields:

    Fields

    -
    -
    reg_name : str
    -
    The name of regularizer. We want to change the tau coefficient of it during training -Note that only one of ("reg_name", "regularizer") should be provided
    -
    regularizer : artm.regularizer.Regularizer
    -
    Regularizer object (if we want to add non-existing regularizer to the model) -Note that only one of ("reg_name", "regularizer") should be provided
    -
    score_to_track : str
    -
    -

    The name of metric which we will track. +

    reg_name: str +The name of regularizer. We want to change the tau coefficient of it during training +Note that only one of ("reg_name", "regularizer") should be provided +regularizer: artm.regularizer.Regularizer +Regularizer object (if we want to add non-existing regularizer to the model) +Note that only one of ("reg_name", "regularizer") should be provided +score_to_track: str +The name of metric which we will track. We assume that if that metric is 'sort of decreasing', then everything is OK and we are allowed to change tau coefficient further; otherwise we revert back to the last "safe" value and stop

    -

    More formal definition of "sort of decreasing": if we divide a curve into two parts like so:

    -
    ##################################### 
    -#. . . .. . . . ..  . .. . .  ... . # 
    -#%. . .  . . . .  .. . . . . .  . ..# 
    -#:t . . . . . . . . . . . . . . .  .# 
    -# t: . . . . . . . . . . . . . . ...# 
    -#. %. . . . . . . . . . . . . . .  .# 
    -#. :t. . . . . . . . .  .  . . . . .# 
    -#.. ;; . .  . . . .  . . . .  . . ..# 
    -#  ..t..  . .  . . . . . . . . . . .# 
    -#. . :t .. . . .  . . . . . . . . ..# 
    -#. .. t: . . . . . . . . . . . . . .# 
    -#.   ..S: . . . . . . . . . . . . ..# 
    -#. . . .:;: . . . . .  . . . . . . .# 
    -#. . .  . :;;  . . . . . . . . . . .# 
    -#. . . . .. :%.      nmmMMmmn   .  .# 
    -# .   . .  . .tt%.ztttt"' '""ttttttt# 
    -#. . .    . . . '"' . . . . . . . . # 
    -##################################### 
    -|                |                  | 
    -|   left part    |                  | 
    -           global minimum           | 
    -                 |     right part   |
    +
    'sort of decreasing' performs best with <code>PerplexityScore</code>,
    +and all scores which behave like perplexity
    +(nonnegative, and which should decrease when a model gets better).
    +If you want to track a different kind of score,
    +it is recommended to use <code>score\_controller</code> parameter
    +
    +More formal definition of "sort of decreasing":
    +if we divide a curve into two parts like so:
    +
    +
    +    ##################################### 
    +    #. . . .. . . . ..  . .. . .  ... . # 
    +    #%. . .  . . . .  .. . . . . .  . ..# 
    +    #:t . . . . . . . . . . . . . . .  .# 
    +    # t: . . . . . . . . . . . . . . ...# 
    +    #. %. . . . . . . . . . . . . . .  .# 
    +    #. :t. . . . . . . . .  .  . . . . .# 
    +    #.. ;; . .  . . . .  . . . .  . . ..# 
    +    #  ..t..  . .  . . . . . . . . . . .# 
    +    #. . :t .. . . .  . . . . . . . . ..# 
    +    #. .. t: . . . . . . . . . . . . . .# 
    +    #.   ..S: . . . . . . . . . . . . ..# 
    +    #. . . .:;: . . . . .  . . . . . . .# 
    +    #. . .  . :;;  . . . . . . . . . . .# 
    +    #. . . . .. :%.      nmmMMmmn   .  .# 
    +    # .   . .  . .tt%.ztttt"' '""ttttttt# 
    +    #. . .    . . . '"' . . . . . . . . # 
    +    ##################################### 
    +    |                |                  | 
    +    |   left part    |                  | 
    +               global minimum           | 
    +                     |     right part   |
    +
    +then the right part is no higher than 5% of global minimum
    +(you can change 5% if you like by adjusting <code>fraction\_threshold</code> parameter)
    +
    +If <code>score\_to\_track</code> is None and <code>score\_controller</code> is None,
    +then <code><a title="topicnet.cooking_machine.cubes.controller_cube.ControllerAgent" href="#topicnet.cooking_machine.cubes.controller_cube.ControllerAgent">ControllerAgent</a></code> will never stop
    +(useful for e.g. decaying coefficients)
     
    -

    then the right part is no higher than 5% of global minimum -(you can change 5% if you like by adjusting fraction_threshold -in is_score_out_of_control() function)

    -

    If score_to_track is None, then ControllerAgent will never stop -(useful for e.g. decaying coefficients)

    -
    -
    tau_converter : str or callable
    -
    -

    Notably, def-style functions and lambda functions are allowed +

    fraction_threshold: float +Threshold to control a score by 'sort of decreasing' metric +score_controller: BaseScoreController +Custom score controller +In case of 'sort of decreasing' is not proper to control score, +you are able to create custom Score Controller +inherited from BaseScoreController. +tau_converter: str or callable +Notably, def-style functions and lambda functions are allowed If it is function, then it should accept four arguments: (initial_tau, prev_tau, cur_iter, user_value) For example:

    -
    >> lambda initial_tau, prev_tau, cur_iter, user_value:
    ->>     initial_tau if cur_iter % 2 == 0 else 0
    -
    -

    (Note that experiment description might display lambda functions incorrectly; -Try to keep them to a single line or use def-style functions instead)

    -
    >> def func(initial_tau, prev_tau, cur_iter, user_value):
    ->>     relu_grower = user_value * (cur_iter - 8) if cur_iter > 8 else 0
    ->>     return 0 if cur_iter % 2 else relu_grower
    +
        >> lambda initial_tau, prev_tau, cur_iter, user_value:
    +    >>     initial_tau if cur_iter % 2 == 0 else 0
    +
    +(Note that experiment description might display lambda functions incorrectly;
    + Try to keep them to a single line or use def-style functions instead)
    +
    +    >> def func(initial_tau, prev_tau, cur_iter, user_value):
    +    >>     relu_grower = user_value * (cur_iter - 8) if cur_iter > 8 else 0
    +    >>     return 0 if cur_iter % 2 else relu_grower
    +
    +If it is a string, then it should be an expression consisting of numbers, operations
    +    and variables (four are allowed: <code>initial\_tau, prev\_tau, cur\_iter, user\_value</code>)
    +For example:
    +
    +`>> "initial_tau * ((cur_iter + 1) % 2)"`
    +
    +or
    +
    +`>> "prev_tau * user_value"`
     
    -

    If it is a string, then it should be an expression consisting of numbers, operations -and variables (four are allowed: initial_tau, prev_tau, cur_iter, user_value) -For example:

    -

    >> "initial_tau * ((cur_iter + 1) % 2)"

    -

    or

    -

    >> "prev_tau * user_value"

    -
    -
    user_value_grid : list of numeric
    -
    -

    Values for user_value variable +

    user_value_grid: list of numeric +Values for user_value variable When writing tau_converter, you can use user_value variable.

    -

    For example:

    -
    >> tau_converter: "prev_tau * user_value"
    ->> user_value_grid: [1, 0.99, 0.95, 0.90, 0.80, 0.5]
    -
    -

    (I know that tau should decay exponentially, but I'm unsure of exact half-life)

    -
    >> tau_converter: "prev_tau + user_value"
    ->> user_value_grid: [50, 100, 150, 200, 250]
    -
    -

    (I know that tau should increase linearly, but I'm unsure of exact speed)

    -
    >> def func(initial_tau, prev_tau, cur_iter, user_value):
    ->>     new_tau = 50 * (cur_iter - user_value) if cur_iter > user_value else 0
    ->>     return new_tau
    ->> tau_converter: func
    ->> user_value_grid: [10, 15, 20, 25, 30]
    +
    For example:
    +
    +    >> tau_converter: "prev_tau * user_value"
    +    >> user_value_grid: [1, 0.99, 0.95, 0.90, 0.80, 0.5]
    +
    +(I know that tau should decay exponentially, but I'm unsure of exact half-life)
    +
    +    >> tau_converter: "prev_tau + user_value"
    +    >> user_value_grid: [50, 100, 150, 200, 250]
    +
    +(I know that tau should increase linearly, but I'm unsure of exact speed)
    +
    +    >> def func(initial_tau, prev_tau, cur_iter, user_value):
    +    >>     new_tau = 50 * (cur_iter - user_value) if cur_iter > user_value else 0
    +    >>     return new_tau
    +    >> tau_converter: func
    +    >> user_value_grid: [10, 15, 20, 25, 30]
    +
    +(Tau should start with zero, then increase linearly. I don't know when to start this process)
     
    -

    (Tau should start with zero, then increase linearly. I don't know when to start this process)

    -
    -
    max_iter : numeric
    -
    Optional (default value is num_iter specified for cube) +

    max_iter: numeric +Optional (default value is num_iter specified for cube) Agent will stop changing tau after max_iters iterations max_iters could be float("NaN") and float("inf") values: -that way agent will continue operating even outside this RegularizationControllerCube

    -
    +that way agent will continue operating even outside this RegularizationControllerCube

    -Source code + +Expand source code +
    """
     Allows to add `ControllerAgent` (with unknown parameters) to the model, which enables user to
     change `tau` during the `_fit` method.
    @@ -140,7 +161,14 @@ 

    Fields

    and we are allowed to change tau coefficient further; otherwise we revert back to the last "safe" value and stop - More formal definition of "sort of decreasing": if we divide a curve into two parts like so: + 'sort of decreasing' performs best with `PerplexityScore`, + and all scores which behave like perplexity + (nonnegative, and which should decrease when a model gets better). + If you want to track a different kind of score, + it is recommended to use `score_controller` parameter + + More formal definition of "sort of decreasing": + if we divide a curve into two parts like so: ##################################### @@ -167,12 +195,18 @@

    Fields

    | right part | then the right part is no higher than 5% of global minimum - (you can change 5% if you like by adjusting `fraction_threshold` - in `is_score_out_of_control` function) + (you can change 5% if you like by adjusting `fraction_threshold` parameter) - If score_to_track is None, then `ControllerAgent` will never stop + If `score_to_track` is None and `score_controller` is None, + then `ControllerAgent` will never stop (useful for e.g. decaying coefficients) - +fraction_threshold: float + Threshold to control a score by 'sort of decreasing' metric +score_controller: BaseScoreController + Custom score controller + In case of 'sort of decreasing' is not proper to control score, + you are able to create custom Score Controller + inherited from `BaseScoreController`. tau_converter: str or callable Notably, def-style functions and lambda functions are allowed If it is function, then it should accept four arguments: @@ -230,58 +264,112 @@

    Fields

    that way agent will continue operating even outside this `RegularizationControllerCube` """ # noqa: W291 -from .base_cube import BaseCube -from ..rel_toolbox_lite import count_vocab_size, handle_regularizer - -import numexpr as ne import warnings -from dill.source import getsource from copy import deepcopy +from dataclasses import dataclass +from numbers import Number +from typing import ( + Callable, + List, + Optional, + Union, +) + +import numexpr as ne import numpy as np +from dill.source import getsource +from .base_cube import BaseCube +from ..models.base_regularizer import BaseRegularizer +from ..rel_toolbox_lite import count_vocab_size, handle_regularizer W_HALT_CONTROL = "Process of dynamically changing tau was stopped at {} iteration" W_MAX_ITERS = "Maximum number of iterations is exceeded; turning off" -def is_score_out_of_control(model, score_name, fraction_threshold=0.05): - """ - Returns True if score isn't 'sort of decreasing' anymore. +@dataclass +class OutOfControlAnswer: + answer: bool + error_message: Optional[str] = None - See docstring for RegularizationControllerCube for details - Parameters - ---------- - model : TopicModel - score_name : str or None - fraction_threshold : float +class BaseScoreController: + def __init__(self, score_name): + self.score_name = score_name - Returns - ------- - bool + def get_score_values(self, model): + if self.score_name not in model.scores: # case of None is handled here as well + return None + + vals = model.scores[self.score_name] + + if len(vals) == 0: + return None + + return vals + def __call__(self, model): + values = self.get_score_values(model) + + if values is None: + return False + + try: + out_of_control_result = self.is_out_of_control(values) + except Exception as ex: + raise ValueError( + f"An error occurred while controlling {self.score_name}!" + f" Message: {ex}. Score values: {values}" + ) + + if out_of_control_result.error_message is not None: + warnings.warn(out_of_control_result.error_message) + + return out_of_control_result.answer + + def is_out_of_control(self, values: List[float]) -> OutOfControlAnswer: + raise NotImplementedError + + +class PerplexityScoreController(BaseScoreController): + """ + Controller is proper to control the Perplexity score. + For others, please ensure for yourself. """ + DEFAULT_FRACTION_THRESHOLD = 0.05 - if score_name not in model.scores: # case of None is handled here as well - return False + def __init__(self, score_name, fraction_threshold=DEFAULT_FRACTION_THRESHOLD): + super().__init__(score_name) + self.fraction_threshold = fraction_threshold - vals = model.scores[score_name] - if len(vals) == 0: - return False + def is_out_of_control(self, values: List[float]): + idxmin = np.argmin(values) - idxmin = np.argmin(vals) + if idxmin == len(values): # score is monotonically decreasing + return False - if idxmin == len(vals): # score is monotonically decreasing - return False - maxval = max(vals[idxmin:]) - minval = vals[idxmin] - answer = ((maxval - minval)/abs(minval) - 1.0) > fraction_threshold - if answer: - msg = (f"Score {score_name} is too high: during training the value {maxval}" - f" passed a treshold of {(1 + fraction_threshold) * minval}" - f" (estimate is based on {idxmin} iteration)") - warnings.warn(msg) - return answer + right_maxval = max(values[idxmin:]) + minval = values[idxmin] + + if minval <= 0: + raise ValueError( + f'Score "{self.score_name}" has min_value = {minval} which is <= 0.' + f' This control scheme is using to control scores acting like Perplexity.' + f' Ensure you control the Perplexity score or write your own controller!' + ) + + answer = (right_maxval - minval) / minval > self.fraction_threshold + + if answer: + return OutOfControlAnswer( + answer=answer, + error_message=( + f"Score {self.score_name} is too high!" + f" Right max value: {right_maxval}, min value: {minval}" + ), + ) + + return OutOfControlAnswer(answer=answer) class ControllerAgent: @@ -293,8 +381,10 @@

    Fields

    Each agent is described by: * reg_name: the name of regularizer having `tau` which needs to be changed - * score_to_track: score providing control of the callback execution * tau_converter: function or string describing how to get new `tau` from old `tau` + * score_to_track: score name providing control of the callback execution + * fraction_threshold: threshold to control score_to_track + * score_controller: custom score controller providing control of the callback execution * local_dict: dictionary containing values of several variables, most notably, `user_value` * is_working: @@ -304,37 +394,125 @@

    Fields

    See top-level docstring for details. """ - def __init__(self, reg_name, score_to_track, tau_converter, max_iters, local_dict=None): + + def __init__( + self, + reg_name: str, + tau_converter: Callable or str, + max_iters: int or float, + score_to_track: Union[str, List[str], None] = None, + fraction_threshold: Union[float, List[float], None] = None, + score_controller: Union[BaseScoreController, List[BaseScoreController], None] = None, + local_dict: dict = None): """ Parameters ---------- - reg_name : str - score_to_track : str, list of str or None - tau_converter : callable or str - local_dict : dict - max_iters : int or float - Agent will stop changing tau after `max_iters` iterations + reg_name + tau_converter + max_iters + Agent will stop changing tau after `max_iters` iterations, `max_iters` could be `float("NaN")` and `float("inf")` values: that way agent will continue operating even outside this `RegularizationControllerCube` + score_to_track + Name of score to track. + Please, use this definition to track only scores of type PerplexityScore. + In other cases we recommend you to write you own ScoreController + fraction_threshold + Uses to define threshold to control PerplexityScore + Default value is 0.05. + If `fraction_threshold` is a list, it should be of the same length, as `score_to_track`. + score_controller + Score controller or controllers. + One can use this parameter for scores other than Perplexity + (or other scores that behave like Perplexity). + This is a more flexible and customizable way to control scores. + local_dict """ if local_dict is None: local_dict = dict() self.reg_name = reg_name self.tau_converter = tau_converter - if isinstance(score_to_track, list): - self.score_to_track = score_to_track - elif isinstance(score_to_track, str): - self.score_to_track = [score_to_track] - else: - self.score_to_track = [] + + scores_to_track = self._validate_score_to_track(score_to_track) + fraction_thresholds = self._validate_fraction_threshold( + fraction_threshold, required_length=len(scores_to_track) + ) + + assert len(scores_to_track) == len(fraction_thresholds) + + perplexity_like_score_controllers = [ + PerplexityScoreController(name, threshold) + for (name, threshold) in zip(scores_to_track, fraction_thresholds) + ] + + self.score_controllers = list() + self.score_controllers.extend(perplexity_like_score_controllers) + self.score_controllers.extend( + self._validate_score_controller(score_controller) + ) self.is_working = True self.local_dict = local_dict self.tau_history = [] self.max_iters = max_iters + @staticmethod + def _validate_score_to_track( + score_to_track: Union[str, List[str], None]) -> List[str]: + + if isinstance(score_to_track, list): + return score_to_track + if score_to_track is None: + return list() + if isinstance(score_to_track, str): + return [score_to_track] + + raise TypeError(f'Wrong type of `score_to_track`: "{type(score_to_track)}"!') + + @staticmethod + def _validate_fraction_threshold( + fraction_threshold: Union[float, List[float], None], + required_length: int, + ) -> List[float]: + + if fraction_threshold is None: + return [PerplexityScoreController.DEFAULT_FRACTION_THRESHOLD] * required_length + if isinstance(fraction_threshold, Number): + return [float(fraction_threshold)] * required_length + + if not isinstance(fraction_threshold, list): + raise TypeError( + f'Wrong type of `fraction_threshold`: "{type(fraction_threshold)}"!' + ) + + if len(fraction_threshold) != required_length: + raise ValueError( + f'Wrong length of `fraction_threshold`: {len(fraction_threshold)}!' + f' Expected the length to be equal to {required_length}.' + ) + + return fraction_threshold + + @staticmethod + def _validate_score_controller( + score_controller: Union[BaseScoreController, List[BaseScoreController], None] + ) -> List[BaseScoreController]: + + if score_controller is None: + return list() + + elif isinstance(score_controller, BaseScoreController): + return [score_controller] + + elif (not isinstance(score_controller, list) or not all( + isinstance(score, BaseScoreController) for score in score_controller)): + raise TypeError(f'Wrong type of `score_controller`: "{type(score_controller)}"!') + + else: + return score_controller + def _convert_tau(self): """ """ if isinstance(self.tau_converter, str): @@ -365,7 +543,7 @@

    Fields

    Note that zero means "cube just started", not "the model is brand new" """ - current_tau = model.regularizers[self.reg_name].tau + current_tau = model.get_regularizer(self.reg_name).tau self.tau_history.append(current_tau) self.local_dict["prev_tau"] = current_tau self.local_dict["cur_iter"] = cur_iter @@ -379,14 +557,14 @@

    Fields

    if self.is_working: should_stop = any( - is_score_out_of_control(model, score) for score in self.score_to_track + score_controller(model) for score_controller in self.score_controllers ) if should_stop: warnings.warn(W_HALT_CONTROL.format(len(self.tau_history))) self.is_working = False - model.regularizers[self.reg_name].tau = self._find_safe_tau() + model.get_regularizer(self.reg_name).tau = self._find_safe_tau() else: - model.regularizers[self.reg_name].tau = self._convert_tau() + model.get_regularizer(self.reg_name).tau = self._convert_tau() class RegularizationControllerCube(BaseCube): @@ -404,26 +582,33 @@

    Fields

    regularizers params each dict should contain the following fields: ("reg_name" or "regularizer"), - "score_to_track" (optional), "tau_converter", + "score_to_track" (optional), + "fraction_threshold" (optional), + "score_controller" (optional), "user_value_grid" See top-level docstring for details. Examples: >> {"regularizer": artm.regularizers.<...>, - >> "score_to_track": "PerplexityScore@all", >> "tau_converter": "prev_tau * user_value", + >> "score_to_track": "PerplexityScore@all", + >> "fraction_threshold": 0.1, >> "user_value_grid": [0.5, 1, 2]} ----------- >> {"reg_name": "decorrelator_for_ngramms", - >> "score_to_track": None, >> "tau_converter": ( >> lambda initial_tau, prev_tau, cur_iter, user_value: >> initial_tau * (cur_iter % 2) + user_value >> ) + >> "score_to_track": None, + >> "fraction_threshold": None, + >> "score_controller": [ + >> PerplexityScoreController("PerplexityScore@all", 0.1) + >> ], >> "user_value_grid": [0, 1]} reg_search : str @@ -450,9 +635,9 @@

    Fields

    separate_thread=separate_thread) self._relative = use_relative_coefficients self.data_stats = None - self.raw_parameters = parameters if isinstance(parameters, dict): parameters = [parameters] + self.raw_parameters = parameters self._convert_parameters(parameters) def _convert_parameters(self, all_parameters): @@ -513,22 +698,29 @@

    Fields

    for (agent_blueprint_template, field_name, current_user_value) in one_model_parameter: agent_blueprint = dict(agent_blueprint_template) - if agent_blueprint["reg_name"] is None: - regularizer = agent_blueprint["regularizer"] - new_regularizer = deepcopy(regularizer) - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) - agent_blueprint["reg_name"] = new_regularizer.name - else: - if agent_blueprint['reg_name'] not in new_model.regularizers.data: + if agent_blueprint.get("reg_name") is not None: + reg_name = agent_blueprint['reg_name'] + + if reg_name not in new_model.all_regularizers: error_msg = (f"Regularizer {agent_blueprint['reg_name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif agent_blueprint.get("regularizer") is not None: + regularizer = agent_blueprint["regularizer"] + new_regularizer = deepcopy(regularizer) + if isinstance(regularizer, BaseRegularizer): + new_model.custom_regularizers[new_regularizer.name] = new_regularizer + else: # classic bigARTM regularizer, attempt to relativize it's coefficients + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) + agent_blueprint["reg_name"] = new_regularizer.name + else: + raise ValueError("Either 'reg_name' or 'regularizer' should be set") agent_blueprint['local_dict']['user_value'] = current_user_value # ControllerAgent needs only reg_name in constructor agent_blueprint.pop("regularizer") @@ -562,88 +754,114 @@

    Fields

    -

    Functions

    +
    +
    +

    Classes

    -
    -def is_score_out_of_control(model, score_name, fraction_threshold=0.05) +
    +class BaseScoreController +(score_name)
    -

    Returns True if score isn't 'sort of decreasing' anymore.

    -

    See docstring for RegularizationControllerCube for details

    -

    Parameters

    -
    -
    model : TopicModel
    -
     
    -
    score_name : str or None
    -
     
    -
    fraction_threshold : float
    -
     
    -
    -

    Returns

    -
    -
    bool
    -
     
    -
    +
    -Source code -
    def is_score_out_of_control(model, score_name, fraction_threshold=0.05):
    -    """
    -    Returns True if score isn't 'sort of decreasing' anymore.
    +
    +Expand source code
    +
    +
    class BaseScoreController:
    +    def __init__(self, score_name):
    +        self.score_name = score_name
     
    -    See docstring for RegularizationControllerCube for details
    +    def get_score_values(self, model):
    +        if self.score_name not in model.scores:  # case of None is handled here as well
    +            return None
     
    -    Parameters
    -    ----------
    -    model : TopicModel
    -    score_name : str or None
    -    fraction_threshold : float
    +        vals = model.scores[self.score_name]
     
    -    Returns
    -    -------
    -    bool
    +        if len(vals) == 0:
    +            return None
     
    -    """
    +        return vals
     
    -    if score_name not in model.scores:  # case of None is handled here as well
    -        return False
    +    def __call__(self, model):
    +        values = self.get_score_values(model)
     
    -    vals = model.scores[score_name]
    -    if len(vals) == 0:
    -        return False
    +        if values is None:
    +            return False
     
    -    idxmin = np.argmin(vals)
    +        try:
    +            out_of_control_result = self.is_out_of_control(values)
    +        except Exception as ex:
    +            raise ValueError(
    +                f"An error occurred while controlling {self.score_name}!"
    +                f" Message: {ex}. Score values: {values}"
    +            )
     
    -    if idxmin == len(vals):  # score is monotonically decreasing
    -        return False
    -    maxval = max(vals[idxmin:])
    -    minval = vals[idxmin]
    -    answer = ((maxval - minval)/abs(minval) - 1.0) > fraction_threshold
    -    if answer:
    -        msg = (f"Score {score_name} is too high: during training the value {maxval}"
    -               f" passed a treshold of {(1 + fraction_threshold) * minval}"
    -               f" (estimate is based on {idxmin} iteration)")
    -        warnings.warn(msg)
    -    return answer
    + if out_of_control_result.error_message is not None: + warnings.warn(out_of_control_result.error_message) + + return out_of_control_result.answer + + def is_out_of_control(self, values: List[float]) -> OutOfControlAnswer: + raise NotImplementedError
    +
    +

    Subclasses

    + +

    Methods

    +
    +
    +def get_score_values(self, model) +
    +
    +
    +
    + +Expand source code + +
    def get_score_values(self, model):
    +    if self.score_name not in model.scores:  # case of None is handled here as well
    +        return None
    +
    +    vals = model.scores[self.score_name]
    +
    +    if len(vals) == 0:
    +        return None
    +
    +    return vals
    +
    +
    +
    +def is_out_of_control(self, values: List[float]) -> OutOfControlAnswer +
    +
    +
    +
    + +Expand source code + +
    def is_out_of_control(self, values: List[float]) -> OutOfControlAnswer:
    +    raise NotImplementedError
    -
    -
    -

    Classes

    -
    +

    class ControllerAgent -(reg_name, score_to_track, tau_converter, max_iters, local_dict=None) +(reg_name: str, tau_converter: Callable, max_iters: int, score_to_track: Union[str, List[str], NoneType] = None, fraction_threshold: Union[float, List[float], NoneType] = None, score_controller: Union[topicnet.cooking_machine.cubes.controller_cube.BaseScoreController, List[topicnet.cooking_machine.cubes.controller_cube.BaseScoreController], NoneType] = None, local_dict: dict = None)
    -

    Allows to change tau during the _fit method.

    +

    Allows to change tau during the _fit method.

    Each TopicModel has a .callbacks attribute. -This is a list consisting of various ControllerAgents. +This is a list consisting of various ControllerAgents. Each agent is described by:

    • reg_name: the name of regularizer having tau which needs to be changed
    • -
    • score_to_track: score providing control of the callback execution
    • tau_converter: function or string describing how to get new tau from old tau
    • +
    • score_to_track: score name providing control of the callback execution
    • +
    • fraction_threshold: threshold to control score_to_track
    • +
    • score_controller: custom score controller providing control of the callback execution
    • local_dict: dictionary containing values of several variables, most notably, user_value
    • is_working: @@ -654,21 +872,34 @@

      Classes

      See top-level docstring for details.

      Parameters

      -
      reg_name : str
      -
       
      -
      score_to_track : str, list of str or None
      +
      reg_name
       
      -
      tau_converter : callable or str
      +
      tau_converter
       
      -
      local_dict : dict
      -
       
      -
      max_iters : int or float
      -
      Agent will stop changing tau after max_iters iterations +
      max_iters
      +
      Agent will stop changing tau after max_iters iterations, max_iters could be float("NaN") and float("inf") values: -that way agent will continue operating even outside this RegularizationControllerCube
      -
    +that way agent will continue operating even outside this RegularizationControllerCube
    +
    score_to_track
    +
    Name of score to track. +Please, use this definition to track only scores of type PerplexityScore. +In other cases we recommend you to write you own ScoreController
    +
    fraction_threshold
    +
    Uses to define threshold to control PerplexityScore +Default value is 0.05. +If fraction_threshold is a list, it should be of the same length, as score_to_track.
    +
    score_controller
    +
    Score controller or controllers. +One can use this parameter for scores other than Perplexity +(or other scores that behave like Perplexity). +This is a more flexible and customizable way to control scores.
    +
    local_dict
    +
     
    +
    -Source code + +Expand source code +
    class ControllerAgent:
         """
         Allows to change `tau` during the `_fit` method.
    @@ -678,8 +909,10 @@ 

    Parameters

    Each agent is described by: * reg_name: the name of regularizer having `tau` which needs to be changed - * score_to_track: score providing control of the callback execution * tau_converter: function or string describing how to get new `tau` from old `tau` + * score_to_track: score name providing control of the callback execution + * fraction_threshold: threshold to control score_to_track + * score_controller: custom score controller providing control of the callback execution * local_dict: dictionary containing values of several variables, most notably, `user_value` * is_working: @@ -689,37 +922,125 @@

    Parameters

    See top-level docstring for details. """ - def __init__(self, reg_name, score_to_track, tau_converter, max_iters, local_dict=None): + + def __init__( + self, + reg_name: str, + tau_converter: Callable or str, + max_iters: int or float, + score_to_track: Union[str, List[str], None] = None, + fraction_threshold: Union[float, List[float], None] = None, + score_controller: Union[BaseScoreController, List[BaseScoreController], None] = None, + local_dict: dict = None): """ Parameters ---------- - reg_name : str - score_to_track : str, list of str or None - tau_converter : callable or str - local_dict : dict - max_iters : int or float - Agent will stop changing tau after `max_iters` iterations + reg_name + tau_converter + max_iters + Agent will stop changing tau after `max_iters` iterations, `max_iters` could be `float("NaN")` and `float("inf")` values: that way agent will continue operating even outside this `RegularizationControllerCube` + score_to_track + Name of score to track. + Please, use this definition to track only scores of type PerplexityScore. + In other cases we recommend you to write you own ScoreController + fraction_threshold + Uses to define threshold to control PerplexityScore + Default value is 0.05. + If `fraction_threshold` is a list, it should be of the same length, as `score_to_track`. + score_controller + Score controller or controllers. + One can use this parameter for scores other than Perplexity + (or other scores that behave like Perplexity). + This is a more flexible and customizable way to control scores. + local_dict """ if local_dict is None: local_dict = dict() self.reg_name = reg_name self.tau_converter = tau_converter - if isinstance(score_to_track, list): - self.score_to_track = score_to_track - elif isinstance(score_to_track, str): - self.score_to_track = [score_to_track] - else: - self.score_to_track = [] + + scores_to_track = self._validate_score_to_track(score_to_track) + fraction_thresholds = self._validate_fraction_threshold( + fraction_threshold, required_length=len(scores_to_track) + ) + + assert len(scores_to_track) == len(fraction_thresholds) + + perplexity_like_score_controllers = [ + PerplexityScoreController(name, threshold) + for (name, threshold) in zip(scores_to_track, fraction_thresholds) + ] + + self.score_controllers = list() + self.score_controllers.extend(perplexity_like_score_controllers) + self.score_controllers.extend( + self._validate_score_controller(score_controller) + ) self.is_working = True self.local_dict = local_dict self.tau_history = [] self.max_iters = max_iters + @staticmethod + def _validate_score_to_track( + score_to_track: Union[str, List[str], None]) -> List[str]: + + if isinstance(score_to_track, list): + return score_to_track + if score_to_track is None: + return list() + if isinstance(score_to_track, str): + return [score_to_track] + + raise TypeError(f'Wrong type of `score_to_track`: "{type(score_to_track)}"!') + + @staticmethod + def _validate_fraction_threshold( + fraction_threshold: Union[float, List[float], None], + required_length: int, + ) -> List[float]: + + if fraction_threshold is None: + return [PerplexityScoreController.DEFAULT_FRACTION_THRESHOLD] * required_length + if isinstance(fraction_threshold, Number): + return [float(fraction_threshold)] * required_length + + if not isinstance(fraction_threshold, list): + raise TypeError( + f'Wrong type of `fraction_threshold`: "{type(fraction_threshold)}"!' + ) + + if len(fraction_threshold) != required_length: + raise ValueError( + f'Wrong length of `fraction_threshold`: {len(fraction_threshold)}!' + f' Expected the length to be equal to {required_length}.' + ) + + return fraction_threshold + + @staticmethod + def _validate_score_controller( + score_controller: Union[BaseScoreController, List[BaseScoreController], None] + ) -> List[BaseScoreController]: + + if score_controller is None: + return list() + + elif isinstance(score_controller, BaseScoreController): + return [score_controller] + + elif (not isinstance(score_controller, list) or not all( + isinstance(score, BaseScoreController) for score in score_controller)): + raise TypeError(f'Wrong type of `score_controller`: "{type(score_controller)}"!') + + else: + return score_controller + def _convert_tau(self): """ """ if isinstance(self.tau_converter, str): @@ -750,7 +1071,7 @@

    Parameters

    Note that zero means "cube just started", not "the model is brand new" """ - current_tau = model.regularizers[self.reg_name].tau + current_tau = model.get_regularizer(self.reg_name).tau self.tau_history.append(current_tau) self.local_dict["prev_tau"] = current_tau self.local_dict["cur_iter"] = cur_iter @@ -764,14 +1085,14 @@

    Parameters

    if self.is_working: should_stop = any( - is_score_out_of_control(model, score) for score in self.score_to_track + score_controller(model) for score_controller in self.score_controllers ) if should_stop: warnings.warn(W_HALT_CONTROL.format(len(self.tau_history))) self.is_working = False - model.regularizers[self.reg_name].tau = self._find_safe_tau() + model.get_regularizer(self.reg_name).tau = self._find_safe_tau() else: - model.regularizers[self.reg_name].tau = self._convert_tau()
    + model.get_regularizer(self.reg_name).tau = self._convert_tau()

    Methods

    @@ -779,16 +1100,18 @@

    Methods

    def invoke(self, model, cur_iter)
    -

    Attempts to change tau if is_working == True. Otherwise, keeps to the last safe value.

    +

    Attempts to change tau if is_working == True. Otherwise, keeps to the last safe value.

    Parameters

    model : TopicModel
     
    cur_iter : int
    Note that zero means "cube just started", not "the model is brand new"
    -
    +
    -Source code + +Expand source code +
    def invoke(self, model, cur_iter):
         """
         Attempts to change tau if `is_working == True`. Otherwise, keeps to the last safe value.
    @@ -800,7 +1123,7 @@ 

    Parameters

    Note that zero means "cube just started", not "the model is brand new" """ - current_tau = model.regularizers[self.reg_name].tau + current_tau = model.get_regularizer(self.reg_name).tau self.tau_history.append(current_tau) self.local_dict["prev_tau"] = current_tau self.local_dict["cur_iter"] = cur_iter @@ -814,51 +1137,185 @@

    Parameters

    if self.is_working: should_stop = any( - is_score_out_of_control(model, score) for score in self.score_to_track + score_controller(model) for score_controller in self.score_controllers ) if should_stop: warnings.warn(W_HALT_CONTROL.format(len(self.tau_history))) self.is_working = False - model.regularizers[self.reg_name].tau = self._find_safe_tau() + model.get_regularizer(self.reg_name).tau = self._find_safe_tau() else: - model.regularizers[self.reg_name].tau = self._convert_tau()
    + model.get_regularizer(self.reg_name).tau = self._convert_tau()
    +
    +
    + +
    +
    +class OutOfControlAnswer +(answer: bool, error_message: Union[str, NoneType] = None) +
    +
    +

    OutOfControlAnswer(answer:bool, error_message:Union[str, NoneType]=None)

    +
    + +Expand source code + +
    class OutOfControlAnswer:
    +    answer: bool
    +    error_message: Optional[str] = None
    +
    +

    Class variables

    +
    +
    var error_message : Union[str, NoneType]
    +
    +
    +
    +
    +
    +
    +class PerplexityScoreController +(score_name, fraction_threshold=0.05) +
    +
    +

    Controller is proper to control the Perplexity score. +For others, please ensure for yourself.

    +
    + +Expand source code + +
    class PerplexityScoreController(BaseScoreController):
    +    """
    +    Controller is proper to control the Perplexity score.
    +    For others, please ensure for yourself.
    +    """
    +    DEFAULT_FRACTION_THRESHOLD = 0.05
    +
    +    def __init__(self, score_name, fraction_threshold=DEFAULT_FRACTION_THRESHOLD):
    +        super().__init__(score_name)
    +        self.fraction_threshold = fraction_threshold
    +
    +    def is_out_of_control(self, values: List[float]):
    +        idxmin = np.argmin(values)
    +
    +        if idxmin == len(values):  # score is monotonically decreasing
    +            return False
    +
    +        right_maxval = max(values[idxmin:])
    +        minval = values[idxmin]
    +
    +        if minval <= 0:
    +            raise ValueError(
    +                f'Score "{self.score_name}" has min_value = {minval} which is <= 0.'
    +                f' This control scheme is using to control scores acting like Perplexity.'
    +                f' Ensure you control the Perplexity score or write your own controller!'
    +            )
    +
    +        answer = (right_maxval - minval) / minval > self.fraction_threshold
    +
    +        if answer:
    +            return OutOfControlAnswer(
    +                answer=answer,
    +                error_message=(
    +                    f"Score {self.score_name} is too high!"
    +                    f" Right max value: {right_maxval}, min value: {minval}"
    +                ),
    +            )
    +
    +        return OutOfControlAnswer(answer=answer)
    +
    +

    Ancestors

    + +

    Class variables

    +
    +
    var DEFAULT_FRACTION_THRESHOLD
    +
    +
    +
    +
    +

    Methods

    +
    +
    +def is_out_of_control(self, values: List[float]) +
    +
    +
    +
    + +Expand source code + +
    def is_out_of_control(self, values: List[float]):
    +    idxmin = np.argmin(values)
    +
    +    if idxmin == len(values):  # score is monotonically decreasing
    +        return False
    +
    +    right_maxval = max(values[idxmin:])
    +    minval = values[idxmin]
    +
    +    if minval <= 0:
    +        raise ValueError(
    +            f'Score "{self.score_name}" has min_value = {minval} which is <= 0.'
    +            f' This control scheme is using to control scores acting like Perplexity.'
    +            f' Ensure you control the Perplexity score or write your own controller!'
    +        )
    +
    +    answer = (right_maxval - minval) / minval > self.fraction_threshold
    +
    +    if answer:
    +        return OutOfControlAnswer(
    +            answer=answer,
    +            error_message=(
    +                f"Score {self.score_name} is too high!"
    +                f" Right max value: {right_maxval}, min value: {minval}"
    +            ),
    +        )
    +
    +    return OutOfControlAnswer(answer=answer)
    class RegularizationControllerCube -(num_iter, parameters, reg_search='grid', use_relative_coefficients=True, strategy=None, tracked_score_function=None, verbose=False, separate_thread=True) +(num_iter: int, parameters, reg_search='grid', use_relative_coefficients: bool = True, strategy=None, tracked_score_function=None, verbose: bool = False, separate_thread: bool = True)
    -

    Abstract class for all cubes.

    +

    Abstract class for all cubes.

    Initialize stage. Checks params and update internal attributes.

    Parameters

    num_iter : int
    number of iterations or method
    -
    parameters : list[dict] or dict
    +
    parameters : list[dict] or dict

    regularizers params each dict should contain the following fields: ("reg_name" or "regularizer"), -"score_to_track" (optional), "tau_converter", +"score_to_track" (optional), +"fraction_threshold" (optional), +"score_controller" (optional), "user_value_grid" See top-level docstring for details. Examples:

        >>  {"regularizer": artm.regularizers.<...>,
    -    >>   "score_to_track": "PerplexityScore@all",
         >>   "tau_converter": "prev_tau * user_value",
    +    >>   "score_to_track": "PerplexityScore@all",
    +    >>   "fraction_threshold": 0.1,
         >>   "user_value_grid": [0.5, 1, 2]}
     

        >>  {"reg_name": "decorrelator_for_ngramms",
    -    >>   "score_to_track": None,
         >>   "tau_converter": (
         >>       lambda initial_tau, prev_tau, cur_iter, user_value:
         >>       initial_tau * (cur_iter % 2) + user_value
         >>   )
    +    >>   "score_to_track": None,
    +    >>   "fraction_threshold": None,
    +    >>   "score_controller": [
    +    >>       PerplexityScoreController("PerplexityScore@all", 0.1)
    +    >>   ],
         >>   "user_value_grid": [0, 1]}
     
    @@ -874,13 +1331,15 @@

    Parameters

    i.e. normalized over collection properties
    strategy : BaseStrategy
    optimization approach (Default value = None)
    -
    tracked_score_function : str ot callable
    +
    tracked_score_function : str ot callable
    optimizable function for strategy (Default value = None)
    verbose : bool
    visualization flag (Default value = False)
    - +
    -Source code + +Expand source code +
    class RegularizationControllerCube(BaseCube):
         def __init__(self, num_iter: int, parameters,
                      reg_search='grid', use_relative_coefficients: bool = True, strategy=None,
    @@ -896,26 +1355,33 @@ 

    Parameters

    regularizers params each dict should contain the following fields: ("reg_name" or "regularizer"), - "score_to_track" (optional), "tau_converter", + "score_to_track" (optional), + "fraction_threshold" (optional), + "score_controller" (optional), "user_value_grid" See top-level docstring for details. Examples: >> {"regularizer": artm.regularizers.<...>, - >> "score_to_track": "PerplexityScore@all", >> "tau_converter": "prev_tau * user_value", + >> "score_to_track": "PerplexityScore@all", + >> "fraction_threshold": 0.1, >> "user_value_grid": [0.5, 1, 2]} ----------- >> {"reg_name": "decorrelator_for_ngramms", - >> "score_to_track": None, >> "tau_converter": ( >> lambda initial_tau, prev_tau, cur_iter, user_value: >> initial_tau * (cur_iter % 2) + user_value >> ) + >> "score_to_track": None, + >> "fraction_threshold": None, + >> "score_controller": [ + >> PerplexityScoreController("PerplexityScore@all", 0.1) + >> ], >> "user_value_grid": [0, 1]} reg_search : str @@ -942,9 +1408,9 @@

    Parameters

    separate_thread=separate_thread) self._relative = use_relative_coefficients self.data_stats = None - self.raw_parameters = parameters if isinstance(parameters, dict): parameters = [parameters] + self.raw_parameters = parameters self._convert_parameters(parameters) def _convert_parameters(self, all_parameters): @@ -1005,22 +1471,29 @@

    Parameters

    for (agent_blueprint_template, field_name, current_user_value) in one_model_parameter: agent_blueprint = dict(agent_blueprint_template) - if agent_blueprint["reg_name"] is None: - regularizer = agent_blueprint["regularizer"] - new_regularizer = deepcopy(regularizer) - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) - agent_blueprint["reg_name"] = new_regularizer.name - else: - if agent_blueprint['reg_name'] not in new_model.regularizers.data: + if agent_blueprint.get("reg_name") is not None: + reg_name = agent_blueprint['reg_name'] + + if reg_name not in new_model.all_regularizers: error_msg = (f"Regularizer {agent_blueprint['reg_name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif agent_blueprint.get("regularizer") is not None: + regularizer = agent_blueprint["regularizer"] + new_regularizer = deepcopy(regularizer) + if isinstance(regularizer, BaseRegularizer): + new_model.custom_regularizers[new_regularizer.name] = new_regularizer + else: # classic bigARTM regularizer, attempt to relativize it's coefficients + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) + agent_blueprint["reg_name"] = new_regularizer.name + else: + raise ValueError("Either 'reg_name' or 'regularizer' should be set") agent_blueprint['local_dict']['user_value'] = current_user_value # ControllerAgent needs only reg_name in constructor agent_blueprint.pop("regularizer") @@ -1058,7 +1531,7 @@

    Methods

    def apply(self, topic_model, one_model_parameter, dictionary=None, model_id=None)
    -

    Applies regularizers and controller agents to model

    +

    Applies regularizers and controller agents to model

    Parameters

    topic_model : TopicModel
    @@ -1074,9 +1547,11 @@

    Returns

    TopicModel
     
    -
    +
    -Source code + +Expand source code +
    def apply(self, topic_model, one_model_parameter, dictionary=None, model_id=None):
         """
         Applies regularizers and controller agents to model
    @@ -1106,22 +1581,29 @@ 

    Returns

    for (agent_blueprint_template, field_name, current_user_value) in one_model_parameter: agent_blueprint = dict(agent_blueprint_template) - if agent_blueprint["reg_name"] is None: - regularizer = agent_blueprint["regularizer"] - new_regularizer = deepcopy(regularizer) - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) - agent_blueprint["reg_name"] = new_regularizer.name - else: - if agent_blueprint['reg_name'] not in new_model.regularizers.data: + if agent_blueprint.get("reg_name") is not None: + reg_name = agent_blueprint['reg_name'] + + if reg_name not in new_model.all_regularizers: error_msg = (f"Regularizer {agent_blueprint['reg_name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif agent_blueprint.get("regularizer") is not None: + regularizer = agent_blueprint["regularizer"] + new_regularizer = deepcopy(regularizer) + if isinstance(regularizer, BaseRegularizer): + new_model.custom_regularizers[new_regularizer.name] = new_regularizer + else: # classic bigARTM regularizer, attempt to relativize it's coefficients + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) + agent_blueprint["reg_name"] = new_regularizer.name + else: + raise ValueError("Either 'reg_name' or 'regularizer' should be set") agent_blueprint['local_dict']['user_value'] = current_user_value # ControllerAgent needs only reg_name in constructor agent_blueprint.pop("regularizer") @@ -1156,13 +1638,15 @@

    Index

  • topicnet.cooking_machine.cubes
  • -
  • Functions

    +
  • Classes

    +
      +
    • +

      BaseScoreController

    • -
    • Classes

      -
      • ControllerAgent

          @@ -1170,6 +1654,19 @@

          OutOfControlAnswer

          + + +
        • +

          PerplexityScoreController

          + +
        • +
        • RegularizationControllerCube

          • apply
          • @@ -1181,7 +1678,7 @@

            -

            Generated by pdoc 0.6.3.

            +

            Generated by pdoc 0.8.1.

            diff --git a/docs/cooking_machine/cubes/cube_creator.html b/docs/cooking_machine/cubes/cube_creator.html index 34d1188..609589c 100644 --- a/docs/cooking_machine/cubes/cube_creator.html +++ b/docs/cooking_machine/cubes/cube_creator.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes.cube_creator API documentation - - + + @@ -21,7 +21,9 @@

            Module topicnet.cooking_machine.cubes.cube_creator
            -Source code + +Expand source code +
            from .base_cube import BaseCube
             from inspect import signature
             from copy import deepcopy
            @@ -265,17 +267,17 @@ 

            Classes

            class CubeCreator -(num_iter, parameters, reg_search='grid', strategy=None, model_class='TopicModel', second_level=False, tracked_score_function=None, verbose=False, separate_thread=True) +(num_iter: int, parameters, reg_search='grid', strategy=None, model_class='TopicModel', second_level=False, tracked_score_function=None, verbose=False, separate_thread=True)
            -

            Class for creating models with different initial parameters.

            +

            Class for creating models with different initial parameters.

            Parameters

            model : TopicModel
            TopicModel instance
            num_iter : int
            number of iterations or method
            -
            parameters : list[dict] or dict
            +
            parameters : list[dict] or dict
            parameters for model initialization
            reg_search : str
            "grid" or "pair"
            @@ -289,9 +291,11 @@

            Parameters

            visualization flag (Default value = False)
            separate_thread : bool
            will train models inside a separate thread if True
            -
            +
            -Source code + +Expand source code +
            class CubeCreator(BaseCube):
                 """
                 Class for creating models with different initial parameters.
            @@ -525,7 +529,7 @@ 

            Class variables

            var DEFAULT_SEED_VALUE
            -
            +

            Methods

            @@ -534,7 +538,7 @@

            Methods

            def apply(self, topic_model, one_cube_parameter, dictionary=None, model_id=None)
            -

            Parameters

            +

            Parameters

            topic_model : TopicModel
             
            @@ -545,9 +549,11 @@

            Methods

            model_id : str
            (Default value = None)
            -

            Returns

            +

            Returns

            -Source code + +Expand source code +
            def apply(self, topic_model, one_cube_parameter, dictionary=None, model_id=None):
                 """
             
            @@ -642,7 +648,7 @@ 

            diff --git a/docs/cooking_machine/cubes/greedy_strategy.html b/docs/cooking_machine/cubes/greedy_strategy.html index ba6a784..903ef72 100644 --- a/docs/cooking_machine/cubes/greedy_strategy.html +++ b/docs/cooking_machine/cubes/greedy_strategy.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes.greedy_strategy API documentation - - + + @@ -21,7 +21,9 @@

            Module topicnet.cooking_machine.cubes.greedy_strategy
            -Source code + +Expand source code +
            import numpy as np
             from .strategy import BaseStrategy
             
            @@ -203,19 +205,21 @@ 

            Classes

            class GreedyStrategy -(renormalize=False) +(renormalize: bool = False)
            -

            Allows to visit nodes of parameters' grid in a particular order.

            +

            Allows to visit nodes of parameters' grid in a particular order.

            The rough idea:
            We are given grid of (values1 x values2 x values3).
            This strategy will find best value among points of form [v1, 0, 0] and will mark first coordinate as finished.
            Then we search for best v2 among [v1, v2, 0].
            Then [v1, v2, v3] etc.

            -

            Initialize stage. Updates internal attributes.

            +

            Initialize stage. Updates internal attributes.

            -Source code + +Expand source code +
            class GreedyStrategy(BaseStrategy):
                 """
                 Allows to visit nodes of parameters' grid in a particular order.
            @@ -391,7 +395,7 @@ 

            Methods

            def grid_visit_generator(self, other_parameters, reg_search)
            -

            Converts the search point given to the internal format +

            Converts the search point given to the internal format Notably, pads with zero and normalizees with some rudimentary sanity checking.

            Parameters

            @@ -405,9 +409,11 @@

            Yields

            list of lists
             
            -
            +
            -Source code + +Expand source code +
            def grid_visit_generator(self, other_parameters, reg_search):
                 """
                 Converts the search point given to the internal format
            @@ -447,16 +453,18 @@ 

            Yields

            def prepare_grid(self, other_parameters, reg_search)
            -

            Sets parameters of grid and prepares grid length for verbosity.

            +

            Sets parameters of grid and prepares grid length for verbosity.

            Parameters

            other_parameters : dict or list of dict
             
            reg_search : str
             
            -
            +
            -Source code + +Expand source code +
            def prepare_grid(self, other_parameters, reg_search):
                 """
                 Sets parameters of grid and prepares grid length for verbosity.
            @@ -510,7 +518,7 @@ 

            -

            Generated by pdoc 0.6.3.

            +

            Generated by pdoc 0.8.1.

            diff --git a/docs/cooking_machine/cubes/index.html b/docs/cooking_machine/cubes/index.html index be868bf..8adfcd7 100644 --- a/docs/cooking_machine/cubes/index.html +++ b/docs/cooking_machine/cubes/index.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes API documentation - - + + @@ -19,44 +19,11 @@

            Module topicnet.cooking_machine.cubes

            -

            Cubes and their Strategies

            -

            Cube types:

            -
              -
            • BaseCube — a parent class for all the Cubes
            • -
            • RegularizersModifierCube — cube that adds or alter model regularizers
            • -
            • CubeCreator — cube that allows to change model fundamental hyperparameters (topic number)
            • -
            • RegularizationControllerCube - cube that ties together a complicated usage of RegularizersModifierCube. This cube allows for change of regularization coefficients across the model training. This allows to obtain soemwhat unique results by combining contradictionary restrictions on the model.
            • -
            -
            -

            Strategy types:

            -
              -
            • BaseStrategy — a parent class for all the Strategies
            • -
            • PerplexityStrategy — performs search in given hyperparameter space until certain score exceeds a boundary
            • -
            • GreedyStrategy — strategy that performes search in hyperparameter space consequently changing dimensions to perform a 1D search for a minimum
            • -
            -
            -

            Cube internal structure

            -

            The main cube attributes:

            -
              -
            • parameters — paramteres is an iterable object containing all the specific information about current cube. The class architecture implies that parameters should contain an iterable field describing the hyperparameters search space
            • -
            -

            Cube methods worth noticing:

            -
              -
            • __call__ — performes the cube actions to the model using provided dataset. Always recieves instance of TopicModel class and instance of Dataset class. This method does the internal workings of training models with new hyperparameters. It is responsible for logging the events (which parameters where changed) happening during the model training.

            • -
            • apply — method of the cube that prepares model for further training. This method should be specified by the user as it contains an “essence” of what is happening at this stage of the training. It could be new type of model reinitialization, change of the regualarization coefficient, adding a new level of hierarchy etc. This function defines what the cube does in the training pipeline.

            • -
            • get_jsonable_from_parameters — is a cube-specific function that transforms it parameters to dict-like form which later is written in JSON format log of the experiment.

            • -
            -
            -

            What do you need to create a new cube?

            -

            Following this 3 easy steps you will be able to write down your own cube:

            -
              -
            1. Inherit your Cube from BaseCube.

            2. -
            3. Child class should define following methods __init__, apply, get_jsonable_from_parameters. It is strongly descouraged to change __call__ method.

            4. -
            5. get_jsonable_from_parameters()[i] corresponds to the same cube step as parameters[i].

            6. -
            -Source code + +Expand source code +
            from .base_cube import BaseCube, retrieve_score_for_strategy
             from .regularizer_cube import RegularizersModifierCube
             from .controller_cube import RegularizationControllerCube
            @@ -71,32 +38,32 @@ 

            Sub-modules

            topicnet.cooking_machine.cubes.base_cube
            -
            +
            topicnet.cooking_machine.cubes.controller_cube
            -

            Allows to add ControllerAgent (with unknown parameters) to the model, which enables user to -change tau during the _fit method …

            +

            Allows to add ControllerAgent (with unknown parameters) to the model, which enables user to +change tau during the _fit method …

            topicnet.cooking_machine.cubes.cube_creator
            -
            +
            topicnet.cooking_machine.cubes.greedy_strategy
            -
            +
            topicnet.cooking_machine.cubes.perplexity_strategy
            -
            +
            topicnet.cooking_machine.cubes.regularizer_cube
            -
            +
            topicnet.cooking_machine.cubes.strategy
            -
            +
            @@ -108,7 +75,7 @@

            Sub-modules

            diff --git a/docs/cooking_machine/cubes/perplexity_strategy.html b/docs/cooking_machine/cubes/perplexity_strategy.html index 3e35ffd..5e10a12 100644 --- a/docs/cooking_machine/cubes/perplexity_strategy.html +++ b/docs/cooking_machine/cubes/perplexity_strategy.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes.perplexity_strategy API documentation - - + + @@ -21,7 +21,9 @@

            Module topicnet.cooking_machine.cubes.perplexity_strateg
            -Source code + +Expand source code +
            import numpy as np
             from itertools import product
             import warnings
            @@ -201,7 +203,7 @@ 

            Module topicnet.cooking_machine.cubes.perplexity_strateg self.grid = product(*all_coeffs_grid) self.grid_len = len(all_coeffs_grid[0]) if self.grid is None: - raise ValueError(f'Failed to initialize self.grid, check initial parameters.') + raise ValueError('Failed to initialize self.grid, check initial parameters.') def grid_visit_generator(self, other_parameters, reg_search): """ @@ -263,10 +265,10 @@

            Classes

            class PerplexityStrategy -(start_point=None, step=None, max_len=25, threshold=1.05) +(start_point: float = None, step: float = None, max_len: float = 25, threshold: float = 1.05)
            -

            Search for the best perplexity score.

            +

            Search for the best perplexity score.

            Initialize stage.

            Parameters

            @@ -279,9 +281,11 @@

            Parameters

            length of progression
            threshold : float
            threshold for "perplexity out of control"
            -
            +
            -Source code + +Expand source code +
            class PerplexityStrategy(BaseStrategy):
                 """
                 Search for the best perplexity score.
            @@ -454,7 +458,7 @@ 

            Parameters

            self.grid = product(*all_coeffs_grid) self.grid_len = len(all_coeffs_grid[0]) if self.grid is None: - raise ValueError(f'Failed to initialize self.grid, check initial parameters.') + raise ValueError('Failed to initialize self.grid, check initial parameters.') def grid_visit_generator(self, other_parameters, reg_search): """ @@ -514,7 +518,7 @@

            Methods

            def grid_visit_generator(self, other_parameters, reg_search)
            -

            Yields points from search space with sanity checking of current result.

            +

            Yields points from search space with sanity checking of current result.

            Parameters

            other_parameters : dict
            @@ -524,11 +528,13 @@

            Parameters

            Yields

            -
            sequence of points in search space
            +
            sequence of points in search space
             
            -
            +
            -Source code + +Expand source code +
            def grid_visit_generator(self, other_parameters, reg_search):
                 """
                 Yields points from search space with sanity checking of current result.
            @@ -570,7 +576,7 @@ 

            Yields

            def prepare_grid(self, other_parameters, reg_search='add')
            -

            Creates search space and length for tqdm. +

            Creates search space and length for tqdm. Note, that first point in sequence is always 0.

            Parameters

            @@ -579,9 +585,11 @@

            Parameters

            reg_search : str
            "grid", "add" or "mul" defines grid search or arithmetic or geometric progression
            -
            +
            -Source code + +Expand source code +
            def prepare_grid(self, other_parameters, reg_search="add"):
                 """
                 Creates search space and length for tqdm.
            @@ -619,7 +627,7 @@ 

            Parameters

            self.grid = product(*all_coeffs_grid) self.grid_len = len(all_coeffs_grid[0]) if self.grid is None: - raise ValueError(f'Failed to initialize self.grid, check initial parameters.')
            + raise ValueError('Failed to initialize self.grid, check initial parameters.')
            @@ -661,7 +669,7 @@

            -

            Generated by pdoc 0.6.3.

            +

            Generated by pdoc 0.8.1.

            diff --git a/docs/cooking_machine/cubes/regularizer_cube.html b/docs/cooking_machine/cubes/regularizer_cube.html index ab481e6..30a94fa 100644 --- a/docs/cooking_machine/cubes/regularizer_cube.html +++ b/docs/cooking_machine/cubes/regularizer_cube.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes.regularizer_cube API documentation - - + + @@ -21,10 +21,13 @@

            Module topicnet.cooking_machine.cubes.regularizer_cube
            -Source code + +Expand source code +
            from .base_cube import BaseCube
             from ..routine import transform_complex_entity_to_dict
             from ..rel_toolbox_lite import count_vocab_size, handle_regularizer
            +from ..models.base_regularizer import BaseRegularizer
             from copy import deepcopy
             
             
            @@ -168,18 +171,28 @@ 

            Module topicnet.cooking_machine.cubes.regularizer_cubeClasses

            class RegularizersModifierCube -(num_iter, regularizer_parameters, reg_search='grid', use_relative_coefficients=True, strategy=None, tracked_score_function=None, verbose=False, separate_thread=True) +(num_iter: int, regularizer_parameters, reg_search='grid', use_relative_coefficients: bool = True, strategy=None, tracked_score_function=None, verbose: bool = False, separate_thread: bool = True)
            -

            Allows to create cubes of training and apply them to a topic model.

            +

            Allows to create cubes of training and apply them to a topic model.

            Initialize stage. Checks params and update internal attributes.

            Parameters

            num_iter : int
            number of iterations or method
            -
            regularizer_parameters : list[dict] or dict
            +
            regularizer_parameters : list[dict] or dict
            regularizers params
            reg_search : str
            "grid", "pair", "add" or "mul". @@ -251,9 +264,11 @@

            Parameters

            visualization flag (Default value = False)
            separate_thread : bool
            will train models inside a separate thread if True
            -
            +
            -Source code + +Expand source code +
            class RegularizersModifierCube(BaseCube):
                 """
                 Allows to create cubes of training and apply them to a topic model.
            @@ -394,18 +409,28 @@ 

            Parameters

            regularizer_type = str(type(regularizer)) if isinstance(regularizer, dict): if regularizer['name'] in new_model.all_regularizers.keys(): + # TODO: do we actually need to deepcopy custom regularizers? new_regularizer = deepcopy(new_model.all_regularizers[regularizer['name']]) - new_regularizer._tau = params - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) + if regularizer['name'] in new_model.custom_regularizers: + new_model.custom_regularizers[regularizer['name']].tau = params + else: + # if this is classic regularizer, we attempt to relativize it's coefficients + new_regularizer._tau = params + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) else: error_msg = (f"Regularizer {regularizer['name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif isinstance(regularizer, BaseRegularizer): + # TODO: do we actually need to deepcopy here? + new_regularizer = deepcopy(regularizer) + new_regularizer.tau = params + new_model.custom_regularizers[regularizer.name] = new_regularizer elif 'Regularizer' in regularizer_type: new_regularizer = deepcopy(regularizer) new_regularizer._tau = params @@ -446,7 +471,7 @@

            Methods

            def apply(self, topic_model, one_model_parameter, dictionary=None, model_id=None)
            -

            Applies regularizers and parameters to model

            +

            Applies regularizers and parameters to model

            Parameters

            topic_model : TopicModel
            @@ -462,9 +487,11 @@

            Returns

            TopicModel
             
            -
            +
            -Source code + +Expand source code +
            def apply(self, topic_model, one_model_parameter, dictionary=None, model_id=None):
                 """
                 Applies regularizers and parameters to model
            @@ -498,18 +525,28 @@ 

            Returns

            regularizer_type = str(type(regularizer)) if isinstance(regularizer, dict): if regularizer['name'] in new_model.all_regularizers.keys(): + # TODO: do we actually need to deepcopy custom regularizers? new_regularizer = deepcopy(new_model.all_regularizers[regularizer['name']]) - new_regularizer._tau = params - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) + if regularizer['name'] in new_model.custom_regularizers: + new_model.custom_regularizers[regularizer['name']].tau = params + else: + # if this is classic regularizer, we attempt to relativize it's coefficients + new_regularizer._tau = params + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) else: error_msg = (f"Regularizer {regularizer['name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif isinstance(regularizer, BaseRegularizer): + # TODO: do we actually need to deepcopy here? + new_regularizer = deepcopy(regularizer) + new_regularizer.tau = params + new_model.custom_regularizers[regularizer.name] = new_regularizer elif 'Regularizer' in regularizer_type: new_regularizer = deepcopy(regularizer) new_regularizer._tau = params @@ -563,7 +600,7 @@

            -

            Generated by pdoc 0.6.3.

            +

            Generated by pdoc 0.8.1.

            diff --git a/docs/cooking_machine/cubes/strategy.html b/docs/cooking_machine/cubes/strategy.html index f64f6ca..884f293 100644 --- a/docs/cooking_machine/cubes/strategy.html +++ b/docs/cooking_machine/cubes/strategy.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.cubes.strategy API documentation - - + + @@ -21,7 +21,9 @@

            Module topicnet.cooking_machine.cubes.strategy
            -Source code + +Expand source code +
            from itertools import product
             from functools import reduce
             from operator import mul
            @@ -145,10 +147,12 @@ 

            Classes

            class BaseStrategy
            -

            Allows to visit nodes of parameters' grid in a particular order.

            -

            Initialize stage. Checks params and update internal attributes.

            +

            Allows to visit nodes of parameters' grid in a particular order.

            +

            Initialize stage. Checks params and update internal attributes.

            -Source code + +Expand source code +
            class BaseStrategy():
                 """
                 Allows to visit nodes of parameters' grid in a particular order.
            @@ -255,8 +259,8 @@ 

            Classes

            Subclasses

            Methods

            @@ -264,7 +268,7 @@

            Methods

            def grid_visit_generator(self, other_parameters, reg_search)
            -

            Parameters

            +

            Parameters

            other_parameters : dict or list of dict
             
            @@ -275,9 +279,11 @@

            Yields

            list or tuple
            one parameters set for model
            -
            +
            -Source code + +Expand source code +
            def grid_visit_generator(self, other_parameters, reg_search):
                 """
             
            @@ -300,16 +306,18 @@ 

            Yields

            def prepare_grid(self, other_parameters, reg_search)
            -

            Creates grid for the search. Inplace.

            +

            Creates grid for the search. Inplace.

            Parameters

            other_parameters : dict or list of dict
             
            reg_search : str
            "grid" or "pair" (and "add" or "mul" for perplexity)
            -
            +
            -Source code + +Expand source code +
            def prepare_grid(self, other_parameters, reg_search):
                 """
                 Creates grid for the search. Inplace.
            @@ -339,13 +347,15 @@ 

            Parameters

            def update_scores(self, new_value)
            -

            Parameters

            +

            Parameters

            new_value : float
             
            -
            +
            -Source code + +Expand source code +
            def update_scores(self, new_value):
                 """
             
            @@ -389,7 +399,7 @@ 

            -

            Generated by pdoc 0.6.3.

            +

            Generated by pdoc 0.8.1.

            diff --git a/docs/cooking_machine/dataset.html b/docs/cooking_machine/dataset.html index 9d5c255..23b26cf 100644 --- a/docs/cooking_machine/dataset.html +++ b/docs/cooking_machine/dataset.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.dataset API documentation - - + + @@ -21,13 +21,14 @@

            Module topicnet.cooking_machine.dataset

            -Source code + +Expand source code +
            import csv
             import os
             import pandas as pd
             import shutil
             import sys
            -import tempfile
             import warnings
             
             from glob import glob
            @@ -427,6 +428,7 @@ 

            Module topicnet.cooking_machine.dataset

            """ data_path = os.path.join(save_dataset_path, dataframe_name + '.csv') dataframe.to_csv(data_path) + return cls(data_path=data_path, **kwargs) def get_dataset(self): @@ -582,17 +584,16 @@

            Module topicnet.cooking_machine.dataset

            return False, path_to_collection if self._data_hash is None: - temp_file_descriptor, temp_file_path = tempfile.mkstemp( - prefix='temp_vw__', - suffix='.txt', - dir=self._internals_folder_path + temp_file_path = os.path.join( + self._internals_folder_path, 'temp_vw.txt' ) - self.write_vw(temp_file_path) - self._data_hash = blake2bchecksum(temp_file_path) - - os.close(temp_file_descriptor) - os.remove(temp_file_path) + try: + self.write_vw(temp_file_path) + self._data_hash = blake2bchecksum(temp_file_path) + finally: + if os.path.isfile(temp_file_path): + os.remove(temp_file_path) if os.path.isfile(path_to_collection): same_collection = blake2bchecksum(path_to_collection) == self._data_hash @@ -744,7 +745,7 @@

            Functions

            def get_modality_names(vw_string)
            -

            Gets modality names from vw_string.

            +

            Gets modality names from vw_string.

            Parameters

            vw_string : str
            @@ -756,9 +757,11 @@

            Returns

            document id
            list of str
            modalities in document
            -
            +
            -Source code + +Expand source code +
            def get_modality_names(vw_string):
                 """
                 Gets modality names from vw_string.
            @@ -788,7 +791,7 @@ 

            Returns

            def get_modality_vw(vw_string, modality_name)
            -

            Gets modality string from document vw string.

            +

            Gets modality string from document vw string.

            Parameters

            vw_string : str
            @@ -800,9 +803,11 @@

            Returns

            str
            content of modality_name modality
            -
            +
            -Source code + +Expand source code +
            def get_modality_vw(vw_string, modality_name):
                 """
                 Gets modality string from document vw string.
            @@ -836,12 +841,13 @@ 

            Classes

            class BaseDataset -(*args, **kwargs)
            -
            +
            -Source code + +Expand source code +
            class BaseDataset:
                 """ """
                 def get_source_document(self, document_id):
            @@ -868,13 +874,15 @@ 

            Methods

            def get_source_document(self, document_id)
            -

            Parameters

            +

            Parameters

            document_id : str
             
            -
            +
            -Source code + +Expand source code +
            def get_source_document(self, document_id):
                 """
             
            @@ -890,10 +898,10 @@ 

            Methods

            class Dataset -(data_path, keep_in_memory=True, batch_vectorizer_path=None, internals_folder_path=None, batch_size=1000) +(data_path: str, keep_in_memory: bool = True, batch_vectorizer_path: str = None, internals_folder_path: str = None, batch_size: int = 1000)
            -

            Class for keeping training data and documents for creation models.

            +

            Class for keeping training data and documents for creation models.

            Parameters

            data_path : str
            @@ -965,9 +973,11 @@

            Notes

          It is also worth emphasizing that, if the text collection is big, Theta matrix may not fit in memory. -So, in this case, some BigARTM scores (which depend on Theta) will stop working.

          +So, in this case, some BigARTM scores (which depend on Theta) will stop working.

          -Source code + +Expand source code +
          class Dataset(BaseDataset):
               """
               Class for keeping training data and documents for creation models.
          @@ -1256,6 +1266,7 @@ 

          Notes

          """ data_path = os.path.join(save_dataset_path, dataframe_name + '.csv') dataframe.to_csv(data_path) + return cls(data_path=data_path, **kwargs) def get_dataset(self): @@ -1411,17 +1422,16 @@

          Notes

          return False, path_to_collection if self._data_hash is None: - temp_file_descriptor, temp_file_path = tempfile.mkstemp( - prefix='temp_vw__', - suffix='.txt', - dir=self._internals_folder_path + temp_file_path = os.path.join( + self._internals_folder_path, 'temp_vw.txt' ) - self.write_vw(temp_file_path) - self._data_hash = blake2bchecksum(temp_file_path) - - os.close(temp_file_descriptor) - os.remove(temp_file_path) + try: + self.write_vw(temp_file_path) + self._data_hash = blake2bchecksum(temp_file_path) + finally: + if os.path.isfile(temp_file_path): + os.remove(temp_file_path) if os.path.isfile(path_to_collection): same_collection = blake2bchecksum(path_to_collection) == self._data_hash @@ -1572,10 +1582,10 @@

          Subclasses

          Static methods

          -def from_dataframe(dataframe, save_dataset_path, dataframe_name='dataset', **kwargs) +def from_dataframe(dataframe: pandas.core.frame.DataFrame, save_dataset_path: str, dataframe_name: str = 'dataset', **kwargs) -> Dataset
          -

          Creates dataset from pd.DataFrame +

          Creates dataset from pd.DataFrame reuqires to specify technical folder for dataset files

          Parameters

          @@ -1588,12 +1598,12 @@

          Parameters

          name for the dataset file to be saved in csv format Another Parameters


          -
          -
          **kwargs
          -
          kwargs are optional init topicnet.Dataset parameters
          -
          +

          kwargs +kwargs are optional init topicnet.Dataset parameters

          -Source code + +Expand source code +
          @classmethod
           def from_dataframe(
               cls,
          @@ -1621,17 +1631,20 @@ 

          Parameters

          """ data_path = os.path.join(save_dataset_path, dataframe_name + '.csv') dataframe.to_csv(data_path) + return cls(data_path=data_path, **kwargs)

          Instance variables

          -
          var documents
          +
          var documents : List[str]
          -
          +
          -Source code + +Expand source code +
          @property
           def documents(self) -> List[str]:
               return list(self._data_index)
          @@ -1644,9 +1657,11 @@

          Methods

          def clear_batches_folder(self)
          -

          Clear batches folder

          +

          Clear batches folder

          -Source code + +Expand source code +
          def clear_batches_folder(self):
               """
               Clear batches folder
          @@ -1662,9 +1677,11 @@ 

          Methods

          def clear_folder(self)
          -

          Clear internals_folder_path

          +

          Clear internals_folder_path

          -Source code + +Expand source code +
          def clear_folder(self):
               """
               Clear internals_folder_path
          @@ -1678,17 +1695,19 @@ 

          Methods

          -def get_batch_vectorizer(self) +def get_batch_vectorizer(self) -> artm.batches_utils.BatchVectorizer
          -

          Gets batch vectorizer.

          +

          Gets batch vectorizer.

          Returns

          artm.BatchVectorizer
           
          -
          +
          -Source code + +Expand source code +
          def get_batch_vectorizer(self) -> artm.BatchVectorizer:
               """
               Gets batch vectorizer.
          @@ -1736,26 +1755,30 @@ 

          Returns

          def get_dataset(self)
          -
          +
          -Source code + +Expand source code +
          def get_dataset(self):
               """ """
               return self._data
          -def get_dictionary(self) +def get_dictionary(self) -> artm.dictionary.Dictionary
          -

          Gets dataset's dictionary.

          +

          Gets dataset's dictionary.

          Returns

          artm.Dictionary
           
          -
          +
          -Source code + +Expand source code +
          def get_dictionary(self) -> artm.Dictionary:
               """
               Gets dataset's dictionary.
          @@ -1797,14 +1820,16 @@ 

          Returns

          def get_possible_modalities(self)
          -

          Returns extracted modalities.

          +

          Returns extracted modalities.

          Returns

          set
          all modalities in Dataset
          -
          +
          -Source code + +Expand source code +
          def get_possible_modalities(self):
               """
               Returns extracted modalities.
          @@ -1819,10 +1844,10 @@ 

          Returns

          -def get_source_document(self, document_id) +def get_source_document(self, document_id: str) -> pandas.core.frame.DataFrame
          -

          Get 'raw_text' for the document with document_id.

          +

          Get 'raw_text' for the document with document_id.

          Parameters

          document_id
          @@ -1832,9 +1857,11 @@

          Returns

          pd.DataFrame
          document_id and content of 'raw_text' column
          -
          +
          -Source code + +Expand source code +
          def get_source_document(self, document_id: str or List[str]) -> pd.DataFrame:
               """
               Get 'raw_text' for the document with `document_id`.
          @@ -1882,10 +1909,10 @@ 

          Returns

          -def get_vw_document(self, document_id) +def get_vw_document(self, document_id: str) -> pandas.core.frame.DataFrame
          -

          Get 'vw_text' for the document with document_id.

          +

          Get 'vw_text' for the document with document_id.

          Parameters

          document_id
          @@ -1895,9 +1922,11 @@

          Returns

          pd.DataFrame
          document_id and content of 'vw_text' column
          -
          +
          -Source code + +Expand source code +
          def get_vw_document(self, document_id: str or List[str]) -> pd.DataFrame:
               """
               Get 'vw_text' for the document with `document_id`.
          @@ -1945,12 +1974,14 @@ 

          Returns

          -def write_vw(self, file_path) +def write_vw(self, file_path: str) -> NoneType
          -

          Saves dataset as text file in Vowpal Wabbit format

          +

          Saves dataset as text file in Vowpal Wabbit format

          -Source code + +Expand source code +
          def write_vw(self, file_path: str) -> None:
               """
               Saves dataset as text file in Vowpal Wabbit format
          @@ -2033,7 +2064,7 @@ 

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/dataset_cooc.html b/docs/cooking_machine/dataset_cooc.html index df452e2..0003134 100644 --- a/docs/cooking_machine/dataset_cooc.html +++ b/docs/cooking_machine/dataset_cooc.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.dataset_cooc API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.dataset_cooc

          -Source code + +Expand source code +
          from .dataset import Dataset
           import artm
           
          @@ -316,10 +318,10 @@ 

          Classes

          class DatasetCooc -(data_path, internals_folder_path=None, cooc_window=10, min_tf=5, min_df=5, threshold=2, **kwargs) +(data_path: str, internals_folder_path: str = None, cooc_window: int = 10, min_tf: int = 5, min_df: int = 5, threshold: int = 2, **kwargs)
          -

          Class prepare dataset in vw format for WNTM model

          +

          Class prepare dataset in vw format for WNTM model

          Parameters

          data_path : str
          @@ -360,9 +362,11 @@

          Parameters

          The frequency threshold above which the received pairs are selected to form the dataset
          -
          +
          -Source code + +Expand source code +
          class DatasetCooc(Dataset):
               """
               Class prepare dataset in vw format for WNTM model
          @@ -642,9 +646,11 @@ 

          Methods

          def clear_all_cooc_files(self)
          -

          Clear cooc_dir folder

          +

          Clear cooc_dir folder

          -Source code + +Expand source code +
          def clear_all_cooc_files(self):
               """
               Clear cooc_dir folder
          @@ -668,9 +674,11 @@ 

          Methods

          def transform_theta(self, model)
          -

          Transform theta matrix

          +

          Transform theta matrix

          -Source code + +Expand source code +
          def transform_theta(self, model):
               """
               Transform theta matrix
          @@ -753,7 +761,7 @@ 

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/experiment.html b/docs/cooking_machine/experiment.html index b45b81c..298e57d 100644 --- a/docs/cooking_machine/experiment.html +++ b/docs/cooking_machine/experiment.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.experiment API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.experiment

          -Source code + +Expand source code +
          import os
           import re
           import json
          @@ -481,7 +483,7 @@ 

          Module topicnet.cooking_machine.experiment

          Experiment """ - from .models import TopicModel + from .models import DummyTopicModel files = os.listdir(load_path) if "params.json" not in files: @@ -496,7 +498,7 @@

          Module topicnet.cooking_machine.experiment

          for model_id in experiment.models.keys(): if model_id != START: model_save_path = os.path.join(load_path, model_id) - experiment.models[model_id] = TopicModel.load( + experiment.models[model_id] = DummyTopicModel.load( model_save_path, experiment ) @@ -667,7 +669,7 @@

          Module topicnet.cooking_machine.experiment

          return [] - def run(self, dataset, verbose=False, nb_verbose=False): + def run(self, dataset, verbose=False, nb_verbose=False, restore_mode=False): # noqa C901 """ Runs defined pipeline and prints out the result. @@ -688,7 +690,22 @@

          Module topicnet.cooking_machine.experiment

          continue cube = cube_description['cube'] - cube(stage_models, dataset) + if not restore_mode: + cube(stage_models, dataset) + else: + if cube_index < self.depth - 1: + print(f"[Restoring experiment]: skipping cube {cube_index}") + continue + if cube_index == self.depth - 1: + print( + f"[Restoring experiment]: selecting models at cube number" + f"{cube_index} (some models could be lost)" + ) + if cube_index >= self.depth: + print( + f"[Restoring experiment]: applying cube number {cube_index}" + ) + cube(stage_models, dataset) # TODO: either delete this line completely # or come up with a way to restore any cube using just info about it in self.cubes @@ -827,7 +844,7 @@

          Module topicnet.cooking_machine.experiment

          req_equal, metric, extremum) = parse_query_string(inner_query_string) if metric is not None or extremum is not None: - warnings.warn(f'You try to optimize model parameters.') + warnings.warn('You try to optimize model parameters.') candidate_tmodels = self.get_models_by_depth(level=level) special_models = choose_best_models( @@ -874,6 +891,9 @@

          Module topicnet.cooking_machine.experiment

          try: self.cubes += [{ 'action': stage_cube.action, + # TODO: should it be 'params': cube_param instead? + # it seems that it is possible to restore failed + # experiment with load() that way..? 'params': stage_cube.get_jsonable_from_parameters(), 'cube': stage_cube }] @@ -918,10 +938,10 @@

          Classes

          class Experiment -(topic_model, experiment_id, save_path, save_model_history=False, save_experiment=True, tree=None, models_info=None, cubes=None, low_memory=False) +(topic_model, experiment_id: str, save_path: str, save_model_history: bool = False, save_experiment: bool = True, tree: dict = None, models_info: dict = None, cubes: List[dict] = None, low_memory: bool = False)
          -

          Contains experiment, its description and descriptions of all models in the experiment.

          +

          Contains experiment, its description and descriptions of all models in the experiment.

          Initialize stage, also used for loading and creating new experiments.

          Parameters

          @@ -954,9 +974,11 @@

          Parameters

          If one wants to use squeezed topic model as before (eg. call topic_model.get_phi()), its inner ARTM model should be restored first. See docstring for TopicModel.make_dummy() method for reference.
          -
          +
          -Source code + +Expand source code +
          class Experiment(object):
               """
               Contains experiment, its description and descriptions of all models in the experiment.
          @@ -1377,7 +1399,7 @@ 

          Parameters

          Experiment """ - from .models import TopicModel + from .models import DummyTopicModel files = os.listdir(load_path) if "params.json" not in files: @@ -1392,7 +1414,7 @@

          Parameters

          for model_id in experiment.models.keys(): if model_id != START: model_save_path = os.path.join(load_path, model_id) - experiment.models[model_id] = TopicModel.load( + experiment.models[model_id] = DummyTopicModel.load( model_save_path, experiment ) @@ -1563,7 +1585,7 @@

          Parameters

          return [] - def run(self, dataset, verbose=False, nb_verbose=False): + def run(self, dataset, verbose=False, nb_verbose=False, restore_mode=False): # noqa C901 """ Runs defined pipeline and prints out the result. @@ -1584,7 +1606,22 @@

          Parameters

          continue cube = cube_description['cube'] - cube(stage_models, dataset) + if not restore_mode: + cube(stage_models, dataset) + else: + if cube_index < self.depth - 1: + print(f"[Restoring experiment]: skipping cube {cube_index}") + continue + if cube_index == self.depth - 1: + print( + f"[Restoring experiment]: selecting models at cube number" + f"{cube_index} (some models could be lost)" + ) + if cube_index >= self.depth: + print( + f"[Restoring experiment]: applying cube number {cube_index}" + ) + cube(stage_models, dataset) # TODO: either delete this line completely # or come up with a way to restore any cube using just info about it in self.cubes @@ -1723,7 +1760,7 @@

          Parameters

          req_equal, metric, extremum) = parse_query_string(inner_query_string) if metric is not None or extremum is not None: - warnings.warn(f'You try to optimize model parameters.') + warnings.warn('You try to optimize model parameters.') candidate_tmodels = self.get_models_by_depth(level=level) special_models = choose_best_models( @@ -1770,6 +1807,9 @@

          Parameters

          try: self.cubes += [{ 'action': stage_cube.action, + # TODO: should it be 'params': cube_param instead? + # it seems that it is possible to restore failed + # experiment with load() that way..? 'params': stage_cube.get_jsonable_from_parameters(), 'cube': stage_cube }] @@ -1808,7 +1848,7 @@

          Static methods

          def load(load_path)
          -

          Loads all params of the experiments. Recovers removed files if it is possible.

          +

          Loads all params of the experiments. Recovers removed files if it is possible.

          Parameters

          load_path : str
          @@ -1816,11 +1856,13 @@

          Parameters

          Returns

          -
          Experiment
          +
          Experiment
           
          -
          +
          -Source code + +Expand source code +
          @staticmethod
           def load(load_path):
               """
          @@ -1836,7 +1878,7 @@ 

          Returns

          Experiment """ - from .models import TopicModel + from .models import DummyTopicModel files = os.listdir(load_path) if "params.json" not in files: @@ -1851,7 +1893,7 @@

          Returns

          for model_id in experiment.models.keys(): if model_id != START: model_save_path = os.path.join(load_path, model_id) - experiment.models[model_id] = TopicModel.load( + experiment.models[model_id] = DummyTopicModel.load( model_save_path, experiment ) @@ -1863,10 +1905,12 @@

          Instance variables

          var depth
          -

          Returns depth of the tree.
          -Be careful, depth of the tree may not be the real experiment depth.

          +

          Returns depth of the tree.
          +Be careful, depth of the tree may not be the real experiment depth.

          -Source code + +Expand source code +
          @property
           def depth(self):
               """
          @@ -1879,9 +1923,11 @@ 

          Instance variables

          var root
          -
          +
          -Source code + +Expand source code +
          @property
           def root(self):
               """ """
          @@ -1895,14 +1941,16 @@ 

          Methods

          def add_cube(self, cube)
          -

          Adds cube to the experiment.

          +

          Adds cube to the experiment.

          Parameters

          cube : dict
          cube's params
          -
          +
          -Source code + +Expand source code +
          def add_cube(self, cube):
               """
               Adds cube to the experiment.
          @@ -1922,16 +1970,18 @@ 

          Parameters

          def add_dataset(self, dataset_id, dataset)
          -

          Adds dataset to storage.

          +

          Adds dataset to storage.

          Parameters

          dataset_id : str
          id of dataset to save
          dataset : Dataset
           
          -
          +
          -Source code + +Expand source code +
          def add_dataset(self, dataset_id, dataset):
               """
               Adds dataset to storage.
          @@ -1953,14 +2003,16 @@ 

          Parameters

          def add_model(self, topic_model)
          -

          Adds model to the experiment.

          +

          Adds model to the experiment.

          Parameters

          topic_model : TopicModel
          topic model
          -
          +
          -Source code + +Expand source code +
          def add_model(self, topic_model):
               """
               Adds model to the experiment.
          @@ -1982,7 +2034,7 @@ 

          Parameters

          def build(self, settings)
          -

          Builds experiment pipeline from description.

          +

          Builds experiment pipeline from description.

          Parameters

          settings : list of dicts
          @@ -1992,9 +2044,11 @@

          Returns

          Nothing
           
          -
          +
          -Source code + +Expand source code +
          def build(self, settings):
               """
               Builds experiment pipeline from description.
          @@ -2026,6 +2080,9 @@ 

          Returns

          try: self.cubes += [{ 'action': stage_cube.action, + # TODO: should it be 'params': cube_param instead? + # it seems that it is possible to restore failed + # experiment with load() that way..? 'params': stage_cube.get_jsonable_from_parameters(), 'cube': stage_cube }] @@ -2039,7 +2096,7 @@

          Returns

          def describe_model(self, model_id)
          -

          Returns all scores mentioned on the model stage criteria.

          +

          Returns all scores mentioned on the model stage criteria.

          Parameters

          model_id : str
          @@ -2049,9 +2106,11 @@

          Returns

          description_string : str
           
          -
          +
          -Source code + +Expand source code +
          def describe_model(self, model_id):
               """
               Returns all scores mentioned on the model stage criteria.
          @@ -2090,10 +2149,10 @@ 

          Returns

          -def get_description(self, min_len_per_cube=26, len_tree_step=27) +def get_description(self, min_len_per_cube: int = 26, len_tree_step: int = 27)
          -

          Creates description of the tree that you can print. +

          Creates description of the tree that you can print. Print is good when you use no more than 3 cubes at all.

          Parameters

          @@ -2108,9 +2167,11 @@

          Returns

          str
          description to print
          -
          +
          -Source code + +Expand source code +
          def get_description(self,
                               min_len_per_cube: int = MODEL_NAME_LENGTH,
                               len_tree_step: int = MODEL_NAME_LENGTH + 1):
          @@ -2147,9 +2208,11 @@ 

          Returns

          def get_models_by_depth(self, level=None)
          -
          +
          -Source code + +Expand source code +
          def get_models_by_depth(self, level=None):
               """ """
               if level is None:
          @@ -2167,14 +2230,16 @@ 

          Returns

          def get_params(self)
          -

          Gets params of the experiment.

          +

          Gets params of the experiment.

          Returns

          parameters : dict
           
          -
          +
          -Source code + +Expand source code +
          def get_params(self):
               """
               Gets params of the experiment.
          @@ -2196,19 +2261,21 @@ 

          Returns

          -def preprocess_query(self, query_string, level) +def preprocess_query(self, query_string: str, level)
          -

          Preprocesses special queries with functions inside.

          +

          Preprocesses special queries with functions inside.

          Parameters

          query_string : str
          string for processing
          level : int
          model level
          -
          +
          -Source code + +Expand source code +
          def preprocess_query(self, query_string: str, level):
               """
               Preprocesses special queries with functions inside.
          @@ -2246,7 +2313,7 @@ 

          Parameters

          req_equal, metric, extremum) = parse_query_string(inner_query_string) if metric is not None or extremum is not None: - warnings.warn(f'You try to optimize model parameters.') + warnings.warn('You try to optimize model parameters.') candidate_tmodels = self.get_models_by_depth(level=level) special_models = choose_best_models( @@ -2267,14 +2334,16 @@

          Parameters

          def remove_dataset(self, dataset_id)
          -

          Removes dataset from storage.

          +

          Removes dataset from storage.

          Parameters

          dataset_id : str
          id of dataset to remove
          -
          +
          -Source code + +Expand source code +
          def remove_dataset(self, dataset_id):
               """
               Removes dataset from storage.
          @@ -2292,10 +2361,10 @@ 

          Parameters

          -def run(self, dataset, verbose=False, nb_verbose=False) +def run(self, dataset, verbose=False, nb_verbose=False, restore_mode=False)
          -

          Runs defined pipeline and prints out the result.

          +

          Runs defined pipeline and prints out the result.

          Parameters

          dataset : Dataset
          @@ -2305,10 +2374,12 @@

          Parameters

          nb_verbose : bool
          parameter that determines where the output is produced if False prints in console (Default value = False)
          -
          +
          -Source code -
          def run(self, dataset, verbose=False, nb_verbose=False):
          +
          +Expand source code
          +
          +
          def run(self, dataset, verbose=False, nb_verbose=False, restore_mode=False):  # noqa C901
               """
               Runs defined pipeline and prints out the result.
           
          @@ -2329,7 +2400,22 @@ 

          Parameters

          continue cube = cube_description['cube'] - cube(stage_models, dataset) + if not restore_mode: + cube(stage_models, dataset) + else: + if cube_index < self.depth - 1: + print(f"[Restoring experiment]: skipping cube {cube_index}") + continue + if cube_index == self.depth - 1: + print( + f"[Restoring experiment]: selecting models at cube number" + f"{cube_index} (some models could be lost)" + ) + if cube_index >= self.depth: + print( + f"[Restoring experiment]: applying cube number {cube_index}" + ) + cube(stage_models, dataset) # TODO: either delete this line completely # or come up with a way to restore any cube using just info about it in self.cubes @@ -2358,17 +2444,19 @@

          Parameters

          -def save(self, window_size=1500, mode='all') +def save(self, window_size: int = 1500, mode: str = 'all')
          -

          Saves all params of the experiment to save_path/experiment_id.

          +

          Saves all params of the experiment to save_path/experiment_id.

          Parameters

          window_size : int
          pixels size of window in html description (Default value = 1500)
          -
          +
          -Source code + +Expand source code +
          def save(self, window_size: int = 1500, mode: str = 'all'):
               """
               Saves all params of the experiment to save_path/experiment_id.
          @@ -2398,7 +2486,7 @@ 

          Parameters

          def save_models(self, mode='all')
          -

          Saves experiment models with respect to selected way of saving.

          +

          Saves experiment models with respect to selected way of saving.

          Parameters

          mode : str
          @@ -2406,9 +2494,11 @@

          Parameters

          'all' - save all models in experiment
          'tree' - save only stem and leaves from the last level
          'last' save only leaves from the last level
          -

          +
          -Source code + +Expand source code +
          def save_models(self, mode='all'):
               """
               Saves experiment models with respect to selected way of saving.
          @@ -2454,7 +2544,7 @@ 

          Parameters

          def select(self, query_string='', models_num=None, level=None)
          -

          Selects all models satisfying the query string +

          Selects all models satisfying the query string from all models on a particular depth.

          Parameters

          @@ -2467,7 +2557,7 @@

          Parameters

          Returns

          -
          result_topic_models : list of restored TopicModels
          +
          result_topic_models : list of restored TopicModels
           

          String Format

          @@ -2507,9 +2597,11 @@

          Examples

          "PerplexityScore@words < 1.1 * MINIMUM(PerplexityScore@all) and model.num_topics > 12" )

          -
          +
          -Source code + +Expand source code +
          def select(self, query_string='', models_num=None, level=None):
               """
               Selects all models satisfying the query string
          @@ -2628,7 +2720,7 @@ 

          Examples

          def set_criteria(self, cube_index, criteria)
          -

          Allows to edit model selection criteria +

          Allows to edit model selection criteria on each stage of the Experiment

          Parameters

          @@ -2641,9 +2733,11 @@

          Returns

          Nothing
           
          -
          +
          -Source code + +Expand source code +
          def set_criteria(self, cube_index, criteria):
               """
               Allows to edit model selection criteria
          @@ -2673,9 +2767,11 @@ 

          Returns

          def show(self)
          -

          Shows description of the experiment.

          +

          Shows description of the experiment.

          -Source code + +Expand source code +
          def show(self):
               """
               Shows description of the experiment.
          @@ -2687,17 +2783,19 @@ 

          Returns

          -def squeeze_models(self, depth=None) +def squeeze_models(self, depth: int = None)
          -

          Transforms models to dummies so as to occupy less RAM memory

          +

          Transforms models to dummies so as to occupy less RAM memory

          Parameters

          depth : int
          Models on what depth are to be squeezed, i.e. transformed to dummies
          -
          +
          -Source code + +Expand source code +
          def squeeze_models(self, depth: int = None):
               """Transforms models to dummies so as to occupy less RAM memory
           
          @@ -2764,7 +2862,7 @@ 

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/index.html b/docs/cooking_machine/index.html index 14a1e0d..0ca01e5 100644 --- a/docs/cooking_machine/index.html +++ b/docs/cooking_machine/index.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine API documentation - - + + @@ -19,38 +19,11 @@

          Module topicnet.cooking_machine

          -

          Cooking Machine

          -

          Cube

          -

          A unit of model training pipeline. This unit encapsulates an action over one or many model hyperparameters. This action and hyperparameter space are stored as cube properties and then saved in Experiment.

          -

          Input: model or list of models, regularizer or list of them, hyperparameter search sapce(grid), iterations number or a function defining it, custom metrics.
          -Output: models.
          -Body: performs actions over artm model. Can modify, create new models and alter their Experiment.

          -

          Model

          -

          A class containing Topic Model and its description:

          -
            -
          • stores topic model description;
          • -
          • outputs the description in human-readable form;
          • -
          • the model can only load and copy itself, the artm-model is an attribute and in order to change it is should be extracted, modified and put back;
          • -
          • stores experiment id;
          • -
          • stores parent model id;
          • -
          • stores model topic names;
          • -
          • stores regularizers list with their parameters;
          • -
          • stores modality weights;
          • -
          • stores save path for data, model and model information;
          • -
          • stores training metric values.
          • -
          -

          Experiment

          -

          Class providing experiment infrastructure:

          -
            -
          • keeps the description of all actions on the models;
          • -
          • provides human-readable log of experiment;
          • -
          • keeps the model training sequence in memory;
          • -
          • automaticly runs integrity check;
          • -
          • able to copy itself.
          • -
          -Source code + +Expand source code +
          from .dataset import Dataset
           from .dataset import BaseDataset
           from .experiment import Experiment
          @@ -63,52 +36,52 @@ 

          Sub-modules

          topicnet.cooking_machine.config_parser
          -

          Parsing text file into Experiment instance using strictyaml -(github.com/crdoconnor/strictyaml/) …

          +

          Parsing text file into Experiment instance using strictyaml +(github.com/crdoconnor/strictyaml/) …

          topicnet.cooking_machine.cubes
          -
          +
          topicnet.cooking_machine.dataset
          -
          +
          topicnet.cooking_machine.dataset_cooc
          -
          +
          topicnet.cooking_machine.experiment
          -
          +
          topicnet.cooking_machine.model_constructor
          -
          +
          topicnet.cooking_machine.model_tracking
          -
          +
          topicnet.cooking_machine.models
          -
          +
          topicnet.cooking_machine.pretty_output
          -
          +
          topicnet.cooking_machine.recipes
          -
          +
          topicnet.cooking_machine.rel_toolbox_lite
          -
          +
          topicnet.cooking_machine.routine
          -
          +
          @@ -120,7 +93,7 @@

          Sub-modules

          diff --git a/docs/cooking_machine/model_constructor.html b/docs/cooking_machine/model_constructor.html index c68ce3b..5699f8b 100644 --- a/docs/cooking_machine/model_constructor.html +++ b/docs/cooking_machine/model_constructor.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.model_constructor API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.model_constructor
          -Source code + +Expand source code +
          import warnings
           
           from typing import (
          @@ -244,10 +246,10 @@ 

          Module topicnet.cooking_machine.model_constructor

          Functions

          -def add_standard_scores(model, dictionary=None, main_modality='@lemmatized', all_modalities=('@lemmatized', '@ngramms')) +def add_standard_scores(model: artm.artm_model.ARTM, dictionary: artm.dictionary.Dictionary = None, main_modality: str = '@lemmatized', all_modalities: List[str] = ('@lemmatized', '@ngramms')) -> NoneType
          -

          Adds standard scores for the model.

          +

          Adds standard scores for the model.

          Parameters

          model
          @@ -258,9 +260,11 @@

          Parameters

           
          all_modalities
           
          -
          +
          -Source code + +Expand source code +
          def add_standard_scores(
                   model: artm.ARTM,
                   dictionary: artm.Dictionary = None,
          @@ -323,7 +327,7 @@ 

          Parameters

          def create_default_topics(specific_topics, background_topics)
          -

          Creates list of background topics and specific topics

          +

          Creates list of background topics and specific topics

          Parameters

          specific_topics : list or int
          @@ -332,9 +336,11 @@

          Parameters

           

          Returns

          -

          (list, list)

          +

          (list, list)

          -Source code + +Expand source code +
          def create_default_topics(specific_topics, background_topics):
               """
               Creates list of background topics and specific topics
          @@ -379,9 +385,11 @@ 

          Returns

          def init_model(topic_names, seed=None, class_ids=None)
          -

          Creates basic artm model

          +

          Creates basic artm model

          -Source code + +Expand source code +
          def init_model(topic_names, seed=None, class_ids=None):
               """
               Creates basic artm model
          @@ -401,10 +409,10 @@ 

          Returns

          -def init_simple_default_model(dataset, modalities_to_use, main_modality, specific_topics, background_topics) +def init_simple_default_model(dataset: Dataset, modalities_to_use: List[str], main_modality: str, specific_topics: List[str], background_topics: List[str]) -> artm.artm_model.ARTM
          -

          Creates simple artm.ARTM model with standard scores.

          +

          Creates simple artm.ARTM model with standard scores.

          Parameters

          dataset
          @@ -431,9 +439,11 @@

          Returns

          model : artm.ARTM
           
          -
          +
          -Source code + +Expand source code +
          def init_simple_default_model(
                   dataset: Dataset,
                   modalities_to_use: List[str] or Dict[str, float],
          @@ -544,7 +554,7 @@ 

          Index

          diff --git a/docs/cooking_machine/model_tracking.html b/docs/cooking_machine/model_tracking.html index 55c2975..eb64232 100644 --- a/docs/cooking_machine/model_tracking.html +++ b/docs/cooking_machine/model_tracking.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.model_tracking API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.model_tracking
          -Source code + +Expand source code +
          import numpy as np
           
           from copy import deepcopy
          @@ -351,18 +353,20 @@ 

          Classes

          class Tree -(tree=None) +(tree: dict = None)
          -

          Contains tree of an experiment and methods to work with it.

          +

          Contains tree of an experiment and methods to work with it.

          Initial stage.

          Parameters

          tree : dict
          tree of an experiment (Default value = None)
          -
          +
          -Source code + +Expand source code +
          class Tree(object):
               """
               Contains tree of an experiment and methods to work with it.
          @@ -669,7 +673,7 @@ 

          Static methods

          def transform_to_leaf(topic_model)
          -

          Transforms TopicModel to a leaf for the tree for further integration in the tree.

          +

          Transforms TopicModel to a leaf for the tree for further integration in the tree.

          Parameters

          topic_model : TopicModel
          @@ -679,9 +683,11 @@

          Returns

          dict
          leaf of the tree
          -
          +
          -Source code + +Expand source code +
          @staticmethod
           def transform_to_leaf(topic_model):
               """
          @@ -711,14 +717,16 @@ 

          Methods

          def add_model(self, topic_model)
          -

          Adds model in the tree of an experiment.

          +

          Adds model in the tree of an experiment.

          Parameters

          topic_model : TopicModel
          topic model
          -
          +
          -Source code + +Expand source code +
          def add_model(self, topic_model):
               """
               Adds model in the tree of an experiment.
          @@ -736,14 +744,16 @@ 

          Parameters

          def clone(self)
          -

          Clones Tree class object.

          +

          Clones Tree class object.

          Returns

          -
          tree : Tree
          +
          tree : Tree
          copy of Tree object
          -
          +
          -Source code + +Expand source code +
          def clone(self):
               """
               Clones Tree class object.
          @@ -762,14 +772,16 @@ 

          Returns

          def get_depth(self)
          -

          Gets current depth of the tree.

          +

          Gets current depth of the tree.

          Returns

          int
          depth of the tree
          -
          +
          -Source code + +Expand source code +
          def get_depth(self):
               """
               Gets current depth of the tree.
          @@ -787,14 +799,16 @@ 

          Returns

          def get_description(self)
          -

          Creates description of the tree.

          +

          Creates description of the tree.

          Returns

          list
          strings of description
          -
          +
          -Source code + +Expand source code +
          def get_description(self):
               """
               Creates description of the tree.
          @@ -814,14 +828,16 @@ 

          Returns

          def get_model_ids(self)
          -

          Gets models_ids of all models in the tree.

          +

          Gets models_ids of all models in the tree.

          Returns

          list
          model_ids of all models in the tree
          -
          +
          -Source code + +Expand source code +
          def get_model_ids(self):
               """
               Gets models_ids of all models in the tree.
          @@ -839,14 +855,16 @@ 

          Returns

          def prune(self, depth)
          -

          Prunes tree to get particular depth and updates it.

          +

          Prunes tree to get particular depth and updates it.

          Parameters

          depth : int
          desired tree depth
          -
          +
          -Source code + +Expand source code +
          def prune(self, depth):
               """
               Prunes tree to get particular depth and updates it.
          @@ -864,15 +882,17 @@ 

          Parameters

          def show(self)
          -

          Shows the tree of an experiment in text format. +

          Shows the tree of an experiment in text format. Shows description ot the tree.

          Returns

          str
          description in txt format
          -
          +
          -Source code + +Expand source code +
          def show(self):
               """
               Shows the tree of an experiment in text format.
          @@ -925,7 +945,7 @@ 

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/base_model.html b/docs/cooking_machine/models/base_model.html index d57972c..9467dc1 100644 --- a/docs/cooking_machine/models/base_model.html +++ b/docs/cooking_machine/models/base_model.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.base_model API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.base_model
          -Source code + +Expand source code +
          import json
           import os
           from copy import deepcopy
          @@ -350,9 +352,11 @@ 

          Functions

          def padd_model_name(model_id)
          -
          +
          -Source code + +Expand source code +
          def padd_model_name(model_id):
               padding = MODEL_NAME_LENGTH - len(model_id)
           
          @@ -374,7 +378,7 @@ 

          Classes

          (model_id=None, parent_model_id=None, experiment=None, *args, **kwargs)
          -

          Initialize stage, also used for loading previously saved experiments.

          +

          Initialize stage, also used for loading previously saved experiments.

          Parameters

          model_id : str
          @@ -383,9 +387,11 @@

          Parameters

          model id from which current model was created (Default value = None)
          experiment : Experiment
          the experiment to which the model is bound (Default value = None)
          -
          +
          -Source code + +Expand source code +
          class BaseModel(object):
               def __init__(self, model_id=None, parent_model_id=None, experiment=None, *args, **kwargs):
                   """
          @@ -683,8 +689,6 @@ 

          Parameters

          Subclasses

          • TopicModel
          • -
          • topicnet.tests.test_intratext_coherence_score.MockModel
          • -
          • topicnet.tests.test_topic_mapping.dummy_model

          Static methods

          @@ -692,13 +696,15 @@

          Static methods

          def load(path, *args, **kwargs)
          -

          Parameters

          +

          Parameters

          path : str
           
          -
          +
          -Source code + +Expand source code +
          @staticmethod
           def load(path, *args, **kwargs):
               """
          @@ -716,9 +722,11 @@ 

          Instance variables

          var depth
          -

          Returns depth of the model.

          +

          Returns depth of the model.

          -Source code + +Expand source code +
          @property
           def depth(self):
               """
          @@ -730,9 +738,11 @@ 

          Instance variables

          var description
          -
          +
          -Source code + +Expand source code +
          @property
           def description(self):
               """ """
          @@ -741,9 +751,11 @@ 

          Instance variables

          var model_default_save_path
          -
          +
          -Source code + +Expand source code +
          @property
           def model_default_save_path(self):
               """ """
          @@ -772,9 +784,11 @@ 

          Instance variables

          var model_id
          -
          +
          -Source code + +Expand source code +
          @property
           def model_id(self):
               """ """
          @@ -783,9 +797,11 @@ 

          Instance variables

          var parent_model_id
          -
          +
          -Source code + +Expand source code +
          @property
           def parent_model_id(self):
               """ """
          @@ -794,9 +810,11 @@ 

          Instance variables

          var score_functions
          -
          +
          -Source code + +Expand source code +
          @property
           def score_functions(self):
               """ """
          @@ -805,9 +823,11 @@ 

          Instance variables

          var scores
          -
          +
          -Source code + +Expand source code +
          @property
           def scores(self):
               """ """
          @@ -821,14 +841,16 @@ 

          Methods

          def add_cube(self, cube)
          -

          Adds cube to the model.

          +

          Adds cube to the model.

          Parameters

          cube : dict
          training cube params.
          -
          +
          -Source code + +Expand source code +
          def add_cube(self, cube):
               """
               Adds cube to the model.
          @@ -847,9 +869,11 @@ 

          Parameters

          def clone(self)
          -
          +
          -Source code + +Expand source code +
          def clone(self):
               """ """
               return deepcopy(self)
          @@ -859,9 +883,11 @@

          Parameters

          def get_jsonable_from_parameters(self)
          -
          +
          -Source code + +Expand source code +
          def get_jsonable_from_parameters(self):
               """ """
               raise NotImplementedError
          @@ -871,14 +897,16 @@

          Parameters

          def get_parameters(self)
          -

          Gets all params of the model.

          +

          Gets all params of the model.

          Returns

          dict
          parameters of the model
          -
          +
          -Source code + +Expand source code +
          def get_parameters(self):
               """
               Gets all params of the model.
          @@ -910,9 +938,11 @@ 

          Returns

          def get_phi(self, *args, **kwargs)
          -
          +
          -Source code + +Expand source code +
          def get_phi(self, *args, **kwargs):
               """ """
               raise NotImplementedError
          @@ -922,13 +952,15 @@

          Returns

          def get_theta(self, dataset=None, *args, **kwargs)
          -

          Parameters

          +

          Parameters

          dataset : Dataset
          (Default value = None)
          -
          +
          -Source code + +Expand source code +
          def get_theta(self, dataset=None, *args, **kwargs):
               """
           
          @@ -945,13 +977,15 @@ 

          Returns

          def save(self, path, *args, **kwargs)
          -

          Parameters

          +

          Parameters

          path : str
           
          -
          +
          -Source code + +Expand source code +
          def save(self, path, *args, **kwargs):
               """
           
          @@ -967,9 +1001,11 @@ 

          Returns

          def save_parameters(self, model_save_path=None)
          -

          Saves params of the model.

          +

          Saves params of the model.

          -Source code + +Expand source code +
          def save_parameters(self, model_save_path=None):
               """
               Saves params of the model.
          @@ -988,9 +1024,11 @@ 

          Returns

          def set_model_id_as_timestamp(self)
          -
          +
          -Source code + +Expand source code +
          def set_model_id_as_timestamp(self):
               """ """
               self._model_id = padd_model_name(get_timestamp_in_str_format())
          @@ -1047,7 +1085,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/base_regularizer.html b/docs/cooking_machine/models/base_regularizer.html index db1dbd1..2283130 100644 --- a/docs/cooking_machine/models/base_regularizer.html +++ b/docs/cooking_machine/models/base_regularizer.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.base_regularizer API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.base_regularizer<
          -Source code + +Expand source code +
          class BaseRegularizer:
               """
               Base regularizer class to construct custom regularizers.
          @@ -60,9 +62,11 @@ 

          Classes

          (name, tau, gamma=None)
          -

          Base regularizer class to construct custom regularizers.

          +

          Base regularizer class to construct custom regularizers.

          -Source code + +Expand source code +
          class BaseRegularizer:
               """
               Base regularizer class to construct custom regularizers.
          @@ -98,13 +102,15 @@ 

          Methods

          def attach(self, model)
          -

          Parameters

          +

          Parameters

          -
          model : ARTM model
          +
          model : ARTM model
          necessary to apply master component
          -
          +
          -Source code + +Expand source code +
          def attach(self, model):
               """
           
          @@ -120,9 +126,11 @@ 

          Methods

          def grad(self, pwt, nwt)
          -
          +
          -Source code + +Expand source code +
          def grad(self, pwt, nwt):
               raise NotImplementedError('grad method should be overrided in an inherited class')
          @@ -158,7 +166,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/base_score.html b/docs/cooking_machine/models/base_score.html index 6e28d94..ebdb7b3 100644 --- a/docs/cooking_machine/models/base_score.html +++ b/docs/cooking_machine/models/base_score.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.base_score API documentation - - + + @@ -21,8 +21,17 @@

          Module topicnet.cooking_machine.models.base_score
          -Source code + +Expand source code +
          import dill
          +
          +from typing import (
          +    Any,
          +    Callable,
          +    Dict,
          +)
          +
           from . import scores as tn_scores
           
           
          @@ -31,21 +40,93 @@ 

          Module topicnet.cooking_machine.models.base_score Base Class to construct custom score functions. """ - def __init__(self, name: str = None): # TODO: name should not be optional + _PRECOMPUTED_DATA_PARAMETER_NAME = 'precomputed_data' + + # TODO: name should not be optional + def __init__( + self, + name: str = None, + should_compute: Callable[[int], bool] or bool = None): """ Parameters ---------- - name: + name Name of the score - + should_compute + Function which decides whether the score should be computed + on the current fit iteration or not. + If `should_compute` is `None`, then score is going to be computed on every iteration. + At the same time, whatever function one defines, + score is always computed on the last fit iteration. + This is done for two reasons. + Firstly, so that the score is always computed at least once during `model._fit()`. + Secondly, so that `experiment.select()` works correctly. + + The parameter `should_compute` might be helpful + if the score is slow but one still needs + to get the dependence of the score on iteration + (for the described case, one may compute the score + on every even iteration or somehow else). + However, be aware that if `should_compute` is used for some model's scores, + then the scores may have different number of values in `model.scores`! + Number of score values is the number of times the scores was calculated; + first value corresponds to the first fit iteration + which passed `should_compute` etc. + + There are a couple of things also worth noting. + Fit iteration numbering starts from zero. + And every new `model._fit()` call is a new range of fit iterations. + + Examples + -------- + Scores created below are unworkable (as BaseScore has no `call` method inplemented). + These are just the examples of how one can create a score and set some of its parameters. + + Scores to be computed on every iteration: + + >>> score = BaseScore() + >>> score = BaseScore(should_compute=BaseScore.compute_always) + >>> score = BaseScore(should_compute=lambda i: True) + >>> score = BaseScore(should_compute=True) + + Scores to be computed only on the last iteration: + + >>> score = BaseScore(should_compute=BaseScore.compute_on_last) + >>> score = BaseScore(should_compute=lambda i: False) + >>> score = BaseScore(should_compute=False) + + Score to be computed only on even iterations: + + >>> score = BaseScore(should_compute=lambda i: i % 2 == 0) """ self._name = name + + if should_compute is None: + should_compute = self.compute_always + elif should_compute is True: + should_compute = self.compute_always + elif should_compute is False: + should_compute = self.compute_on_last + elif not isinstance(should_compute, type(lambda: None)): + raise TypeError(f'Unknown type of `should_compute`: {type(should_compute)}!') + else: + pass + + self._should_compute = should_compute self.value = [] if not hasattr(tn_scores, self.__class__.__name__): setattr(tn_scores, self.__class__.__name__, self.__class__) + @staticmethod + def compute_always(fit_iteration: int) -> bool: + return True + + @staticmethod + def compute_on_last(fit_iteration: int) -> bool: + return False + def __repr__(self): return f'{self.__class__.__name__}' @@ -81,7 +162,7 @@

          Module topicnet.cooking_machine.models.base_score self.value.append(score) - def call(self, model): + def call(self, model, precomputed_data: Dict[str, Any] = None): """ Call to custom score function. @@ -89,6 +170,12 @@

          Module topicnet.cooking_machine.models.base_score ---------- model : TopicModel a TopicNet model inherited from BaseModel + precomputed_data + Data which scores may share between each other during *one fit iteration*. + For example, if the model has several scores of the same score class, + and there is a heavy time consuming computation inside this score class, + it may be useful to perform the calculations *only once*, for one score instance, + and then make the result visible for all other scores that might need it. Returns ------- @@ -102,6 +189,45 @@

          Module topicnet.cooking_machine.models.base_score and then use this logic in query in Experiment's `select()` method. If one need ARTM model for score (not TopicNet one), it is available as model._model + + When creating a custom score class, + it is recommended to use `**kwargs` in the score's `call` method, + so that all `BaseScore` optional parameters are also available + in its successor score classes. + + Examples + -------- + + Score which uses `precomputed_data`: + + >>> import time + ... + >>> class NewScore(BaseScore): + ... def __init__(self, name: str, multiplier: float): + ... super().__init__(name=name) + ... + ... self._multiplier = multiplier + ... self._heavy_value_name = 'time_consuming_value_name' + ... + ... def call(self, model, precomputed_data = None): + ... if precomputed_data is None: + ... # Parameter `precomputed_data` is optional in BaseScore + ... # So this case also should be supported + ... heavy_value = self._compute_heavy(model) + ... elif self._heavy_value_name in precomputed_data: + ... # This is going to be fast + ... heavy_value = precomputed_data[self._heavy_value_name] + ... else: + ... # This is slow (but only one such call!) + ... heavy_value = self._compute_heavy(model) + ... precomputed_data[self._heavy_value_name] = heavy_value + ... + ... return heavy_value * self._multiplier + ... + ... def _compute_heavy(self, model): + ... time.sleep(100) # just for demonstration + ... + ... return 0 """ raise NotImplementedError('Define your score here')

          @@ -117,35 +243,152 @@

          Classes

          class BaseScore -(name=None) +(name: str = None, should_compute: Callable[[int], bool] = None)
          -

          Base Class to construct custom score functions.

          +

          Base Class to construct custom score functions.

          Parameters

          -

          name: -Name of the score

          +
          +
          name
          +
          Name of the score
          +
          should_compute
          +
          +

          Function which decides whether the score should be computed +on the current fit iteration or not. +If should_compute is None, then score is going to be computed on every iteration. +At the same time, whatever function one defines, +score is always computed on the last fit iteration. +This is done for two reasons. +Firstly, so that the score is always computed at least once during model._fit(). +Secondly, so that experiment.select() works correctly.

          +

          The parameter should_compute might be helpful +if the score is slow but one still needs +to get the dependence of the score on iteration +(for the described case, one may compute the score +on every even iteration or somehow else). +However, be aware that if should_compute is used for some model's scores, +then the scores may have different number of values in model.scores! +Number of score values is the number of times the scores was calculated; +first value corresponds to the first fit iteration +which passed should_compute etc.

          +

          There are a couple of things also worth noting. +Fit iteration numbering starts from zero. +And every new model._fit() call is a new range of fit iterations.

          +
          +
          +

          Examples

          +

          Scores created below are unworkable (as BaseScore has no call method inplemented). +These are just the examples of how one can create a score and set some of its parameters.

          +

          Scores to be computed on every iteration:

          +
          >>> score = BaseScore()
          +>>> score = BaseScore(should_compute=BaseScore.compute_always)
          +>>> score = BaseScore(should_compute=lambda i: True)
          +>>> score = BaseScore(should_compute=True)
          +
          +

          Scores to be computed only on the last iteration:

          +
          >>> score = BaseScore(should_compute=BaseScore.compute_on_last)
          +>>> score = BaseScore(should_compute=lambda i: False)
          +>>> score = BaseScore(should_compute=False)
          +
          +

          Score to be computed only on even iterations:

          +
          >>> score = BaseScore(should_compute=lambda i: i % 2 == 0)
          +
          -Source code + +Expand source code +
          class BaseScore:
               """
               Base Class to construct custom score functions.
           
               """
          -    def __init__(self, name: str = None):  # TODO: name should not be optional
          +    _PRECOMPUTED_DATA_PARAMETER_NAME = 'precomputed_data'
          +
          +    # TODO: name should not be optional
          +    def __init__(
          +            self,
          +            name: str = None,
          +            should_compute: Callable[[int], bool] or bool = None):
                   """
           
                   Parameters
                   ----------
          -        name:
          +        name
                       Name of the score
          -
          +        should_compute
          +            Function which decides whether the score should be computed
          +            on the current fit iteration or not.
          +            If `should_compute` is `None`, then score is going to be computed on every iteration.
          +            At the same time, whatever function one defines,
          +            score is always computed on the last fit iteration.
          +            This is done for two reasons.
          +            Firstly, so that the score is always computed at least once during `model._fit()`.
          +            Secondly, so that `experiment.select()` works correctly.
          +
          +            The parameter `should_compute` might be helpful
          +            if the score is slow but one still needs
          +            to get the dependence of the score on iteration
          +            (for the described case, one may compute the score
          +            on every even iteration or somehow else).
          +            However, be aware that if `should_compute` is used for some model's scores,
          +            then the scores may have different number of values in `model.scores`!
          +            Number of score values is the number of times the scores was calculated;
          +            first value corresponds to the first fit iteration
          +            which passed `should_compute` etc.
          +
          +            There are a couple of things also worth noting.
          +            Fit iteration numbering starts from zero.
          +            And every new `model._fit()` call is a new range of fit iterations.
          +
          +        Examples
          +        --------
          +        Scores created below are unworkable (as BaseScore has no `call` method inplemented).
          +        These are just the examples of how one can create a score and set some of its parameters.
          +
          +        Scores to be computed on every iteration:
          +
          +        >>> score = BaseScore()
          +        >>> score = BaseScore(should_compute=BaseScore.compute_always)
          +        >>> score = BaseScore(should_compute=lambda i: True)
          +        >>> score = BaseScore(should_compute=True)
          +
          +        Scores to be computed only on the last iteration:
          +
          +        >>> score = BaseScore(should_compute=BaseScore.compute_on_last)
          +        >>> score = BaseScore(should_compute=lambda i: False)
          +        >>> score = BaseScore(should_compute=False)
          +
          +        Score to be computed only on even iterations:
          +
          +        >>> score = BaseScore(should_compute=lambda i: i % 2 == 0)
                   """
                   self._name = name
          +
          +        if should_compute is None:
          +            should_compute = self.compute_always
          +        elif should_compute is True:
          +            should_compute = self.compute_always
          +        elif should_compute is False:
          +            should_compute = self.compute_on_last
          +        elif not isinstance(should_compute, type(lambda: None)):
          +            raise TypeError(f'Unknown type of `should_compute`: {type(should_compute)}!')
          +        else:
          +            pass
          +
          +        self._should_compute = should_compute
                   self.value = []
           
                   if not hasattr(tn_scores, self.__class__.__name__):
                       setattr(tn_scores, self.__class__.__name__, self.__class__)
           
          +    @staticmethod
          +    def compute_always(fit_iteration: int) -> bool:
          +        return True
          +
          +    @staticmethod
          +    def compute_on_last(fit_iteration: int) -> bool:
          +        return False
          +
               def __repr__(self):
                   return f'{self.__class__.__name__}'
           
          @@ -181,7 +424,7 @@ 

          Parameters

          self.value.append(score) - def call(self, model): + def call(self, model, precomputed_data: Dict[str, Any] = None): """ Call to custom score function. @@ -189,6 +432,12 @@

          Parameters

          ---------- model : TopicModel a TopicNet model inherited from BaseModel + precomputed_data + Data which scores may share between each other during *one fit iteration*. + For example, if the model has several scores of the same score class, + and there is a heavy time consuming computation inside this score class, + it may be useful to perform the calculations *only once*, for one score instance, + and then make the result visible for all other scores that might need it. Returns ------- @@ -202,26 +451,95 @@

          Parameters

          and then use this logic in query in Experiment's `select()` method. If one need ARTM model for score (not TopicNet one), it is available as model._model + + When creating a custom score class, + it is recommended to use `**kwargs` in the score's `call` method, + so that all `BaseScore` optional parameters are also available + in its successor score classes. + + Examples + -------- + + Score which uses `precomputed_data`: + + >>> import time + ... + >>> class NewScore(BaseScore): + ... def __init__(self, name: str, multiplier: float): + ... super().__init__(name=name) + ... + ... self._multiplier = multiplier + ... self._heavy_value_name = 'time_consuming_value_name' + ... + ... def call(self, model, precomputed_data = None): + ... if precomputed_data is None: + ... # Parameter `precomputed_data` is optional in BaseScore + ... # So this case also should be supported + ... heavy_value = self._compute_heavy(model) + ... elif self._heavy_value_name in precomputed_data: + ... # This is going to be fast + ... heavy_value = precomputed_data[self._heavy_value_name] + ... else: + ... # This is slow (but only one such call!) + ... heavy_value = self._compute_heavy(model) + ... precomputed_data[self._heavy_value_name] = heavy_value + ... + ... return heavy_value * self._multiplier + ... + ... def _compute_heavy(self, model): + ... time.sleep(100) # just for demonstration + ... + ... return 0 """ raise NotImplementedError('Define your score here')

          Subclasses

          Static methods

          +
          +def compute_always(fit_iteration: int) -> bool +
          +
          +
          +
          + +Expand source code + +
          @staticmethod
          +def compute_always(fit_iteration: int) -> bool:
          +    return True
          +
          +
          +
          +def compute_on_last(fit_iteration: int) -> bool +
          +
          +
          +
          + +Expand source code + +
          @staticmethod
          +def compute_on_last(fit_iteration: int) -> bool:
          +    return False
          +
          +
          def load(path)
          -
          +
          -Source code + +Expand source code +
          @classmethod
           def load(cls, path):
               with open(path, "rb") as f:
          @@ -234,14 +552,20 @@ 

          Static methods

          Methods

          -def call(self, model) +def call(self, model, precomputed_data: Dict[str, Any] = None)
          -

          Call to custom score function.

          +

          Call to custom score function.

          Parameters

          model : TopicModel
          a TopicNet model inherited from BaseModel
          +
          precomputed_data
          +
          Data which scores may share between each other during one fit iteration. +For example, if the model has several scores of the same score class, +and there is a heavy time consuming computation inside this score class, +it may be useful to perform the calculations only once, for one score instance, +and then make the result visible for all other scores that might need it.

          Returns

          @@ -252,10 +576,47 @@

          Notes

          Higher score not necessarily should correspond to better model. It is up to user to decide what the meaning is behind the score, and then use this logic in query in Experiment's select() method.

          -

          If one need ARTM model for score (not TopicNet one), it is available as model._model

          +

          If one need ARTM model for score (not TopicNet one), it is available as model._model

          +

          When creating a custom score class, +it is recommended to use **kwargs in the score's call method, +so that all BaseScore optional parameters are also available +in its successor score classes.

          +

          Examples

          +

          Score which uses precomputed_data:

          +
          >>> import time
          +...
          +>>> class NewScore(BaseScore):
          +...     def __init__(self, name: str, multiplier: float):
          +...         super().__init__(name=name)
          +...
          +...         self._multiplier = multiplier
          +...         self._heavy_value_name = 'time_consuming_value_name'
          +...
          +...     def call(self, model, precomputed_data = None):
          +...         if precomputed_data is None:
          +...             # Parameter <code>precomputed\_data</code> is optional in BaseScore
          +...             # So this case also should be supported
          +...             heavy_value = self._compute_heavy(model)
          +...         elif self._heavy_value_name in precomputed_data:
          +...             # This is going to be fast
          +...             heavy_value = precomputed_data[self._heavy_value_name]
          +...         else:
          +...             # This is slow (but only one such call!)
          +...             heavy_value = self._compute_heavy(model)
          +...             precomputed_data[self._heavy_value_name] = heavy_value
          +...
          +...         return heavy_value * self._multiplier
          +...
          +...     def _compute_heavy(self, model):
          +...         time.sleep(100)  # just for demonstration
          +...
          +...         return 0
          +
          -Source code -
          def call(self, model):
          +
          +Expand source code
          +
          +
          def call(self, model, precomputed_data: Dict[str, Any] = None):
               """
               Call to custom score function.
           
          @@ -263,6 +624,12 @@ 

          Notes

          ---------- model : TopicModel a TopicNet model inherited from BaseModel + precomputed_data + Data which scores may share between each other during *one fit iteration*. + For example, if the model has several scores of the same score class, + and there is a heavy time consuming computation inside this score class, + it may be useful to perform the calculations *only once*, for one score instance, + and then make the result visible for all other scores that might need it. Returns ------- @@ -276,6 +643,45 @@

          Notes

          and then use this logic in query in Experiment's `select()` method. If one need ARTM model for score (not TopicNet one), it is available as model._model + + When creating a custom score class, + it is recommended to use `**kwargs` in the score's `call` method, + so that all `BaseScore` optional parameters are also available + in its successor score classes. + + Examples + -------- + + Score which uses `precomputed_data`: + + >>> import time + ... + >>> class NewScore(BaseScore): + ... def __init__(self, name: str, multiplier: float): + ... super().__init__(name=name) + ... + ... self._multiplier = multiplier + ... self._heavy_value_name = 'time_consuming_value_name' + ... + ... def call(self, model, precomputed_data = None): + ... if precomputed_data is None: + ... # Parameter `precomputed_data` is optional in BaseScore + ... # So this case also should be supported + ... heavy_value = self._compute_heavy(model) + ... elif self._heavy_value_name in precomputed_data: + ... # This is going to be fast + ... heavy_value = precomputed_data[self._heavy_value_name] + ... else: + ... # This is slow (but only one such call!) + ... heavy_value = self._compute_heavy(model) + ... precomputed_data[self._heavy_value_name] = heavy_value + ... + ... return heavy_value * self._multiplier + ... + ... def _compute_heavy(self, model): + ... time.sleep(100) # just for demonstration + ... + ... return 0 """ raise NotImplementedError('Define your score here')
          @@ -284,9 +690,11 @@

          Notes

          def save(self, path)
          -
          +
          -Source code + +Expand source code +
          def save(self, path):
               with open(path, "wb") as f:
                   dill.dump(self, f)
          @@ -296,14 +704,16 @@

          Notes

          def update(self, score)
          -

          Parameters

          +

          Parameters

          score : float
          score value
          -

          Returns

          +

          Returns

          -Source code + +Expand source code +
          def update(self, score):
               """
           
          @@ -346,8 +756,10 @@ 

          Index

          • BaseScore

            -
              +
              • call
              • +
              • compute_always
              • +
              • compute_on_last
              • load
              • save
              • update
              • @@ -359,7 +771,7 @@

                -

                Generated by pdoc 0.6.3.

                +

                Generated by pdoc 0.8.1.

                diff --git a/docs/cooking_machine/models/blei_lafferty_score.html b/docs/cooking_machine/models/blei_lafferty_score.html index c2c9e5c..b2c1dde 100644 --- a/docs/cooking_machine/models/blei_lafferty_score.html +++ b/docs/cooking_machine/models/blei_lafferty_score.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.blei_lafferty_score API documentation - - + + @@ -21,8 +21,13 @@

                Module topicnet.cooking_machine.models.blei_lafferty_sco
                -Source code + +Expand source code +
                import numpy as np
                +
                +from typing import Callable
                +
                 from .base_score import BaseScore
                 
                 
                @@ -35,7 +40,11 @@ 

                Module topicnet.cooking_machine.models.blei_lafferty_sco to describe given topic. Summing up that score helps to estimate how well the model distinguishes between topics. The higher this score - better """ - def __init__(self, name: str = None, num_top_tokens: int = 30): + def __init__( + self, + name: str = None, + num_top_tokens: int = 30, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -46,7 +55,7 @@

                Module topicnet.cooking_machine.models.blei_lafferty_sco now many tokens we consider to be """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.num_top_tokens = num_top_tokens @@ -84,7 +93,7 @@

                Module topicnet.cooking_machine.models.blei_lafferty_sco scores = phi * multiplier return scores - def call(self, model): + def call(self, model, **kwargs): modalities = list(model.class_ids.keys()) score = 0 @@ -110,10 +119,10 @@

                Classes

                class BleiLaffertyScore -(name=None, num_top_tokens=30) +(name: str = None, num_top_tokens: int = 30, should_compute: Callable[[int], bool] = None)
                -

                This score implements method described in 2009 paper +

                This score implements method described in 2009 paper Blei, David M., and John D. Lafferty. "Topic models." Text Mining. Chapman and Hall/CRC, 2009. 101-124. At the core this score helps to discover tokens that are most likely @@ -125,9 +134,11 @@

                Parameters

                name of the score
                num_top_tokens : int
                now many tokens we consider to be
                -

                +

          -Source code + +Expand source code +
          class BleiLaffertyScore(BaseScore):
               """
               This score implements method described in 2009 paper
          @@ -137,7 +148,11 @@ 

          Parameters

          to describe given topic. Summing up that score helps to estimate how well the model distinguishes between topics. The higher this score - better """ - def __init__(self, name: str = None, num_top_tokens: int = 30): + def __init__( + self, + name: str = None, + num_top_tokens: int = 30, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -148,7 +163,7 @@

          Parameters

          now many tokens we consider to be """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.num_top_tokens = num_top_tokens @@ -186,7 +201,7 @@

          Parameters

          scores = phi * multiplier return scores - def call(self, model): + def call(self, model, **kwargs): modalities = list(model.class_ids.keys()) score = 0 @@ -239,7 +254,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/dummy_topic_model.html b/docs/cooking_machine/models/dummy_topic_model.html index 979efea..41aedb1 100644 --- a/docs/cooking_machine/models/dummy_topic_model.html +++ b/docs/cooking_machine/models/dummy_topic_model.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.dummy_topic_model API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.dummy_topic_model
          -Source code + +Expand source code +
          import artm
           import json
           import os
          @@ -394,6 +396,9 @@ 

          Module topicnet.cooking_machine.models.dummy_topic_model raise InvalidOperationError() def add_cube(self, cube): + raise InvalidOperationError() + + def describe_regularizers(self): raise InvalidOperationError()

          @@ -411,12 +416,14 @@

          Classes

          (scores, init_parameters=None, model_id=None, parent_model_id=None, description=None, experiment=None, save_path=None, *args, **kwargs)
          -

          Topic Model contains artm model and all necessary information: scores, training pipeline, etc.

          +

          Topic Model contains artm model and all necessary information: scores, training pipeline, etc.

          Notes

          Only TopicModel supposed to be able to create DummyTopicModel -("private" < access < "public")

          +("private" < access < "public")

          -Source code + +Expand source code +
          class DummyTopicModel(TopicModel):
               _dummy_attribute = '_is_dummy'
           
          @@ -702,9 +709,11 @@ 

          Instance variables

          var class_ids
          -
          +
          -Source code + +Expand source code +
          @property
           def class_ids(self):
               """"""
          @@ -718,19 +727,21 @@ 

          Methods

          def get_init_parameters(self, not_include=None)
          -
          +
          -Source code + +Expand source code +
          def get_init_parameters(self, not_include=None):
               """"""
               return self._init_parameters
          -def restore(self, dataset=None) +def restore(self, dataset: Dataset = None)
          -

          Restores dummy to original TopicModel

          +

          Restores dummy to original TopicModel

          Tries to load the data from drive (if model was saved). Otherwise tries to train the model using parent model, experiment and dataset.

          Parameters

          @@ -744,9 +755,11 @@

          Returns

          TopicModel
          Restored topic model
          -
          +
          -Source code + +Expand source code +
          def restore(self, dataset: Dataset = None):
               """Restores dummy to original TopicModel
           
          @@ -793,6 +806,7 @@ 

          Inherited members

        • get_phi
        • get_phi_dense
        • get_phi_sparse
        • +
        • get_regularizer
        • get_theta
        • load
        • make_dummy
        • @@ -811,9 +825,11 @@

          Inherited members

          (message="Dummy model can't do this")
          -

          Unspecified run-time error.

          +

          Unspecified run-time error.

          -Source code + +Expand source code +
          class InvalidOperationError(RuntimeError):
               def __init__(self, message='Dummy model can\'t do this'):
                   super().__init__(message)
          @@ -858,7 +874,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/example_score.html b/docs/cooking_machine/models/example_score.html index 6d9320c..22f2653 100644 --- a/docs/cooking_machine/models/example_score.html +++ b/docs/cooking_machine/models/example_score.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.example_score API documentation - - + + @@ -21,8 +21,13 @@

          Module topicnet.cooking_machine.models.example_score
          -Source code + +Expand source code +
          import numpy as np
          +
          +from typing import Callable
          +
           from .base_score import BaseScore
           
           
          @@ -34,7 +39,11 @@ 

          Module topicnet.cooking_machine.models.example_scoreModule topicnet.cooking_machine.models.example_scoreClasses

          class ScoreExample -(name=None, token_threshold=0.001) +(name: str = None, token_threshold: float = 0.001, should_compute: Callable[[int], bool] = None)
          -

          Example score that calculates +

          Example score that calculates average size of topic kernel across all topics. We inherit from BaseScore in order to have self.value property and self.update() method (the internal logic of TopicNet relies on them)

          @@ -93,9 +102,11 @@

          Parameters

          name of the score
          token_threshold : float
          what probabilities to take as token belonging to the topic
          -
          +

          -Source code + +Expand source code +
          class ScoreExample(BaseScore):
               """
               Example score that calculates
          @@ -104,7 +115,11 @@ 

          Parameters

          (the internal logic of TopicNet relies on them) """ - def __init__(self, name: str = None, token_threshold: float = 1e-3): + def __init__( + self, + name: str = None, + token_threshold: float = 1e-3, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -115,11 +130,11 @@

          Parameters

          what probabilities to take as token belonging to the topic """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.threshold = token_threshold - def call(self, model): + def call(self, model, **kwargs): """ Method that calculates the score @@ -145,10 +160,10 @@

          Ancestors

          Methods

          -def call(self, model) +def call(self, model, **kwargs)
          -

          Method that calculates the score

          +

          Method that calculates the score

          Parameters

          model : TopicModel
          @@ -158,10 +173,12 @@

          Returns

          score : float
          mean kernel size for all topics in the model
          -
          +
          -Source code -
          def call(self, model):
          +
          +Expand source code
          +
          +
          def call(self, model, **kwargs):
               """
               Method that calculates the score
           
          @@ -219,7 +236,7 @@ 

          diff --git a/docs/cooking_machine/models/frozen_score.html b/docs/cooking_machine/models/frozen_score.html index b37d832..2d424ba 100644 --- a/docs/cooking_machine/models/frozen_score.html +++ b/docs/cooking_machine/models/frozen_score.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.frozen_score API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.frozen_score
          -Source code + +Expand source code +
          import warnings
           
           from enum import Enum
          @@ -121,18 +123,63 @@ 

          Classes

          class FrozenScore -(value, original_score=None) +(value: List[Union[float, NoneType]], original_score: BaseScore = None)
          -

          Custom scores can have anything inside. +

          Custom scores can have anything inside. So there is a probability that pickle will not be able to dump them. Frozen score helps to store the value of the original score without its internal logic, so as it can be saved.

          Parameters

          -

          name: -Name of the score

          +
          +
          name
          +
          Name of the score
          +
          should_compute
          +
          +

          Function which decides whether the score should be computed +on the current fit iteration or not. +If should_compute is None, then score is going to be computed on every iteration. +At the same time, whatever function one defines, +score is always computed on the last fit iteration. +This is done for two reasons. +Firstly, so that the score is always computed at least once during model._fit(). +Secondly, so that experiment.select() works correctly.

          +

          The parameter should_compute might be helpful +if the score is slow but one still needs +to get the dependence of the score on iteration +(for the described case, one may compute the score +on every even iteration or somehow else). +However, be aware that if should_compute is used for some model's scores, +then the scores may have different number of values in model.scores! +Number of score values is the number of times the scores was calculated; +first value corresponds to the first fit iteration +which passed should_compute etc.

          +

          There are a couple of things also worth noting. +Fit iteration numbering starts from zero. +And every new model._fit() call is a new range of fit iterations.

          +
          +
          +

          Examples

          +

          Scores created below are unworkable (as BaseScore has no call method inplemented). +These are just the examples of how one can create a score and set some of its parameters.

          +

          Scores to be computed on every iteration:

          +
          >>> score = BaseScore()
          +>>> score = BaseScore(should_compute=BaseScore.compute_always)
          +>>> score = BaseScore(should_compute=lambda i: True)
          +>>> score = BaseScore(should_compute=True)
          +
          +

          Scores to be computed only on the last iteration:

          +
          >>> score = BaseScore(should_compute=BaseScore.compute_on_last)
          +>>> score = BaseScore(should_compute=lambda i: False)
          +>>> score = BaseScore(should_compute=False)
          +
          +

          Score to be computed only on even iterations:

          +
          >>> score = BaseScore(should_compute=lambda i: i % 2 == 0)
          +
          -Source code + +Expand source code +
          class FrozenScore(BaseScore):
               """
               Custom scores can have anything inside.
          @@ -215,15 +262,17 @@ 

          Ancestors

          Methods

          -def update(self, score_value) +def update(self, score_value: float) -> NoneType
          -

          Update is not supposed to be applied to Frozen score. +

          Update is not supposed to be applied to Frozen score. It is not supposed to be changed. Still, the situation with an endeavour to update can generally happen if one tries -to train the model further after loading.

          +to train the model further after loading.

          -Source code + +Expand source code +
          def update(self, score_value: float) -> None:
               """
               Update is not supposed to be applied to Frozen score.
          @@ -286,7 +335,7 @@ 

          diff --git a/docs/cooking_machine/models/index.html b/docs/cooking_machine/models/index.html index ba57f76..6d87e37 100644 --- a/docs/cooking_machine/models/index.html +++ b/docs/cooking_machine/models/index.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models API documentation - - + + @@ -19,57 +19,15 @@

          Module topicnet.cooking_machine.models

          -

          Models and scores

          -

          Availiable models:

          -
            -
          • BaseModel — Parent class for model creation
          • -
          • TopicModel — a wrapper class for bigartm topic model
          • -
          • DummyTopicModel — a fake model that contains training information but not actual artm model. Needed to save memory space during the training.
          • -
          -
          -

          Availiable scores:

          -
            -
          • BaseScore — a parent class for all the Strategies
          • -
          • ExampleScore — Example of minimal working example of custom score
          • -
          • IntratextCoherenceScore — score that calculates coherence as a measure of interpretability of the model using raw documents from dataset. Calculation-heavy score. Recommended to be used after model training
          • -
          • BleiLaffertyScore — An experimental light-weight score to estimate interpretability of the topics
          • -
          • SemanticRadiusScore — An experimental score reflecting whether collection is adequately described by topics. Lower if better. Calculation-heavy score.
          • -
          -
          -

          Internal model structure

          -

          main model attributes:

          -
            -
          • model_id — a model string id, unique for its Experiment.

          • -
          • scores — dict of lists, each list corresponds to the score value or list of values at certain training stage.

          • -
          • custom_scores — variable providing custom scores for the model

          • -
          • custom_regularizers — variable providing custom regularizers for the model. An example is provided in topic_prior_regularizer.py.

          • -
          -

          main model methods:

          -
            -
          • _fit — function performing model training. Takes the dataset and number of iterations. Optionally, you can pass custom_regularizers here, if you wish to apply them to a single iteration.

            -

            Important Notice! We assume that the model training happens through Cube interface and this method, while important should never be used by users if they are hope to have their actions logged

          • -
          • get_phi — function that returns p(token|topic/cluster) probability distributions that returns pandas.DataFrame with tokens as index and topics/clusters as columns

            -

            Important Notice! Strictly speaking the function returns degree to which token belongs to the topic/cluster and shouldn’t be a probability distribution. But scince its main use-case intended for topic models some of the functions using this method might work incorrectly in non-distribution case

          • -
          • get_theta — function that returns p(topic/cluster|document) probability distributions that returns pandas.DataFrame with topics/clusters as index and document ids as columns.

            -

            Important Notice! Strictly speaking the function returns degree to which document belongs to the topic/cluster and shouldn’t be a probability distribution. But scince its main use-case intended for topic models some of the functions using this method might work incorrectly in non-distribution case

          • -
          • save — saves model to the path directory.

          • -
          • load — loads model from the path directory

          • -
          • clone — creates copy of a model.

          • -
          • get_jsonable_from_parameters — turns model parameters to jsonable format for logging purposes

          • -
          -
          -

          What do you need to create your own model?

          -

          Following this steps you should be able to code a model integrated with the library methods:

          -
            -
          1. New model class is inherrited from BaseModel

          2. -
          3. A child class should contain methods __init__, _fit, get_phi, get_theta, save, load, clone, get_jsonable_from_parameters.

          4. -
          -Source code + +Expand source code +
          from .base_model import BaseModel
           from .topic_model import TopicModel
           from .dummy_topic_model import DummyTopicModel
          +
           from .base_score import BaseScore
           from .example_score import ScoreExample
           from .intratext_coherence_score import IntratextCoherenceScore
          @@ -84,59 +42,59 @@ 

          Sub-modules

          topicnet.cooking_machine.models.base_model
          -
          +
          topicnet.cooking_machine.models.base_regularizer
          -
          +
          topicnet.cooking_machine.models.base_score
          -
          +
          topicnet.cooking_machine.models.blei_lafferty_score
          -
          +
          topicnet.cooking_machine.models.dummy_topic_model
          -
          +
          topicnet.cooking_machine.models.example_score
          -
          +
          topicnet.cooking_machine.models.frozen_score
          -
          +
          topicnet.cooking_machine.models.intratext_coherence_score
          -
          +
          topicnet.cooking_machine.models.scores
          -
          +
          topicnet.cooking_machine.models.scores_wrapper
          -
          +
          topicnet.cooking_machine.models.semantic_radius_score
          -
          +
          topicnet.cooking_machine.models.thetaless_regularizer
          -
          +
          topicnet.cooking_machine.models.topic_model
          -
          +
          topicnet.cooking_machine.models.topic_prior_regularizer
          -
          +
          @@ -148,7 +106,7 @@

          Sub-modules

          diff --git a/docs/cooking_machine/models/intratext_coherence_score.html b/docs/cooking_machine/models/intratext_coherence_score.html index f110817..2c0e902 100644 --- a/docs/cooking_machine/models/intratext_coherence_score.html +++ b/docs/cooking_machine/models/intratext_coherence_score.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.intratext_coherence_score API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.intratext_coheren
          -Source code + +Expand source code +
          import dill
           import numpy as np
           import pandas as pd
          @@ -32,6 +34,7 @@ 

          Module topicnet.cooking_machine.models.intratext_coheren from collections import defaultdict from enum import Enum, IntEnum, auto from typing import ( + Callable, Dict, List, Optional, @@ -129,6 +132,7 @@

          Module topicnet.cooking_machine.models.intratext_coheren self, dataset: Union[Dataset, str], name: str = None, + should_compute: Callable[[int], bool] = None, keep_dataset_in_memory: bool = None, keep_dataset: bool = True, documents: List[str] = None, @@ -213,7 +217,7 @@

          Module topicnet.cooking_machine.models.intratext_coheren >>> topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iterations) """ # TODO: word_topic_relatedness seems to be connected with TopTokensViewer stuff - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self._keep_dataset = keep_dataset @@ -387,7 +391,7 @@

          Module topicnet.cooking_machine.models.intratext_coheren return score - def call(self, model: BaseModel) -> float: + def call(self, model: BaseModel, **kwargs) -> float: if (self._current_iteration - self._start_fit_iteration) % self._fit_iteration_step != 0: self._current_iteration += 1 @@ -736,10 +740,10 @@

          Classes

          class ComputationMethod -(*args, **kwargs) +(value, names=None, *, module=None, qualname=None, type=None, start=1)
          -

          Ways to compute intra-text coherence +

          Ways to compute intra-text coherence (see more about coherence below in IntratextCoherenceScore)

          Attributes

          SEGMENT_LENGTH : @@ -751,9 +755,11 @@

          Attributes

          Sum of specificities for the topic over words in given window. The process is as follows: word of the topic is found in text, it is the center of the first window; -next word of the topic is found (outside of the previous window), window; etc

          +next word of the topic is found (outside of the previous window), window; etc

          -Source code + +Expand source code +
          class ComputationMethod(IntEnum):
               """
               Ways to compute intra-text coherence
          @@ -786,24 +792,24 @@ 

          Class variables

          var SEGMENT_LENGTH
          -
          +
          var SEGMENT_WEIGHT
          -
          +
          var SUM_OVER_WINDOW
          -
          +
          class IntratextCoherenceScore -(dataset, name=None, keep_dataset_in_memory=None, keep_dataset=True, documents=None, documents_fraction=1.0, text_type=, computation_method=, word_topic_relatedness=, specificity_estimation=, max_num_out_of_topic_words=10, window=20, start_fit_iteration=0, fit_iteration_step=1, seed=11221963, verbose=False) +(dataset: Union[topicnet.cooking_machine.dataset.Dataset, str], name: str = None, should_compute: Callable[[int], bool] = None, keep_dataset_in_memory: bool = None, keep_dataset: bool = True, documents: List[str] = None, documents_fraction: float = 1.0, text_type: TextType = TextType.VW_TEXT, computation_method: ComputationMethod = ComputationMethod.SEGMENT_WEIGHT, word_topic_relatedness: WordTopicRelatednessType = WordTopicRelatednessType.PWT, specificity_estimation: SpecificityEstimationMethod = SpecificityEstimationMethod.NONE, max_num_out_of_topic_words: int = 10, window: int = 20, start_fit_iteration: int = 0, fit_iteration_step: int = 1, seed: int = 11221963, verbose: bool = False)
          -

          Computes intratext coherence

          +

          Computes intratext coherence

          For each topic of topic model its distribution throughout document collection is observed. Hypothetically, the better the topic, the more often it is represented by long segments of words highly related to the topic. @@ -830,16 +836,16 @@

          Parameters

          documents_fraction
          The fraction of all the documents in the Dataset to be used for coherence computation if documents parameter is not specified
          -
          text_type : TextType
          +
          text_type : TextType
          What text to use when computing coherence: raw text or VW text Preferable to use VW (as it is usually preprocessed, stop-words removed etc.), and with words in natural order. Score needs "real" text to compute coherence
          -
          computation_method : ComputationMethod
          +
          computation_method : ComputationMethod
          The way to compute intra-text coherence
          -
          word_topic_relatedness : WordTopicRelatednessType
          +
          word_topic_relatedness : WordTopicRelatednessType
          How to estimate word relevance to topic: using p(w | t) or p(t | w)
          -
          specificity_estimation : SpecificityEstimationMethod
          +
          specificity_estimation : SpecificityEstimationMethod
          How to estimate specificity of word to topic
          max_num_out_of_topic_words : int
          In case computation_method = ComputationMethod.SEGMENT_LENGTH or @@ -866,7 +872,7 @@

          Notes

          at the end of the training process (and not in the dependence of score on iteration), one should adjust start_fit_iteration and fit_iteration_step correspondingly. For example:

          -
          >>> # dataset = Dataset(...)
          +
          >>> # dataset = Dataset(...)
           >>> # topic_model = TopicModel(...)
           >>> num_iterations = 100
           >>> topic_model.custom_scores['intratext_coherence'] = IntratextCoherenceScore(
          @@ -874,9 +880,11 @@ 

          Notes

          >>> start_fit_iteration=num_iterations - 1 # last iteration: starting from zero >>> ) >>> topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iterations) -
          +

          -Source code + +Expand source code +
          class IntratextCoherenceScore(BaseScore):
               """Computes intratext coherence
           
          @@ -891,6 +899,7 @@ 

          Notes

          self, dataset: Union[Dataset, str], name: str = None, + should_compute: Callable[[int], bool] = None, keep_dataset_in_memory: bool = None, keep_dataset: bool = True, documents: List[str] = None, @@ -975,7 +984,7 @@

          Notes

          >>> topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iterations) """ # TODO: word_topic_relatedness seems to be connected with TopTokensViewer stuff - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self._keep_dataset = keep_dataset @@ -1149,7 +1158,7 @@

          Notes

          return score - def call(self, model: BaseModel) -> float: + def call(self, model: BaseModel, **kwargs) -> float: if (self._current_iteration - self._start_fit_iteration) % self._fit_iteration_step != 0: self._current_iteration += 1 @@ -1493,21 +1502,23 @@

          Ancestors

          Static methods

          -def load(path) +def load(path: str)
          -

          Parameters

          +

          Parameters

          path
           

          Returns

          -
          IntratextCoherenceScore
          +
          IntratextCoherenceScore
           
          -
          +
          -Source code + +Expand source code +
          @classmethod
           def load(cls, path: str):
               """
          @@ -1541,11 +1552,13 @@ 

          Returns

          Instance variables

          -
          var dataset
          +
          var datasetDataset
          -
          +
          -Source code + +Expand source code +
          @property
           def dataset(self) -> Dataset:
               return self._dataset
          @@ -1555,12 +1568,14 @@

          Instance variables

          Methods

          -def compute(self, model, topics=None, documents=None) +def compute(self, model: BaseModel, topics: List[str] = None, documents: List[str] = None) -> Dict[str, Union[float, NoneType]]
          -
          +
          -Source code + +Expand source code +
          def compute(
                   self,
                   model: BaseModel,
          @@ -1624,12 +1639,14 @@ 

          Methods

          -def save(self, path) +def save(self, path: str) -> NoneType
          -
          +
          -Source code + +Expand source code +
          def save(self, path: str) -> None:
               dataset = self._dataset
               self._dataset = None
          @@ -1653,10 +1670,10 @@ 

          Inherited members

          class SpecificityEstimationMethod -(*args, **kwargs) +(value, names=None, *, module=None, qualname=None, type=None, start=1)
          -

          Way to estimate how particular word is specific for particular topic. +

          Way to estimate how particular word is specific for particular topic. Unlike probability, eg. p(w | t), specificity_estimation takes into account values for all topics, eg. p(w | t_1), p(w | t_2), …, p(w | t_n): the higher the value p(w | t) comparing other p(w | t_i), @@ -1669,9 +1686,11 @@

          Attributes

          extract maximum among probabilities for the word and other topics AVERAGE : From probability, corresponding to word and topic, -extract average among probabilities for the word and other topics

          +extract average among probabilities for the word and other topics

          -Source code + +Expand source code +
          class SpecificityEstimationMethod(IntEnum):
               """
               Way to estimate how particular word is specific for particular topic.
          @@ -1705,26 +1724,28 @@ 

          Class variables

          var AVERAGE
          -
          +
          var MAXIMUM
          -
          +
          var NONE
          -
          +
          class TextType -(*args, **kwargs) +(value, names=None, *, module=None, qualname=None, type=None, start=1)
          -

          An enumeration.

          +

          An enumeration.

          -Source code + +Expand source code +
          class TextType(Enum):
               VW_TEXT = VW_TEXT_COL
               RAW_TEXT = RAW_TEXT_COL
          @@ -1737,27 +1758,29 @@

          Class variables

          var RAW_TEXT
          -
          +
          var VW_TEXT
          -
          +
          class WordTopicRelatednessType -(*args, **kwargs) +(value, names=None, *, module=None, qualname=None, type=None, start=1)
          -

          Word-topic relatedness estimate

          +

          Word-topic relatedness estimate

          Attributes

          PWT : p(w | t) PTW : -p(t | w)

          +p(t | w)

          -Source code + +Expand source code +
          class WordTopicRelatednessType(IntEnum):
               """
               Word-topic relatedness estimate
          @@ -1782,11 +1805,11 @@ 

          Class variables

          var PTW
          -
          +
          var PWT
          -
          +
          @@ -1851,7 +1874,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/scores.html b/docs/cooking_machine/models/scores.html index 2f8238f..016e55a 100644 --- a/docs/cooking_machine/models/scores.html +++ b/docs/cooking_machine/models/scores.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.scores API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.scores

          -Source code + +Expand source code +
          from .example_score import ScoreExample
           from .intratext_coherence_score import IntratextCoherenceScore
           from .blei_lafferty_score import BleiLaffertyScore
          @@ -41,10 +43,10 @@ 

          Classes

          class BleiLaffertyScore -(name=None, num_top_tokens=30) +(name: str = None, num_top_tokens: int = 30, should_compute: Callable[[int], bool] = None)
          -

          This score implements method described in 2009 paper +

          This score implements method described in 2009 paper Blei, David M., and John D. Lafferty. "Topic models." Text Mining. Chapman and Hall/CRC, 2009. 101-124. At the core this score helps to discover tokens that are most likely @@ -56,9 +58,11 @@

          Parameters

          name of the score
          num_top_tokens : int
          now many tokens we consider to be
          -
          +

          -Source code + +Expand source code +
          class BleiLaffertyScore(BaseScore):
               """
               This score implements method described in 2009 paper
          @@ -68,7 +72,11 @@ 

          Parameters

          to describe given topic. Summing up that score helps to estimate how well the model distinguishes between topics. The higher this score - better """ - def __init__(self, name: str = None, num_top_tokens: int = 30): + def __init__( + self, + name: str = None, + num_top_tokens: int = 30, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -79,7 +87,7 @@

          Parameters

          now many tokens we consider to be """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.num_top_tokens = num_top_tokens @@ -117,7 +125,7 @@

          Parameters

          scores = phi * multiplier return scores - def call(self, model): + def call(self, model, **kwargs): modalities = list(model.class_ids.keys()) score = 0 @@ -147,10 +155,10 @@

          Inherited members

          class IntratextCoherenceScore -(dataset, name=None, keep_dataset_in_memory=None, keep_dataset=True, documents=None, documents_fraction=1.0, text_type=, computation_method=, word_topic_relatedness=, specificity_estimation=, max_num_out_of_topic_words=10, window=20, start_fit_iteration=0, fit_iteration_step=1, seed=11221963, verbose=False) +(dataset: Union[topicnet.cooking_machine.dataset.Dataset, str], name: str = None, should_compute: Callable[[int], bool] = None, keep_dataset_in_memory: bool = None, keep_dataset: bool = True, documents: List[str] = None, documents_fraction: float = 1.0, text_type: TextType = TextType.VW_TEXT, computation_method: ComputationMethod = ComputationMethod.SEGMENT_WEIGHT, word_topic_relatedness: WordTopicRelatednessType = WordTopicRelatednessType.PWT, specificity_estimation: SpecificityEstimationMethod = SpecificityEstimationMethod.NONE, max_num_out_of_topic_words: int = 10, window: int = 20, start_fit_iteration: int = 0, fit_iteration_step: int = 1, seed: int = 11221963, verbose: bool = False)
          -

          Computes intratext coherence

          +

          Computes intratext coherence

          For each topic of topic model its distribution throughout document collection is observed. Hypothetically, the better the topic, the more often it is represented by long segments of words highly related to the topic. @@ -213,7 +221,7 @@

          Notes

          at the end of the training process (and not in the dependence of score on iteration), one should adjust start_fit_iteration and fit_iteration_step correspondingly. For example:

          -
          >>> # dataset = Dataset(...)
          +
          >>> # dataset = Dataset(...)
           >>> # topic_model = TopicModel(...)
           >>> num_iterations = 100
           >>> topic_model.custom_scores['intratext_coherence'] = IntratextCoherenceScore(
          @@ -221,9 +229,11 @@ 

          Notes

          >>> start_fit_iteration=num_iterations - 1 # last iteration: starting from zero >>> ) >>> topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iterations) -
          +

          -Source code + +Expand source code +
          class IntratextCoherenceScore(BaseScore):
               """Computes intratext coherence
           
          @@ -238,6 +248,7 @@ 

          Notes

          self, dataset: Union[Dataset, str], name: str = None, + should_compute: Callable[[int], bool] = None, keep_dataset_in_memory: bool = None, keep_dataset: bool = True, documents: List[str] = None, @@ -322,7 +333,7 @@

          Notes

          >>> topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iterations) """ # TODO: word_topic_relatedness seems to be connected with TopTokensViewer stuff - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self._keep_dataset = keep_dataset @@ -496,7 +507,7 @@

          Notes

          return score - def call(self, model: BaseModel) -> float: + def call(self, model: BaseModel, **kwargs) -> float: if (self._current_iteration - self._start_fit_iteration) % self._fit_iteration_step != 0: self._current_iteration += 1 @@ -840,21 +851,23 @@

          Ancestors

          Static methods

          -def load(path) +def load(path: str)
          -

          Parameters

          +

          Parameters

          path
           

          Returns

          -
          IntratextCoherenceScore
          +
          IntratextCoherenceScore
           
          -
          +
          -Source code + +Expand source code +
          @classmethod
           def load(cls, path: str):
               """
          @@ -888,11 +901,13 @@ 

          Returns

          Instance variables

          -
          var dataset
          +
          var datasetDataset
          -
          +
          -Source code + +Expand source code +
          @property
           def dataset(self) -> Dataset:
               return self._dataset
          @@ -902,12 +917,14 @@

          Instance variables

          Methods

          -def compute(self, model, topics=None, documents=None) +def compute(self, model: BaseModel, topics: List[str] = None, documents: List[str] = None) -> Dict[str, Union[float, NoneType]]
          -
          +
          -Source code + +Expand source code +
          def compute(
                   self,
                   model: BaseModel,
          @@ -971,12 +988,14 @@ 

          Methods

          -def save(self, path) +def save(self, path: str) -> NoneType
          -
          +
          -Source code + +Expand source code +
          def save(self, path: str) -> None:
               dataset = self._dataset
               self._dataset = None
          @@ -1000,10 +1019,10 @@ 

          Inherited members

          class ScoreExample -(name=None, token_threshold=0.001) +(name: str = None, token_threshold: float = 0.001, should_compute: Callable[[int], bool] = None)
          -

          Example score that calculates +

          Example score that calculates average size of topic kernel across all topics. We inherit from BaseScore in order to have self.value property and self.update() method (the internal logic of TopicNet relies on them)

          @@ -1013,9 +1032,11 @@

          Parameters

          name of the score
          token_threshold : float
          what probabilities to take as token belonging to the topic
          -
          +
          -Source code + +Expand source code +
          class ScoreExample(BaseScore):
               """
               Example score that calculates
          @@ -1024,7 +1045,11 @@ 

          Parameters

          (the internal logic of TopicNet relies on them) """ - def __init__(self, name: str = None, token_threshold: float = 1e-3): + def __init__( + self, + name: str = None, + token_threshold: float = 1e-3, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -1035,11 +1060,11 @@

          Parameters

          what probabilities to take as token belonging to the topic """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.threshold = token_threshold - def call(self, model): + def call(self, model, **kwargs): """ Method that calculates the score @@ -1065,10 +1090,10 @@

          Ancestors

          Methods

          -def call(self, model) +def call(self, model, **kwargs)
          -

          Method that calculates the score

          +

          Method that calculates the score

          Parameters

          model : TopicModel
          @@ -1078,10 +1103,12 @@

          Returns

          score : float
          mean kernel size for all topics in the model
          -
          +
          -Source code -
          def call(self, model):
          +
          +Expand source code
          +
          +
          def call(self, model, **kwargs):
               """
               Method that calculates the score
           
          @@ -1113,10 +1140,10 @@ 

          Inherited members

          class SemanticRadiusScore -(batch_vectorizer, name=None) +(batch_vectorizer, name: str = None)
          -

          This score implements cluster semantic radius, described in paper +

          This score implements cluster semantic radius, described in paper 'Проверка гипотезы условной независимости для оценивания качества тематической кластеризации' by Rogozina A. At the core this score helps to discover topics uniformity. @@ -1127,9 +1154,11 @@

          Parameters

          Name of the score
          batch_vectorizer
           
          -
          +
          -Source code + +Expand source code +
          class SemanticRadiusScore(BaseScore):
               """
               This score implements cluster semantic radius, described in paper
          @@ -1209,7 +1238,7 @@ 

          Methods

          def call(self, model, max_sampled_document_len=None, sample_step=5, sample_size=3, alpha=0.1)
          -

          Parameters

          +

          Parameters

          model : TopicModel
           
          @@ -1225,9 +1254,11 @@

          Methods

          alpha : float
          (1 - alpha) quantile level, must be <= 1
          (Default value = 0.1)
          -
          +
          -Source code + +Expand source code +
          def call(self, model, max_sampled_document_len=None, sample_step=5, sample_size=3, alpha=0.1):
               """
           
          @@ -1321,7 +1352,7 @@ 

          diff --git a/docs/cooking_machine/models/scores_wrapper.html b/docs/cooking_machine/models/scores_wrapper.html index c3e1add..bb0418f 100644 --- a/docs/cooking_machine/models/scores_wrapper.html +++ b/docs/cooking_machine/models/scores_wrapper.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.scores_wrapper API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.scores_wrapper
          -Source code + +Expand source code +
          import artm
           import copy
           from collections.abc import Mapping
          @@ -82,9 +84,9 @@ 

          Module topicnet.cooking_machine.models.scores_wrapperClasses

          class ScoresWrapper -(topicnet_scores, artm_scores) +(topicnet_scores: Dict[str, topicnet.cooking_machine.models.base_score.BaseScore], artm_scores: artm.scores.Scores)
          -
          +
          -Source code + +Expand source code +
          class ScoresWrapper(Mapping):
               def __init__(self,
                            topicnet_scores: Dict[str, BaseScore],
          @@ -169,9 +173,9 @@ 

          Classes

          elif isinstance(score, BaseScore): if score._name is None: raise ValueError( - f'When using `model.scores.add(score)` method,' - f' one should specify score name parameter during score initialization.' - f' For example `model.scores.add(IntratextCoherenceScore(name="name", ...))' + 'When using `model.scores.add(score)` method,' + ' one should specify score name parameter during score initialization.' + ' For example `model.scores.add(IntratextCoherenceScore(name="name", ...))' ) self._topicnet_scores[score._name] = score @@ -204,12 +208,14 @@

          Ancestors

          Methods

          -def add(self, score) +def add(self, score: Union[topicnet.cooking_machine.models.base_score.BaseScore, artm.scores.BaseScore])
          -
          +
          -Source code + +Expand source code +
          def add(self, score: Union[BaseScore, artm.scores.BaseScore]):
               if isinstance(score, FrozenScore):
                   raise TypeError('FrozenScore is not supposed to be added to model')
          @@ -217,9 +223,9 @@ 

          Methods

          elif isinstance(score, BaseScore): if score._name is None: raise ValueError( - f'When using `model.scores.add(score)` method,' - f' one should specify score name parameter during score initialization.' - f' For example `model.scores.add(IntratextCoherenceScore(name="name", ...))' + 'When using `model.scores.add(score)` method,' + ' one should specify score name parameter during score initialization.' + ' For example `model.scores.add(IntratextCoherenceScore(name="name", ...))' ) self._topicnet_scores[score._name] = score @@ -266,7 +272,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/semantic_radius_score.html b/docs/cooking_machine/models/semantic_radius_score.html index 24c1fdf..e4a14ba 100644 --- a/docs/cooking_machine/models/semantic_radius_score.html +++ b/docs/cooking_machine/models/semantic_radius_score.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.semantic_radius_score API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.semantic_radius_s
          -Source code + +Expand source code +
          import artm
           
           import operator
          @@ -243,9 +245,11 @@ 

          Functions

          def calculate_n(model, batch_vectorizer)
          -

          Calculate all necessary statistics from batch. This may take some time.

          +

          Calculate all necessary statistics from batch. This may take some time.

          -Source code + +Expand source code +
          def calculate_n(model, batch_vectorizer):
               """
               Calculate all necessary statistics from batch. This may take some time.
          @@ -296,9 +300,11 @@ 

          Functions

          def cressie_reed_sampled(topic, ntdw_calc, ntd_calc, nwt, nt, gimel=-0.5)
          -

          Calculate Cressie-Reed divergence for sampled pseudo-document.

          +

          Calculate Cressie-Reed divergence for sampled pseudo-document.

          -Source code + +Expand source code +
          def cressie_reed_sampled(topic, ntdw_calc, ntd_calc, nwt, nt, gimel=-1/2):
               """
               Calculate Cressie-Reed divergence for sampled pseudo-document.
          @@ -326,9 +332,11 @@ 

          Functions

          def radii_for_ntd(ntd, regression_coeff)
          -
          +
          -Source code + +Expand source code +
          def radii_for_ntd(ntd, regression_coeff):
               return ntd.apply(lambda x: third_degree(x, *regression_coeff))
          @@ -337,9 +345,11 @@

          Functions

          def radii_vs_ntd(max_len, sample_step, sample_size, nwt, nt, alpha)
          -
          +
          -Source code + +Expand source code +
          def radii_vs_ntd(max_len, sample_step, sample_size, nwt, nt, alpha):
               regression_coeffs = []
               for topic in range(len(nt)):
          @@ -354,9 +364,11 @@ 

          Functions

          def radius_for_ntd(ntd, regression_coeff)
          -
          +
          -Source code + +Expand source code +
          def radius_for_ntd(ntd, regression_coeff):
               return third_degree(ntd, *regression_coeff)
          @@ -365,9 +377,11 @@

          Functions

          def radius_vs_ndt(topic, max_len, sample_step, sample_size, nwt, nt, alpha)
          -

          Calculate third degree approximation for radius vs ndt dependency.

          +

          Calculate third degree approximation for radius vs ndt dependency.

          -Source code + +Expand source code +
          def radius_vs_ndt(topic, max_len, sample_step, sample_size, nwt, nt, alpha):
               """
               Calculate third degree approximation for radius vs ndt dependency.
          @@ -393,9 +407,11 @@ 

          Functions

          def synthetic_doc_ntdw_and_ntd(doc_len, nwt)
          -

          Create synthetic document from nwt with specific doc_len.

          +

          Create synthetic document from nwt with specific doc_len.

          -Source code + +Expand source code +
          def synthetic_doc_ntdw_and_ntd(doc_len, nwt):
               """
               Create synthetic document from nwt with specific doc_len.
          @@ -416,9 +432,11 @@ 

          Functions

          def third_degree(x, a, b, c, d)
          -
          +
          -Source code + +Expand source code +
          def third_degree(x, a, b, c, d):
               return a + b * x + c * x ** 2 + d * x ** 3
          @@ -430,10 +448,10 @@

          Classes

          class SemanticRadiusScore -(batch_vectorizer, name=None) +(batch_vectorizer, name: str = None)
          -

          This score implements cluster semantic radius, described in paper +

          This score implements cluster semantic radius, described in paper 'Проверка гипотезы условной независимости для оценивания качества тематической кластеризации' by Rogozina A. At the core this score helps to discover topics uniformity. @@ -444,9 +462,11 @@

          Parameters

          Name of the score
          batch_vectorizer
           
          -
          +

          -Source code + +Expand source code +
          class SemanticRadiusScore(BaseScore):
               """
               This score implements cluster semantic radius, described in paper
          @@ -526,7 +546,7 @@ 

          Methods

          def call(self, model, max_sampled_document_len=None, sample_step=5, sample_size=3, alpha=0.1)
          -

          Parameters

          +

          Parameters

          model : TopicModel
           
          @@ -542,9 +562,11 @@

          Methods

          alpha : float
          (1 - alpha) quantile level, must be <= 1
          (Default value = 0.1)
          -
          +
          -Source code + +Expand source code +
          def call(self, model, max_sampled_document_len=None, sample_step=5, sample_size=3, alpha=0.1):
               """
           
          @@ -632,7 +654,7 @@ 

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/thetaless_regularizer.html b/docs/cooking_machine/models/thetaless_regularizer.html index cef6a37..9288ffd 100644 --- a/docs/cooking_machine/models/thetaless_regularizer.html +++ b/docs/cooking_machine/models/thetaless_regularizer.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.thetaless_regularizer API documentation - - + + @@ -21,17 +21,167 @@

          Module topicnet.cooking_machine.models.thetaless_regular
          -Source code + +Expand source code +
          import numpy as np
          -from numba import jit
          +import os
          +import pandas as pd
           import scipy.sparse
          +import warnings
          +
          +from numba import jit
          +
          +import artm
           
           from .base_regularizer import BaseRegularizer
          +from ..dataset import Dataset
          +
          +
          +# TODO: move this to BigARTM
          +# ==================================
          +
          +FIELDS = 'token class_id token_value token_tf token_df'.split()
          +
          +
          +def artm_dict2df(artm_dict):
          +    """
          +    :Description: converts the BigARTM dictionary of the collection
          +        to the pandas.DataFrame.
          +        This is approximately equivalent to the dictionary.save_text()
          +        but has no I/O overhead
          +
          +    """
          +    dictionary_data = artm_dict._master.get_dictionary(artm_dict._name)
          +    dict_pandas = {field: getattr(dictionary_data, field)
          +                   for field in FIELDS}
          +    return pd.DataFrame(dict_pandas)
          +
          +# ==================================
           
           
           EPS = 1e-20
           
           
          +# TODO: is there a better way to do this?
          +def obtain_token2id(dataset: Dataset):
          +    """
          +    Allows one to obtain the mapping from token to the artm.dictionary id of that token
          +    (useful for low-level operations such as reading batches manually)
          +
          +    Returns
          +    -------
          +    dict:
          +        maps (token, class_id) to integer (corresponding to the row of Phi / dictionary id)
          +
          +    """
          +    df = artm_dict2df(dataset.get_dictionary())
          +    df_inverted_index = df[['token', 'class_id']].reset_index().set_index(['token', 'class_id'])
          +
          +    return df_inverted_index.to_dict()['index']
          +
          +
          +def dataset2sparse_matrix(dataset, modality, modalities_to_use=None):
          +    """
          +    Builds a sparse matrix from batch_vectorizer linked to the Dataset
          +
          +    If you need an inverse mapping:
          +
          +    >>> d = sparse_n_dw_matrix.todok()  # convert to dictionary of keys format
          +    >>> dict_of_csr = dict(d.items())
          +
          +    Parameters
          +    ----------
          +    dataset: Dataset
          +    modality: str
          +        the remaining modalities will be ignored
          +        (their occurrences will be replaced with zeros, but they will continue to exist)
          +    modalities_to_use: iterable
          +        a set of modalities the underlying topic model is using (this is about topic model,
          +        not regularizer; this parameter ensures that the shapes of n_dw matrix and actual
          +        Phi matrix match).
          +
          +        The tokens outside of this list will be discarded utterly
          +        (the resulting matrix will have no entries corresponding to them)
          +
          +        For artm.ARTM() models, you need to pass whatever is inside class_ids;
          +        while TopicModel usually requires this to be set inside modalities_to_use.
          +
          +        If you hadn't explicitly listed any modalities yet, you probably could
          +        leave this argument as None.
          +
          +        If you use a single modality, wrap it into a list (e.g.['@word'])
          +
          +    Returns
          +    -------
          +    n_dw_matrix: scipy.sparse.csr_matrix  
          +        The matrix of document-word occurrences.  
          +        `n_dw` is a number of the occurrences of the word `w` in the document `d`  
          +        this matrix determines the dependence between the Theta and Phi matrices  
          +        (Phi is the result of one iteration of the ARTM's EM algorihtm  
          +        with uniform theta initialization and `n_dw` matrix of the document-word occurrences)  
          +    """  # noqa: W291
          +    token2id = obtain_token2id(dataset)
          +
          +    batch_vectorizer = dataset.get_batch_vectorizer()
          +
          +    return _batch_vectorizer2sparse_matrix(
          +        batch_vectorizer, token2id, modality, modalities_to_use
          +    )
          +
          +
          +def _batch_vectorizer2sparse_matrix(batch_vectorizer, token2id, modality, modalities_to_use=None):
          +    """
          +    """
          +    theta_column_naming = 'id'  # scipy sparse matrix doesn't support non-integer indices
          +    matrix_row, matrix_col, matrix_data = [], [], []
          +
          +    for batch_id in range(len(batch_vectorizer._batches_list)):
          +        batch_name = batch_vectorizer._batches_list[batch_id]._filename
          +        batch = artm.messages.Batch()
          +        with open(batch_name, "rb") as f:
          +            batch.ParseFromString(f.read())
          +
          +        for item_id in range(len(batch.item)):
          +            item = batch.item[item_id]
          +            theta_item_id = getattr(item, theta_column_naming)
          +
          +            for local_token_id, token_weight in zip(item.token_id, item.token_weight):
          +                token_class_id = batch.class_id[local_token_id]
          +                token = batch.token[local_token_id]
          +                if (token, token_class_id) not in token2id:
          +                    # probably dictionary was filtered
          +                    continue
          +                if modalities_to_use and token_class_id not in modalities_to_use:
          +                    continue
          +                if token_class_id != modality:
          +                    # we still need these tokens,
          +                    # shapes of n_dw matrix and actual Phi matrix should be in sync.
          +                    # this will be changed to zero at the end
          +                    token_weight = np.nan
          +                token_id = token2id[(token, token_class_id)]
          +                matrix_row.append(theta_item_id)
          +                matrix_col.append(token_id)
          +                matrix_data.append(token_weight)
          +
          +    sparse_n_dw_matrix = scipy.sparse.csr_matrix(
          +        (matrix_data, (matrix_row, matrix_col)),
          +    )
          +    # remove the columns whose all elements are zero
          +    # (i.e. tokens which are of different modalities)
          +    # and renumber index (fill any "holes")
          +    # this is needed to be in sync with artm dictionary after filtering elements out
          +    # (they need to have the same shape)
          +    ind = sparse_n_dw_matrix.sum(axis=0)
          +    nonzeros = np.ravel(ind > 0)
          +    sparse_n_dw_matrix = sparse_n_dw_matrix[:, nonzeros]
          +
          +    # re-encode values to transform NaNs to explicitly stored zeros
          +    sparse_n_dw_matrix.data = np.nan_to_num(sparse_n_dw_matrix.data)
          +
          +    return sparse_n_dw_matrix
          +
          +
           @jit(nopython=True)
           def memory_efficient_inner1d(fst_arr, fst_indices, snd_arr, snd_indices):
               """
          @@ -116,6 +266,7 @@ 

          Module topicnet.cooking_machine.models.thetaless_regular # set rows where sum of row is small to uniform res[np.sum(res, axis=1) < EPS, :] = 1. res /= np.sum(res, axis=1)[:, np.newaxis] + return res @@ -137,40 +288,63 @@

          Module topicnet.cooking_machine.models.thetaless_regular class ThetalessRegularizer(BaseRegularizer): - def __init__(self, name, tau, n_dw_matrix): + def __init__(self, name, tau, modality, dataset: Dataset): """ - Creates a node in the graph with the given args and kwargs. + A regularizer based on a "thetaless" topic model inference + + Note: this implementation stores sparse `n_dw` matrix in memory, + so this is not particularly memory- and space-efficient for huge datasets Parameters ---------- name: str name of the regularizer tau: Number - fictive parameter it's not used, just passed to the parent conctructor - n_dw_matrix: scipy.sparse.csr_matrix - The matrix of document-word occurrences - n_dw is a number of the occurrences of the word w in the document d - this matrix determines the dependence between the Theta and Phi matrices - (Phi is the result of one iteration of the ARTM's EM algorihtm - with uniform theta initialization and n_dw matrix of the document-word occurrences) - """ + according to the math, `tau` should be set to 1 (to correctly emulate a different + inference process). But you do you, it's not like there's a regularizer + police or something. + modality: str + name of modality on which the inference should be based + dataset + will be transformed to n_dw_matrix + """ # noqa: W291 super().__init__(name, tau) - self.n_dw_matrix = n_dw_matrix + + self.modality = modality + self.modalities_to_use = None + self.n_dw_matrix = None + + self.token2id = obtain_token2id(dataset) + self._batches_path = os.path.join(dataset._internals_folder_path, "batches") + + def _initialize_matrices(self, batch_vectorizer, token2id): + self.n_dw_matrix = _batch_vectorizer2sparse_matrix( + batch_vectorizer, token2id, self.modality, self.modalities_to_use + ) self.B = scipy.sparse.csr_matrix( ( - 1. * n_dw_matrix.data / calc_docsizes(n_dw_matrix), - n_dw_matrix.indices, - n_dw_matrix.indptr + 1. * self.n_dw_matrix.data / calc_docsizes(self.n_dw_matrix), + self.n_dw_matrix.indices, + self.n_dw_matrix.indptr ), - shape=n_dw_matrix.shape + shape=self.n_dw_matrix.shape ).tocsc() - self.docptr = get_docptr(n_dw_matrix) - self.wordptr = n_dw_matrix.indices + self.docptr = get_docptr(self.n_dw_matrix) + self.wordptr = self.n_dw_matrix.indices def grad(self, pwt, nwt): phi_matrix_tr = np.array(pwt) phi_matrix = phi_matrix_tr.T phi_rev_matrix = get_prob_matrix_by_counters(phi_matrix_tr) + + if self.n_dw_matrix.shape[1] != phi_rev_matrix.shape[0]: + raise ValueError( + f"Thetaless regularizer has prepared {self.n_dw_matrix.shape} n_dw matrix," + f" but was passed {phi_rev_matrix.T.shape} Phi matrix containing different" + f" number of tokens ({self.n_dw_matrix.shape[1]} != {phi_rev_matrix.shape[0]})" + f"\n(Are modalities the same?)" + ) + theta_matrix = get_prob_matrix_by_counters( self.n_dw_matrix.dot(phi_rev_matrix) ) @@ -188,7 +362,27 @@

          Module topicnet.cooking_machine.models.thetaless_regular tmp = g_dt.T * self.B / (phi_matrix_tr.sum(axis=1) + EPS) n_tw += (tmp - np.einsum('ij,ji->i', phi_rev_matrix, tmp)) * phi_matrix - return n_tw.T - nwt

          + return self.tau * (n_tw.T - nwt) + + def attach(self, model): + """ + + Parameters + ---------- + model : ARTM model + necessary to apply master component + """ + if model.num_document_passes != 1: + warnings.warn( + f"num_document_passes is equal to {model.num_document_passes}, but it" + f" should be set to {1} to correctly emulate a thetaless inference process" + ) + + self.modalities_to_use = model.class_ids.keys() + bv = artm.BatchVectorizer(data_path=self._batches_path, data_format='batches') + self._initialize_matrices(bv, self.token2id) + + self._model = model

          @@ -198,13 +392,41 @@

          Module topicnet.cooking_machine.models.thetaless_regular

          Functions

          +
          +def artm_dict2df(artm_dict) +
          +
          +

          :Description: converts the BigARTM dictionary of the collection +to the pandas.DataFrame. +This is approximately equivalent to the dictionary.save_text() +but has no I/O overhead

          +
          + +Expand source code + +
          def artm_dict2df(artm_dict):
          +    """
          +    :Description: converts the BigARTM dictionary of the collection
          +        to the pandas.DataFrame.
          +        This is approximately equivalent to the dictionary.save_text()
          +        but has no I/O overhead
          +
          +    """
          +    dictionary_data = artm_dict._master.get_dictionary(artm_dict._name)
          +    dict_pandas = {field: getattr(dictionary_data, field)
          +                   for field in FIELDS}
          +    return pd.DataFrame(dict_pandas)
          +
          +
          def calc_A_matrix(n_dw_matrix, theta_matrix, docptr, phi_matrix_tr, wordptr)
          -
          +
          -Source code + +Expand source code +
          def calc_A_matrix(
               n_dw_matrix, theta_matrix, docptr, phi_matrix_tr, wordptr
           ):
          @@ -226,9 +448,11 @@ 

          Functions

          def calc_docsizes(n_dw_matrix)
          -
          +
          -Source code + +Expand source code +
          def calc_docsizes(n_dw_matrix):
               D, _ = n_dw_matrix.shape
               docsizes = []
          @@ -242,22 +466,117 @@ 

          Functions

          return np.array(docsizes)
          +
          +def dataset2sparse_matrix(dataset, modality, modalities_to_use=None) +
          +
          +

          Builds a sparse matrix from batch_vectorizer linked to the Dataset

          +

          If you need an inverse mapping:

          +
          >>> d = sparse_n_dw_matrix.todok()  # convert to dictionary of keys format
          +>>> dict_of_csr = dict(d.items())
          +
          +

          Parameters

          +
          +
          dataset : Dataset
          +
           
          +
          modality : str
          +
          the remaining modalities will be ignored +(their occurrences will be replaced with zeros, but they will continue to exist)
          +
          modalities_to_use : iterable
          +
          +

          a set of modalities the underlying topic model is using (this is about topic model, +not regularizer; this parameter ensures that the shapes of n_dw matrix and actual +Phi matrix match).

          +

          The tokens outside of this list will be discarded utterly +(the resulting matrix will have no entries corresponding to them)

          +

          For artm.ARTM() models, you need to pass whatever is inside class_ids; +while TopicModel usually requires this to be set inside modalities_to_use.

          +

          If you hadn't explicitly listed any modalities yet, you probably could +leave this argument as None.

          +

          If you use a single modality, wrap it into a list (e.g.['@word'])

          +
          +
          +

          Returns

          +
          +
          n_dw_matrix : scipy.sparse.csr_matrix +
          +
          The matrix of document-word occurrences.
          +n_dw is a number of the occurrences of the word w in the document d
          +this matrix determines the dependence between the Theta and Phi matrices
          +(Phi is the result of one iteration of the ARTM's EM algorihtm
          +with uniform theta initialization and n_dw matrix of the document-word occurrences)
          +
          +
          + +Expand source code + +
          def dataset2sparse_matrix(dataset, modality, modalities_to_use=None):
          +    """
          +    Builds a sparse matrix from batch_vectorizer linked to the Dataset
          +
          +    If you need an inverse mapping:
          +
          +    >>> d = sparse_n_dw_matrix.todok()  # convert to dictionary of keys format
          +    >>> dict_of_csr = dict(d.items())
          +
          +    Parameters
          +    ----------
          +    dataset: Dataset
          +    modality: str
          +        the remaining modalities will be ignored
          +        (their occurrences will be replaced with zeros, but they will continue to exist)
          +    modalities_to_use: iterable
          +        a set of modalities the underlying topic model is using (this is about topic model,
          +        not regularizer; this parameter ensures that the shapes of n_dw matrix and actual
          +        Phi matrix match).
          +
          +        The tokens outside of this list will be discarded utterly
          +        (the resulting matrix will have no entries corresponding to them)
          +
          +        For artm.ARTM() models, you need to pass whatever is inside class_ids;
          +        while TopicModel usually requires this to be set inside modalities_to_use.
          +
          +        If you hadn't explicitly listed any modalities yet, you probably could
          +        leave this argument as None.
          +
          +        If you use a single modality, wrap it into a list (e.g.['@word'])
          +
          +    Returns
          +    -------
          +    n_dw_matrix: scipy.sparse.csr_matrix  
          +        The matrix of document-word occurrences.  
          +        `n_dw` is a number of the occurrences of the word `w` in the document `d`  
          +        this matrix determines the dependence between the Theta and Phi matrices  
          +        (Phi is the result of one iteration of the ARTM's EM algorihtm  
          +        with uniform theta initialization and `n_dw` matrix of the document-word occurrences)  
          +    """  # noqa: W291
          +    token2id = obtain_token2id(dataset)
          +
          +    batch_vectorizer = dataset.get_batch_vectorizer()
          +
          +    return _batch_vectorizer2sparse_matrix(
          +        batch_vectorizer, token2id, modality, modalities_to_use
          +    )
          +
          +
          def get_docptr(n_dw_matrix)
          -

          Parameters

          +

          Parameters

          -
          n_dw_matrix : array-like
          +
          n_dw_matrix : array-like
           

          Returns

          np.array
          row indices for the provided matrix
          -
          +
          -Source code + +Expand source code +
          def get_docptr(n_dw_matrix):
               """
               Parameters
          @@ -276,9 +595,11 @@ 

          Returns

          def get_prob_matrix_by_counters(counters, inplace=False)
          -
          +
          -Source code + +Expand source code +
          def get_prob_matrix_by_counters(counters, inplace=False):
               if inplace:
                   res = counters
          @@ -288,6 +609,7 @@ 

          Returns

          # set rows where sum of row is small to uniform res[np.sum(res, axis=1) < EPS, :] = 1. res /= np.sum(res, axis=1)[:, np.newaxis] + return res
          @@ -295,15 +617,15 @@

          Returns

          def memory_efficient_inner1d(fst_arr, fst_indices, snd_arr, snd_indices)
          -

          Parameters

          +

          Parameters

          -
          fst_arr : array-like
          +
          fst_arr : array-like
          2d array, shape is N x T
          -
          fst_indices : array-like
          +
          fst_indices : array-like
          indices of the rows in fst_arr
          -
          snd_arr : array-like
          +
          snd_arr : array-like
          2d array, shape is M x T
          -
          snd_indices : array-like
          +
          snd_indices : array-like
          indices of the rows in fst_arr

          Returns

          @@ -314,9 +636,11 @@

          Returns

          sum(fst_arr[i, k] * snd_arr[j, k] for k in 0..T) for i, j in fst_indices, snd_indices ])
          -
          +
          -Source code + +Expand source code +
          @jit(nopython=True)
           def memory_efficient_inner1d(fst_arr, fst_indices, snd_arr, snd_indices):
               """
          @@ -355,6 +679,38 @@ 

          Returns

          return result

          +
          +def obtain_token2id(dataset: Dataset) +
          +
          +

          Allows one to obtain the mapping from token to the artm.dictionary id of that token +(useful for low-level operations such as reading batches manually)

          +

          Returns

          +
          +
          dict:
          +
          maps (token, class_id) to integer (corresponding to the row of Phi / dictionary id)
          +
          +
          + +Expand source code + +
          def obtain_token2id(dataset: Dataset):
          +    """
          +    Allows one to obtain the mapping from token to the artm.dictionary id of that token
          +    (useful for low-level operations such as reading batches manually)
          +
          +    Returns
          +    -------
          +    dict:
          +        maps (token, class_id) to integer (corresponding to the row of Phi / dictionary id)
          +
          +    """
          +    df = artm_dict2df(dataset.get_dictionary())
          +    df_inverted_index = df[['token', 'class_id']].reset_index().set_index(['token', 'class_id'])
          +
          +    return df_inverted_index.to_dict()['index']
          +
          +
          @@ -362,61 +718,88 @@

          Classes

          class ThetalessRegularizer -(name, tau, n_dw_matrix) +(name, tau, modality, dataset: Dataset)
          -

          Base regularizer class to construct custom regularizers.

          -

          Creates a node in the graph with the given args and kwargs.

          +

          Base regularizer class to construct custom regularizers.

          +

          A regularizer based on a "thetaless" topic model inference

          +

          Note: this implementation stores sparse n_dw matrix in memory, +so this is not particularly memory- and space-efficient for huge datasets

          Parameters

          name : str
          name of the regularizer
          tau : Number
          -
          fictive parameter it's not used, just passed to the parent conctructor
          -
          n_dw_matrix : scipy.sparse.csr_matrix
          -
          The matrix of document-word occurrences -n_dw is a number of the occurrences of the word w in the document d -this matrix determines the dependence between the Theta and Phi matrices -(Phi is the result of one iteration of the ARTM's EM algorihtm -with uniform theta initialization and n_dw matrix of the document-word occurrences)
          -
          +
          according to the math, tau should be set to 1 (to correctly emulate a different
          +inference process). But you do you, it's not like there's a regularizer
          +police or something.
          +
          modality : str
          +
          name of modality on which the inference should be based
          +
          dataset
          +
          will be transformed to n_dw_matrix
          +
          -Source code + +Expand source code +
          class ThetalessRegularizer(BaseRegularizer):
          -    def __init__(self, name, tau, n_dw_matrix):
          +    def __init__(self, name, tau, modality, dataset: Dataset):
                   """
          -        Creates a node in the graph with the given args and kwargs.
          +        A regularizer based on a "thetaless" topic model inference
          +
          +        Note: this implementation stores sparse `n_dw` matrix in memory,
          +        so this is not particularly memory- and space-efficient for huge datasets
           
                   Parameters
                   ----------
                   name: str
                       name of the regularizer
                   tau: Number
          -            fictive parameter it's not used, just passed to the parent conctructor
          -        n_dw_matrix: scipy.sparse.csr_matrix
          -            The matrix of document-word occurrences
          -            n_dw is a number of the occurrences of the word w in the document d
          -            this matrix determines the dependence between the Theta and Phi matrices
          -            (Phi is the result of one iteration of the ARTM's EM algorihtm
          -            with uniform theta initialization and n_dw matrix of the document-word occurrences)
          -        """
          +            according to the math, `tau` should be set to 1 (to correctly emulate a different  
          +            inference process). But you do you, it's not like there's a regularizer  
          +            police or something.  
          +        modality: str
          +            name of modality on which the inference should be based
          +        dataset
          +            will be transformed to n_dw_matrix
          +        """  # noqa: W291
                   super().__init__(name, tau)
          -        self.n_dw_matrix = n_dw_matrix
          +
          +        self.modality = modality
          +        self.modalities_to_use = None
          +        self.n_dw_matrix = None
          +
          +        self.token2id = obtain_token2id(dataset)
          +        self._batches_path = os.path.join(dataset._internals_folder_path, "batches")
          +
          +    def _initialize_matrices(self, batch_vectorizer, token2id):
          +        self.n_dw_matrix = _batch_vectorizer2sparse_matrix(
          +            batch_vectorizer, token2id, self.modality, self.modalities_to_use
          +        )
                   self.B = scipy.sparse.csr_matrix(
                       (
          -                1. * n_dw_matrix.data / calc_docsizes(n_dw_matrix),
          -                n_dw_matrix.indices,
          -                n_dw_matrix.indptr
          +                1. * self.n_dw_matrix.data / calc_docsizes(self.n_dw_matrix),
          +                self.n_dw_matrix.indices,
          +                self.n_dw_matrix.indptr
                       ),
          -            shape=n_dw_matrix.shape
          +            shape=self.n_dw_matrix.shape
                   ).tocsc()
          -        self.docptr = get_docptr(n_dw_matrix)
          -        self.wordptr = n_dw_matrix.indices
          +        self.docptr = get_docptr(self.n_dw_matrix)
          +        self.wordptr = self.n_dw_matrix.indices
           
               def grad(self, pwt, nwt):
                   phi_matrix_tr = np.array(pwt)
                   phi_matrix = phi_matrix_tr.T
                   phi_rev_matrix = get_prob_matrix_by_counters(phi_matrix_tr)
          +
          +        if self.n_dw_matrix.shape[1] != phi_rev_matrix.shape[0]:
          +            raise ValueError(
          +                f"Thetaless regularizer has prepared {self.n_dw_matrix.shape} n_dw matrix,"
          +                f" but was passed {phi_rev_matrix.T.shape} Phi matrix containing different"
          +                f" number of tokens ({self.n_dw_matrix.shape[1]} != {phi_rev_matrix.shape[0]})"
          +                f"\n(Are modalities the same?)"
          +            )
          +
                   theta_matrix = get_prob_matrix_by_counters(
                       self.n_dw_matrix.dot(phi_rev_matrix)
                   )
          @@ -434,7 +817,27 @@ 

          Parameters

          tmp = g_dt.T * self.B / (phi_matrix_tr.sum(axis=1) + EPS) n_tw += (tmp - np.einsum('ij,ji->i', phi_rev_matrix, tmp)) * phi_matrix - return n_tw.T - nwt
          + return self.tau * (n_tw.T - nwt) + + def attach(self, model): + """ + + Parameters + ---------- + model : ARTM model + necessary to apply master component + """ + if model.num_document_passes != 1: + warnings.warn( + f"num_document_passes is equal to {model.num_document_passes}, but it" + f" should be set to {1} to correctly emulate a thetaless inference process" + ) + + self.modalities_to_use = model.class_ids.keys() + bv = artm.BatchVectorizer(data_path=self._batches_path, data_format='batches') + self._initialize_matrices(bv, self.token2id) + + self._model = model

          Ancestors

            @@ -446,13 +849,24 @@

            Methods

            def grad(self, pwt, nwt)
            -
            +
            -Source code + +Expand source code +
            def grad(self, pwt, nwt):
                 phi_matrix_tr = np.array(pwt)
                 phi_matrix = phi_matrix_tr.T
                 phi_rev_matrix = get_prob_matrix_by_counters(phi_matrix_tr)
            +
            +    if self.n_dw_matrix.shape[1] != phi_rev_matrix.shape[0]:
            +        raise ValueError(
            +            f"Thetaless regularizer has prepared {self.n_dw_matrix.shape} n_dw matrix,"
            +            f" but was passed {phi_rev_matrix.T.shape} Phi matrix containing different"
            +            f" number of tokens ({self.n_dw_matrix.shape[1]} != {phi_rev_matrix.shape[0]})"
            +            f"\n(Are modalities the same?)"
            +        )
            +
                 theta_matrix = get_prob_matrix_by_counters(
                     self.n_dw_matrix.dot(phi_rev_matrix)
                 )
            @@ -470,7 +884,7 @@ 

            Methods

            tmp = g_dt.T * self.B / (phi_matrix_tr.sum(axis=1) + EPS) n_tw += (tmp - np.einsum('ij,ji->i', phi_rev_matrix, tmp)) * phi_matrix - return n_tw.T - nwt
            + return self.tau * (n_tw.T - nwt)
          @@ -499,11 +913,14 @@

          Index

        • Functions

        • Classes

          @@ -520,7 +937,7 @@

          -

          Generated by pdoc 0.6.3.

          +

          Generated by pdoc 0.8.1.

          diff --git a/docs/cooking_machine/models/topic_model.html b/docs/cooking_machine/models/topic_model.html index 23601f0..8d92d6b 100644 --- a/docs/cooking_machine/models/topic_model.html +++ b/docs/cooking_machine/models/topic_model.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.topic_model API documentation - - + + @@ -21,7 +21,9 @@

          Module topicnet.cooking_machine.models.topic_model
          -Source code + +Expand source code +
          import artm
           import dill
           import glob
          @@ -33,7 +35,6 @@ 

          Module topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_modelModule topicnet.cooking_machine.models.topic_model

          + return result.set_index(["model_id", "regularizer_name"]).sort_values(by="regularizer_name") + + def get_regularizer( + self, reg_name: str) -> Union[BaseRegularizer, artm.regularizers.BaseRegularizer]: + """ + Retrieves the regularizer specified, no matter is it custom or "classic" + + Returns + ------- + regularizer + + """ + # TODO: RegularizersWrapper? + + if reg_name in self.custom_regularizers: + return self.custom_regularizers[reg_name] + elif reg_name in self._model.regularizers.data: + return self._model.regularizers.data[reg_name] + else: + raise KeyError( + f'There is no such regularizer "{reg_name}"' + f' among custom and ARTM regularizers!' + )

  • @@ -866,14 +937,14 @@

    Classes

    class TopicModel -(artm_model=None, model_id=None, parent_model_id=None, data_path=None, description=None, experiment=None, callbacks=None, custom_scores=None, custom_regularizers=None, *args, **kwargs) +(artm_model: artm.artm_model.ARTM = None, model_id: str = None, parent_model_id: str = None, data_path: str = None, description: List[Dict[str, Any]] = None, experiment=None, callbacks: List[ControllerAgent] = None, custom_scores: Dict[str, topicnet.cooking_machine.models.base_score.BaseScore] = None, custom_regularizers: Dict[str, topicnet.cooking_machine.models.base_regularizer.BaseRegularizer] = None, *args, **kwargs)
    -

    Topic Model contains artm model and all necessary information: scores, training pipeline, etc.

    +

    Topic Model contains artm model and all necessary information: scores, training pipeline, etc.

    Initialize stage, also used for loading previously saved experiments.

    Parameters

    -
    artm_model : artm model or None
    +
    artm_model : artm model or None
    model to use, None if you want to create model (Default value = None)
    model_id : str
    model id (Default value = None)
    @@ -885,7 +956,7 @@

    Parameters

    description of the model (Default value = None)
    experiment : Experiment
    the experiment to which the model is bound (Default value = None)
    -
    callbacks : list of objects with invoke() method
    +
    callbacks : list of objects with invoke() method
    function called inside _fit which alters model parameters mainly used for fancy regularizer coefficients manipulation
    custom_scores : dict
    @@ -893,9 +964,11 @@

    Parameters

    (score class with functionality like those of BaseScore)
    custom_regularizers : dict
    dictionary with regularizer names as keys and regularizer classes as values
    -
    +
    -Source code + +Expand source code +
    class TopicModel(BaseModel):
         """
         Topic Model contains artm model and all necessary information: scores, training pipeline, etc.
    @@ -911,7 +984,7 @@ 

    Parameters

    experiment=None, callbacks: List[ControllerAgent] = None, custom_scores: Dict[str, BaseScore] = None, - custom_regularizers: Dict[str, artm.regularizers.BaseRegularizer] = None, + custom_regularizers: Dict[str, BaseRegularizer] = None, *args, **kwargs): """ Initialize stage, also used for loading previously saved experiments. @@ -1028,24 +1101,13 @@

    Parameters

    return score_values - def _fit(self, dataset_trainable, num_iterations, custom_regularizers=None): - """ - - Parameters - ---------- - dataset_trainable : BatchVectorizer - Data for model fit - num_iterations : int - Amount of fit steps - custom_regularizers : dict of BaseRegularizer - Regularizers to apply to model - - """ + def _prepare_custom_regularizers(self, custom_regularizers): if custom_regularizers is None: custom_regularizers = dict() all_custom_regularizers = deepcopy(custom_regularizers) all_custom_regularizers.update(self.custom_regularizers) + base_regularizers_name, base_regularizers_tau = None, None if len(all_custom_regularizers) != 0: for regularizer in all_custom_regularizers.values(): @@ -1056,7 +1118,29 @@

    Parameters

    base_regularizers_tau = [regularizer.tau for regularizer in self._model.regularizers.data.values()] + return base_regularizers_name, base_regularizers_tau, all_custom_regularizers + + def _fit(self, dataset_trainable, num_iterations, custom_regularizers=None): + """ + + Parameters + ---------- + dataset_trainable : BatchVectorizer + Data for model fit + num_iterations : int + Amount of fit steps + custom_regularizers : dict of BaseRegularizer + Regularizers to apply to model + + """ + (base_regularizers_name, + base_regularizers_tau, + all_custom_regularizers) = self._prepare_custom_regularizers(custom_regularizers) + for cur_iter in range(num_iterations): + precomputed_data = dict() + iter_is_last = cur_iter == num_iterations - 1 + self._model.fit_offline(batch_vectorizer=dataset_trainable, num_collection_passes=1) @@ -1068,9 +1152,26 @@

    Parameters

    for name, custom_score in self.custom_scores.items(): try: - score = custom_score.call(self) + should_compute_now = iter_is_last or custom_score._should_compute(cur_iter) + + if not should_compute_now: + continue + + # TODO: this check is probably should be refined somehow... + # what if some new parameter added to BaseScore.call -> new check?.. + call_parameters = signature(custom_score.call).parameters + + # if-else instead of try-catch: to speed up + if (BaseScore._PRECOMPUTED_DATA_PARAMETER_NAME not in call_parameters + and not any(str(p).startswith('**') for p in call_parameters.values())): + + score = custom_score.call(self) + else: + score = custom_score.call(self, precomputed_data=precomputed_data) + custom_score.update(score) self._model.score_tracker[name] = custom_score + except AttributeError: # TODO: means no "call" attribute? raise AttributeError(f'Score {name} doesn\'t have a desired attribute') @@ -1168,17 +1269,32 @@

    Parameters

    model_save_path = self.model_default_save_path for regularizer_name, regularizer_object in self.custom_regularizers.items(): - try: - save_path = os.path.join(model_save_path, regularizer_name + '.rd') - with open(save_path, 'wb') as reg_f: - dill.dump(regularizer_object, reg_f) - except (TypeError, AttributeError): + # If not do this, there may be problems with pickling: + # `model` is an ARTM-C-like thing, and it may cause problems + # This is safe, because `model` appears in attach(), + # which is called before each iteration + # P.S. and the `model` itself may be needed for a regularizer inside `grad()` + regularizer_object._model = None + + managed_to_pickle = False + + for (pickler, extension) in zip([dill, pickle], ['.rd', '.rp']): + save_path = os.path.join(model_save_path, regularizer_name + extension) + try: - save_path = os.path.join(model_save_path, regularizer_name + '.rp') with open(save_path, 'wb') as reg_f: - pickle.dump(regularizer_object, reg_f) + pickler.dump(regularizer_object, reg_f) except (TypeError, AttributeError): - warnings.warn(f'Cannot save {regularizer_name} regularizer.') + if os.path.isfile(save_path): + os.remove(save_path) + else: + managed_to_pickle = True + + if managed_to_pickle: + break + + if not managed_to_pickle: + warnings.warn(f'Cannot save {regularizer_name} regularizer!') def save(self, model_save_path=None, @@ -1367,6 +1483,7 @@

    Parameters

    class_ids = [class_ids] class_ids_iter = class_ids or self._model.class_ids # TODO: this workaround seems to be a correct solution to this problem + # maybe the next for-loop could be replaced with these three lines if not class_ids_iter: valid_model_name = self._model.model_pwt info = self._model.master.get_phi_info(valid_model_name) @@ -1680,7 +1797,29 @@

    Parameters

    result = pd.DataFrame( columns=["model_id", "regularizer_name", "tau", "gamma", "class_ids"], data=data ) - return result.set_index(["model_id", "regularizer_name"]).sort_values(by="regularizer_name")
    + return result.set_index(["model_id", "regularizer_name"]).sort_values(by="regularizer_name") + + def get_regularizer( + self, reg_name: str) -> Union[BaseRegularizer, artm.regularizers.BaseRegularizer]: + """ + Retrieves the regularizer specified, no matter is it custom or "classic" + + Returns + ------- + regularizer + + """ + # TODO: RegularizersWrapper? + + if reg_name in self.custom_regularizers: + return self.custom_regularizers[reg_name] + elif reg_name in self._model.regularizers.data: + return self._model.regularizers.data[reg_name] + else: + raise KeyError( + f'There is no such regularizer "{reg_name}"' + f' among custom and ARTM regularizers!' + )

    Ancestors

      @@ -1689,7 +1828,6 @@

      Ancestors

      Subclasses

      Static methods

      @@ -1697,7 +1835,7 @@

      Static methods

      def load(path, experiment=None)
      -

      Loads the model.

      +

      Loads the model.

      Parameters

      path : str
      @@ -1707,11 +1845,13 @@

      Parameters

      Returns

      -
      TopicModel
      +
      TopicModel
       
      -
      +
      -Source code + +Expand source code +
      @staticmethod
       def load(path, experiment=None):
           """
      @@ -1782,14 +1922,16 @@ 

      Instance variables

      var all_regularizers
      -

      Gets all regularizers with custom regularizers.

      +

      Gets all regularizers with custom regularizers.

      Returns

      regularizers_dict : dict
      dict with artm.regularizer and BaseRegularizer instances
      -
      +
      -Source code + +Expand source code +
      @property
       def all_regularizers(self):
           """
      @@ -1811,9 +1953,11 @@ 

      Returns

    var background_topics
    -
    +
    -Source code + +Expand source code +
    @property
     def background_topics(self):
         return self.select_topics(["background", "bcg"])
    @@ -1821,9 +1965,11 @@

    Returns

    var class_ids
    -
    +
    -Source code + +Expand source code +
    @property
     def class_ids(self):
         """ """
    @@ -1832,9 +1978,11 @@ 

    Returns

    var description
    -
    +
    -Source code + +Expand source code +
    @property
     def description(self):
         """ """
    @@ -1843,9 +1991,11 @@ 

    Returns

    var regularizers
    -

    Gets regularizers from model.

    +

    Gets regularizers from model.

    -Source code + +Expand source code +
    @property
     def regularizers(self):
         """
    @@ -1855,16 +2005,18 @@ 

    Returns

    return self._model.regularizers
    -
    var scores
    +
    var scores : Dict[str, List[float]]
    -

    Gets score values by name.

    +

    Gets score values by name.

    Returns

    -
    dict : string -> list
    +
    dict : string -> list
    dictionary with scores and corresponding values
    -
    +
    -Source code + +Expand source code +
    @property
     def scores(self) -> Dict[str, List[float]]:
         """
    @@ -1883,9 +2035,11 @@ 

    Returns

    var specific_topics
    -
    +
    -Source code + +Expand source code +
    @property
     def specific_topics(self):
         return self.select_topics(["background", "bcg"], invert=True)
    @@ -1898,7 +2052,7 @@

    Methods

    def clone(self, model_id=None)
    -

    Creates a copy of the model except model_id.

    +

    Creates a copy of the model except model_id.

    Parameters

    model_id : str
    @@ -1906,11 +2060,13 @@

    Parameters

    Returns

    -
    TopicModel
    +
    TopicModel
     
    -
    +
    -Source code + +Expand source code +
    def clone(self, model_id=None):
         """
         Creates a copy of the model except model_id.
    @@ -1943,9 +2099,11 @@ 

    Returns

    def describe_regularizers(self)
    -
    +
    -Source code + +Expand source code +
    def describe_regularizers(self):
         data = []
         for reg_name, reg in self.regularizers._data.items():
    @@ -1966,9 +2124,11 @@ 

    Returns

    def describe_scores(self, verbose=False)
    -
    +
    -Source code + +Expand source code +
    def describe_scores(self, verbose=False):
         data = []
         for score_name, score in self.scores.items():
    @@ -1985,9 +2145,11 @@ 

    Returns

    def get_init_parameters(self, not_include=None)
    -
    +
    -Source code + +Expand source code +
    def get_init_parameters(self, not_include=None):
         if not_include is None:
             not_include = list()
    @@ -2007,14 +2169,16 @@ 

    Returns

    def get_jsonable_from_parameters(self)
    -

    Gets artm model params.

    +

    Gets artm model params.

    Returns

    dict
    artm model parameters
    -
    +
    -Source code + +Expand source code +
    def get_jsonable_from_parameters(self):
         """
         Gets artm model params.
    @@ -2053,7 +2217,7 @@ 

    Returns

    def get_phi(self, topic_names=None, class_ids=None, model_name=None)
    -

    Gets custom Phi matrix of model.

    +

    Gets custom Phi matrix of model.

    Parameters

    topic_names : list of str or str
    @@ -2070,9 +2234,11 @@

    Returns

    pd.DataFrame
    phi matrix
    -
    +
    -Source code + +Expand source code +
    def get_phi(self, topic_names=None, class_ids=None, model_name=None):
         """
         Gets custom Phi matrix of model.
    @@ -2101,6 +2267,7 @@ 

    Returns

    class_ids = [class_ids] class_ids_iter = class_ids or self._model.class_ids # TODO: this workaround seems to be a correct solution to this problem + # maybe the next for-loop could be replaced with these three lines if not class_ids_iter: valid_model_name = self._model.model_pwt info = self._model.master.get_phi_info(valid_model_name) @@ -2124,7 +2291,7 @@

    Returns

    def get_phi_dense(self, topic_names=None, class_ids=None, model_name=None)
    -

    Gets custom Phi matrix of model.

    +

    Gets custom Phi matrix of model.

    Parameters

    topic_names : list of str or str
    @@ -2139,11 +2306,13 @@

    Parameters

    Returns

    -
    3-tuple
    +
    3-tuple
    dense phi matrix
    -
    +
    -Source code + +Expand source code +
    def get_phi_dense(self, topic_names=None, class_ids=None, model_name=None):
         """
         Gets custom Phi matrix of model.
    @@ -2173,7 +2342,7 @@ 

    Returns

    def get_phi_sparse(self, topic_names=None, class_ids=None, model_name=None, eps=None)
    -

    Gets custom Phi matrix of model as sparse scipy matrix.

    +

    Gets custom Phi matrix of model as sparse scipy matrix.

    Parameters

    topic_names : list of str or str
    @@ -2190,11 +2359,13 @@

    Parameters

    Returns

    -
    3-tuple
    +
    3-tuple
    sparse phi matrix
    -
    +
    -Source code + +Expand source code +
    def get_phi_sparse(self, topic_names=None, class_ids=None, model_name=None, eps=None):
         """
         Gets custom Phi matrix of model as sparse scipy matrix.
    @@ -2222,11 +2393,48 @@ 

    Returns

    return self._model.get_phi_sparse(topic_names, class_ids, model_name, eps)
    +
    +def get_regularizer(self, reg_name: str) -> Union[BaseRegularizer, artm.regularizers.BaseRegularizer] +
    +
    +

    Retrieves the regularizer specified, no matter is it custom or "classic"

    +

    Returns

    +
    +
    regularizer
    +
     
    +
    +
    + +Expand source code + +
    def get_regularizer(
    +        self, reg_name: str) -> Union[BaseRegularizer, artm.regularizers.BaseRegularizer]:
    +    """
    +    Retrieves the regularizer specified, no matter is it custom or "classic"
    +
    +    Returns
    +    -------
    +    regularizer
    +
    +    """
    +    # TODO: RegularizersWrapper?
    +
    +    if reg_name in self.custom_regularizers:
    +        return self.custom_regularizers[reg_name]
    +    elif reg_name in self._model.regularizers.data:
    +        return self._model.regularizers.data[reg_name]
    +    else:
    +        raise KeyError(
    +            f'There is no such regularizer "{reg_name}"'
    +            f' among custom and ARTM regularizers!'
    +        )
    +
    +
    def get_theta(self, topic_names=None, dataset=None, theta_matrix_type='dense_theta', predict_class_id=None, sparse=False, eps=None)
    -

    Gets Theta matrix as pandas DataFrame +

    Gets Theta matrix as pandas DataFrame or sparse scipy matrix.

    Parameters

    @@ -2254,9 +2462,11 @@

    Returns

    pd.DataFrame
    theta matrix
    -
    +
    -Source code + +Expand source code +
    def get_theta(self, topic_names=None,
                   dataset=None,
                   theta_matrix_type='dense_theta',
    @@ -2321,12 +2531,12 @@ 

    Returns

    def make_dummy(self, save_to_drive=True, save_path=None, dataset=None)
    -

    Makes topic model dummy in-place.

    +

    Makes topic model dummy in-place.

    Parameters

    save_to_drive : bool
    Whether to save model to drive or not. If not, the info will be lost
    -
    save_path : str (or None)
    +
    save_path : str (or None)
    Path to folder to dump info to
    dataset : Dataset
    Dataset with text collection on which the model was trained. @@ -2337,11 +2547,13 @@

    Notes

    but there is no ARTM model inside! (so model.get_phi() won't work!) If one wants to use the topic model as before, this ARTM model should be restored first:

    -
    >>> save_path = topic_model.model_default_save_path
    +
    >>> save_path = topic_model.model_default_save_path
     >>> topic_model._model = artm.load_artm_model(f'{save_path}/model')
    -
    +
    -Source code + +Expand source code +
    def make_dummy(self, save_to_drive=True, save_path=None, dataset=None):
         """Makes topic model dummy in-place.
     
    @@ -2394,7 +2606,7 @@ 

    Notes

    def save(self, model_save_path=None, phi=True, theta=False, dataset=None)
    -

    Saves model description and dumps artm model. +

    Saves model description and dumps artm model. Use this method if you want to dump the model.

    Parameters

    @@ -2406,9 +2618,11 @@

    Parameters

    save theta in csv format if True
    dataset : Dataset
    dataset
    -
    +
    -Source code + +Expand source code +
    def save(self,
              model_save_path=None,
              phi=True,
    @@ -2482,39 +2696,58 @@ 

    Parameters

    def save_custom_regularizers(self, model_save_path=None)
    -
    +
    -Source code + +Expand source code +
    def save_custom_regularizers(self, model_save_path=None):
         if model_save_path is None:
             model_save_path = self.model_default_save_path
     
         for regularizer_name, regularizer_object in self.custom_regularizers.items():
    -        try:
    -            save_path = os.path.join(model_save_path, regularizer_name + '.rd')
    -            with open(save_path, 'wb') as reg_f:
    -                dill.dump(regularizer_object, reg_f)
    -        except (TypeError, AttributeError):
    +        # If not do this, there may be problems with pickling:
    +        # `model` is an ARTM-C-like thing, and it may cause problems
    +        # This is safe, because `model` appears in attach(),
    +        # which is called before each iteration
    +        # P.S. and the `model` itself may be needed for a regularizer inside `grad()`
    +        regularizer_object._model = None
    +
    +        managed_to_pickle = False
    +
    +        for (pickler, extension) in zip([dill, pickle], ['.rd', '.rp']):
    +            save_path = os.path.join(model_save_path, regularizer_name + extension)
    +
                 try:
    -                save_path = os.path.join(model_save_path, regularizer_name + '.rp')
                     with open(save_path, 'wb') as reg_f:
    -                    pickle.dump(regularizer_object, reg_f)
    +                    pickler.dump(regularizer_object, reg_f)
                 except (TypeError, AttributeError):
    -                warnings.warn(f'Cannot save {regularizer_name} regularizer.')
    + if os.path.isfile(save_path): + os.remove(save_path) + else: + managed_to_pickle = True + + if managed_to_pickle: + break + + if not managed_to_pickle: + warnings.warn(f'Cannot save {regularizer_name} regularizer!')
    def select_topics(self, substrings, invert=False)
    -

    Gets all topics containing specified substring

    +

    Gets all topics containing specified substring

    Returns

    list
     
    -
    +
    -Source code + +Expand source code +
    def select_topics(self, substrings, invert=False):
         """
         Gets all topics containing specified substring
    @@ -2535,10 +2768,10 @@ 

    Returns

    def to_dummy(self, save_path=None)
    -

    Creates dummy model

    +

    Creates dummy model

    Parameters

    -
    save_path : str (or None)
    +
    save_path : str (or None)
    Path to folder with dumped info about topic model

    Returns

    @@ -2549,9 +2782,11 @@

    Returns

    Notes

    Dummy model has the same model_id as the original model, -but "model_id" key in experiment.models contains original model, not dummy

    +but "model_id" key in experiment.models contains original model, not dummy

    -Source code + +Expand source code +
    def to_dummy(self, save_path=None):
         """Creates dummy model
     
    @@ -2638,6 +2873,7 @@ 

    get_phi
  • get_phi_dense
  • get_phi_sparse
  • +
  • get_regularizer
  • get_theta
  • load
  • make_dummy
  • @@ -2656,7 +2892,7 @@

    diff --git a/docs/cooking_machine/models/topic_prior_regularizer.html b/docs/cooking_machine/models/topic_prior_regularizer.html index 0f066f0..7f31094 100644 --- a/docs/cooking_machine/models/topic_prior_regularizer.html +++ b/docs/cooking_machine/models/topic_prior_regularizer.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.models.topic_prior_regularizer API documentation - - + + @@ -21,7 +21,9 @@

    Module topicnet.cooking_machine.models.topic_prior_regul
    -Source code + +Expand source code +
    import numpy as np
     import warnings
     from .base_regularizer import BaseRegularizer
    @@ -154,7 +156,7 @@ 

    Classes

    (name, tau, num_topics=None, beta=1)
    -

    TopicPriorRegularizer adds prior beta_t to every column +

    TopicPriorRegularizer adds prior beta_t to every column in Phi matrix of ARTM model. Thus every phi_wt has preassigned prior probability of being attached to topic t.

    If beta is balanced with respect to apriori collection balance, @@ -169,9 +171,11 @@

    Parameters

    Number of topics for uniform sampling
    beta : float or list or np.array
    Prior for columns of Phi matrix (Default value = 1)
    -
    +
    -Source code + +Expand source code +
    class TopicPriorRegularizer(BaseRegularizer):
         """
         TopicPriorRegularizer adds prior beta_t to every column
    @@ -232,9 +236,11 @@ 

    Methods

    def grad(self, pwt, nwt)
    -
    +
    -Source code + +Expand source code +
    def grad(self, pwt, nwt):
         grad_array = np.repeat([self.beta * self.tau], pwt.shape[0], axis=0)
     
    @@ -256,7 +262,7 @@ 

    Inherited members

    (name, tau, num_topics=None, beta_prior=(), random_seed=42)
    -

    TopicPriorSampleRegularizer adds prior beta_t to every column +

    TopicPriorSampleRegularizer adds prior beta_t to every column in Phi matrix of ARTM model. Thus every phi_wt has preassigned prior probability of being attached to topic t.

    Beta vector is sampled from @@ -280,9 +286,11 @@

    Parameters

    Prior for Dirichlet distribution to sample beta parameter
    random_seed : int
    Random seed for Dirichlet distribution (Default value = 42)
    -
    +
    -Source code + +Expand source code +
    class TopicPriorSampledRegularizer(BaseRegularizer):
         """
         TopicPriorSampleRegularizer adds prior beta_t to every column
    @@ -354,9 +362,11 @@ 

    Methods

    def grad(self, pwt, nwt)
    -
    +
    -Source code + +Expand source code +
    def grad(self, pwt, nwt):
         grad_array = np.repeat([self.beta * self.tau], pwt.shape[0], axis=0)
     
    @@ -407,7 +417,7 @@ 

    -

    Generated by pdoc 0.6.3.

    +

    Generated by pdoc 0.8.1.

    diff --git a/docs/cooking_machine/pretty_output.html b/docs/cooking_machine/pretty_output.html index 85a190d..597d6aa 100644 --- a/docs/cooking_machine/pretty_output.html +++ b/docs/cooking_machine/pretty_output.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.pretty_output API documentation - - + + @@ -21,7 +21,9 @@

    Module topicnet.cooking_machine.pretty_output

    -Source code + +Expand source code +
    import numpy as np
     
     from datetime import datetime
    @@ -336,7 +338,7 @@ 

    Functions

    def add_non_tree_strings(strings, strings_to_add, add_separator=True)
    -

    Adding training stage strings +

    Adding training stage strings to the experiment description

    Parameters

    @@ -352,9 +354,11 @@

    Returns

    strings : list of strings
    description of the experiment
    -
    +
    -Source code + +Expand source code +
    def add_non_tree_strings(strings, strings_to_add, add_separator=True):
         """
         Adding training stage strings
    @@ -390,11 +394,11 @@ 

    Returns

    -def get_criteria_strings(criteria, tab=' -', min_len_per_cube=26) +def get_criteria_strings(criteria, tab: str = ' +', min_len_per_cube: int = 26)
    -

    Parameters

    +

    Parameters

    criteria : list of str
     
    @@ -408,9 +412,11 @@

    Returns

    dict
     
    -
    +
    -Source code + +Expand source code +
    def get_criteria_strings(criteria, tab: str = "  ", min_len_per_cube: int = MODEL_NAME_LENGTH):
         """
     
    @@ -449,11 +455,11 @@ 

    Returns

    -def get_cube_strings(cubes, tab=' -', min_len_per_cube=26) +def get_cube_strings(cubes, tab: str = ' +', min_len_per_cube: int = 26)
    -

    Parameters

    +

    Parameters

    cubes : list of dict
     
    @@ -467,9 +473,11 @@

    Returns

    dict
     
    -
    +
    -Source code + +Expand source code +
    def get_cube_strings(cubes, tab: str = "  ", min_len_per_cube: int = MODEL_NAME_LENGTH):
         """
     
    @@ -509,10 +517,10 @@ 

    Returns

    -def get_html(experiment, window_size=1500) +def get_html(experiment, window_size: int = 1500)
    -

    Gets html text to save human-readable description of the experiment.

    +

    Gets html text to save human-readable description of the experiment.

    Parameters

    window_size : int
    @@ -522,9 +530,11 @@

    Returns

    str
    description of the experiment in html format
    -
    +
    -Source code + +Expand source code +
    def get_html(experiment, window_size: int = 1500):
         """
         Gets html text to save human-readable description of the experiment.
    @@ -564,11 +574,11 @@ 

    Returns

    -def give_strings_description(experiment, tab=' -', min_len_per_cube=26, len_tree_step=27) +def give_strings_description(experiment, tab: str = ' +', min_len_per_cube: int = 26, len_tree_step: int = 27)
    -

    Gets strings description of the experiment.

    +

    Gets strings description of the experiment.

    Parameters

    tab : str
    @@ -585,9 +595,11 @@

    Returns

    list
    strings description
    -
    +
    -Source code + +Expand source code +
    def give_strings_description(experiment,
                                  tab: str = "  ",
                                  min_len_per_cube: int = MODEL_NAME_LENGTH,
    @@ -662,9 +674,11 @@ 

    Returns

    def make_notebook_pretty()
    -
    +
    -Source code + +Expand source code +
    def make_notebook_pretty():
         from IPython.core.display import display, HTML
     
    @@ -684,15 +698,15 @@ 

    Returns

    -def resize_value(key, value, tab=' +def resize_value(key, value, tab: str = ' ')
    -

    Parameters

    +

    Parameters

    key : str
     
    -
    value : optional
    +
    value : optional
     
    tab : str
    (Default value = " @@ -702,9 +716,11 @@

    Returns

    list
     
    -
    +
    -Source code + +Expand source code +
    def resize_value(key, value, tab: str = "  "):
         """
     
    @@ -777,7 +793,7 @@ 

    Index

    diff --git a/docs/cooking_machine/recipes/artm_baseline_pipeline.html b/docs/cooking_machine/recipes/artm_baseline_pipeline.html index eede585..9c2ee47 100644 --- a/docs/cooking_machine/recipes/artm_baseline_pipeline.html +++ b/docs/cooking_machine/recipes/artm_baseline_pipeline.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes.artm_baseline_pipeline API documentation - - + + @@ -21,11 +21,15 @@

    Module topicnet.cooking_machine.recipes.artm_baseline_pi
    -Source code + +Expand source code +
    from typing import List
    +
     from .recipe_wrapper import BaseRecipe
     from .. import Dataset
     
    +
     ARTM_baseline_template = '''
     # This config follows a strategy described by Murat Apishev
     # one of the core programmers of BigARTM library in personal correspondence.
    @@ -64,6 +68,7 @@ 

    Module topicnet.cooking_machine.recipes.artm_baseline_pi num_top_tokens: 30 model: dataset_path: {dataset_path} + {dictionary_filter_parameters} modalities_to_use: {modality_list} main_modality: '{main_modality}' @@ -86,6 +91,8 @@

    Module topicnet.cooking_machine.recipes.artm_baseline_pi use_relative_coefficients: true ''' +ONE_CONFIG_INDENT = 4 * ' ' + class BaselineRecipe(BaseRecipe): """ @@ -98,6 +105,7 @@

    Module topicnet.cooking_machine.recipes.artm_baseline_pi def format_recipe( self, dataset_path: str, + dictionary_filter_parameters: dict = None, modality_list: List[str] = None, topic_number: int = 20, background_topic_number: int = 1, @@ -110,13 +118,23 @@

    Module topicnet.cooking_machine.recipes.artm_baseline_pi background_topics = [f'bcg_{i}' for i in range( len(specific_topics), len(specific_topics) + background_topic_number)] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, modality_list=modality_list, main_modality=modality_list[0], specific_topics=specific_topics, background_topics=background_topics, ) + return self._recipe

    @@ -133,10 +151,12 @@

    Classes

    class BaselineRecipe
    -

    Class for baseline recipe creation and -unification of recipe interface

    +

    Class for baseline recipe creation and +unification of recipe interface

    -Source code + +Expand source code +
    class BaselineRecipe(BaseRecipe):
         """
         Class for baseline recipe creation and
    @@ -148,6 +168,7 @@ 

    Classes

    def format_recipe( self, dataset_path: str, + dictionary_filter_parameters: dict = None, modality_list: List[str] = None, topic_number: int = 20, background_topic_number: int = 1, @@ -160,13 +181,23 @@

    Classes

    background_topics = [f'bcg_{i}' for i in range( len(specific_topics), len(specific_topics) + background_topic_number)] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, modality_list=modality_list, main_modality=modality_list[0], specific_topics=specific_topics, background_topics=background_topics, ) + return self._recipe

    Ancestors

    @@ -208,7 +239,7 @@

    -

    Generated by pdoc 0.6.3.

    +

    Generated by pdoc 0.8.1.

    diff --git a/docs/cooking_machine/recipes/exploratory_search_pipeline.html b/docs/cooking_machine/recipes/exploratory_search_pipeline.html index 9fee611..28b8686 100644 --- a/docs/cooking_machine/recipes/exploratory_search_pipeline.html +++ b/docs/cooking_machine/recipes/exploratory_search_pipeline.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes.exploratory_search_pipeline API documentation - - + + @@ -21,7 +21,9 @@

    Module topicnet.cooking_machine.recipes.exploratory_sear
    -Source code + +Expand source code +
    from .recipe_wrapper import BaseRecipe
     from .. import Dataset
     
    @@ -44,6 +46,10 @@ 

    Module topicnet.cooking_machine.recipes.exploratory_sear # specific_topics=specific_topics, background_topics=background_topics) # when loading the recipe to adjust for your dataset +# If you have more than one modaity you want to use, we recommend employing +# more advanced MultimodalSearchRecipe from multimodal_exploratory_search_pipeline instead + + topics: # Describes number of model topics, in the actuall article 200 topics were found to be optimal specific_topics: {{specific_topics}} @@ -166,10 +172,12 @@

    Classes

    class SearchRecipe
    -

    Class for baseline recipe creation and -unification of recipe interface

    +

    Class for baseline recipe creation and +unification of recipe interface

    -Source code + +Expand source code +
    class SearchRecipe(BaseRecipe):
         """
         Class for baseline recipe creation and
    @@ -239,7 +247,7 @@ 

    -

    Generated by pdoc 0.6.3.

    +

    Generated by pdoc 0.8.1.

    diff --git a/docs/cooking_machine/recipes/index.html b/docs/cooking_machine/recipes/index.html index 1642adf..603a2bc 100644 --- a/docs/cooking_machine/recipes/index.html +++ b/docs/cooking_machine/recipes/index.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes API documentation - - + + @@ -19,19 +19,11 @@

    Module topicnet.cooking_machine.recipes

    -

    TopicNet Recipes

    -

    This module contains mechanisms to generate code for training topic models on your data. It was created in orded to simplify knowledge transition about model training from the field researchers to the end users and possibly for easier exchange of a code between the research groups. As a backbone it uses snippets of YAML configs that require filling in information about the collection and hyperparameters of the required topic model. Currently it is recommended to import BaselineRecipe, SearchRecipe, MultimodalSearchRecipe classes for the experiment environment generation. However, for the compatibility with previous examples found in topicnet/demos/*-Recipe.ipynb notebooks we also have ARTM-baseline and exploratory_search configs in YAML format.

    -
    -
      -
    • BaselineRecipe - Class for generating a pipeline training a topic models with decorrelation regularization, maximizing custom BleiLafferty score from TopicNet library topicnet.cooking_machine.models.scores.BleiLaffertyScore.
    • -
    • SearchRecipe - a Class recreating training scenario from exploratory_search YAML config. Provides good startegy for training topic models for collection search properties. A link to the publication can be found in the comments section of the recipe.
    • -
    • MultimodalSearchRecipe - a Class that modifies previos strategy for the case of multimodal data allowing to recreate previous scenario for each modality separately.
    • -
    • intratext_coherence_maximization.yml - a strin in YAML format (like the old recipes) allowing to build topic model with decorrelation, Phi and Theta matrices Sparsing and Smoothing with background topics maximizing the intratext coherence score topicnet.cooking_machine.models.scores.IntratextCoherenceScore.
    • -
    • topic_number_search.yml - a recipe recreating published strategy to find optimal topic number for given dataset. References to the publication can be found in the config dosctring.
    • -
    -Source code + +Expand source code +
    from .multimodal_exploratory_search_pipeline import MultimodalSearchRecipe
     from .artm_baseline_pipeline import BaselineRecipe
     from .intratext_coherence_pipeline import IntratextCoherenceRecipe
    @@ -45,27 +37,27 @@ 

    Sub-modules

    topicnet.cooking_machine.recipes.artm_baseline_pipeline
    -
    +
    topicnet.cooking_machine.recipes.exploratory_search_pipeline
    -
    +
    topicnet.cooking_machine.recipes.intratext_coherence_pipeline
    -
    +
    topicnet.cooking_machine.recipes.multimodal_exploratory_search_pipeline
    -
    +
    topicnet.cooking_machine.recipes.recipe_wrapper
    -
    +
    topicnet.cooking_machine.recipes.wntm
    -
    +
    @@ -77,7 +69,7 @@

    Sub-modules

    diff --git a/docs/cooking_machine/recipes/intratext_coherence_pipeline.html b/docs/cooking_machine/recipes/intratext_coherence_pipeline.html index bbf8bf9..962db37 100644 --- a/docs/cooking_machine/recipes/intratext_coherence_pipeline.html +++ b/docs/cooking_machine/recipes/intratext_coherence_pipeline.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes.intratext_coherence_pipeline API documentation - - + + @@ -21,7 +21,9 @@

    Module topicnet.cooking_machine.recipes.intratext_cohere
    -Source code + +Expand source code +
    import os
     import warnings
     
    @@ -30,6 +32,8 @@ 

    Module topicnet.cooking_machine.recipes.intratext_cohere from .recipe_wrapper import BaseRecipe from .. import Dataset +ONE_CONFIG_INDENT = 4 * ' ' + class IntratextCoherenceRecipe(BaseRecipe): """ @@ -59,6 +63,7 @@

    Module topicnet.cooking_machine.recipes.intratext_cohere dataset_path: str, num_specific_topics: int, main_modality: str = None, + dictionary_filter_parameters: dict = None, num_background_topics: int = 1, modalities: List[str] = None, keep_dataset_in_memory: bool = True, @@ -142,10 +147,19 @@

    Module topicnet.cooking_machine.recipes.intratext_cohere for i in range(num_specific_topics, num_specific_topics + num_background_topics) ] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( modality_names=modalities, main_modality=main_modality, dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, keep_dataset_in_memory=keep_dataset_in_memory, keep_dataset=keep_dataset, documents_fraction=documents_fraction, @@ -171,7 +185,7 @@

    Classes

    class IntratextCoherenceRecipe
    -

    The recipe mainly consists of basic cube stages, +

    The recipe mainly consists of basic cube stages, such as Decorrelation, Sparsing and Smoothing. In this way it is similar to ARTM baseline recipe. The core difference is that models selected based on their IntratextCoherenceScore @@ -179,9 +193,11 @@

    Classes

    PerplexityScore is also calculated to assure that models don't have high perplexity, but the main criteria is IntratextCoherenceScore.

    For more details about IntratextCoherence -one may see the paper http://www.dialog-21.ru/media/4281/alekseevva.pdf

    +one may see the paper http://www.dialog-21.ru/media/4281/alekseevva.pdf

    -Source code + +Expand source code +
    class IntratextCoherenceRecipe(BaseRecipe):
         """
         The recipe mainly consists of basic cube stages,
    @@ -210,6 +226,7 @@ 

    Classes

    dataset_path: str, num_specific_topics: int, main_modality: str = None, + dictionary_filter_parameters: dict = None, num_background_topics: int = 1, modalities: List[str] = None, keep_dataset_in_memory: bool = True, @@ -293,10 +310,19 @@

    Classes

    for i in range(num_specific_topics, num_specific_topics + num_background_topics) ] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( modality_names=modalities, main_modality=main_modality, dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, keep_dataset_in_memory=keep_dataset_in_memory, keep_dataset=keep_dataset, documents_fraction=documents_fraction, @@ -315,10 +341,10 @@

    Ancestors

    Methods

    -def format_recipe(self, dataset_path, num_specific_topics, main_modality=None, num_background_topics=1, modalities=None, keep_dataset_in_memory=True, keep_dataset=False, documents_fraction=0.5, one_stage_num_iter=20, verbose=True) +def format_recipe(self, dataset_path: str, num_specific_topics: int, main_modality: str = None, dictionary_filter_parameters: dict = None, num_background_topics: int = 1, modalities: List[str] = None, keep_dataset_in_memory: bool = True, keep_dataset: bool = False, documents_fraction: float = 0.5, one_stage_num_iter: int = 20, verbose: bool = True) -> str
    -

    Parameters

    +

    Parameters

    dataset_path
    Path to the dataset .csv file
    @@ -370,14 +396,17 @@

    Methods

    and 5 * 5 coherence computations (here may be slow if documents_fraction is high)
    verbose
    Whether to show experiment progress or not
    -

    +
    -Source code + +Expand source code +
    def format_recipe(
             self,
             dataset_path: str,
             num_specific_topics: int,
             main_modality: str = None,
    +        dictionary_filter_parameters: dict = None,
             num_background_topics: int = 1,
             modalities: List[str] = None,
             keep_dataset_in_memory: bool = True,
    @@ -461,10 +490,19 @@ 

    Methods

    for i in range(num_specific_topics, num_specific_topics + num_background_topics) ] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( modality_names=modalities, main_modality=main_modality, dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, keep_dataset_in_memory=keep_dataset_in_memory, keep_dataset=keep_dataset, documents_fraction=documents_fraction, @@ -515,7 +553,7 @@

    -

    Generated by pdoc 0.6.3.

    +

    Generated by pdoc 0.8.1.

    diff --git a/docs/cooking_machine/recipes/multimodal_exploratory_search_pipeline.html b/docs/cooking_machine/recipes/multimodal_exploratory_search_pipeline.html index b8568c0..cf9953b 100644 --- a/docs/cooking_machine/recipes/multimodal_exploratory_search_pipeline.html +++ b/docs/cooking_machine/recipes/multimodal_exploratory_search_pipeline.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes.multimodal_exploratory_search_pipeline API documentation - - + + @@ -21,8 +21,10 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo
    -Source code -
    from typing import List, Union
    +
    +Expand source code
    +
    +
    from typing import List, Union, Dict
     from .recipe_wrapper import BaseRecipe
     from .. import Dataset
     
    @@ -51,7 +53,7 @@ 

    Module topicnet.cooking_machine.recipes.multimodal_explo model: dataset_path: {dataset_path} - modalities_to_use: {modality_list} + {modalities_description} main_modality: '{modality}' stages: @@ -91,7 +93,7 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo verbose: false use_relative_coefficients: True '''.format('PerplexityScore@all < 1.01 * MINIMUM(PerplexityScore@all)' + - ' and SparsityPhiScore{modality} -> max') + ' and SparsityThetaScore -> max') # Had to change tracked score function. Is it fine? decor_phi_cube_template = ''' @@ -104,9 +106,9 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo - {0} strategy: PerplexityStrategy strategy_params: - start_point: 0 - step: 0.02 - max_len: 20 + start_point: 0.005 + step: 0.005 + max_len: 10 tracked_score_function: PerplexityScore{{modality}} verbose: false use_relative_coefficients: True @@ -158,11 +160,38 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo def format_recipe( self, dataset_path: str, - modality_list: List[str] = None, + modality_list: List[str] or Dict = None, + main_modality: str = None, topic_number: int = 20, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20, ): + ''' + Creates a recipe for multimodal search + using basic template at the top of this file + + Parameters + ---------- + dataset_path : path to the data + main_modality : str + chosen to be main modality from modality list, if possible + if it is not specified, the function attempts to user + the first entry of `modality_list` instead + + modality_list : list of modality names to use + or a dict specifying the (relative) weight of each + topic_number: + number of the model topics + background_topic_number : + number of background topics + num_iter : + specifying number of iterations for each cube + + Returns + ------- + string specifying recipe for multimodal search + ''' + if modality_list is None: modality_list = list(Dataset(dataset_path).get_possible_modalities()) @@ -170,8 +199,13 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo background_topics = [f'bcg_{i}' for i in range( len(specific_topics), len(specific_topics) + background_topic_number)] + if main_modality is None: + if isinstance(modality_list, list): + main_modality = modality_list[0] + else: + raise TypeError("main_modality should be specified") self._make_multimodal_recipe( - modality=modality_list[0], + modality=main_modality, dataset_path=dataset_path, specific_topics=specific_topics, background_topics=background_topics, @@ -235,14 +269,12 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo num_iter=iterations)) cube_templates.append(smooth_phi_cube_template.format(modality=modality, num_iter=iterations)) - cube_templates.append(sparse_theta_cube_template.format(modality=modality, - num_iter=iterations)) + cube_templates.append(sparse_theta_cube_template.format(num_iter=iterations)) else: raise ValueError('That option is not availiable') if self._order == 'extended_modalities': iterations = num_iter[-1] - cube_templates.append(sparse_theta_cube_template.format(modality=modality_list[0], - num_iter=iterations)) + cube_templates.append(sparse_theta_cube_template.format(num_iter=iterations)) return ''.join(cube_templates) def _make_multimodal_recipe( @@ -251,41 +283,31 @@

    Module topicnet.cooking_machine.recipes.multimodal_explo modality: str, specific_topics: List[str], background_topics: List[str], - modality_list: List[str] = None, + modality_list: List[str] or Dict = None, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20, ): - ''' - Creates a recipe for multimodal search - using basic template at the top of this file - - Parameters - ---------- - dataset_path : path to the data - modality : str - chosen to be main modality from modality list - modality_list : list of modality names to use - specific_topics : list of str - names of the model topics - background_topics : list of background topic names - num_iter : number or list of numbers - specifying number of iterations for each cube - - Returns - ------- - string specifying recipe for multimodal search - ''' - reg_forms = self._form_regularizers(modality_list) cube_forms = self._form_and_order_cubes( modality_list, num_iter=num_iter,) + if isinstance(modality_list, list): + modalities_description = f"modalities_to_use: {modality_list}" + elif isinstance(modality_list, dict): + # this line has correct whitespace count + header_string = "modalities_weights:" + # these ones should be indented one level more, so 8 spaces + data_strings = [f"'{k}': {v}" for k, v in modality_list.items()] + strings = [header_string] + data_strings + modalities_description = "\n ".join(strings) + else: + raise TypeError("modality_list should be either list or dict, not {type(modality_list}") self._recipe = self.recipe_template.format( modality=modality, dataset_path=dataset_path, specific_topics=specific_topics, background_topics=background_topics, - modality_list=modality_list, + modalities_description=modalities_description, syntesized_regularizers=reg_forms, syntesized_stages=cube_forms)

    @@ -304,7 +326,7 @@

    Classes

    (order='extended_modalities')
    -

    Class for multimodal search recipe creation and +

    Class for multimodal search recipe creation and unification of recipe usage interface

    Parameters

    @@ -314,9 +336,11 @@

    Parameters

    for each dataset modality while 'extended_modalities' extends only modality-reliant blocks of training keeping last part equivalent to the original pipeline
    -
    +
    -Source code + +Expand source code +
    class MultimodalSearchRecipe(BaseRecipe):
         """
         Class for multimodal search recipe creation and
    @@ -340,11 +364,38 @@ 

    Parameters

    def format_recipe( self, dataset_path: str, - modality_list: List[str] = None, + modality_list: List[str] or Dict = None, + main_modality: str = None, topic_number: int = 20, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20, ): + ''' + Creates a recipe for multimodal search + using basic template at the top of this file + + Parameters + ---------- + dataset_path : path to the data + main_modality : str + chosen to be main modality from modality list, if possible + if it is not specified, the function attempts to user + the first entry of `modality_list` instead + + modality_list : list of modality names to use + or a dict specifying the (relative) weight of each + topic_number: + number of the model topics + background_topic_number : + number of background topics + num_iter : + specifying number of iterations for each cube + + Returns + ------- + string specifying recipe for multimodal search + ''' + if modality_list is None: modality_list = list(Dataset(dataset_path).get_possible_modalities()) @@ -352,8 +403,13 @@

    Parameters

    background_topics = [f'bcg_{i}' for i in range( len(specific_topics), len(specific_topics) + background_topic_number)] + if main_modality is None: + if isinstance(modality_list, list): + main_modality = modality_list[0] + else: + raise TypeError("main_modality should be specified") self._make_multimodal_recipe( - modality=modality_list[0], + modality=main_modality, dataset_path=dataset_path, specific_topics=specific_topics, background_topics=background_topics, @@ -417,14 +473,12 @@

    Parameters

    num_iter=iterations)) cube_templates.append(smooth_phi_cube_template.format(modality=modality, num_iter=iterations)) - cube_templates.append(sparse_theta_cube_template.format(modality=modality, - num_iter=iterations)) + cube_templates.append(sparse_theta_cube_template.format(num_iter=iterations)) else: raise ValueError('That option is not availiable') if self._order == 'extended_modalities': iterations = num_iter[-1] - cube_templates.append(sparse_theta_cube_template.format(modality=modality_list[0], - num_iter=iterations)) + cube_templates.append(sparse_theta_cube_template.format(num_iter=iterations)) return ''.join(cube_templates) def _make_multimodal_recipe( @@ -433,41 +487,31 @@

    Parameters

    modality: str, specific_topics: List[str], background_topics: List[str], - modality_list: List[str] = None, + modality_list: List[str] or Dict = None, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20, ): - ''' - Creates a recipe for multimodal search - using basic template at the top of this file - - Parameters - ---------- - dataset_path : path to the data - modality : str - chosen to be main modality from modality list - modality_list : list of modality names to use - specific_topics : list of str - names of the model topics - background_topics : list of background topic names - num_iter : number or list of numbers - specifying number of iterations for each cube - - Returns - ------- - string specifying recipe for multimodal search - ''' - reg_forms = self._form_regularizers(modality_list) cube_forms = self._form_and_order_cubes( modality_list, num_iter=num_iter,) + if isinstance(modality_list, list): + modalities_description = f"modalities_to_use: {modality_list}" + elif isinstance(modality_list, dict): + # this line has correct whitespace count + header_string = "modalities_weights:" + # these ones should be indented one level more, so 8 spaces + data_strings = [f"'{k}': {v}" for k, v in modality_list.items()] + strings = [header_string] + data_strings + modalities_description = "\n ".join(strings) + else: + raise TypeError("modality_list should be either list or dict, not {type(modality_list}") self._recipe = self.recipe_template.format( modality=modality, dataset_path=dataset_path, specific_topics=specific_topics, background_topics=background_topics, - modality_list=modality_list, + modalities_description=modalities_description, syntesized_regularizers=reg_forms, syntesized_stages=cube_forms)
    @@ -475,12 +519,104 @@

    Ancestors

    +

    Methods

    +
    +
    +def format_recipe(self, dataset_path: str, modality_list: List[str] = None, main_modality: str = None, topic_number: int = 20, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20) +
    +
    +

    Creates a recipe for multimodal search +using basic template at the top of this file

    +

    Parameters

    +
    +
    dataset_path : path to the data
    +
     
    +
    main_modality : str
    +
    chosen to be main modality from modality list, if possible +if it is not specified, the function attempts to user +the first entry of modality_list instead
    +
    modality_list : list of modality names to use
    +
    or a dict specifying the (relative) weight of each
    +
    +

    topic_number: +number of the model topics +background_topic_number : +number of background topics +num_iter : +specifying number of iterations for each cube

    +

    Returns

    +
    +
    string specifying recipe for multimodal search
    +
     
    +
    +
    + +Expand source code + +
    def format_recipe(
    +    self,
    +    dataset_path: str,
    +    modality_list: List[str] or Dict = None,
    +    main_modality: str = None,
    +    topic_number: int = 20,
    +    background_topic_number: int = 1,
    +    num_iter: Union[int, List[int]] = 20,
    +):
    +    '''
    +    Creates a recipe for multimodal search
    +    using basic template at the top of this file
    +
    +    Parameters
    +    ----------
    +    dataset_path : path to the data
    +    main_modality : str
    +        chosen to be main modality from modality list, if possible
    +        if it is not specified, the function attempts to user
    +        the first entry of `modality_list` instead
    +
    +    modality_list : list of modality names to use
    +                    or a dict specifying the (relative) weight of each
    +    topic_number:
    +        number of the model topics
    +    background_topic_number :
    +        number of background topics
    +    num_iter :
    +        specifying number of iterations for each cube
    +
    +    Returns
    +    -------
    +    string specifying recipe for multimodal search
    +    '''
    +
    +    if modality_list is None:
    +        modality_list = list(Dataset(dataset_path).get_possible_modalities())
    +
    +    specific_topics = [f'topic_{i}' for i in range(topic_number)]
    +    background_topics = [f'bcg_{i}' for i in range(
    +        len(specific_topics), len(specific_topics) + background_topic_number)]
    +
    +    if main_modality is None:
    +        if isinstance(modality_list, list):
    +            main_modality = modality_list[0]
    +        else:
    +            raise TypeError("main_modality should be specified")
    +    self._make_multimodal_recipe(
    +        modality=main_modality,
    +        dataset_path=dataset_path,
    +        specific_topics=specific_topics,
    +        background_topics=background_topics,
    +        modality_list=modality_list,
    +        num_iter=num_iter,
    +    )
    +    return self._recipe
    +
    +
    +

    Inherited members

    @@ -503,6 +639,9 @@

    Index

    @@ -510,7 +649,7 @@

    -

    Generated by pdoc 0.6.3.

    +

    Generated by pdoc 0.8.1.

    diff --git a/docs/cooking_machine/recipes/recipe_wrapper.html b/docs/cooking_machine/recipes/recipe_wrapper.html index 34509cd..b4173ef 100644 --- a/docs/cooking_machine/recipes/recipe_wrapper.html +++ b/docs/cooking_machine/recipes/recipe_wrapper.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes.recipe_wrapper API documentation - - + + @@ -21,12 +21,21 @@

    Module topicnet.cooking_machine.recipes.recipe_wrapper
    -Source code -
    from typing import Tuple
    +
    +Expand source code
    +
    +
    from typing import (
    +    Dict,
    +    Tuple,
    +    Union,
    +)
     
     from .. import Dataset
     from .. import Experiment
    -from ..config_parser import build_experiment_environment_from_yaml_config
    +from ..config_parser import (
    +    build_experiment_environment_from_yaml_config,
    +    KEY_DICTIONARY_FILTER_PARAMETERS,
    +)
     
     
     recipe_template_example = """
    @@ -54,19 +63,18 @@ 

    Module topicnet.cooking_machine.recipes.recipe_wrapperModule topicnet.cooking_machine.recipes.recipe_wrapperModule topicnet.cooking_machine.recipes.recipe_wrapper

    @@ -108,9 +140,11 @@

    Classes

    (recipe_template)
    -

    Base class to work with recipes

    +

    Base class to work with recipes

    -Source code + +Expand source code +
    class BaseRecipe:
         """
         Base class to work with recipes
    @@ -127,19 +161,18 @@ 

    Classes

    def format_recipe(self, *args, **kwargs) -> str: """ - Updates self._recipe variable - with variables specific for the dataset + Updates `self._recipe` + with variables specific for the dataset. """ raise NotImplementedError( 'Method needs to be specified for the recipe template' ) - return self._recipe def build_experiment_environment( self, save_path: str, experiment_id: str = 'default_experiment_name', - force_separate_thread: bool = False + force_separate_thread: bool = False, ) -> Tuple[Experiment, Dataset]: """ Returns experiment and dataset instances @@ -148,11 +181,13 @@

    Classes

    Parameters ---------- - save_path: path to the folder to save experiment logs and models - experiment_id: name of the experiment folder - force_separate_thread: train each model in dedicated process + save_path + path to the folder to save experiment logs and models + experiment_id + name of the experiment folder + force_separate_thread + train each model in dedicated process; this feature helps to handle resources in Jupyter notebooks - """ if self._recipe is None: raise ValueError( @@ -163,42 +198,67 @@

    Classes

    self._recipe, save_path=save_path, experiment_id=experiment_id, - force_separate_thread=force_separate_thread + force_separate_thread=force_separate_thread, + ) + + @staticmethod + def _format_dictionary_filter_parameters( + parameters: Dict[Union[int, float, str, bool], Union[int, float, str, bool]], + indent: str) -> str: + + blank_dictionary = '{}' + + if len(parameters) == 0: + parameters_block = blank_dictionary + else: + parameters_block = '\n'.join([ + f'{indent}{k}: {v}' + for k, v in parameters.items() + ]) + + return ( + KEY_DICTIONARY_FILTER_PARAMETERS + + ':' + + ('\n' if parameters_block != blank_dictionary else ' ') + + parameters_block )

    Subclasses

    Methods

    -def build_experiment_environment(self, save_path, experiment_id='default_experiment_name', force_separate_thread=False) +def build_experiment_environment(self, save_path: str, experiment_id: str = 'default_experiment_name', force_separate_thread: bool = False) -> Tuple[ExperimentDataset]
    -

    Returns experiment and dataset instances +

    Returns experiment and dataset instances needed to perform the hyperparameter tuning on the data according to recipe

    Parameters

    -
    save_path : path to the folder to save experiment logs and models
    -
     
    -
    experiment_id : name of the experiment folder
    -
     
    -
    force_separate_thread : train each model in dedicated process
    -
    this feature helps to handle resources in Jupyter notebooks
    -
    +
    save_path
    +
    path to the folder to save experiment logs and models
    +
    experiment_id
    +
    name of the experiment folder
    +
    force_separate_thread
    +
    train each model in dedicated process; +this feature helps to handle resources in Jupyter notebooks
    +
    -Source code + +Expand source code +
    def build_experiment_environment(
             self,
             save_path: str,
             experiment_id: str = 'default_experiment_name',
    -        force_separate_thread: bool = False
    +        force_separate_thread: bool = False,
     ) -> Tuple[Experiment, Dataset]:
         """
         Returns experiment and dataset instances
    @@ -207,11 +267,13 @@ 

    Parameters

    Parameters ---------- - save_path: path to the folder to save experiment logs and models - experiment_id: name of the experiment folder - force_separate_thread: train each model in dedicated process + save_path + path to the folder to save experiment logs and models + experiment_id + name of the experiment folder + force_separate_thread + train each model in dedicated process; this feature helps to handle resources in Jupyter notebooks - """ if self._recipe is None: raise ValueError( @@ -222,27 +284,28 @@

    Parameters

    self._recipe, save_path=save_path, experiment_id=experiment_id, - force_separate_thread=force_separate_thread + force_separate_thread=force_separate_thread, )
    -def format_recipe(self, *args, **kwargs) +def format_recipe(self, *args, **kwargs) -> str
    -

    Updates self._recipe variable -with variables specific for the dataset

    +

    Updates self._recipe +with variables specific for the dataset.

    -Source code + +Expand source code +
    def format_recipe(self, *args, **kwargs) -> str:
         """
    -    Updates self._recipe variable
    -    with variables specific for the dataset
    +    Updates `self._recipe`
    +    with variables specific for the dataset.
         """
         raise NotImplementedError(
             'Method needs to be specified for the recipe template'
    -    )
    -    return self._recipe
    + )

    @@ -276,7 +339,7 @@

    diff --git a/docs/cooking_machine/recipes/wntm.html b/docs/cooking_machine/recipes/wntm.html index 337579e..05bb7cb 100644 --- a/docs/cooking_machine/recipes/wntm.html +++ b/docs/cooking_machine/recipes/wntm.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.recipes.wntm API documentation - - + + @@ -21,7 +21,9 @@

    Module topicnet.cooking_machine.recipes.wntm

    -Source code + +Expand source code +
    from typing import List, Tuple
     
     from .recipe_wrapper import BaseRecipe
    @@ -167,10 +169,12 @@ 

    Classes

    class WNTMRecipe
    -

    Class for baseline recipe creation and -unification of recipe interface

    +

    Class for baseline recipe creation and +unification of recipe interface

    -Source code + +Expand source code +
    class WNTMRecipe(BaseRecipe):
         """
         Class for baseline recipe creation and
    @@ -247,11 +251,68 @@ 

    Ancestors

    +

    Methods

    +
    +
    +def build_experiment_environment(self, save_path: str, experiment_id: str = 'default_experiment_name', force_separate_thread: bool = False) -> Tuple[ExperimentDataset] +
    +
    +

    Returns experiment and dataset instances +needed to perform the hyperparameter tuning on the data +according to recipe

    +

    Parameters

    +
    +
    save_path : path to the folder to save experiment logs and models
    +
     
    +
    experiment_id : name of the experiment folder
    +
     
    +
    force_separate_thread : train each model in dedicated process
    +
    this feature helps to handle resources in Jupyter notebooks
    +
    +
    + +Expand source code + +
    def build_experiment_environment(
    +        self,
    +        save_path: str,
    +        experiment_id: str = 'default_experiment_name',
    +        force_separate_thread: bool = False
    +) -> Tuple[Experiment, Dataset]:
    +    """
    +    Returns experiment and dataset instances
    +    needed to perform the hyperparameter tuning on the data
    +    according to recipe
    +
    +    Parameters
    +    ----------
    +    save_path: path to the folder to save experiment logs and models
    +    experiment_id: name of the experiment folder
    +    force_separate_thread: train each model in dedicated process
    +        this feature helps to handle resources in Jupyter notebooks
    +
    +    """
    +    if self._recipe is None:
    +        raise ValueError(
    +            'Recipe missing data specific parameters. '
    +            'Provide them with "format_recipe" method!')
    +
    +    settings, regs, model, dataset = parse(
    +        self._recipe,
    +        force_separate_thread=force_separate_thread,
    +        dataset_class=DatasetCooc
    +    )
    +    # TODO: handle dynamic addition of regularizers
    +    experiment = Experiment(experiment_id=experiment_id, save_path=save_path, topic_model=model)
    +    experiment.build(settings)
    +    return experiment, dataset
    +
    +
    +

    Inherited members

    • BaseRecipe:
    • @@ -275,6 +336,9 @@

      Index

      @@ -282,7 +346,7 @@

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/cooking_machine/rel_toolbox_lite.html b/docs/cooking_machine/rel_toolbox_lite.html index 415d3be..2a9d352 100644 --- a/docs/cooking_machine/rel_toolbox_lite.html +++ b/docs/cooking_machine/rel_toolbox_lite.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.rel_toolbox_lite API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.cooking_machine.rel_toolbox_lite<
      -Source code + +Expand source code +
      import os
       
       
      @@ -240,9 +242,11 @@ 

      Functions

      def calc_docs_avg_len(ds, weights)
      -
      +
      -Source code + +Expand source code +
      def calc_docs_avg_len(ds, weights):
           (modality_count, modality_vocab_size, n_docs) = ds
           docs_total_len = 0
      @@ -257,9 +261,11 @@ 

      Functions

      def compute_regularizer_gimel(tokens_data, reg, modality_weights, n_topics)
      -
      +
      -Source code + +Expand source code +
      def compute_regularizer_gimel(tokens_data, reg, modality_weights, n_topics):
       
           (modality_count, modality_vocab_size, num_docs) = tokens_data
      @@ -287,9 +293,11 @@ 

      Functions

      def compute_regularizer_tau(tokens_data, reg, modality_weights, n_topics)
      -
      +
      -Source code + +Expand source code +
      def compute_regularizer_tau(tokens_data, reg, modality_weights, n_topics):
       
           (modality_count, modality_vocab_size, num_docs) = tokens_data
      @@ -319,9 +327,11 @@ 

      Functions

      def count_vocab_size(dictionary, modalities)
      -
      +
      -Source code + +Expand source code +
      def count_vocab_size(dictionary, modalities):
           # TODO: check tokens filtered by dict.filter()
           fname = 'tmp.txt'
      @@ -347,7 +357,7 @@ 

      Functions

      def handle_regularizer(use_relative_coefficients, model, regularizer, data_stats)
      -

      Handles the case of various regularizers that +

      Handles the case of various regularizers that contain 'Regularizer' in their name, namely all artm regularizers

      Parameters

      @@ -355,7 +365,7 @@

      Parameters

      indicates whether regularizer should be altered
      model : TopicModel or artm.ARTM
      to be changed in place
      -
      regularizer : an instance of Regularizer from artm library
      +
      regularizer : an instance of Regularizer from artm library
       
      data_stats : dict
      collection-specific data
      @@ -364,9 +374,11 @@

      Returns

      None
       
      -
      +
      -Source code + +Expand source code +
      def handle_regularizer(use_relative_coefficients, model, regularizer, data_stats):
           """
           Handles the case of various regularizers that
      @@ -416,9 +428,11 @@ 

      Returns

      def modality_weight_rel2abs(tokens_data, weights, default_modality)
      -
      +
      -Source code + +Expand source code +
      def modality_weight_rel2abs(tokens_data, weights, default_modality):
           (modality_count, modality_vocab_size, num_docs) = tokens_data
           taus = {}
      @@ -437,9 +451,11 @@ 

      Returns

      def phi_weight_abs2rel(ds, modality_weights, n_topics, tau, modalities_list=None)
      -
      +
      -Source code + +Expand source code +
      def phi_weight_abs2rel(ds, modality_weights, n_topics, tau, modalities_list=None):
           (modality_count, modality_vocab_size, n_docs) = ds
           if modalities_list is None:
      @@ -461,9 +477,11 @@ 

      Returns

      def phi_weight_rel2abs(ds, modality_weights, n_topics, gimel, modalities_list=None)
      -
      +
      -Source code + +Expand source code +
      def phi_weight_rel2abs(ds, modality_weights, n_topics, gimel, modalities_list=None):
           (modality_count, modality_vocab_size, n_docs) = ds
           if modalities_list is None:
      @@ -484,9 +502,11 @@ 

      Returns

      def theta_weight_abs2rel(ds, modality_weights, n_topics, tau)
      -
      +
      -Source code + +Expand source code +
      def theta_weight_abs2rel(ds, modality_weights, n_topics, tau):
           avg_doc_len = calc_docs_avg_len(ds, modality_weights)
           gimel_multiplier = avg_doc_len / n_topics + tau
      @@ -498,9 +518,11 @@ 

      Returns

      def theta_weight_rel2abs(ds, modality_weights, n_topics, gimel)
      -
      +
      -Source code + +Expand source code +
      def theta_weight_rel2abs(ds, modality_weights, n_topics, gimel):
           avg_doc_len = calc_docs_avg_len(ds, modality_weights)
           tau = (avg_doc_len / n_topics) * gimel / (1 - gimel)
      @@ -511,9 +533,11 @@ 

      Returns

      def transform_regularizer(tokens_data, reg, modality_weights, n_topics=None)
      -
      +
      -Source code + +Expand source code +
      def transform_regularizer(tokens_data, reg, modality_weights, n_topics=None):
       
           if n_topics is None and len(reg.topic_names) == 0:
      @@ -569,7 +593,7 @@ 

      Index

      diff --git a/docs/cooking_machine/routine.html b/docs/cooking_machine/routine.html index de9150d..a624a2c 100644 --- a/docs/cooking_machine/routine.html +++ b/docs/cooking_machine/routine.html @@ -3,14 +3,14 @@ - + topicnet.cooking_machine.routine API documentation - - + + @@ -21,15 +21,20 @@

      Module topicnet.cooking_machine.routine

      -Source code -
      import numpy as np
      +
      +Expand source code
      +
      +
      import glob
       import hashlib
       import json
      +import numexpr as ne
      +import numpy as np
      +import os
       import re
       import warnings
      +
       from datetime import datetime
       from statistics import mean, median
      -import numexpr as ne
       
       
       W_TOO_STRICT = 'No models match criteria '
      @@ -465,7 +470,7 @@ 

      Module topicnet.cooking_machine.routine

      models_num = models_num_from_query if models_num is not None and int(models_num) < 0: - raise ValueError(f"Cannot return negative number of models") + raise ValueError("Cannot return negative number of models") return models_num @@ -688,7 +693,30 @@

      Module topicnet.cooking_machine.routine

      if not data: break m.update(data) - return m.hexdigest()
      + return m.hexdigest() + + +def load_models_from_disk(experiment_directory, base_experiment_name): + """ + Is useful for restoring failed experiment + """ + from topicnet.cooking_machine.experiment import START + from topicnet.cooking_machine.models import DummyTopicModel + + result_models = [] + + mask = f"{experiment_directory}/{base_experiment_name}_*" + msg = (f'Trying to load models from {mask}.' + f' {len(glob.glob(mask))} models found.') + print(msg) + for folder in glob.glob(mask): + model_pathes = [ + f.path for f in os.scandir(folder) + if f.is_dir() and f.name != START + ] + result_models += [DummyTopicModel.load(path) for path in model_pathes] + + return result_models
      @@ -702,14 +730,16 @@

      Functions

      def blake2bchecksum(file_path)
      -

      Calculates hash of the file

      +

      Calculates hash of the file

      Parameters

      file_path : str
      path to the file
      -
      +
      -Source code + +Expand source code +
      def blake2bchecksum(file_path):
           """
           Calculates hash of the file
      @@ -730,10 +760,10 @@ 

      Parameters

      -def choose_best_models(models, requirement_lesser, requirement_greater, requirement_equal, metric, extremum='min', models_num=None) +def choose_best_models(models: list, requirement_lesser: list, requirement_greater: list, requirement_equal: list, metric: str, extremum='min', models_num=None)
      -

      Get best model according to specified metric.

      +

      Get best model according to specified metric.

      Parameters

      models : list of TopicModel
      @@ -759,9 +789,11 @@

      Returns

      best_models : list of models
      models with best scores or matching request
      -
      +
      -Source code + +Expand source code +
      def choose_best_models(models: list, requirement_lesser: list, requirement_greater: list,
                              requirement_equal: list, metric: str, extremum="min", models_num=None):
           """
      @@ -821,12 +853,14 @@ 

      Returns

      -def choose_value_for_models_num_and_check(models_num_as_parameter, models_num_from_query) +def choose_value_for_models_num_and_check(models_num_as_parameter, models_num_from_query) -> int
      -
      +
      -Source code + +Expand source code +
      def choose_value_for_models_num_and_check(
               models_num_as_parameter, models_num_from_query) -> int:
       
      @@ -851,7 +885,7 @@ 

      Returns

      models_num = models_num_from_query if models_num is not None and int(models_num) < 0: - raise ValueError(f"Cannot return negative number of models") + raise ValueError("Cannot return negative number of models") return models_num
      @@ -860,9 +894,11 @@

      Returns

      def compute_special_queries(special_models, special_queries)
      -

      Computes special queries with functions.

      +

      Computes special queries with functions.

      -Source code + +Expand source code +
      def compute_special_queries(special_models, special_queries):
           """
           Computes special queries with functions.
      @@ -908,7 +944,7 @@ 

      Returns

      def extract_required_parameter(model, parameter)
      -

      Extracts necessary parameter from model.

      +

      Extracts necessary parameter from model.

      Parameters

      model : TopicModel
      @@ -918,11 +954,13 @@

      Parameters

      Returns

      -
      optional
      +
      optional
       
      -
      +
      -Source code + +Expand source code +
      def extract_required_parameter(model, parameter):
           """
           Extracts necessary parameter from model.
      @@ -975,10 +1013,10 @@ 

      Returns

      -def get_equal_lists(one_dict, min_len=0, sep=' ', sep_len='last') +def get_equal_lists(one_dict, min_len: int = 0, sep: str = ' ', sep_len='last')
      -

      Transforms all lists to list with the same length, but not less that min_len. +

      Transforms all lists to list with the same length, but not less that min_len. Fills lists with sep. Inplace.

      Parameters

      @@ -991,9 +1029,11 @@

      Parameters

      sep_len : int or "last"
      length of added strings, if "last" than length of added strings is equal to the length of the last string in the list (Default value = "last")
      -
      +
      -Source code + +Expand source code +
      def get_equal_lists(one_dict, min_len: int = 0, sep: str = " ", sep_len="last"):
           """
           Transforms all lists to list with the same length, but not less that min_len.
      @@ -1024,10 +1064,10 @@ 

      Parameters

      -def get_equal_strings(strings, min_len=0, sep=' ') +def get_equal_strings(strings, min_len: int = 0, sep: str = ' ')
      -

      Transforms all strings to strings with the same length, but not less that min_len. +

      Transforms all strings to strings with the same length, but not less that min_len. Fills strings with sep. Inplace.

      Parameters

      @@ -1037,9 +1077,11 @@

      Parameters

      minimal length of the string (Default value = 0)
      sep : str
      filling symbol (Default value = " ")
      -
      +
      -Source code + +Expand source code +
      def get_equal_strings(strings, min_len: int = 0, sep: str = " "):
           """
           Transforms all strings to strings with the same length, but not less that min_len.
      @@ -1063,10 +1105,10 @@ 

      Parameters

      -def get_fix_list(input_list, length, num) +def get_fix_list(input_list: list, length: int, num: int)
      -

      Returns list with strings of size length that contains not more than num strings.

      +

      Returns list with strings of size length that contains not more than num strings.

      Parameters

      input_list : list
      @@ -1080,9 +1122,11 @@

      Returns

      list
      list with no more than num of beautiful strings
      -
      +
      -Source code + +Expand source code +
      def get_fix_list(input_list: list, length: int, num: int):
           """
           Returns list with strings of size length that contains not more than num strings.
      @@ -1130,10 +1174,10 @@ 

      Returns

      -def get_fix_string(input_string, length) +def get_fix_string(input_string: str, length: int)
      -

      Transforms input_string to the string of the size length.

      +

      Transforms input_string to the string of the size length.

      Parameters

      input_string : str
      @@ -1145,9 +1189,11 @@

      Returns

      str
      beautiful string of the size length
      -
      +
      -Source code + +Expand source code +
      def get_fix_string(input_string: str, length: int):
           """
           Transforms input_string to the string of the size length.
      @@ -1184,19 +1230,21 @@ 

      Returns

      def get_public_instance_attributes(instance)
      -

      Get list of all instance public atrributes.

      +

      Get list of all instance public atrributes.

      Parameters

      -
      instance : optional
      +
      instance : optional
       

      Returns

      list of str
       
      -
      +
      -Source code + +Expand source code +
      def get_public_instance_attributes(instance):
           """
           Get list of all instance public atrributes.
      @@ -1221,14 +1269,16 @@ 

      Returns

      def get_timestamp_in_str_format()
      -

      Returns current timestamp.

      +

      Returns current timestamp.

      Returns

      str
      timestamp in "%Hh%Mm%Ss_%dd%mm%Yy" format
      -
      +
      -Source code + +Expand source code +
      def get_timestamp_in_str_format():
           """
           Returns current timestamp.
      @@ -1248,7 +1298,7 @@ 

      Returns

      def is_acceptable(model, requirement_lesser, requirement_greater, requirement_equal)
      -

      Checks if model suits request.

      +

      Checks if model suits request.

      Parameters

      model : TopicModel
      @@ -1264,9 +1314,11 @@

      Returns

      bool
       
      -
      +
      -Source code + +Expand source code +
      def is_acceptable(model, requirement_lesser, requirement_greater, requirement_equal):
           """
           Checks if model suits request.
      @@ -1307,19 +1359,21 @@ 

      Returns

      def is_jsonable(x)
      -

      Check that x is jsonable

      +

      Check that x is jsonable

      Parameters

      -
      x : optional
      +
      x : optional
       

      Returns

      bool
       
      -
      +
      -Source code + +Expand source code +
      def is_jsonable(x):
           """
           Check that x is jsonable
      @@ -1344,9 +1398,11 @@ 

      Returns

      def is_saveable_model(model=None, model_id=None, experiment=None)
      -

      Little helpful function. May be extended later.

      +

      Little helpful function. May be extended later.

      -Source code + +Expand source code +
      def is_saveable_model(model=None, model_id=None, experiment=None):
           """
           Little helpful function. May be extended later.
      @@ -1368,11 +1424,43 @@ 

      Returns

      return isinstance(model, SUPPORTED_MODEL_CLASSES)
      +
      +def load_models_from_disk(experiment_directory, base_experiment_name) +
      +
      +

      Is useful for restoring failed experiment

      +
      + +Expand source code + +
      def load_models_from_disk(experiment_directory, base_experiment_name):
      +    """
      +    Is useful for restoring failed experiment
      +    """
      +    from topicnet.cooking_machine.experiment import START
      +    from topicnet.cooking_machine.models import DummyTopicModel
      +
      +    result_models = []
      +
      +    mask = f"{experiment_directory}/{base_experiment_name}_*"
      +    msg = (f'Trying to load models from {mask}.'
      +           f' {len(glob.glob(mask))} models found.')
      +    print(msg)
      +    for folder in glob.glob(mask):
      +        model_pathes = [
      +            f.path for f in os.scandir(folder)
      +            if f.is_dir() and f.name != START
      +        ]
      +        result_models += [DummyTopicModel.load(path) for path in model_pathes]
      +
      +    return result_models
      +
      +
      -def parse_query_string(query_string) +def parse_query_string(query_string: str)
      -

      This function will parse query string and subdivide it into following parts:

      +

      This function will parse query string and subdivide it into following parts:

      Parameters

      query_string : str
      @@ -1390,9 +1478,11 @@

      Returns

       
      extremum : str
       
      -
      +
      -Source code + +Expand source code +
      def parse_query_string(query_string: str):
           """
           This function will parse query string and subdivide it into following parts:
      @@ -1460,18 +1550,20 @@ 

      Returns

      def transform_complex_entity_to_dict(some_entity)
      -

      Parameters

      +

      Parameters

      -
      some_entity : optional
      +
      some_entity : optional
       

      Returns

      dict
      jsonable entity
      -
      +
      -Source code + +Expand source code +
      def transform_complex_entity_to_dict(some_entity):
           """
       
      @@ -1505,7 +1597,7 @@ 

      Returns

      def transform_topic_model_description_to_jsonable(obj)
      -

      Change object to handle serialization problems with json.

      +

      Change object to handle serialization problems with json.

      Parameters

      obj : object
      @@ -1515,9 +1607,11 @@

      Returns

      int
      jsonable object
      -
      +
      -Source code + +Expand source code +
      def transform_topic_model_description_to_jsonable(obj):
           """
           Change object to handle serialization problems with json.
      @@ -1588,6 +1682,7 @@ 

      Index

    • is_acceptable
    • is_jsonable
    • is_saveable_model
    • +
    • load_models_from_disk
    • parse_query_string
    • transform_complex_entity_to_dict
    • transform_topic_model_description_to_jsonable
    • @@ -1597,7 +1692,7 @@

      Index

      diff --git a/docs/dataset_manager/api.html b/docs/dataset_manager/api.html index f3636a2..764a318 100644 --- a/docs/dataset_manager/api.html +++ b/docs/dataset_manager/api.html @@ -3,14 +3,14 @@ - + topicnet.dataset_manager.api API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.dataset_manager.api

      -Source code + +Expand source code +
      import gzip
       import os
       import pandas as pd
      @@ -41,7 +43,7 @@ 

      Module topicnet.dataset_manager.api

      from ..cooking_machine.dataset import Dataset -_SERVER_URL = 'https://93.175.29.159:8085' +_SERVER_URL = 'https://topicnet-datasets.machine-intelligence.ru' _ARCHIVE_EXTENSION = '.gz' _DEFAULT_DATASET_FILE_EXTENSION = '.csv' @@ -92,12 +94,14 @@

      Module topicnet.dataset_manager.api

      dataset_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dataset_name) try: + print(f'Checking if dataset "{dataset_name}" was already downloaded before') + saved_dataset = _init_dataset_if_downloaded(dataset_path, **kwargs) except FileNotFoundError: - pass + print(f'Dataset "{dataset_name}" not found on the machine') else: print( - f'Dataset already downloaded!' + f'Dataset is found on the machine.' f' Save path is: "{saved_dataset._data_path}"' ) @@ -111,6 +115,8 @@

      Module topicnet.dataset_manager.api

      print(f'Downloading the "{dataset_name}" dataset...') + save_path = None + try: with urlopen(req, data=data, context=context) as answer: total_size = int(answer.headers.get('content-length', 0)) @@ -133,7 +139,7 @@

      Module topicnet.dataset_manager.api

      if total_size != 0 and t.n != total_size: raise RuntimeError( - "Failed to download dataset!" + "Failed to download the dataset!" " Some data was lost during network transfer" ) @@ -146,19 +152,19 @@

      Module topicnet.dataset_manager.api

      return Dataset(save_path, **kwargs) except Exception as exception: - if os.path.isfile(save_path): + if save_path is not None and os.path.isfile(save_path): os.remove(save_path) raise exception finally: - if os.path.isfile(save_path + _ARCHIVE_EXTENSION): + if save_path is not None and os.path.isfile(save_path + _ARCHIVE_EXTENSION): os.remove(save_path + _ARCHIVE_EXTENSION) def _init_dataset_if_downloaded(dataset_path: str, **kwargs) -> Dataset: saved_dataset_path_candidates = [ - p for p in glob(dataset_path + '*') + p for p in glob(dataset_path + '.*') if os.path.isfile(p) and not p.endswith(_ARCHIVE_EXTENSION) ] dataset = None @@ -185,25 +191,27 @@

      Module topicnet.dataset_manager.api

      Functions

      -def get_info() +def get_info() -> str
      -

      Gets info about all datasets.

      +

      Gets info about all datasets.

      Returns

      -
      str with MarkDown syntax
      +
      str with MarkDown syntax
       

      Examples

      As the return value is MarkDown text, in Jupyter Notebook one may do the following to format the output information nicely

      -
      >>> from IPython.display import Markdown
      +
      >>> from IPython.display import Markdown
       ...
       >>> Markdown(get_info())
      -
      +
      -Source code + +Expand source code +
      def get_info() -> str:
           """
           Gets info about all datasets.
      @@ -231,24 +239,24 @@ 

      Examples

      -def load_dataset(dataset_name, **kwargs) +def load_dataset(dataset_name: str, **kwargs) -> Dataset
      -

      Load dataset by dataset_name. -Run <a title="topicnet.dataset_manager.api.get_info" href="#topicnet.dataset_manager.api.get_info">get_info()</a> to get dataset information

      +

      Load dataset by dataset_name. +Run get_info() to get dataset information

      Parameters

      dataset_name : str
      dataset name for download

      Another Parameters

      -
      -
      kwargs
      -
      optional properties of -:class:~topicnet.cooking_machine.Dataset
      -
      +

      kwargs +optional properties of +:class:~topicnet.cooking_machine.Dataset

      -Source code + +Expand source code +
      def load_dataset(dataset_name: str, **kwargs) -> Dataset:
           """
           Load dataset by dataset_name.
      @@ -269,12 +277,14 @@ 

      Another Parameters

      dataset_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dataset_name) try: + print(f'Checking if dataset "{dataset_name}" was already downloaded before') + saved_dataset = _init_dataset_if_downloaded(dataset_path, **kwargs) except FileNotFoundError: - pass + print(f'Dataset "{dataset_name}" not found on the machine') else: print( - f'Dataset already downloaded!' + f'Dataset is found on the machine.' f' Save path is: "{saved_dataset._data_path}"' ) @@ -288,6 +298,8 @@

      Another Parameters

      print(f'Downloading the "{dataset_name}" dataset...') + save_path = None + try: with urlopen(req, data=data, context=context) as answer: total_size = int(answer.headers.get('content-length', 0)) @@ -310,7 +322,7 @@

      Another Parameters

      if total_size != 0 and t.n != total_size: raise RuntimeError( - "Failed to download dataset!" + "Failed to download the dataset!" " Some data was lost during network transfer" ) @@ -323,13 +335,13 @@

      Another Parameters

      return Dataset(save_path, **kwargs) except Exception as exception: - if os.path.isfile(save_path): + if save_path is not None and os.path.isfile(save_path): os.remove(save_path) raise exception finally: - if os.path.isfile(save_path + _ARCHIVE_EXTENSION): + if save_path is not None and os.path.isfile(save_path + _ARCHIVE_EXTENSION): os.remove(save_path + _ARCHIVE_EXTENSION)
      @@ -359,7 +371,7 @@

      Index

      diff --git a/docs/dataset_manager/index.html b/docs/dataset_manager/index.html index d07624e..3b81a4b 100644 --- a/docs/dataset_manager/index.html +++ b/docs/dataset_manager/index.html @@ -3,14 +3,14 @@ - + topicnet.dataset_manager API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.dataset_manager

      -Source code + +Expand source code +
      from .api import (
           get_info,
           load_dataset,
      @@ -33,7 +35,7 @@ 

      Sub-modules

      topicnet.dataset_manager.api
      -
      +
      @@ -64,7 +66,7 @@

      Index

      diff --git a/docs/index.html b/docs/index.html index a1a46e2..dc0efc1 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,36 +3,27 @@ - + topicnet API documentation - - + +
      -

      Module topicnet

      +

      Package topicnet

      -

      TopicNet

      -

      The library was created to assist in the task of building topic models. It aims to automate away many routine tasks related to topic model training, allowing a user to focus on the task at hand. Also, it provides additional tools to construct advanced topic models. The library consists of the following modules:

      -
        -
      • cooking_machine — provides tools to design a topic model construction pipeline, or experiment with regularizers fitting
      • -
      • viewers — provides information about the topic model in an accessible format
      • -
      • demos — demo .ipynb notebooks
      • -
      • dataset_manager — gives opportunity to download datasets for experiments
      • -
      • tests — provides a user with means to test library functionality (contains some examples of intended library usage)
      • -
      -

      Project description

      -

      In TopicNet framework, advanced topic models are build using Experiment class. An experiment consists of stages (that we call “cubes”) which perform actions over the “models” which are objects of the Experiment. The experiment instance of Experiment class contains all the information about the experiment process and automatically updates its log when a cube is applied to the last level models. It is worth noting that the experiment is linear, meaning it does not support multiple different cubes at the same stage of the experiment. If that need arises one is recommended to create a new experiment with a new cube on the last level. The experiment instance of Experiment class contains all the information about the experiment process and automatically updates its log when the cube is applied to the last level models. Summarizing: the key entity Experiment is a sequence of cubes that produce models on each stage of the experiment process

      -Source code + +Expand source code +
      import artm
       
       # change log style
      @@ -49,15 +40,15 @@ 

      Sub-modules

      topicnet.cooking_machine
      -
      +
      topicnet.dataset_manager
      -
      +
      topicnet.viewers
      -
      +
      @@ -69,7 +60,7 @@

      Sub-modules

      diff --git a/docs/viewers/base_viewer.html b/docs/viewers/base_viewer.html index 93fa6f2..8352072 100644 --- a/docs/viewers/base_viewer.html +++ b/docs/viewers/base_viewer.html @@ -3,14 +3,14 @@ - + topicnet.viewers.base_viewer API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.base_viewer

      -Source code + +Expand source code +
      from ..cooking_machine.models.base_model import BaseModel
       
       
      @@ -64,9 +66,11 @@ 

      Classes

      (model)
      -
      +
      -Source code + +Expand source code +
      class BaseViewer:
           """ """
           def __init__(self, model):
      @@ -94,21 +98,23 @@ 

      Classes

      Subclasses

      Instance variables

      var model
      -
      +
      -Source code + +Expand source code +
      @property
       def model(self):
           """ """
      @@ -122,14 +128,16 @@ 

      Methods

      def view(self, *args, **kwargs)
      -

      Main method of viewer.

      +

      Main method of viewer.

      Returns

      -
      optional
      +
      optional
       
      -
      +
      -Source code + +Expand source code +
      def view(self, *args, **kwargs):
           """
           Main method of viewer.
      @@ -173,7 +181,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/viewers/document_cluster.html b/docs/viewers/document_cluster.html index 09e5961..f9d17dd 100644 --- a/docs/viewers/document_cluster.html +++ b/docs/viewers/document_cluster.html @@ -3,14 +3,14 @@ - + topicnet.viewers.document_cluster API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.document_cluster

      -Source code + +Expand source code +
      import numpy as np
       import colorlover as cl
       import plotly.graph_objs as go
      @@ -181,14 +183,16 @@ 

      Classes

      (model)
      -

      This viewer performs dimesionality reduction over document embeddings

      +

      This viewer performs dimesionality reduction over document embeddings

      Parameters

      model : TopicModel
       
      -
      +
      -Source code + +Expand source code +
      class DocumentClusterViewer(BaseViewer):
           """
           This viewer performs dimesionality reduction over document embeddings
      @@ -330,10 +334,10 @@ 

      Ancestors

      Methods

      -def viev_from_jupyter(self, dataset, method='TSNE', save_path='DocumentCluster_view.html', width=800, height=600, display_output=True, give_html=False) +def viev_from_jupyter(self, dataset, method: str = 'TSNE', save_path: str = 'DocumentCluster_view.html', width: int = 800, height: int = 600, display_output: bool = True, give_html: bool = False)
      -

      Parameters

      +

      Parameters

      dataset : Dataset
       
      @@ -359,9 +363,11 @@

      Returns

      out_html : string
      an html string containing the plotly graph returned only if give_html is True
      -
      +
      -Source code + +Expand source code +
      def viev_from_jupyter(
           self,
           dataset,
      @@ -418,7 +424,7 @@ 

      Returns

      def view(self, dataset, save_path, method='TSNE', to_html=True)
      -

      Parameters

      +

      Parameters

      dataset : Dataset
       
      @@ -431,14 +437,16 @@

      Returns

      Returns

      -
      reduced_data : an np.array of (num_docs, dim) dimensions
      +
      reduced_data : an np.array of (num_docs, dim) dimensions
      reduced dumensions of the original document embeddings
      html_div : string
      an html string containing the plotly graph returned only if to_html is True
      -
      +
      -Source code + +Expand source code +
      def view(
               self,
               dataset,
      @@ -540,7 +548,7 @@ 

      diff --git a/docs/viewers/index.html b/docs/viewers/index.html index b49bbd0..1051a57 100644 --- a/docs/viewers/index.html +++ b/docs/viewers/index.html @@ -3,14 +3,14 @@ - + topicnet.viewers API documentation - - + + @@ -19,81 +19,11 @@

      Module topicnet.viewers

      -

      Viewers

      -

      Module viewers provides information from a topic model allowing to estimate the model quality. Its advantage is in unified call ifrastucture to the topic model making the routine and tedious task of extracting the information easy.

      -

      Currently module contains the following viewers:

      -

      base_viewer (BaseViewer)

      -

      Module responsible for base infrastructure.

      -

      document_cluster (DocumentClusterViewer)

      -

      Module which allows to visualize collection documents. May be slow for large document collections as it uses TSNE algorithm from sklearn library.

      -

      -

      -
          <img src="../docs/images/doc_cluster__plot.png" width="80%" alt/>
      -</div>
      -<em>
      -    Visualisation of reduced document embeddings colored according to their topic made by DocumentClusterViewer.
      -</em>
      -

      -

      spectrum (TopicSpectrumViewer)

      -

      Module contains heuristics for solving TSP to arrange topics minimizing total distance of the spectrum.

      -

      -

      -
          <img src="../docs/images/topic_spectrum__refined_view.png" width="80%" alt/>
      -</div>
      -<em>
      -    Each point on the plot represents some topic.
      -    The viewer helped to calculate such a route between topics when one topic is connected with similar one, and so on, forming a circle.
      -</em>
      -

      -

      top_documents_viewer (TopDocumentsViewer)

      -

      Module with functions that work with dataset document collections.

      -

      -

      -
          <img src="../docs/images/top_doc__view.png" width="80%" alt/>
      -</div>
      -<em>
      -    The viewer shows fragments of top documents corresponding to some topic.
      -</em>
      -

      -

      top_similar_documents_viewer (TopSimilarDocumentsViewer)

      -

      Module containing class for finding similar document for a given one. This viewer helps to estimate homogeneity of clusters given by the model.

      -

      -

      -
          <img src="../docs/images/top_sim_doc__refined_view.png" width="80%" alt/>
      -</div>
      -<em>
      -    Some document from text collection (on top), and documents nearest to it given topic model.
      -    The viewer (currently) gives only document names as output, but the picture is not very difficult to be made.
      -</em>
      -

      -

      top_tokens_viewer (TopTokensViewer)

      -

      Module with class for displaying the most relevant tokens in each topic of the model.

      -

      -

      -
          <img src="../docs/images/top_tokens__view.png" width="80%" alt/>
      -</div>
      -<em>
      -    Output of the TopTokensViewer. Token score in the topic is calculated for every token, score function can be specified at the stage of a viewer initialization.
      -</em>
      -

      -

      topic_mapping (TopicMapViewer)

      -

      Module allowing to compare topics between two different models trained on the same collection.

      -

      -

      -
          <img src="../docs/images/topic_map__view.png" width="80%" alt/>
      -</div>
      -<em>
      -    The mapping between topics of two models (currently only topic names are displayed).
      -</em>
      -

      -

      Deprecated

      -
        -
      • initial_doc_to_topic_viewer — first edition of TopDocumentsViewer

      • -
      • tokens_viewer - first edition of TopTokensViewer

      • -
      -Source code + +Expand source code +
      from .base_viewer import BaseViewer
       from .document_cluster import DocumentClusterViewer
       from .spectrum import TopicSpectrumViewer
      @@ -108,41 +38,41 @@ 

      Sub-modules

      topicnet.viewers.base_viewer
      -
      +
      topicnet.viewers.document_cluster
      -
      +
      topicnet.viewers.initial_doc_to_topic_viewer
      -
      +
      topicnet.viewers.spectrum
      -

      A few ways to obtain "decent" solution to TSP problem +

      A few ways to obtain "decent" solution to TSP problem which returns a spectre of topics in our case.
      -If speed is the essence I recommend to use …

      +If speed is the essence I recommend to use …

      topicnet.viewers.top_documents_viewer
      -
      +
      topicnet.viewers.top_similar_documents_viewer
      -
      +
      topicnet.viewers.top_tokens_viewer
      -
      +
      topicnet.viewers.topic_flow_viewer
      -
      +
      topicnet.viewers.topic_mapping
      -
      +
      @@ -154,7 +84,7 @@

      Sub-modules

      diff --git a/docs/viewers/initial_doc_to_topic_viewer.html b/docs/viewers/initial_doc_to_topic_viewer.html index 7f03d3e..e071870 100644 --- a/docs/viewers/initial_doc_to_topic_viewer.html +++ b/docs/viewers/initial_doc_to_topic_viewer.html @@ -3,14 +3,14 @@ - + topicnet.viewers.initial_doc_to_topic_viewer API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.initial_doc_to_topic_viewer
      -Source code + +Expand source code +
      from .base_viewer import BaseViewer
       
       
      @@ -85,9 +87,11 @@ 

      Classes

      (dataset_id, model)
      -
      +
      -Source code + +Expand source code +
      class TopTopicsFeatures(BaseViewer):
           """ """
           def __init__(self, dataset_id, model):
      @@ -143,22 +147,24 @@ 

      Methods

      def view(self, document_id, topic_name=None, batch_vectorizer=None)
      -

      Parameters

      +

      Parameters

      document_id : str
      id of document
      topic_name : str
      (Default value = None)
      -
      batch_vectorizer : optional
      +
      batch_vectorizer : optional
      (Default value = None)

      Returns

      result : dict
       
      -
      +
      -Source code + +Expand source code +
      def view(self, document_id, topic_name=None, batch_vectorizer=None):
           """
       
      @@ -229,7 +235,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/viewers/spectrum.html b/docs/viewers/spectrum.html index ce8d61a..ee5e06a 100644 --- a/docs/viewers/spectrum.html +++ b/docs/viewers/spectrum.html @@ -3,7 +3,7 @@ - + topicnet.viewers.spectrum API documentation - - + + @@ -32,7 +32,9 @@

      Module topicnet.viewers.spectrum

      Within a few runs with right temperature selected it can provide a solution better than the initial.

      -Source code + +Expand source code +
      """
       A few ways to obtain "decent" solution to TSP problem
       which returns a spectre of topics in our case.  
      @@ -508,19 +510,21 @@ 

      Functions

      def generate_all_segments(n)
      -

      Generates all segments combinations for 3-opt swap operation.

      +

      Generates all segments combinations for 3-opt swap operation.

      Parameters

      -
      n : int > 5
      +
      n : int > 5
      length of path for fixed endpoint

      Yields

      list of int
       
      -
      +
      -Source code + +Expand source code +
      def generate_all_segments(n):
           """
           Generates all segments combinations for 3-opt swap operation.
      @@ -545,20 +549,22 @@ 

      Yields

      def generate_index_candidates(n)
      -

      Randomly chooses 3 indexes from the path.
      +

      Randomly chooses 3 indexes from the path.
      Does not swap the first or the last point because they fixed.

      Parameters

      -
      n : int > 5
      +
      n : int > 5
      length of the path

      Returns

      segment : list of int
      sorted list of candidates for 3 opt swap optimization
      -
      +
      -Source code + +Expand source code +
      def generate_index_candidates(n):
           """
           Randomly chooses 3 indexes from the path.  
      @@ -593,7 +599,7 @@ 

      Returns

      def generate_three_opt_candidates(path, sequence)
      -

      Generates all possible tour connections and filters out a trivial one.

      +

      Generates all possible tour connections and filters out a trivial one.

      Parameters

      path : np.array of float
      @@ -605,9 +611,11 @@

      Yields

      list of int
      possible tour
      -
      +
      -Source code + +Expand source code +
      def generate_three_opt_candidates(path, sequence):
           """
           Generates all possible tour connections and filters out a trivial one.
      @@ -655,7 +663,7 @@ 

      Yields

      def get_annealed_spectrum(phi_matrix, t_coeff, start_topic=0, metric='jensenshannon', init_path=None, max_iter=1000000, early_stopping=100000)
      -

      Returns annealed spectrum for the topics in the Phi matrix +

      Returns annealed spectrum for the topics in the Phi matrix with default metrics being Jensen-Shannon.

      Parameters

      @@ -682,9 +690,11 @@

      Returns

      best path obtained during the run
      best_score : float
      length of the best path during the run
      -
      +
      -Source code + +Expand source code +
      def get_annealed_spectrum(phi_matrix,
                                 t_coeff,
                                 start_topic=0,
      @@ -766,7 +776,7 @@ 

      Returns

      def get_nearest_neighbour_init(phi_matrix, metric='jensenshannon', start_topic=0)
      -

      Given the matrix calculates the initial path by nearest neighbour heuristic.

      +

      Given the matrix calculates the initial path by nearest neighbour heuristic.

      Parameters

      phi_matrix : np.array of float
      @@ -780,9 +790,11 @@

      Returns

      init_path : list of int
      order of initial topic distribution
      -
      +
      -Source code + +Expand source code +
      def get_nearest_neighbour_init(phi_matrix, metric='jensenshannon', start_topic=0):
           """
           Given the matrix calculates the initial path by nearest neighbour heuristic.
      @@ -824,7 +836,7 @@ 

      Returns

      def get_three_opt_path(path, distance_m, max_iter=20)
      -

      Iterative improvement based on 3 opt exchange.

      +

      Iterative improvement based on 3 opt exchange.

      Parameters

      path : list of int
      @@ -839,9 +851,11 @@

      Returns

      path : list of int
      end optimization of the route
      -
      +
      -Source code + +Expand source code +
      def get_three_opt_path(path, distance_m, max_iter=20):
           """
           Iterative improvement based on 3 opt exchange.
      @@ -882,7 +896,7 @@ 

      Returns

      def make_three_opt_swap(path, distance_m, sequence, temperature=None)
      -

      Performs swap based on the selection candidates, +

      Performs swap based on the selection candidates, allows for non-optimal solution to be accepted based on Boltzman distribution.

      Parameters

      @@ -905,9 +919,11 @@

      Returns

      best path after the permutation
      val : float
      a value gained after the path permutation
      -
      +
      -Source code + +Expand source code +
      def make_three_opt_swap(path, distance_m, sequence, temperature=None):
           """
           Performs swap based on the selection candidates,
      @@ -975,7 +991,7 @@ 

      Classes

      (model, t_coeff=100000.0, start_topic=0, metric='jensenshannon', init_path=None, max_iter=1000000, early_stopping=100000, verbose=False, class_ids=None)
      -

      Class providing wrap around for functions +

      Class providing wrap around for functions that allow to view a collection of topics in order of their similarity to each other.

      Parameters

      @@ -1001,9 +1017,11 @@

      Parameters

      parameter for model.get_phi method contains list of modalities to obtain from the model (Default value = None)
      -
      +
      -Source code + +Expand source code +
      class TopicSpectrumViewer(BaseViewer):
           def __init__(
               self,
      @@ -1157,7 +1175,7 @@ 

      Methods

      def view(self, class_ids=None)
      -

      The class method returning ordered spectrum of +

      The class method returning ordered spectrum of the topics.

      Parameters

      @@ -1166,9 +1184,11 @@

      Parameters

      contains list of modalities to obtain from the model (Default value = None)
      ordered_topics : list of str
      topic names from the model ordered as spectrum
      -

      +
      -Source code + +Expand source code +
      def view(self, class_ids=None):
           """
           The class method returning ordered spectrum of
      @@ -1208,7 +1228,7 @@ 

      Parameters

      def view_from_jupyter(self, class_ids=None, display_output=True, give_html=False, **kwargs)
      -

      TopicSpectrumViewer method recommended for use +

      TopicSpectrumViewer method recommended for use from jupyter notebooks returns ordered list of topics minimizing path that connects all of them in topic space @@ -1228,12 +1248,12 @@

      Returns

      html string of the output

      Another Parameters

      -
      -
      **kwargs
      -
      kwargs are optional ~.TopTokenViewer properties
      -
      +

      kwargs +kwargs are optional ~.TopTokenViewer properties

      -Source code + +Expand source code +
      def view_from_jupyter(
               self,
               class_ids=None,
      @@ -1334,7 +1354,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/viewers/top_documents_viewer.html b/docs/viewers/top_documents_viewer.html index 5b95f29..3977fb2 100644 --- a/docs/viewers/top_documents_viewer.html +++ b/docs/viewers/top_documents_viewer.html @@ -3,14 +3,14 @@ - + topicnet.viewers.top_documents_viewer API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.top_documents_viewer

      -Source code + +Expand source code +
      import numpy as np
       
       from collections import defaultdict
      @@ -348,7 +350,7 @@ 

      Functions

      def compute_cluster_top_objects_by_distance(precomputed_distances, max_top_number=10, object_clusters=None)
      -

      Compute the most representative objects for each cluster +

      Compute the most representative objects for each cluster using the precomputed_distances.

      Parameters

      @@ -358,16 +360,18 @@

      Parameters

      max_top_number : int
      maximum number of top objects of cluster (resulting number can be less than it) (Default value = 10)
      -
      object_clusters : np,array
      +
      object_clusters : np,array
      array of shape n_objects - precomputed clusters for objects

      Returns

      -
      clusters_top_objects : list of list of indexes
      +
      clusters_top_objects : list of list of indexes
      (Default value = None)
      -
      +
      -Source code + +Expand source code +
      def compute_cluster_top_objects_by_distance(precomputed_distances,
                                                   max_top_number=10,
                                                   object_clusters=None):
      @@ -434,7 +438,7 @@ 

      Returns

      def predict_cluster_by_precomputed_distances(precomputed_distances)
      -

      Predict a cluster for each object with precomputed distances.

      +

      Predict a cluster for each object with precomputed distances.

      Parameters

      precomputed_distances : np.array
      @@ -444,9 +448,11 @@

      Returns

      np.array
      array of length X.shape[0], each element is cluster of ith object
      -
      +
      -Source code + +Expand source code +
      def predict_cluster_by_precomputed_distances(precomputed_distances):
           """
           Predict a cluster for each object with precomputed distances.
      @@ -466,20 +472,20 @@ 

      Returns

      -def prepare_html_string(document, num_sentences_in_snippet=4, num_words=15) +def prepare_html_string(document, num_sentences_in_snippet: int = 4, num_words: int = 15)
      -

      Prepares basic version of raw html +

      Prepares basic version of raw html representing the document. Takes title (document_id) and combines it with portion of the document text (first few sentences) also makes sure that every line contains same number of words

      Parameters

      -
      document : Padas.DataFrame row
      +
      document : Padas.DataFrame row
      a row that contains columns raw_text and index in string form
      -
      distance : float between 0 and 1
      +
      distance : float between 0 and 1
      measure of how close found document to the initial inquiry
      num_sentences_in_snippet
      @@ -492,9 +498,11 @@

      Returns

      doc_html : str
       
      -
      +
      -Source code + +Expand source code +
      def prepare_html_string(
           document,
           num_sentences_in_snippet: int = 4,
      @@ -542,7 +550,7 @@ 

      Returns

      def transform_cluster_objects_list_to_dict(object_clusters)
      -

      Transforms list of object clusters to dict.

      +

      Transforms list of object clusters to dict.

      Parameters

      object_clusters : list
      @@ -552,9 +560,11 @@

      Returns

      clusters : dict
      dict, where key is clusterlabel (int), value is cluster objects (list)
      -
      +
      -Source code + +Expand source code +
      def transform_cluster_objects_list_to_dict(object_clusters):
           """
           Transforms list of object clusters to dict.
      @@ -590,7 +600,7 @@ 

      Classes

      (model, dataset=None, precomputed_distances=None, object_clusters=None, max_top_number=10)
      -

      The class provide information about +

      The class provide information about top documents for the model topics from some collection.

      Parameters

      @@ -599,7 +609,7 @@

      Parameters

      a class of topic model
      dataset : Dataset
      a class that stores information about the collection
      -
      precomputed_distances :  np.array
      +
      precomputed_distances :  np.array
      array of shape (n_topics, n_objects) - an optional matrix of pairwise distances: distance from ith cluster centroid to the jth object
      @@ -609,9 +619,11 @@

      Parameters

      ith element of list is cluster of ith object
      max_top_number : int
      number of top documents to provide for each cluster
      -
      +
      -Source code + +Expand source code +
      class TopDocumentsViewer(BaseViewer):
           """ """
           def __init__(self,
      @@ -782,7 +794,7 @@ 

      Methods

      def view(self, current_num_top_doc=None, topic_names=None)
      -

      Returns list of tuples (token,score) for +

      Returns list of tuples (token,score) for each topic in the model.

      Parameters

      @@ -798,9 +810,11 @@

      Returns

      returns dict for each topic of the model dict contains document_ids of top documents for that topic and their probability of belonging to the topic
      -
      +
      -Source code + +Expand source code +
      def view(
           self,
           current_num_top_doc=None,
      @@ -875,10 +889,10 @@ 

      Returns

      -def view_from_jupyter(self, current_num_top_doc=None, topic_names=None, display_output=True, give_html=False) +def view_from_jupyter(self, current_num_top_doc: int = None, topic_names: list = None, display_output: bool = True, give_html: bool = False)
      -

      TopDocumentsViewer method recommended for use +

      TopDocumentsViewer method recommended for use from jupyter notebooks Returns texts of the actual documents.

      Parameters

      @@ -897,9 +911,11 @@

      Returns

      html_output
      html string of the output
      -
      +
      -Source code + +Expand source code +
      def view_from_jupyter(
               self,
               current_num_top_doc: int = None,
      @@ -991,7 +1007,7 @@ 

      diff --git a/docs/viewers/top_similar_documents_viewer.html b/docs/viewers/top_similar_documents_viewer.html index 6cbeec5..bab1db1 100644 --- a/docs/viewers/top_similar_documents_viewer.html +++ b/docs/viewers/top_similar_documents_viewer.html @@ -3,14 +3,14 @@ - + topicnet.viewers.top_similar_documents_viewer API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.top_similar_documents_viewer
      -Source code + +Expand source code +
      import numpy as np
       import warnings
       
      @@ -591,17 +593,17 @@ 

      Module topicnet.viewers.top_similar_documents_viewerFunctions

      -def prepare_doc_html_with_similarity(document, distance, num_digits=3, num_sentences_in_snippet=4, num_words=15) +def prepare_doc_html_with_similarity(document, distance, num_digits: int = 3, num_sentences_in_snippet: int = 4, num_words: int = 15)
      -

      Prepares intital document and search results +

      Prepares intital document and search results html strings

      Parameters

      -
      document : Padas.DataFrame row
      +
      document : Padas.DataFrame row
      a row that contains columns raw_text and index in string form
      -
      distance : float between 0 and 1
      +
      distance : float between 0 and 1
      measure of how close found document to the initial inquiry
      num_digits
      @@ -617,9 +619,11 @@

      Returns

      doc_html : str
      an html string with data about document plus additional info for the output clarification
      -
      +
      -Source code + +Expand source code +
      def prepare_doc_html_with_similarity(
           document,
           distance,
      @@ -681,16 +685,18 @@ 

      Classes

      (model, dataset)
      -

      Viewer which uses topic model to find documents similar to given one

      +

      Viewer which uses topic model to find documents similar to given one

      Parameters

      model : BaseModel
      Topic model
      dataset : BaseDataset
      Dataset with information about documents
      -
      +
      -Source code + +Expand source code +
      class TopSimilarDocumentsViewer(BaseViewer):
           def __init__(self, model, dataset):
               """Viewer which uses topic model to find documents similar to given one
      @@ -1092,7 +1098,7 @@ 

      Methods

      def view(self, document_id, metric='jensenshannon', num_top_similar=5, keep_similar_by_words=True)
      -

      Shows documents similar to given one by distribution of topics

      +

      Shows documents similar to given one by distribution of topics

      Parameters

      document_id
      @@ -1110,11 +1116,13 @@

      Parameters

      Returns

      -
      tuple(list, list)
      +
      tuple(list, list)
      Top similar words, and corresponding distances to given document
      -
      +
      -Source code + +Expand source code +
      def view(self,
                document_id,
                metric='jensenshannon',
      @@ -1163,10 +1171,10 @@ 

      Returns

      -def view_from_jupyter(self, document_id, metric='jensenshannon', num_top_similar=5, num_digits=3, keep_similar_by_words=True, display_output=True, give_html=False) +def view_from_jupyter(self, document_id: str, metric: str = 'jensenshannon', num_top_similar: int = 5, num_digits: int = 3, keep_similar_by_words: bool = True, display_output: bool = True, give_html: bool = False)
      -

      Method for viewing documents similar to requested one +

      Method for viewing documents similar to requested one from jupyter notebook. Provides document titles and snippets of first few sentences.

      Parameters

      @@ -1192,9 +1200,11 @@

      Returns

      topic_html
      html string of the generated output
      -
      +
      -Source code + +Expand source code +
      def view_from_jupyter(
               self,
               document_id: str,
      @@ -1293,7 +1303,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/viewers/top_tokens_viewer.html b/docs/viewers/top_tokens_viewer.html index e1cc2a1..13ec518 100644 --- a/docs/viewers/top_tokens_viewer.html +++ b/docs/viewers/top_tokens_viewer.html @@ -3,14 +3,14 @@ - + topicnet.viewers.top_tokens_viewer API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.top_tokens_viewer

      -Source code + +Expand source code +
      import bisect
       import numpy as np
       import pandas as pd
      @@ -714,7 +716,7 @@ 

      Functions

      def compute_blei_scores(phi)
      -

      Computes Blei score
      +

      Computes Blei score
      phi[wt] * [log(phi[wt]) - 1/T sum_k log(phi[wk])]

      Parameters

      @@ -725,9 +727,11 @@

      Returns

      score : pd.DataFrame
      weighted phi matrix
      -
      +
      -Source code + +Expand source code +
      def compute_blei_scores(phi):
           """
           Computes Blei score  
      @@ -764,7 +768,7 @@ 

      Returns

      def compute_clusters_top_tokens_by_clusters_tfidf(objects_cluster, objects_content, max_top_number=10, n_topics=None)
      -

      Function for document-like clusters.
      +

      Function for document-like clusters.
      For each cluster compute top tokens of cluster. Top tokens are defined by tf-idf scheme. Tf-idf is computed as if clusters is concatenation of all it documents.

      Parameters

      @@ -782,11 +786,13 @@

      Parameters

      Returns

      -
      clusters_top_tokens : list of list of str:
      +
      clusters_top_tokens : list of list of str:
      ith element of list is list of top tokens of ith cluster
      -
      +
      -Source code + +Expand source code +
      def compute_clusters_top_tokens_by_clusters_tfidf(
               objects_cluster, objects_content,
               max_top_number=10, n_topics=None):
      @@ -861,7 +867,7 @@ 

      Returns

      def compute_joint_pwt_distribution(phi, p_t)
      -

      p(t) is prob(topic = t), defined as p(t) = sum_t n_t / n +

      p(t) is prob(topic = t), defined as p(t) = sum_t n_t / n

      if we fix some word w, we can calculate weighted_pk:
      wp_t = p(t) p(w|t)

      @@ -877,9 +883,11 @@

      Returns

      joint_pwt : np.array of float
      array of probabilities that a fixed token from the collection belongs to that topic
      -
      +
      -Source code + +Expand source code +
      def compute_joint_pwt_distribution(phi, p_t):
           """
           p(t) is prob(topic = t), defined as p(t) = sum_t n_t / n  
      @@ -910,7 +918,7 @@ 

      Returns

      def compute_likelihood_vectorised(phi, p_t, joint_pwt)
      -

      Likelihood ratio is defined as
      +

      Likelihood ratio is defined as
      L = phi_wt / sum_k p(k)/p(!t) phi_wk
      equivalently:
      L = phi_wt * p(!t) / sum_k!=t p(k) phi_wk
      @@ -932,9 +940,11 @@

      Returns

      target_values : np.array of float
      vector of likelihood ratios that tokens belong to the given topic
      -
      +
      -Source code + +Expand source code +
      def compute_likelihood_vectorised(phi, p_t, joint_pwt):
           """
           Likelihood ratio is defined as  
      @@ -984,7 +994,7 @@ 

      Returns

      def compute_pt_distribution(model, class_ids=None)
      -

      Calculates the Prob(t) vector (vector contains an entry for each topic).

      +

      Calculates the Prob(t) vector (vector contains an entry for each topic).

      Parameters

      model : TopicModel
      @@ -995,11 +1005,13 @@

      Parameters

      Returns

      -
      float probability that a random token from the collection belongs to that topic
      +
      float probability that a random token from the collection belongs to that topic
       
      -
      +
      -Source code + +Expand source code +
      def compute_pt_distribution(model, class_ids=None):
           """
           Calculates the Prob(t) vector (vector contains an entry for each topic).
      @@ -1028,9 +1040,11 @@ 

      Returns

      def compute_ptw(joint_pwt)
      -
      +
      -Source code + +Expand source code +
      def compute_ptw(joint_pwt):
           return joint_pwt / np.sum(joint_pwt, axis=0)  # sum by all T
      @@ -1039,9 +1053,11 @@

      Returns

      def convert_df_to_html(df)
      -
      +
      -Source code + +Expand source code +
      def convert_df_to_html(df):
           return df.style\
                      .set_table_attributes("style='display:inline'")\
      @@ -1052,7 +1068,7 @@ 

      Returns

      def get_top_values(values, top_number)
      -

      Returns top_number top values from the matrix for each column.

      +

      Returns top_number top values from the matrix for each column.

      Parameters

      values : np.array
      @@ -1066,9 +1082,11 @@

      Returns

      array of top_number top values for each column of the initial array
      top_indexes : nd.array
      array of original indexes for top_values array (Default value = True)
      -
      +
      -Source code + +Expand source code +
      def get_top_values(values, top_number):
           """
           Returns top_number top values from the matrix for each column.
      @@ -1111,7 +1129,7 @@ 

      Returns

      def get_top_values_by_sum(values, min_sum_value)
      -

      Returns top values until sum of their scores breaches min_sum_value.

      +

      Returns top values until sum of their scores breaches min_sum_value.

      Parameters

      values : np.array
      @@ -1127,16 +1145,15 @@

      Returns

      array of original indexes for top_values array (Default value = True)

      Examples

      -
      >>> values = np.array([1, 3, 2, 0.1, 5, 0])
      +
      >>> values = np.array([1, 3, 2, 0.1, 5, 0])
       >>> min_sum = 8.1
       >>> top_values, top_indexes = get_top_values_by_sum(values, min_sum)
      -**`Result`** :&ensp;`top_values`, `top_indexes` = (`array`([`5.`, `3.`, `2.`]), `array`([`4`, `1`, `2`]))
      -
      -
      -
       
      -
      +Result: top_values, top_indexes = (array([5., 3., 2.]), array([4, 1, 2])) +
      -Source code + +Expand source code +
      def get_top_values_by_sum(values, min_sum_value,):
           """
           Returns top values until sum of their scores breaches `min_sum_value`.
      @@ -1186,10 +1203,10 @@ 

      Classes

      class TopTokensViewer -(model, class_ids=None, method='blei', num_top_tokens=10, alpha=1, by_sum=False, sum_value=None, dataset=None) +(model, class_ids: List[str] = None, method: str = 'blei', num_top_tokens: int = 10, alpha: float = 1, by_sum: bool = False, sum_value: float = None, dataset=None)
      -

      Gets top tokens from topic (sorted by scores)

      +

      Gets top tokens from topic (sorted by scores)

      The class provide information about top tokens of the model topics providing with different methods to score that.

      Parameters

      @@ -1207,7 +1224,7 @@

      Parameters

      ptw - something like likelihood
      num_top_tokens : int
      number of top tokens to provide for each topic
      -
      alpha : float between 0 and 1
      +
      alpha : float between 0 and 1
      additional constant needed for ptw method of scoring
      by_sum
      @@ -1218,9 +1235,11 @@

      Parameters

      a good default value might be different depending on self.method value
      dataset : Dataset
      a class that stores infromation about the collection
      -
      +
      -Source code + +Expand source code +
      class TopTokensViewer(BaseViewer):
           """Gets top tokens from topic (sorted by scores)"""
           def __init__(self,
      @@ -1611,9 +1630,11 @@ 

      Instance variables

      var cached_top_tokens
      -
      +
      -Source code + +Expand source code +
      @property
       def cached_top_tokens(self):
           if self._cached_top_tokens is None:
      @@ -1625,12 +1646,14 @@ 

      Instance variables

      Methods

      -def to_df(self, topic_names=None, digits=5) +def to_df(self, topic_names: Iterator[str] = None, digits: int = 5) -> pandas.core.frame.DataFrame
      -
      +
      -Source code + +Expand source code +
      def to_df(self, topic_names: Iterator[str] = None, digits: int = 5) -> pd.DataFrame:
           topic_top_tokens = self.cached_top_tokens
       
      @@ -1651,24 +1674,24 @@ 

      Methods

      -def to_html(self, topic_names=None, digits=5, thresh=None, horizontally_stack=True) +def to_html(self, topic_names: Union[str, List[str]] = None, digits: int = 5, thresh: float = None, horizontally_stack: bool = True) -> str
      -

      Generates html version of dataframes to be displayed by Jupyter notebooks

      +

      Generates html version of dataframes to be displayed by Jupyter notebooks

      Parameters

      topic_names : list of strings
      Initial dictionary keys
      digits : int
      Number of digits to round each probability to
      -
      thresh : float [Deprecated]
      +
      thresh : float [Deprecated]
      Threshold used for calculating digits and throwing out too low probabilities
      horizontally_stack : bool
      if True, then tokens for each modality will be stacked horizontally (instead of being a single long multi-line DataFrame)

      Examples

      -
      >>> from IPython.display import HTML, display_html
      +
      >>> from IPython.display import HTML, display_html
       >>>
       >>> # model training here
       >>> # ...
      @@ -1676,9 +1699,11 @@ 

      Examples

      >>> display_html(viewer.to_html(), raw=True) >>> # or >>> HTML(viewer.to_html()) -
      +
      -Source code + +Expand source code +
      def to_html(
               self,
               topic_names: Union[str, List[str]] = None,
      @@ -1749,10 +1774,10 @@ 

      Examples

      -def view(self, class_ids=None, raw_data=None, three_levels=True) +def view(self, class_ids: List[str] = None, raw_data: List[List[str]] = None, three_levels: bool = True) -> Union[Dict[str, Dict[str, Dict[str, float]]], Dict[str, Dict[Tuple[str, str], float]]]
      -

      Returns list of tuples (token, score) for each topic in the model.

      +

      Returns list of tuples (token, score) for each topic in the model.

      Parameters

      class_ids
      @@ -1762,14 +1787,16 @@

      Parameters

      three_levels
      If true, three level dict will be returned, otherwise — two level one
      -

      returns

      +

      Returns

      -
      topic_top_tokens : nested 3 or 2-level dict
      +
      topic_top_tokens : nested 3 or 2-level dict
      Topic -> Modality -> Token -> Probability or Topic -> (Modality, Token) -> Probability
      -
      +
      -Source code + +Expand source code +
      def view(
               self,
               class_ids: List[str] = None,
      @@ -1858,10 +1885,10 @@ 

      returns

      -def view_from_jupyter(self, topic_names=None, digits=5, horizontally_stack=True, one_topic_per_row=True, display_output=True, give_html=False) +def view_from_jupyter(self, topic_names: Union[str, List[str]] = None, digits: int = 5, horizontally_stack: bool = True, one_topic_per_row: bool = True, display_output: bool = True, give_html: bool = False)
      -

      TopTokensViewer method recommended for use +

      TopTokensViewer method recommended for use from jupyter notebooks

      Parameters

      @@ -1884,19 +1911,21 @@

      Parameters

      Returns

      -
      topic_html_strings : list of strings in HTML format
      +
      topic_html_strings : list of strings in HTML format
       

      Examples

      -
      >>> # model training here
      +
      >>> # model training here
       >>> # ...
       >>> viewer = TopTokensViewer(model)
       >>> information = viewer.view_from_jupyter()
       >>> # or
       >>> information = viewer.view_from_jupyter(output=False)
      -
      +
      -Source code + +Expand source code +
      def view_from_jupyter(
               self,
               topic_names: Union[str, List[str]] = None,
      @@ -2024,7 +2053,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/viewers/topic_flow_viewer.html b/docs/viewers/topic_flow_viewer.html index 805021a..17f491b 100644 --- a/docs/viewers/topic_flow_viewer.html +++ b/docs/viewers/topic_flow_viewer.html @@ -3,14 +3,14 @@ - + topicnet.viewers.topic_flow_viewer API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.topic_flow_viewer

      -Source code + +Expand source code +
      import numpy as np
       import plotly.graph_objects as go
       import artm
      @@ -179,7 +181,7 @@ 

      Classes

      (model, time_labels, dataset, modality='@lemmatized', sort_key_function=None)
      -

      Viewer to show trending topics over time.

      +

      Viewer to show trending topics over time.

      Parameters

      model : TopicModel
      @@ -192,9 +194,11 @@

      Parameters

      model's modality for topics description
      sort_key_function : Function
      function that can be used with python sorted
      -
      +
      -Source code + +Expand source code +
      class TopicFlowViewer(BaseViewer):
           """
           Viewer to show trending topics over time.
      @@ -340,14 +344,16 @@ 

      Methods

      def compute_nd(self, number_of_docs)
      -

      Compute number of tokens in each document from dataset.

      +

      Compute number of tokens in each document from dataset.

      Parameters

      number_of_docs : int
      number of documents in theta
      -
      +
      -Source code + +Expand source code +
      def compute_nd(self, number_of_docs):
           """
           Compute number of tokens in each document from dataset.
      @@ -380,13 +386,15 @@ 

      Parameters

      def compute_top_tokens(self, model, modality)
      -

      Function for top tokens extraction.

      +

      Function for top tokens extraction.

      Parameters:

      model : TopicModel modality : str -modality for topic representation

      +modality for topic representation

      -Source code + +Expand source code +
      def compute_top_tokens(self, model, modality):
           """
           Function for top tokens extraction.
      @@ -409,16 +417,18 @@ 

      Parameters:

      def plot(self, topics, significance_threshold=0.01)
      -

      Function for plotly graph building.

      +

      Function for plotly graph building.

      Parameters

      topics : list of int
      topics that need to be visualized
      significance_threshold : float
      plot ignores values lower than threshold
      -
      +
      -Source code + +Expand source code +
      def plot(self, topics, significance_threshold=1e-2):
           """
           Function for plotly graph building.
      @@ -464,13 +474,15 @@ 

      Parameters

      def view(self, topic_names=None)
      -

      Parameters

      +

      Parameters

      topic_names : list of str
      topics that user wants to see on plot
      -
      +
      -Source code + +Expand source code +
      def view(self, topic_names=None):
           """
           Parameters
      @@ -515,7 +527,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/docs/viewers/topic_mapping.html b/docs/viewers/topic_mapping.html index cb30d3c..4d14735 100644 --- a/docs/viewers/topic_mapping.html +++ b/docs/viewers/topic_mapping.html @@ -3,14 +3,14 @@ - + topicnet.viewers.topic_mapping API documentation - - + + @@ -21,7 +21,9 @@

      Module topicnet.viewers.topic_mapping

      -Source code + +Expand source code +
      import numpy as np
       from scipy import optimize
       from scipy.spatial import distance
      @@ -277,7 +279,7 @@ 

      Functions

      def compute_topic_mapping(matrix_left, matrix_right, metric='euclidean')
      -

      This function provides mapping of topics +

      This function provides mapping of topics from one model to the topics of the other model based on their simmularity defined by the metrics.

      Parameters

      @@ -297,9 +299,11 @@

      Returns

      tuple of ndarrays
      returns two ndarrays of indices, where each index corresponds to a topic from respective models
      -
      +
      -Source code + +Expand source code +
      def compute_topic_mapping(matrix_left, matrix_right, metric='euclidean'):
           """
           This function provides mapping of topics
      @@ -344,7 +348,7 @@ 

      Classes

      (model, second_model, mode='min', metric='euclidean', class_ids=None)
      -

      Performs a mapping between topics of two model +

      Performs a mapping between topics of two model matching two closest topics together based on the Hungarian algorithm.

      Parameters

      @@ -364,9 +368,11 @@

      Parameters

      name of scipy metrics used in distance computation or function that computes pairwise distance between 2 matrices (Default value = "euclidean")
      -
      +
      -Source code + +Expand source code +
      class TopicMapViewer(BaseViewer):
           def __init__(
               self,
      @@ -578,19 +584,21 @@ 

      Methods

      def view(self, class_ids=None)
      -

      Returns pairs of close topics.

      +

      Returns pairs of close topics.

      Parameters

      -
      class_ids : list of str, default - None
      +
      class_ids : list of str, default - None
      parameter for model.get_phi method

      Returns

      -
      tuple of nd.arrays of strings:
      +
      tuple of nd.arrays of strings:
      two ordered arrays of topic name pairs
      -
      +
      -Source code + +Expand source code +
      def view(self, class_ids=None):
           """
           Returns pairs of close topics.
      @@ -682,10 +690,10 @@ 

      Returns

      -def view_from_jupyter(self, display_output=True, give_html=False, **kwargs) +def view_from_jupyter(self, display_output: bool = True, give_html: bool = False, **kwargs)
      -

      TopicMapViewer method recommended for use +

      TopicMapViewer method recommended for use from jupyter notebooks returns closest pairs of models topics and visualizes their top tokens

      @@ -704,12 +712,12 @@

      Returns

      html string of the output

      Another Parameters

      -
      -
      **kwargs
      -
      kwargs are optional ~.TopTokenViewer properties
      -
      +

      kwargs +kwargs are optional ~.TopTokenViewer properties

      -Source code + +Expand source code +
      def view_from_jupyter(
               self,
               display_output: bool = True,
      @@ -817,7 +825,7 @@ 

      -

      Generated by pdoc 0.6.3.

      +

      Generated by pdoc 0.8.1.

      diff --git a/topicnet/README-rus.md b/topicnet/README-rus.md deleted file mode 100644 index 19f6b4f..0000000 --- a/topicnet/README-rus.md +++ /dev/null @@ -1,23 +0,0 @@ -## TopicNet -[English version](README.md) - -Библиотека ```topicnet``` создана для автоматизации и унификации практик тематического моделирования. Она помогает снять с пользователя заботу о рутинной составляющей моделирования, освободив время для творческого процесса подбора функционала отвечающего критериям задачи. -На данный момент библиотека представлена тремя модулями: - - -* ```cooking_machine``` - отвечает за логирование эксперимента по построению тематических моделей и связь между сущностями образующими эксперимент. - - -* ```viewers``` - помогает визуализовать информацию о полученной тематической модели в доступном для экспериментатора формате. - - -* ```tests``` - помогает проверить правильность установки библиотеки TopicNet и содержит примеры работы с кодом библиотеки. - ---- -### Описание -Важно отметить, что на каждом шаге эксперимента используется один и тот же кубик для дальнейшей настройки моделей. -Если для разным тематических моделей после очередного этапа обучения применяются разные кубики, то эксперимент разделяется на два отдельных эксперимента, которые отличаются данным и последующими этапами обучения, иными словами последовательностями применения кубиков с соответствующими им параметрам. -Объект эксперимента содержит всю необходимую информацию для воспроизведения данного эксперимента с нуля и изменяется в процессе добавления кубиков пользователя. -При использовании кубиков также изменяется информация о моделях и их описание, создаются новые модели копии основной, которые соответствуют перебираемым внутри кубика параметрам. -Описание моделей также позволяет построить их по описанию и создать скрипт их создания и обучения. -Таким образом, ключевым объектом - является кубик, а точнее их последовательность, которые взаимодействуют с объектами модель и эксперимент. \ No newline at end of file diff --git a/topicnet/bitbucket-pipelines.yml b/topicnet/bitbucket-pipelines.yml index 76adcc8..28535ed 100644 --- a/topicnet/bitbucket-pipelines.yml +++ b/topicnet/bitbucket-pipelines.yml @@ -4,7 +4,7 @@ pipelines: '**': - step: name: Lint by Flake8 - image: python:3.6.0 + image: python:3.7.0 caches: - pip script: diff --git a/topicnet/cooking_machine/config_parser.py b/topicnet/cooking_machine/config_parser.py index ecbf063..7c4fbb3 100644 --- a/topicnet/cooking_machine/config_parser.py +++ b/topicnet/cooking_machine/config_parser.py @@ -26,7 +26,7 @@ checked and which aren't quite here yet. Our process consists of three stages: -1) we check the high-level structure using `base_schema`. +1) we check the high-level structure using `BASE_SCHEMA`. The presence of each required key is ensured. After this stage we could be sure than we can create a valid model using specified parameters. @@ -45,61 +45,46 @@ def __init__(self, num_iters: int = 5) but it's a work-in-progress currently. """ # noqa: W291 -from .cubes import RegularizersModifierCube, CubeCreator + +from inspect import signature, Parameter +from typing import ( + Callable, + Type, +) + +from .cubes import ( + CubeCreator, + RegularizersModifierCube, + GreedyStrategy, + PerplexityStrategy, +) from .experiment import Experiment from .dataset import Dataset from .models import scores as tnscores from .models import TopicModel - -from .cubes import PerplexityStrategy, GreedyStrategy -from .model_constructor import init_simple_default_model, create_default_topics -from .rel_toolbox_lite import count_vocab_size, handle_regularizer +from .model_constructor import ( + create_default_topics, + init_simple_default_model, +) +from .rel_toolbox_lite import ( + count_vocab_size, + handle_regularizer, +) import artm -from inspect import signature, Parameter from strictyaml import Map, Str, Int, Seq, Float, Bool from strictyaml import Any, Optional, EmptyDict, EmptyNone, EmptyList from strictyaml import dirty_load -from typing import Type - -# TODO: use stackoverflow.com/questions/37929851/parse-numpydoc-docstring-and-access-components -# for now just hardcode most common / important types -ARTM_TYPES = { - "tau": Float(), - "topic_names": Str() | Seq(Str()) | EmptyNone(), - # TODO: handle class_ids in model and in regularizers separately - "class_ids": Str() | Seq(Str()) | EmptyNone(), - "gamma": Float() | EmptyNone(), - "seed": Int(), - "num_document_passes": Int(), - "num_processors": Int(), - "cache_theta": Bool(), - "reuse_theta": Bool(), - "theta_name": Str() -} - -element = Any() -base_schema = Map({ - 'regularizers': Seq(element), - Optional('scores'): Seq(element), - 'stages': Seq(element), - 'model': Map({ - "dataset_path": Str(), - Optional("modalities_to_use"): Seq(Str()), - Optional("modalities_weights"): Any(), - "main_modality": Str(), - }), - 'topics': Map({ - "background_topics": Seq(Str()) | Int() | EmptyList(), - "specific_topics": Seq(Str()) | Int() | EmptyList(), - }) -}) SUPPORTED_CUBES = [CubeCreator, RegularizersModifierCube] SUPPORTED_STRATEGIES = [PerplexityStrategy, GreedyStrategy] +TYPE_VALIDATORS = { + 'int': Int(), 'bool': Bool(), 'str': Str(), 'float': Float() +} + def choose_key(param): """ @@ -113,6 +98,7 @@ def choose_key(param): """ if param.default is not Parameter.empty: return Optional(param.name) + return param.name @@ -136,9 +122,79 @@ def choose_validator(param): return Str() if param.name in ARTM_TYPES: return ARTM_TYPES[param.name] + return Any() +# TODO: maybe this is cool, but do we really need this? +def build_schema_from_function(func: Callable) -> dict: + from docstring_parser import parse as docstring_parse + + func_params = signature(func).parameters + func_params_schema = dict() + + for elem in docstring_parse(func.__doc__).params: + if elem.arg_name in func_params: + key = choose_key(func_params[elem.arg_name]) + func_params_schema[key] = TYPE_VALIDATORS[elem.type_name] + + return func_params_schema + + +# TODO: use stackoverflow.com/questions/37929851/parse-numpydoc-docstring-and-access-components +# for now just hardcode most common / important types +ARTM_TYPES = { + "tau": Float(), + "topic_names": Str() | Seq(Str()) | EmptyNone(), + # TODO: handle class_ids in model and in regularizers separately + "class_ids": Str() | Seq(Str()) | EmptyNone(), + "gamma": Float() | EmptyNone(), + "seed": Int(), + "num_document_passes": Int(), + "num_processors": Int(), + "cache_theta": Bool(), + "reuse_theta": Bool(), + "theta_name": Str() +} + + +_ELEMENT = Any() + +# TODO: maybe better _DICTIONARY_FILTER_SCHEMA = build_schema_from_function(artm.Dictionary.filter) +# TODO: modalities, filter params - these all are dataset's options, not model's +# maybe make separate YML block for dataset? + +BASE_SCHEMA = Map({ + 'regularizers': Seq(_ELEMENT), + Optional('scores'): Seq(_ELEMENT), + 'stages': Seq(_ELEMENT), + 'model': Map({ + "dataset_path": Str(), + Optional("dictionary_filter_parameters"): Map({ + Optional("class_id"): Str(), + Optional("min_df"): Float(), + Optional("max_df"): Float(), + Optional("min_df_rate"): Float(), + Optional("max_df_rate"): Float(), + Optional("min_tf"): Float(), + Optional("max_tf"): Float(), + Optional("max_dictionary_size"): Float(), + Optional("recalculate_value"): Bool(), + }), + Optional("keep_in_memory"): Bool(), + Optional("internals_folder_path"): Bool(), + Optional("modalities_to_use"): Seq(Str()), + Optional("modalities_weights"): Any(), + "main_modality": Str(), + }), + 'topics': Map({ + "background_topics": Seq(Str()) | Int() | EmptyList(), + "specific_topics": Seq(Str()) | Int() | EmptyList(), + }) +}) +KEY_DICTIONARY_FILTER_PARAMETERS = 'dictionary_filter_parameters' + + def build_schema_from_signature(class_of_object, use_optional=True): """ Parameters @@ -439,7 +495,7 @@ def parse_modalities_data(parsed): # exactly one should be specified if has_modalities_to_use == has_weights: - raise ValueError(f"Either 'modalities_to_use' or 'modalities_weights' should be specified") + raise ValueError("Either 'modalities_to_use' or 'modalities_weights' should be specified") if has_weights: modalities_to_use = list(parsed["model"]["modalities_weights"].data) @@ -474,8 +530,9 @@ def parse( regularizers: list topic_model: TopicModel dataset: Dataset + """ - parsed = dirty_load(yaml_string, base_schema, allow_flow_style=True) + parsed = dirty_load(yaml_string, BASE_SCHEMA, allow_flow_style=True) specific_topic_names, background_topic_names = create_default_topics( parsed.data["topics"]["specific_topics"], @@ -484,12 +541,22 @@ def parse( revalidate_section(parsed, "stages") revalidate_section(parsed, "regularizers") + if "scores" in parsed: revalidate_section(parsed, "scores") - cube_settings = [] + dataset = dataset_class( + data_path=parsed.data["model"]["dataset_path"], + keep_in_memory=parsed.data["model"].get("keep_in_memory", True), + internals_folder_path=parsed.data["model"].get("internals_folder_path", None), + ) + filter_parameters = parsed.data["model"].get( + KEY_DICTIONARY_FILTER_PARAMETERS, dict() + ) - dataset = dataset_class(parsed.data["model"]["dataset_path"]) + if len(filter_parameters) > 0: + filtered_dictionary = dataset.get_dictionary().filter(**filter_parameters) + dataset._cached_dict = filtered_dictionary modalities_to_use = parse_modalities_data(parsed) @@ -508,11 +575,12 @@ def parse( topic_model = TopicModel(model) _add_parsed_scores(parsed, topic_model) + cube_settings = list() + for stage in parsed['stages']: for elemtype, elem_args in stage.items(): settings = build_cube_settings(elemtype.data, elem_args) - if force_separate_thread: - settings[elemtype]["separate_thread"] = False + settings[elemtype]["separate_thread"] = force_separate_thread cube_settings.append(settings) return cube_settings, regularizers, topic_model, dataset @@ -548,8 +616,12 @@ def revalidate_section(parsed, section): stage.revalidate(local_schema) -def build_experiment_environment_from_yaml_config(yaml_string, experiment_id, - save_path, force_separate_thread=False): +def build_experiment_environment_from_yaml_config( + yaml_string, + experiment_id, + save_path, + force_separate_thread=False, +): """ Wraps up parameter extraction and class instances creation from yaml formatted string @@ -572,11 +644,12 @@ def build_experiment_environment_from_yaml_config(yaml_string, experiment_id, Returns ------- - tuple experiment, dataset instances of corresponding classes from topicnet + """ settings, regs, model, dataset = parse(yaml_string, force_separate_thread) # TODO: handle dynamic addition of regularizers experiment = Experiment(experiment_id=experiment_id, save_path=save_path, topic_model=model) experiment.build(settings) + return experiment, dataset diff --git a/topicnet/cooking_machine/cubes/controller_cube.py b/topicnet/cooking_machine/cubes/controller_cube.py index 65f8d95..1e01655 100644 --- a/topicnet/cooking_machine/cubes/controller_cube.py +++ b/topicnet/cooking_machine/cubes/controller_cube.py @@ -18,12 +18,15 @@ We assume that if that metric is 'sort of decreasing', then everything is OK and we are allowed to change tau coefficient further; otherwise we revert back to the last "safe" value and stop - - 'sort of decreasing' performs best with `PerplexityScore`, and all scores which - behave like perplexity (nonnegative, and which should decrease when a model gets better). - If you want to track a different kind of score, it is recommended to use `score_controller` parameter - More formal definition of "sort of decreasing": if we divide a curve into two parts like so: + 'sort of decreasing' performs best with `PerplexityScore`, + and all scores which behave like perplexity + (nonnegative, and which should decrease when a model gets better). + If you want to track a different kind of score, + it is recommended to use `score_controller` parameter + + More formal definition of "sort of decreasing": + if we divide a curve into two parts like so: ##################################### @@ -52,13 +55,15 @@ then the right part is no higher than 5% of global minimum (you can change 5% if you like by adjusting `fraction_threshold` parameter) - If `score_to_track` is None and `score_controller` is None, then `ControllerAgent` will never stop + If `score_to_track` is None and `score_controller` is None, + then `ControllerAgent` will never stop (useful for e.g. decaying coefficients) fraction_threshold: float Threshold to control a score by 'sort of decreasing' metric score_controller: BaseScoreController Custom score controller - In case of 'sort of decreasing' is not proper to control score, you are able to create custom Score Controller + In case of 'sort of decreasing' is not proper to control score, + you are able to create custom Score Controller inherited from `BaseScoreController`. tau_converter: str or callable Notably, def-style functions and lambda functions are allowed @@ -120,13 +125,20 @@ import warnings from copy import deepcopy from dataclasses import dataclass -from typing import List, Optional +from numbers import Number +from typing import ( + Callable, + List, + Optional, + Union, +) import numexpr as ne import numpy as np from dill.source import getsource from .base_cube import BaseCube +from ..models.base_regularizer import BaseRegularizer from ..rel_toolbox_lite import count_vocab_size, handle_regularizer W_HALT_CONTROL = "Process of dynamically changing tau was stopped at {} iteration" @@ -148,6 +160,7 @@ def get_score_values(self, model): return None vals = model.scores[self.score_name] + if len(vals) == 0: return None @@ -162,8 +175,10 @@ def __call__(self, model): try: out_of_control_result = self.is_out_of_control(values) except Exception as ex: - message = (f"An error occured while controlling {self.score_name}. Message: {ex}. Score values: {values}") - raise ValueError(message) + raise ValueError( + f"An error occurred while controlling {self.score_name}!" + f" Message: {ex}. Score values: {values}" + ) if out_of_control_result.error_message is not None: warnings.warn(out_of_control_result.error_message) @@ -176,7 +191,8 @@ def is_out_of_control(self, values: List[float]) -> OutOfControlAnswer: class PerplexityScoreController(BaseScoreController): """ - Controller is proper to control the Perplexity score. For others, please ensure for yourself. + Controller is proper to control the Perplexity score. + For others, please ensure for yourself. """ DEFAULT_FRACTION_THRESHOLD = 0.05 @@ -194,23 +210,26 @@ def is_out_of_control(self, values: List[float]): minval = values[idxmin] if minval <= 0: - err_message = f"""Score {self.score_name} has min_value = {minval} which is <= 0. - This control scheme is using to control scores acting like Perplexity. - Ensure you control the Perplexity score or write your own controller""" - raise ValueError(err_message) + raise ValueError( + f'Score "{self.score_name}" has min_value = {minval} which is <= 0.' + f' This control scheme is using to control scores acting like Perplexity.' + f' Ensure you control the Perplexity score or write your own controller!' + ) answer = (right_maxval - minval) / minval > self.fraction_threshold if answer: - message = (f"Score {self.score_name} is too high! Right max value: {right_maxval}, min value: {minval}") - return OutOfControlAnswer(answer=answer, error_message=message) + return OutOfControlAnswer( + answer=answer, + error_message=( + f"Score {self.score_name} is too high!" + f" Right max value: {right_maxval}, min value: {minval}" + ), + ) return OutOfControlAnswer(answer=answer) -class ControllerAgentException(Exception): pass - - class ControllerAgent: """ Allows to change `tau` during the `_fit` method. @@ -234,27 +253,39 @@ class ControllerAgent: See top-level docstring for details. """ - def __init__(self, reg_name, tau_converter, max_iters, score_to_track=None, fraction_threshold=None, - score_controller=None, local_dict=None): + def __init__( + self, + reg_name: str, + tau_converter: Callable or str, + max_iters: int or float, + score_to_track: Union[str, List[str], None] = None, + fraction_threshold: Union[float, List[float], None] = None, + score_controller: Union[BaseScoreController, List[BaseScoreController], None] = None, + local_dict: dict = None): """ Parameters ---------- - reg_name : str - tau_converter : callable or str - max_iters : int or float - Agent will stop changing tau after `max_iters` iterations + reg_name + tau_converter + max_iters + Agent will stop changing tau after `max_iters` iterations, `max_iters` could be `float("NaN")` and `float("inf")` values: that way agent will continue operating even outside this `RegularizationControllerCube` - score_to_track : str, list of str or None + score_to_track Name of score to track. Please, use this definition to track only scores of type PerplexityScore. In other cases we recommend you to write you own ScoreController - fraction_threshold : float, list of float of the same length as score_to_track or None + fraction_threshold Uses to define threshold to control PerplexityScore - Default value is 0.05 - score_controller : BaseScoreController, list of BaseScoreController or None - local_dict : dict + Default value is 0.05. + If `fraction_threshold` is a list, it should be of the same length, as `score_to_track`. + score_controller + Score controller or controllers. + One can use this parameter for scores other than Perplexity + (or other scores that behave like Perplexity). + This is a more flexible and customizable way to control scores. + local_dict """ if local_dict is None: local_dict = dict() @@ -262,41 +293,84 @@ def __init__(self, reg_name, tau_converter, max_iters, score_to_track=None, frac self.reg_name = reg_name self.tau_converter = tau_converter - self.score_controllers = [] - if isinstance(score_to_track, list): - if fraction_threshold is None: - controller_params = [(name, PerplexityScoreController.DEFAULT_FRACTION_THRESHOLD) for name in - score_to_track] - elif isinstance(fraction_threshold, list) and len(score_to_track) == len(fraction_threshold): - controller_params = list(zip(score_to_track, fraction_threshold)) - else: - err_message = """Length of score_to_track and fraction_threshold must be same. - Otherwise fraction_threshold must be None""" - raise ControllerAgentException(err_message) - - self.score_controllers.append( - [PerplexityScoreController(name, threshold) for (name, threshold) in controller_params]) + scores_to_track = self._validate_score_to_track(score_to_track) + fraction_thresholds = self._validate_fraction_threshold( + fraction_threshold, required_length=len(scores_to_track) + ) - elif isinstance(score_to_track, str): - self.score_controllers.append([PerplexityScoreController( - score_to_track, - fraction_threshold or PerplexityScoreController.DEFAULT_FRACTION_THRESHOLD - )]) + assert len(scores_to_track) == len(fraction_thresholds) - if isinstance(score_controller, BaseScoreController): - self.score_controllers.append(score_controller) - elif isinstance(score_controller, list): - if not all(isinstance(score, BaseScoreController) for score in score_controller): - err_message = """score_controller must be of type BaseScoreController or list of BaseScoreController""" - raise ControllerAgentException(err_message) + perplexity_like_score_controllers = [ + PerplexityScoreController(name, threshold) + for (name, threshold) in zip(scores_to_track, fraction_thresholds) + ] - self.score_controllers.extend(score_controller) + self.score_controllers = list() + self.score_controllers.extend(perplexity_like_score_controllers) + self.score_controllers.extend( + self._validate_score_controller(score_controller) + ) self.is_working = True self.local_dict = local_dict self.tau_history = [] self.max_iters = max_iters + @staticmethod + def _validate_score_to_track( + score_to_track: Union[str, List[str], None]) -> List[str]: + + if isinstance(score_to_track, list): + return score_to_track + if score_to_track is None: + return list() + if isinstance(score_to_track, str): + return [score_to_track] + + raise TypeError(f'Wrong type of `score_to_track`: "{type(score_to_track)}"!') + + @staticmethod + def _validate_fraction_threshold( + fraction_threshold: Union[float, List[float], None], + required_length: int, + ) -> List[float]: + + if fraction_threshold is None: + return [PerplexityScoreController.DEFAULT_FRACTION_THRESHOLD] * required_length + if isinstance(fraction_threshold, Number): + return [float(fraction_threshold)] * required_length + + if not isinstance(fraction_threshold, list): + raise TypeError( + f'Wrong type of `fraction_threshold`: "{type(fraction_threshold)}"!' + ) + + if len(fraction_threshold) != required_length: + raise ValueError( + f'Wrong length of `fraction_threshold`: {len(fraction_threshold)}!' + f' Expected the length to be equal to {required_length}.' + ) + + return fraction_threshold + + @staticmethod + def _validate_score_controller( + score_controller: Union[BaseScoreController, List[BaseScoreController], None] + ) -> List[BaseScoreController]: + + if score_controller is None: + return list() + + elif isinstance(score_controller, BaseScoreController): + return [score_controller] + + elif (not isinstance(score_controller, list) or not all( + isinstance(score, BaseScoreController) for score in score_controller)): + raise TypeError(f'Wrong type of `score_controller`: "{type(score_controller)}"!') + + else: + return score_controller + def _convert_tau(self): """ """ if isinstance(self.tau_converter, str): @@ -327,7 +401,7 @@ def invoke(self, model, cur_iter): Note that zero means "cube just started", not "the model is brand new" """ - current_tau = model.regularizers[self.reg_name].tau + current_tau = model.get_regularizer(self.reg_name).tau self.tau_history.append(current_tau) self.local_dict["prev_tau"] = current_tau self.local_dict["cur_iter"] = cur_iter @@ -346,9 +420,9 @@ def invoke(self, model, cur_iter): if should_stop: warnings.warn(W_HALT_CONTROL.format(len(self.tau_history))) self.is_working = False - model.regularizers[self.reg_name].tau = self._find_safe_tau() + model.get_regularizer(self.reg_name).tau = self._find_safe_tau() else: - model.regularizers[self.reg_name].tau = self._convert_tau() + model.get_regularizer(self.reg_name).tau = self._convert_tau() class RegularizationControllerCube(BaseCube): @@ -390,7 +464,9 @@ def __init__(self, num_iter: int, parameters, >> ) >> "score_to_track": None, >> "fraction_threshold": None, - >> "score_controller": [PerplexityScoreController("PerplexityScore@all", 0.1)], + >> "score_controller": [ + >> PerplexityScoreController("PerplexityScore@all", 0.1) + >> ], >> "user_value_grid": [0, 1]} reg_search : str @@ -417,9 +493,9 @@ def __init__(self, num_iter: int, parameters, separate_thread=separate_thread) self._relative = use_relative_coefficients self.data_stats = None - self.raw_parameters = parameters if isinstance(parameters, dict): parameters = [parameters] + self.raw_parameters = parameters self._convert_parameters(parameters) def _convert_parameters(self, all_parameters): @@ -480,22 +556,29 @@ def apply(self, topic_model, one_model_parameter, dictionary=None, model_id=None for (agent_blueprint_template, field_name, current_user_value) in one_model_parameter: agent_blueprint = dict(agent_blueprint_template) - if agent_blueprint["reg_name"] is None: - regularizer = agent_blueprint["regularizer"] - new_regularizer = deepcopy(regularizer) - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) - agent_blueprint["reg_name"] = new_regularizer.name - else: - if agent_blueprint['reg_name'] not in new_model.regularizers.data: + if agent_blueprint.get("reg_name") is not None: + reg_name = agent_blueprint['reg_name'] + + if reg_name not in new_model.all_regularizers: error_msg = (f"Regularizer {agent_blueprint['reg_name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif agent_blueprint.get("regularizer") is not None: + regularizer = agent_blueprint["regularizer"] + new_regularizer = deepcopy(regularizer) + if isinstance(regularizer, BaseRegularizer): + new_model.custom_regularizers[new_regularizer.name] = new_regularizer + else: # classic bigARTM regularizer, attempt to relativize it's coefficients + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) + agent_blueprint["reg_name"] = new_regularizer.name + else: + raise ValueError("Either 'reg_name' or 'regularizer' should be set") agent_blueprint['local_dict']['user_value'] = current_user_value # ControllerAgent needs only reg_name in constructor agent_blueprint.pop("regularizer") diff --git a/topicnet/cooking_machine/cubes/perplexity_strategy.py b/topicnet/cooking_machine/cubes/perplexity_strategy.py index ed90af3..48bc03d 100644 --- a/topicnet/cooking_machine/cubes/perplexity_strategy.py +++ b/topicnet/cooking_machine/cubes/perplexity_strategy.py @@ -177,7 +177,7 @@ def prepare_grid(self, other_parameters, reg_search="add"): self.grid = product(*all_coeffs_grid) self.grid_len = len(all_coeffs_grid[0]) if self.grid is None: - raise ValueError(f'Failed to initialize self.grid, check initial parameters.') + raise ValueError('Failed to initialize self.grid, check initial parameters.') def grid_visit_generator(self, other_parameters, reg_search): """ diff --git a/topicnet/cooking_machine/cubes/regularizer_cube.py b/topicnet/cooking_machine/cubes/regularizer_cube.py index 0dfa4f0..1137078 100644 --- a/topicnet/cooking_machine/cubes/regularizer_cube.py +++ b/topicnet/cooking_machine/cubes/regularizer_cube.py @@ -1,6 +1,7 @@ from .base_cube import BaseCube from ..routine import transform_complex_entity_to_dict from ..rel_toolbox_lite import count_vocab_size, handle_regularizer +from ..models.base_regularizer import BaseRegularizer from copy import deepcopy @@ -144,18 +145,28 @@ def apply(self, topic_model, one_model_parameter, dictionary=None, model_id=None regularizer_type = str(type(regularizer)) if isinstance(regularizer, dict): if regularizer['name'] in new_model.all_regularizers.keys(): + # TODO: do we actually need to deepcopy custom regularizers? new_regularizer = deepcopy(new_model.all_regularizers[regularizer['name']]) - new_regularizer._tau = params - handle_regularizer( - self._relative, - new_model, - new_regularizer, - self.data_stats, - ) + if regularizer['name'] in new_model.custom_regularizers: + new_model.custom_regularizers[regularizer['name']].tau = params + else: + # if this is classic regularizer, we attempt to relativize it's coefficients + new_regularizer._tau = params + handle_regularizer( + self._relative, + new_model, + new_regularizer, + self.data_stats, + ) else: error_msg = (f"Regularizer {regularizer['name']} does not exist. " f"Cannot be modified.") raise ValueError(error_msg) + elif isinstance(regularizer, BaseRegularizer): + # TODO: do we actually need to deepcopy here? + new_regularizer = deepcopy(regularizer) + new_regularizer.tau = params + new_model.custom_regularizers[regularizer.name] = new_regularizer elif 'Regularizer' in regularizer_type: new_regularizer = deepcopy(regularizer) new_regularizer._tau = params diff --git a/topicnet/cooking_machine/dataset.py b/topicnet/cooking_machine/dataset.py index 4d96866..30dfe90 100644 --- a/topicnet/cooking_machine/dataset.py +++ b/topicnet/cooking_machine/dataset.py @@ -3,7 +3,6 @@ import pandas as pd import shutil import sys -import tempfile import warnings from glob import glob @@ -403,6 +402,7 @@ def from_dataframe( """ data_path = os.path.join(save_dataset_path, dataframe_name + '.csv') dataframe.to_csv(data_path) + return cls(data_path=data_path, **kwargs) def get_dataset(self): @@ -558,17 +558,16 @@ def _check_collection(self): return False, path_to_collection if self._data_hash is None: - temp_file_descriptor, temp_file_path = tempfile.mkstemp( - prefix='temp_vw__', - suffix='.txt', - dir=self._internals_folder_path + temp_file_path = os.path.join( + self._internals_folder_path, 'temp_vw.txt' ) - self.write_vw(temp_file_path) - self._data_hash = blake2bchecksum(temp_file_path) - - os.close(temp_file_descriptor) - os.remove(temp_file_path) + try: + self.write_vw(temp_file_path) + self._data_hash = blake2bchecksum(temp_file_path) + finally: + if os.path.isfile(temp_file_path): + os.remove(temp_file_path) if os.path.isfile(path_to_collection): same_collection = blake2bchecksum(path_to_collection) == self._data_hash diff --git a/topicnet/cooking_machine/experiment.py b/topicnet/cooking_machine/experiment.py index eb75bac..65484ad 100644 --- a/topicnet/cooking_machine/experiment.py +++ b/topicnet/cooking_machine/experiment.py @@ -457,7 +457,7 @@ def load(load_path): Experiment """ - from .models import TopicModel + from .models import DummyTopicModel files = os.listdir(load_path) if "params.json" not in files: @@ -472,7 +472,7 @@ def load(load_path): for model_id in experiment.models.keys(): if model_id != START: model_save_path = os.path.join(load_path, model_id) - experiment.models[model_id] = TopicModel.load( + experiment.models[model_id] = DummyTopicModel.load( model_save_path, experiment ) @@ -643,7 +643,7 @@ def select(self, query_string='', models_num=None, level=None): return [] - def run(self, dataset, verbose=False, nb_verbose=False): + def run(self, dataset, verbose=False, nb_verbose=False, restore_mode=False): # noqa C901 """ Runs defined pipeline and prints out the result. @@ -664,7 +664,22 @@ def run(self, dataset, verbose=False, nb_verbose=False): continue cube = cube_description['cube'] - cube(stage_models, dataset) + if not restore_mode: + cube(stage_models, dataset) + else: + if cube_index < self.depth - 1: + print(f"[Restoring experiment]: skipping cube {cube_index}") + continue + if cube_index == self.depth - 1: + print( + f"[Restoring experiment]: selecting models at cube number" + f"{cube_index} (some models could be lost)" + ) + if cube_index >= self.depth: + print( + f"[Restoring experiment]: applying cube number {cube_index}" + ) + cube(stage_models, dataset) # TODO: either delete this line completely # or come up with a way to restore any cube using just info about it in self.cubes @@ -803,7 +818,7 @@ def preprocess_query(self, query_string: str, level): req_equal, metric, extremum) = parse_query_string(inner_query_string) if metric is not None or extremum is not None: - warnings.warn(f'You try to optimize model parameters.') + warnings.warn('You try to optimize model parameters.') candidate_tmodels = self.get_models_by_depth(level=level) special_models = choose_best_models( @@ -850,6 +865,9 @@ def build(self, settings): try: self.cubes += [{ 'action': stage_cube.action, + # TODO: should it be 'params': cube_param instead? + # it seems that it is possible to restore failed + # experiment with load() that way..? 'params': stage_cube.get_jsonable_from_parameters(), 'cube': stage_cube }] diff --git a/topicnet/cooking_machine/models/__init__.py b/topicnet/cooking_machine/models/__init__.py index 20d5755..99cd588 100644 --- a/topicnet/cooking_machine/models/__init__.py +++ b/topicnet/cooking_machine/models/__init__.py @@ -1,6 +1,7 @@ from .base_model import BaseModel from .topic_model import TopicModel from .dummy_topic_model import DummyTopicModel + from .base_score import BaseScore from .example_score import ScoreExample from .intratext_coherence_score import IntratextCoherenceScore diff --git a/topicnet/cooking_machine/models/base_score.py b/topicnet/cooking_machine/models/base_score.py index 1e6ee04..c3b43cb 100644 --- a/topicnet/cooking_machine/models/base_score.py +++ b/topicnet/cooking_machine/models/base_score.py @@ -1,4 +1,11 @@ import dill + +from typing import ( + Any, + Callable, + Dict, +) + from . import scores as tn_scores @@ -7,21 +14,93 @@ class BaseScore: Base Class to construct custom score functions. """ - def __init__(self, name: str = None): # TODO: name should not be optional + _PRECOMPUTED_DATA_PARAMETER_NAME = 'precomputed_data' + + # TODO: name should not be optional + def __init__( + self, + name: str = None, + should_compute: Callable[[int], bool] or bool = None): """ Parameters ---------- - name: + name Name of the score - + should_compute + Function which decides whether the score should be computed + on the current fit iteration or not. + If `should_compute` is `None`, then score is going to be computed on every iteration. + At the same time, whatever function one defines, + score is always computed on the last fit iteration. + This is done for two reasons. + Firstly, so that the score is always computed at least once during `model._fit()`. + Secondly, so that `experiment.select()` works correctly. + + The parameter `should_compute` might be helpful + if the score is slow but one still needs + to get the dependence of the score on iteration + (for the described case, one may compute the score + on every even iteration or somehow else). + However, be aware that if `should_compute` is used for some model's scores, + then the scores may have different number of values in `model.scores`! + Number of score values is the number of times the scores was calculated; + first value corresponds to the first fit iteration + which passed `should_compute` etc. + + There are a couple of things also worth noting. + Fit iteration numbering starts from zero. + And every new `model._fit()` call is a new range of fit iterations. + + Examples + -------- + Scores created below are unworkable (as BaseScore has no `call` method inplemented). + These are just the examples of how one can create a score and set some of its parameters. + + Scores to be computed on every iteration: + + >>> score = BaseScore() + >>> score = BaseScore(should_compute=BaseScore.compute_always) + >>> score = BaseScore(should_compute=lambda i: True) + >>> score = BaseScore(should_compute=True) + + Scores to be computed only on the last iteration: + + >>> score = BaseScore(should_compute=BaseScore.compute_on_last) + >>> score = BaseScore(should_compute=lambda i: False) + >>> score = BaseScore(should_compute=False) + + Score to be computed only on even iterations: + + >>> score = BaseScore(should_compute=lambda i: i % 2 == 0) """ self._name = name + + if should_compute is None: + should_compute = self.compute_always + elif should_compute is True: + should_compute = self.compute_always + elif should_compute is False: + should_compute = self.compute_on_last + elif not isinstance(should_compute, type(lambda: None)): + raise TypeError(f'Unknown type of `should_compute`: {type(should_compute)}!') + else: + pass + + self._should_compute = should_compute self.value = [] if not hasattr(tn_scores, self.__class__.__name__): setattr(tn_scores, self.__class__.__name__, self.__class__) + @staticmethod + def compute_always(fit_iteration: int) -> bool: + return True + + @staticmethod + def compute_on_last(fit_iteration: int) -> bool: + return False + def __repr__(self): return f'{self.__class__.__name__}' @@ -57,7 +136,7 @@ def update(self, score): self.value.append(score) - def call(self, model): + def call(self, model, precomputed_data: Dict[str, Any] = None): """ Call to custom score function. @@ -65,6 +144,12 @@ def call(self, model): ---------- model : TopicModel a TopicNet model inherited from BaseModel + precomputed_data + Data which scores may share between each other during *one fit iteration*. + For example, if the model has several scores of the same score class, + and there is a heavy time consuming computation inside this score class, + it may be useful to perform the calculations *only once*, for one score instance, + and then make the result visible for all other scores that might need it. Returns ------- @@ -78,5 +163,44 @@ def call(self, model): and then use this logic in query in Experiment's `select()` method. If one need ARTM model for score (not TopicNet one), it is available as model._model + + When creating a custom score class, + it is recommended to use `**kwargs` in the score's `call` method, + so that all `BaseScore` optional parameters are also available + in its successor score classes. + + Examples + -------- + + Score which uses `precomputed_data`: + + >>> import time + ... + >>> class NewScore(BaseScore): + ... def __init__(self, name: str, multiplier: float): + ... super().__init__(name=name) + ... + ... self._multiplier = multiplier + ... self._heavy_value_name = 'time_consuming_value_name' + ... + ... def call(self, model, precomputed_data = None): + ... if precomputed_data is None: + ... # Parameter `precomputed_data` is optional in BaseScore + ... # So this case also should be supported + ... heavy_value = self._compute_heavy(model) + ... elif self._heavy_value_name in precomputed_data: + ... # This is going to be fast + ... heavy_value = precomputed_data[self._heavy_value_name] + ... else: + ... # This is slow (but only one such call!) + ... heavy_value = self._compute_heavy(model) + ... precomputed_data[self._heavy_value_name] = heavy_value + ... + ... return heavy_value * self._multiplier + ... + ... def _compute_heavy(self, model): + ... time.sleep(100) # just for demonstration + ... + ... return 0 """ raise NotImplementedError('Define your score here') diff --git a/topicnet/cooking_machine/models/blei_lafferty_score.py b/topicnet/cooking_machine/models/blei_lafferty_score.py index baf2393..1c6e90b 100644 --- a/topicnet/cooking_machine/models/blei_lafferty_score.py +++ b/topicnet/cooking_machine/models/blei_lafferty_score.py @@ -1,4 +1,7 @@ import numpy as np + +from typing import Callable + from .base_score import BaseScore @@ -11,7 +14,11 @@ class BleiLaffertyScore(BaseScore): to describe given topic. Summing up that score helps to estimate how well the model distinguishes between topics. The higher this score - better """ - def __init__(self, name: str = None, num_top_tokens: int = 30): + def __init__( + self, + name: str = None, + num_top_tokens: int = 30, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -22,7 +29,7 @@ def __init__(self, name: str = None, num_top_tokens: int = 30): now many tokens we consider to be """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.num_top_tokens = num_top_tokens @@ -60,7 +67,7 @@ def _compute_blei_scores(self, phi): scores = phi * multiplier return scores - def call(self, model): + def call(self, model, **kwargs): modalities = list(model.class_ids.keys()) score = 0 diff --git a/topicnet/cooking_machine/models/dummy_topic_model.py b/topicnet/cooking_machine/models/dummy_topic_model.py index b00b5d5..37d802d 100644 --- a/topicnet/cooking_machine/models/dummy_topic_model.py +++ b/topicnet/cooking_machine/models/dummy_topic_model.py @@ -371,3 +371,6 @@ def get_theta(self, *args, **kwargs): def add_cube(self, cube): raise InvalidOperationError() + + def describe_regularizers(self): + raise InvalidOperationError() diff --git a/topicnet/cooking_machine/models/example_score.py b/topicnet/cooking_machine/models/example_score.py index 35ff6a8..a19504d 100644 --- a/topicnet/cooking_machine/models/example_score.py +++ b/topicnet/cooking_machine/models/example_score.py @@ -1,4 +1,7 @@ import numpy as np + +from typing import Callable + from .base_score import BaseScore @@ -10,7 +13,11 @@ class ScoreExample(BaseScore): (the internal logic of TopicNet relies on them) """ - def __init__(self, name: str = None, token_threshold: float = 1e-3): + def __init__( + self, + name: str = None, + token_threshold: float = 1e-3, + should_compute: Callable[[int], bool] = None): """ Parameters @@ -21,11 +28,11 @@ def __init__(self, name: str = None, token_threshold: float = 1e-3): what probabilities to take as token belonging to the topic """ - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self.threshold = token_threshold - def call(self, model): + def call(self, model, **kwargs): """ Method that calculates the score diff --git a/topicnet/cooking_machine/models/intratext_coherence_score.py b/topicnet/cooking_machine/models/intratext_coherence_score.py index 7aa22df..3e68969 100644 --- a/topicnet/cooking_machine/models/intratext_coherence_score.py +++ b/topicnet/cooking_machine/models/intratext_coherence_score.py @@ -8,6 +8,7 @@ from collections import defaultdict from enum import Enum, IntEnum, auto from typing import ( + Callable, Dict, List, Optional, @@ -105,6 +106,7 @@ def __init__( # noqa: C901 self, dataset: Union[Dataset, str], name: str = None, + should_compute: Callable[[int], bool] = None, keep_dataset_in_memory: bool = None, keep_dataset: bool = True, documents: List[str] = None, @@ -189,7 +191,7 @@ def __init__( # noqa: C901 >>> topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iterations) """ # TODO: word_topic_relatedness seems to be connected with TopTokensViewer stuff - super().__init__(name=name) + super().__init__(name=name, should_compute=should_compute) self._keep_dataset = keep_dataset @@ -363,7 +365,7 @@ def load(cls, path: str): return score - def call(self, model: BaseModel) -> float: + def call(self, model: BaseModel, **kwargs) -> float: if (self._current_iteration - self._start_fit_iteration) % self._fit_iteration_step != 0: self._current_iteration += 1 diff --git a/topicnet/cooking_machine/models/scores_wrapper.py b/topicnet/cooking_machine/models/scores_wrapper.py index fe0c7ac..4796769 100644 --- a/topicnet/cooking_machine/models/scores_wrapper.py +++ b/topicnet/cooking_machine/models/scores_wrapper.py @@ -58,9 +58,9 @@ def add(self, score: Union[BaseScore, artm.scores.BaseScore]): elif isinstance(score, BaseScore): if score._name is None: raise ValueError( - f'When using `model.scores.add(score)` method,' - f' one should specify score name parameter during score initialization.' - f' For example `model.scores.add(IntratextCoherenceScore(name="name", ...))' + 'When using `model.scores.add(score)` method,' + ' one should specify score name parameter during score initialization.' + ' For example `model.scores.add(IntratextCoherenceScore(name="name", ...))' ) self._topicnet_scores[score._name] = score diff --git a/topicnet/cooking_machine/models/thetaless_regularizer.py b/topicnet/cooking_machine/models/thetaless_regularizer.py index a1218ec..1903b1a 100644 --- a/topicnet/cooking_machine/models/thetaless_regularizer.py +++ b/topicnet/cooking_machine/models/thetaless_regularizer.py @@ -1,13 +1,161 @@ import numpy as np -from numba import jit +import os +import pandas as pd import scipy.sparse +import warnings + +from numba import jit + +import artm from .base_regularizer import BaseRegularizer +from ..dataset import Dataset + + +# TODO: move this to BigARTM +# ================================== + +FIELDS = 'token class_id token_value token_tf token_df'.split() + + +def artm_dict2df(artm_dict): + """ + :Description: converts the BigARTM dictionary of the collection + to the pandas.DataFrame. + This is approximately equivalent to the dictionary.save_text() + but has no I/O overhead + + """ + dictionary_data = artm_dict._master.get_dictionary(artm_dict._name) + dict_pandas = {field: getattr(dictionary_data, field) + for field in FIELDS} + return pd.DataFrame(dict_pandas) + +# ================================== EPS = 1e-20 +# TODO: is there a better way to do this? +def obtain_token2id(dataset: Dataset): + """ + Allows one to obtain the mapping from token to the artm.dictionary id of that token + (useful for low-level operations such as reading batches manually) + + Returns + ------- + dict: + maps (token, class_id) to integer (corresponding to the row of Phi / dictionary id) + + """ + df = artm_dict2df(dataset.get_dictionary()) + df_inverted_index = df[['token', 'class_id']].reset_index().set_index(['token', 'class_id']) + + return df_inverted_index.to_dict()['index'] + + +def dataset2sparse_matrix(dataset, modality, modalities_to_use=None): + """ + Builds a sparse matrix from batch_vectorizer linked to the Dataset + + If you need an inverse mapping: + + >>> d = sparse_n_dw_matrix.todok() # convert to dictionary of keys format + >>> dict_of_csr = dict(d.items()) + + Parameters + ---------- + dataset: Dataset + modality: str + the remaining modalities will be ignored + (their occurrences will be replaced with zeros, but they will continue to exist) + modalities_to_use: iterable + a set of modalities the underlying topic model is using (this is about topic model, + not regularizer; this parameter ensures that the shapes of n_dw matrix and actual + Phi matrix match). + + The tokens outside of this list will be discarded utterly + (the resulting matrix will have no entries corresponding to them) + + For artm.ARTM() models, you need to pass whatever is inside class_ids; + while TopicModel usually requires this to be set inside modalities_to_use. + + If you hadn't explicitly listed any modalities yet, you probably could + leave this argument as None. + + If you use a single modality, wrap it into a list (e.g.['@word']) + + Returns + ------- + n_dw_matrix: scipy.sparse.csr_matrix + The matrix of document-word occurrences. + `n_dw` is a number of the occurrences of the word `w` in the document `d` + this matrix determines the dependence between the Theta and Phi matrices + (Phi is the result of one iteration of the ARTM's EM algorihtm + with uniform theta initialization and `n_dw` matrix of the document-word occurrences) + """ # noqa: W291 + token2id = obtain_token2id(dataset) + + batch_vectorizer = dataset.get_batch_vectorizer() + + return _batch_vectorizer2sparse_matrix( + batch_vectorizer, token2id, modality, modalities_to_use + ) + + +def _batch_vectorizer2sparse_matrix(batch_vectorizer, token2id, modality, modalities_to_use=None): + """ + """ + theta_column_naming = 'id' # scipy sparse matrix doesn't support non-integer indices + matrix_row, matrix_col, matrix_data = [], [], [] + + for batch_id in range(len(batch_vectorizer._batches_list)): + batch_name = batch_vectorizer._batches_list[batch_id]._filename + batch = artm.messages.Batch() + with open(batch_name, "rb") as f: + batch.ParseFromString(f.read()) + + for item_id in range(len(batch.item)): + item = batch.item[item_id] + theta_item_id = getattr(item, theta_column_naming) + + for local_token_id, token_weight in zip(item.token_id, item.token_weight): + token_class_id = batch.class_id[local_token_id] + token = batch.token[local_token_id] + if (token, token_class_id) not in token2id: + # probably dictionary was filtered + continue + if modalities_to_use and token_class_id not in modalities_to_use: + continue + if token_class_id != modality: + # we still need these tokens, + # shapes of n_dw matrix and actual Phi matrix should be in sync. + # this will be changed to zero at the end + token_weight = np.nan + token_id = token2id[(token, token_class_id)] + matrix_row.append(theta_item_id) + matrix_col.append(token_id) + matrix_data.append(token_weight) + + sparse_n_dw_matrix = scipy.sparse.csr_matrix( + (matrix_data, (matrix_row, matrix_col)), + ) + # remove the columns whose all elements are zero + # (i.e. tokens which are of different modalities) + # and renumber index (fill any "holes") + # this is needed to be in sync with artm dictionary after filtering elements out + # (they need to have the same shape) + ind = sparse_n_dw_matrix.sum(axis=0) + nonzeros = np.ravel(ind > 0) + sparse_n_dw_matrix = sparse_n_dw_matrix[:, nonzeros] + + # re-encode values to transform NaNs to explicitly stored zeros + sparse_n_dw_matrix.data = np.nan_to_num(sparse_n_dw_matrix.data) + + return sparse_n_dw_matrix + + @jit(nopython=True) def memory_efficient_inner1d(fst_arr, fst_indices, snd_arr, snd_indices): """ @@ -92,6 +240,7 @@ def get_prob_matrix_by_counters(counters, inplace=False): # set rows where sum of row is small to uniform res[np.sum(res, axis=1) < EPS, :] = 1. res /= np.sum(res, axis=1)[:, np.newaxis] + return res @@ -113,40 +262,63 @@ def calc_A_matrix( class ThetalessRegularizer(BaseRegularizer): - def __init__(self, name, tau, n_dw_matrix): + def __init__(self, name, tau, modality, dataset: Dataset): """ - Creates a node in the graph with the given args and kwargs. + A regularizer based on a "thetaless" topic model inference + + Note: this implementation stores sparse `n_dw` matrix in memory, + so this is not particularly memory- and space-efficient for huge datasets Parameters ---------- name: str name of the regularizer tau: Number - fictive parameter it's not used, just passed to the parent conctructor - n_dw_matrix: scipy.sparse.csr_matrix - The matrix of document-word occurrences - n_dw is a number of the occurrences of the word w in the document d - this matrix determines the dependence between the Theta and Phi matrices - (Phi is the result of one iteration of the ARTM's EM algorihtm - with uniform theta initialization and n_dw matrix of the document-word occurrences) - """ + according to the math, `tau` should be set to 1 (to correctly emulate a different + inference process). But you do you, it's not like there's a regularizer + police or something. + modality: str + name of modality on which the inference should be based + dataset + will be transformed to n_dw_matrix + """ # noqa: W291 super().__init__(name, tau) - self.n_dw_matrix = n_dw_matrix + + self.modality = modality + self.modalities_to_use = None + self.n_dw_matrix = None + + self.token2id = obtain_token2id(dataset) + self._batches_path = os.path.join(dataset._internals_folder_path, "batches") + + def _initialize_matrices(self, batch_vectorizer, token2id): + self.n_dw_matrix = _batch_vectorizer2sparse_matrix( + batch_vectorizer, token2id, self.modality, self.modalities_to_use + ) self.B = scipy.sparse.csr_matrix( ( - 1. * n_dw_matrix.data / calc_docsizes(n_dw_matrix), - n_dw_matrix.indices, - n_dw_matrix.indptr + 1. * self.n_dw_matrix.data / calc_docsizes(self.n_dw_matrix), + self.n_dw_matrix.indices, + self.n_dw_matrix.indptr ), - shape=n_dw_matrix.shape + shape=self.n_dw_matrix.shape ).tocsc() - self.docptr = get_docptr(n_dw_matrix) - self.wordptr = n_dw_matrix.indices + self.docptr = get_docptr(self.n_dw_matrix) + self.wordptr = self.n_dw_matrix.indices def grad(self, pwt, nwt): phi_matrix_tr = np.array(pwt) phi_matrix = phi_matrix_tr.T phi_rev_matrix = get_prob_matrix_by_counters(phi_matrix_tr) + + if self.n_dw_matrix.shape[1] != phi_rev_matrix.shape[0]: + raise ValueError( + f"Thetaless regularizer has prepared {self.n_dw_matrix.shape} n_dw matrix," + f" but was passed {phi_rev_matrix.T.shape} Phi matrix containing different" + f" number of tokens ({self.n_dw_matrix.shape[1]} != {phi_rev_matrix.shape[0]})" + f"\n(Are modalities the same?)" + ) + theta_matrix = get_prob_matrix_by_counters( self.n_dw_matrix.dot(phi_rev_matrix) ) @@ -164,4 +336,24 @@ def grad(self, pwt, nwt): tmp = g_dt.T * self.B / (phi_matrix_tr.sum(axis=1) + EPS) n_tw += (tmp - np.einsum('ij,ji->i', phi_rev_matrix, tmp)) * phi_matrix - return n_tw.T - nwt + return self.tau * (n_tw.T - nwt) + + def attach(self, model): + """ + + Parameters + ---------- + model : ARTM model + necessary to apply master component + """ + if model.num_document_passes != 1: + warnings.warn( + f"num_document_passes is equal to {model.num_document_passes}, but it" + f" should be set to {1} to correctly emulate a thetaless inference process" + ) + + self.modalities_to_use = model.class_ids.keys() + bv = artm.BatchVectorizer(data_path=self._batches_path, data_format='batches') + self._initialize_matrices(bv, self.token2id) + + self._model = model diff --git a/topicnet/cooking_machine/models/topic_model.py b/topicnet/cooking_machine/models/topic_model.py index 3dfd57e..8e397d1 100644 --- a/topicnet/cooking_machine/models/topic_model.py +++ b/topicnet/cooking_machine/models/topic_model.py @@ -9,7 +9,6 @@ import shutil import warnings -from artm.wrapper.exceptions import ArtmException from copy import deepcopy from inspect import signature from numbers import Number @@ -18,10 +17,14 @@ Any, Dict, List, + Union, ) +from artm.wrapper.exceptions import ArtmException + from . import scores as tn_scores from .base_model import BaseModel +from .base_regularizer import BaseRegularizer from .base_score import BaseScore from .frozen_score import FrozenScore from ..cubes.controller_cube import ControllerAgent @@ -59,7 +62,7 @@ def __init__( experiment=None, callbacks: List[ControllerAgent] = None, custom_scores: Dict[str, BaseScore] = None, - custom_regularizers: Dict[str, artm.regularizers.BaseRegularizer] = None, + custom_regularizers: Dict[str, BaseRegularizer] = None, *args, **kwargs): """ Initialize stage, also used for loading previously saved experiments. @@ -176,24 +179,13 @@ def get_score_properties_and_values(score_name, score_object): return score_values - def _fit(self, dataset_trainable, num_iterations, custom_regularizers=None): - """ - - Parameters - ---------- - dataset_trainable : BatchVectorizer - Data for model fit - num_iterations : int - Amount of fit steps - custom_regularizers : dict of BaseRegularizer - Regularizers to apply to model - - """ + def _prepare_custom_regularizers(self, custom_regularizers): if custom_regularizers is None: custom_regularizers = dict() all_custom_regularizers = deepcopy(custom_regularizers) all_custom_regularizers.update(self.custom_regularizers) + base_regularizers_name, base_regularizers_tau = None, None if len(all_custom_regularizers) != 0: for regularizer in all_custom_regularizers.values(): @@ -204,7 +196,29 @@ def _fit(self, dataset_trainable, num_iterations, custom_regularizers=None): base_regularizers_tau = [regularizer.tau for regularizer in self._model.regularizers.data.values()] + return base_regularizers_name, base_regularizers_tau, all_custom_regularizers + + def _fit(self, dataset_trainable, num_iterations, custom_regularizers=None): + """ + + Parameters + ---------- + dataset_trainable : BatchVectorizer + Data for model fit + num_iterations : int + Amount of fit steps + custom_regularizers : dict of BaseRegularizer + Regularizers to apply to model + + """ + (base_regularizers_name, + base_regularizers_tau, + all_custom_regularizers) = self._prepare_custom_regularizers(custom_regularizers) + for cur_iter in range(num_iterations): + precomputed_data = dict() + iter_is_last = cur_iter == num_iterations - 1 + self._model.fit_offline(batch_vectorizer=dataset_trainable, num_collection_passes=1) @@ -216,9 +230,26 @@ def _fit(self, dataset_trainable, num_iterations, custom_regularizers=None): for name, custom_score in self.custom_scores.items(): try: - score = custom_score.call(self) + should_compute_now = iter_is_last or custom_score._should_compute(cur_iter) + + if not should_compute_now: + continue + + # TODO: this check is probably should be refined somehow... + # what if some new parameter added to BaseScore.call -> new check?.. + call_parameters = signature(custom_score.call).parameters + + # if-else instead of try-catch: to speed up + if (BaseScore._PRECOMPUTED_DATA_PARAMETER_NAME not in call_parameters + and not any(str(p).startswith('**') for p in call_parameters.values())): + + score = custom_score.call(self) + else: + score = custom_score.call(self, precomputed_data=precomputed_data) + custom_score.update(score) self._model.score_tracker[name] = custom_score + except AttributeError: # TODO: means no "call" attribute? raise AttributeError(f'Score {name} doesn\'t have a desired attribute') @@ -316,17 +347,32 @@ def save_custom_regularizers(self, model_save_path=None): model_save_path = self.model_default_save_path for regularizer_name, regularizer_object in self.custom_regularizers.items(): - try: - save_path = os.path.join(model_save_path, regularizer_name + '.rd') - with open(save_path, 'wb') as reg_f: - dill.dump(regularizer_object, reg_f) - except (TypeError, AttributeError): + # If not do this, there may be problems with pickling: + # `model` is an ARTM-C-like thing, and it may cause problems + # This is safe, because `model` appears in attach(), + # which is called before each iteration + # P.S. and the `model` itself may be needed for a regularizer inside `grad()` + regularizer_object._model = None + + managed_to_pickle = False + + for (pickler, extension) in zip([dill, pickle], ['.rd', '.rp']): + save_path = os.path.join(model_save_path, regularizer_name + extension) + try: - save_path = os.path.join(model_save_path, regularizer_name + '.rp') with open(save_path, 'wb') as reg_f: - pickle.dump(regularizer_object, reg_f) + pickler.dump(regularizer_object, reg_f) except (TypeError, AttributeError): - warnings.warn(f'Cannot save {regularizer_name} regularizer.') + if os.path.isfile(save_path): + os.remove(save_path) + else: + managed_to_pickle = True + + if managed_to_pickle: + break + + if not managed_to_pickle: + warnings.warn(f'Cannot save {regularizer_name} regularizer!') def save(self, model_save_path=None, @@ -515,6 +561,7 @@ def get_phi(self, topic_names=None, class_ids=None, model_name=None): class_ids = [class_ids] class_ids_iter = class_ids or self._model.class_ids # TODO: this workaround seems to be a correct solution to this problem + # maybe the next for-loop could be replaced with these three lines if not class_ids_iter: valid_model_name = self._model.model_pwt info = self._model.master.get_phi_info(valid_model_name) @@ -829,3 +876,25 @@ def describe_regularizers(self): columns=["model_id", "regularizer_name", "tau", "gamma", "class_ids"], data=data ) return result.set_index(["model_id", "regularizer_name"]).sort_values(by="regularizer_name") + + def get_regularizer( + self, reg_name: str) -> Union[BaseRegularizer, artm.regularizers.BaseRegularizer]: + """ + Retrieves the regularizer specified, no matter is it custom or "classic" + + Returns + ------- + regularizer + + """ + # TODO: RegularizersWrapper? + + if reg_name in self.custom_regularizers: + return self.custom_regularizers[reg_name] + elif reg_name in self._model.regularizers.data: + return self._model.regularizers.data[reg_name] + else: + raise KeyError( + f'There is no such regularizer "{reg_name}"' + f' among custom and ARTM regularizers!' + ) diff --git a/topicnet/cooking_machine/recipes/artm_baseline_pipeline.py b/topicnet/cooking_machine/recipes/artm_baseline_pipeline.py index edee710..25477df 100644 --- a/topicnet/cooking_machine/recipes/artm_baseline_pipeline.py +++ b/topicnet/cooking_machine/recipes/artm_baseline_pipeline.py @@ -1,7 +1,9 @@ from typing import List + from .recipe_wrapper import BaseRecipe from .. import Dataset + ARTM_baseline_template = ''' # This config follows a strategy described by Murat Apishev # one of the core programmers of BigARTM library in personal correspondence. @@ -40,6 +42,7 @@ num_top_tokens: 30 model: dataset_path: {dataset_path} + {dictionary_filter_parameters} modalities_to_use: {modality_list} main_modality: '{main_modality}' @@ -62,6 +65,8 @@ use_relative_coefficients: true ''' +ONE_CONFIG_INDENT = 4 * ' ' + class BaselineRecipe(BaseRecipe): """ @@ -74,6 +79,7 @@ def __init__(self): def format_recipe( self, dataset_path: str, + dictionary_filter_parameters: dict = None, modality_list: List[str] = None, topic_number: int = 20, background_topic_number: int = 1, @@ -86,11 +92,21 @@ def format_recipe( background_topics = [f'bcg_{i}' for i in range( len(specific_topics), len(specific_topics) + background_topic_number)] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, modality_list=modality_list, main_modality=modality_list[0], specific_topics=specific_topics, background_topics=background_topics, ) + return self._recipe diff --git a/topicnet/cooking_machine/recipes/exploratory_search_pipeline.py b/topicnet/cooking_machine/recipes/exploratory_search_pipeline.py index f0be531..ea6372e 100644 --- a/topicnet/cooking_machine/recipes/exploratory_search_pipeline.py +++ b/topicnet/cooking_machine/recipes/exploratory_search_pipeline.py @@ -20,6 +20,10 @@ # specific_topics=specific_topics, background_topics=background_topics) # when loading the recipe to adjust for your dataset +# If you have more than one modaity you want to use, we recommend employing +# more advanced MultimodalSearchRecipe from multimodal_exploratory_search_pipeline instead + + topics: # Describes number of model topics, in the actuall article 200 topics were found to be optimal specific_topics: {{specific_topics}} diff --git a/topicnet/cooking_machine/recipes/intratext_coherence_maximization.yml b/topicnet/cooking_machine/recipes/intratext_coherence_maximization.yml index 72244bf..d134629 100644 --- a/topicnet/cooking_machine/recipes/intratext_coherence_maximization.yml +++ b/topicnet/cooking_machine/recipes/intratext_coherence_maximization.yml @@ -67,6 +67,7 @@ scores: model: dataset_path: {dataset_path} + {dictionary_filter_parameters} modalities_to_use: {modality_names} main_modality: '{main_modality}' diff --git a/topicnet/cooking_machine/recipes/intratext_coherence_pipeline.py b/topicnet/cooking_machine/recipes/intratext_coherence_pipeline.py index 2c712a3..b89afe6 100644 --- a/topicnet/cooking_machine/recipes/intratext_coherence_pipeline.py +++ b/topicnet/cooking_machine/recipes/intratext_coherence_pipeline.py @@ -6,6 +6,8 @@ from .recipe_wrapper import BaseRecipe from .. import Dataset +ONE_CONFIG_INDENT = 4 * ' ' + class IntratextCoherenceRecipe(BaseRecipe): """ @@ -35,6 +37,7 @@ def format_recipe( dataset_path: str, num_specific_topics: int, main_modality: str = None, + dictionary_filter_parameters: dict = None, num_background_topics: int = 1, modalities: List[str] = None, keep_dataset_in_memory: bool = True, @@ -118,10 +121,19 @@ def format_recipe( for i in range(num_specific_topics, num_specific_topics + num_background_topics) ] + if dictionary_filter_parameters is None: + dictionary_filter_parameters = dict() + + dictionary_filter_parameters_as_yml = self._format_dictionary_filter_parameters( + dictionary_filter_parameters, + indent=2 * ONE_CONFIG_INDENT, + ) + self._recipe = self.recipe_template.format( modality_names=modalities, main_modality=main_modality, dataset_path=dataset_path, + dictionary_filter_parameters=dictionary_filter_parameters_as_yml, keep_dataset_in_memory=keep_dataset_in_memory, keep_dataset=keep_dataset, documents_fraction=documents_fraction, diff --git a/topicnet/cooking_machine/recipes/multimodal_exploratory_search_pipeline.py b/topicnet/cooking_machine/recipes/multimodal_exploratory_search_pipeline.py index 6f97a3d..c0a304d 100644 --- a/topicnet/cooking_machine/recipes/multimodal_exploratory_search_pipeline.py +++ b/topicnet/cooking_machine/recipes/multimodal_exploratory_search_pipeline.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import List, Union, Dict from .recipe_wrapper import BaseRecipe from .. import Dataset @@ -27,7 +27,7 @@ model: dataset_path: {dataset_path} - modalities_to_use: {modality_list} + {modalities_description} main_modality: '{modality}' stages: @@ -67,7 +67,7 @@ verbose: false use_relative_coefficients: True '''.format('PerplexityScore@all < 1.01 * MINIMUM(PerplexityScore@all)' + - ' and SparsityPhiScore{modality} -> max') + ' and SparsityThetaScore -> max') # Had to change tracked score function. Is it fine? decor_phi_cube_template = ''' @@ -80,9 +80,9 @@ - {0} strategy: PerplexityStrategy strategy_params: - start_point: 0 - step: 0.02 - max_len: 20 + start_point: 0.005 + step: 0.005 + max_len: 10 tracked_score_function: PerplexityScore{{modality}} verbose: false use_relative_coefficients: True @@ -134,11 +134,38 @@ def __init__(self, order='extended_modalities'): def format_recipe( self, dataset_path: str, - modality_list: List[str] = None, + modality_list: List[str] or Dict = None, + main_modality: str = None, topic_number: int = 20, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20, ): + ''' + Creates a recipe for multimodal search + using basic template at the top of this file + + Parameters + ---------- + dataset_path : path to the data + main_modality : str + chosen to be main modality from modality list, if possible + if it is not specified, the function attempts to user + the first entry of `modality_list` instead + + modality_list : list of modality names to use + or a dict specifying the (relative) weight of each + topic_number: + number of the model topics + background_topic_number : + number of background topics + num_iter : + specifying number of iterations for each cube + + Returns + ------- + string specifying recipe for multimodal search + ''' + if modality_list is None: modality_list = list(Dataset(dataset_path).get_possible_modalities()) @@ -146,8 +173,13 @@ def format_recipe( background_topics = [f'bcg_{i}' for i in range( len(specific_topics), len(specific_topics) + background_topic_number)] + if main_modality is None: + if isinstance(modality_list, list): + main_modality = modality_list[0] + else: + raise TypeError("main_modality should be specified") self._make_multimodal_recipe( - modality=modality_list[0], + modality=main_modality, dataset_path=dataset_path, specific_topics=specific_topics, background_topics=background_topics, @@ -211,14 +243,12 @@ def _form_and_order_cubes( num_iter=iterations)) cube_templates.append(smooth_phi_cube_template.format(modality=modality, num_iter=iterations)) - cube_templates.append(sparse_theta_cube_template.format(modality=modality, - num_iter=iterations)) + cube_templates.append(sparse_theta_cube_template.format(num_iter=iterations)) else: raise ValueError('That option is not availiable') if self._order == 'extended_modalities': iterations = num_iter[-1] - cube_templates.append(sparse_theta_cube_template.format(modality=modality_list[0], - num_iter=iterations)) + cube_templates.append(sparse_theta_cube_template.format(num_iter=iterations)) return ''.join(cube_templates) def _make_multimodal_recipe( @@ -227,40 +257,30 @@ def _make_multimodal_recipe( modality: str, specific_topics: List[str], background_topics: List[str], - modality_list: List[str] = None, + modality_list: List[str] or Dict = None, background_topic_number: int = 1, num_iter: Union[int, List[int]] = 20, ): - ''' - Creates a recipe for multimodal search - using basic template at the top of this file - - Parameters - ---------- - dataset_path : path to the data - modality : str - chosen to be main modality from modality list - modality_list : list of modality names to use - specific_topics : list of str - names of the model topics - background_topics : list of background topic names - num_iter : number or list of numbers - specifying number of iterations for each cube - - Returns - ------- - string specifying recipe for multimodal search - ''' - reg_forms = self._form_regularizers(modality_list) cube_forms = self._form_and_order_cubes( modality_list, num_iter=num_iter,) + if isinstance(modality_list, list): + modalities_description = f"modalities_to_use: {modality_list}" + elif isinstance(modality_list, dict): + # this line has correct whitespace count + header_string = "modalities_weights:" + # these ones should be indented one level more, so 8 spaces + data_strings = [f"'{k}': {v}" for k, v in modality_list.items()] + strings = [header_string] + data_strings + modalities_description = "\n ".join(strings) + else: + raise TypeError("modality_list should be either list or dict, not {type(modality_list}") self._recipe = self.recipe_template.format( modality=modality, dataset_path=dataset_path, specific_topics=specific_topics, background_topics=background_topics, - modality_list=modality_list, + modalities_description=modalities_description, syntesized_regularizers=reg_forms, syntesized_stages=cube_forms) diff --git a/topicnet/cooking_machine/recipes/recipe_wrapper.py b/topicnet/cooking_machine/recipes/recipe_wrapper.py index 0438f87..15d9303 100644 --- a/topicnet/cooking_machine/recipes/recipe_wrapper.py +++ b/topicnet/cooking_machine/recipes/recipe_wrapper.py @@ -1,8 +1,15 @@ -from typing import Tuple +from typing import ( + Dict, + Tuple, + Union, +) from .. import Dataset from .. import Experiment -from ..config_parser import build_experiment_environment_from_yaml_config +from ..config_parser import ( + build_experiment_environment_from_yaml_config, + KEY_DICTIONARY_FILTER_PARAMETERS, +) recipe_template_example = """ @@ -30,19 +37,18 @@ def __str__(self): def format_recipe(self, *args, **kwargs) -> str: """ - Updates self._recipe variable - with variables specific for the dataset + Updates `self._recipe` + with variables specific for the dataset. """ raise NotImplementedError( 'Method needs to be specified for the recipe template' ) - return self._recipe def build_experiment_environment( self, save_path: str, experiment_id: str = 'default_experiment_name', - force_separate_thread: bool = False + force_separate_thread: bool = False, ) -> Tuple[Experiment, Dataset]: """ Returns experiment and dataset instances @@ -51,11 +57,13 @@ def build_experiment_environment( Parameters ---------- - save_path: path to the folder to save experiment logs and models - experiment_id: name of the experiment folder - force_separate_thread: train each model in dedicated process + save_path + path to the folder to save experiment logs and models + experiment_id + name of the experiment folder + force_separate_thread + train each model in dedicated process; this feature helps to handle resources in Jupyter notebooks - """ if self._recipe is None: raise ValueError( @@ -66,5 +74,27 @@ def build_experiment_environment( self._recipe, save_path=save_path, experiment_id=experiment_id, - force_separate_thread=force_separate_thread + force_separate_thread=force_separate_thread, + ) + + @staticmethod + def _format_dictionary_filter_parameters( + parameters: Dict[Union[int, float, str, bool], Union[int, float, str, bool]], + indent: str) -> str: + + blank_dictionary = '{}' + + if len(parameters) == 0: + parameters_block = blank_dictionary + else: + parameters_block = '\n'.join([ + f'{indent}{k}: {v}' + for k, v in parameters.items() + ]) + + return ( + KEY_DICTIONARY_FILTER_PARAMETERS + + ':' + + ('\n' if parameters_block != blank_dictionary else ' ') + + parameters_block ) diff --git a/topicnet/cooking_machine/routine.py b/topicnet/cooking_machine/routine.py index 9160108..3a69035 100644 --- a/topicnet/cooking_machine/routine.py +++ b/topicnet/cooking_machine/routine.py @@ -1,11 +1,14 @@ -import numpy as np +import glob import hashlib import json +import numexpr as ne +import numpy as np +import os import re import warnings + from datetime import datetime from statistics import mean, median -import numexpr as ne W_TOO_STRICT = 'No models match criteria ' @@ -441,7 +444,7 @@ def choose_value_for_models_num_and_check( models_num = models_num_from_query if models_num is not None and int(models_num) < 0: - raise ValueError(f"Cannot return negative number of models") + raise ValueError("Cannot return negative number of models") return models_num @@ -665,3 +668,26 @@ def blake2bchecksum(file_path): break m.update(data) return m.hexdigest() + + +def load_models_from_disk(experiment_directory, base_experiment_name): + """ + Is useful for restoring failed experiment + """ + from topicnet.cooking_machine.experiment import START + from topicnet.cooking_machine.models import DummyTopicModel + + result_models = [] + + mask = f"{experiment_directory}/{base_experiment_name}_*" + msg = (f'Trying to load models from {mask}.' + f' {len(glob.glob(mask))} models found.') + print(msg) + for folder in glob.glob(mask): + model_pathes = [ + f.path for f in os.scandir(folder) + if f.is_dir() and f.name != START + ] + result_models += [DummyTopicModel.load(path) for path in model_pathes] + + return result_models diff --git a/topicnet/dataset_manager/api.py b/topicnet/dataset_manager/api.py index 519e984..9c72d40 100644 --- a/topicnet/dataset_manager/api.py +++ b/topicnet/dataset_manager/api.py @@ -17,7 +17,7 @@ from ..cooking_machine.dataset import Dataset -_SERVER_URL = 'https://93.175.29.159:8085' +_SERVER_URL = 'https://topicnet-datasets.machine-intelligence.ru' _ARCHIVE_EXTENSION = '.gz' _DEFAULT_DATASET_FILE_EXTENSION = '.csv' @@ -68,12 +68,14 @@ def load_dataset(dataset_name: str, **kwargs) -> Dataset: dataset_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dataset_name) try: + print(f'Checking if dataset "{dataset_name}" was already downloaded before') + saved_dataset = _init_dataset_if_downloaded(dataset_path, **kwargs) except FileNotFoundError: - pass + print(f'Dataset "{dataset_name}" not found on the machine') else: print( - f'Dataset already downloaded!' + f'Dataset is found on the machine.' f' Save path is: "{saved_dataset._data_path}"' ) @@ -87,6 +89,8 @@ def load_dataset(dataset_name: str, **kwargs) -> Dataset: print(f'Downloading the "{dataset_name}" dataset...') + save_path = None + try: with urlopen(req, data=data, context=context) as answer: total_size = int(answer.headers.get('content-length', 0)) @@ -109,7 +113,7 @@ def load_dataset(dataset_name: str, **kwargs) -> Dataset: if total_size != 0 and t.n != total_size: raise RuntimeError( - "Failed to download dataset!" + "Failed to download the dataset!" " Some data was lost during network transfer" ) @@ -122,19 +126,19 @@ def load_dataset(dataset_name: str, **kwargs) -> Dataset: return Dataset(save_path, **kwargs) except Exception as exception: - if os.path.isfile(save_path): + if save_path is not None and os.path.isfile(save_path): os.remove(save_path) raise exception finally: - if os.path.isfile(save_path + _ARCHIVE_EXTENSION): + if save_path is not None and os.path.isfile(save_path + _ARCHIVE_EXTENSION): os.remove(save_path + _ARCHIVE_EXTENSION) def _init_dataset_if_downloaded(dataset_path: str, **kwargs) -> Dataset: saved_dataset_path_candidates = [ - p for p in glob(dataset_path + '*') + p for p in glob(dataset_path + '.*') if os.path.isfile(p) and not p.endswith(_ARCHIVE_EXTENSION) ] dataset = None diff --git a/topicnet/demos/README.md b/topicnet/demos/README.md index 0369a4d..a33f51a 100644 --- a/topicnet/demos/README.md +++ b/topicnet/demos/README.md @@ -30,3 +30,4 @@ This section provides demonstrations of how to use this library in NLP tasks. ---- P.S. All the guides are supposed to contain **working** examples of the library code. If you happen to find code that is no longer works, please write about it in the library issues. +We will try to resolve it as soon as possible and plan to include fixes in the nearest releases. diff --git a/topicnet/demos/Topic-Thetaless-Regularizer.ipynb b/topicnet/demos/Topic-Thetaless-Regularizer.ipynb new file mode 100644 index 0000000..002c559 --- /dev/null +++ b/topicnet/demos/Topic-Thetaless-Regularizer.ipynb @@ -0,0 +1,1290 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Topic modeling without Theta distribution\n", + "\n", + "Based on unpublished (yet) research by Ilya Irhin, Victor Bulatov, Konstantin Vorontsov\n", + "\n", + "One of the interpretations of topic modeling is this:\n", + "\n", + "\\begin{equation}\n", + "L(\\Phi, \\Theta) + R(\\Phi, \\Theta) \\to \\max_{\\Phi, \\Theta},\n", + "\\end{equation}\n", + "\n", + "Once the inference is directly formulated as an optimization problem, one can ask: is it possible to reduce the number f inferred parameters? \n", + "\n", + "In the practice, word-topic matrix $(\\Phi)$ has a larger importance than document-topic matrix $(\\Theta)$; so we can replace the original optimization problem with a different one:\n", + "\n", + "\\begin{equation}\n", + "L(\\Phi, f(\\Phi) ) + R(\\Phi, f(\\Phi) ) \\to \\max_{\\Phi},\n", + "\\end{equation}\n", + "\n", + "Some math later, it turns out that the \"formally correct\" optimization algorithm for this problem is different from conventional EM algorithm, and has some surprising advantages. Technically speaking, these formulas require a different iteration process (which is implemented here: https://github.com/ilirhin/python_artm), but the formulas look similar enough to a custom $\\Phi$ regularizer (if one sets `num_document_passes=1` in BigARTM)\n", + "\n", + "This notebook serves as a proof-of-concept for this idea.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "sys.path.insert(0, '../..')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import requests\n", + "import scipy.sparse\n", + "\n", + "from numba import jit\n", + "from numpy import random\n", + "from matplotlib import pyplot as plt\n", + "\n", + "import artm\n", + "\n", + "from topicnet.cooking_machine.dataset import Dataset\n", + "from topicnet.cooking_machine.models import (\n", + " BaseScore as BaseTopicNetScore,\n", + " TopicModel\n", + ")\n", + "from topicnet.cooking_machine.models.base_regularizer import BaseRegularizer\n", + "from topicnet.cooking_machine.models.topic_prior_regularizer import TopicPriorRegularizer\n", + "from topicnet.cooking_machine.models.thetaless_regularizer import (\n", + " ThetalessRegularizer,\n", + " dataset2sparse_matrix,\n", + ")\n", + "\n", + "from topicnet.viewers.top_tokens_viewer import TopTokensViewer\n", + "from topicnet.viewers.top_documents_viewer import TopDocumentsViewer" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'@footer', '@header', '@word'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataset = Dataset(\n", + " \"/data/datasets/20_News_dataset/20NG_BOW.csv\",\n", + " internals_folder_path=\"./20NG_internals\",\n", + ")\n", + "\n", + "dataset.get_possible_modalities()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "MAIN_MODALITY = \"@word\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preparing the coherence score" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Top-token coherence (co-occurrences are calculated using the same corpus)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "def calc_doc_occurrences(dataset, modality):\n", + " \"\"\"\n", + " :param n_dw_matrix: sparse document-word matrix, shape is D x W\n", + " :return: sparse matrix of co-occurrences\n", + "\n", + " doc_occurrences[w1, w2] = the number of the documents\n", + " where there are w1 and w2\n", + " \"\"\"\n", + " n_dw_matrix = dataset2sparse_matrix(dataset, modality, modalities_to_use=[modality])\n", + " matrix = (scipy.sparse.csc_matrix(n_dw_matrix) > 0).astype(int)\n", + " co_occurrences = matrix.T * matrix\n", + "\n", + " return co_occurrences.diagonal(), co_occurrences\n", + "\n", + "\n", + "def create_pmi_top_function(\n", + " doc_occurrences, doc_co_occurrences,\n", + " documents_number, top_sizes,\n", + " co_occurrences_smooth=1.\n", + "):\n", + " \"\"\"\n", + " :param doc_occurrences: array of doc occurrences of words\n", + " :param doc_co_occurrences: sparse matrix of doc co-occurrences of words\n", + " :param documents_number: number of the documents\n", + " :param top_sizes: list of top values to calculate top-pmi for\n", + " :param co_occurrences_smooth: constant to smooth co-occurrences in log\n", + " :return: function which takes phi and theta and returns\n", + " pair of two arrays: pmi-s of the tops and ppmi-s of the tops\n", + "\n", + " pmi[i] - pmi(top of size top_sizes[i])\n", + " ppmi[i] - ppmi(top of size top_sizes[i])\n", + "\n", + " pmi(words) = sum_{u in words, v in words, u != v}\n", + " log(\n", + " (doc_co_occurrences[u, v] * documents_number + co_occurrences_smooth)\n", + " / doc_occurrences[u] / doc_occurrences[v]\n", + " )\n", + "\n", + " ppmi(words) = sum_{u in words, v in words, u != v}\n", + " max(log(\n", + " (doc_co_occurrences[u, v] * documents_number + co_occurrences_smooth)\n", + " / doc_occurrences[u] / doc_occurrences[v]\n", + " ), 0)\n", + "\n", + " \"\"\"\n", + " def func(phi):\n", + " T, W = phi.shape\n", + " max_top_size = max(top_sizes)\n", + " pmi, ppmi = np.zeros(max_top_size), np.zeros(max_top_size)\n", + " tops = np.argpartition(phi, -max_top_size, axis=1)[:, -max_top_size:]\n", + " \n", + " for t in range(T):\n", + " top = sorted(tops[t], key=lambda w: - phi[t, w])\n", + " co_occurrences = doc_co_occurrences[top, :][:, top].todense()\n", + " occurrences = doc_occurrences[top]\n", + " values = np.log(\n", + " (co_occurrences * documents_number + co_occurrences_smooth)\n", + " / (occurrences[:, np.newaxis] * occurrences[np.newaxis, :] + co_occurrences_smooth)\n", + " )\n", + " diag = np.diag_indices(len(values))\n", + " values.cumsum(axis=0).cumsum(axis=1)[diag] - values[diag].cumsum()\n", + " pmi += np.array(\n", + " values.cumsum(axis=0).cumsum(axis=1)[diag] - values[diag].cumsum()\n", + " ).ravel()\n", + "\n", + " values[values < 0.] = 0.\n", + " ppmi += np.array(\n", + " values.cumsum(axis=0).cumsum(axis=1)[diag] - values[diag].cumsum()\n", + " ).ravel()\n", + " \n", + " sizes = np.arange(2, max_top_size + 1)\n", + " pmi[1:] /= (T * sizes * (sizes - 1))\n", + " ppmi[1:] /= (T * sizes * (sizes - 1))\n", + " indices = np.array(top_sizes) - 1\n", + " \n", + " return pmi[indices], ppmi[indices]\n", + "\n", + " return func" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 9.41 s, sys: 1.27 s, total: 10.7 s\n", + "Wall time: 10.6 s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "occurences, co_occurences = calc_doc_occurrences(dataset, MAIN_MODALITY)\n", + "\n", + "calc_pmi = create_pmi_top_function(\n", + " occurences, co_occurences,\n", + " dataset.get_dataset().shape[0], [20],\n", + " co_occurrences_smooth=1e-2,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "class TopTokenCoherence(BaseTopicNetScore):\n", + " def __init__(self, func):\n", + " super().__init__()\n", + "\n", + " self.calc_pmi = func\n", + "\n", + " def call(self, model: TopicModel):\n", + " values = self.calc_pmi(model.get_phi_dense()[0].T)\n", + "\n", + " return values[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model training" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "thetaless_reg = ThetalessRegularizer(\n", + " name='thetaless', \n", + " tau=1,\n", + " dataset=dataset, \n", + " modality=MAIN_MODALITY,\n", + ")\n", + "\n", + "sparse_reg = artm.regularizers.SmoothSparsePhiRegularizer(\n", + " name='sparse_reg', \n", + " tau=-0.5,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def create_model(dataset, modality: str):\n", + " model = artm.ARTM(num_topics=30, num_document_passes=1, class_ids={modality: 1.0})\n", + " model.initialize(dataset.get_dictionary())\n", + " \n", + " model.scores.add(\n", + " artm.scores.PerplexityScore(name='perplexity', class_ids=[modality])\n", + " )\n", + " model.scores.add(\n", + " artm.scores.SparsityPhiScore(name='sparsity', class_id=modality)\n", + " )\n", + " model.scores.add(\n", + " artm.scores.TopicKernelScore(name='kernel', class_id=modality)\n", + " )\n", + " \n", + " return model" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "NUM_FIT_ITERATIONS = 50" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 4min 7s, sys: 9.59 s, total: 4min 16s\n", + "Wall time: 1min 30s\n" + ] + } + ], + "source": [ + "plsa_model = TopicModel(\n", + " artm_model=create_model(dataset, MAIN_MODALITY), \n", + " model_id='plsa_model',\n", + " custom_scores={\n", + " \"coherence_ppmi\": TopTokenCoherence(calc_pmi)\n", + " }\n", + ")\n", + "\n", + "%time plsa_model._fit(dataset.get_batch_vectorizer(), NUM_FIT_ITERATIONS)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 6min 4s, sys: 37.7 s, total: 6min 42s\n", + "Wall time: 3min 18s\n" + ] + } + ], + "source": [ + "thetaless_model = TopicModel(\n", + " artm_model=create_model(dataset, MAIN_MODALITY), \n", + " model_id='thetaless_model',\n", + " custom_regularizers={\n", + " thetaless_reg.name: thetaless_reg\n", + " },\n", + " custom_scores={\n", + " \"coherence_ppmi\": TopTokenCoherence(calc_pmi)\n", + " }\n", + ")\n", + "\n", + "%time thetaless_model._fit(dataset.get_batch_vectorizer(), NUM_FIT_ITERATIONS)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 4min 21s, sys: 27.5 s, total: 4min 48s\n", + "Wall time: 1min 45s\n" + ] + } + ], + "source": [ + "artm_model = create_model(dataset, MAIN_MODALITY)\n", + "artm_model.regularizers.add(sparse_reg)\n", + "\n", + "sparse_model = TopicModel(\n", + " artm_model=artm_model, \n", + " model_id='sparse_model',\n", + " custom_scores={\"coherence_ppmi\": TopTokenCoherence(calc_pmi)}\n", + ")\n", + "\n", + "%time sparse_model._fit(dataset.get_batch_vectorizer(), NUM_FIT_ITERATIONS)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 7min 1s, sys: 1min 20s, total: 8min 21s\n", + "Wall time: 3min 49s\n" + ] + } + ], + "source": [ + "artm_model = create_model(dataset, MAIN_MODALITY)\n", + "artm_model.regularizers.add(sparse_reg)\n", + "\n", + "sparse_thetaless_model = TopicModel(\n", + " artm_model=artm_model, \n", + " model_id='sparse_thetaless_model',\n", + " custom_regularizers={\n", + " thetaless_reg.name: thetaless_reg\n", + " },\n", + " custom_scores={\n", + " \"coherence_ppmi\": TopTokenCoherence(calc_pmi)\n", + " }\n", + ")\n", + "\n", + "%time sparse_thetaless_model._fit(dataset.get_batch_vectorizer(), NUM_FIT_ITERATIONS)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quality Visualizing" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_metric(view_name, value_name, title_name):\n", + " plt.figure(figsize=(14,8))\n", + "\n", + " plt.subplot(2,2,1)\n", + " values = np.array(getattr(thetaless_model.score_tracker[view_name], value_name))\n", + " med = np.median(values)\n", + " values[values > 5 * med] = np.nan\n", + " last_thetaless = values[-1]\n", + "\n", + " plt.title(f'{title_name} with Thetaless regularizer')\n", + " plt.plot(values[1:])\n", + "\n", + " plt.subplot(2,2,2)\n", + " values = np.array(getattr(plsa_model.score_tracker[view_name], value_name))\n", + " med = np.median(values)\n", + " values[values > 5 * med] = np.nan\n", + " last_plsa = values[-1]\n", + " \n", + " plt.title(f'{title_name} without any regularization')\n", + " plt.plot(values[1:])\n", + " \n", + " plt.subplot(2,2,3)\n", + " values = np.array(getattr(sparse_model.score_tracker[view_name], value_name))\n", + " med = np.median(values)\n", + " values[values > 5 * med] = np.nan\n", + " last_sparse = values[-1]\n", + "\n", + " plt.title(f'{title_name} with sparsity regularizer')\n", + " plt.plot(values[1:])\n", + " \n", + " plt.subplot(2,2,4)\n", + " values = np.array(getattr(sparse_thetaless_model.score_tracker[view_name], value_name))\n", + " med = np.median(values)\n", + " values[values > 5 * med] = np.nan\n", + " last_sparse_thetaless = values[-1]\n", + "\n", + " plt.title(f'{title_name} with both regularizers')\n", + " plt.plot(values[1:])\n", + " \n", + " print(\n", + " f'Last values:\\n\\t'\n", + " f'thetaless – {last_thetaless}\\n\\t'\n", + " f'plsa – {last_plsa}\\n\\t'\n", + " f'sparse – {last_sparse}\\n\\t'\n", + " f'sparse_thetaless – {last_sparse_thetaless}\\n\\t' \n", + " )\n", + "\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last values:\n", + "\tthetaless – 1787.9063720703125\n", + "\tplsa – 1876.0892333984375\n", + "\tsparse – 1317.7843017578125\n", + "\tsparse_thetaless – 1274.2459716796875\n", + "\t\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
      " + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_metric(view_name='perplexity', value_name='value', title_name='Perplexity')" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last values:\n", + "\tthetaless – 0.93656986951828\n", + "\tplsa – 0.8662593960762024\n", + "\tsparse – 0.9919413924217224\n", + "\tsparse_thetaless – 0.9935382604598999\n", + "\t\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
      " + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_metric(view_name='sparsity', value_name='value', title_name='Sparsity')" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last values:\n", + "\tthetaless – 0.7902413010597229\n", + "\tplsa – 0.7366632223129272\n", + "\tsparse – 0.8241350650787354\n", + "\tsparse_thetaless – 0.8964435458183289\n", + "\t\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
      " + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_metric(view_name='kernel', value_name='average_contrast', title_name='Kernel Contrast')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last values:\n", + "\tthetaless – 0.9562450051307678\n", + "\tplsa – 0.8865759372711182\n", + "\tsparse – 0.9308170080184937\n", + "\tsparse_thetaless – 0.9824467897415161\n", + "\t\n" + ] + }, + { + "data": { + "image/png": "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myj47eslvPzeHr51yXROHKfKSEREjsySkkpOKcgmKUHVs4jI4dKe8yi9WVLJz57fwCXHj+ZTp48POhwREYlRO2uaKN5Tz5mT1Q1NRORIKLE5CnvqmvnykyspGJ7OTz52PGa6rkZERI7M6xvLATh36oiAIxERiU26xuYIhcKOrzy5kvqWNh6/YTZDklWUIiJy5F7bWM6ozBSmjhwSdCgiIjFJLTZH6L6Xili6aS93z53JsaMygw5HRERiWHsozKKiCs6ZmqPWfxGRI6TE5ggsLq7ggZeL+OhJeVxZOC7ocEREJMatLqumrrld3dBERI6CEpvDtKeumZvnrWJiTjrfmzsz6HBERGQAeG1DOXEGZ03WjTlFRI6ULgw5DKGw46tPraKu2buuJl3X1YiISBS8trGcE8dlMTQtMehQRERillpsDsODrxSzuLiSu+fO0HU1IiISFXsbWlmzvUbd0EREjpISmx5auqmSX764kctOHKPrakREJGoWFZXjHJx7TG7QoYiIxDQlNj1QUd/CV/z71Xz/I8dpxBoREYma1zaWMywtkePyhgYdiohITNNFIocQ9q+rqW5q49HP6H41IiISPeGw4/WNFZw1JZf4OJ00ExE5GmqxOYSHXt/EoqIKvvPh6Uwfo+tqREQketbvqqWivoVzp6obmojI0VJicxDrd9byixc28MHjRnHN7PygwxERkQHmtY3lAJwzRcM8i4gcLSU23WhtD/O1+asZmprE9y/TdTUiIhJ9r20oZ/roTEZkpgQdiohIzFNi040HXi5i/c5afvTR48hOTwo6HBERGWDqmttYsbWKc9QNTUQkKpTYHMCq0mp+/WoJl588lgunjww6HBERGYDeLKmkPex0fY2ISJT0KLExszlmtsHMis3sjgO8/jUzW2dma8zsJTMbH/1Q+0ZzW4hb569iZEYyd354etDhiIjIAPXaxnLSk+I5efywoEMRERkQDpnYmFk88CBwMTAduNrMuh7xrwQKnXPHA88AP412oH3lZ89voKS8gZ9efgKZKYlBhyMiIgOQc47XNpZz+qQckhLUeUJEJBp6sjedDRQ75zY551qBecDcyAWcc6845xr9p0uBsdENs2+8tamSPyzezCdPG89ZGqFGRER6yaaKBsqqmjj3GHVDExGJlp4kNnlAacTzMn9ed64H/nk0QQWhoaWd255ZTX52Gndc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\n", + "text/plain": [ + "
      " + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_metric(view_name='kernel', value_name='average_purity', title_name='Kernel purity')" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last values:\n", + "\tthetaless – 4043.699951171875\n", + "\tplsa – 4254.63330078125\n", + "\tsparse – 663.4000244140625\n", + "\tsparse_thetaless – 613.4666748046875\n", + "\t\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
      " + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_metric(view_name='kernel', value_name='average_size', title_name='Kernel size')" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Last values:\n", + "\tthetaless – 1.6930015502023317\n", + "\tplsa – 1.5415081854659372\n", + "\tsparse – 1.4994938821199244\n", + "\tsparse_thetaless – 1.6034352180930274\n", + "\t\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
      " + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot_metric(view_name='coherence_ppmi', value_name='value', title_name='Top-20 Coherence')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coherence evaluation on an external corpus (with Palmetto)\n", + "\n", + "Unfortunately, the service isn't available right now" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def eval_top_words(name, model, num_top_tokens=10):\n", + " ttv = TopTokensViewer(model, method='blei', num_top_tokens=num_top_tokens)\n", + " output = ttv.view()\n", + " S = \"http://palmetto.aksw.org/palmetto-webapp/service/{}?words=\".format(\"umass\")\n", + " topic_score = []\n", + "\n", + " for key, modalities in output.items():\n", + " if 'topic' in key:\n", + " words = list(output[key]['@default_class'].keys())\n", + "\n", + " print(S + \"%20\".join(words))\n", + "\n", + " result = requests.get(S + \"%20\".join(words)).text\n", + " topic_score += [float(result)]\n", + "\n", + " print(round(np.mean(topic_score), 2), round(np.median(topic_score), 2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# eval_top_words('thetaless', thetaless_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# eval_top_words('plsa', plsa_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# eval_top_words('sparse', sparse_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# eval_top_words('sparse_thetaless', sparse_thetaless_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Let's look at topics" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "def print_top_words(name, model, num_top_tokens=10):\n", + " ttv = TopTokensViewer(model, method='blei', num_top_tokens=num_top_tokens)\n", + " output = ttv.view()\n", + "\n", + " for key, modalities in output.items():\n", + " if 'topic' in key:\n", + " print(', '.join(output[key][MAIN_MODALITY].keys()))\n", + " print()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "space, earth, launch, project, mission, nasa, satellite, station, orbit, degree\n", + "\n", + "car, light, bike, ground, engine, front, ride, mile, road, wire\n", + "\n", + "game, play, team, win, player, hit, season, lose, fan, league\n", + "\n", + "system, mail, send, list, software, computer, user, address, email, package\n", + "\n", + "find, question, many, post, read, write, name, follow, book, must\n", + "\n", + "also, high, power, large, small, low, offer, control, full, add\n", + "\n", + "key, information, chip, public, message, encryption, technology, security, clipper, algorithm\n", + "\n", + "run, work, window, thank, problem, please, help, set, appreciate, driver\n", + "\n", + "use, need, number, line, order, buy, price, sell, require, check\n", + "\n", + "drive, card, disk, mac, board, hard, mb, monitor, ms, memory\n", + "\n", + "study, drug, effect, medical, disease, patient, doctor, food, ed, health\n", + "\n", + "one, get, think, good, seem, really, put, hear, probably, much\n", + "\n", + "x, output, function, entry, widget, sun, motif, define, return, include\n", + "\n", + "believe, claim, fact, true, exist, evidence, agree, matter, argument, religion\n", + "\n", + "kill, armenian, war, armenians, turkish, attack, history, dead, jews, russian\n", + "\n", + "first, two, second, end, three, lead, hold, save, close, head\n", + "\n", + "time, new, year, come, day, call, last, old, great, still\n", + "\n", + "report, university, 1993, research, national, news, april, center, page, school\n", + "\n", + "state, government, law, gun, mr, president, country, us, house, member\n", + "\n", + "make, point, even, case, mean, keep, long, actually, consider, every\n", + "\n", + "1, 2, 0, 3, 4, 5, 6, 25, 10, 7\n", + "\n", + "say, people, right, never, life, child, man, person, live, feel\n", + "\n", + "would, know, like, could, tell, give, something, anyone, someone, anything\n", + "\n", + "8, 5, e, 7, 6, b, 9, c, 4, h\n", + "\n", + "g, w, r, q, p, v, k, u, z, n\n", + "\n", + "go, see, well, take, want, look, thing, way, try, back\n", + "\n", + "file, program, image, version, color, display, format, code, available, server\n", + "\n", + "god, jesus, church, christian, israel, bible, love, christ, sin, word\n", + "\n", + "ax, max, g9v, b8f, q, a86, 7, 145, 1d9, n\n", + "\n", + "may, also, since, part, group, change, however, problem, etc, cause\n", + "\n" + ] + } + ], + "source": [ + "print_top_words('thetaless', thetaless_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "space, launch, earth, project, mission, nasa, satellite, orbit, system, station\n", + "\n", + "car, bike, turn, power, light, ground, engine, back, front, ride\n", + "\n", + "year, game, last, win, play, lose, team, hit, player, guy\n", + "\n", + "system, mail, software, computer, list, user, pc, support, mac, package\n", + "\n", + "question, post, read, find, many, book, write, different, answer, article\n", + "\n", + "also, high, work, small, offer, large, time, several, low, interested\n", + "\n", + "key, public, information, message, chip, encryption, system, security, clipper, private\n", + "\n", + "window, run, thank, anyone, work, problem, help, please, use, get\n", + "\n", + "use, number, call, line, need, sell, price, may, order, phone\n", + "\n", + "drive, card, disk, hard, board, system, mb, speed, fast, scsi\n", + "\n", + "effect, cause, drug, study, medical, disease, patient, increase, doctor, food\n", + "\n", + "one, think, get, seem, good, really, hear, come, though, talk\n", + "\n", + "x, server, output, include, file, application, entry, widget, sun, program\n", + "\n", + "believe, people, true, exist, argument, religion, fact, matter, agree, belief\n", + "\n", + "mr, kill, people, armenian, q, armenians, president, turkish, say, attack\n", + "\n", + "first, right, two, point, second, give, keep, yes, end, pass\n", + "\n", + "go, could, new, time, get, ask, great, come, old, call\n", + "\n", + "report, university, 1993, april, national, research, page, include, school, center\n", + "\n", + "state, government, gun, law, country, control, us, member, crime, weapon\n", + "\n", + "make, even, may, case, consider, fire, less, mean, long, force\n", + "\n", + "25, db, team, play, period, hockey, goal, la, 10, 1st\n", + "\n", + "say, reason, claim, life, even, never, child, person, man, people\n", + "\n", + "would, know, like, something, tell, someone, good, bad, remember, enough\n", + "\n", + "1, 2, 0, 3, 4, 5, 6, 7, 8, 9\n", + "\n", + "w, g, r, b, c, k, v, p, u, e\n", + "\n", + "well, see, want, thing, look, try, go, make, take, let\n", + "\n", + "file, image, program, version, available, color, format, display, ftp, copy\n", + "\n", + "god, jesus, israel, word, jews, christ, bible, man, love, sin\n", + "\n", + "ax, max, q, g, r, b8f, g9v, 7, p, a86\n", + "\n", + "since, group, problem, etc, may, find, also, result, use, form\n", + "\n" + ] + } + ], + "source": [ + "print_top_words('plsa', plsa_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "space, center, science, research, launch, design, project, cost, material, mission\n", + "\n", + "get, good, back, turn, buy, need, stuff, little, bike, anybody\n", + "\n", + "year, game, last, team, play, win, player, lose, guy, next\n", + "\n", + "system, computer, software, mail, machine, user, mac, base, memory, monitor\n", + "\n", + "question, post, read, write, answer, must, article, different, message, bit\n", + "\n", + "number, also, high, small, large, several, open, case, use, type\n", + "\n", + "key, information, chip, use, group, provide, may, encryption, security, clipper\n", + "\n", + "use, work, problem, run, please, anyone, help, thank, need, set\n", + "\n", + "use, new, call, price, sell, offer, check, include, interested, box\n", + "\n", + "drive, card, hard, disk, power, speed, board, sound, driver, mb\n", + "\n", + "effect, condition, hit, soon, disease, patient, compare, mile, low, year\n", + "\n", + "think, one, seem, much, hear, though, idea, see, really, put\n", + "\n", + "x, list, send, line, copy, include, name, source, output, code\n", + "\n", + "believe, people, true, church, christian, argument, fact, israel, jews, agree\n", + "\n", + "kill, people, mr, president, armenian, child, armenians, q, attack, house\n", + "\n", + "first, second, two, name, body, save, three, third, face, mark\n", + "\n", + "go, take, come, could, time, start, leave, week, around, ago\n", + "\n", + "book, report, university, 1993, national, news, april, drug, page, turkish\n", + "\n", + "state, right, law, gun, government, people, issue, control, country, support\n", + "\n", + "point, long, yes, feel, make, less, may, never, mean, time\n", + "\n", + "10, 15, 12, 20, db, 11, 13, period, series, 30\n", + "\n", + "say, even, one, reason, right, claim, make, evidence, mind, perhaps\n", + "\n", + "would, know, like, tell, something, someone, car, really, wrong, remember\n", + "\n", + "1, 2, 0, 3, 4, 5, 6, 7, 8, 25\n", + "\n", + "g, w, r, b, p, c, q, n, v, e\n", + "\n", + "want, look, well, make, see, thing, try, way, let, like\n", + "\n", + "file, window, image, program, version, color, application, available, display, format\n", + "\n", + "god, man, word, jesus, life, world, christ, death, war, love\n", + "\n", + "ax, max, q, b8f, g9v, a86, 7, 145, 1d9, r\n", + "\n", + "find, since, part, many, cause, result, also, group, people, make\n", + "\n" + ] + } + ], + "source": [ + "print_top_words('sparse', sparse_model)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "space, earth, science, design, center, launch, field, project, material, mission\n", + "\n", + "get, good, back, power, buy, around, big, turn, stuff, light\n", + "\n", + "year, game, last, play, team, win, player, lose, guy, season\n", + "\n", + "system, mail, software, support, computer, machine, user, pc, standard, contact\n", + "\n", + "question, post, read, write, follow, must, ask, different, bit, note\n", + "\n", + "also, number, high, source, large, several, small, add, build, require\n", + "\n", + "key, information, chip, provide, phone, use, service, order, encryption, technology\n", + "\n", + "use, work, problem, need, run, please, help, thank, anyone, window\n", + "\n", + "new, call, include, price, etc, sell, offer, check, pay, cost\n", + "\n", + "drive, card, disk, driver, hard, speed, board, mb, fast, ms\n", + "\n", + "effect, condition, medical, soon, disease, compare, patient, mile, average, ed\n", + "\n", + "one, think, much, seem, really, still, put, hear, sure, probably\n", + "\n", + "x, send, list, line, copy, 25, output, return, section, request\n", + "\n", + "believe, people, true, fact, church, agree, argument, matter, israel, jews\n", + "\n", + "kill, mr, president, armenian, fire, child, armenians, attack, house, press\n", + "\n", + "first, two, second, lead, three, great, body, save, hit, john\n", + "\n", + "go, time, take, come, day, start, old, leave, next, week\n", + "\n", + "book, report, university, 1993, study, news, national, april, drug, page\n", + "\n", + "state, government, law, issue, gun, control, public, country, war, people\n", + "\n", + "may, point, case, mean, long, show, keep, course, actually, enough\n", + "\n", + "0, 10, 20, 15, 12, 14, 11, 16, 13, db\n", + "\n", + "say, even, right, people, never, reason, every, claim, person, live\n", + "\n", + "would, know, like, could, tell, give, something, someone, car, anything\n", + "\n", + "1, 2, 3, 4, 5, 6, 8, 7, 9, 27\n", + "\n", + "g, w, r, q, p, c, e, b, n, v\n", + "\n", + "make, see, well, want, look, thing, way, try, ca, let\n", + "\n", + "file, program, image, version, color, application, display, available, code, format\n", + "\n", + "god, man, word, life, world, jesus, christian, bible, love, christ\n", + "\n", + "ax, max, b8f, g9v, a86, q, 145, 1d9, 7, bhj\n", + "\n", + "find, many, since, part, group, however, cause, result, allow, free\n", + "\n" + ] + } + ], + "source": [ + "print_top_words('sparse_thetaless', sparse_thetaless_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Last coherence values (using different number of top tokens)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The plots above show how the coherence (PPMI) of top 20 tokens evolves. In addition, we can look at the last PMI and PPMI values using different sizes of top-token list (5, 10, 20, 50, 100 words)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "occurences, co_occurences = calc_doc_occurrences(dataset, MAIN_MODALITY)\n", + "\n", + "calc_pmi = create_pmi_top_function(\n", + " occurences, co_occurences,\n", + " dataset.get_dataset().shape[0], [5, 10, 20, 50, 100],\n", + " co_occurrences_smooth=1e-2\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([1.21499297, 1.41841121, 1.4773908 , 1.64972323, 1.70756978]),\n", + " array([1.31236076, 1.47163629, 1.54150819, 1.69634847, 1.80704321]))" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calc_pmi(plsa_model.get_phi_dense()[0].T)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([1.57331496, 1.62475971, 1.65381981, 1.86711067, 1.87356811]),\n", + " array([1.57331496, 1.62499837, 1.69300155, 1.94859555, 2.09445053]))" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calc_pmi(thetaless_model.get_phi_dense()[0].T)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([1.35890928, 1.35700836, 1.48383579, 1.59996205, 1.60108423]),\n", + " array([1.35890928, 1.37848896, 1.49985511, 1.65918859, 1.78920465]))" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calc_pmi(sparse_model.get_phi_dense()[0].T)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([1.42275237, 1.48636823, 1.59154109, 1.75142554, 1.55162446]),\n", + " array([1.42275237, 1.48636823, 1.60343522, 1.83606053, 1.9786894 ]))" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calc_pmi(sparse_thetaless_model.get_phi_dense()[0].T)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: Also, let's take a look at train + test together, instead of relying on train only:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We observed several interesting things.\n", + "\n", + "1) the regularizer improves coherence and sparsity without explicitly optimizing for it\n", + "\n", + "2) The regularizer moves common but uninformative words away from \"informative\" topics. Compare the 3rd topic of PLSA: \n", + "\n", + "`year, game, last, win, play, lose, team, hit, player, guy`\n", + "\n", + "with the corresponding topic of Thetaless:\n", + "\n", + "`game, play, team, win, player, hit, season, lose, fan, league`\n", + "\n", + "If we look at 20th topic, we will see the reverse trend.\n", + "\n", + "The topic of PLSA is uninformative with a few meaningful words thrown in:\n", + "\n", + "`make, even, may, case, consider, fire, less, mean, long, force`\n", + "\n", + "The corresponding topic of Thetaless consist of common parlance almost entirely, without any signal lost in noise.\n", + "\n", + "`make, point, even, case, mean, keep, long, actually, consider, every`\n", + "\n", + "It should be noted that we had not specified the usual distincton between specific and background topics, the separation here is purely emergent.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "topicnet", + "language": "python", + "name": "topicnet" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/topicnet/demos/Visualizing-Your-Model-Documents.ipynb b/topicnet/demos/Visualizing-Your-Model-Documents.ipynb index e091dd2..1a60d57 100644 --- a/topicnet/demos/Visualizing-Your-Model-Documents.ipynb +++ b/topicnet/demos/Visualizing-Your-Model-Documents.ipynb @@ -26,10 +26,12 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ + "import os\n", + "\n", "import colorlover as cl\n", "import plotly.graph_objs as go\n", "\n", @@ -4226,14 +4228,14 @@ " \n", " " ], "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -4242,7 +4244,7 @@ } ], "source": [ - "IFrame(src='topic_clusters.html', width=900, height=700)" + "IFrame(src=os.path.join('images', 'topic_clusters.html'), width=900, height=700)" ] }, { @@ -36598,9 +36600,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Environment (conda_artm10)", + "display_name": "topicnet", "language": "python", - "name": "conda_artm10" + "name": "topicnet" }, "language_info": { "codemirror_mode": { @@ -36612,7 +36614,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.6.10" } }, "nbformat": 4, diff --git a/topicnet/demos/topic_clusters.html b/topicnet/demos/images/topic_clusters.html similarity index 100% rename from topicnet/demos/topic_clusters.html rename to topicnet/demos/images/topic_clusters.html diff --git a/topicnet/demos/topic_thetaless_regularizer.ipynb b/topicnet/demos/topic_thetaless_regularizer.ipynb deleted file mode 100644 index 11438f9..0000000 --- a/topicnet/demos/topic_thetaless_regularizer.ipynb +++ /dev/null @@ -1,1512 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Topic modeling without Theta distribution\n", - "\n", - "Based on unpublished (yet) research by Ilya Irhin, Victor Bulatov, Konstantin Vorontsov\n", - "\n", - "One of the interpretations of topic modeling is this:\n", - "\n", - "\\begin{equation}\n", - "L(\\Phi, \\Theta) + R(\\Phi, \\Theta) \\to \\max_{\\Phi, \\Theta},\n", - "\\end{equation}\n", - "\n", - "Once the inference is directly formulated as an optimization problem, one can ask: is it possible to reduce the number f inferred parameters? \n", - "\n", - "In the practice, word-topic matrix $(\\Phi)$ has a larger importance than document-topic matrix $(\\Theta)$; so we can replace the original optimization problem with a different one:\n", - "\n", - "\\begin{equation}\n", - "L(\\Phi, f(\\Phi) ) + R(\\Phi, f(\\Phi) ) \\to \\max_{\\Phi},\n", - "\\end{equation}\n", - "\n", - "Some math later, it turns out that the \"formally correct\" optimization algorithm for this problem is different from conventional EM algorithm, and has some surprising advantages. Technically speaking, these formulas require a different iteration process (which is implemented here: https://github.com/ilirhin/python_artm), but the formulas look similar enough to a custom $\\Phi$ regularizer (if one sets `num_document_passes=1` in BigARTM)\n", - "\n", - "This notebook serves as a proof-of-concept for this idea.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from collections import Counter\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "from numpy import random\n", - "from matplotlib import pyplot as plt\n", - "from numba import jit\n", - "import scipy.sparse\n", - "\n", - "import artm\n", - "\n", - "from topicnet.cooking_machine.dataset import Dataset\n", - "\n", - "from topicnet.cooking_machine.models import TopicModel\n", - "from topicnet.cooking_machine.models.base_regularizer import BaseRegularizer\n", - "from topicnet.cooking_machine.models.topic_prior_regularizer import TopicPriorRegularizer\n", - "\n", - "from topicnet.viewers.top_tokens_viewer import TopTokensViewer\n", - "from topicnet.viewers.top_documents_viewer import TopDocumentsViewer" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from topicnet.cooking_machine.models.thetaless_regularizer import ThetalessRegularizer\n", - "\n", - "# TopicPriorRegularizer are used here to make Phi sparse\n", - "# usually, we use an artm.SmoothSparsePhiRegularizer for this, but then it makes the performance comparision \"unfair\"\n", - "\n", - "from topicnet.cooking_machine.models.topic_prior_regularizer import TopicPriorRegularizer\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# the definition of Thetaless regularizer\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Preparing the dataset" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "import ast\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def csv2sparse_matrix(train_df, token_to_id=None, ignore_unknown_tokens=False):\n", - " if token_to_id is None:\n", - " token_to_id = dict()\n", - " train_row, train_col, train_data = [], [], []\n", - " for row_num, r in enumerate(train_df.iterrows()):\n", - " bag_of_words = Counter()\n", - " for token, _ in ast.literal_eval(r[1].tokenized):\n", - " token_id = token_to_id.get(token)\n", - " if token_id is None:\n", - " if ignore_unknown_tokens:\n", - " continue\n", - " token_id = len(token_to_id)\n", - " token_to_id[token] = token_id\n", - " bag_of_words[token_id] += 1\n", - "\n", - " for token_id, count in bag_of_words.items():\n", - " train_row.append(row_num)\n", - " train_col.append(token_id)\n", - " train_data.append(count)\n", - "\n", - " train_n_dw_matrix = scipy.sparse.csr_matrix(\n", - " (train_data, (train_row, train_col)), \n", - " shape=(len(train_df), len(token_to_id))\n", - " )\n", - " return train_n_dw_matrix, token_to_id\n", - "\n", - "\n", - "train_df = pd.read_csv('/data_mil/datasets/20_News_dataset/train_preprocessed.csv')\n", - "train_n_dw_matrix, token_to_id = csv2sparse_matrix(train_df)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "test_df = pd.read_csv('/data_mil/datasets/20_News_dataset/train_preprocessed.csv')\n", - "test_n_dw_matrix, _ = csv2sparse_matrix(test_df, dict(token_to_id), True)\n", - "\n", - "vocabulary = {\n", - " token_id: token\n", - " for token, token_id in token_to_id.items()\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "train_batch_vectorizer = artm.BatchVectorizer(\n", - " data_format='bow_n_wd', \n", - " n_wd=train_n_dw_matrix.T, \n", - " vocabulary=vocabulary, \n", - " batch_size=100000\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "test_batch_vectorizer = artm.BatchVectorizer(\n", - " data_format='bow_n_wd', \n", - " n_wd=test_n_dw_matrix.T, \n", - " vocabulary=vocabulary, \n", - " batch_size=100000\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Обучение модели" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "def create_model():\n", - " model = artm.ARTM(num_topics=30, num_document_passes=1)\n", - " model.scores.add(artm.scores.PerplexityScore(name='perplexity'))\n", - " model.scores.add(artm.scores.PerplexityScore(\n", - " name='test_perplexity', \n", - " dictionary=test_batch_vectorizer.dictionary\n", - " ))\n", - " model.scores.add(artm.scores.SparsityPhiScore(name='sparsity'))\n", - " model.scores.add(artm.scores.TopicKernelScore(name='kernel'))\n", - " model.initialize(train_batch_vectorizer.dictionary)\n", - " return model\n", - "\n", - "\n", - "def fit(model):\n", - " model._fit(\n", - " dataset_trainable=train_batch_vectorizer, \n", - " num_iterations=50\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "thetaless_reg = ThetalessRegularizer(\n", - " name='thetaless', \n", - " tau=1, \n", - " n_dw_matrix=train_n_dw_matrix\n", - ")\n", - "\n", - "sparse_reg = TopicPriorRegularizer(\n", - " name='sparse_reg', \n", - " tau=-0.5, \n", - " beta=1,\n", - " num_topics=30\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Top-token coherence (co-occurrences are calculated using the same corpus)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "\n", - "def calc_doc_occurrences(n_dw_matrix):\n", - " \"\"\"\n", - " :param n_dw_matrix: sparse document-word matrix, shape is D x W\n", - " :return: sparse matrix of co-occurrences\n", - "\n", - " doc_occurrences[w1, w2] = the number of the documents\n", - " where there are w1 and w2\n", - " \"\"\"\n", - " matrix = (scipy.sparse.csc_matrix(n_dw_matrix) > 0).astype(int)\n", - " co_occurrences = matrix.T * matrix\n", - " return co_occurrences.diagonal(), co_occurrences\n", - "\n", - "\n", - "def create_pmi_top_function(\n", - " doc_occurrences, doc_co_occurrences,\n", - " documents_number, top_sizes,\n", - " co_occurrences_smooth=1.\n", - "):\n", - " \"\"\"\n", - " :param doc_occurrences: array of doc occurrences of words\n", - " :param doc_co_occurrences: sparse matrix of doc co-occurrences of words\n", - " :param documents_number: number of the documents\n", - " :param top_sizes: list of top values to calculate top-pmi for\n", - " :param co_occurrences_smooth: constant to smooth co-occurrences in log\n", - " :return: function which takes phi and theta and returns\n", - " pair of two arrays: pmi-s of the tops and ppmi-s of the tops\n", - "\n", - " pmi[i] - pmi(top of size top_sizes[i])\n", - " ppmi[i] - ppmi(top of size top_sizes[i])\n", - "\n", - " pmi(words) = sum_{u in words, v in words, u != v}\n", - " log(\n", - " (doc_co_occurrences[u, v] * documents_number + co_occurrences_smooth)\n", - " / doc_occurrences[u] / doc_occurrences[v]\n", - " )\n", - "\n", - " ppmi(words) = sum_{u in words, v in words, u != v}\n", - " max(log(\n", - " (doc_co_occurrences[u, v] * documents_number + co_occurrences_smooth)\n", - " / doc_occurrences[u] / doc_occurrences[v]\n", - " ), 0)\n", - "\n", - " \"\"\"\n", - " def func(phi):\n", - " T, W = phi.shape\n", - " max_top_size = max(top_sizes)\n", - " pmi, ppmi = np.zeros(max_top_size), np.zeros(max_top_size)\n", - " tops = np.argpartition(phi, -max_top_size, axis=1)[:, -max_top_size:]\n", - " for t in range(T):\n", - " top = sorted(tops[t], key=lambda w: - phi[t, w])\n", - " co_occurrences = doc_co_occurrences[top, :][:, top].todense()\n", - " occurrences = doc_occurrences[top]\n", - " values = np.log(\n", - " (co_occurrences * documents_number + co_occurrences_smooth)\n", - " / occurrences[:, np.newaxis]\n", - " / occurrences[np.newaxis, :]\n", - " )\n", - " diag = np.diag_indices(len(values))\n", - " values.cumsum(axis=0).cumsum(axis=1)[diag] - values[diag].cumsum()\n", - " pmi += np.array(\n", - " values.cumsum(axis=0).cumsum(axis=1)[diag] - values[diag].cumsum()\n", - " ).ravel()\n", - " values[values < 0.] = 0.\n", - " ppmi += np.array(\n", - " values.cumsum(axis=0).cumsum(axis=1)[diag] - values[diag].cumsum()\n", - " ).ravel()\n", - " sizes = np.arange(2, max_top_size + 1)\n", - " pmi[1:] /= (T * sizes * (sizes - 1))\n", - " ppmi[1:] /= (T * sizes * (sizes - 1))\n", - " indices = np.array(top_sizes) - 1\n", - " return pmi[indices], ppmi[indices]\n", - "\n", - " return func\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "occurences, co_occurences = calc_doc_occurrences(train_n_dw_matrix)\n", - "calc_pmi = create_pmi_top_function(\n", - " occurences, co_occurences,\n", - " test_n_dw_matrix.shape[0], [20],\n", - " co_occurrences_smooth=1.\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "from topicnet.cooking_machine.models import BaseScore as BaseTopicNetScore, TopicModel\n", - "\n", - "\n", - "class TopTokenCoherence(BaseTopicNetScore):\n", - " def __init__(self, func):\n", - " super().__init__()\n", - "\n", - " self.calc_pmi = func\n", - "\n", - " def call(self, model: TopicModel):\n", - " values = self.calc_pmi(model.get_phi_dense()[0].T)\n", - "\n", - " return values[1]\n" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 2min, sys: 17 s, total: 2min 17s\n", - "Wall time: 1min 42s\n" - ] - } - ], - "source": [ - "plsa_model = TopicModel(\n", - " artm_model=create_model(), \n", - " model_id='plsa_model',\n", - " custom_scores={\"coherence_ppmi\": TopTokenCoherence(calc_pmi)}\n", - ")\n", - "\n", - "%time fit(plsa_model)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 9min 50s, sys: 1min 1s, total: 10min 52s\n", - "Wall time: 2min 47s\n" - ] - } - ], - "source": [ - "thetaless_model = TopicModel(\n", - " artm_model=create_model(), \n", - " model_id='thetaless_model',\n", - " custom_regularizers={\n", - " thetaless_reg.name: thetaless_reg\n", - " },\n", - " custom_scores={\"coherence_ppmi\": TopTokenCoherence(calc_pmi)}\n", - ")\n", - "\n", - "%time fit(thetaless_model)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 3min 51s, sys: 1min 15s, total: 5min 6s\n", - "Wall time: 2min 33s\n" - ] - } - ], - "source": [ - "sparse_model = TopicModel(\n", - " artm_model=create_model(), \n", - " model_id='sparse_model',\n", - " custom_regularizers={\n", - " sparse_reg.name: sparse_reg\n", - " },\n", - " custom_scores={\"coherence_ppmi\": TopTokenCoherence(calc_pmi)}\n", - ")\n", - "\n", - "%time fit(sparse_model)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 10min 42s, sys: 1min 56s, total: 12min 38s\n", - "Wall time: 2min 47s\n" - ] - } - ], - "source": [ - "sparse_thetaless_model = TopicModel(\n", - " artm_model=create_model(), \n", - " model_id='sparse_thetaless_model',\n", - " custom_regularizers={\n", - " sparse_reg.name: sparse_reg,\n", - " thetaless_reg.name: thetaless_reg\n", - " },\n", - " custom_scores={\"coherence_ppmi\": TopTokenCoherence(calc_pmi)}\n", - ")\n", - "\n", - "%time fit(sparse_thetaless_model)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Quality Visualizing" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [], - "source": [ - "def plot_metric(view_name, value_name, title_name):\n", - " plt.figure(figsize=(14,8))\n", - "\n", - " plt.subplot(2,2,1)\n", - " values = np.array(getattr(thetaless_model.score_tracker[view_name], value_name))\n", - " med = np.median(values)\n", - " values[values > 5 * med] = np.nan\n", - " last_thetaless = values[-1]\n", - "\n", - " plt.title(f'{title_name} with Thetaless regularizer')\n", - " plt.plot(values[1:])\n", - "\n", - " plt.subplot(2,2,2)\n", - " values = np.array(getattr(plsa_model.score_tracker[view_name], value_name))\n", - " med = np.median(values)\n", - " values[values > 5 * med] = np.nan\n", - " last_plsa = values[-1]\n", - " \n", - " plt.title(f'{title_name} without any regularization')\n", - " plt.plot(values[1:])\n", - " \n", - " plt.subplot(2,2,3)\n", - " values = np.array(getattr(sparse_model.score_tracker[view_name], value_name))\n", - " med = np.median(values)\n", - " values[values > 5 * med] = np.nan\n", - " last_sparse = values[-1]\n", - "\n", - " plt.title(f'{title_name} with sparsity regularizer')\n", - " plt.plot(values[1:])\n", - " \n", - " plt.subplot(2,2,4)\n", - " values = np.array(getattr(sparse_thetaless_model.score_tracker[view_name], value_name))\n", - " med = np.median(values)\n", - " values[values > 5 * med] = np.nan\n", - " last_sparse_thetaless = values[-1]\n", - "\n", - " plt.title(f'{title_name} with both regularizers')\n", - " plt.plot(values[1:])\n", - " \n", - " \n", - " print(\n", - " f'Last values:\\n\\t'\n", - " f'thetaless – {last_thetaless}\\n\\t'\n", - " f'plsa – {last_plsa}\\n\\t'\n", - " f'sparse – {last_sparse}\\n\\t'\n", - " f'sparse_thetaless – {last_sparse_thetaless}\\n\\t' \n", - " )\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 1227.15234375\n", - "\tplsa – 1314.651123046875\n", - "\tsparse – 957.7000732421875\n", - "\tsparse_thetaless – 895.8453979492188\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='perplexity', value_name='value', title_name='Perplexity')" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 1227.15234375\n", - "\tplsa – 1314.651123046875\n", - "\tsparse – 1528.09033203125\n", - "\tsparse_thetaless – 1408.190185546875\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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4pTi1EkUpzJmJE6d9jk/lyibokzT2uU4MysEfgL8jXOu3+s8yT3+g4fXmlkp8DPg8qkOm91azT4zuwPocM5tM7NN48VTHDrv28J3k3PumJlVA4+b2Z65rtA8l8t/azINxSmP4pRHceoUMxKn5uuVnxZgUdb2QuDYHNXlYtJuZgsA/GXHHNdn1phZCC+g/Jtz7qd+cc62B4Bzrhf4Nd748lIzG+8MyZXz5Sbg7WZ2GG/I0RvwethysS1wzh3zlx14/2xcR46fIxeY4tTUcvZvTnHqVIpTilPZZipOzdfkZyuwwp8NIwy8G3hojut0MXgIeJ+//j7gwTmsy6zxx8d+F9jtnPtq1q6caw8zq/J70jCzfOBWvLHlTwF/5h+WE23hnPuUc26hc64R7zviSefcn5ODbWFmBWZWNL4OvAnYQQ6eI7NIcWpqOfk3pziVoTiVoTiVMZNxat4+5NTM3oKXHQeALc65L85xlWaVmd0HbAIqgXbgs8D/Ax4AGoCjwDudcyffbDrvmNlG4BngVTJjZj+NN546p9rDzK7CuyEwgNf58YBz7vNmthSvV6kceAn4T865+NzVdHb5wwk+7py7Ixfbwv+df+ZvBoEfOue+aGYV5Ng5MpsUpxSnxilOZShOTU1xaubi1LxNfkRERERERLLN12FvIiIiIiIikyj5ERERERGRnKDkR0REREREcoKSHxERERERyQlKfkREREREJCco+RERERERkZyg5EdERERERHKCkh8REREREckJ/x9WakewgEbGmAAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='test_perplexity', value_name='value', title_name='Test perplexity')" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 0.9200233817100525\n", - "\tplsa – 0.7717972993850708\n", - "\tsparse – 0.9903578758239746\n", - "\tsparse_thetaless – 0.9920385479927063\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='sparsity', value_name='value', title_name='Sparsity')" - ] - }, - { - "cell_type": "code", - "execution_count": 50, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 0.7567857503890991\n", - "\tplsa – 0.6893280148506165\n", - "\tsparse – 0.8266897797584534\n", - "\tsparse_thetaless – 0.8727331757545471\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='kernel', value_name='average_contrast', title_name='Kernel Contrast')" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 0.9363216161727905\n", - "\tplsa – 0.7150622010231018\n", - "\tsparse – 0.7518076300621033\n", - "\tsparse_thetaless – 0.9405104517936707\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='kernel', value_name='average_purity', title_name='Kerne purity')" - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 3574.2333984375\n", - "\tplsa – 3774.466552734375\n", - "\tsparse – 623.433349609375\n", - "\tsparse_thetaless – 621.7000122070312\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='kernel', value_name='average_size', title_name='Kernel size')" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Last values:\n", - "\tthetaless – 1.2295665407796712\n", - "\tplsa – 0.7130331816199633\n", - "\tsparse – 0.6366304865617266\n", - "\tsparse_thetaless – 1.0301379151242755\n", - "\t\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
      " - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plot_metric(view_name='coherence_ppmi', value_name='value', title_name='Top-20 Coherence')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Coherence evaluation on an external corpus (with Palmetto)\n", - "\n", - "Unfortunately, the service isn't available right now" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": {}, - "outputs": [], - "source": [ - "from topicnet.viewers.top_tokens_viewer import TopTokensViewer\n", - "import requests\n", - "\n", - "\n", - "def eval_top_words(name, model, num_top_tokens=10):\n", - " ttv = TopTokensViewer(model, method='blei', num_top_tokens=num_top_tokens)\n", - " output = ttv.view()\n", - " S = \"http://palmetto.aksw.org/palmetto-webapp/service/{}?words=\".format(\"umass\")\n", - " topic_score = []\n", - " for key, modalities in output.items():\n", - " if 'topic' in key:\n", - " words = list(output[key]['@default_class'].keys())\n", - " print(S + \"%20\".join(words))\n", - " result = requests.get(S + \"%20\".join(words)).text\n", - " topic_score += [float(result)]\n", - " print(round(np.mean(topic_score), 2), round(np.median(topic_score), 2))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# eval_top_words('thetaless', thetaless_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# eval_top_words('plsa', plsa_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# eval_top_words('sparse', sparse_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# eval_top_words('sparse_thetaless', sparse_thetaless_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Let's look at topics" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [], - "source": [ - "def print_top_words(name, model, num_top_tokens=10):\n", - " ttv = TopTokensViewer(model, method='blei', num_top_tokens=num_top_tokens)\n", - " output = ttv.view()\n", - " for key, modalities in output.items():\n", - " if 'topic' in key:\n", - " print(', '.join(output[key]['@default_class'].keys()))\n", - " print()" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "this, have, has, any, would, know, does, been, anyone, problem\n", - "\n", - "key, system, information, public, chip, number, data, encryption, message, access\n", - "\n", - "my, me, am, car, up, .., ...., :-), buy, bike\n", - "\n", - "they, there, what, no, all, them, people, because, here, even\n", - "\n", - "god, who, jesus, life, his, man, .\", bible, christian, church\n", - "\n", - "10, 00, 15, 12, 25, game, 11, team, 20, 14\n", - "\n", - "of, are, ..., more, than, many, these, course, different, article\n", - "\n", - "is, as, an, of, not, such, the, however, be, example\n", - "\n", - "space, research, center, nasa, earth, 1993, launch, data, science, dr\n", - "\n", - "or, for, use, also, can, please, need, thanks, etc, mail\n", - "\n", - "it, but, so, one, think, could, only, why, good, really\n", - "\n", - "the, of, by, one, must, )., book, current, subject, should\n", - "\n", - "windows, drive, software, card, dos, system, disk, bit, pc, scsi\n", - "\n", - "to, be, will, the, able, order, make, time, next, of\n", - "\n", - "were, of, their, world, ,\", armenian, by, war, armenians, turkish\n", - "\n", - "graphics, ftp, edu, contact, available, runs, ->, fax, 800, pub\n", - "\n", - "for, new, best, each, used, old, price, original, 50, 100\n", - "\n", - "it, is, which, the, where, most, )., since, when, find\n", - "\n", - "the, at, into, two, first, and, second, power, end, place\n", - "\n", - "that, not, say, would, believe, being, fact, true, mean, reason\n", - "\n", - "you, if, can, do, your, how, get, want, should, re\n", - "\n", - "the, on, with, be, other, may, ),, all, and, same\n", - "\n", - "of, by, the, law, its, state, against, their, under, right\n", - "\n", - "we, --, us, our, their, who, people, government, mr, president\n", - "\n", - "file, program, );, window, */, /*, code, server, output, files\n", - "\n", - "and, in, from, the, of, over, years, through, before, after\n", - "\n", - "gm, +\", :), ah, +', mm, \\<, slave, mp, :<\n", - "\n", - "was, he, had, were, his, said, him, did, didn, she\n", - "\n", - "year, last, gun, control, by, 000, bill, local, week, national\n", - "\n", - "about, like, just, well, don, now, time, very, much, ve\n", - "\n" - ] - } - ], - "source": [ - "print_top_words('thetaless', thetaless_model)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "this, have, the, any, is, to, does, not, know, anyone\n", - "\n", - "is, key, of, and, the, system, information, are, public, chip\n", - "\n", - "to, me, my, or, the, on, if, have, in, would\n", - "\n", - "what, that, of, the, there, not, just, they, no, but\n", - "\n", - "he, we, god, his, that, and, us, our, him, who\n", - "\n", - "00, 10, game, 15, team, 25, 12, 11, 20, 13\n", - "\n", - "of, ..., that, to, are, is, and, with, not, the\n", - "\n", - "is, as, the, that, of, not, to, be, an, and\n", - "\n", - "of, and, space, for, 1993, research, center, in, national, by\n", - "\n", - "for, to, can, or, and, if, get, use, please, also\n", - "\n", - "it, but, have, so, that, think, the, to, really, why\n", - "\n", - "the, of, in, is, are, be, to, one, )., by\n", - "\n", - "with, windows, drive, system, card, dos, software, bit, disk, scsi\n", - "\n", - "to, is, the, on, in, this, of, with, it, can\n", - "\n", - "of, the, in, and, were, by, their, armenian, war, armenians\n", - "\n", - "from, available, on, list, ftp, and, mail, edu, )., version\n", - "\n", - "for, and, new, good, very, with, all, best, old, price\n", - "\n", - "it, is, the, in, be, too, of, an, to, are\n", - "\n", - "the, and, at, into, first, in, two, on, second, back\n", - "\n", - "that, not, to, believe, of, who, would, say, think, are\n", - "\n", - "you, your, to, it, and, do, can, if, how, are\n", - "\n", - "the, be, with, in, will, and, it, on, are, if\n", - "\n", - "of, the, by, as, to, state, in, and, israel, law\n", - "\n", - "the, to, we, and, they, that, in, their, people, --\n", - "\n", - "file, the, program, window, );, */, /*, code, server, output\n", - "\n", - "the, in, and, to, of, from, years, after, was, over\n", - "\n", - "55, ----, gm, :), __, slave, +\", master, ah, air\n", - "\n", - "was, they, he, had, were, out, said, the, when, up\n", - "\n", - "the, of, and, in, by, on, for, to, at, year\n", - "\n", - "about, it, would, but, don, well, the, just, to, there\n", - "\n" - ] - } - ], - "source": [ - "print_top_words('plsa', plsa_model)" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "this, any, have, to, the, does, is, anyone, not, problem\n", - "\n", - "is, the, are, key, of, used, as, and, will, public\n", - "\n", - "to, or, the, have, if, on, my, for, be, in\n", - "\n", - "that, what, the, of, but, just, no, it, something, here\n", - "\n", - "we, that, me, there, know, our, they, what, us, so\n", - "\n", - "10, 00, 20, 15, 12, 25, 11, game, 14, 16\n", - "\n", - "that, is, of, are, to, not, you, this, the, and\n", - "\n", - "the, is, as, to, of, that, be, an, not, or\n", - "\n", - "and, of, for, space, on, the, data, high, in, research\n", - "\n", - "for, to, use, and, or, please, can, on, mail, also\n", - "\n", - "it, have, but, so, think, that, to, really, not, the\n", - "\n", - "the, not, of, is, god, that, jesus, be, must, as\n", - "\n", - "with, windows, thanks, drive, software, system, card, using, version, bit\n", - "\n", - "to, is, the, get, can, if, it, in, this, of\n", - "\n", - "of, the, in, and, their, by, were, people, against, armenian\n", - "\n", - "),, )., net, list, 80, contact, 22, box, runs, 60\n", - "\n", - "for, and, new, good, very, )., or, have, work, all\n", - "\n", - "is, it, the, too, an, in, be, or, are, to\n", - "\n", - "the, and, first, into, in, at, back, second, with, to\n", - "\n", - "that, to, not, who, would, be, of, are, in, believe\n", - "\n", - "you, your, to, it, can, do, and, how, if, are\n", - "\n", - "the, be, with, in, will, may, on, and, this, are\n", - "\n", - "of, the, by, as, to, state, and, in, from, which\n", - "\n", - "the, to, and, in, they, their, been, of, government, that\n", - "\n", - "the, file, program, );, window, */, /*, code, by, source\n", - "\n", - "the, in, and, to, from, of, through, where, over, up\n", - "\n", - "the, at, ..., --, in, year, will, new, last, team\n", - "\n", - "was, he, they, had, his, were, said, him, and, the\n", - "\n", - "the, and, of, in, on, by, for, two, to, gun\n", - "\n", - "it, about, my, the, on, off, to, and, well, but\n", - "\n" - ] - } - ], - "source": [ - "print_top_words('sparse', sparse_model)" - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "this, have, any, has, know, does, anyone, work, problem, would\n", - "\n", - "key, number, information, public, group, chip, news, message, encryption, posting\n", - "\n", - "my, me, like, up, am, get, could, want, on, someone\n", - "\n", - "there, what, no, don, they, some, even, see, why, here\n", - "\n", - "we, he, who, his, people, us, our, all, him, say\n", - "\n", - "10, 00, 20, 15, 12, 25, 11, game, 14, 16\n", - "\n", - "are, of, more, ..., than, many, only, case, different, course\n", - "\n", - "is, as, the, not, such, of, however, or, example, makes\n", - "\n", - "space, 1993, university, high, .,, national, research, center, science, of\n", - "\n", - "use, on, system, or, please, thanks, can, etc, mail, hard\n", - "\n", - "it, but, so, good, one, only, really, take, doesn, thing\n", - "\n", - "god, of, the, must, \"., jesus, one, not, person, word\n", - "\n", - "windows, drive, software, card, dos, version, image, bit, disk, pc\n", - "\n", - "to, the, get, on, order, way, able, when, then, time\n", - "\n", - "their, were, world, against, of, ,\", armenian, during, .\", children\n", - "\n", - "->, 100, runs, 800, 200, ----, 90, 300, steve, 500\n", - "\n", - "for, and, )., new, ),, all, best, each, both, call\n", - "\n", - "an, or, which, the, where, most, is, of, find, should\n", - "\n", - "the, into, first, and, power, second, end, place, db, three\n", - "\n", - "that, not, would, think, believe, made, fact, being, mean, be\n", - "\n", - "you, if, can, do, your, how, re, make, should, then\n", - "\n", - "with, be, the, will, also, may, on, and, other, these\n", - "\n", - "of, by, the, its, state, law, israel, rights, states, church\n", - "\n", - "the, and, --, been, to, government, those, has, them, people\n", - "\n", - "file, program, );, window, */, /*, code, available, source, ftp\n", - "\n", - "in, and, from, the, over, through, years, found, several, of\n", - "\n", - "team, win, st, series, points, mark, york, 60, 000, division\n", - "\n", - "was, they, had, were, after, when, said, didn, she, out\n", - "\n", - "at, the, on, two, and, of, year, last, control, gun\n", - "\n", - "about, out, just, well, time, on, now, very, ve, right\n", - "\n" - ] - } - ], - "source": [ - "print_top_words('sparse_thetaless', sparse_thetaless_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Last coherence values (using different number of top tokens)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The plots above show how the coherence (PPMI) of top 20 tokens evolves. In addition, we can look at the last PMI and PPMI values using different sizes of top-token list (5, 10, 20, 50, 100 words)" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [], - "source": [ - "occurences, co_occurences = calc_doc_occurrences(train_n_dw_matrix)\n", - "calc_pmi = create_pmi_top_function(\n", - " occurences, co_occurences,\n", - " test_n_dw_matrix.shape[0], [5, 10, 20, 50, 100],\n", - " co_occurrences_smooth=1.\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.39794082, 0.5118048 , 0.67965463, 0.88237313, 1.04378611]),\n", - " array([0.42403162, 0.5602503 , 0.71303318, 0.93598612, 1.11396004]))" - ] - }, - "execution_count": 34, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(plsa_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.91811772, 1.01885883, 1.20539864, 1.29182469, 1.36843634]),\n", - " array([0.91811772, 1.05672401, 1.22956654, 1.38632289, 1.55204473]))" - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(thetaless_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.38628136, 0.48798776, 0.63637099, 0.84816317, 1.00993652]),\n", - " array([0.38628136, 0.48798776, 0.63663049, 0.86840002, 1.06419641]))" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(sparse_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.73225952, 0.86980454, 1.01066112, 1.1870601 , 1.11520662]),\n", - " array([0.73225952, 0.87038446, 1.03023163, 1.25691501, 1.40597935]))" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(sparse_thetaless_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Also, let's take a look at train + test together, instead of relying on train only:" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "metadata": {}, - "outputs": [], - "source": [ - "matrix = scipy.sparse.vstack((train_n_dw_matrix, test_n_dw_matrix))\n", - "occurences, co_occurences = calc_doc_occurrences(matrix)\n", - "calc_pmi = create_pmi_top_function(\n", - " occurences, co_occurences,\n", - " matrix.shape[0], [5, 10, 20, 50, 100],\n", - " co_occurrences_smooth=1.\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.39331874, 0.50256166, 0.6726003 , 0.86943263, 1.02683013]),\n", - " array([0.42403052, 0.56024917, 0.71303195, 0.93598439, 1.11395708]))" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(plsa_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.91811657, 1.01064248, 1.20028921, 1.27103585, 1.32921158]),\n", - " array([0.91811657, 1.05672276, 1.22956457, 1.38631923, 1.55203787]))" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(thetaless_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.38628123, 0.48798752, 0.63637053, 0.84472894, 1.00085916]),\n", - " array([0.38628123, 0.48798752, 0.63663005, 0.8683986 , 1.06419316]))" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(sparse_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(array([0.73225886, 0.86980354, 1.00774106, 1.17468343, 1.06144551]),\n", - " array([0.73225886, 0.87038357, 1.03023015, 1.25691137, 1.40597128]))" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calc_pmi(sparse_thetaless_model.get_phi_dense()[0].T)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We observed several interesting things.\n", - "\n", - "1) the regularizer improves coherence and sparsity without explicitly optimizing for it\n", - "\n", - "2) The regularizer moves common but uninformative words away from \"informative\" topics. Compare the second topic of PLSA: \n", - "\n", - "`is, key, of, and, the, system, information, are, public, chip`\n", - "\n", - "with the second topic of thetaless:\n", - "\n", - "`key, system, information, public, chip, number, data, encryption, message, access`\n", - "\n", - "It should be noted that we had not specified the usual distincton between specific and background topics, the separation here is purely emergent.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/topicnet/tests/test_cube_controller.py b/topicnet/tests/test_cube_controller.py index a716757..1797e6f 100644 --- a/topicnet/tests/test_cube_controller.py +++ b/topicnet/tests/test_cube_controller.py @@ -32,7 +32,7 @@ def resource_teardown(): """ """ - dataset = Dataset(f'tests/test_data/test_dataset.csv') + dataset = Dataset('tests/test_data/test_dataset.csv') if os.path.exists("tests/experiments"): shutil.rmtree("tests/experiments") @@ -150,7 +150,7 @@ def experiment_enviroment(request): """ """ with warnings.catch_warnings(): warnings.filterwarnings(action="ignore", message=W_DIFF_BATCHES_1) - dataset = Dataset(f'tests/test_data/test_dataset.csv') + dataset = Dataset('tests/test_data/test_dataset.csv') dictionary = dataset.get_dictionary() model_artm = artm.ARTM( diff --git a/topicnet/tests/test_cube_utils.py b/topicnet/tests/test_cube_utils.py index faaa7a3..3ab5e3f 100644 --- a/topicnet/tests/test_cube_utils.py +++ b/topicnet/tests/test_cube_utils.py @@ -1,6 +1,9 @@ import pytest -from topicnet.cooking_machine.cubes.controller_cube import PerplexityScoreController, ControllerAgent +from ..cooking_machine.cubes.controller_cube import ( + ControllerAgent, + PerplexityScoreController, +) DATA_REG_CONTROLLER_SORT_OF_DECREASING = [ ([246.77072143554688, diff --git a/topicnet/tests/test_cubes.py b/topicnet/tests/test_cubes.py index ceae819..f04a12d 100644 --- a/topicnet/tests/test_cubes.py +++ b/topicnet/tests/test_cubes.py @@ -10,7 +10,9 @@ from ..cooking_machine.cubes.greedy_strategy import GreedyStrategy from ..cooking_machine.cubes.perplexity_strategy import PerplexityStrategy -from ..cooking_machine.cubes import RegularizersModifierCube, CubeCreator +from ..cooking_machine.cubes import ( + RegularizersModifierCube, CubeCreator, RegularizationControllerCube +) from ..cooking_machine.models.topic_model import TopicModel from ..cooking_machine.models.topic_prior_regularizer import TopicPriorRegularizer from ..cooking_machine.models.topic_prior_regularizer import TopicPriorSampledRegularizer @@ -18,7 +20,8 @@ from ..cooking_machine.dataset import Dataset, W_DIFF_BATCHES_1 from ..cooking_machine.rel_toolbox_lite import count_vocab_size, compute_regularizer_gimel -DATA_PATH = f'tests/test_data/test_dataset.csv' + +DATA_PATH = 'tests/test_data/test_dataset.csv' MAIN_MODALITY = "@text" NGRAM_MODALITY = "@ngramms" @@ -92,11 +95,11 @@ def test_simple_experiment(experiment_enviroment, thread_flag): """ """ tm, dataset, experiment, dictionary = experiment_enviroment - TAU_GRID = [0.1, 0.5, 1, 5, 10] + tau_grid = [0.1, 0.5, 1, 5, 10] regularizer_parameters = { "regularizer": artm.regularizers.SmoothSparsePhiRegularizer(name='test', class_ids=MAIN_MODALITY), - "tau_grid": TAU_GRID + "tau_grid": tau_grid } cube = RegularizersModifierCube( @@ -110,9 +113,9 @@ def test_simple_experiment(experiment_enviroment, thread_flag): tmodels = [dummy.restore() for dummy in dummies] - assert len(tmodels) == len(TAU_GRID) + assert len(tmodels) == len(tau_grid) for i, one_model in enumerate(tmodels): - assert one_model.regularizers['test'].tau == TAU_GRID[i] + assert one_model.regularizers['test'].tau == tau_grid[i] @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) @@ -120,11 +123,11 @@ def test_simple_experiment_pair_strategy(experiment_enviroment, thread_flag): """ """ tm, dataset, experiment, dictionary = experiment_enviroment - TAU_GRID = [0.1, 0.5, 1, 5, 10] + tau_grid = [0.1, 0.5, 1, 5, 10] regularizer_parameters = { "regularizer": artm.regularizers.SmoothSparsePhiRegularizer(name='test', class_ids=MAIN_MODALITY), - "tau_grid": TAU_GRID + "tau_grid": tau_grid } cube_pair = RegularizersModifierCube( @@ -145,7 +148,7 @@ def test_simple_experiment_pair_strategy(experiment_enviroment, thread_flag): assert len(tmodels_pair) == 5 for i, one_model in enumerate(tmodels_pair): - assert one_model.regularizers['test'].tau == TAU_GRID[i] + assert one_model.regularizers['test'].tau == tau_grid[i] @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) @@ -200,11 +203,11 @@ def test_relative_coefficients(experiment_enviroment, artm_regularizer, thread_f tm, dataset, experiment, dictionary = experiment_enviroment modality_weights = tm.class_ids random_tau = np.random.rand() + 0.01 - TAU_GRID = np.array([1.0, 2.0, 3.0, 4.0, 5.0, ]) + tau_grid = np.array([1.0, 2.0, 3.0, 4.0, 5.0, ]) regularizer_parameters = { "regularizer": artm_regularizer, - "tau_grid": TAU_GRID * random_tau + "tau_grid": tau_grid * random_tau } cube_first = RegularizersModifierCube( @@ -228,9 +231,9 @@ def test_relative_coefficients(experiment_enviroment, artm_regularizer, thread_f data_stats = count_vocab_size(dictionary, modality_weights) gimels = [] if artm_regularizer.name == 'test_decor': - gimels = TAU_GRID * random_tau + gimels = tau_grid * random_tau else: - for tau in TAU_GRID * random_tau: + for tau in tau_grid * random_tau: artm_regularizer._tau = tau gimels.append(compute_regularizer_gimel( data_stats, @@ -254,12 +257,12 @@ def test_relative_coefficients(experiment_enviroment, artm_regularizer, thread_f second_cube_models = cube_second(tm, dataset) - assert len(first_cube_tmodels) == len(TAU_GRID) == len(second_cube_models) + assert len(first_cube_tmodels) == len(tau_grid) == len(second_cube_models) if artm_regularizer.name == 'test_decor': for one_model, second_model in zip(first_cube_tmodels, second_cube_models): assert one_model.scores['PerplexityScore'] != second_model.scores['PerplexityScore'] else: - assert np.all(gimels != TAU_GRID * random_tau) + assert np.all(gimels != tau_grid * random_tau) for one_model, second_model in zip(first_cube_tmodels, second_cube_models): assert one_model.scores['PerplexityScore'] == second_model.scores['PerplexityScore'] @@ -621,11 +624,11 @@ def test_perplexity_strategy_grid(experiment_enviroment, thread_flag): """ """ tm, dataset, experiment, dictionary = experiment_enviroment - TAU_GRID = [0.1, 0.5, 1, 5, 50] + tau_grid = [0.1, 0.5, 1, 5, 50] regularizer_parameters = { "regularizer": artm.DecorrelatorPhiRegularizer(name='decorrelator_phi_regularizer', class_ids=MAIN_MODALITY), - "tau_grid": TAU_GRID + "tau_grid": tau_grid } cube = RegularizersModifierCube( @@ -642,7 +645,7 @@ def test_perplexity_strategy_grid(experiment_enviroment, thread_flag): tmodels = [dummy.restore() for dummy in dummies] visited_taus = extract_visited_taus(tmodels) - expected_taus = [0] + TAU_GRID + expected_taus = [0] + tau_grid assert visited_taus == expected_taus SCORES = [3.756, 3.756, 3.753, 3.75, 3.72, 2.887] @@ -821,3 +824,119 @@ def test_phi_matrix_after_lda_sampled_regularizer(experiment_enviroment): phi_second = tm_2.get_phi() assert any(phi_first != phi_second), 'Phi matrices are the same after regularization.' + + +@pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) +@pytest.mark.parametrize('by_name', [True, False]) +def test_custom_regularizer_cubed(experiment_enviroment, thread_flag, by_name): + """ """ + _, dataset, _, dictionary = experiment_enviroment + tau_grid = [1, 0, -1] + + custom_reg = TopicPriorSampledRegularizer( + name='topic_prior', tau=5, + num_topics=5, beta_prior=[10, 1, 100, 2, 1000] + ) + + model_artm = artm.ARTM( + num_processors=1, + num_topics=5, + cache_theta=True, + class_ids={MAIN_MODALITY: 1.0, NGRAM_MODALITY: 1.0}, + num_document_passes=1, + dictionary=dictionary, + scores=[artm.PerplexityScore(name='PerplexityScore',)], + ) + tm = TopicModel( + model_artm, model_id='new_id_1', + custom_regularizers={custom_reg.name: custom_reg} if by_name else {} + ) + experiment = Experiment( # noqa: F841 + tm, experiment_id="cubed_reg", save_path="tests/experiments" + ) + + if by_name: + regularizer_parameters = { + "name": custom_reg.name, + "tau_grid": tau_grid + } + else: + regularizer_parameters = { + "regularizer": custom_reg, + "tau_grid": tau_grid + } + + cube = RegularizersModifierCube( + num_iter=10, + regularizer_parameters=regularizer_parameters, + reg_search="grid", + use_relative_coefficients=False, + separate_thread=thread_flag + ) + dummies = cube(tm, dataset) + + tmodels = [dummy.restore() for dummy in dummies] + + assert len(tmodels) == len(tau_grid) + for tau, one_model in zip(tau_grid, tmodels): + assert one_model.all_regularizers[custom_reg.name].tau == tau + + +@pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) +@pytest.mark.parametrize('by_name', [True, False]) +def test_custom_regularizer_cubed_controlled(experiment_enviroment, thread_flag, by_name): + """ """ + _, dataset, _, dictionary = experiment_enviroment + multiplier = 2 + initial_tau = 5 + + custom_reg = TopicPriorSampledRegularizer( + name='topic_prior', tau=initial_tau, + num_topics=5, beta_prior=[10, 1, 100, 2, 1000] + ) + + model_artm = artm.ARTM( + num_processors=1, + num_topics=5, + cache_theta=True, + class_ids={MAIN_MODALITY: 1.0, NGRAM_MODALITY: 1.0}, + num_document_passes=1, + dictionary=dictionary, + scores=[artm.PerplexityScore(name='PerplexityScore',)], + ) + tm = TopicModel( + model_artm, model_id='new_id_1', + custom_regularizers={custom_reg.name: custom_reg} if by_name else {} + ) + experiment = Experiment( # noqa: F841 + tm, experiment_id="cubed_controlled_reg", save_path="tests/experiments" + ) + + parameters = { + "score_to_track": None, + "tau_converter": f"prev_tau * {multiplier}", + "user_value_grid": [0.3], + "max_iters": float("inf") + } + + if by_name: + parameters["reg_name"] = custom_reg.name + else: + parameters["regularizer"] = custom_reg + + num_iter = 10 + cube = RegularizationControllerCube( + num_iter=num_iter, + parameters=parameters, + reg_search="grid", + use_relative_coefficients=False, + separate_thread=thread_flag + ) + dummies = cube(tm, dataset) + + tmodels = [dummy.restore() for dummy in dummies] + + for one_model in tmodels: + actual_tau = one_model.all_regularizers[custom_reg.name].tau + + assert actual_tau == initial_tau * (multiplier ** num_iter) diff --git a/topicnet/tests/test_dataset.py b/topicnet/tests/test_dataset.py index adbd3c1..9d6e996 100644 --- a/topicnet/tests/test_dataset.py +++ b/topicnet/tests/test_dataset.py @@ -60,7 +60,6 @@ def test_get_dict_two_times(self, small): assert len(record) == 0 - @pytest.mark.xfail @pytest.mark.parametrize("small", KEEP_DATA) def test_get_dict_two_times_alternating(self, small): """ """ diff --git a/topicnet/tests/test_experiment.py b/topicnet/tests/test_experiment.py index 5b94479..b893aca 100644 --- a/topicnet/tests/test_experiment.py +++ b/topicnet/tests/test_experiment.py @@ -173,6 +173,9 @@ def test_double_steps_experiment(two_experiment_enviroments, thread_flag): dummies_second = cube_first_2(tm_2, dataset) tmodels_lvl2_2 = [dummy.restore() for dummy in dummies_second] + for models in zip(tmodels_lvl2_1, tmodels_lvl2_2): + assert np.array_equal(models[0].get_phi(), models[1].get_phi()) + experiment_1.save_path = 'tests/experiments/test' experiment_1.save() experiment = Experiment.load('tests/experiments/test_1') @@ -210,9 +213,6 @@ def test_double_steps_experiment(two_experiment_enviroments, thread_flag): assert len(tmodels_lvl3) == len(tmodels_lvl3_2) assert cube_second.strategy.score == cube_second_2.strategy.score - for models in zip(tmodels_lvl2_1, tmodels_lvl2): - assert np.array_equal(models[0].get_phi(), models[1].get_phi()) - for models in zip(tmodels_lvl3, tmodels_lvl3_2): assert ( models[0].regularizers['test_second'].tau == models[1].regularizers['test_second'].tau diff --git a/topicnet/tests/test_experiment_restore.py b/topicnet/tests/test_experiment_restore.py new file mode 100644 index 0000000..646fd95 --- /dev/null +++ b/topicnet/tests/test_experiment_restore.py @@ -0,0 +1,295 @@ +import os +import pytest +import shutil + +from typing import ( + Dict, + List, +) + +import artm + +from ..cooking_machine.dataset import Dataset +from ..cooking_machine.experiment import Experiment +from ..cooking_machine.models.base_score import BaseScore +from ..cooking_machine.models.topic_model import TopicModel + +_MAIN_MODALITY = "@text" + +_ONE_CUBE_NUM_ITERATIONS = 10 +_NUM_CUBES = 3 +_INTERRUPT_CUBE_ITERATION = 5 +_ONCE_INTERRUPTED = [False] + +_TAU_GRID = [0.1, 0.5, 1.0] # better three or more values (for testing purposes) + +_SCORE_NAME = 'bad_score' +_REGULARIZER_NAME = 'reglurellriser' +_SELECT_CRITERION_FOR_ALL_MODELS = '' + +_DEBUG_MODE = False + + +class InterruptingScore(BaseScore): + def __init__(self, name: str, interrupt_cube: int, interrupt_tau: float): + super().__init__(name=name) + + self._iteration = 0 + self._interrupt_cube = interrupt_cube + self._interrupt_tau = interrupt_tau + + def call(self, model: TopicModel) -> float: + regularizer_tau = model.regularizers[_REGULARIZER_NAME].tau + current_cube = model.depth + current_cube_iteration = ( + len(model.scores[_SCORE_NAME]) - (current_cube - 1) * _ONE_CUBE_NUM_ITERATIONS + ) + + if (current_cube == self._interrupt_cube + and regularizer_tau == self._interrupt_tau + and current_cube_iteration >= _INTERRUPT_CUBE_ITERATION + and not _ONCE_INTERRUPTED[0]): + + _ONCE_INTERRUPTED[0] = True + + raise KeyboardInterrupt() + + self._iteration += 1 + + return self._iteration + + +class TestExperimentRestore: + dataset = None + dictionary = None + experiments_save_path = None + + @classmethod + def setup_class(cls): + cls.dataset = Dataset('tests/test_data/test_dataset.csv') + cls.dictionary = cls.dataset.get_dictionary() + cls.experiments_save_path = 'tests/experiments' + + def setup_method(self): + _ONCE_INTERRUPTED[0] = False + + os.makedirs(self.experiments_save_path, exist_ok=True) + + def teardown_method(self): + if os.path.isdir(self.experiments_save_path): + shutil.rmtree(self.experiments_save_path) + + @classmethod + def teardown_class(cls): + if os.path.isdir(cls.experiments_save_path): + shutil.rmtree(cls.experiments_save_path) + + if cls.dataset is not None: + cls.dataset.clear_folder() + + @pytest.mark.parametrize( + 'interrupt_cube_index, interrupt_model_index', + [(1, 0), (0, -1), (-1, 1)] + ) + def test_ctrl_c_and_proceed(self, interrupt_cube_index, interrupt_model_index): + self._test_ctrl_c_and_proceed( + interrupt_cube_index=interrupt_cube_index, + interrupt_model_index=interrupt_model_index, + thread_flag=False, + load_experiment=False, + ) + + # TODO: something happens in multiprocess, and it takes infinity to wait till the end + @pytest.mark.xfail + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'interrupt_cube_index, interrupt_model_index', + [(1, 0)] # , (0, -1), (-1, 1)] + ) + def test_ctrl_c_and_proceed_multiprocess(self, interrupt_cube_index, interrupt_model_index): + self._test_ctrl_c_and_proceed( + interrupt_cube_index=interrupt_cube_index, + interrupt_model_index=interrupt_model_index, + thread_flag=True, + load_experiment=False, + ) + + # TODO: cubes are loaded as strings, not as Python objects -> experiment.run fails + @pytest.mark.xfail + @pytest.mark.parametrize( + 'interrupt_cube_index, interrupt_model_index', + [(1, 0), (0, -1), (-1, 1)] + ) + def test_ctrl_c_and_load(self, interrupt_cube_index, interrupt_model_index): + self._test_ctrl_c_and_proceed( + interrupt_cube_index=interrupt_cube_index, + interrupt_model_index=interrupt_model_index, + thread_flag=False, + load_experiment=True, + ) + + @pytest.mark.xfail + @pytest.mark.timeout(10) + @pytest.mark.parametrize( + 'interrupt_cube_index, interrupt_model_index', + [(1, 0)] # , (0, -1), (-1, 1)] + ) + def test_ctrl_c_and_load_multiprocess(self, interrupt_cube_index, interrupt_model_index): + self._test_ctrl_c_and_proceed( + interrupt_cube_index=interrupt_cube_index, + interrupt_model_index=interrupt_model_index, + thread_flag=True, + load_experiment=True, + ) + + def _test_ctrl_c_and_proceed( + self, + interrupt_cube_index: int, + interrupt_model_index: int, + thread_flag: bool, + load_experiment: bool) -> None: + + experiment = self._initialize_experiment( + experiment_id=f'Experiment_{thread_flag}', + interrupt_cube_index=interrupt_cube_index, + interrupt_model_index=interrupt_model_index, + ) + cube_settings = self._initialize_cube_settings(thread_flag) + experiment.build(cube_settings) + + models: List[TopicModel] = None + is_interrupt_detected = False + + try: + experiment.run( + self.dataset, verbose=False, nb_verbose=False + ) + except KeyboardInterrupt: + is_interrupt_detected = True + + if load_experiment: + experiment = Experiment.load( + os.path.join(experiment.save_path, experiment.experiment_id) + ) # TODO: need to concatenate? + + models = experiment.run( + self.dataset, verbose=False, nb_verbose=False, + restore_mode=True, + ) + finally: + self._print_debug_info(experiment) + + assert is_interrupt_detected, 'No KeyboardInterrupt detected!' + + self._check_result(cube_settings, experiment, models) + + def _initialize_experiment( + self, + experiment_id: str, + interrupt_cube_index: int, + interrupt_model_index: int) -> Experiment: + + artm_model = artm.ARTM( + num_processors=1, + num_topics=5, + cache_theta=True, + num_document_passes=1, + dictionary=self.dictionary, + scores=[ + artm.PerplexityScore( + name='PerplexityScore' + ), + artm.SparsityPhiScore( + name='SparsityPhiScore', class_id=_MAIN_MODALITY + ) + ] + ) + + topic_model = TopicModel(artm_model, model_id='start_id') + interrupt_cube = list(range(_NUM_CUBES))[interrupt_cube_index] + 1 + interrupt_tau = _TAU_GRID[interrupt_model_index] + topic_model.scores.add( + InterruptingScore( + name=_SCORE_NAME, + interrupt_cube=interrupt_cube, + interrupt_tau=interrupt_tau, + ) + ) + + return Experiment( + topic_model, + experiment_id=experiment_id, + save_path=self.experiments_save_path, + ) + + def _initialize_cube_settings(self, separate_thread: bool) -> List[Dict]: + return [ + self._one_cube_description( + num_iter=_ONE_CUBE_NUM_ITERATIONS, + separate_thread=separate_thread, + ) + for _ in range(_NUM_CUBES) + ] + + def _one_cube_description(self, num_iter: int, separate_thread: bool) -> dict: + return { + 'RegularizersModifierCube': + { + 'num_iter': num_iter, + 'regularizer_parameters': + { + "regularizer": artm.regularizers.SmoothSparsePhiRegularizer( + name=_REGULARIZER_NAME + ), + "tau_grid": _TAU_GRID, + }, + 'reg_search': 'grid', + 'use_relative_coefficients': False, + 'separate_thread': separate_thread, + }, + 'selection': [_SELECT_CRITERION_FOR_ALL_MODELS] + } + + def _check_result( + self, + cube_settings: List[Dict], + experiment: Experiment, + models: List[TopicModel]) -> None: + + assert experiment.depth > 0 + assert len(models) == len(_TAU_GRID) ** (experiment.depth - 1) + assert experiment.depth == len(experiment.cubes) + assert experiment.depth == len(cube_settings) + 1 + + assert len(experiment.get_models_by_depth(0)) == 0 + assert len(experiment.get_models_by_depth(1)) == 1 + assert len(experiment.get_models_by_depth(2)) == 1 * len(_TAU_GRID) + + for d in range(3, experiment.depth + 1): + assert len(experiment.get_models_by_depth(d)) == len(_TAU_GRID) ** (d - 1) + + assert len(experiment.models) == sum( + len(_TAU_GRID) ** (d - 1) for d in range(1, experiment.depth + 1) + ) + + def _print_debug_info(self, experiment: Experiment) -> None: + if not _DEBUG_MODE: + return + + print(f'Experiment save path: {experiment.save_path}') + print(f'Experiment depth: {experiment.depth}') + print(f'Num cubes: {len(experiment.cubes)}') + + print('Cubes:' + '\n') + + for c in experiment.cubes: + print(c) + print() + + last_model = list(experiment.models.values())[-1] + score_names = last_model.scores.keys() + + print(score_names) + + if _SCORE_NAME in score_names: + print(last_model.scores[_SCORE_NAME]) diff --git a/topicnet/tests/test_experiment_select.py b/topicnet/tests/test_experiment_select.py index 094049a..f18c252 100644 --- a/topicnet/tests/test_experiment_select.py +++ b/topicnet/tests/test_experiment_select.py @@ -148,7 +148,8 @@ def set_score(self, name: str, values: list): @staticmethod def get_start_model(): - model = MockTopicModel(name=f'<<< Start Model >>>', depth=0) + # TODO: can we rename it because of Win compatibility? + model = MockTopicModel(name='<<< Start Model >>>', depth=0) for score in SCORES: model.set_score(score, []) @@ -1082,7 +1083,7 @@ def test_constrained_optimization( def test_constraints_on_same_attribute_contradict(self, parameter, threshold, signs): experiment = TestExperimentSelect.get_experiment() - constraint_template = f'{{0}} {{1}} {{2}}' + constraint_template = f'{{0}} {{1}} {{2}}' # noqa: F541 query = combine_constraints( *[constraint_template.format(parameter, sign, threshold) for sign in signs] diff --git a/topicnet/tests/test_pipeline.py b/topicnet/tests/test_pipeline.py index 9014860..01fa060 100644 --- a/topicnet/tests/test_pipeline.py +++ b/topicnet/tests/test_pipeline.py @@ -13,9 +13,10 @@ from ..cooking_machine.dataset import Dataset, W_DIFF_BATCHES_1 from ..cooking_machine.config_parser import build_experiment_environment_from_yaml_config from ..cooking_machine.model_tracking import START +from ..cooking_machine.recipes import BaselineRecipe -# MULTIPROCESSING_FLAGS = [True, False] -MULTIPROCESSING_FLAGS = [True] +# TODO: MULTIPROCESSING_FLAGS = [True, False] +MULTIPROCESSING_FLAGS = [False] USE_MULTIPROCESS = True @@ -96,7 +97,7 @@ def experiment_enviroment(request): @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) -def test_bad_empty_config(experiment_enviroment, thread_flag): +def test_bad_empty_config(thread_flag): with open("tests/test_data/bad_empty_config.yml", "r", encoding='utf-8') as f: yaml_string = f.read() @@ -113,7 +114,7 @@ def test_bad_empty_config(experiment_enviroment, thread_flag): @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) -def test_bad_config(experiment_enviroment, thread_flag): +def test_bad_config(thread_flag): with open("tests/test_data/bad_config.yml", "r", encoding='utf-8') as f: yaml_string = f.read() @@ -127,7 +128,7 @@ def test_bad_config(experiment_enviroment, thread_flag): @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) -def test_pipeline_from_config(experiment_enviroment, thread_flag): +def test_pipeline_from_config(thread_flag): with open("tests/test_data/config.yml", "r", encoding='utf-8') as f: yaml_string = f.read() @@ -188,7 +189,7 @@ def test_pipeline_from_config(experiment_enviroment, thread_flag): @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) -def test_config_with_blei_score(experiment_enviroment, thread_flag): +def test_config_with_blei_score(thread_flag): with open("tests/test_data/config_blei.yml", "r", encoding='utf-8') as f: yaml_string = f.read() @@ -207,7 +208,7 @@ def test_config_with_blei_score(experiment_enviroment, thread_flag): @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) -def test_config_with_scores(experiment_enviroment, thread_flag): +def test_config_with_scores(thread_flag): with open("tests/test_data/config_short.yml", "r", encoding='utf-8') as f: yaml_string = f.read() @@ -227,7 +228,7 @@ def test_config_with_scores(experiment_enviroment, thread_flag): @pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) -def test_config_with_greedy_strategy(experiment_enviroment, thread_flag): +def test_config_with_greedy_strategy(thread_flag): with open("tests/test_data/config2.yml", "r", encoding='utf-8') as f: yaml_string = f.read() @@ -286,3 +287,29 @@ def test_pipeline_with_new_cube_after(experiment_enviroment, thread_flag): assert len(new_models) == 3, 'Incorrect number of final models.' assert len(experiment.cubes) == 4, 'Incorrect number of cubes in the experiment.' assert len(experiment.criteria) == 4, 'Incorrect number of criteria in the experiment.' + + +@pytest.mark.parametrize('thread_flag', MULTIPROCESSING_FLAGS) +def test_filter_dictionary(thread_flag): + datasets = dict() + big_dataset_name = 'big' + small_dataset_name = 'small' + + for dataset_name, min_df in [(big_dataset_name, 0), (small_dataset_name, 2)]: + pipeline = BaselineRecipe() + pipeline.format_recipe( + dataset_path='./tests/test_data/test_dataset.csv', + dictionary_filter_parameters={'min_df': min_df}, + ) + _, dataset = pipeline.build_experiment_environment( + experiment_id=dataset_name, + save_path='tests/experiments', + ) + datasets[dataset_name] = dataset + + big_dictionary = datasets[big_dataset_name].get_dictionary() + small_dictionary = datasets[small_dataset_name].get_dictionary() + big_num_entries = Dataset._get_dictionary_num_entries(big_dictionary) + small_num_entries = Dataset._get_dictionary_num_entries(small_dictionary) + + assert big_num_entries > small_num_entries diff --git a/topicnet/tests/test_topic_model.py b/topicnet/tests/test_topic_model.py index b99d07b..65659ae 100644 --- a/topicnet/tests/test_topic_model.py +++ b/topicnet/tests/test_topic_model.py @@ -1,6 +1,10 @@ +import datetime import pytest import warnings import shutil +import time + + import artm from ..cooking_machine.models.dummy_topic_model import DummyTopicModel @@ -9,6 +13,7 @@ from ..cooking_machine.dataset import Dataset, W_DIFF_BATCHES_1 from ..cooking_machine.models.example_score import ScoreExample from ..cooking_machine.models.blei_lafferty_score import BleiLaffertyScore +from ..cooking_machine.models import BaseScore ARTM_NINE = artm.version().split(".")[1] == "9" MAIN_MODALITY = "@text" @@ -230,3 +235,144 @@ def test_to_dummy_and_back_with_scores(experiment_enviroment): assert len(restored_topic_model.scores[custom_score_name]) == num_iterations assert len(restored_topic_model.scores[artm_score_name]) == num_iterations + + +@pytest.mark.parametrize( + 'should_compute', + [False, True, None, lambda i: False, lambda i: True, lambda i: i == 2] +) +def test_should_compute(experiment_enviroment, should_compute): + topic_model, dataset, experiment, dictionary = experiment_enviroment + + score_name = 'blei' + topic_model.scores.add( + BleiLaffertyScore( + name=score_name, + should_compute=should_compute, + ) + ) + score_should_compute = topic_model.custom_scores[score_name]._should_compute + + num_iters = 20 + last_iter = num_iters - 1 + + score_num_iters = sum( + 1 * [score_should_compute(i) for i in range(num_iters)] + ) + + if not score_should_compute(last_iter): + score_num_iters = score_num_iters + 1 + + topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iters) + model_scores = topic_model.scores + + assert len(model_scores[score_name]) == score_num_iters + + +def test_compute_on_custom_iterations(experiment_enviroment): + topic_model, dataset, experiment, dictionary = experiment_enviroment + + score_name_a = 'blei' + score_should_compute_a = lambda iter: iter == 5 # noqa E731 + topic_model.scores.add( + BleiLaffertyScore( + name=score_name_a, + should_compute=score_should_compute_a, + ) + ) + + score_name_b = 'perp' + score_should_compute_b = lambda iter: iter % 2 == 0 # noqa E731 + topic_model.scores.add( + BleiLaffertyScore( + name=score_name_b, + should_compute=score_should_compute_b, + ) + ) + + num_iters = 20 + last_iter = num_iters - 1 + + score_num_iters_a = sum( + 1 * [score_should_compute_a(i) for i in range(num_iters)] + ) + score_num_iters_b = sum( + 1 * [score_should_compute_b(i) for i in range(num_iters)] + ) + + if not score_should_compute_a(last_iter): + score_num_iters_a = score_num_iters_a + 1 + if not score_should_compute_b(last_iter): + score_num_iters_b = score_num_iters_b + 1 + + topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iters) + model_scores = topic_model.scores + + assert len(model_scores[score_name_a]) == score_num_iters_a + assert len(model_scores[score_name_b]) == score_num_iters_b + + +def test_precomputed(experiment_enviroment): + topic_model, dataset, experiment, dictionary = experiment_enviroment + + class SlowScore(BaseScore): + def __init__(self, name): + super().__init__(name=name) + + self._data_key = 'some_precomputed_data_key' + + def call(self, model: TopicModel, **kwargs): + precomputed_data = kwargs.get( + BaseScore._PRECOMPUTED_DATA_PARAMETER_NAME, dict() + ) + + if self._data_key in precomputed_data: + return 0 + + time.sleep(1) + + precomputed_data[self._data_key] = 0 + + return 1 + + topic_model.scores.add(SlowScore(name='slow_score')) + + num_iters = 5 + start_time = datetime.datetime.now() + + topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iters) + + middle_time = datetime.datetime.now() + time_for_one_score = (middle_time - start_time).total_seconds() + + topic_model.scores.add(SlowScore(name='score_b')) + topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iters) + + end_time = datetime.datetime.now() + time_for_two_scores = (end_time - middle_time).total_seconds() + + assert time_for_two_scores / time_for_one_score < 1.1 + + +def test_score_with_no_precomputed_for_compatibility(experiment_enviroment): + topic_model, dataset, experiment, dictionary = experiment_enviroment + + class ScoreWithNoKwargs(BaseScore): + return_value = 3 + + def __init__(self, name): + super().__init__(name=name) + + def call(self, model: TopicModel): + time.sleep(0.01) + + return self.return_value + + score_name = 'score_without_precomputed' + topic_model.scores.add(ScoreWithNoKwargs(name=score_name)) + + num_iters = 5 + topic_model._fit(dataset.get_batch_vectorizer(), num_iterations=num_iters) + + assert len(topic_model.scores[score_name]) == num_iters + assert all(v == ScoreWithNoKwargs.return_value for v in topic_model.scores[score_name]) From e41cfed905df3f1a8f4b4d68e5a795acb09fb23f Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 00:43:44 +0300 Subject: [PATCH 3/9] change travis file: py v, run tessts command --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8b9f63f..61faee8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ cache: - $HOME/.ccache python: - - "3.6" + - "3.7" env: global: @@ -31,7 +31,7 @@ install: script: - cd topicnet - - py.test . --timeout=60 --cov + - py.test tests --timeout=45 --reruns 3 --reruns-delay 3 --cov after_success: - codecov From de3d87dc32673fee61d3c51b19f89b07118985c3 Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 00:45:05 +0300 Subject: [PATCH 4/9] add new reqs --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 10744c4..0b0e970 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,14 @@ dask[dataframe] dill ipython jinja2 +numba numexpr numpy pandas plotly pytest pytest-cov +pytest-rerunfailures pytest-timeout scikit-learn scipy From 96099309fbcb303e95a0584cbd9bad36bcd2ec0f Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 00:47:22 +0300 Subject: [PATCH 5/9] modify setup: reqs, version --- setup.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 03087dc..9207168 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ setup( - name = 'topicnet', - packages = [ + name='topicnet', + packages=[ 'topicnet', 'topicnet.cooking_machine', 'topicnet.cooking_machine.cubes', @@ -12,14 +12,14 @@ 'topicnet.dataset_manager', 'topicnet.viewers' ], - version = '0.7.1', + version='0.8.0', license='MIT', - description = 'TopicNet is a module for topic modelling using ARTM algorithm', - author = 'Machine Intelligence Laboratory', - author_email = 'alex.goncharov@phystech.edu', - url = 'https://github.com/machine-intelligence-laboratory/TopicNet', - download_url = 'https://github.com/machine-intelligence-laboratory/TopicNet/archive/v0.7.1.tar.gz', - keywords = [ + description='TopicNet is a module for topic modelling using ARTM algorithm', + author='Machine Intelligence Laboratory', + author_email='alex.goncharov@phystech.edu', + url='https://github.com/machine-intelligence-laboratory/TopicNet', + download_url='https://github.com/machine-intelligence-laboratory/TopicNet/archive/v0.8.0.tar.gz', + keywords=[ 'ARTM', 'topic modeling', 'regularization', @@ -33,7 +33,7 @@ 'dill', 'ipython', 'jinja2', - 'numba', + 'numba', 'numexpr', 'numpy', 'pandas', @@ -52,6 +52,6 @@ 'Topic :: Scientific/Engineering :: Artificial Intelligence', 'Topic :: Scientific/Engineering :: Information Analysis', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], ) From 91fef51225d00deb797c545c6031612ab54e02bd Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 00:48:14 +0300 Subject: [PATCH 6/9] add a couple of commas in setup --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9207168..99c510d 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ 'topicnet.cooking_machine.models', 'topicnet.cooking_machine.recipes', 'topicnet.dataset_manager', - 'topicnet.viewers' + 'topicnet.viewers', ], version='0.8.0', license='MIT', @@ -24,7 +24,7 @@ 'topic modeling', 'regularization', 'multimodal learning', - 'document vector representation' + 'document vector representation', ], install_requires=[ 'bigartm', From 48b1c7ab132eea024fe02892256763eb3e264b7e Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 01:06:20 +0300 Subject: [PATCH 7/9] add kostyl-run tests command in travis to speed up --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61faee8..8a82598 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,8 @@ install: script: - cd topicnet - - py.test tests --timeout=45 --reruns 3 --reruns-delay 3 --cov + - py.test tests -k "not tests/test_pipeline.py" --timeout=45 --reruns 3 --reruns-delay 3 --cov + # TODO: - py.test tests --timeout=45 --reruns 3 --reruns-delay 3 --cov after_success: - codecov - From 147c3ab983a6672f45f93d76d294b627ac42f8ff Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 01:25:02 +0300 Subject: [PATCH 8/9] add section about contribution --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7b8c06a..b194ee2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

      TopicNet

      +

      TopicNet

      @@ -31,9 +31,9 @@ It aims at automating model training routine freeing more time for artistic proc Consider using TopicNet if: -* you want to explore BigARTM functionality without writing an overhead. -* you need help with rapid solution prototyping. -* you want to build a good topic model quickly (out-of-box, with default parameters). +* you want to explore BigARTM functionality without writing an overhead; +* you need help with rapid solution prototyping; +* you want to build a good topic model quickly (out-of-box, with default parameters); * you have an ARTM model at hand and you want to explore it's topics. `TopicNet` provides an infrastructure for your prototyping with the help of `Experiment` class and helps to observe results of your actions via [`viewers`](topicnet/viewers) module. @@ -351,7 +351,20 @@ parameters:[ ] ``` +# Contribution + +If you find a bug, or if you would like the library to have some new features — you are welcome to contact us or create an issue or a pull request! + +It also worth noting that TopicNet library is always open to improvements in several areas: + +* New custom regularizers. +* New topic model scores. +* New topic models or recipes to train topic models for a particular task/with some special properties. +* New datasets (so as to make them available for everyone to download and conduct experiments with topic models). + + # Citing TopicNet + When citing `topicnet` in academic papers and theses, please use this BibTeX entry: ``` From 1d005304b4362a856eb20ebb515efed7eb47840c Mon Sep 17 00:00:00 2001 From: Vasiliy Alekseev Date: Tue, 7 Jul 2020 02:05:28 +0300 Subject: [PATCH 9/9] [ci skip] fix link in demos readme --- topicnet/demos/README.md | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/topicnet/demos/README.md b/topicnet/demos/README.md index a33f51a..384785f 100644 --- a/topicnet/demos/README.md +++ b/topicnet/demos/README.md @@ -1,33 +1,37 @@ -# Demo +# Demos + This section provides demonstrations of how to use this library in NLP tasks. -1. [RTL-Wiki-Preprocessing](RTL-Wiki-Preprocessing.ipynb) -- notebook working with a dataset introduced in [1]. It serves as an example of a typical preprocessing pipeline: getting a dataset, lemmatizing it, extracting n-grams/collocations, writing data in VW format +1. [RTL-Wiki-Preprocessing](RTL-Wiki-Preprocessing.ipynb) — notebook working with a dataset introduced in [1]. It serves as an example of a typical preprocessing pipeline: getting a dataset, lemmatizing it, extracting n-grams/collocations, writing data in VW format + +2. [RTL-Wiki-Building-Topic-Mode](RTL-Wiki-Building-Topic-Model.ipynb) — notebook with first steps to build topic model by consequently tuning its hyperparameters -2. [RTL-Wiki-Building-Topic-Mode](RTL-Wiki-Building-Topic-Model.ipynb) -- notebook with first steps to build topic model by consequently tuning its hyperparameters +3. [Visualizing-Your-Model-Documents](Visualizing-Your-Model-Documents.ipynb) — notebook providing a fresh outlook on unstructured document collection with the help of a topic model -3. [Visualizing-Your-Model-Documents](Visualizing-Your-Model-Documents.ipynb) -- notebook providing a fresh outlook on unstructured document collection with the help of a topic model +4. [20NG-Preprocessing](20NG-Preprocessing.ipynb) — preparing data from a well-know 20 Newsgroups dataset -4. [20NG-Preprocessing](20NG-Preprocessing.ipynb) -- preparing data from a well-know 20 Newsgroups dataset +5. [20NG-GenSim-vs-TopicNet](20NG-GenSim-vs-TopicNet.ipynb) — a comparison between two topic models build by Gensim and TopicNet library. In the notebook we compare model topics by calculating their UMass coherence measure with the help of [Palmetto](https://palmetto.demos.dice-research.org/) and using Jaccard measure to compare topic top-tokens diversity -5. [20NG-GenSim-vs-TopicNet](20NG-GenSim-vs-TopicNet.ipynb) -- a comparison between two topic models build by Gensim and TopicNet library. In the notebook we compare model topics by calculating their UMass coherence measure with the help of [Palmetto](https://palmetto.demos.dice-research.org/) and using Jaccard measure to compare topic top-tokens diversity +6. [PostNauka-Building-Topic-Model](PostNauka-Building-Topic-Model.ipynb) — an analog of the RTL-Wiki notebook performed on the corpus of Russian pop-science articles given by postnauka.ru -6. [PostNauka-Building-Topic-Model](PostNauka-Building-Topic-Model.ipynb)-- an analog of the RTL-Wiki notebook performed on the corpus of Russian pop-science articles given by postnauka.ru +7. [PostNauka-Recipe](PostNauka-Recipe.ipynb) — a demonstration of rapid-prototyping methods provided by the library -7. [PostNauka-Recipe](PostNauka-Recipe.ipynb) -- a demonstration of rapid-prototyping methods provided by the library +8. [Coherence-Maximization-Recipe](Coherence-Maximization-Recipe.ipynb) — a recipe for hyperparameter search in regard to custom Coherence metric -8. [Coherence-Maximization-Recipe](Coherence-Maximization-Recipe.ipynb) -- a recipe for hyperparameter search in regard to custom Coherence metric +9. [Topic-Prior-Regularizer-Tutorial](Topic-Prior-Regularizer-Tutorial.ipynb) — a demonstration of the approach to learning topics from the unbalanced corpus -9. [Topic-Prior-Regularizer-Tutorial](Topic-Prior-Regularizer-Tutorial.ipynb) -- a demonstration of the approach to learning topics from the unbalanced corpus +10. [Making-Decorrelation-and-Topic-Selection-Friends](Making-Decorrelation-and-Topic-Selection-Friends.ipynb) — reproduction of a very complicated experiment on automatically learning optimal number of topics from the collection. Hurdle is -- both needed regularizers when working together nullify token-topic matrix. -10. [Making-Decorrelation-and-Topic-Selection-Friends](Making-Decorrelation-and-Topic-Selection-Friends.ipynb) -- reproduction of a very complicated experiment on automatically learning optimal number of topics from the collection. Hurdle is -- both needed regularizers when working together nullify token-topic matrix. +11. [Topic-Thetaless-Regularizer](Topic-Thetaless-Regularizer.ipynb) — the Additive Regularization of Topic Models (ARTM) formalism views the topic modeling task as an optimization problem and leverages various computational and optimization tricks to make it converge faster and more predictably. This is an example of such optimization (let's reduce the number of inferred parameters), interpreted as a powerful custom regularizer. -11. [Topic-Thetaless-Regularizer](topic_thetaless_regularizer.ipynb) -- The Additive Regularization of Topic Models (ARTM) formalism views the topic modeling task as an optimization problem and leverages various computational and optimization tricks to make it converge faster and more predictably. This is an example of such optimization (let's reduce the number of inferred parameters), interpreted as a powerful custom regularizer. +## References ----- [1](https://dl.acm.org/doi/10.5555/2984093.2984126) Jonathan Chang, Jordan Boyd-Graber, Sean Gerrish, Chong Wang, and David M. Blei. 2009. Reading tea leaves: how humans interpret topic models. In Proceedings of the 22nd International Conference on Neural Information Processing Systems (NIPS’09). Curran Associates Inc., Red Hook, NY, USA, 288–296. ----- -P.S. All the guides are supposed to contain **working** examples of the library code. + +## P.S. + +All the guides are supposed to contain **working** examples of the library code. If you happen to find code that is no longer works, please write about it in the library issues. We will try to resolve it as soon as possible and plan to include fixes in the nearest releases.