Repository: ku-nlp/kwja (language: python)

Issue:
Fix bugs of word module writer



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 00000000..7c4e0b1e
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,29 @@
+name: Publish
+
+on:
+  release:
+    types: [ created ]
+
+jobs:
+  build-publish:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python 3.9
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.9"
+      - name: Install Poetry
+        run: pipx install poetry
+      - name: Add path for Python packages
+        run: echo "$HOME/.local/bin" >> $GITHUB_PATH
+      - name: Install dependencies
+        run: poetry install --no-interaction --only main
+      - name: Build package
+        run: poetry build
+
+      - name: Publish package
+        uses: pypa/gh-action-pypi-publish@release/v1
+        with:
+          password: ${{ secrets.PYPI_API_TOKEN }}
+          verbose: true
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2549a23d..5b8683f9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,6 +18,10 @@ repos:
     hooks:
       - id: isort
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.971
+    rev: v0.982
     hooks:
       - id: mypy
+        additional_dependencies:
+          - hydra-core==1.2.0
+          - torch==1.12.1
+          - transformers==4.24.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2bd69f42..291adce7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+- Fix bugs of word module writer and interactive mode.
 
 ## [1.2.0] - 2022-10-27
 ### Added
diff --git a/poetry.lock b/poetry.lock
index b02833e0..9bfec500 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -45,6 +45,28 @@ category = "main"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "appnope"
+version = "0.1.3"
+description = "Disable App Nap on macOS >= 10.9"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "asttokens"
+version = "2.1.0"
+description = "Annotate AST trees with source code positions"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[package.extras]
+test = ["astroid (<=2.5.3)", "pytest"]
+
 [[package]]
 name = "async-timeout"
 version = "4.0.2"
@@ -65,7 +87,15 @@ python-versions = ">=3.5"
 dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
 docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
 tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
+
+[[package]]
+name = "backcall"
+version = "0.2.0"
+description = "Specifications for callback functions passed in to an API"
+category = "dev"
+optional = false
+python-versions = "*"
 
 [[package]]
 name = "BetterJSONStorage"
@@ -117,7 +147,7 @@ optional = false
 python-versions = ">=3.6.0"
 
 [package.extras]
-unicode_backport = ["unicodedata2"]
+unicode-backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
@@ -182,6 +212,14 @@ python-versions = "*"
 [package.dependencies]
 Cython = "*"
 
+[[package]]
+name = "decorator"
+version = "5.1.1"
+description = "Decorators for Humans"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
 [[package]]
 name = "docker-pycreds"
 version = "0.4.0"
@@ -193,6 +231,17 @@ python-versions = "*"
 [package.dependencies]
 six = ">=1.4.0"
 
+[[package]]
+name = "executing"
+version = "1.2.0"
+description = "Get the currently executing AST node of a frame, and other information"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+tests = ["asttokens", "littleutils", "pytest", "rich"]
+
 [[package]]
 name = "filelock"
 version = "3.8.0"
@@ -286,7 +335,7 @@ six = ">=1.9.0"
 
 [package.extras]
 aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"]
-enterprise_cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"]
+enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"]
 pyopenssl = ["pyopenssl (>=20.0.0)"]
 reauth = ["pyu2f (>=0.1.5)"]
 
@@ -422,6 +471,55 @@ category = "main"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "ipdb"
+version = "0.13.9"
+description = "IPython-enabled pdb"
+category = "dev"
+optional = false
+python-versions = ">=2.7"
+
+[package.dependencies]
+decorator = {version = "*", markers = "python_version > \"3.6\""}
+ipython = {version = ">=7.17.0", markers = "python_version > \"3.6\""}
+setuptools = "*"
+toml = {version = ">=0.10.2", markers = "python_version > \"3.6\""}
+
+[[package]]
+name = "ipython"
+version = "8.6.0"
+description = "IPython: Productive Interactive Computing"
+category = "dev"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+appnope = {version = "*", markers = "sys_platform == \"darwin\""}
+backcall = "*"
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+decorator = "*"
+jedi = ">=0.16"
+matplotlib-inline = "*"
+pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
+pickleshare = "*"
+prompt-toolkit = ">3.0.1,<3.1.0"
+pygments = ">=2.4.0"
+stack-data = "*"
+traitlets = ">=5"
+
+[package.extras]
+all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.20)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"]
+black = ["black"]
+doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"]
+kernel = ["ipykernel"]
+nbconvert = ["nbconvert"]
+nbformat = ["nbformat"]
+notebook = ["ipywidgets", "notebook"]
+parallel = ["ipyparallel"]
+qtconsole = ["qtconsole"]
+test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
+test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.20)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"]
+
 [[package]]
 name = "jaconv"
 version = "0.3"
@@ -430,6 +528,21 @@ category = "main"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "jedi"
+version = "0.18.1"
+description = "An autocompletion tool for Python that can be used for text editors."
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+parso = ">=0.8.0,<0.9.0"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"]
+
 [[package]]
 name = "jinf"
 version = "1.0.4"
@@ -486,6 +599,17 @@ category = "main"
 optional = false
 python-versions = ">=3.7"
 
+[[package]]
+name = "matplotlib-inline"
+version = "0.1.6"
+description = "Inline Matplotlib backend for Jupyter"
+category = "dev"
+optional = false
+python-versions = ">=3.5"
+
+[package.dependencies]
+traitlets = "*"
+
 [[package]]
 name = "msgpack"
 version = "1.0.4"
@@ -599,6 +723,18 @@ pytz = ">=2020.1"
 [package.extras]
 test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
 
+[[package]]
+name = "parso"
+version = "0.8.3"
+description = "A Python Parser"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.extras]
+qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
+testing = ["docopt", "pytest (<6.0.0)"]
+
 [[package]]
 name = "pathtools"
 version = "0.1.2"
@@ -607,6 +743,25 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "pexpect"
+version = "4.8.0"
+description = "Pexpect allows easy control of interactive console applications."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+ptyprocess = ">=0.5"
+
+[[package]]
+name = "pickleshare"
+version = "0.7.5"
+description = "Tiny 'shelve'-like database with concurrency support"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "pluggy"
 version = "1.0.0"
@@ -633,6 +788,17 @@ six = "*"
 [package.extras]
 test = ["coveralls", "futures", "mock", "pytest (>=2.7.3)", "pytest-benchmark", "pytest-cov"]
 
+[[package]]
+name = "prompt-toolkit"
+version = "3.0.31"
+description = "Library for building powerful interactive command lines in Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6.2"
+
+[package.dependencies]
+wcwidth = "*"
+
 [[package]]
 name = "protobuf"
 version = "3.19.6"
@@ -652,6 +818,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 [package.extras]
 test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
 
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+description = "Run a subprocess in a pseudo terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "pure-cdb"
 version = "4.0.0"
@@ -660,6 +834,17 @@ category = "main"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "pure-eval"
+version = "0.2.2"
+description = "Safely evaluate AST nodes without side effects"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+tests = ["pytest"]
+
 [[package]]
 name = "py"
 version = "1.11.0"
@@ -833,10 +1018,10 @@ tqdm = ">=4.57.0"
 typing-extensions = ">=4.0.0"
 
 [package.extras]
-all = ["cloudpickle (>=1.3)", "codecov (>=2.1)", "comet-ml (>=3.1.12)", "coverage (>=6.4)", "deepspeed (>=0.6.0)", "fairscale (>=0.4.5)", "fastapi", "gcsfs (>=2021.5.0)", "gym[classic_control] (>=0.17.0)", "hivemind (>=1.0.1)", "horovod (>=0.21.2,!=0.24.0)", "hydra-core (>=1.0.5)", "ipython[all]", "jsonargparse[signatures] (>=4.12.0)", "matplotlib (>3.1)", "mlflow (>=1.0.0)", "mypy (==0.971)", "neptune-client (>=0.10.0)", "omegaconf (>=2.0.5)", "onnxruntime", "pandas (>1.0)", "pre-commit (>=1.0)", "protobuf (<=3.20.1)", "psutil", "pytest (>=7.0)", "pytest-cov", "pytest-forked", "pytest-rerunfailures (>=10.2)", "rich (>=10.14.0,!=10.15.0.a)", "scikit-learn (>0.22.1)", "torchtext (>=0.10)", "torchvision (>=0.10)", "uvicorn", "wandb (>=0.10.22)"]
+all = ["cloudpickle (>=1.3)", "codecov (>=2.1)", "comet-ml (>=3.1.12)", "coverage (>=6.4)", "deepspeed (>=0.6.0)", "fairscale (>=0.4.5)", "fastapi", "gcsfs (>=2021.5.0)", "gym[classic-control] (>=0.17.0)", "hivemind (>=1.0.1)", "horovod (>=0.21.2,!=0.24.0)", "hydra-core (>=1.0.5)", "ipython[all]", "jsonargparse[signatures] (>=4.12.0)", "matplotlib (>3.1)", "mlflow (>=1.0.0)", "mypy (==0.971)", "neptune-client (>=0.10.0)", "omegaconf (>=2.0.5)", "onnxruntime", "pandas (>1.0)", "pre-commit (>=1.0)", "protobuf (<=3.20.1)", "psutil", "pytest (>=7.0)", "pytest-cov", "pytest-forked", "pytest-rerunfailures (>=10.2)", "rich (>=10.14.0,!=10.15.0.a)", "scikit-learn (>0.22.1)", "torchtext (>=0.10)", "torchvision (>=0.10)", "uvicorn", "wandb (>=0.10.22)"]
 deepspeed = ["deepspeed (>=0.6.0)"]
 dev = ["cloudpickle (>=1.3)", "codecov (>=2.1)", "comet-ml (>=3.1.12)", "coverage (>=6.4)", "fastapi", "gcsfs (>=2021.5.0)", "hydra-core (>=1.0.5)", "jsonargparse[signatures] (>=4.12.0)", "matplotlib (>3.1)", "mlflow (>=1.0.0)", "mypy (==0.971)", "neptune-client (>=0.10.0)", "omegaconf (>=2.0.5)", "onnxruntime", "pandas (>1.0)", "pre-commit (>=1.0)", "protobuf (<=3.20.1)", "psutil", "pytest (>=7.0)", "pytest-cov", "pytest-forked", "pytest-rerunfailures (>=10.2)", "rich (>=10.14.0,!=10.15.0.a)", "scikit-learn (>0.22.1)", "torchtext (>=0.10)", "uvicorn", "wandb (>=0.10.22)"]
