Package: hojichar
HojiCharOfficial docs:https://hojichar.github.io/HojiChar/hojichar.htmlFeaturesHojiChar provides a way to combine multiple arbitrary text processing tasks into a streamlined pipeline.The sequence of operations can be described declaratively, ensuring portability.HojiChar allows users to gather detailed statistical information from large amounts of text during processing.It enables management of any Python text processing tasks, providing a Command Line Interface (CLI) capable of parallel processing.Background and what is for HojiCharText preprocessing is far from a one-size-fits-all process. Depending on the data source and the specific task at hand, various steps including normalization, noise removal, and filtering may be necessary. Not all texts require the same level of preprocessing. For instance, relatively clean texts may only need minimal filtering, while "dirtier" sources like Common Crawl data often require more thorough processing. As a result, the preprocessing profile has to be tailored to each specific domain.Many preprocessing operations can be viewed as filters, taking string as input, applying a transformation, and outputting the processed string. Even though these operations might seem straightforward individually, managing them in a multi-layered, efficient manner can be challenging.Inspired bytorchvision.transformsandiver56/audiomentations, HojiChar addresses these challenges. It enables users to define each text processing step as a class inheriting fromhojichar.Filterand usehojichar.Composeto chain them together into a single filter. By writing out theComposerecipe as a profile, the preprocessing process for a specific domain's text can be made portable. Moreover,Composeautomatically logs various metrics for each filter, such as byte changes, processing time, and number of rejected texts. This allows users to assess the validity of each operation and consider trade-offs between computation time and performance.While there are other text normalization tools available, most are designed to perform a specific set of operations. Text preprocessing, despite its importance, is often considered a mundane task compared to machine learning or artificial intelligence tasks. As a result, many existing solutions can be ad hoc, poorly maintained, or inadequately tested. Recognizing these issues, we developed HojiChar as a robust tool for configuring text preprocessing.Installpip install hojicharDefining a Compose ObjectTheComposeclass in HojiChar allows you to create a sequence of text processing filters.fromhojicharimportCompose,document_filterscleaner=Compose([document_filters.JSONLoader(key="text"),document_filters.AcceptJapanese(),document_filters.DocumentLengthFilter(min_doc_len=0,max_doc_len=1000),document_filters.ExampleHojiChar(),document_filters.JSONDumper()])When aComposeobject is called, it accepts a string and returns the processed string.>>>cleaner('{"text": "こんにちは、"}'){"text":"こんにちは、<hojichar>"}The filter pipeline above accomplishes the following steps:Extracts the value from the'text'key in the JSON object.Discards the string if it's not in Japanese.Rejects any text shorter than 0 characters or longer than 1000 characters.Appends<hojichar>to the string.Outputs the processed string as JSON with the key "text".The filters used in the pipeline are predefined filters found inhojichar.filters.While HojiChar provides some fundamental text processing filters and plans to add more in the future, users can also define their custom filters.User-defined FiltersA filter composing aComposeobject is a class that inherits theFilterclass and implements the text processing within theapplyfunction.fromhojichar.core.filter_interfaceimportFilterclassYourFilter(Filter):defapply(self,document):text=document.text"""Write your text transformation..."""document.text=textreturndocumentTheapplymethod accepts ahojichar.Documenttype as an argument and returns it after the transformations. TheDocumentis a class that encapsulates a string.Reject documentsThehojichar.Documenthas anis_rejectedattribute. If a filter sets this flag toTrue,Composewill discard the document during processing.Definition of__init__for custom filterWhen creating a user-defined class and applying a custom constructor, make sure to initialize the parent class.classYourFilter(Filter):def__init__(self,your_param,*args,**kwargs)->None:super().__init__(*args,**kwargs)self.your_param=your_paramdefapply(self,document):text=document.texttext=process(text,self.your_param)document.text=textreturndocumentThis is because TheFilterclass implicitly has several arguments, one of which isp.cleaner=Compose([document_filters.JSONLoader(key="text"),document_filters.AcceptJapanese(p=0.5),document_filters.JSONDumper()])Thepargument passed to thedocument_filters.AcceptJapaneseconstructor determines the probability of applying the filter; with a probability of1-p, it acts as an identity function. This behavior is defined in the parent classhojichar.Filter.Additional Notes on ComposeEven though the behavior of aComposeobject when called is a text-in, text-out function,Composeitself also inherits from theFilterclass. Therefore, applying theapplymethod to aComposeobject results inhojihcar.Documentclass being used as input and output.Composeclass behaves like a Filter. If you add a Compose object as one of the filters in the constructor of Compose, the filter will be unfolded recursively.You can access various statistics regarding the processing performed byComposethroughCompose.statisticsorCompose.statistics_obj.Compose.statisticsis a dictionary like above.{"total_info":{"processed_num":10928,"discard_num":5513,"input_MB":104.514584,"output_MB":25.33024,"cumulative_time":114.071047143,"total_token_num":0},"layers_info":[{"name":"0-JSONLoader","discard_num":0,"diff_MB":-1.9647932052612305,"cumulative_time":0.420034328,"params":{"name":"JSONLoader","p":1,"skip_rejected":true,"key":"text","ignore":true}},{"name":"1-DocumentNormalizer","discard_num":0,"diff_MB":-1.5221118927001953,"cumulative_time":8.286988707,"params":{"name":"DocumentNormalizer","p":1,"skip_rejected":true}},{"name":"2-DocumentLengthFilter","discard_num":344,"diff_MB":-0.05566596984863281,"cumulative_time":0.093768306,"params":{"name":"DocumentLengthFilter","p":1,"skip_rejected":true,"min_doc_len":100,"max_doc_len":null}},]}Compose.statistics_objis ahojichar.StatsContainerclass. Thehojichar.StatsContainerclass stores the raw values of the statistics dictionary, and addition operations are defined to easily calculate the total statistics processed with the same filter. You can get the statistics dictionary by callingCompose.statistics_obj.get_human_readable_values().Parallel application ofComposeThehojichar.Parallelclass allows for the application ofComposeto an iterable ofDocumentconcurrently. This class empowers users to process vast collections of documents by harnessing the power of multiple CPU cores.Example usage ofParallelclass to proces a very large JSON Lines file concurrently.importhojicharinput_file="your_text.jsonl"input_doc_iter=(hojichar.Document(line)forlineinopen(input_file))cleaner=hojichar.Compose([hojichar.document_filters.JSONLoader(),hojichar.document_filters.DocumentNormalizer(),# Insert your filtershojichar.document_filters.JSONDumper(),])withhojichar.Parallel(cleaner,num_jobs=10)aspfilter:out_doc_iter=pfilter.imap_apply(input_doc_iter)withopen("your_processed_text.jsonl","w")asfp:fordocinout_doc_iter:fp.write(doc.text+"\n")Always use theParallelclass within awithstatement.Parallel.imap_apply(doc_iter)processes an iterator ofDocumentand returns an iterator of the processed documents.For additional options and details about theParallelclass, please refer to the official documentation.CLI tool and preprocessing profileHojiChar provides CLI tools for text preprocess pipeline.User defines a series of preprocessing into a python file as profile.Example:cat<your_text.jsonl>|hojichar-pyour_preprocessing_profile.py-oyour_text_preprocessed.jsonlhojichar --helpusage: hojichar [-h] --profile <profile.py> [--args ARGS [ARGS ...]] [--output OUTPUT] [--input INPUT] [--dump-stats <path to stats.json>] [--exit-on-error] [--all] [--jobs JOBS]

  options:
  -h, --help            show this help message and exit
  --profile <profile.py>, -p <profile.py>
                          Path to a Python file that implements your custom filter.
  --args ARGS [ARGS ...]
                          Pass additional arguments to the profile. Use it like `--args arg1 arg2` etc. The arguments should be space-separated.
  --output OUTPUT, -o OUTPUT
                          Specifies the path for the output file. Defaults to standard output.
  --input INPUT, -i INPUT
                          Specifies the path for the input file. Defaults to standard input. If set this path, the progress bar is enabled.
  --dump-stats <path to stats.json>
                          Dump statistics to file. If the file exists, it will be appended.
  --exit-on-error       Exit if an exception occurs during filtering. Useful for debugging custom filters.
  --all                 A flag that specifies whether to include discarded samples. This is useful when inspecting discarded samples.
  --jobs JOBS, -j JOBS  The number ob parallel jobs. By default, the nuber of the CPU core.Definition of ProfileHojiChar CLI receives a series of preprocessing as a profile.The preprocessing profile is provided as a Python file. Two patterns of the file are allowed.hojichar.utils.load_compose.load_compose() loads these profile.FILTERprofilehojichar.Composemust be defined asFILTERvariable.Example.importjsonfromhojicharimportCompose,Filterfromhojichar.filters.document_filtersimportExampleHojiChar,JSONLoaderclassJSONDumper(Filter):defapply(self,document):text=document.textdocument.text=json.dumps({"text":text},ensure_ascii=False)returndocument# FILTER must define Compose object.FILTER=Compose([JSONLoader(),ExampleHojiChar(),JSONDumper(),])Pass the texts to the filter you have defined using a pipe as follows.cat<your_file>|hojichar-pexample_profile.pyhojichar.utils.load_compose.load_filter_from_file()loads this type of profile.FACTORYprofileA callable function that returnshojichar.Composemust be defined asFACTORYvariable.The callable can receive arguments. In this way, parameters can be passed to the profile.Some kinds of value are not preferred to static. For example, random seeds and some flags modify the behavior of a filter, etcFACTORYprovides a mechanism to pass those values as arguments to the preprocessing.Example.importjsonfromhojicharimportCompose,Filterfromhojichar.filters.document_filtersimportJSONLoaderclassAddSomething(Filter):#  Concat some value after every document.def__init__(self,something:str,*args,**kwargs)->None:self.something=somethingdefapply(self,document):text=document.text+self.somethingdocument.text=textreturndocumentclassJSONDumper(Filter):defapply(self,document):text=document.textdocument.text=json.dumps({"text":text},ensure_ascii=False)returndocumentdefcallback(something):returnCompose([JSONLoader(),AddSomething(something),JSONDumper(),])# FACTORY must be callable which returns Compose object.FACTORY=callbackUsingFACTORYprofile with arguments in CLI.cat<your_file>|hojichar-pexample_profile.py--argsarg1arg2hojichar.utils.load_compose.load_parametrized_filter_from_file()orload_factory_from_fileloads this type of profile.For DevelopersInstalling from the Source DirectoryTo install the package, execute the following commands:git clone https://github.com/HojiChar/HojiChar.git
cd HojiChar
poetry installTo install packages related to development, use:poetry install --extras "dev lint test doc"TestingSome filters incorporate doctests. You can run these tests with the command:pytest --doctest-modules .This command should be executed from the root of the project.Code styleHojiChar requires type hints for all code. Type checking is performed in continuous integration (CI) in addition to the pytest tests.HojiChar code is subject to inspection by the Flake8 Linter and is formatted using Black and isort. For configuration details, please refer topyproject.toml. You can perform linting and formatting from the root of the project using the following commands:Lintingpoetry run task lintFormtattingpoetry run task formatBuilding the DocumentationWe use Pdoc for building the documentation. You can build the documentation using the following command:pdoc -o docs hojicharRun this command from the project root.In practice, the process of building the documentation is automated by CI. When a Pull Request is merged into the main branch, the documentation is built in thedocs/directory of thedocsbranch. This directory is then deployed to the official documentation site by GitHub Pages.Creating a Source TarballTo create a source tarball, for instance, for packaging or distribution, run the following command:poetry buildThe tarball will be created in the dist directory. This command will compile the source code, and the resulting tarball can be installed with no additional dependencies other than the Python standard library.Creating a Release and Uploading it to PyPIThis command is primarily used by the project manager to create a release and upload it to PyPI.Versions uploaded to PyPI are identified by git tags. The__version__variable in__init__.pyor theversionentry inpyproject.tomlare ignored. Thepoetry-dynamic-versioningPoetry plugin is used to implement this process.To add the plugin, use:poetry self add "poetry-dynamic-versioning[plugin]"The steps to push to PyPI are as follows, although in actuality, the process is automated by CI when a GitHub release is created from the tag.git checkout v0.1.2
poetry config pypi-token.pypi <API TOKEN>
poetry build 
poetry publishThe actual task for the manager is to apply the appropriate tag to the commit to be released and to create the release from GitHub:git tag -a v0.1.2 -m "Version 0.1.2"
git push origin v0.1.2