-examples = ["gym[classic_control] (>=0.17.0)", "ipython[all]", "torchvision (>=0.10)"]
+examples = ["gym[classic-control] (>=0.17.0)", "ipython[all]", "torchvision (>=0.10)"]
 extra = ["gcsfs (>=2021.5.0)", "hydra-core (>=1.0.5)", "jsonargparse[signatures] (>=4.12.0)", "matplotlib (>3.1)", "omegaconf (>=2.0.5)", "protobuf (<=3.20.1)", "rich (>=10.14.0,!=10.15.0.a)", "torchtext (>=0.10)"]
 fairscale = ["fairscale (>=0.4.5)"]
 hivemind = ["hivemind (>=1.0.1)"]
@@ -885,7 +1070,7 @@ urllib3 = ">=1.21.1,<1.27"
 
 [package.extras]
 socks = ["PySocks (>=1.5.6,!=1.5.7)"]
-use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
 [[package]]
 name = "requests-oauthlib"
@@ -904,11 +1089,11 @@ rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
 
 [[package]]
 name = "rhoknp"
-version = "0.4.4"
-description = "Yet another Python binding for Juman++/KNP"
+version = "0.4.6"
+description = "Yet another Python binding for Juman++/KNP/KWJA"
 category = "main"
 optional = false
-python-versions = ">=3.8,<3.11"
+python-versions = ">=3.8,<3.12"
 
 [package.dependencies]
 PyYAML = ">=6.0,<7.0"
@@ -1008,7 +1193,7 @@ falcon = ["falcon (>=1.4)"]
 fastapi = ["fastapi (>=0.79.0)"]
 flask = ["blinker (>=1.1)", "flask (>=0.11)"]
 httpx = ["httpx (>=0.16.0)"]
-pure_eval = ["asttokens", "executing", "pure-eval"]
+pure-eval = ["asttokens", "executing", "pure-eval"]
 pyspark = ["pyspark (>=2.4.4)"]
 quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]
 rq = ["rq (>=0.6)"]
@@ -1077,6 +1262,22 @@ category = "dev"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "stack-data"
+version = "0.6.0"
+description = "Extract data from python stack frames and tracebacks for informative displays"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+asttokens = ">=2.1.0"
+executing = ">=1.2.0"
+pure-eval = "*"
+
+[package.extras]
+tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
+
 [[package]]
 name = "tensorboard"
 version = "2.10.1"
@@ -1144,6 +1345,14 @@ python-versions = "*"
 docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"]
 testing = ["datasets", "numpy", "pytest", "requests"]
 
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+
 [[package]]
 name = "tomli"
 version = "2.0.1"
@@ -1203,6 +1412,18 @@ notebook = ["ipywidgets (>=6)"]
 slack = ["slack-sdk"]
 telegram = ["requests"]
 
+[[package]]
+name = "traitlets"
+version = "5.5.0"
+description = ""
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
+test = ["pre-commit", "pytest"]
+
 [[package]]
 name = "transformers"
 version = "4.19.4"
@@ -1232,7 +1453,7 @@ dev = ["GitPython (<3.1.19)", "Pillow", "black (>=22.0,<23.0)", "codecarbon (==1
 dev-tensorflow = ["GitPython (<3.1.19)", "Pillow", "black (>=22.0,<23.0)", "cookiecutter (==1.7.3)", "datasets", "faiss-cpu", "flake8 (>=3.8.3)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf (<=3.20.1)", "psutil", "pyctcdecode (>=0.3.0)", "pytest", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorflow (>=2.3)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.11.1,!=0.11.3,<0.13)"]
 dev-torch = ["GitPython (<3.1.19)", "Pillow", "black (>=22.0,<23.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets", "faiss-cpu", "flake8 (>=3.8.3)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.1)", "psutil", "pyctcdecode (>=0.3.0)", "pytest", "pytest-timeout", "pytest-xdist", "ray[tune]", "rjieba", "rouge-score", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.13)", "torch (>=1.0)", "torchaudio", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"]
 docs = ["Pillow", "codecarbon (==1.2.0)", "flax (>=0.3.5)", "hf-doc-builder", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "librosa", "onnxconverter-common", "optax (>=0.0.8)", "optuna", "phonemizer", "protobuf (<=3.20.1)", "pyctcdecode (>=0.3.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.3)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.13)", "torch (>=1.0)", "torchaudio"]
-docs_specific = ["hf-doc-builder"]
+docs-specific = ["hf-doc-builder"]
 fairscale = ["fairscale (>0.3)"]
 flax = ["flax (>=0.3.5)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "optax (>=0.0.8)"]
 flax-speech = ["librosa", "phonemizer", "pyctcdecode (>=0.3.0)"]
@@ -1336,6 +1557,14 @@ media = ["bokeh", "moviepy", "numpy", "pillow", "plotly", "rdkit-pypi", "soundfi
 models = ["cloudpickle"]
 sweeps = ["sweeps (>=0.2.0)"]
 
+[[package]]
+name = "wcwidth"
+version = "0.2.5"
+description = "Measures the displayed width of unicode strings in a terminal"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "Werkzeug"
 version = "2.2.2"
@@ -1388,7 +1617,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 [metadata]
 lock-version = "1.1"
 python-versions = ">=3.8,<3.11"
-content-hash = "213e2e50447ecb29c6b3c8600aac9c0cdad3042a2f9baf5fb97fb125fd96fe95"
+content-hash = "573772b7e4cf703ce19e3758842631c1aad05b2a382a3eaf260e08d6262b2b8c"
 
 [metadata.files]
 absl-py = [
@@ -1491,6 +1720,14 @@ aiosignal = [
 antlr4-python3-runtime = [
     {file = "antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b"},
 ]
+appnope = [
+    {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
+    {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
+]
+asttokens = [
+    {file = "asttokens-2.1.0-py2.py3-none-any.whl", hash = "sha256:1b28ed85e254b724439afc783d4bee767f780b936c3fe8b3275332f42cf5f561"},
+    {file = "asttokens-2.1.0.tar.gz", hash = "sha256:4aa76401a151c8cc572d906aad7aea2a841780834a19d780f4321c0fe1b54635"},
+]
 async-timeout = [
     {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
     {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
@@ -1499,6 +1736,10 @@ attrs = [
     {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
     {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
 ]
+backcall = [
+    {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
+    {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
+]
 BetterJSONStorage = [
     {file = "BetterJSONStorage-1.3.0-py3-none-any.whl", hash = "sha256:93f0918f7edeecefa7bc152c2e64cc5d6ffea08239501062062d51847048dc1e"},
     {file = "BetterJSONStorage-1.3.0.tar.gz", hash = "sha256:b81e12bb26c762edcbccd8bf12f6f3f8aa56534dceb646809e7038ff66310860"},
@@ -1663,10 +1904,18 @@ dartsclone = [
     {file = "dartsclone-0.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:61768572849d9ce7af468bfa4be7dc0a9b00e31f8e060628860bd911c0b2b1c4"},
     {file = "dartsclone-0.10.2.tar.gz", hash = "sha256:a6835cf63d8a2947ea80cbdbd7b203b6f1102678c93937d05c119d49546d24b4"},
 ]
+decorator = [
+    {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
+    {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
+]
 docker-pycreds = [
     {file = "docker-pycreds-0.4.0.tar.gz", hash = "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4"},
     {file = "docker_pycreds-0.4.0-py2.py3-none-any.whl", hash = "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49"},
 ]
+executing = [
+    {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"},
+    {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"},
+]
 filelock = [
     {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
     {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
@@ -1830,9 +2079,20 @@ iniconfig = [
 ipadic = [
     {file = "ipadic-1.0.0.tar.gz", hash = "sha256:f5923d31eca6131acaaf18ed28d8998665b1347b640d3a6476f64650e9a71c07"},
 ]
+ipdb = [
+    {file = "ipdb-0.13.9.tar.gz", hash = "sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"},
+]
+ipython = [
+    {file = "ipython-8.6.0-py3-none-any.whl", hash = "sha256:91ef03016bcf72dd17190f863476e7c799c6126ec7e8be97719d1bc9a78a59a4"},
+    {file = "ipython-8.6.0.tar.gz", hash = "sha256:7c959e3dedbf7ed81f9b9d8833df252c430610e2a4a6464ec13cd20975ce20a5"},
+]
 jaconv = [
     {file = "jaconv-0.3.tar.gz", hash = "sha256:cc70c796c19a6765598c03eac59d1399a555a9a8839cc70e540ec26f0ec3e66e"},
 ]
+jedi = [
+    {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"},
+    {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"},
+]
 jinf = [
     {file = "jinf-1.0.4-py3-none-any.whl", hash = "sha256:e35bef8efb476c78a2d6d9bb3c60f722cdf5f0df4d7642b19b46b8cc10f8f23f"},
     {file = "jinf-1.0.4.tar.gz", hash = "sha256:749982c729a76ea73a075edb1fc9226c81f676b549497f9cb935898ae08f6495"},
@@ -1890,6 +2150,10 @@ MarkupSafe = [
     {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
     {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
 ]
+matplotlib-inline = [
+    {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
+    {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
+]
 msgpack = [
     {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"},
     {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"},
@@ -2150,9 +2414,21 @@ pandas = [
     {file = "pandas-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec"},
     {file = "pandas-1.5.0.tar.gz", hash = "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977"},
 ]
+parso = [
+    {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
+    {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
+]
 pathtools = [
     {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
 ]
+pexpect = [
+    {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
+    {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
+]
+pickleshare = [
+    {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
+    {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
+]
 pluggy = [
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
     {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
@@ -2160,6 +2436,10 @@ pluggy = [
 promise = [
     {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
 ]
+prompt-toolkit = [
+    {file = "prompt_toolkit-3.0.31-py3-none-any.whl", hash = "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d"},
+    {file = "prompt_toolkit-3.0.31.tar.gz", hash = "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148"},
+]
 protobuf = [
     {file = "protobuf-3.19.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:010be24d5a44be7b0613750ab40bc8b8cedc796db468eae6c779b395f50d1fa1"},
     {file = "protobuf-3.19.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11478547958c2dfea921920617eb457bc26867b0d1aa065ab05f35080c5d9eb6"},
@@ -2221,6 +2501,10 @@ psutil = [
     {file = "psutil-5.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8"},
     {file = "psutil-5.9.2.tar.gz", hash = "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c"},
 ]
+ptyprocess = [
+    {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
+    {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
+]
 pure-cdb = [
     {file = "pure-cdb-4.0.0.tar.gz", hash = "sha256:8c44801e281f029769ef1cf2a53cfeec67bc962d595b83365c0463daea95b08c"},
     {file = "pure_cdb-4.0.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f78d336bf18dbc0997879a95cbd854536176810af262d13486145273584a5528"},
@@ -2230,39 +2514,21 @@ pure-cdb = [
     {file = "pure_cdb-4.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac7ac6f25bf857a82d93ed0be12399c68f6dc2b32885f48693a51c8d97209426"},
     {file = "pure_cdb-4.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:add3e82a6476076fecaddc58689e326125e56d0593f705dcd44a0467df08ba2f"},
 ]
+pure-eval = [
+    {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
+    {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
+]
 py = [
     {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
 ]
 pyasn1 = [
-    {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
-    {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
-    {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"},
-    {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"},
     {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
-    {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"},
-    {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"},
-    {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"},
-    {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"},
-    {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"},
-    {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"},
-    {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"},
     {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
 ]
 pyasn1-modules = [
     {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
-    {file = "pyasn1_modules-0.2.8-py2.4.egg", hash = "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199"},
-    {file = "pyasn1_modules-0.2.8-py2.5.egg", hash = "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"},
-    {file = "pyasn1_modules-0.2.8-py2.6.egg", hash = "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb"},
-    {file = "pyasn1_modules-0.2.8-py2.7.egg", hash = "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8"},
     {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
-    {file = "pyasn1_modules-0.2.8-py3.1.egg", hash = "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d"},
-    {file = "pyasn1_modules-0.2.8-py3.2.egg", hash = "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45"},
-    {file = "pyasn1_modules-0.2.8-py3.3.egg", hash = "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4"},
-    {file = "pyasn1_modules-0.2.8-py3.4.egg", hash = "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811"},
-    {file = "pyasn1_modules-0.2.8-py3.5.egg", hash = "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed"},
-    {file = "pyasn1_modules-0.2.8-py3.6.egg", hash = "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0"},
-    {file = "pyasn1_modules-0.2.8-py3.7.egg", hash = "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd"},
 ]
 pyDeprecate = [
     {file = "pyDeprecate-0.3.2-py3-none-any.whl", hash = "sha256:ed86b68ed837e6465245904a3de2f59bf9eef78ac7a2502ee280533d04802457"},
@@ -2452,8 +2718,8 @@ requests-oauthlib = [
     {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"},
 ]
 rhoknp = [
-    {file = "rhoknp-0.4.4-py3-none-any.whl", hash = "sha256:3b856a7c036fe45c6e01347559e3137083ec68652a5b674b2bcf989939e9af6e"},
-    {file = "rhoknp-0.4.4.tar.gz", hash = "sha256:457b11535eabbf65d847e0313dee6ef9c05299212b3e88f4228e90a462826778"},
+    {file = "rhoknp-0.4.6-py3-none-any.whl", hash = "sha256:27796588674c245a8fd8c48ee21e0fe196dc8e6501f50c4ae74b9fbe5e1bc500"},
+    {file = "rhoknp-0.4.6.tar.gz", hash = "sha256:1462d34cc310bbd5680c1a51adafff5114952f615e9c7cee0ad06dfc078b4847"},
 ]
 rich = [
     {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
@@ -2630,6 +2896,10 @@ smmap = [
     {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"},
     {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
 ]
+stack-data = [
+    {file = "stack_data-0.6.0-py3-none-any.whl", hash = "sha256:b92d206ef355a367d14316b786ab41cb99eb453a21f2cb216a4204625ff7bc07"},
+    {file = "stack_data-0.6.0.tar.gz", hash = "sha256:8e515439f818efaa251036af72d89e4026e2b03993f3453c000b200fb4f2d6aa"},
+]
 tensorboard = [
     {file = "tensorboard-2.10.1-py3-none-any.whl", hash = "sha256:fb9222c1750e2fa35ef170d998a1e229f626eeced3004494a8849c88c15d8c1c"},
 ]
@@ -2684,6 +2954,10 @@ tokenizers = [
     {file = "tokenizers-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:2158baf80cbc09259bfd6e0e0fc4597b611e7a72ad5443dad63918a90f1dd304"},
     {file = "tokenizers-0.12.1.tar.gz", hash = "sha256:070746f86efa6c873db341e55cf17bb5e7bdd5450330ca8eca542f5c3dab2c66"},
 ]
+toml = [
+    {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+    {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
 tomli = [
     {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
     {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
@@ -2717,6 +2991,10 @@ tqdm = [
     {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"},
     {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"},
 ]
+traitlets = [
+    {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"},
+    {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"},
+]
 transformers = [
     {file = "transformers-4.19.4-py3-none-any.whl", hash = "sha256:572d8ecbff29ec53769e0459b4334ebd1038f75ad25119a3006f8816643dccc4"},
     {file = "transformers-4.19.4.tar.gz", hash = "sha256:b8c0f9816b4c4c2f0265b24d0a0b9d4ae8b7b98fc779d63f92e3c121c4c2d483"},
@@ -2737,6 +3015,10 @@ wandb = [
     {file = "wandb-0.13.4-py2.py3-none-any.whl", hash = "sha256:ce64648325a22c3166f23e00aaf373732d0450196242a0a93efd774f7e79f9c0"},
     {file = "wandb-0.13.4.tar.gz", hash = "sha256:51e2672e12cca94680f6bcff0af80822d562e6b7846036050fc5bdd00240ea75"},
 ]
+wcwidth = [
+    {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
+    {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
+]
 Werkzeug = [
     {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
     {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
diff --git a/pyproject.toml b/pyproject.toml
index 37106a94..dc9dd68c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -76,6 +76,7 @@ kyoto-reader = "^2.5"
 pytest-profiling = "^1.7"
 pytest = "^7.1"
 pytest-cov = "^4.0"
+ipdb = "^0.13.9"
 
 [build-system]
 requires = ["poetry-core"]
diff --git a/src/kwja/callbacks/word_module_discourse_writer.py b/src/kwja/callbacks/word_module_discourse_writer.py
index 372993ca..31c5e5df 100644
--- a/src/kwja/callbacks/word_module_discourse_writer.py
+++ b/src/kwja/callbacks/word_module_discourse_writer.py
@@ -85,8 +85,6 @@ def write_on_batch_end(
             example: Union[WordExampleSet, WordInferenceExample] = dataset.examples[example_id]
             doc_id = example.doc_id
             document = dataset.doc_id2document[doc_id]
-            document.doc_id = doc_id
-            document = document.reparse()  # reparse to get clauses
             self._add_discourse(document, discourse_parsing_preds)
             sentences += extract_target_sentences(document)
 
diff --git a/src/kwja/callbacks/word_module_writer.py b/src/kwja/callbacks/word_module_writer.py
index 45f4e029..d7350994 100644
--- a/src/kwja/callbacks/word_module_writer.py
+++ b/src/kwja/callbacks/word_module_writer.py
@@ -13,7 +13,7 @@
 from pytorch_lightning.callbacks import BasePredictionWriter
 from rhoknp import BasePhrase, Document, Morpheme, Phrase, Sentence
 from rhoknp.cohesion import ExophoraReferent, RelTag, RelTagList
-from rhoknp.props import DepType, FeatureDict, NamedEntity, NamedEntityCategory, SemanticsDict
+from rhoknp.props import DepType, NamedEntity, NamedEntityCategory, SemanticsDict
 from rhoknp.units.morpheme import MorphemeAttributes
 
 from kwja.datamodule.datasets import WordDataset, WordInferenceDataset
@@ -25,12 +25,14 @@
     BASE_PHRASE_FEATURES,
     CONJFORM_TYPES,
     CONJTYPE_CONJFORM_TYPE2ID,
+    CONJTYPE_TYPES,
     INDEX2CONJFORM_TYPE,
     INDEX2CONJTYPE_TYPE,
     INDEX2DEPENDENCY_TYPE,
     INDEX2DISCOURSE_RELATION,
     INDEX2POS_TYPE,
     INDEX2SUBPOS_TYPE,
+    INFLECTABLE,
     NE_TAGS,
     POS_SUBPOS_TYPE2ID,
     POS_TYPE2ID,
@@ -167,7 +169,7 @@ def _create_morphemes(
                 conjform=conjform,
                 conjform_id=conjform_id,
             )
-            morpheme = Morpheme(word, attributes, SemanticsDict(semantics), FeatureDict(semantics))
+            morpheme = Morpheme(word, attributes=attributes, semantics=SemanticsDict(semantics))
             morphemes.append(morpheme)
             if len(homograph_ops) >= 1:
                 range_list = []
@@ -185,11 +187,11 @@ def _create_morphemes(
                             semantics2 = v
                         else:
                             raise NotImplementedError
-                    morpheme2 = Morpheme(
-                        word, attributes2, SemanticsDict(semantics2), FeatureDict(semantics2), homograph=True
+                    homograph = Morpheme(
+                        word, attributes=attributes2, semantics=SemanticsDict(semantics2), homograph=True
                     )
                     # rhoknp coverts homographs into KNP's ALT features
-                    morpheme.homographs.append(morpheme2)
+                    morpheme.homographs.append(homograph)
         return morphemes
 
     @staticmethod
@@ -198,50 +200,51 @@ def _get_mrph_type_preds(
         subpos_logits: torch.Tensor,
         conjtype_logits: torch.Tensor,
         conjform_logits: torch.Tensor,
-    ) -> Tuple[List[List[int]], List[List[int]], List[List[int]], List[List[int]]]:
-        batch_pos_preds: List[List[int]] = torch.argmax(pos_logits, dim=-1).tolist()
-        batch_subpos_logits: List[List[List[float]]] = subpos_logits.tolist()
-        refined_batch_subpos_preds: List[List[int]] = []
-        for batch_idx, batch_pos_pred in enumerate(batch_pos_preds):
-            refined_subpos_preds: List[int] = []
-            for pos_idx, pos in enumerate(batch_pos_pred):
-                possible_subpos_ids: Set[int] = {
-                    SUBPOS_TYPES.index(x) for x in POS_SUBPOS_TYPE2ID[INDEX2POS_TYPE[pos]].keys()
-                }
-                refined_subpos_pred: int = 0
-                refined_subpos_logit: float = float("-inf")
-                for logit_idx, logit in enumerate(batch_subpos_logits[batch_idx][pos_idx]):
-                    if logit_idx in possible_subpos_ids and logit > refined_subpos_logit:
-                        refined_subpos_pred = logit_idx
-                        refined_subpos_logit = logit
-                refined_subpos_preds.append(refined_subpos_pred)
-            refined_batch_subpos_preds.append(refined_subpos_preds)
+    ) -> Tuple[List[int], List[int], List[int], List[int]]:
+        pos_preds: List[int] = torch.argmax(pos_logits, dim=1).tolist()
+        subpos_logits_list: List[List[float]] = subpos_logits.tolist()
+        subpos_preds: List[int] = []
+        for mrph_idx, pos_pred in enumerate(pos_preds):
+            possible_subpos_ids: Set[int] = {
+                SUBPOS_TYPES.index(x) for x in POS_SUBPOS_TYPE2ID[INDEX2POS_TYPE[pos_pred]].keys()
+            }
+            subpos_logit: float = float("-inf")
+            subpos_pred: int = 0
+            for logit_idx, logit in enumerate(subpos_logits_list[mrph_idx]):
+                if logit_idx in possible_subpos_ids and logit > subpos_logit:
+                    subpos_logit = logit
+                    subpos_pred = logit_idx
+            subpos_preds.append(subpos_pred)
 
-        batch_conjtype_preds: List[List[int]] = torch.argmax(conjtype_logits, dim=-1).tolist()
-        batch_conjform_logits: List[List[List[float]]] = conjform_logits.tolist()
-        refined_batch_conjform_preds: List[List[int]] = []
-        for batch_idx, batch_conjtype_pred in enumerate(batch_conjtype_preds):
-            refined_conjform_preds: List[int] = []
-            for conjtype_idx, conjtype in enumerate(batch_conjtype_pred):
+        conjtype_preds: List[int] = torch.argmax(conjtype_logits, dim=1).tolist()
+        conjform_logits_list: List[List[float]] = conjform_logits.tolist()
+        conjform_preds: List[int] = []
+        for mrph_idx, (pos_pred, subpos_pred, conjtype_pred) in enumerate(zip(pos_preds, subpos_preds, conjtype_preds)):
+            pos: str = INDEX2POS_TYPE[pos_pred]
+            subpos: str = INDEX2SUBPOS_TYPE[subpos_pred]
+            if (pos, subpos) in INFLECTABLE:
                 possible_conjform_ids: Set[int] = {
-                    CONJFORM_TYPES.index(x) for x in CONJTYPE_CONJFORM_TYPE2ID[INDEX2CONJTYPE_TYPE[conjtype]].keys()
+                    CONJFORM_TYPES.index(x)
+                    for x in CONJTYPE_CONJFORM_TYPE2ID[INDEX2CONJTYPE_TYPE[conjtype_pred]].keys()
                 }
-                refined_conjform_pred: int = 0
-                refined_conjform_logit: float = float("-inf")
-                for logit_idx, logit in enumerate(batch_conjform_logits[batch_idx][conjtype_idx]):
-                    if logit_idx in possible_conjform_ids and logit > refined_conjform_logit:
-                        refined_conjform_pred = logit_idx
-                        refined_conjform_logit = logit
-                refined_conjform_preds.append(refined_conjform_pred)
-            refined_batch_conjform_preds.append(refined_conjform_preds)
+                conjform_logit: float = float("-inf")
+                conjform_pred: int = 0
+                for logit_idx, logit in enumerate(conjform_logits_list[mrph_idx]):
+                    if logit_idx in possible_conjform_ids and logit > conjform_logit:
+                        conjform_logit = logit
+                        conjform_pred = logit_idx
+            else:
+                conjtype_preds[mrph_idx] = CONJTYPE_TYPES.index("*")
+                conjform_pred = CONJTYPE_CONJFORM_TYPE2ID["*"]["*"]
+            conjform_preds.append(conjform_pred)
 
-        return batch_pos_preds, refined_batch_subpos_preds, batch_conjtype_preds, refined_batch_conjform_preds
+        return pos_preds, subpos_preds, conjtype_preds, conjform_preds
 
     @staticmethod
     def _chunk_morphemes(
-        document: Document, morphemes: List[Morpheme], word_feature_preds: List[List[float]]
+        document: Document, morphemes: List[Morpheme], word_feature_logits: List[List[float]]
     ) -> Document:
-        assert len(morphemes) <= len(word_feature_preds)
+        assert len(morphemes) <= len(word_feature_logits)
         sentences = []
         for sentence in document.sentences:
             phrases_buff = []
@@ -255,15 +258,14 @@ def _chunk_morphemes(
                     base_phrase_head_prob,
                     base_phrase_end_prob,
                     phrase_end_prob,
-                    declinable_word_surf_head,
-                    declinable_word_surf_end,
-                ) = word_feature_preds[i]
+                    inflectable_word_surf_head_prob,
+                    inflectable_word_surf_end_prob,
+                ) = word_feature_logits[i]
                 if base_phrase_head_prob >= 0.5:
                     morpheme.features["基本句-主辞"] = True
-                # TODO: refactor & set strict condition
-                if declinable_word_surf_head >= 0.5:
+                if inflectable_word_surf_head_prob >= 0.5:
                     morpheme.features["用言表記先頭"] = True
-                if declinable_word_surf_end >= 0.5:
+                if inflectable_word_surf_end_prob >= 0.5:
                     morpheme.features["用言表記末尾"] = True
                 # even if base_phrase_end_prob is low, if phrase_end_prob is high enough, create chunk here
                 if base_phrase_end_prob >= 0.5 or base_phrase_end_prob + phrase_end_prob >= 1.0:
@@ -288,64 +290,59 @@ def _chunk_morphemes(
                 phrase.base_phrases = base_phrases_buff
                 phrases_buff.append(phrase)
 
-            sentence = sentence.reparse()  # reparse to get clauses
             sentence.phrases = phrases_buff
-            sentence.from_knp(sentence.to_knp())
+            sentence = sentence.reparse()
             sentences.append(sentence)
         return Document.from_sentences(sentences)
 
     @staticmethod
-    def _add_base_phrase_features(document: Document, base_phrase_feature_preds: List[List[float]]) -> None:
+    def _add_base_phrase_features(document: Document, base_phrase_feature_logits: List[List[float]]) -> None:
         for sentence in document.sentences:
-            base_phrases = sentence.base_phrases
-            if len(base_phrases) == 0:
-                continue
+            phrases = sentence.phrases
+            clause_boundary, clause_start = None, 0
+            for phrase in phrases:
+                for base_phrase in phrase.base_phrases:
+                    for feature, prob in zip(
+                        BASE_PHRASE_FEATURES, base_phrase_feature_logits[base_phrase.head.global_index]
+                    ):
+                        if feature.startswith("節-区切") and prob >= 0.5:
+                            clause_boundary = feature
+                        elif feature != "節-主辞" and prob >= 0.5:
+                            k, *vs = feature.split(":")
+                            base_phrase.features[k] = ":".join(vs) or True
+                if phrase == phrases[-1] and clause_boundary is None:
+                    clause_boundary = "節-区切"
 
-            clause_start_index_set: Set[int] = {0}
-            for base_phrase in base_phrases:
-                for feature, pred in zip(
-                    BASE_PHRASE_FEATURES, base_phrase_feature_preds[base_phrase.head.global_index]
-                ):
-                    if feature != "節-主辞" and pred >= 0.5:
-                        k, *vs = feature.split(":")
-                        base_phrase.features[k] = ":".join(vs) or True
-                    if feature.startswith("節-区切") and pred >= 0.5:
-                        clause_start_index_set.add(base_phrase.index + 1)
-            if base_phrases[-1].features.get("節-区切", False) is False:
-                clause_start_index_set.add(len(base_phrases))
-            clause_start_indices: List[int] = sorted(clause_start_index_set)
-
-            for span in zip(clause_start_indices[:-1], clause_start_indices[1:]):
-                clause = base_phrases[slice(*span)]
-                clause_head_scores = [
-                    base_phrase_feature_preds[base_phrase.head.global_index][BASE_PHRASE_FEATURES.index("節-主辞")]
-                    for base_phrase in clause
-                ]
-                clause_head = clause[clause_head_scores.index(max(clause_head_scores))]
-                clause_head.features["節-主辞"] = True
+                if clause_boundary:
+                    k, *vs = clause_boundary.split(":")
+                    phrase.base_phrases[-1].features[k] = ":".join(vs) or True
+                    base_phrases = [bp for p in phrases[clause_start : phrase.index + 1] for bp in p.base_phrases]
+                    clause_head_probs = [
+                        base_phrase_feature_logits[base_phrase.head.global_index][BASE_PHRASE_FEATURES.index("節-主辞")]
+                        for base_phrase in base_phrases
+                    ]
+                    clause_head = base_phrases[clause_head_probs.index(max(clause_head_probs))]
+                    clause_head.features["節-主辞"] = True
+                    clause_boundary, clause_start = None, phrase.index + 1
 
     @staticmethod
     def _add_named_entities(document: Document, ne_tag_preds: List[int]) -> None:
-        for sentence in document.sentences:
-            morphemes = sentence.morphemes
-            category = ""
-            morphemes_buff = []
-            for morpheme, ne_tag_pred in zip(morphemes, ne_tag_preds):
-                ne_tag: str = NE_TAGS[ne_tag_pred]
-                if ne_tag.startswith("B-"):
-                    category = ne_tag[2:]
-                    morphemes_buff.append(morpheme)
-                elif ne_tag.startswith("I-") and ne_tag[2:] == category:
-                    morphemes_buff.append(morpheme)
-                else:
-                    if morphemes_buff:
-                        named_entity = NamedEntity(category=NamedEntityCategory(category), morphemes=morphemes_buff)
-                        # NE feature must be tagged to the last base phrase the named entity contains
-                        morphemes_buff[-1].base_phrase.features[
-                            "NE"
-                        ] = f"{named_entity.category.value}:{named_entity.text}"
-                    category = ""
-                    morphemes_buff = []
+        category = ""
+        morphemes_buff = []
+        for morpheme, ne_tag_pred in zip(document.morphemes, ne_tag_preds):
+            ne_tag: str = NE_TAGS[ne_tag_pred]
+            if ne_tag.startswith("B-"):
+                category = ne_tag[2:]
+                morphemes_buff.append(morpheme)
+            elif ne_tag.startswith("I-") and ne_tag[2:] == category:
+                morphemes_buff.append(morpheme)
+            else:
+                if morphemes_buff:
+                    named_entity = NamedEntity(category=NamedEntityCategory(category), morphemes=morphemes_buff)
+                    # NE feature must be tagged to the last base phrase the named entity contains
+                    morphemes_buff[-1].base_phrase.features["NE"] = f"{named_entity.category.value}:{named_entity.text}"
+                category = ""
+                morphemes_buff = []
 
     @staticmethod
     def _resolve_dependency(base_phrase: BasePhrase, dependency_manager: DependencyManager) -> None:
@@ -378,7 +375,7 @@ def _add_dependency(
         dependency_type_preds: List[List[int]],
         special_to_index: Dict[str, int],
     ) -> None:
-        for sentence in extract_target_sentences(document):
+        for sentence in document.sentences:
             base_phrases = sentence.base_phrases
             morpheme_global_index2base_phrase_index = {
                 morpheme.global_index: base_phrase.index
@@ -517,16 +514,14 @@ def write_on_batch_end(
         dataloader_idx = prediction["dataloader_idx"]
         dataset: Union[WordDataset, WordInferenceDataset] = dataloaders[dataloader_idx].dataset
         batch_reading_subword_map = prediction["reading_subword_map"]
-        batch_reading_preds = torch.argmax(prediction["reading_prediction_logits"], dim=-1)
-        (batch_pos_preds, batch_subpos_preds, batch_conjtype_preds, batch_conjform_preds,) = self._get_mrph_type_preds(
-            pos_logits=prediction["word_analysis_pos_logits"],
-            subpos_logits=prediction["word_analysis_subpos_logits"],
-            conjtype_logits=prediction["word_analysis_conjtype_logits"],
-            conjform_logits=prediction["word_analysis_conjform_logits"],
-        )
-        batch_ne_tag_preds = torch.argmax(prediction["ne_logits"], dim=-1)
-        batch_word_feature_preds = prediction["word_feature_logits"]
-        batch_base_phrase_feature_preds = prediction["base_phrase_feature_logits"]
+        batch_reading_preds = torch.argmax(prediction["reading_prediction_logits"], dim=2)
+        batch_pos_logits = prediction["word_analysis_pos_logits"]
+        batch_subpos_logits = prediction["word_analysis_subpos_logits"]
+        batch_conjtype_logits = prediction["word_analysis_conjtype_logits"]
+        batch_conjform_logits = prediction["word_analysis_conjform_logits"]
+        batch_word_feature_logits = prediction["word_feature_logits"]
+        batch_ne_tag_preds = torch.argmax(prediction["ne_logits"], dim=2)
+        batch_base_phrase_feature_logits = prediction["base_phrase_feature_logits"]
         batch_dependency_preds = torch.topk(
             prediction["dependency_logits"],
             k=4,  # TODO
@@ -540,13 +535,13 @@ def write_on_batch_end(
             example_id,
             reading_subword_map,
             reading_preds,
-            pos_preds,
-            subpos_preds,
-            conjtype_preds,
-            conjform_preds,
-            word_feature_preds,
+            pos_logits,
+            subpos_logits,
+            conjtype_logits,
+            conjform_logits,
+            word_feature_logits,
             ne_tag_preds,
-            base_phrase_feature_preds,
+            base_phrase_feature_logits,
             dependency_preds,
             dependency_type_preds,
             cohesion_preds,
@@ -556,13 +551,13 @@ def write_on_batch_end(
             batch_example_ids,
             batch_reading_subword_map.tolist(),
             batch_reading_preds.tolist(),
-            batch_pos_preds,
-            batch_subpos_preds,
-            batch_conjtype_preds,
-            batch_conjform_preds,
-            batch_word_feature_preds.tolist(),
+            batch_pos_logits,
+            batch_subpos_logits,
+            batch_conjtype_logits,
+            batch_conjform_logits,
+            batch_word_feature_logits.tolist(),
             batch_ne_tag_preds.tolist(),
-            batch_base_phrase_feature_preds.tolist(),
+            batch_base_phrase_feature_logits.tolist(),
             batch_dependency_preds.tolist(),
             batch_dependency_type_preds.tolist(),
             batch_cohesion_preds.tolist(),
@@ -573,6 +568,12 @@ def write_on_batch_end(
             document = dataset.doc_id2document[doc_id]
             readings = [self.id2reading[pred] for pred in reading_preds]
             word_reading_preds = get_word_level_readings(readings, tokens.split(" "), reading_subword_map)
+            pos_preds, subpos_preds, conjtype_preds, conjform_preds = self._get_mrph_type_preds(
+                pos_logits=pos_logits,
+                subpos_logits=subpos_logits,
+                conjtype_logits=conjtype_logits,
+                conjform_logits=conjform_logits,
+            )
             morphemes = self._create_morphemes(
                 [m.text for m in document.morphemes],
                 [m.lemma if m.attributes is not None else m.text for m in document.morphemes],
@@ -582,9 +583,8 @@ def write_on_batch_end(
                 conjtype_preds,
                 conjform_preds,
             )
-            document = self._chunk_morphemes(document, morphemes, word_feature_preds)
-            document.doc_id = doc_id
-            self._add_base_phrase_features(document, base_phrase_feature_preds)
+            document = self._chunk_morphemes(document, morphemes, word_feature_logits)
+            self._add_base_phrase_features(document, base_phrase_feature_logits)
             self._add_named_entities(document, ne_tag_preds)
             self._add_dependency(document, dependency_preds, dependency_type_preds, dataset.special_to_index)
             self._add_cohesion(
diff --git a/src/kwja/cli/cli.py b/src/kwja/cli/cli.py
index 8636b095..2fd07a21 100644
--- a/src/kwja/cli/cli.py
+++ b/src/kwja/cli/cli.py
@@ -11,6 +11,7 @@
 from omegaconf import OmegaConf
 from pytorch_lightning.trainer.states import TrainerFn
 from rhoknp import Document
+from rhoknp.utils.reader import chunk_by_document
 
 import kwja
 from kwja.callbacks.word_module_discourse_writer import WordModuleDiscourseWriter
@@ -268,16 +269,25 @@ def apply_word_discourse(self) -> None:
         )
 
     def output_word_result(self) -> None:
-        document: Document = Document.from_knp(self.word_path.read_text())
-        # Remove the result of discourse relation analysis by the jointly learned model.
-        for base_phrase in document.base_phrases:
-            if "談話関係" in base_phrase.features:
-                del base_phrase.features["談話関係"]
-        print(document.to_knp(), end="")
+        knp_texts = []
+        with self.word_path.open(mode="r") as f:
+            for knp_text in chunk_by_document(f):
+                document = Document.from_knp(knp_text)
+                # Remove the result of discourse relation analysis by the jointly learned model.
+                for base_phrase in document.base_phrases:
+                    if "談話関係" in base_phrase.features:
+                        del base_phrase.features["談話関係"]
+                knp_texts.append(document.to_knp())
+        print("\n".join(knp_texts), end="")
 
     def output_word_discourse_result(self) -> None:
-        discourse_document: Document = Document.from_knp(self.word_discourse_path.read_text())
-        print(discourse_document.to_knp(), end="")
+        print(self.word_discourse_path.read_text(), end="")
+
+    def refresh(self) -> None:
+        self.typo_path.unlink(missing_ok=True)
+        self.char_path.unlink(missing_ok=True)
+        self.word_path.unlink(missing_ok=True)
+        self.word_discourse_path.unlink(missing_ok=True)
 
 
 def version_callback(value: bool) -> None:
@@ -358,6 +368,7 @@ def main(
         while True:
             inp = input()
             if inp == "EOD":
+                processor.refresh()
                 processor.apply_typo([input_text])
                 processor.apply_char()
                 processor.apply_word()
diff --git a/src/kwja/datamodule/datamodule.py b/src/kwja/datamodule/datamodule.py
index 02e0f39a..3bdd9735 100644
--- a/src/kwja/datamodule/datamodule.py
+++ b/src/kwja/datamodule/datamodule.py
@@ -46,6 +46,7 @@ def setup(self, stage: Optional[str] = None) -> None:
             self.predict_dataset = hydra.utils.instantiate(self.cfg.predict)
 
     def train_dataloader(self) -> DataLoader:
+        assert self.train_dataset is not None
         return self._get_dataloader(dataset=self.train_dataset, shuffle=True)
 
     def val_dataloader(self) -> List[DataLoader]:
@@ -55,6 +56,7 @@ def test_dataloader(self) -> List[DataLoader]:
         return [self._get_dataloader(dataset, shuffle=False) for dataset in self.test_datasets.values()]
 
     def predict_dataloader(self) -> DataLoader:
+        assert isinstance(self.predict_dataset, Dataset)
         return self._get_dataloader(self.predict_dataset, shuffle=False)
 
     def _get_dataloader(self, dataset: Dataset, shuffle: bool) -> DataLoader:
diff --git a/src/kwja/datamodule/datasets/typo_dataset.py b/src/kwja/datamodule/datasets/typo_dataset.py
index ead66ea6..672eeb51 100644
--- a/src/kwja/datamodule/datasets/typo_dataset.py
+++ b/src/kwja/datamodule/datasets/typo_dataset.py
@@ -40,7 +40,7 @@ def __init__(
     def __len__(self) -> int:
         return len(self.documents)
 
-    def __getitem__(self, index: int) -> Dict[str, torch.Tensor]:
+    def __getitem__(self, index: int) -> Dict[str, Union[torch.Tensor, str]]:
         return self.encode(self.documents[index])
 
     @staticmethod
@@ -59,7 +59,7 @@ def get_opn2id(self, path: Path) -> Dict[str, int]:
                 opn2id[str(line.strip())] = len(opn2id)
         return opn2id
 
-    def encode(self, document: Dict[str, Union[str, List[str]]]) -> Dict[str, torch.Tensor]:
+    def encode(self, document: Dict[str, Union[str, List[str]]]) -> Dict[str, Union[torch.Tensor, str]]:
         if isinstance(document["pre_text"], list):
             raise ValueError('document["pre_text"] must be string')
         encoding: BatchEncoding = self.tokenizer(
diff --git a/src/kwja/datamodule/datasets/typo_inference_dataset.py b/src/kwja/datamodule/datasets/typo_inference_dataset.py
index 5352e9dc..b0497061 100644
--- a/src/kwja/datamodule/datasets/typo_inference_dataset.py
+++ b/src/kwja/datamodule/datasets/typo_inference_dataset.py
@@ -1,4 +1,4 @@
-from typing import Dict, List
+from typing import Dict, List, Union
 
 import torch
 from torch.utils.data import Dataset
@@ -27,10 +27,10 @@ def __init__(
     def __len__(self) -> int:
         return len(self.texts)
 
-    def __getitem__(self, index: int) -> Dict[str, torch.Tensor]:
+    def __getitem__(self, index: int) -> Dict[str, Union[torch.Tensor, str]]:
         return self.encode(self.texts[index])
 
-    def encode(self, text: str) -> Dict[str, torch.Tensor]:
+    def encode(self, text: str) -> Dict[str, Union[torch.Tensor, str]]:
         encoding: BatchEncoding = self.tokenizer(
             text + TYPO_DUMMY_TOKEN,
             truncation=True,
diff --git a/src/kwja/datamodule/datasets/word_dataset.py b/src/kwja/datamodule/datasets/word_dataset.py
index a473ba06..088e1dc9 100644
--- a/src/kwja/datamodule/datasets/word_dataset.py
+++ b/src/kwja/datamodule/datasets/word_dataset.py
@@ -195,10 +195,10 @@ def _load_examples(self, documents: List[Document]) -> List[WordExampleSet]:
     def __len__(self) -> int:
         return len(self.examples)
 
-    def __getitem__(self, index: int) -> Dict[str, torch.Tensor]:
+    def __getitem__(self, index: int) -> Dict[str, Union[torch.Tensor, str]]:
         return self.encode(self.examples[index])
 
-    def encode(self, example: WordExampleSet) -> Dict[str, torch.Tensor]:
+    def encode(self, example: WordExampleSet) -> Dict[str, Union[torch.Tensor, str]]:
         merged_encoding: Encoding = Encoding.merge([example.encoding, self.special_encoding])
 
         document = self.doc_id2document[example.doc_id]
diff --git a/src/kwja/datamodule/datasets/word_inference_dataset.py b/src/kwja/datamodule/datasets/word_inference_dataset.py
index c0108c0e..c743fcec 100644
--- a/src/kwja/datamodule/datasets/word_inference_dataset.py
+++ b/src/kwja/datamodule/datasets/word_inference_dataset.py
@@ -139,7 +139,7 @@ def _create_documents_from_texts(texts: List[str], doc_id_prefix: Optional[str])
     def __len__(self) -> int:
         return len(self.examples)
 
-    def __getitem__(self, index: int) -> Dict[str, torch.Tensor]:
+    def __getitem__(self, index: int) -> Dict[str, Union[torch.Tensor, str]]:
         return self.encode(self.examples[index])
 
     def _load_examples(self, documents: List[Document]) -> List[WordInferenceExample]:
@@ -173,7 +173,7 @@ def _load_examples(self, documents: List[Document]) -> List[WordInferenceExample
             logger.error("No examples to process. Make sure any texts are given and they are not too long.")
         return examples
 
-    def encode(self, example: WordInferenceExample) -> Dict[str, torch.Tensor]:
+    def encode(self, example: WordInferenceExample) -> Dict[str, Union[torch.Tensor, str]]:
         document = self.doc_id2document[example.doc_id]
 
         target_mask = [False for _ in range(self.max_seq_length)]
diff --git a/src/kwja/evaluators/cohesion_analysis_metric.py b/src/kwja/evaluators/cohesion_analysis_metric.py
index f18de291..e2b336a9 100644
--- a/src/kwja/evaluators/cohesion_analysis_metric.py
+++ b/src/kwja/evaluators/cohesion_analysis_metric.py
@@ -47,7 +47,7 @@ def compute(self, dataset: WordDataset) -> Tuple[ScoreResult, Dict[str, float]]:
         knp_writer = CohesionKNPWriter(dataset)
         assert len(self.example_ids) == len(self.predictions), f"{len(self.example_ids)} vs {len(self.predictions)}"
         predictions: Dict[int, List[List[int]]] = {
-            eid.item(): prediction.tolist() for eid, prediction in zip(self.example_ids, self.predictions)
+            int(eid.item()): prediction.tolist() for eid, prediction in zip(self.example_ids, self.predictions)
         }
         documents_pred = knp_writer.write(predictions, destination=None)
         targets2label = {
diff --git a/src/kwja/evaluators/ner_metric.py b/src/kwja/evaluators/ner_metric.py
index afaffcbd..356ec908 100644
--- a/src/kwja/evaluators/ner_metric.py
+++ b/src/kwja/evaluators/ner_metric.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, Tuple, Union
+from typing import Dict, List, Tuple
 
 import numpy as np
 import torch
@@ -26,7 +26,7 @@ def update(
         self.ne_tag_predictions.append(ne_tag_predictions)
         self.ne_tags.append(ne_tags)
 
-    def compute(self) -> Dict[str, Union[torch.Tensor, float]]:
+    def compute(self) -> Dict[str, float]:
         sorted_indices = self._unique(self.example_ids)
         ne_tag_predictions, ne_tags = map(
             lambda x: x[sorted_indices],
diff --git a/src/kwja/evaluators/word_analysis_metric.py b/src/kwja/evaluators/word_analysis_metric.py
index 07d6ed98..103bf9b2 100644
--- a/src/kwja/evaluators/word_analysis_metric.py
+++ b/src/kwja/evaluators/word_analysis_metric.py
@@ -41,7 +41,7 @@ def update(
 
     @staticmethod
     def _convert(preds: torch.Tensor, labels: torch.Tensor):
-        ignore_index_pos: torch.BoolTensor = labels != IGNORE_INDEX
+        ignore_index_pos: torch.Tensor = labels != IGNORE_INDEX
         converted_preds = torch.masked_select(preds, ignore_index_pos)
         converted_labels = torch.masked_select(labels, ignore_index_pos)
         return converted_preds, converted_labels
diff --git a/src/kwja/models/models/phrase_analyzer.py b/src/kwja/models/models/phrase_analyzer.py
index 7422d966..a1888cfa 100644
--- a/src/kwja/models/models/phrase_analyzer.py
+++ b/src/kwja/models/models/phrase_analyzer.py
@@ -180,19 +180,19 @@ def __repr__(self) -> str:
 
     def forward(
         self,
-        emissions: torch.FloatTensor,
-        tags: torch.LongTensor,
-        mask: Optional[torch.ByteTensor] = None,
+        emissions: torch.Tensor,
+        tags: torch.Tensor,
+        mask: Optional[torch.Tensor] = None,
         reduction: str = "token_mean",
     ) -> torch.Tensor:
         """Compute the conditional log likelihood of a sequence of tags given emission scores.
 
         Args:
-            emissions (`~torch.FloatTensor`): Emission score tensor of size ``(seq_length, batch_size, num_tags)`` or
+            emissions (`~torch.Tensor`): Emission score tensor of size ``(seq_length, batch_size, num_tags)`` or
                                               ``(batch_size, seq_length, num_tags)`` (if batch_first is True).
-            tags (`~torch.LongTensor`): Sequence of tags tensor of size ``(seq_length, batch_size)`` or
+            tags (`~torch.Tensor`): Sequence of tags tensor of size ``(seq_length, batch_size)`` or
                                         ``(batch_size, seq_length, num_tags)`` (if batch_first is True).
-            mask (`~torch.ByteTensor`): Mask tensor of size ``(seq_length, batch_size)`` or
+            mask (`~torch.Tensor`): Mask tensor of size ``(seq_length, batch_size)`` or
                                         ``(batch_size, seq_length)`` (if batch_first is True).
             reduction: Specifies  the reduction to apply to the output: ``none|sum|mean|token_mean``.
                        ``none``: no reduction will be applied.
@@ -228,7 +228,7 @@ def forward(
         assert reduction == "token_mean"
         return llh.sum() / mask.type_as(emissions).sum()
 
-    def decode(self, emissions: torch.FloatTensor, mask: Optional[torch.ByteTensor] = None) -> List[List[int]]:
+    def decode(self, emissions: torch.Tensor, mask: Optional[torch.Tensor] = None) -> List[List[int]]:
         """Find the most likely tag sequence using Viterbi algorithm.
 
         Args:
@@ -241,20 +241,19 @@ def decode(self, emissions: torch.FloatTensor, mask: Optional[torch.ByteTensor]
             `~list[list[int]]`: list of list containing the best tag sequence for each batch.
         """
         self._validate(emissions, mask=mask)
-        if mask is None:
-            mask = emissions.new_ones(emissions.shape[:2], dtype=torch.uint8)
+        mask_: torch.Tensor = mask or emissions.new_ones(emissions.shape[:2], dtype=torch.uint8)
 
         if self.batch_first:
             emissions = emissions.transpose(0, 1)
-            mask = mask.transpose(0, 1)
+            mask_ = mask_.transpose(0, 1)
 
-        return self._viterbi_decode(emissions, mask)
+        return self._viterbi_decode(emissions, mask_)
 
     def _validate(
         self,
-        emissions: torch.FloatTensor,
-        tags: Optional[torch.LongTensor] = None,
-        mask: Optional[torch.ByteTensor] = None,
+        emissions: torch.Tensor,
+        tags: Optional[torch.Tensor] = None,
+        mask: Optional[torch.Tensor] = None,
     ) -> None:
         if emissions.dim() != 3:
             raise ValueError(f"emissions must have dimension of 3, got {emissions.dim()}")
@@ -279,9 +278,7 @@ def _validate(
             # if not no_empty_seq and not no_empty_seq_bf:
             #     raise ValueError('mask of the first timestep must all be on')
 
-    def _compute_score(
-        self, emissions: torch.FloatTensor, tags: torch.LongTensor, mask: torch.ByteTensor
-    ) -> torch.Tensor:
+    def _compute_score(self, emissions: torch.Tensor, tags: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
         # emissions: (seq_length, batch_size, num_tags)
         # tags: (seq_length, batch_size)
         assert emissions.dim() == 3 and tags.dim() == 2
@@ -314,7 +311,7 @@ def _compute_score(
 
         return score
 
-    def _compute_normalizer(self, emissions: torch.FloatTensor, mask: torch.ByteTensor) -> torch.Tensor:
+    def _compute_normalizer(self, emissions: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
         # emissions: (seq_length, batch_size, num_tags)
         # mask: (seq_length, batch_size)
         assert emissions.dim() == 3 and mask.dim() == 2
@@ -353,7 +350,7 @@ def _compute_normalizer(self, emissions: torch.FloatTensor, mask: torch.ByteTens
         # Sum (log-sum-exp) over all possible tags
         return torch.logsumexp(score, dim=1)  # (batch_size,)
 
-    def _viterbi_decode(self, emissions: torch.FloatTensor, mask: torch.ByteTensor) -> List[List[int]]:
+    def _viterbi_decode(self, emissions: torch.Tensor, mask: torch.Tensor) -> List[List[int]]:
         # emissions: (seq_length, batch_size, num_tags)
         # mask: (seq_length, batch_size)
         assert emissions.dim() == 3 and mask.dim() == 2
@@ -398,7 +395,7 @@ def _viterbi_decode(self, emissions: torch.FloatTensor, mask: torch.ByteTensor)
 
         # Now, compute the best path for each sample
 
-        seq_ends = mask.long().sum(dim=0) - 1  # (batch_size,)
+        seq_ends: torch.Tensor = mask.long().sum(dim=0) - 1  # (batch_size,)
         best_tags_list = []
 
         for idx in range(batch_size):
@@ -408,7 +405,9 @@ def _viterbi_decode(self, emissions: torch.FloatTensor, mask: torch.ByteTensor)
 
             # We trace back where the best last tag comes from, append that to our best tag sequence, and
             # trace it back again, and so on
-            for hist in reversed(history[: seq_ends[idx]]):
+            end_index = seq_ends[idx].item()
+            assert isinstance(end_index, int)
+            for hist in reversed(history[:end_index]):
                 best_last_tag = hist[idx][best_tags[-1]]
                 best_tags.append(best_last_tag.item())
 
diff --git a/src/kwja/models/models/pooling.py b/src/kwja/models/models/pooling.py
index be4aaa6e..e4953d5f 100644
--- a/src/kwja/models/models/pooling.py
+++ b/src/kwja/models/models/pooling.py
@@ -38,5 +38,6 @@ def pool_subwords(
         return torch.amax(word_sequence_output + mask.unsqueeze(dim=3), dim=2)  # (b, word, hid)
     if strategy == PoolingStrategy.AVE:
         subword_map = subword_map.float()
-        conv_matrix = subword_map / (subword_map.sum(dim=2, keepdims=True) + 1e-6)  # (b, word, seq)
+        conv_matrix = subword_map / (subword_map.sum(dim=2, keepdim=True) + 1e-6)  # (b, word, seq)
         return torch.bmm(conv_matrix, sequence_output)  # (b, word, hid)
+    raise ValueError(f"Unknown pooling strategy: {strategy}")
diff --git a/src/kwja/utils/constants.py b/src/kwja/utils/constants.py
index 67b173bc..be2fd345 100644
--- a/src/kwja/utils/constants.py
+++ b/src/kwja/utils/constants.py
@@ -829,6 +829,8 @@
 )
 CONJFORM_TYPES = tuple(sorted(set(CONJFORM_TYPES_WITH_OVERLAP), key=CONJFORM_TYPES_WITH_OVERLAP.index))
 INDEX2CONJFORM_TYPE: Dict[int, str] = {index: conjform_type for index, conjform_type in enumerate(CONJFORM_TYPES)}
+INFLECTABLE = {(pos, subpos) for pos in ["動詞", "形容詞", "判定詞", "助動詞"] for subpos in POS_SUBPOS_TYPE2ID[pos].keys()}
+INFLECTABLE |= {("接尾辞", subpos) for subpos in ["形容詞性述語接尾辞", "形容詞性名詞接尾辞", "動詞性接尾辞"]}
 
 NE_TAGS: Tuple[str, ...] = sum(
     [(f"B-{cat.value}", f"I-{cat.value}") for cat in NamedEntityCategory if cat != NamedEntityCategory.OPTIONAL], ("O",)
diff --git a/src/kwja/utils/reading.py b/src/kwja/utils/reading.py
index b9c1603e..07eab12b 100644
--- a/src/kwja/utils/reading.py
+++ b/src/kwja/utils/reading.py
@@ -231,7 +231,7 @@ class Node:
         # build lattice
         # no node can cross boundaries
         td_lattice: List[List[List[Node]]] = []
-        td_holder: List[List[Tuple[Optional[Node], Optional[Node], int]]] = []
+        td_holder: List[List[Tuple[Optional[Node], Optional[Node], float]]] = []
         node: Optional[Node] = None
         node_prev: Optional[Node] = None
         for i in range(len(surf)):
diff --git a/src/kwja/utils/sub_document.py b/src/kwja/utils/sub_document.py
index afd9af65..b3aecdc3 100644
--- a/src/kwja/utils/sub_document.py
+++ b/src/kwja/utils/sub_document.py
@@ -7,9 +7,9 @@
 
 
 def is_target_sentence(sentence: Sentence) -> bool:
-    if sentence.doc_id is None:
+    if sentence.document.doc_id is None:
         return True
-    match = SUB_DOC_PAT.match(sentence.doc_id)
+    match = SUB_DOC_PAT.match(sentence.document.doc_id)
     if match is None:
         return True
     stride = int(match.group("stride"))
diff --git a/src/kwja/utils/util.py b/src/kwja/utils/util.py
index 7656677f..39efc19b 100644
--- a/src/kwja/utils/util.py
+++ b/src/kwja/utils/util.py
@@ -1,9 +1,10 @@
-from typing import Any, MutableMapping, MutableSequence, Union
+from omegaconf import DictConfig, ListConfig
 
 
 def filter_dict_items(
-    item: MutableMapping[str, Any], keys_to_ignore: MutableSequence[Union[str, MutableMapping[str, MutableSequence]]]
-) -> MutableMapping[str, Any]:
+    item: DictConfig,
+    keys_to_ignore: ListConfig,
+) -> DictConfig:
     """Filter out dictionary items whose key is in keys_to_ignore recursively."""
     for key, value in item.items():
         ignore = False
@@ -11,7 +12,7 @@ def filter_dict_items(
             if isinstance(key_to_ignore, str) and key == key_to_ignore:
                 ignore = True
                 break
-            elif isinstance(key_to_ignore, MutableMapping) and key in key_to_ignore:
+            elif isinstance(key_to_ignore, (dict, DictConfig)) and key in key_to_ignore:
                 item[key] = filter_dict_items(value, key_to_ignore[key])
                 break
         if ignore is True:
diff --git a/src/kwja/utils/word_normalize.py b/src/kwja/utils/word_normalize.py
index 54a45d83..905c0b9b 100644
--- a/src/kwja/utils/word_normalize.py
+++ b/src/kwja/utils/word_normalize.py
@@ -1,5 +1,5 @@
 import logging
-from typing import List
+from typing import List, Tuple
 
 import numpy as np
 from jinf import Jinf
@@ -341,20 +341,20 @@ def _denormalize(self, surf: str) -> str:
         if len(surf) == 1:
             return surf
         surf2 = self._denormalize_deterministic(surf, var_prolonged=True)
-        cands = [[surf, 10.0]]
+        cands: List[Tuple[str, float]] = [(surf, 10.0)]
         # D: ー, っ
         if is_kana(surf2[-1]):
             d = 0.1
             if surf2[-1] in ("ん", "ン", "っ", "ッ"):
                 d *= 0.1
-            cands.append([surf2 + "ー", d * 1.0])
-            cands.append([surf2 + "ーー", d * 0.1])
-            cands.append([surf2 + "ーーー", d * 0.05])
-            cands.append([surf2 + "〜", d * 0.2])
-            cands.append([surf2 + "〜〜", d * 0.02])
-            cands.append([surf2 + "〜〜〜", d * 0.001])
-            cands.append([surf2 + "っ", d * 0.1])
-            cands.append([surf2 + "ッ", d * 0.05])
+            cands.append((surf2 + "ー", d * 1.0))
+            cands.append((surf2 + "ーー", d * 0.1))
+            cands.append((surf2 + "ーーー", d * 0.05))
+            cands.append((surf2 + "〜", d * 0.2))
+            cands.append((surf2 + "〜〜", d * 0.02))
+            cands.append((surf2 + "〜〜〜", d * 0.001))
+            cands.append((surf2 + "っ", d * 0.1))
+            cands.append((surf2 + "ッ", d * 0.05))
         # D: ぁ, ぅ, ぉ, っ
         if surf2[-1] in (
             "あ",
@@ -375,9 +375,9 @@ def _denormalize(self, surf: str) -> str:
             "ゃ",
         ):
             d = 0.5
-            cands.append([surf2 + "ぁ", d * 1.0])
-            cands.append([surf2 + "ぁー", d * 0.1])
-            cands.append([surf2 + "ぁーー", d * 0.05])
+            cands.append((surf2 + "ぁ", d * 1.0))
+            cands.append((surf2 + "ぁー", d * 0.1))
+            cands.append((surf2 + "ぁーー", d * 0.05))
         elif surf2[-1] in (
             "う",
             "く",
@@ -396,9 +396,9 @@ def _denormalize(self, surf: str) -> str:
             "ゅ",
         ):
             d = 0.5
-            cands.append([surf2 + "ぅ", d * 1.0])
-            cands.append([surf2 + "ぅー", d * 0.01])
-            cands.append([surf2 + "ぅーー", d * 0.05])
+            cands.append((surf2 + "ぅ", d * 1.0))
+            cands.append((surf2 + "ぅー", d * 0.01))
+            cands.append((surf2 + "ぅーー", d * 0.05))
         elif surf2[-1] in (
             "お",
             "こ",
@@ -417,28 +417,28 @@ def _denormalize(self, surf: str) -> str:
             "ょ",
         ):
             d = 0.5
-            cands.append([surf2 + "ぉ", d * 1.0])
-            cands.append([surf2 + "ぉー", d * 0.1])
-            cands.append([surf2 + "ぉーー", d * 0.05])
+            cands.append((surf2 + "ぉ", d * 1.0))
+            cands.append((surf2 + "ぉー", d * 0.1))
+            cands.append((surf2 + "ぉーー", d * 0.05))
         elif surf2[-1] == "っ":
             d = 0.5
-            cands.append([surf2 + "っ", d * 1.0])
-            cands.append([surf2 + "っっ", d * 0.1])
-            cands.append([surf2 + "っっ", d * 0.05])
+            cands.append((surf2 + "っ", d * 1.0))
+            cands.append((surf2 + "っっ", d * 0.1))
+            cands.append((surf2 + "っっ", d * 0.05))
         elif len(surf2) >= 2 and surf2[-1] == "い" and is_kana(surf2[-2]):
             d = 1.0
-            cands.append([surf2[:-1] + "ーい", d * 1.0])
-            cands.append([surf2[:-1] + "ーーい", d * 0.1])
+            cands.append((surf2[:-1] + "ーい", d * 1.0))
+            cands.append((surf2[:-1] + "ーーい", d * 0.1))
         elif len(surf2) >= 2 and surf2[-1] == "く" and is_kana(surf2[-2]):
             d = 1.0
-            cands.append([surf2[:-1] + "ーく", d * 1.0])
-            cands.append([surf2[:-1] + "ーーく", d * 0.1])
+            cands.append((surf2[:-1] + "ーく", d * 1.0))
+            cands.append((surf2[:-1] + "ーーく", d * 0.1))
         if surf2 == "です":
-            cands.append(["でーす", 1.0])
-            cands.append(["で〜す", 0.2])
+            cands.append(("でーす", 1.0))
+            cands.append(("で〜す", 0.2))
         if surf2 == "ます":
-            cands.append(["まーす", 1.0])
-            cands.append(["ま〜す", 0.2])
+            cands.append(("まーす", 1.0))
+            cands.append(("ま〜す", 0.2))
         if len(cands) <= 1:
             return surf2
         probs = np.array(list(map(lambda x: x[1], cands)))


Test patch:
diff --git a/tests/callbacks/test_word_module_writer.py b/tests/callbacks/test_word_module_writer.py
index 70f58b5f..fd1d7857 100644
--- a/tests/callbacks/test_word_module_writer.py
+++ b/tests/callbacks/test_word_module_writer.py
@@ -225,10 +225,10 @@ def test_write_on_batch_end():
             # S-ID:test-0-0 kwja:{kwja.__version__}
             * 1D
             + 1D <体言>
-            今日 きょう 今日 名詞 6 時相名詞 10 * 0 * 0 "代表表記:今日/きょう カテゴリ:時間" <代表表記:今日/きょう><カテゴリ:時間><基本句-主辞>
+            今日 きょう 今日 名詞 6 時相名詞 10 * 0 * 0 "代表表記:今日/きょう カテゴリ:時間" <基本句-主辞>
             は は は 助詞 9 副助詞 2 * 0 * 0
             * -1D
-            + -1D <rel type="ガ" target="今日" sid="test-0-0" id="0"/><用言:判><時制:非過去><節-区切><レベル:C><状態述語><節-主辞><NE:DATE:晴れ><談話関係:test-0-0/1/原因・理由>
+            + -1D <rel type="ガ" target="今日" sid="test-0-0" id="0"/><用言:判><時制:非過去><レベル:C><状態述語><節-区切><節-主辞><NE:DATE:晴れ><談話関係:test-0-0/1/原因・理由>
             晴れ はれ 晴れ 名詞 6 普通名詞 1 * 0 * 0 <基本句-主辞><用言表記先頭><用言表記末尾>
             だ だ だ 判定詞 4 * 0 判定詞 25 基本形 2
             EOS