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

Issue:
Implement LR scheduler
## What

Implement the following LR schedulers:
- constant_schedule_with_warmup
- linear_schedule_with_warmup
- cosine_schedule_with_warmup

Use constant_schedule_with_warmup with warmup_steps 100 by default.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 2bfe4e90..6abf4ee4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,6 +104,7 @@ venv.bak/
 
 # local debugging
 data/
+!tests/data
 model/
 !configs/model/
 result/
diff --git a/configs/datamodule/word.yaml b/configs/datamodule/word.yaml
index 6442b47f..fe4a2b81 100644
--- a/configs/datamodule/word.yaml
+++ b/configs/datamodule/word.yaml
@@ -2,3 +2,7 @@ _target_: jula.datamodule.datasets.word_dataset.WordDataset
 model_name_or_path: ${model.model_name_or_path}
 max_seq_length: ${model.max_seq_length}
 tokenizer_kwargs: ${dataset.tokenizer_kwargs}
+cases: ${cases}
+bar_rels: ${bar_rels}
+exophora_referents: ${exophora_referents}
+cohesion_tasks: ${cohesion_tasks}
diff --git a/configs/dataset/word_dataset.yaml b/configs/dataset/word_dataset.yaml
index b82bf378..baedcc8e 100644
--- a/configs/dataset/word_dataset.yaml
+++ b/configs/dataset/word_dataset.yaml
@@ -13,4 +13,4 @@ defaults:
     - word_fuman.yaml
 
 tokenizer_kwargs:
-  additional_special_tokens: ["[ROOT]"]
+  additional_special_tokens: ["著者", "読者", "不特定:人", "不特定:物", "[NULL]", "[NA]", "[ROOT]"]
diff --git a/configs/scheduler/constant_schedule_with_warmup.yaml b/configs/scheduler/constant_schedule_with_warmup.yaml
new file mode 100644
index 00000000..0b90ac2f
--- /dev/null
+++ b/configs/scheduler/constant_schedule_with_warmup.yaml
@@ -0,0 +1,2 @@
+_target_: transformers.optimization.get_constant_schedule_with_warmup
+# num_warmup_steps:
diff --git a/configs/scheduler/cosine_schedule_with_warmup.yaml b/configs/scheduler/cosine_schedule_with_warmup.yaml
new file mode 100644
index 00000000..b23b65c1
--- /dev/null
+++ b/configs/scheduler/cosine_schedule_with_warmup.yaml
@@ -0,0 +1,3 @@
+_target_: transformers.optimization.get_cosine_schedule_with_warmup
+# num_warmup_steps:
+# num_training_steps:
diff --git a/configs/scheduler/linear_schedule_with_warmup.yaml b/configs/scheduler/linear_schedule_with_warmup.yaml
new file mode 100644
index 00000000..bc4b3edd
--- /dev/null
+++ b/configs/scheduler/linear_schedule_with_warmup.yaml
@@ -0,0 +1,3 @@
+_target_: transformers.optimization.get_linear_schedule_with_warmup
+# num_warmup_steps:
+# num_training_steps:
diff --git a/configs/typo_corrector.debug.yaml b/configs/typo_corrector.debug.yaml
index 3a9131ca..1a58efff 100644
--- a/configs/typo_corrector.debug.yaml
+++ b/configs/typo_corrector.debug.yaml
@@ -7,6 +7,7 @@ defaults:
   - model: char_roberta_base
   - module: default
   - optimizer: adamw
+  - scheduler: constant_schedule_with_warmup
   - trainer: debug
   - _self_
 
@@ -19,3 +20,5 @@ mode: min
 # batch size
 batch_size: 4
 accumulate_grad_batches: 1
+# scheduler options
+warmup_steps: 100
diff --git a/configs/typo_corrector.yaml b/configs/typo_corrector.yaml
index 2c6f63ab..a41ff10a 100644
--- a/configs/typo_corrector.yaml
+++ b/configs/typo_corrector.yaml
@@ -7,6 +7,7 @@ defaults:
   - model: char_roberta_base
   - module: default
   - optimizer: adamw
+  - scheduler: constant_schedule_with_warmup
   - trainer: default
   - _self_
 
@@ -20,3 +21,5 @@ mode: min
 # batch size
 batch_size: 4
 accumulate_grad_batches: 16
+# scheduler options
+warmup_steps: 100
diff --git a/configs/word_module.debug.yaml b/configs/word_module.debug.yaml
index 03d4cb9b..4013c708 100644
--- a/configs/word_module.debug.yaml
+++ b/configs/word_module.debug.yaml
@@ -7,15 +7,28 @@ defaults:
   - model: word_roberta_base
   - module: default
   - optimizer: adamw
+  - scheduler: constant_schedule_with_warmup
   - trainer: debug
   - _self_
 
 devices: ${oc.env:GPUS,0}
 do_predict_after_train: False
 
+# for cohesion analysis
+cases: ["ガ", "ヲ", "ニ", "ガ２"]
+bar_rels: ["ノ", "ノ？"]
+exophora_referents:
+  - 著者
+  - 読者
+  - 不特定:人
+  - 不特定:物
+cohesion_tasks: [pas_analysis, bridging, coreference]  # pas_analysis, bridging, and/or coreference
+
 # set monitor and mode for early_stopping and model_checkpoint
 monitor: valid/f1
 mode: max
 # batch size
-batch_size: 8
+batch_size: 4
 accumulate_grad_batches: 1
+# scheduler options
+warmup_steps: 100
diff --git a/configs/word_module.yaml b/configs/word_module.yaml
index b86fa1cd..7743be00 100644
--- a/configs/word_module.yaml
+++ b/configs/word_module.yaml
@@ -7,6 +7,7 @@ defaults:
   - model: word_roberta_base
   - module: default
   - optimizer: adamw
+  - scheduler: constant_schedule_with_warmup
   - trainer: default
   - _self_
 
@@ -14,9 +15,21 @@ devices: ${oc.env:GPUS,0}
 do_predict_after_train: True
 checkpoint_path: ""
 
+# for cohesion analysis
+cases: ["ガ", "ヲ", "ニ", "ガ２"]
+bar_rels: ["ノ", "ノ？"]
+exophora_referents:
+  - 著者
+  - 読者
+  - 不特定:人
+  - 不特定:物
+cohesion_tasks: [pas_analysis, bridging, coreference]  # pas_analysis, bridging, and/or coreference
+
 # set monitor and mode for early_stopping and model_checkpoint
 monitor: valid/f1
 mode: max
 # batch size
-batch_size: 8
+batch_size: 4
 accumulate_grad_batches: 1
+# scheduler options
+warmup_steps: 100
diff --git a/configs/word_segmenter.debug.yaml b/configs/word_segmenter.debug.yaml
index ac65b550..4674d1f7 100644
--- a/configs/word_segmenter.debug.yaml
+++ b/configs/word_segmenter.debug.yaml
@@ -7,6 +7,7 @@ defaults:
   - model: char_roberta_base
   - module: default
   - optimizer: adamw
+  - scheduler: constant_schedule_with_warmup
   - trainer: debug
   - _self_
 
@@ -21,3 +22,5 @@ batch_size: 8
 accumulate_grad_batches: 1
 # option
 add_ene_embed_before_encoder: True
+# scheduler options
+warmup_steps: 100
diff --git a/configs/word_segmenter.yaml b/configs/word_segmenter.yaml
index b3ebc85d..f3b80df1 100644
--- a/configs/word_segmenter.yaml
+++ b/configs/word_segmenter.yaml
@@ -7,6 +7,7 @@ defaults:
   - model: char_roberta_base
   - module: default
   - optimizer: adamw
+  - scheduler: constant_schedule_with_warmup
   - trainer: default
   - _self_
 
@@ -22,3 +23,5 @@ batch_size: 8
 accumulate_grad_batches: 1
 # option
 add_ene_embed_before_encoder: True
+# scheduler options
+warmup_steps: 100
diff --git a/poetry.lock b/poetry.lock
index c4c51a5f..fafdd95c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -465,6 +465,27 @@ python-versions = ">=3.6"
 [package.dependencies]
 pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
 
+[[package]]
+name = "pandas"
+version = "1.4.2"
+description = "Powerful data structures for data analysis, time series, and statistics"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+
+[package.dependencies]
+numpy = [
+    {version = ">=1.18.5", markers = "platform_machine != \"aarch64\" and platform_machine != \"arm64\" and python_version < \"3.10\""},
+    {version = ">=1.19.2", markers = "platform_machine == \"aarch64\" and python_version < \"3.10\""},
+    {version = ">=1.20.0", markers = "platform_machine == \"arm64\" and python_version < \"3.10\""},
+    {version = ">=1.21.0", markers = "python_version >= \"3.10\""},
+]
+python-dateutil = ">=2.8.1"
+pytz = ">=2020.1"
+
+[package.extras]
+test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
+
 [[package]]
 name = "pathtools"
 version = "0.1.2"
@@ -672,6 +693,14 @@ loggers = ["neptune-client (>=0.10.0)", "comet-ml (>=3.1.12)", "mlflow (>=1.0.0)
 strategies = ["fairscale (>=0.4.5)", "deepspeed", "horovod (>=0.21.2,!=0.24.0)", "hivemind (>=1.0.1)"]
 test = ["coverage (>=6.4)", "codecov (>=2.1)", "pytest (>=6.0)", "pytest-rerunfailures (>=10.2)", "mypy (>=0.920)", "flake8 (>=3.9.2)", "pre-commit (>=1.0)", "pytest-forked", "cloudpickle (>=1.3)", "scikit-learn (>0.22.1)", "onnxruntime", "pandas"]
 
+[[package]]
+name = "pytz"
+version = "2022.1"
+description = "World timezone definitions, modern and historical"
+category = "main"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "pyyaml"
 version = "6.0"
@@ -733,8 +762,8 @@ develop = false
 [package.source]
 type = "git"
 url = "https://github.com/ku-nlp/rhoknp.git"
-reference = "main"
-resolved_reference = "49921a059d07f27195a72ef3673bbc753a0a32e8"
+reference = "develop"
+resolved_reference = "b18316259d68e16637b5ddb492c81813a7672924"
 
 [[package]]
 name = "rich"
@@ -1164,7 +1193,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
 [metadata]
 lock-version = "1.1"
 python-versions = ">=3.9,<3.11"
-content-hash = "4041c31693cfe2de0ce9cefa285521035b71cb1f80e13171892df9b20c3ca4a5"
+content-hash = "92226622769f65fed22980030963ee7280d620fbaff3cb16128065247d01158d"
 
 [metadata.files]
 absl-py = [
@@ -1695,6 +1724,29 @@ packaging = [
     {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
     {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
 ]
+pandas = [
+    {file = "pandas-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be67c782c4f1b1f24c2f16a157e12c2693fd510f8df18e3287c77f33d124ed07"},
+    {file = "pandas-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5a206afa84ed20e07603f50d22b5f0db3fb556486d8c2462d8bc364831a4b417"},
+    {file = "pandas-1.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0010771bd9223f7afe5f051eb47c4a49534345dfa144f2f5470b27189a4dd3b5"},
+    {file = "pandas-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3228198333dd13c90b6434ddf61aa6d57deaca98cf7b654f4ad68a2db84f8cfe"},
+    {file = "pandas-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b79af3a69e5175c6fa7b4e046b21a646c8b74e92c6581a9d825687d92071b51"},
+    {file = "pandas-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5586cc95692564b441f4747c47c8a9746792e87b40a4680a2feb7794defb1ce3"},
+    {file = "pandas-1.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:061609334a8182ab500a90fe66d46f6f387de62d3a9cb9aa7e62e3146c712167"},
+    {file = "pandas-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b8134651258bce418cb79c71adeff0a44090c98d955f6953168ba16cc285d9f7"},
+    {file = "pandas-1.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:df82739e00bb6daf4bba4479a40f38c718b598a84654cbd8bb498fd6b0aa8c16"},
+    {file = "pandas-1.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:385c52e85aaa8ea6a4c600a9b2821181a51f8be0aee3af6f2dcb41dafc4fc1d0"},
+    {file = "pandas-1.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295872bf1a09758aba199992c3ecde455f01caf32266d50abc1a073e828a7b9d"},
+    {file = "pandas-1.4.2-cp38-cp38-win32.whl", hash = "sha256:95c1e422ced0199cf4a34385ff124b69412c4bc912011ce895582bee620dfcaa"},
+    {file = "pandas-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5c54ea4ef3823108cd4ec7fb27ccba4c3a775e0f83e39c5e17f5094cb17748bc"},
+    {file = "pandas-1.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c072c7f06b9242c855ed8021ff970c0e8f8b10b35e2640c657d2a541c5950f59"},
+    {file = "pandas-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f549097993744ff8c41b5e8f2f0d3cbfaabe89b4ae32c8c08ead6cc535b80139"},
+    {file = "pandas-1.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff08a14ef21d94cdf18eef7c569d66f2e24e0bc89350bcd7d243dd804e3b5eb2"},
+    {file = "pandas-1.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c5bf555b6b0075294b73965adaafb39cf71c312e38c5935c93d78f41c19828a"},
+    {file = "pandas-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51649ef604a945f781105a6d2ecf88db7da0f4868ac5d45c51cb66081c4d9c73"},
+    {file = "pandas-1.4.2-cp39-cp39-win32.whl", hash = "sha256:d0d4f13e4be7ce89d7057a786023c461dd9370040bdb5efa0a7fe76b556867a0"},
+    {file = "pandas-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:09d8be7dd9e1c4c98224c4dfe8abd60d145d934e9fc1f5f411266308ae683e6a"},
+    {file = "pandas-1.4.2.tar.gz", hash = "sha256:92bc1fc585f1463ca827b45535957815b7deb218c549b7c18402c322c7549a12"},
+]
 pathtools = [
     {file = "pathtools-0.1.2.tar.gz", hash = "sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0"},
 ]
@@ -1834,6 +1886,10 @@ pytorch-lightning = [
     {file = "pytorch-lightning-1.6.4.tar.gz", hash = "sha256:5459f2c3e67676ec59e94576d1499e9559d214e7df41eadd135db64b4ccf54b9"},
     {file = "pytorch_lightning-1.6.4-py3-none-any.whl", hash = "sha256:0f42f93116a3fcb6fd8c9ea45cf7c918e4aa3f848ae21d0e9ac2bf39f2865dd7"},
 ]
+pytz = [
+    {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
+    {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
+]
 pyyaml = [
     {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
     {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
@@ -2013,8 +2069,6 @@ sentencepiece = [
     {file = "sentencepiece-0.1.96-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e9e9fe8094ca57549d801e9a2017ac5c24108bbf485ea4f8994a72e8e96ee135"},
     {file = "sentencepiece-0.1.96-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b77d27f59d515c43b61745b8173fbe7c7b3014b14b3702a75bf1793471e7def6"},
     {file = "sentencepiece-0.1.96-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dac8c2ad02b5ebc1179c0a14cbc7d7c6f4fd73d4dd51820626402d0aefc974e"},
-    {file = "sentencepiece-0.1.96-cp310-cp310-win32.whl", hash = "sha256:3028699bdb2fb0230804f3b8a617fe3af22f5c5a56416419b31a7da5e7bf83bc"},
-    {file = "sentencepiece-0.1.96-cp310-cp310-win_amd64.whl", hash = "sha256:203443a7bd4295b6a3695787235abe0e77d4c369d7156a6b9a397c540a38bd27"},
     {file = "sentencepiece-0.1.96-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:e8ec5bb6777e2060e1499750c50e1b69dca5a0f80f90f2c66656c5f3e5244593"},
     {file = "sentencepiece-0.1.96-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:99ea2d9db19e63a2d17d5dc64f9ace83fb9308a735be05a1aaf98eb4b496fba7"},
     {file = "sentencepiece-0.1.96-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeb090ad462833df03af1debce4ae607a2766ef861f992003ad0c56d074ab805"},
diff --git a/pyproject.toml b/pyproject.toml
index c7df4a30..4012c6c4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,11 +26,12 @@ protobuf = "~3.20"
 seqeval = "^1.2.2"
 wandb = "^0.12.17"
 rich = "^12.4.4"
-rhoknp = {git = "https://github.com/ku-nlp/rhoknp.git", rev = "main"}
+rhoknp = { git = "https://github.com/ku-nlp/rhoknp.git", rev = "develop" }
 python-Levenshtein = "^0.12.2"
 fugashi = "^1.1.2"
 ipadic = "^1.0.0"
 python-dotenv = "^0.20.0"
+pandas = "^1.4.2"
 dartsclone = "^0.10.2"
 
 [tool.poetry.dev-dependencies]
diff --git a/scripts/train.py b/scripts/train.py
index bf0caaf0..9d69c54c 100644
--- a/scripts/train.py
+++ b/scripts/train.py
@@ -1,3 +1,4 @@
+import logging
 from typing import Optional, Union
 
 import hydra
@@ -15,6 +16,7 @@
 from jula.models.word_module import WordModule
 
 hf_logging.set_verbosity(hf_logging.ERROR)
+logging.getLogger("rhoknp").setLevel(logging.WARNING)
 
 
 @hydra.main(version_base=None, config_path="../configs", config_name="word_segmenter")
diff --git a/src/jula/callbacks/word_module_writer.py b/src/jula/callbacks/word_module_writer.py
index 04268901..68139576 100644
--- a/src/jula/callbacks/word_module_writer.py
+++ b/src/jula/callbacks/word_module_writer.py
@@ -17,6 +17,7 @@
     INDEX2DEPENDENCY_TYPE,
     INDEX2POS_TYPE,
     INDEX2SUBPOS_TYPE,
+    WORD_FEATURES,
 )
 
 
@@ -82,50 +83,55 @@ def write_on_epoch_end(
                 batch_conjform_preds = torch.argmax(
                     prediction_step_output["word_analysis_conjform_logits"], dim=-1
                 )
-                batch_phrase_analysis_preds = torch.where(
-                    prediction_step_output["phrase_analysis_logits"] >= 0.5, 1.0, 0.0
+                batch_word_feature_predictions = (
+                    prediction_step_output["word_feature_logits"].ge(0.5).long()
                 )
-                batch_dependency_preds = torch.argmax(
+                batch_base_phrase_feature_predictions = (
+                    prediction_step_output["base_phrase_feature_logits"].ge(0.5).long()
+                )
+                batch_dependency_predictions = torch.argmax(
                     prediction_step_output["dependency_logits"], dim=2
                 )
-                batch_dependency_type_preds = torch.argmax(
+                batch_dependency_type_predictions = torch.argmax(
                     prediction_step_output["dependency_type_logits"], dim=2
                 )
                 for (
-                    document_id,
+                    example_id,
                     pos_preds,
                     subpos_preds,
                     conjtype_preds,
                     conjform_preds,
-                    phrase_analysis_preds,
-                    base_phrase_features,
-                    dependency_preds,
-                    dependency_type_preds,
+                    word_feature_predictions,
+                    base_phrase_feature_predictions,
+                    dependency_predictions,
+                    dependency_type_predictions,
                 ) in zip(
-                    prediction_step_output["document_ids"],
+                    prediction_step_output["example_ids"],
                     batch_pos_preds.tolist(),
                     batch_subpos_preds.tolist(),
                     batch_conjtype_preds.tolist(),
                     batch_conjform_preds.tolist(),
-                    batch_phrase_analysis_preds.tolist(),
-                    prediction_step_output["base_phrase_features"].tolist(),
-                    batch_dependency_preds.tolist(),
-                    batch_dependency_type_preds.tolist(),
+                    batch_word_feature_predictions.tolist(),
+                    batch_base_phrase_feature_predictions.tolist(),
+                    batch_dependency_predictions.tolist(),
+                    batch_dependency_type_predictions.tolist(),
                 ):
-                    document = dataset.documents[document_id]
+                    document = dataset.documents[example_id]
                     results[corpus].append(
                         [
-                            self.convert_predictions(values, len(dependency_preds))
+                            self.convert_predictions(
+                                values, len(dependency_predictions)
+                            )
                             for values in zip(
                                 document.morphemes,
                                 pos_preds,
                                 subpos_preds,
                                 conjtype_preds,
                                 conjform_preds,
-                                phrase_analysis_preds,
-                                base_phrase_features,
-                                dependency_preds,
-                                dependency_type_preds,
+                                word_feature_predictions,
+                                base_phrase_feature_predictions,
+                                dependency_predictions,
+                                dependency_type_predictions,
                             )
                         ]
                     )
@@ -154,25 +160,40 @@ def convert_word_analysis_pred(
         return f"{pred}|{label}"
 
     @staticmethod
-    def convert_phrase_analysis_pred(pred, label, head):
-        pred, label = map(
-            lambda x: " ".join(
-                f"<{feature}>"
-                for feature, element in zip(BASE_PHRASE_FEATURES, x)
-                if element == 1.0
-            ),
-            [pred, label],
+    def convert_phrase_analysis_prediction(
+        word_feature_prediction,
+        base_phrase_feature_prediction,
+        is_base_phrase_head: bool,
+    ):
+        word_features = "".join(
+            f"<{feature}>"
+            for feature, pred in zip(WORD_FEATURES, word_feature_prediction)
+            if pred == 1
         )
-        if not head:
-            return f"|{label}"
+        if not is_base_phrase_head:
+            return f"{word_features}|"
         else:
-            return f"{pred}|{label}"
+            base_phrase_features = "".join(
+                f"<{feature}>"
+                for feature, pred in zip(
+                    BASE_PHRASE_FEATURES, base_phrase_feature_prediction
+                )
+                if pred == 1
+            )
+            return f"{word_features}|{base_phrase_features}"
 
     @staticmethod
-    def convert_dependency_parsing_pred(morpheme, pred, type_pred, max_seq_len):
+    def convert_dependency_parsing_prediction(
+        morpheme, dependency_prediction, dependency_type_prediction, seq_len
+    ):
         offset = min(morpheme.global_index for morpheme in morpheme.sentence.morphemes)
-        system_head = pred - offset if pred != max_seq_len - 1 else -1
-        system_deprel = INDEX2DEPENDENCY_TYPE[type_pred] if system_head >= 0 else "ROOT"
+        if dependency_prediction == seq_len - 1:
+            system_head = -1
+            system_deprel = "D"
+        else:
+            system_head = dependency_prediction - offset
+            system_deprel = INDEX2DEPENDENCY_TYPE[dependency_type_prediction]
+
         if morpheme == morpheme.base_phrase.head:
             gold_head = morpheme.parent.index if morpheme.parent else -1
             gold_deprel = (
@@ -184,17 +205,17 @@ def convert_dependency_parsing_pred(morpheme, pred, type_pred, max_seq_len):
             gold_deprel = "D"
             return f"{system_head}{system_deprel}|{gold_head}{gold_deprel}"
 
-    def convert_predictions(self, values, max_seq_len):
+    def convert_predictions(self, values, seq_len):
         (
             morpheme,
             pos_pred,
             subpos_pred,
             conjtype_pred,
             conjform_pred,
-            phrase_analysis_pred,
-            base_phrase_feature,
-            dependency_pred,
-            dependency_type_pred,
+            word_feature_prediction,
+            base_phrase_feature_prediction,
+            dependency_prediction,
+            dependency_type_prediction,
         ) = values
         id_, surf = morpheme.index, morpheme.surf
         word_analysis_result = self.convert_word_analysis_pred(
@@ -204,12 +225,12 @@ def convert_predictions(self, values, max_seq_len):
             conjtype_pred,
             conjform_pred,
         )
-        phrase_analysis_result = self.convert_phrase_analysis_pred(
-            phrase_analysis_pred,
-            base_phrase_feature,
+        phrase_analysis_result = self.convert_phrase_analysis_prediction(
+            word_feature_prediction,
+            base_phrase_feature_prediction,
             morpheme == morpheme.base_phrase.head,
         )
-        dependency_parsing_result = self.convert_dependency_parsing_pred(
-            morpheme, dependency_pred, dependency_type_pred, max_seq_len
+        dependency_parsing_result = self.convert_dependency_parsing_prediction(
+            morpheme, dependency_prediction, dependency_type_prediction, seq_len
         )
         return f"{id_}|{surf}|{word_analysis_result}|{phrase_analysis_result}|{dependency_parsing_result}"
diff --git a/src/jula/datamodule/datasets/base_dataset.py b/src/jula/datamodule/datasets/base_dataset.py
index 7b30bdd7..b12befaf 100644
--- a/src/jula/datamodule/datasets/base_dataset.py
+++ b/src/jula/datamodule/datasets/base_dataset.py
@@ -1,3 +1,4 @@
+import sys
 from pathlib import Path
 
 import hydra
@@ -14,12 +15,14 @@ def __init__(
         max_seq_length: int = 512,
         tokenizer_kwargs: dict = None,
         ext: str = "knp",
+        **kwargs,
     ) -> None:
         self.path = Path(path)
         assert self.path.is_dir()
 
         self.documents = self.load_documents(self.path, ext)
         assert len(self) != 0
+        self.document_ids: list[str] = [doc.doc_id for doc in self.documents]
 
         if tokenizer_kwargs:
             tokenizer_kwargs = hydra.utils.instantiate(
@@ -40,6 +43,9 @@ def __len__(self) -> int:
     def load_documents(path: Path, ext: str = "knp") -> list[Document]:
         documents = []
         for file_path in sorted(path.glob(f"**/*.{ext}")):
-            with file_path.open("rt") as f:
-                documents.append(Document.from_knp(f.read()))
+            # TODO: fix document file
+            try:
+                documents.append(Document.from_knp(file_path.read_text()))
+            except AssertionError:
+                print(f"{file_path} is not a valid knp file.", file=sys.stderr)
         return documents
diff --git a/src/jula/datamodule/datasets/char_dataset.py b/src/jula/datamodule/datasets/char_dataset.py
index d4c8692c..b39c8255 100644
--- a/src/jula/datamodule/datasets/char_dataset.py
+++ b/src/jula/datamodule/datasets/char_dataset.py
@@ -4,6 +4,7 @@
 import torch
 from rhoknp import Document
 from transformers import BatchEncoding
+from transformers.utils import PaddingStrategy
 
 from jula.datamodule.datasets.base_dataset import BaseDataset
 from jula.utils.utils import ENE_TYPE_BIES, IGNORE_INDEX, SEG_TYPES
@@ -28,7 +29,7 @@ def __init__(
     def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
         document = self.documents[index]
         return {
-            "document_id": torch.tensor(index, dtype=torch.long),
+            "example_ids": torch.tensor(index, dtype=torch.long),
             **self.encode(document),
         }
 
@@ -66,7 +67,7 @@ def encode(self, document: Document) -> dict[str, torch.Tensor]:
         encoding: BatchEncoding = self.tokenizer(
             document.text,
             truncation=True,
-            padding="max_length",
+            padding=PaddingStrategy.MAX_LENGTH,
             max_length=self.max_seq_length,
         )
         input_ids = encoding["input_ids"]
diff --git a/src/jula/datamodule/datasets/custom_concat_dataset.py b/src/jula/datamodule/datasets/custom_concat_dataset.py
index 8a3d63bf..d2bc0c47 100644
--- a/src/jula/datamodule/datasets/custom_concat_dataset.py
+++ b/src/jula/datamodule/datasets/custom_concat_dataset.py
@@ -17,5 +17,5 @@ def __getitem__(self, idx):
         else:
             sample_idx = idx - self.cumulative_sizes[dataset_idx - 1]
         sample = self.datasets[dataset_idx][sample_idx]
-        sample["document_id"] = idx
+        sample["example_ids"] = idx
         return sample
diff --git a/src/jula/datamodule/datasets/raw_text_dataset.py b/src/jula/datamodule/datasets/raw_text_dataset.py
index 707e830b..7dbb6df4 100644
--- a/src/jula/datamodule/datasets/raw_text_dataset.py
+++ b/src/jula/datamodule/datasets/raw_text_dataset.py
@@ -2,6 +2,7 @@
 import torch
 from torch.utils.data import Dataset
 from transformers import AutoTokenizer, BatchEncoding, PreTrainedTokenizer
+from transformers.utils import PaddingStrategy
 
 
 class RawTextDataset(Dataset):
@@ -35,7 +36,7 @@ def encode(self, text: str) -> dict[str, torch.Tensor]:
         encoding: BatchEncoding = self.tokenizer(
             text,
             truncation=True,
-            padding="max_length",
+            padding=PaddingStrategy.MAX_LENGTH,
             max_length=self.max_seq_length,
         )
         input_ids = encoding["input_ids"]
diff --git a/src/jula/datamodule/datasets/segmented_text_dataset.py b/src/jula/datamodule/datasets/segmented_text_dataset.py
index 57a534df..820b9c54 100644
--- a/src/jula/datamodule/datasets/segmented_text_dataset.py
+++ b/src/jula/datamodule/datasets/segmented_text_dataset.py
@@ -2,6 +2,7 @@
 import torch
 from torch.utils.data import Dataset
 from transformers import AutoTokenizer, BatchEncoding, PreTrainedTokenizer
+from transformers.utils import PaddingStrategy
 
 
 class SegmentedTextDataset(Dataset):
@@ -30,7 +31,7 @@ def __len__(self) -> int:
 
     def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
         return {
-            "document_id": torch.tensor(index, dtype=torch.long),
+            "example_ids": torch.tensor(index, dtype=torch.long),
             **self.encode(self.texts[index]),
         }
 
@@ -38,7 +39,7 @@ def encode(self, text: str) -> dict[str, torch.Tensor]:
         encoding: BatchEncoding = self.tokenizer(
             text,
             truncation=True,
-            padding="max_length",
+            padding=PaddingStrategy.MAX_LENGTH,
             max_length=self.max_seq_length - 1,
         )
         input_ids = encoding["input_ids"] + [self.tokenizer.vocab["[ROOT]"]]
diff --git a/src/jula/datamodule/datasets/typo_dataset.py b/src/jula/datamodule/datasets/typo_dataset.py
index 6b48305c..5ebbf76b 100644
--- a/src/jula/datamodule/datasets/typo_dataset.py
+++ b/src/jula/datamodule/datasets/typo_dataset.py
@@ -6,6 +6,7 @@
 import torch
 from torch.utils.data import Dataset
 from transformers import AutoTokenizer, BatchEncoding, PreTrainedTokenizer
+from transformers.utils import PaddingStrategy
 
 from jula.utils.utils import TYPO_DUMMY_TOKEN, TYPO_OPN2TOKEN
 
@@ -68,7 +69,7 @@ def encode(
         encoding: BatchEncoding = self.tokenizer(
             document["pre_text"] + TYPO_DUMMY_TOKEN,
             truncation=True,
-            padding="max_length",
+            padding=PaddingStrategy.MAX_LENGTH,
             max_length=self.max_seq_length,
         )
         input_ids = encoding["input_ids"]
diff --git a/src/jula/datamodule/datasets/word_dataset.py b/src/jula/datamodule/datasets/word_dataset.py
index 685c7e9b..b1923e93 100644
--- a/src/jula/datamodule/datasets/word_dataset.py
+++ b/src/jula/datamodule/datasets/word_dataset.py
@@ -1,8 +1,18 @@
 import torch
 from rhoknp import Document
-from transformers import BatchEncoding
+from rhoknp.rel import ExophoraReferent
+from scipy.special import softmax
+from tokenizers import Encoding
+from transformers.utils import PaddingStrategy
 
 from jula.datamodule.datasets.base_dataset import BaseDataset
+from jula.datamodule.examples import CohesionExample, Task
+from jula.datamodule.extractors import (
+    BridgingExtractor,
+    CoreferenceExtractor,
+    PasExtractor,
+)
+from jula.datamodule.extractors.base import Phrase
 from jula.utils.utils import (
     BASE_PHRASE_FEATURES,
     CONJFORM_TYPES,
@@ -17,29 +27,77 @@
 
 
 class WordDataset(BaseDataset):
+    def __init__(self, *args, **kwargs):
+        self.exophora_referents: list[ExophoraReferent] = [
+            ExophoraReferent(s) for s in kwargs.pop("exophora_referents")
+        ]
+        self.special_tokens: list[str] = [str(e) for e in self.exophora_referents] + [
+            "[NULL]",
+            "[NA]",
+            "[ROOT]",
+        ]
+        tokenizer_kwargs = {
+            "additional_special_tokens": self.special_tokens,
+        }
+        if "tokenizer_kwargs" in kwargs:
+            kwargs["tokenizer_kwargs"].update(tokenizer_kwargs)
+        else:
+            kwargs["tokenizer_kwargs"] = tokenizer_kwargs
+
+        super().__init__(*args, **kwargs)
+
+        self.cohesion_tasks: list[Task] = [Task(t) for t in kwargs["cohesion_tasks"]]
+
+        self.cases: list[str] = list(kwargs["cases"])
+        self.pas_targets: list[str] = ["pred", "noun"]
+        self.bar_rels = list(kwargs["bar_rels"])
+        self.extractors = {
+            Task.PAS_ANALYSIS: PasExtractor(
+                self.cases, self.pas_targets, self.exophora_referents, kc=False
+            ),
+            Task.COREFERENCE: CoreferenceExtractor(self.exophora_referents, kc=False),
+            Task.BRIDGING: BridgingExtractor(
+                self.bar_rels, self.exophora_referents, kc=False
+            ),
+        }
+        self.special_to_index: dict[str, int] = {
+            token: self.max_seq_length - len(self.special_tokens) + i
+            for i, token in enumerate(self.special_tokens)
+        }
+        self.cohesion_examples: dict[str, CohesionExample] = {}
+        for example_id, document in enumerate(self.documents):
+            cohesion_example = self._load_cohesion_example(document)
+            cohesion_example.example_id = example_id
+            self.cohesion_examples[document.doc_id] = cohesion_example
+
+    @property
+    def special_indices(self) -> list[int]:
+        return list(self.special_to_index.values())
+
+    @property
+    def num_special_tokens(self) -> int:
+        return len(self.special_tokens)
+
+    def _load_cohesion_example(self, document: Document) -> CohesionExample:
+        example = CohesionExample()
+        example.load(document, tasks=self.cohesion_tasks, extractors=self.extractors)
+        return example
+
     def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
         document = self.documents[index]
-        return {
-            "document_id": torch.tensor(index, dtype=torch.long),
-            **self.encode(document),
-        }
+        cohesion_example = self.cohesion_examples[document.doc_id]
+        return self.encode(document, cohesion_example)
 
-    def encode(self, document: Document) -> dict[str, torch.Tensor]:
+    def encode(
+        self, document: Document, cohesion_example: CohesionExample
+    ) -> dict[str, torch.Tensor]:
         # TODO: deal with the case that the document is too long
-        encoding: BatchEncoding = self.tokenizer(
+        encoding: Encoding = self.tokenizer(
             " ".join(morpheme.text for morpheme in document.morphemes),
             truncation=True,
-            padding="max_length",
-            max_length=self.max_seq_length - 1,
-        )
-        input_ids = encoding["input_ids"] + [self.tokenizer.vocab["[ROOT]"]]
-        attention_mask = encoding["attention_mask"] + [1]
-        subword_map = [
-            [False] * self.max_seq_length for _ in range(self.max_seq_length)
-        ]
-        for token_id, word_id in enumerate(encoding.word_ids()):
-            if word_id is not None:
-                subword_map[word_id][token_id] = True
+            padding=PaddingStrategy.MAX_LENGTH,
+            max_length=self.max_seq_length - self.num_special_tokens,
+        ).encodings[0]
 
         # NOTE: hereafter, indices are given at the word level
         mrph_types: list[list[int]] = [
@@ -61,7 +119,12 @@ def encode(self, document: Document) -> dict[str, torch.Tensor]:
                     morpheme.conjform
                 )
 
-        word_features = [[0] * len(WORD_FEATURES) for _ in range(self.max_seq_length)]
+        word_features = [
+            [0] * len(WORD_FEATURES)
+            if i < len(document.morphemes)
+            else [IGNORE_INDEX] * len(WORD_FEATURES)
+            for i in range(self.max_seq_length)
+        ]
         for base_phrase in document.base_phrases:
             head = base_phrase.head
             end = base_phrase.morphemes[-1]
@@ -93,7 +156,7 @@ def encode(self, document: Document) -> dict[str, torch.Tensor]:
             if parent:
                 dependencies[morpheme.global_index] = morpheme.parent.global_index
             else:  # 係り先がなければ文末の[ROOT]を指す
-                dependencies[morpheme.global_index] = self.max_seq_length - 1
+                dependencies[morpheme.global_index] = self.special_to_index["[ROOT]"]
 
         intra_mask = [[False] * self.max_seq_length for _ in range(self.max_seq_length)]
         for sentence in document.sentences:
@@ -122,7 +185,43 @@ def encode(self, document: Document) -> dict[str, torch.Tensor]:
                         "D"
                     )
 
-        # TODO: PAS analysis & coreference resolution
+        # PAS analysis & coreference resolution
+        cohesion_example.encoding = encoding
+        cohesion_target: list[list[list[int]]] = []  # (task, src, tgt)
+        candidates_set: list[list[list[int]]] = []  # (task, src, tgt)
+        if Task.PAS_ANALYSIS in self.cohesion_tasks:
+            task = Task.PAS_ANALYSIS
+            annotation = cohesion_example.annotations[task]
+            phrases = cohesion_example.phrases[task]
+            for case in self.cases:
+                arguments_set = [
+                    arguments[case] for arguments in annotation.arguments_set
+                ]
+                ret = self._convert_annotation_to_feature(arguments_set, phrases)
+                cohesion_target.append(ret[0])
+                candidates_set.append(ret[1])
+        for task in (Task.BRIDGING, Task.COREFERENCE):
+            if task not in self.cohesion_tasks:
+                continue
+            annotation = cohesion_example.annotations[task].arguments_set
+            phrases = cohesion_example.phrases[task]
+            ret = self._convert_annotation_to_feature(annotation, phrases)
+            cohesion_target.append(ret[0])
+            candidates_set.append(ret[1])
+
+        special_encoding: Encoding = self.tokenizer(
+            self.special_tokens,
+            is_split_into_words=True,
+            padding=PaddingStrategy.DO_NOT_PAD,
+            truncation=False,
+            add_special_tokens=False,
+        ).encodings[0]
+        merged_encoding: Encoding = Encoding.merge([encoding, special_encoding])
+        cohesion_mask = [
+            [[(x in cands) for x in range(self.max_seq_length)] for cands in candidates]
+            for candidates in candidates_set
+        ]  # False -> mask, True -> keep
+
         # TODO: discourse relation analysis
         discourse_relations = [
             [
@@ -131,20 +230,128 @@ def encode(self, document: Document) -> dict[str, torch.Tensor]:
             ]
             for _ in range(self.max_seq_length)
         ]
+
         return {
-            "input_ids": torch.tensor(input_ids, dtype=torch.long),
-            "attention_mask": torch.tensor(attention_mask, dtype=torch.long),
-            "subword_map": torch.tensor(subword_map, dtype=torch.bool),
+            "example_ids": torch.tensor(cohesion_example.example_id, dtype=torch.long),
+            "input_ids": torch.tensor(merged_encoding.ids, dtype=torch.long),
+            "attention_mask": torch.tensor(
+                merged_encoding.attention_mask, dtype=torch.long
+            ),
+            "subword_map": torch.tensor(
+                self._gen_subword_map(encoding), dtype=torch.bool
+            ),
             "mrph_types": torch.tensor(mrph_types, dtype=torch.long),
-            "word_features": torch.tensor(word_features, dtype=torch.float),
+            "word_features": torch.tensor(word_features, dtype=torch.long),
             "base_phrase_features": torch.tensor(
-                base_phrase_features, dtype=torch.float
-            ),
-            "num_base_phrases": torch.tensor(
-                len(document.base_phrases), dtype=torch.long
+                base_phrase_features, dtype=torch.long
             ),
             "dependencies": torch.tensor(dependencies, dtype=torch.long),
             "intra_mask": torch.tensor(intra_mask, dtype=torch.bool),
             "dependency_types": torch.tensor(dependency_types, dtype=torch.long),
             "discourse_relations": torch.tensor(discourse_relations, dtype=torch.long),
+            "cohesion_target": torch.tensor(cohesion_target, dtype=torch.int),
+            "cohesion_mask": torch.tensor(cohesion_mask, dtype=torch.bool),
         }
+
+    def dump_prediction(
+        self,
+        result: list[list[list[float]]],  # word level
+        example: CohesionExample,
+    ) -> list[list[list[float]]]:  # (phrase, rel, 0 or phrase+special)
+        """1 example 中に存在する基本句それぞれに対してシステム予測のリストを返す．"""
+        ret: list[list[list[float]]] = [
+            [] for _ in next(iter(example.phrases.values()))
+        ]
+        task_idx = 0
+        if Task.PAS_ANALYSIS in self.cohesion_tasks:
+            for _ in self.cases:
+                for i, p in enumerate(
+                    self._token2bp_level(
+                        result[task_idx], example.phrases[Task.PAS_ANALYSIS]
+                    )
+                ):
+                    ret[i].append(p)
+                task_idx += 1
+        if Task.BRIDGING in self.cohesion_tasks:
+            for i, p in enumerate(
+                self._token2bp_level(result[task_idx], example.phrases[Task.BRIDGING])
+            ):
+                ret[i].append(p)
+            task_idx += 1
+        if Task.COREFERENCE in self.cohesion_tasks:
+            for i, p in enumerate(
+                self._token2bp_level(
+                    result[task_idx], example.phrases[Task.COREFERENCE]
+                )
+            ):
+                ret[i].append(p)
+            task_idx += 1
+        return ret
+
+    def _token2bp_level(
+        self,
+        word_level_output: list[list[float]],
+        phrases: list[Phrase],
+    ) -> list[list[float]]:  # (bp, 0 or bp+special)
+        ret: list[list[float]] = [[] for _ in phrases]
+        for anaphor in filter(lambda p: p.is_target, phrases):
+            word_level_scores: list[float] = word_level_output[anaphor.dmid]
+            phrase_level_scores: list[float] = []
+            for tgt_bp in phrases:
+                if tgt_bp.dtid not in anaphor.candidates:
+                    phrase_level_scores.append(-1)  # pad -1 for non-candidate phrases
+                    continue
+                phrase_level_scores.append(word_level_scores[tgt_bp.dmid])
+            phrase_level_scores += [
+                word_level_scores[idx] for idx in self.special_indices
+            ]
+            assert len(phrase_level_scores) == len(phrases) + len(self.special_to_index)
+            ret[anaphor.dtid] = softmax(phrase_level_scores).tolist()
+        return ret
+
+    def _gen_subword_map(self, encoding: Encoding) -> list[list[bool]]:
+        subword_map = [
+            [False] * self.max_seq_length for _ in range(self.max_seq_length)
+        ]
+        for token_id, word_id in enumerate(encoding.word_ids):
+            if word_id is not None:
+                subword_map[word_id][token_id] = True
+        return subword_map
+
+    def _convert_annotation_to_feature(
+        self,
+        annotation: list[list[str]],  # phrase level
+        phrases: list[Phrase],
+    ) -> tuple[list[list[int]], list[list[int]]]:
+        scores_set: list[list[int]] = [
+            [0] * self.max_seq_length for _ in range(self.max_seq_length)
+        ]
+        candidates_set: list[list[int]] = [[] for _ in range(self.max_seq_length)]
+
+        for phrase in phrases:
+            arguments: list[str] = annotation[phrase.dtid]
+            candidates: list[int] = phrase.candidates  # phrase level
+            for mrph in filter(lambda m: m.is_target, phrase.children):
+                scores: list[int] = [0] * self.max_seq_length
+                for arg_string in arguments:
+                    # arg_string: 著者, 8%C, 15%O, 2, NULL, ...
+                    if arg_string[-2:] in ("%C", "%N", "%O"):
+                        # PAS only
+                        # flag = arg_string[-1]
+                        arg_string = arg_string[:-2]
+                    if arg_string in self.special_to_index:
+                        word_index = self.special_to_index[arg_string]
+                    else:
+                        word_index = phrases[int(arg_string)].dmid
+                    scores[word_index] = 1
+
+                word_level_candidates: list[int] = []
+                for candidate in candidates:
+                    word_level_candidates.append(phrases[candidate].dmid)
+                word_level_candidates += self.special_indices
+
+                # use the head subword as the representative of the source word
+                scores_set[mrph.dmid] = scores
+                candidates_set[mrph.dmid] = word_level_candidates
+
+        return scores_set, candidates_set  # word level
diff --git a/src/jula/datamodule/examples/__init__.py b/src/jula/datamodule/examples/__init__.py
new file mode 100644
index 00000000..df6e6338
--- /dev/null
+++ b/src/jula/datamodule/examples/__init__.py
@@ -0,0 +1,3 @@
+from .cohesion import CohesionExample, Task
+
+__all__ = ["CohesionExample", "Task"]
diff --git a/src/jula/datamodule/examples/cohesion.py b/src/jula/datamodule/examples/cohesion.py
new file mode 100644
index 00000000..0f307883
--- /dev/null
+++ b/src/jula/datamodule/examples/cohesion.py
@@ -0,0 +1,108 @@
+import logging
+from enum import Enum
+from typing import Optional
+
+from rhoknp import Document
+from tokenizers import Encoding
+
+from jula.datamodule.extractors import (
+    Annotation,
+    BridgingExtractor,
+    CoreferenceExtractor,
+    PasExtractor,
+)
+from jula.datamodule.extractors.base import Extractor, Mrph, Phrase
+
+logger = logging.getLogger(__file__)
+
+
+class Task(Enum):
+    PAS_ANALYSIS = "pas_analysis"
+    BRIDGING = "bridging"
+    COREFERENCE = "coreference"
+
+
+TASK2EXTRACTOR = {
+    Task.PAS_ANALYSIS: PasExtractor,
+    Task.BRIDGING: BridgingExtractor,
+    Task.COREFERENCE: CoreferenceExtractor,
+}
+
+
+class CohesionExample:
+    """A single training/test example for bridging anaphora resolution."""
+
+    def __init__(self) -> None:
+        self.example_id: int = -1
+        self.doc_id: str = ""
+        self.annotations: dict[Task, Annotation] = {}
+        self.phrases: dict[Task, list[Phrase]] = {}
+        self.encoding: Optional[Encoding] = None
+
+    @property
+    def mrphs(self) -> dict[Task, list[Mrph]]:
+        return {
+            task: sum((p.children for p in phrases), [])
+            for task, phrases in self.phrases.items()
+        }
+
+    def load(
+        self,
+        document: Document,
+        tasks: list[Task],
+        extractors: dict[Task, Extractor],
+    ):
+        self.doc_id = document.doc_id
+        for task in tasks:
+            phrases = self._construct_phrases(document)
+            extractor: Extractor = extractors[task]
+            self.phrases[task] = phrases
+            self.annotations[task] = extractor.extract(
+                document, phrases
+            )  # extract gold
+
+    @staticmethod
+    def _construct_phrases(document: Document) -> list[Phrase]:
+        # construct phrases and mrphs
+        phrases = []
+        for sentence in document.sentences:
+            for anaphor in sentence.base_phrases:
+                phrase = Phrase(
+                    dtid=anaphor.global_index,
+                    surf=anaphor.text,
+                    dmids=[m.global_index for m in anaphor.morphemes],
+                    dmid=anaphor.head.global_index,
+                    children=[],
+                    is_target=False,
+                    candidates=[],
+                )
+                for morpheme in anaphor.morphemes:
+                    mrph = Mrph(
+                        dmid=morpheme.global_index,
+                        surf=morpheme.text,
+                        parent=phrase,
+                    )
+                    phrase.children.append(mrph)
+                phrases.append(phrase)
+        return phrases
+
+    def __str__(self):
+        return self.__repr__()
+
+    def __repr__(self):
+        string = ""
+        for i, phrase in enumerate(next(iter(self.phrases.values()))):
+            string += f"{i:02} {phrase.surf}"
+            string += " " * (6 - len(phrase.surf)) * 2
+            if ann := self.annotations.get(Task.PAS_ANALYSIS):
+                for case, args in ann.arguments_set[i].items():
+                    args_str = ",".join(str(arg) for arg in args)
+                    string += f"{case}:{args_str:6}  "
+            if ann := self.annotations.get(Task.BRIDGING):
+                args_str = ",".join(str(arg) for arg in ann.arguments_set[i])
+                string += f"ノ:{args_str:6}  "
+            if ann := self.annotations.get(Task.COREFERENCE):
+                args_str = ",".join(str(arg) for arg in ann.arguments_set[i])
+                string += f"＝:{args_str:6}"
+            string += "\n"
+        return string
diff --git a/src/jula/datamodule/extractors/__init__.py b/src/jula/datamodule/extractors/__init__.py
new file mode 100644
index 00000000..e729e497
--- /dev/null
+++ b/src/jula/datamodule/extractors/__init__.py
@@ -0,0 +1,17 @@
+from typing import Union
+
+from .bridging import BridgingAnnotation, BridgingExtractor
+from .coreference import CoreferenceAnnotation, CoreferenceExtractor
+from .pas import PasAnnotation, PasExtractor
+
+Annotation = Union[PasAnnotation, BridgingAnnotation, CoreferenceAnnotation]
+
+__all__ = [
+    "PasExtractor",
+    "PasAnnotation",
+    "BridgingExtractor",
+    "BridgingAnnotation",
+    "CoreferenceExtractor",
+    "CoreferenceAnnotation",
+    "Annotation",
+]
diff --git a/src/jula/datamodule/extractors/base.py b/src/jula/datamodule/extractors/base.py
new file mode 100644
index 00000000..5cecc0fe
--- /dev/null
+++ b/src/jula/datamodule/extractors/base.py
@@ -0,0 +1,71 @@
+from dataclasses import dataclass
+
+from rhoknp import BasePhrase, Document, Sentence
+from rhoknp.rel import ExophoraReferent, ExophoraReferentType
+
+
+@dataclass(frozen=True)
+class Mrph:
+    dmid: int
+    surf: str
+    parent: "Phrase"
+
+    @property
+    def is_target(self):
+        return self.parent.is_target and self.parent.dmid == self.dmid
+
+
+@dataclass(frozen=False)
+class Phrase:
+    dtid: int
+    surf: str
+    dmids: list[int]
+    dmid: int
+    children: list[Mrph]
+    is_target: bool
+    candidates: list[int]  # candidates list is always empty if is_target is False
+
+
+class Extractor:
+    def __init__(
+        self,
+        exophora_referents: list[ExophoraReferent],
+        kc: bool = False,
+    ) -> None:
+        self.kc = kc
+        self.exophora_referents = exophora_referents
+
+    def _kc_skip_sentence(self, sentence: Sentence, document: Document) -> bool:
+        # do not skip sentences not from Kyoto Corpus
+        if self.kc is False:
+            return False
+        # do not skip the first sentence
+        if document.doc_id.split("-")[-1] == "00":
+            return False
+        last_sent = document.sentences[-1] if len(document.sentences) > 0 else None
+        return sentence is not last_sent
+
+    @staticmethod
+    def _relax_exophora_referent(
+        exophora_referent: ExophoraReferent,
+    ) -> ExophoraReferent:
+        if exophora_referent.type in (
+            ExophoraReferentType.UNSPECIFIED_PERSON,
+            ExophoraReferentType.UNSPECIFIED_MATTER,
+            ExophoraReferentType.UNSPECIFIED_SITUATION,
+        ):
+            exophora_referent.index = None
+        return exophora_referent
+
+    def extract(self, document: Document, phrases: list[Phrase]):
+        raise NotImplementedError
+
+    def is_target(self, bp: BasePhrase) -> bool:
+        raise NotImplementedError
+
+    @staticmethod
+    def is_candidate(bp: BasePhrase, anaphor: BasePhrase) -> bool:
+        return bp.global_index < anaphor.global_index or (
+            bp.global_index > anaphor.global_index
+            and bp.sentence.sid == anaphor.sentence.sid
+        )
diff --git a/src/jula/datamodule/extractors/bridging.py b/src/jula/datamodule/extractors/bridging.py
new file mode 100644
index 00000000..b93230f8
--- /dev/null
+++ b/src/jula/datamodule/extractors/bridging.py
@@ -0,0 +1,105 @@
+import logging
+from dataclasses import dataclass
+
+from rhoknp import BasePhrase, Document
+from rhoknp.rel import Argument, ExophoraReferent, SpecialArgument
+from rhoknp.rel.pas import BaseArgument
+
+from .base import Extractor, Phrase
+
+logger = logging.getLogger(__file__)
+
+
+@dataclass(frozen=True)
+class BridgingAnnotation:
+    arguments_set: list[list[str]]
+
+
+class BridgingExtractor(Extractor):
+    def __init__(
+        self,
+        bar_rels: list[str],
+        exophors: list[ExophoraReferent],
+        kc: bool = False,
+    ) -> None:
+        super().__init__(exophors, kc)
+        self.rels = bar_rels
+
+    def extract(
+        self,
+        document: Document,
+        phrases: list[Phrase],
+    ) -> BridgingAnnotation:
+        bp_list = document.base_phrases
+        arguments_set: list[list[str]] = [[] for _ in bp_list]
+        for sentence in document.sentences:
+            for anaphor in sentence.base_phrases:
+                is_target_phrase: bool = (
+                    self.is_target(anaphor)
+                    and self._kc_skip_sentence(sentence, document) is False
+                )
+                phrases[anaphor.global_index].is_target = is_target_phrase
+                if is_target_phrase is False:
+                    continue
+                candidates: list[int] = [
+                    bp.global_index
+                    for bp in bp_list
+                    if self.is_candidate(bp, anaphor) is True
+                ]
+                phrases[anaphor.global_index].candidates = candidates
+                arguments: list[BaseArgument] = []
+                for rel in self.rels:
+                    arguments += anaphor.pas.get_arguments(rel, relax=False)
+                arguments_set[anaphor.global_index] = self._get_args(
+                    arguments, candidates
+                )
+
+        return BridgingAnnotation(arguments_set)
+
+    def _get_args(
+        self,
+        orig_args: list[BaseArgument],
+        candidates: list[int],
+    ) -> list[str]:
+        """Get string representations of orig_args.
+        If the return value is an empty list, do not calculate loss for this argument.
+        overt: {dmid}%C
+        case: {dmid}%N
+        zero: {dmid}%O
+        exophor: {exophor}
+        no arg: NULL
+        """
+        # filter out non-target exophors
+        args: list[BaseArgument] = []
+        for arg in orig_args:
+            if isinstance(arg, SpecialArgument):
+                arg.exophora_referent = self._relax_exophora_referent(
+                    arg.exophora_referent
+                )
+                if arg.exophora_referent in self.exophora_referents:
+                    args.append(arg)
+                elif arg.exophora_referent == ExophoraReferent("[不明]"):
+                    return []  # don't train uncertain argument
+            else:
+                args.append(arg)
+        if not args:
+            return ["[NULL]"]
+        arg_strings: list[str] = []
+        for arg in args:
+            if isinstance(arg, Argument):
+                if arg.base_phrase.global_index not in candidates:
+                    logger.debug(f"argument: {arg} is not in candidates and ignored")
+                    continue
+                string = str(arg.base_phrase.global_index)
+            else:
+                # exophora
+                string = str(arg)
+            arg_strings.append(string)
+        return arg_strings
+
+    def is_target(self, bp: BasePhrase) -> bool:
+        return self.is_bridging_target(bp)
+
+    @staticmethod
+    def is_bridging_target(bp: BasePhrase) -> bool:
+        return bp.features.get("体言") is True and bp.features.get("非用言格解析") is not True
diff --git a/src/jula/datamodule/extractors/coreference.py b/src/jula/datamodule/extractors/coreference.py
new file mode 100644
index 00000000..7af97a38
--- /dev/null
+++ b/src/jula/datamodule/extractors/coreference.py
@@ -0,0 +1,95 @@
+import logging
+from dataclasses import dataclass
+
+from rhoknp import BasePhrase, Document
+from rhoknp.rel import ExophoraReferent
+
+from .base import Extractor, Phrase
+
+logger = logging.getLogger(__file__)
+
+
+@dataclass(frozen=True)
+class CoreferenceAnnotation:
+    arguments_set: list[list[str]]
+
+
+class CoreferenceExtractor(Extractor):
+    def __init__(
+        self,
+        exophors: list[ExophoraReferent],
+        kc: bool = False,
+    ) -> None:
+        super().__init__(exophors, kc)
+
+    def extract(
+        self,
+        document: Document,
+        phrases: list[Phrase],
+    ) -> CoreferenceAnnotation:
+        bp_list = document.base_phrases
+        arguments_set: list[list[str]] = [[] for _ in bp_list]
+        for sentence in document.sentences:
+            for anaphor in sentence.base_phrases:
+                is_target_phrase: bool = (
+                    self.is_target(anaphor)
+                    and self._kc_skip_sentence(sentence, document) is False
+                )
+                phrases[anaphor.global_index].is_target = is_target_phrase
+                if is_target_phrase is False:
+                    continue
+                candidates: list[int] = [
+                    bp.global_index
+                    for bp in bp_list
+                    if self.is_candidate(bp, anaphor) is True
+                ]
+                phrases[anaphor.global_index].candidates = candidates
+                arguments_set[anaphor.global_index] = self._get_mentions(
+                    anaphor, candidates
+                )
+
+        return CoreferenceAnnotation(arguments_set)
+
+    def _get_mentions(
+        self,
+        src_mention: BasePhrase,
+        candidates: list[int],
+    ) -> list[str]:
+        if not src_mention.entities:
+            return ["[NA]"]
+
+        ment_strings: list[str] = []
+        tgt_mentions = src_mention.get_coreferents(include_nonidentical=False)
+        exophora_referents = [
+            e.exophora_referent
+            for e in src_mention.entities
+            if e.exophora_referent is not None
+        ]
+        for mention in tgt_mentions:
+            if mention.global_index not in candidates:
+                logger.debug(
+                    f"mention: {mention} in {mention.sentence.sid} is not in candidates and ignored"
+                )
+                continue
+            ment_strings.append(str(mention.global_index))
+        for exophora_referent in exophora_referents:
+            exophora_referent = self._relax_exophora_referent(
+                exophora_referent
+            )  # 不特定:人１ -> 不特定:人
+            if exophora_referent in self.exophora_referents:
+                ment_strings.append(str(exophora_referent))
+        if ment_strings:
+            return ment_strings
+        else:
+            return ["[NA]"]
+
+    def is_target(self, bp: BasePhrase) -> bool:
+        return self.is_coreference_target(bp)
+
+    @staticmethod
+    def is_coreference_target(bp: BasePhrase) -> bool:
+        return bp.features.get("体言") is True
+
+    @staticmethod
+    def is_candidate(bp: BasePhrase, anaphor: BasePhrase) -> bool:
+        return bp.global_index < anaphor.global_index
diff --git a/src/jula/datamodule/extractors/pas.py b/src/jula/datamodule/extractors/pas.py
new file mode 100644
index 00000000..aef4ed24
--- /dev/null
+++ b/src/jula/datamodule/extractors/pas.py
@@ -0,0 +1,123 @@
+import logging
+from collections import defaultdict
+from dataclasses import dataclass
+
+from rhoknp import BasePhrase, Document
+from rhoknp.rel import Argument, ArgumentType, ExophoraReferent, SpecialArgument
+from rhoknp.rel.pas import BaseArgument
+
+from .base import Extractor, Phrase
+
+logger = logging.getLogger(__file__)
+
+
+@dataclass(frozen=True)
+class PasAnnotation:
+    arguments_set: list[dict[str, list[str]]]
+
+
+class PasExtractor(Extractor):
+    def __init__(
+        self,
+        cases: list[str],
+        pas_targets: list[str],
+        exophors: list[ExophoraReferent],
+        kc: bool = False,
+    ) -> None:
+        self.pas_targets = pas_targets
+        super().__init__(exophors, kc)
+        self.cases = cases
+
+    def extract(
+        self,
+        document: Document,
+        phrases: list[Phrase],
+    ) -> PasAnnotation:
+        bp_list = document.base_phrases
+        arguments_set: list[dict[str, list[str]]] = [defaultdict(list) for _ in bp_list]
+        for sentence in document.sentences:
+            for anaphor in sentence.base_phrases:
+                is_target_phrase: bool = (
+                    self.is_target(anaphor)
+                    and self._kc_skip_sentence(sentence, document) is False
+                )
+                phrases[anaphor.global_index].is_target = is_target_phrase
+                if is_target_phrase is False:
+                    continue
+                candidates: list[int] = [
+                    bp.global_index
+                    for bp in bp_list
+                    if self.is_candidate(bp, anaphor) is True
+                ]
+                phrases[anaphor.global_index].candidates = candidates
+                # arguments = document.get_arguments(anaphor, relax=False)
+                for case in self.cases:
+                    arguments = anaphor.pas.get_arguments(case, relax=False)
+                    arguments_set[anaphor.global_index][case] = self._get_args(
+                        arguments, candidates
+                    )
+
+        return PasAnnotation(arguments_set)
+
+    def _get_args(
+        self,
+        orig_args: list[BaseArgument],
+        candidates: list[int],
+    ) -> list[str]:
+        """Get string representations of orig_args.
+        If the return value is an empty list, do not calculate loss for this argument.
+        overt: {dmid}%C
+        case: {dmid}%N
+        zero: {dmid}%O
+        exophor: {exophor}
+        no arg: NULL
+        """
+        # filter out non-target exophors
+        args: list[BaseArgument] = []
+        for arg in orig_args:
+            if isinstance(arg, SpecialArgument):
+                arg.exophora_referent = self._relax_exophora_referent(
+                    arg.exophora_referent
+                )
+                if arg.exophora_referent in self.exophora_referents:
+                    args.append(arg)
+                elif arg.exophora_referent == ExophoraReferent("[不明]"):
+                    return []  # don't train uncertain argument
+            else:
+                args.append(arg)
+        if not args:
+            return ["[NULL]"]
+        arg_strings: list[str] = []
+        for arg in args:
+            if isinstance(arg, Argument):
+                if arg.base_phrase.global_index not in candidates:
+                    logger.debug(f"argument: {arg} is not in candidates and ignored")
+                    continue
+                string = str(arg.base_phrase.global_index)
+                if arg.type == ArgumentType.CASE_EXPLICIT:
+                    string += "%C"
+                elif arg.type == ArgumentType.CASE_HIDDEN:
+                    string += "%N"
+                else:
+                    assert arg.type == ArgumentType.OMISSION
+                    string += "%O"
+            else:
+                # exophora
+                string = str(arg)
+            arg_strings.append(string)
+        return arg_strings
+
+    def is_target(self, bp: BasePhrase) -> bool:
+        return self.is_pas_target(
+            bp,
+            verbal=("pred" in self.pas_targets),
+            nominal=("noun" in self.pas_targets),
+        )
+
+    @staticmethod
+    def is_pas_target(bp: BasePhrase, verbal: bool, nominal: bool) -> bool:
+        if verbal and "用言" in bp.features:
+            return True
+        if nominal and bp.features.get("非用言格解析") is True:
+            return True
+        return False
diff --git a/src/jula/evaluators/cohesion_analysis_metric.py b/src/jula/evaluators/cohesion_analysis_metric.py
new file mode 100644
index 00000000..b68500e7
--- /dev/null
+++ b/src/jula/evaluators/cohesion_analysis_metric.py
@@ -0,0 +1,78 @@
+import numpy as np
+import torch
+from torchmetrics import Metric
+
+from jula.datamodule.datasets.word_dataset import WordDataset
+from jula.datamodule.examples import CohesionExample, Task
+from jula.evaluators.cohesion_scorer import Scorer, ScoreResult
+from jula.writer.knp import CohesionKNPWriter
+
+
+class CohesionAnalysisMetric(Metric):
+    full_state_update: bool = False
+
+    def __init__(self):
+        super().__init__()
+        # Metric state variables can either be torch.Tensor or an empty list which can be used to store torch.Tensors`.
+        # i.e. Expected metric state to be either a Tensor or a list of Tensor
+        self.add_state("example_ids", default=list())  # list[torch.Tensor]
+        self.add_state(
+            "predictions", default=list()
+        )  # list[torch.Tensor]  # [(rel, phrase)]
+
+    def update(
+        self,
+        example_ids: torch.Tensor,  # (b)
+        output: torch.Tensor,  # (b, rel, seq, seq)
+        dataset: WordDataset,
+    ) -> None:
+        assert len(output) == len(example_ids)
+        for out, eid in zip(output, example_ids):
+            self.example_ids.append(eid)
+            gold_example: CohesionExample = dataset.cohesion_examples[
+                dataset.document_ids[eid.item()]
+            ]
+            # (rel, phrase, 0 or phrase+special)
+            preds: list[list[list[float]]] = dataset.dump_prediction(
+                out.tolist(), gold_example
+            )
+            self.predictions.append(
+                torch.as_tensor(
+                    [
+                        [(np.argmax(ps).item() if ps else -1) for ps in pred]
+                        for pred in preds
+                    ],
+                    dtype=torch.long,
+                    device=example_ids.device,
+                )
+            )
+
+    def compute(self, dataset: WordDataset) -> ScoreResult:
+        knp_writer = CohesionKNPWriter(dataset)
+        assert len(self.example_ids) == len(
+            self.predictions
+        ), f"{len(self.example_ids)} vs {len(self.predictions)}"
+        predictions = {
+            eid.item(): prediction.tolist()
+            for eid, prediction in zip(self.example_ids, self.predictions)
+        }
+        documents_pred = knp_writer.write(predictions, destination=None)
+        targets2label = {
+            tuple(): "",
+            ("pred",): "pred",
+            ("noun",): "noun",
+            ("pred", "noun"): "all",
+        }
+
+        scorer = Scorer(
+            documents_pred,
+            dataset.documents,
+            target_cases=dataset.cases,
+            exophora_referents=dataset.exophora_referents,
+            coreference=(Task.COREFERENCE in dataset.cohesion_tasks),
+            bridging=(Task.BRIDGING in dataset.cohesion_tasks),
+            pas_target=targets2label[tuple(dataset.pas_targets)],
+        )
+        score_result: ScoreResult = scorer.run()
+
+        return score_result
diff --git a/src/jula/evaluators/cohesion_scorer.py b/src/jula/evaluators/cohesion_scorer.py
new file mode 100644
index 00000000..58761304
--- /dev/null
+++ b/src/jula/evaluators/cohesion_scorer.py
@@ -0,0 +1,748 @@
+import argparse
+import io
+import logging
+import sys
+from collections import OrderedDict
+from dataclasses import dataclass
+from functools import reduce
+from operator import add
+from pathlib import Path
+from typing import Any, Optional, TextIO, Union
+
+import pandas as pd
+from rhoknp import BasePhrase, Document
+from rhoknp.rel import ExophoraReferent
+from rhoknp.rel.pas import (
+    Argument,
+    ArgumentType,
+    BaseArgument,
+    Predicate,
+    SpecialArgument,
+)
+
+from jula.datamodule.extractors import (
+    BridgingExtractor,
+    CoreferenceExtractor,
+    PasExtractor,
+)
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.WARNING)
+
+
+class Scorer:
+    """A class to evaluate system output.
+
+    To evaluate system output with this class, you have to prepare gold data and system prediction data as instances of
+    :class:`kyoto_reader.Document`
+
+    Args:
+        documents_pred (list[Document]): システム予測文書集合
+        documents_gold (list[Document]): 正解文書集合
+        target_cases (list[str]): 評価の対象とする格 (kyoto_reader.ALL_CASES を参照)
+        exophora_referents (list[ExophoraReferent]): 評価の対象とする外界照応の照応先 (kyoto_reader.ALL_EXOPHORS を参照)
+        bridging (bool): 橋渡し照応の評価を行うかどうか (default: False)
+        coreference (bool): 共参照の評価を行うかどうか (default: False)
+        pas_target (str): 述語項構造解析において述語として扱う対象 ('pred': 用言, 'noun': 体言, 'all': 両方, '': 述語なし (default: pred))
+
+    Attributes:
+        cases (list[str]): 評価の対象となる格
+        doc_ids: (list[str]): 評価の対象となる文書の文書ID集合
+        did2document_pred (dict[str, Document]): 文書IDからシステム予測文書を引くための辞書
+        did2document_gold (dict[str, Document]): 文書IDから正解文書を引くための辞書
+        bridging (bool): 橋渡し照応の評価を行うかどうか
+        coreference (bool): 共参照の評価を行うかどうか
+        pas_target (str): 述語項構造解析において述語として扱う対象
+        comp_result (dict[tuple, str]): 正解と予測を比較した結果を格納するための辞書
+        sub_scorers (list[SubScorer]): 文書ごとの評価を行うオブジェクトのリスト
+        exophora_referents (list[ExophoraReferent]): 「不特定:人１」などを「不特定:人」として評価するためのマップ
+    """
+
+    DEPTYPE2ANALYSIS = OrderedDict(
+        [
+            (ArgumentType.CASE_EXPLICIT, "overt"),
+            (ArgumentType.CASE_HIDDEN, "dep"),
+            (ArgumentType.OMISSION, "zero_endophora"),
+            (ArgumentType.EXOPHORA, "zero_exophora"),
+        ]
+    )
+
+    def __init__(
+        self,
+        documents_pred: list[Document],
+        documents_gold: list[Document],
+        target_cases: list[str],
+        exophora_referents: list[ExophoraReferent],
+        bridging: bool = False,
+        coreference: bool = False,
+        pas_target: str = "pred",
+    ) -> None:
+        # long document may have been ignored
+        assert set(doc.doc_id for doc in documents_pred) <= set(
+            doc.doc_id for doc in documents_gold
+        )
+        self.cases: list[str] = target_cases if pas_target != "" else []
+        self.doc_ids: list[str] = [doc.doc_id for doc in documents_pred]
+        self.did2document_pred: dict[str, Document] = {
+            doc.doc_id: doc for doc in documents_pred
+        }
+        self.did2document_gold: dict[str, Document] = {
+            doc.doc_id: doc for doc in documents_gold
+        }
+        self.bridging: bool = bridging
+        self.coreference: bool = coreference
+        self.pas_target: str = pas_target
+
+        self.comp_result: dict[tuple, str] = {}
+        self.sub_scorers: list[SubScorer] = []
+        self.exophora_referents: list[ExophoraReferent] = exophora_referents
+
+    def run(self) -> "ScoreResult":
+        """読み込んだ正解文書集合とシステム予測文書集合に対して評価を行う
+
+        Returns:
+            ScoreResult: 評価結果のスコア
+        """
+        self.comp_result = {}
+        self.sub_scorers = []
+        all_results = []
+        for doc_id in self.doc_ids:
+            sub_scorer = SubScorer(
+                self.did2document_pred[doc_id],
+                self.did2document_gold[doc_id],
+                cases=self.cases,
+                bridging=self.bridging,
+                coreference=self.coreference,
+                exophora_referents=self.exophora_referents,
+                pas_target=self.pas_target,
+            )
+            all_results.append(sub_scorer.run())
+            self.sub_scorers.append(sub_scorer)
+            self.comp_result.update(
+                {(doc_id, *key): val for key, val in sub_scorer.comp_result.items()}
+            )
+        return reduce(add, all_results)
+
+
+class SubScorer:
+    """Scorer for single document pair.
+
+    Args:
+        document_pred (Document): システム予測文書
+        document_gold (Document): 正解文書
+        cases (list[str]): 評価の対象とする格
+        bridging (bool): 橋渡し照応の評価を行うかどうか (default: False)
+        coreference (bool): 共参照の評価を行うかどうか (default: False)
+        exophora_referents (list[ExophoraReferent]): 「不特定:人１」などを「不特定:人」として評価するためのマップ
+        pas_target (str): 述語項構造解析において述語として扱う対象
+
+    Attributes:
+        doc_id (str): 対象の文書ID
+        document_pred (Document): システム予測文書
+        document_gold (Document): 正解文書
+        cases (list[str]): 評価の対象となる格
+        pas (bool): 述語項構造の評価を行うかどうか
+        bridging (bool): 橋渡し照応の評価を行うかどうか
+        coreference (bool): 共参照の評価を行うかどうか
+        comp_result (dict[tuple, str]): 正解と予測を比較した結果を格納するための辞書
+        exophora_referents (list[ExophoraReferent]): 「不特定:人１」などを「不特定:人」として評価するためのマップ
+        predicates_pred: (list[Predicate]): システム予測文書に含まれる述語
+        bridgings_pred: (list[Predicate]): システム予測文書に含まれる橋渡し照応詞
+        mentions_pred: (list[BasePhrase]): システム予測文書に含まれるメンション
+        predicates_gold: (list[Predicate]): 正解文書に含まれる述語
+        bridgings_gold: (list[Predicate]): 正解文書に含まれる橋渡し照応詞
+        mentions_gold: (list[BasePhrase]): 正解文書に含まれるメンション
+    """
+
+    def __init__(
+        self,
+        document_pred: Document,
+        document_gold: Document,
+        cases: list[str],
+        bridging: bool,
+        coreference: bool,
+        exophora_referents: list[ExophoraReferent],
+        pas_target: str,
+    ):
+        assert document_pred.doc_id == document_gold.doc_id
+        self.doc_id: str = document_gold.doc_id
+        self.document_pred: Document = document_pred
+        self.document_gold: Document = document_gold
+        self.cases: list[str] = cases
+        self.pas: bool = pas_target != ""
+        self.bridging: bool = bridging
+        self.coreference: bool = coreference
+        self.comp_result: dict[tuple, str] = {}
+        self.exophora_referents: list[ExophoraReferent] = exophora_referents
+
+        self.predicates_pred: list[Predicate] = []
+        self.bridgings_pred: list[Predicate] = []
+        self.mentions_pred: list[BasePhrase] = []
+        for bp in document_pred.base_phrases:
+            if PasExtractor.is_pas_target(
+                bp,
+                verbal=(pas_target in ("pred", "all")),
+                nominal=(pas_target in ("noun", "all")),
+            ):
+                self.predicates_pred.append(bp.pas.predicate)
+            if self.bridging and BridgingExtractor.is_bridging_target(bp):
+                self.bridgings_pred.append(bp.pas.predicate)
+            if self.coreference and CoreferenceExtractor.is_coreference_target(bp):
+                self.mentions_pred.append(bp)
+        self.predicates_gold: list[Predicate] = []
+        self.bridgings_gold: list[Predicate] = []
+        self.mentions_gold: list[BasePhrase] = []
+        for bp in document_gold.base_phrases:
+            if PasExtractor.is_pas_target(
+                bp,
+                verbal=(pas_target in ("pred", "all")),
+                nominal=(pas_target in ("noun", "all")),
+            ):
+                self.predicates_gold.append(bp.pas.predicate)
+            if self.bridging and BridgingExtractor.is_bridging_target(bp):
+                self.bridgings_gold.append(bp.pas.predicate)
+            if self.coreference and CoreferenceExtractor.is_coreference_target(bp):
+                self.mentions_gold.append(bp)
+
+    def run(self) -> "ScoreResult":
+        """Perform evaluation for the given gold document and system prediction document.
+
+        Returns:
+            ScoreResult: 評価結果のスコア
+        """
+        self.comp_result = {}
+        measures_pas = self._evaluate_pas() if self.pas else None
+        measures_bridging = self._evaluate_bridging() if self.bridging else None
+        measure_coref = self._evaluate_coref() if self.coreference else None
+        return ScoreResult(measures_pas, measures_bridging, measure_coref)
+
+    def _evaluate_pas(self) -> pd.DataFrame:
+        """calculate predicate-argument structure analysis scores"""
+        measures = pd.DataFrame(
+            [[Measure() for _ in Scorer.DEPTYPE2ANALYSIS.values()] for _ in self.cases],
+            index=self.cases,
+            columns=Scorer.DEPTYPE2ANALYSIS.values(),
+        )
+        global_index2predicate_pred: dict[int, Predicate] = {
+            pred.base_phrase.global_index: pred for pred in self.predicates_pred
+        }
+        global_index2predicate_gold: dict[int, Predicate] = {
+            pred.base_phrase.global_index: pred for pred in self.predicates_gold
+        }
+
+        for global_index in range(len(self.document_pred.base_phrases)):
+            for case in self.cases:
+                if global_index in global_index2predicate_pred:
+                    predicate_pred = global_index2predicate_pred[global_index]
+                    args_pred = predicate_pred.pas.get_arguments(case, relax=False)
+                else:
+                    args_pred = []
+                assert len(args_pred) in (
+                    0,
+                    1,
+                )  # Our analyzer predicts one argument for one predicate
+
+                if global_index in global_index2predicate_gold:
+                    predicate_gold = global_index2predicate_gold[global_index]
+                    args_gold = predicate_gold.pas.get_arguments(case, relax=False)
+                    args_gold = self._filter_args(args_gold, predicate_gold)
+                    args_gold_relaxed = predicate_gold.pas.get_arguments(
+                        case, relax=True
+                    )
+                    if case == "ガ":
+                        args_gold_relaxed += predicate_gold.pas.get_arguments(
+                            "判ガ", relax=True
+                        )
+                else:
+                    args_gold = args_gold_relaxed = []
+
+                key = (global_index, case)
+
+                # calculate precision
+                if args_pred:
+                    arg = args_pred[0]
+                    if arg in args_gold_relaxed:
+                        # use dep_type of gold argument if possible
+                        arg_gold = args_gold_relaxed[args_gold_relaxed.index(arg)]
+                        analysis = Scorer.DEPTYPE2ANALYSIS[arg_gold.type]
+                        self.comp_result[key] = analysis
+                        measures.at[case, analysis].correct += 1
+                    else:
+                        # system出力のdep_typeはgoldのものと違うので不整合が起きるかもしれない
+                        analysis = Scorer.DEPTYPE2ANALYSIS[arg.type]
+                        self.comp_result[key] = "wrong"  # precision が下がる
+                    measures.at[case, analysis].denom_pred += 1
+
+                # calculate recall
+                # 正解が複数ある場合、そのうち一つが当てられていればそれを正解に採用
+                # いずれも当てられていなければ、relax されていない項から一つを選び正解に採用
+                if args_gold or (
+                    self.comp_result.get(key, None) in Scorer.DEPTYPE2ANALYSIS.values()
+                ):
+                    arg_gold = None
+                    for arg in args_gold_relaxed:
+                        if arg in args_pred:
+                            arg_gold = arg  # 予測されている項を優先して正解の項に採用
+                            break
+                    if arg_gold is not None:
+                        analysis = Scorer.DEPTYPE2ANALYSIS[arg_gold.type]
+                        assert self.comp_result[key] == analysis
+                    else:
+                        analysis = Scorer.DEPTYPE2ANALYSIS[args_gold[0].type]
+                        if args_pred:
+                            assert self.comp_result[key] == "wrong"
+                        else:
+                            self.comp_result[key] = "wrong"  # recall が下がる
+                    measures.at[case, analysis].denom_gold += 1
+        return measures
+
+    def _filter_args(
+        self, args: list[BaseArgument], predicate: Predicate
+    ) -> list[BaseArgument]:
+        filtered_args = []
+        for arg in args:
+            if isinstance(arg, SpecialArgument):
+                if (
+                    arg.exophora_referent not in self.exophora_referents
+                ):  # filter out non-target exophors
+                    continue
+                arg.exophora_referent.index = None  # 「不特定:人１」なども「不特定:人」として扱う
+            else:
+                assert isinstance(arg, Argument)
+                # filter out self-anaphora and cataphoras
+                if predicate.base_phrase == arg.base_phrase:
+                    continue
+                if (
+                    predicate.base_phrase.global_index < arg.base_phrase.global_index
+                    and arg.base_phrase.sentence.sid
+                    != predicate.base_phrase.sentence.sid
+                ):
+                    continue
+            filtered_args.append(arg)
+        return filtered_args
+
+    def _evaluate_bridging(self) -> pd.Series:
+        """calculate bridging anaphora resolution scores"""
+        measures: dict[str, Measure] = OrderedDict(
+            (anal, Measure()) for anal in Scorer.DEPTYPE2ANALYSIS.values()
+        )
+        global_index2anaphor_pred: dict[int, Predicate] = {
+            pred.base_phrase.global_index: pred for pred in self.bridgings_pred
+        }
+        global_index2anaphor_gold: dict[int, Predicate] = {
+            pred.base_phrase.global_index: pred for pred in self.bridgings_gold
+        }
+
+        for global_index in range(len(self.document_pred.base_phrases)):
+            if global_index in global_index2anaphor_pred:
+                anaphor_pred = global_index2anaphor_pred[global_index]
+                antecedents_pred: list[BaseArgument] = self._filter_args(
+                    anaphor_pred.pas.get_arguments("ノ", relax=False), anaphor_pred
+                )
+            else:
+                antecedents_pred = []
+            assert len(antecedents_pred) in (
+                0,
+                1,
+            )  # in bert_pas_analysis, predict one argument for one predicate
+
+            if global_index in global_index2anaphor_gold:
+                anaphor_gold: Predicate = global_index2anaphor_gold[global_index]
+                antecedents_gold: list[BaseArgument] = self._filter_args(
+                    anaphor_gold.pas.get_arguments("ノ", relax=False), anaphor_gold
+                )
+                antecedents_gold_relaxed: list[
+                    BaseArgument
+                ] = anaphor_gold.pas.get_arguments("ノ", relax=True)
+                antecedents_gold_relaxed += anaphor_gold.pas.get_arguments(
+                    "ノ？", relax=True
+                )
+                antecedents_gold_relaxed = self._filter_args(
+                    antecedents_gold_relaxed, anaphor_gold
+                )
+            else:
+                antecedents_gold = antecedents_gold_relaxed = []
+
+            key = (global_index, "ノ")
+
+            # calculate precision
+            if antecedents_pred:
+                antecedent_pred = antecedents_pred[0]
+                if antecedent_pred in antecedents_gold_relaxed:
+                    # use dep_type of gold antecedent if possible
+                    antecedent_gold = antecedents_gold_relaxed[
+                        antecedents_gold_relaxed.index(antecedent_pred)
+                    ]
+                    analysis = Scorer.DEPTYPE2ANALYSIS[antecedent_gold.type]
+                    if analysis == "overt":
+                        analysis = "dep"
+                    self.comp_result[key] = analysis
+                    measures[analysis].correct += 1
+                else:
+                    analysis = Scorer.DEPTYPE2ANALYSIS[antecedent_pred.type]
+                    if analysis == "overt":
+                        analysis = "dep"
+                    self.comp_result[key] = "wrong"
+                measures[analysis].denom_pred += 1
+
+            # calculate recall
+            if antecedents_gold or (
+                self.comp_result.get(key, None) in Scorer.DEPTYPE2ANALYSIS.values()
+            ):
+                antecedent_gold = None
+                for ant in antecedents_gold_relaxed:
+                    if ant in antecedents_pred:
+                        antecedent_gold = ant  # 予測されている先行詞を優先して正解の先行詞に採用
+                        break
+                if antecedent_gold is not None:
+                    analysis = Scorer.DEPTYPE2ANALYSIS[antecedent_gold.type]
+                    if analysis == "overt":
+                        analysis = "dep"
+                    assert self.comp_result[key] == analysis
+                else:
+                    analysis = Scorer.DEPTYPE2ANALYSIS[antecedents_gold[0].type]
+                    if analysis == "overt":
+                        analysis = "dep"
+                    if antecedents_pred:
+                        assert self.comp_result[key] == "wrong"
+                    else:
+                        self.comp_result[key] = "wrong"
+                measures[analysis].denom_gold += 1
+        return pd.Series(measures)
+
+    def _evaluate_coref(self) -> pd.Series:
+        """calculate coreference resolution scores"""
+        measure = Measure()
+        for global_index in range(len(self.document_pred.base_phrases)):
+            src_mention_pred = self.document_pred.base_phrases[global_index]
+            tgt_mentions_pred = self.filter_mentions(
+                src_mention_pred.get_coreferents(), src_mention_pred
+            )
+            exophors_pred = {
+                e.exophora_referent.text
+                for e in src_mention_pred.entities
+                if e.exophora_referent is not None
+            }
+
+            src_mention_gold = self.document_gold.base_phrases[global_index]
+            tgt_mentions_gold = self.filter_mentions(
+                src_mention_gold.get_coreferents(include_nonidentical=False),
+                src_mention_gold,
+            )
+            tgt_mentions_gold_relaxed = self.filter_mentions(
+                src_mention_gold.get_coreferents(include_nonidentical=True),
+                src_mention_gold,
+            )
+            exophors_gold = {
+                e.exophora_referent.text
+                for e in src_mention_gold.entities
+                if e.exophora_referent is not None
+            }
+            exophors_gold_relaxed = {
+                e.exophora_referent.text
+                for e in src_mention_gold.entities_all
+                if e.exophora_referent is not None
+            }
+
+            key = (global_index, "=")
+
+            # calculate precision
+            if tgt_mentions_pred or exophors_pred:
+                if (tgt_mentions_pred & tgt_mentions_gold_relaxed) or (
+                    exophors_pred & exophors_gold_relaxed
+                ):
+                    self.comp_result[key] = "correct"
+                    measure.correct += 1
+                else:
+                    self.comp_result[key] = "wrong"
+                measure.denom_pred += 1
+
+            # calculate recall
+            if (
+                tgt_mentions_gold
+                or exophors_gold
+                or (self.comp_result.get(key, None) == "correct")
+            ):
+                if (tgt_mentions_pred & tgt_mentions_gold_relaxed) or (
+                    exophors_pred & exophors_gold_relaxed
+                ):
+                    assert self.comp_result[key] == "correct"
+                else:
+                    self.comp_result[key] = "wrong"
+                measure.denom_gold += 1
+        return pd.Series([measure], index=["all"])
+
+    @staticmethod
+    def filter_mentions(
+        tgt_mentions: set[BasePhrase], src_mention: BasePhrase
+    ) -> set[BasePhrase]:
+        """filter out cataphors"""
+        return {
+            tgt_mention
+            for tgt_mention in tgt_mentions
+            if tgt_mention.global_index < src_mention.global_index
+        }
+
+
+@dataclass(frozen=True)
+class ScoreResult:
+    """A data class for storing the numerical result of an evaluation"""
+
+    measures_pas: Optional[pd.DataFrame]
+    measures_bridging: Optional[pd.Series]
+    measure_coref: Optional[pd.Series]
+
+    def to_dict(self) -> dict[str, dict[str, "Measure"]]:
+        """convert data to dictionary"""
+        df_all = pd.DataFrame(index=["all_case"])
+        if self.pas:
+            assert self.measures_pas is not None
+            df_pas: pd.DataFrame = self.measures_pas.copy()
+            df_pas["zero"] = df_pas["zero_endophora"] + df_pas["zero_exophora"]
+            df_pas["dep_zero"] = df_pas["zero"] + df_pas["dep"]
+            df_pas["all"] = df_pas["dep_zero"] + df_pas["overt"]
+            df_all = pd.concat([df_pas, df_all])
+            df_all.loc["all_case"] = df_pas.sum(axis=0)
+
+        if self.bridging:
+            assert self.measures_bridging is not None
+            df_bar = self.measures_bridging.copy()
+            df_bar["zero"] = df_bar["zero_endophora"] + df_bar["zero_exophora"]
+            df_bar["dep_zero"] = df_bar["zero"] + df_bar["dep"]
+            assert df_bar["overt"] == Measure()  # No overt in BAR
+            df_bar["all"] = df_bar["dep_zero"]
+            df_all.at["all_case", "bridging"] = df_bar["all"]
+
+        if self.coreference:
+            assert self.measure_coref is not None
+            df_all.at["all_case", "coreference"] = self.measure_coref["all"]
+
+        return {
+            k1: {k2: v2 for k2, v2 in v1.items() if pd.notnull(v2)}
+            for k1, v1 in df_all.to_dict(orient="index").items()
+        }
+
+    def export_txt(self, destination: Union[str, Path, TextIO]) -> None:
+        """Export the evaluation results in a text format.
+
+        Args:
+            destination (Union[str, Path, TextIO]): 書き出す先
+        """
+        lines = []
+        for key, ms in self.to_dict().items():
+            lines.append(
+                f"{key}格"
+                if self.measures_pas is not None and key in self.measures_pas.index
+                else key
+            )
+            for analysis, measure in ms.items():
+                lines.append(f"  {analysis}")
+                lines.append(
+                    f"    precision: {measure.precision:.4f} ({measure.correct}/{measure.denom_pred})"
+                )
+                lines.append(
+                    f"    recall   : {measure.recall:.4f} ({measure.correct}/{measure.denom_gold})"
+                )
+                lines.append(f"    F        : {measure.f1:.4f}")
+        text = "\n".join(lines) + "\n"
+
+        if isinstance(destination, str) or isinstance(destination, Path):
+            Path(destination).write_text(text)
+        elif isinstance(destination, io.TextIOBase):
+            destination.write(text)
+
+    def export_csv(self, destination: Union[str, Path, TextIO], sep: str = ",") -> None:
+        """Export the evaluation results in a csv format.
+
+        Args:
+            destination (Union[str, Path, TextIO]): 書き出す先
+            sep (str): 区切り文字 (default: ',')
+        """
+        text = ""
+        result_dict = self.to_dict()
+        text += "case" + sep
+        text += sep.join(result_dict["all_case"].keys()) + "\n"
+        for case, measures in result_dict.items():
+            text += case + sep
+            text += sep.join(f"{measure.f1:.6}" for measure in measures.values())
+            text += "\n"
+
+        if isinstance(destination, str) or isinstance(destination, Path):
+            Path(destination).write_text(text)
+        elif isinstance(destination, io.TextIOBase):
+            destination.write(text)
+
+    @property
+    def pas(self):
+        """Whether self includes the score of predicate-argument structure analysis."""
+        return self.measures_pas is not None
+
+    @property
+    def bridging(self):
+        """Whether self includes the score of bridging anaphora resolution."""
+        return self.measures_bridging is not None
+
+    @property
+    def coreference(self):
+        """Whether self includes the score of coreference resolution."""
+        return self.measure_coref is not None
+
+    def __add__(self, other: "ScoreResult") -> "ScoreResult":
+        if self.pas:
+            assert self.measures_pas is not None and other.measures_pas is not None
+            measures_pas = self.measures_pas + other.measures_pas
+        else:
+            measures_pas = None
+        if self.bridging:
+            assert (
+                self.measures_bridging is not None
+                and other.measures_bridging is not None
+            )
+            measures_bridging = self.measures_bridging + other.measures_bridging
+        else:
+            measures_bridging = None
+        if self.coreference:
+            assert self.measure_coref is not None and other.measure_coref is not None
+            measure_coref = self.measure_coref + other.measure_coref
+        else:
+            measure_coref = None
+        return ScoreResult(measures_pas, measures_bridging, measure_coref)
+
+
+@dataclass
+class Measure:
+    """A data class to calculate and represent F-measure"""
+
+    denom_pred: int = 0
+    denom_gold: int = 0
+    correct: int = 0
+
+    def __add__(self, other: "Measure"):
+        return Measure(
+            self.denom_pred + other.denom_pred,
+            self.denom_gold + other.denom_gold,
+            self.correct + other.correct,
+        )
+
+    def __eq__(self, other: Any) -> bool:
+        if not isinstance(other, Measure):
+            return False
+        return (
+            self.denom_pred == other.denom_pred
+            and self.denom_gold == other.denom_gold
+            and self.correct == other.correct
+        )
+
+    @property
+    def precision(self) -> float:
+        if self.denom_pred == 0:
+            return 0.0
+        return self.correct / self.denom_pred
+
+    @property
+    def recall(self) -> float:
+        if self.denom_gold == 0:
+            return 0.0
+        return self.correct / self.denom_gold
+
+    @property
+    def f1(self) -> float:
+        if self.denom_pred + self.denom_gold == 0:
+            return 0.0
+        return 2 * self.correct / (self.denom_pred + self.denom_gold)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--prediction-dir",
+        default=None,
+        type=str,
+        help="path to directory where system output KWDLC files exist (default: None)",
+    )
+    parser.add_argument(
+        "--gold-dir",
+        default=None,
+        type=str,
+        help="path to directory where gold KWDLC files exist (default: None)",
+    )
+    parser.add_argument(
+        "--coreference",
+        "--coref",
+        "--cr",
+        action="store_true",
+        default=False,
+        help="perform coreference resolution",
+    )
+    parser.add_argument(
+        "--bridging",
+        "--brg",
+        "--bar",
+        action="store_true",
+        default=False,
+        help="perform bridging anaphora resolution",
+    )
+    parser.add_argument(
+        "--case-string",
+        type=str,
+        default="ガ,ヲ,ニ,ガ２",
+        help='case strings separated by ","',
+    )
+    parser.add_argument(
+        "--exophors",
+        "--exo",
+        type=str,
+        default="著者,読者,不特定:人,不特定:物",
+        help='exophor strings separated by ","',
+    )
+    parser.add_argument(
+        "--read-prediction-from-pas-tag",
+        action="store_true",
+        default=False,
+        help="use <述語項構造:> tag instead of <rel > tag in prediction files",
+    )
+    parser.add_argument(
+        "--pas-target",
+        choices=["", "pred", "noun", "all"],
+        default="pred",
+        help="PAS analysis evaluation target (pred: verbal predicates, noun: nominal predicates)",
+    )
+    parser.add_argument(
+        "--result-csv",
+        default=None,
+        type=str,
+        help="path to csv file which prediction result is exported (default: None)",
+    )
+    args = parser.parse_args()
+
+    documents_pred = [
+        Document.from_knp(path.read_text())
+        for path in Path(args.prediction_dir).glob("*.knp")
+    ]
+    documents_gold = [
+        Document.from_knp(path.read_text())
+        for path in Path(args.gold_dir).glob("*.knp")
+    ]
+
+    msg = (
+        '"ノ" found in case string. If you want to perform bridging anaphora resolution, specify "--bridging" '
+        "option instead"
+    )
+    assert "ノ" not in args.case_string.split(","), msg
+    scorer = Scorer(
+        documents_pred,
+        documents_gold,
+        target_cases=args.case_string.split(","),
+        exophora_referents=[ExophoraReferent(e) for e in args.exophors.split(",")],
+        coreference=args.coreference,
+        bridging=args.bridging,
+        pas_target=args.pas_target,
+    )
+    result = scorer.run()
+    if args.result_csv:
+        result.export_csv(args.result_csv)
+    result.export_txt(sys.stdout)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/jula/evaluators/dependency_parsing_metric.py b/src/jula/evaluators/dependency_parsing_metric.py
index f466f038..6385641b 100644
--- a/src/jula/evaluators/dependency_parsing_metric.py
+++ b/src/jula/evaluators/dependency_parsing_metric.py
@@ -5,37 +5,37 @@
 from torchmetrics import Metric
 
 from jula.datamodule.datasets.word_dataset import WordDataset
-from jula.evaluators.my_conll18_ud_eval import main
+from jula.evaluators.my_conll18_ud_eval import main as conll18_ud_eval
 from jula.utils.utils import INDEX2DEPENDENCY_TYPE
 
 
 class DependencyParsingMetric(Metric):
     def __init__(self) -> None:
         super().__init__()
-        self.add_state("document_ids", default=[], dist_reduce_fx="cat")
+        self.add_state("example_ids", default=[], dist_reduce_fx="cat")
         self.add_state("preds", default=[], dist_reduce_fx="cat")
         self.add_state("type_preds", default=[], dist_reduce_fx="cat")
 
     def update(
-        self, document_ids: torch.Tensor, preds: torch.Tensor, type_preds: torch.Tensor
+        self, example_ids: torch.Tensor, preds: torch.Tensor, type_preds: torch.Tensor
     ) -> None:
-        self.document_ids.append(document_ids)
+        self.example_ids.append(example_ids)
         self.preds.append(preds)
         self.type_preds.append(type_preds)
 
     def compute(self, dataset: WordDataset) -> dict[str, Union[torch.Tensor, float]]:
-        sorted_indices = self.unique(self.document_ids)
-        document_ids, preds, type_preds = map(
+        sorted_indices = self.unique(self.example_ids)
+        example_ids, preds, type_preds = map(
             lambda x: x[sorted_indices].tolist(),
-            [self.document_ids, self.preds, self.type_preds],
+            (self.example_ids, self.preds, self.type_preds),
         )
-        documents = [dataset.documents[document_id] for document_id in document_ids]
+        documents = [dataset.documents[example_id] for example_id in example_ids]
 
-        base_phrase_based_metrics = main(
-            *self.to_base_phrase_based_conll(documents, preds, type_preds)
+        base_phrase_based_metrics = conll18_ud_eval(
+            *self._to_base_phrase_based_conll(documents, preds, type_preds)
         )
-        morpheme_based_metrics = main(
-            *self.to_morpheme_based_conll(documents, preds, type_preds)
+        morpheme_based_metrics = conll18_ud_eval(
+            *self._to_morpheme_based_conll(documents, preds, type_preds)
         )
         metrics = {
             "base_phrase_" + key: value
@@ -53,7 +53,7 @@ def unique(x, dim=None):
         inverse, perm = inverse.flip([0]), perm.flip([0])
         return inverse.new_empty(unique.size(0)).scatter_(0, inverse, perm)
 
-    def to_base_phrase_based_conll(
+    def _to_base_phrase_based_conll(
         self,
         documents: list[Document],
         preds: list[list[int]],
@@ -62,7 +62,7 @@ def to_base_phrase_based_conll(
         gold_lines, system_lines = [], []
         for document, pred, type_pred in zip(documents, preds, type_preds):
             for sentence in document.sentences:
-                begin, end, morpheme_global_index2base_phrase_index = self.prefetch(
+                begin, end, morpheme_global_index2base_phrase_index = self._prefetch(
                     sentence, len(pred)
                 )
                 for base_phrase in sentence.base_phrases:
@@ -73,7 +73,7 @@ def to_base_phrase_based_conll(
                         gold_head = 0
                         gold_deprel = "ROOT"
                     gold_lines.append(
-                        self.to_conll_line(base_phrase, gold_head, gold_deprel)
+                        self._to_conll_line(base_phrase, gold_head, gold_deprel)
                     )
 
                     p, tp = map(
@@ -84,7 +84,7 @@ def to_base_phrase_based_conll(
                         INDEX2DEPENDENCY_TYPE[tp] if system_head > 0 else "ROOT"
                     )
                     if 0 < system_head <= base_phrase.index:
-                        system_head, system_deprel = self.find_another_governor(
+                        system_head, system_deprel = self._find_another_governor(
                             base_phrase,
                             morpheme_global_index2base_phrase_index,
                             pred,
@@ -92,14 +92,14 @@ def to_base_phrase_based_conll(
                         )
 
                     system_lines.append(
-                        self.to_conll_line(base_phrase, system_head, system_deprel)
+                        self._to_conll_line(base_phrase, system_head, system_deprel)
                     )
 
                 gold_lines.append("\n")
                 system_lines.append("\n")
         return gold_lines, system_lines
 
-    def to_morpheme_based_conll(
+    def _to_morpheme_based_conll(
         self,
         documents: list[Document],
         preds: list[list[int]],
@@ -108,7 +108,7 @@ def to_morpheme_based_conll(
         gold_lines, system_lines = [], []
         for document, pred, type_pred in zip(documents, preds, type_preds):
             for sentence in document.sentences:
-                begin, end, morpheme_global_index2base_phrase_index = self.prefetch(
+                begin, end, morpheme_global_index2base_phrase_index = self._prefetch(
                     sentence, len(pred)
                 )
                 for morpheme in sentence.morphemes:
@@ -122,7 +122,7 @@ def to_morpheme_based_conll(
                         gold_head = 0
                         gold_deprel = "ROOT"
                     gold_lines.append(
-                        self.to_conll_line(morpheme, gold_head, gold_deprel)
+                        self._to_conll_line(morpheme, gold_head, gold_deprel)
                     )
 
                     p, tp = map(lambda x: x[morpheme.global_index], [pred, type_pred])
@@ -133,7 +133,7 @@ def to_morpheme_based_conll(
                         system_head = 0
                         system_deprel = "ROOT"
                     system_lines.append(
-                        self.to_conll_line(morpheme, system_head, system_deprel)
+                        self._to_conll_line(morpheme, system_head, system_deprel)
                     )
 
                 gold_lines.append("\n")
@@ -141,7 +141,7 @@ def to_morpheme_based_conll(
         return gold_lines, system_lines
 
     @staticmethod
-    def prefetch(
+    def _prefetch(
         sentence: Sentence, max_seq_len: int
     ) -> tuple[int, int, dict[int, int]]:
         morpheme_global_indices = [
@@ -157,7 +157,9 @@ def prefetch(
         return begin, end, morpheme_global_index2base_phrase_index
 
     @staticmethod
-    def to_conll_line(unit: Union[BasePhrase, Morpheme], head: int, deprel: str) -> str:
+    def _to_conll_line(
+        unit: Union[BasePhrase, Morpheme], head: int, deprel: str
+    ) -> str:
         id_ = unit.index + 1  # 0-origin -> 1-origin
         if isinstance(unit, BasePhrase):
             form = "".join(morpheme.surf for morpheme in unit.morphemes)
@@ -175,7 +177,7 @@ def to_conll_line(unit: Union[BasePhrase, Morpheme], head: int, deprel: str) ->
         )
 
     @staticmethod
-    def find_another_governor(
+    def _find_another_governor(
         base_phrase: BasePhrase,
         morpheme_global_index2base_phrase_index: dict[int, int],
         pred: list[int],
diff --git a/src/jula/evaluators/phrase_analysis_metric.py b/src/jula/evaluators/phrase_analysis_metric.py
index 63eac539..8b55298a 100644
--- a/src/jula/evaluators/phrase_analysis_metric.py
+++ b/src/jula/evaluators/phrase_analysis_metric.py
@@ -1,5 +1,5 @@
 from itertools import chain
-from typing import Union
+from typing import Literal, Union
 
 import numpy as np
 import torch
@@ -7,56 +7,64 @@
 from seqeval.scheme import IOB2
 from torchmetrics import Metric
 
-from jula.utils.utils import BASE_PHRASE_FEATURES, IGNORE_INDEX
+from jula.utils.utils import BASE_PHRASE_FEATURES, IGNORE_INDEX, WORD_FEATURES
 
 
 class PhraseAnalysisMetric(Metric):
     def __init__(self) -> None:
         super().__init__()
-        self.add_state("document_ids", default=[], dist_reduce_fx="cat")
-        self.add_state("preds", default=[], dist_reduce_fx="cat")
-        self.add_state("labels", default=[], dist_reduce_fx="cat")
+        self.add_state("example_ids", default=[], dist_reduce_fx="cat")
+        self.add_state("word_feature_predictions", default=[], dist_reduce_fx="cat")
+        self.add_state("word_features", default=[], dist_reduce_fx="cat")
+        self.add_state(
+            "base_phrase_feature_predictions", default=[], dist_reduce_fx="cat"
+        )
+        self.add_state("base_phrase_features", default=[], dist_reduce_fx="cat")
 
     def update(
-        self, document_ids: torch.Tensor, preds: torch.Tensor, labels: torch.Tensor
+        self,
+        example_ids: torch.LongTensor,
+        word_feature_predictions: torch.LongTensor,
+        word_features: torch.LongTensor,
+        base_phrase_feature_predictions: torch.LongTensor,
+        base_phrase_features: torch.LongTensor,
     ):
-        self.document_ids.append(document_ids)
-        self.preds.append(preds)
-        self.labels.append(labels)
+        self.example_ids.append(example_ids)
+        self.word_feature_predictions.append(word_feature_predictions)
+        self.word_features.append(word_features)
+        self.base_phrase_feature_predictions.append(base_phrase_feature_predictions)
+        self.base_phrase_features.append(base_phrase_features)
 
-    def compute(self) -> dict[str, Union[torch.Tensor, float]]:
-        sorted_indices = self.unique(self.document_ids)
-        # (num_base_phrase_features, batch_size, max_seq_len)
-        preds, labels = map(
-            lambda x: x[sorted_indices].permute(2, 0, 1), [self.preds, self.labels]
+    def compute(self) -> dict[str, Union[torch.LongTensor, float]]:
+        sorted_indices = self.unique(self.example_ids)
+        # (num_base_phrase_features, b, seq_len)
+        (
+            word_feature_predictions,
+            word_features,
+            base_phrase_feature_predictions,
+            base_phrase_features,
+        ) = map(
+            lambda x: x[sorted_indices].permute(2, 0, 1),
+            [
+                self.word_feature_predictions,
+                self.word_features,
+                self.base_phrase_feature_predictions,
+                self.base_phrase_features,
+            ],
         )
 
-        metrics, num_base_phrase_features = {}, 0
-        for i, (pred, label) in enumerate(zip(preds, labels)):
-            # pred/label: (batch_size, seq_len)
-            aligned_pred, aligned_label = self.align_predictions(
-                pred=pred.detach().cpu().numpy(),
-                label=label.detach().cpu().numpy(),
+        metrics = self.compute_word_feature_metrics(
+            word_feature_predictions, word_features
+        )
+        metrics.update(
+            self.compute_base_phrase_feature_metrics(
+                base_phrase_feature_predictions, base_phrase_features
             )
-            if (
-                sum(bio_tag == "B" for bio_tag in chain.from_iterable(aligned_label))
-                > 0
-            ):
-                metrics[f"{BASE_PHRASE_FEATURES[i]}_f1"] = f1_score(
-                    y_true=aligned_label,
-                    y_pred=aligned_pred,
-                    mode="strict",
-                    scheme=IOB2,
-                    zero_division=0,
-                )
-                num_base_phrase_features += 1
-        else:
-            sum_f1 = sum(value for key, value in metrics.items() if key.endswith("_f1"))
-            metrics["phrase_analysis_f1"] = sum_f1 / num_base_phrase_features
+        )
         return metrics
 
     @staticmethod
-    def unique(x, dim=None):
+    def unique(x: torch.Tensor, dim: int = None):
         unique, inverse = torch.unique(x, sorted=True, return_inverse=True, dim=dim)
         perm = torch.arange(inverse.size(0), dtype=inverse.dtype, device=inverse.device)
         inverse, perm = inverse.flip([0]), perm.flip([0])
@@ -64,14 +72,98 @@ def unique(x, dim=None):
 
     @staticmethod
     def align_predictions(
-        pred: np.ndarray, label: np.ndarray
+        prediction: np.ndarray, label: np.ndarray, io_tag: Literal["I", "O"]
     ) -> tuple[list[list[str]], list[list[str]]]:
-        batch_size, seq_len = pred.shape
-        aligned_pred: list[list[str]] = [[] for _ in range(batch_size)]
-        aligned_label: list[list[str]] = [[] for _ in range(batch_size)]
-        for i in range(batch_size):
+        b, seq_len = label.shape
+        aligned_prediction: list[list[str]] = [[] for _ in range(b)]
+        aligned_label: list[list[str]] = [[] for _ in range(b)]
+        for i in range(b):
             for j in range(seq_len):
-                if label[i, j] != float(IGNORE_INDEX):
-                    aligned_pred[i].append("B" if pred[i][j] == 1.0 else "I")
-                    aligned_label[i].append("B" if label[i][j] == 1.0 else "I")
-        return aligned_pred, aligned_label
+                # 評価対象外の label / prediction は含めない
+                if label[i, j] != IGNORE_INDEX:
+                    aligned_prediction[i].append(
+                        "B" if prediction[i][j] == 1 else io_tag
+                    )
+                    aligned_label[i].append("B" if label[i][j] == 1 else io_tag)
+        return aligned_prediction, aligned_label
+
+    def compute_word_feature_metrics(
+        self,
+        word_feature_predictions: torch.LongTensor,
+        word_features: torch.LongTensor,
+    ) -> dict[str, float]:
+        word_feature_metrics = {}
+        aligned_predictions, aligned_labels = [], []
+        for i, (word_feature_prediction, word_feature_label) in enumerate(
+            zip(word_feature_predictions, word_features)
+        ):
+            # prediction / label: (b, seq_len)
+            aligned_prediction, aligned_label = self.align_predictions(
+                prediction=word_feature_prediction.detach().cpu().numpy(),
+                label=word_feature_label.detach().cpu().numpy(),
+                io_tag="I",
+            )
+            aligned_predictions += aligned_prediction
+            aligned_labels += aligned_label
+            word_feature_metrics[f"{WORD_FEATURES[i]}_f1"] = f1_score(
+                y_true=aligned_label,
+                y_pred=aligned_prediction,
+                mode="strict",
+                zero_division=0,
+                scheme=IOB2,
+            )
+        else:
+            sum_f1 = sum(value for key, value in word_feature_metrics.items())
+            word_feature_metrics["macro_word_feature_f1"] = sum_f1 / len(
+                word_feature_metrics
+            )
+            word_feature_metrics["micro_word_feature_f1"] = f1_score(
+                y_true=aligned_labels,
+                y_pred=aligned_predictions,
+                mode="strict",
+                zero_division=0,
+                scheme=IOB2,
+            )
+        return word_feature_metrics
+
+    def compute_base_phrase_feature_metrics(
+        self,
+        base_phrase_feature_predictions: torch.LongTensor,
+        base_phrase_features: torch.LongTensor,
+    ) -> dict[str, float]:
+        base_phrase_feature_metrics = {}
+        aligned_predictions, aligned_labels = [], []
+        for i, (base_phrase_feature_prediction, base_phrase_feature_label) in enumerate(
+            zip(base_phrase_feature_predictions, base_phrase_features)
+        ):
+            # pred/label: (batch_size, seq_len)
+            aligned_prediction, aligned_label = self.align_predictions(
+                prediction=base_phrase_feature_prediction.detach().cpu().numpy(),
+                label=base_phrase_feature_label.detach().cpu().numpy(),
+                io_tag="O",
+            )
+            t = sum(bio_tag == "B" for bio_tag in chain.from_iterable(aligned_label))
+            # 正解ラベルがない基本句素性は評価対象外
+            if t > 0:
+                aligned_predictions += aligned_prediction
+                aligned_labels += aligned_label
+                base_phrase_feature_metrics[f"{BASE_PHRASE_FEATURES[i]}_f1"] = f1_score(
+                    y_true=aligned_label,
+                    y_pred=aligned_prediction,
+                    mode="strict",
+                    zero_division=0,
+                    scheme=IOB2,
+                )
+        else:
+            sum_f1 = sum(value for key, value in base_phrase_feature_metrics.items())
+            base_phrase_feature_metrics["macro_base_phrase_feature_f1"] = sum_f1 / len(
+                base_phrase_feature_metrics
+            )
+            base_phrase_feature_metrics["micro_base_phrase_feature_f1"] = f1_score(
+                y_true=aligned_labels,
+                y_pred=aligned_predictions,
+                mode="strict",
+                zero_division=0,
+                scheme=IOB2,
+            )
+        return base_phrase_feature_metrics
diff --git a/src/jula/models/char_module.py b/src/jula/models/char_module.py
index ed4028de..67a8f5a3 100644
--- a/src/jula/models/char_module.py
+++ b/src/jula/models/char_module.py
@@ -56,7 +56,7 @@ def training_step(self, batch: Any, batch_idx: int) -> dict[str, Any]:
             "train/word_segmenter_loss",
             outputs["word_segmenter_outputs"]["loss"],
             on_step=True,
-            on_epoch=True,
+            on_epoch=False,
         )
         return outputs["word_segmenter_outputs"]["loss"]
 
@@ -133,13 +133,51 @@ def predict_step(
     ) -> Any:
         outputs: dict[str, dict[str, torch.Tensor]] = self(**batch)
         return {
-            "document_ids": batch["document_id"],
+            "example_ids": batch["example_ids"],
             "word_segmenter_logits": outputs["word_segmenter_outputs"]["logits"],
             "word_segmenter_labels": batch["seg_labels"],
         }
 
     def configure_optimizers(self):
+        # Split weights in two groups, one with weight decay and the other not.
+        no_decay = ("bias", "LayerNorm.weight")
+        optimizer_grouped_parameters = [
+            {
+                "params": [
+                    p
+                    for n, p in self.named_parameters()
+                    if not any(nd in n for nd in no_decay) and p.requires_grad
+                ],
+                "weight_decay": self.hparams.optimizer.weight_decay,
+                "name": "decay",
+            },
+            {
+                "params": [
+                    p
+                    for n, p in self.named_parameters()
+                    if any(nd in n for nd in no_decay) and p.requires_grad
+                ],
+                "weight_decay": 0.0,
+                "name": "no_decay",
+            },
+        ]
         optimizer = hydra.utils.instantiate(
-            self.hparams.optimizer, params=self.parameters(), _convert_="partial"
+            self.hparams.optimizer,
+            params=optimizer_grouped_parameters,
+            _convert_="partial",
         )
-        return [optimizer], []
+
+        warmup_steps = self.hparams.warmup_steps
+        lr_scheduler = hydra.utils.instantiate(
+            self.hparams.scheduler,
+            optimizer=optimizer,
+            num_warmup_steps=warmup_steps,
+        )
+        return {
+            "optimizer": optimizer,
+            "lr_scheduler": {
+                "scheduler": lr_scheduler,
+                "interval": "step",
+                "frequency": 1,
+            },
+        }
diff --git a/src/jula/models/models/cohesion_analyzer.py b/src/jula/models/models/cohesion_analyzer.py
new file mode 100644
index 00000000..7994af63
--- /dev/null
+++ b/src/jula/models/models/cohesion_analyzer.py
@@ -0,0 +1,57 @@
+import torch
+import torch.nn as nn
+from transformers import PretrainedConfig
+
+
+class CohesionAnalyzer(nn.Module):
+    def __init__(
+        self, pretrained_model_config: PretrainedConfig, num_rels: int
+    ) -> None:
+        super().__init__()
+
+        self.dropout = nn.Dropout(0.0)
+
+        self.num_rels = num_rels
+        mid_hidden_size = pretrained_model_config.hidden_size
+
+        self.l_src = nn.Linear(
+            pretrained_model_config.hidden_size, mid_hidden_size * self.num_rels
+        )
+        self.l_tgt = nn.Linear(
+            pretrained_model_config.hidden_size, mid_hidden_size * self.num_rels
+        )
+        self.out = nn.Linear(mid_hidden_size, 1, bias=False)
+
+    def forward(
+        self,
+        pooled_outputs: torch.Tensor,  # (b, seq, hid)
+    ) -> tuple[torch.Tensor, torch.Tensor]:  # (), (b, seq, seq)
+        batch_size, sequence_len, _ = pooled_outputs.size()
+
+        h_src = self.l_src(self.dropout(pooled_outputs))  # (b, seq, rel*hid)
+        h_tgt = self.l_tgt(self.dropout(pooled_outputs))  # (b, seq, rel*hid)
+        h_src = h_src.view(
+            batch_size, sequence_len, self.num_rels, -1
+        )  # (b, seq, rel, hid)
+        h_tgt = h_tgt.view(
+            batch_size, sequence_len, self.num_rels, -1
+        )  # (b, seq, rel, hid)
+        h = torch.tanh(
+            self.dropout(h_src.unsqueeze(2) + h_tgt.unsqueeze(1))
+        )  # (b, seq, seq, rel, hid)
+        # -> (b, seq, seq, rel, 1) -> (b, seq, seq, rel) -> (b, rel, seq, seq)
+        output = self.out(h).squeeze(-1).permute(0, 3, 1, 2).contiguous()
+
+        return output
+
+
+def cohesion_cross_entropy_loss(
+    output: torch.Tensor,  # (b, rel, seq, seq)
+    target: torch.Tensor,  # (b, rel, seq, seq)
+    mask: torch.Tensor,  # (b, rel, seq, seq)
+) -> torch.Tensor:  # ()
+    eps = 1e-6
+    output += (~mask).float() * -1024.0
+    log_softmax = torch.log_softmax(output, dim=3)  # (b, rel, seq, seq)
+    # reduce using masked mean (target ⊆ mask)
+    return torch.sum(-log_softmax * target).div(torch.sum(target) + eps)
diff --git a/src/jula/models/models/dependency_parser.py b/src/jula/models/models/dependency_parser.py
index 4b96e674..b799786d 100644
--- a/src/jula/models/models/dependency_parser.py
+++ b/src/jula/models/models/dependency_parser.py
@@ -33,7 +33,7 @@ def __init__(self, pretrained_model_config: PretrainedConfig) -> None:
 
     def forward(
         self, pooled_outputs: torch.Tensor, dependencies: Optional[torch.Tensor] = None
-    ):  # (batch_size, max_seq_len, max_seq_len)
+    ):  # (batch_size, max_seq_len, hidden_size)
         h_i = self.W_a(pooled_outputs)
         h_j = self.U_a(pooled_outputs)
         dependency_logits = self.v_a(
diff --git a/src/jula/models/models/phrase_analyzer.py b/src/jula/models/models/phrase_analyzer.py
index cf1457ed..59d812ac 100644
--- a/src/jula/models/models/phrase_analyzer.py
+++ b/src/jula/models/models/phrase_analyzer.py
@@ -3,28 +3,39 @@
 import torch
 import torch.nn as nn
 import torch.nn.functional as F
-from omegaconf import DictConfig
 from transformers import PretrainedConfig
 
-from jula.utils.utils import BASE_PHRASE_FEATURES, IGNORE_INDEX
+from jula.utils.utils import BASE_PHRASE_FEATURES, IGNORE_INDEX, WORD_FEATURES
 
 
 class PhraseAnalyzer(nn.Module):
-    def __init__(
-        self, hparams: DictConfig, pretrained_model_config: PretrainedConfig
-    ) -> None:
+    def __init__(self, pretrained_model_config: PretrainedConfig) -> None:
         super().__init__()
-        self.hparams = hparams
 
         hidden_size = pretrained_model_config.hidden_size
-        self.head = nn.Sequential(
+        hidden_dropout_prob = pretrained_model_config.hidden_dropout_prob
+        self.word_feature_head = nn.Sequential(
             OrderedDict(
                 [
                     ("dense", nn.Linear(hidden_size, hidden_size)),
                     ("hidden_act", nn.GELU()),
                     (
                         "dropout",
-                        nn.Dropout(pretrained_model_config.hidden_dropout_prob),
+                        nn.Dropout(hidden_dropout_prob),
+                    ),
+                    ("fc", nn.Linear(hidden_size, len(WORD_FEATURES))),
+                    ("act", nn.Sigmoid()),
+                ]
+            )
+        )
+        self.base_phrase_feature_head = nn.Sequential(
+            OrderedDict(
+                [
+                    ("dense", nn.Linear(hidden_size, hidden_size)),
+                    ("hidden_act", nn.GELU()),
+                    (
+                        "dropout",
+                        nn.Dropout(hidden_dropout_prob),
                     ),
                     ("fc", nn.Linear(hidden_size, len(BASE_PHRASE_FEATURES))),
                     ("act", nn.Sigmoid()),
@@ -36,22 +47,42 @@ def forward(
         self, pooled_outputs: torch.Tensor, batch: dict[str, torch.Tensor]
     ) -> dict[str, torch.Tensor]:
         outputs: dict[str, torch.Tensor] = {}
-        # (batch_size, seq_len, num_base_phrase_features)
-        phrase_analysis_logits = self.head(pooled_outputs)
-        outputs["phrase_analysis_logits"] = phrase_analysis_logits
+        # (b, seq_len, num_word_features)
+        word_feature_logits = self.word_feature_head(pooled_outputs)
+        outputs["word_feature_logits"] = word_feature_logits
+        # (b, seq_len, num_base_phrase_features)
+        base_phrase_feature_logits = self.base_phrase_feature_head(pooled_outputs)
+        outputs["base_phrase_feature_logits"] = base_phrase_feature_logits
+        if "word_features" in batch:
+            word_feature_mask = batch["word_features"].ne(IGNORE_INDEX)
+            input_ = word_feature_logits * word_feature_mask
+            target = batch["word_features"] * word_feature_mask
+            word_feature_loss = self.compute_loss(
+                input_, target.float(), torch.sum(word_feature_mask[:, :, 0], dim=1)
+            )
+            outputs["word_feature_loss"] = word_feature_loss
         if "base_phrase_features" in batch:
-            input_, target = map(
-                lambda x: torch.where(
-                    batch["base_phrase_features"] != float(IGNORE_INDEX),
-                    x,
-                    torch.zeros_like(x),
-                ),
-                [phrase_analysis_logits, batch["base_phrase_features"]],
+            base_phrase_feature_mask = batch["base_phrase_features"].ne(IGNORE_INDEX)
+            input_ = base_phrase_feature_logits * base_phrase_feature_mask
+            target = batch["base_phrase_features"] * base_phrase_feature_mask
+            base_phrase_feature_loss = self.compute_loss(
+                input_,
+                target.float(),
+                torch.sum(base_phrase_feature_mask[:, :, 0], dim=1),
             )
-            losses = F.binary_cross_entropy(
-                input=input_, target=target, reduction="none"
-            )  # (batch_size, seq_len, num_base_phrase_features)
-            losses = torch.sum(torch.sum(losses, dim=1), dim=1)  # (batch_size, )
-            phrase_analysis_loss = torch.mean(losses / batch["num_base_phrases"])
-            outputs["phrase_analysis_loss"] = phrase_analysis_loss
+            outputs["base_phrase_feature_loss"] = base_phrase_feature_loss
         return outputs
+
+    @staticmethod
+    def compute_loss(
+        input_: torch.FloatTensor,
+        target: torch.FloatTensor,
+        num_units: torch.LongTensor,
+    ) -> torch.Tensor:
+        # (b, seq_len, num_features)
+        losses = F.binary_cross_entropy(input=input_, target=target, reduction="none")
+        # 各featureのlossの和
+        losses = torch.sum(torch.sum(losses, dim=1), dim=1)  # (b, )
+        # binary_cross_entropyはIGNORE_INDEXを渡せない
+        loss = torch.mean(losses / num_units)
+        return loss
diff --git a/src/jula/models/models/relation_analyzer.py b/src/jula/models/models/relation_analyzer.py
index dc931bc1..4cac2a87 100644
--- a/src/jula/models/models/relation_analyzer.py
+++ b/src/jula/models/models/relation_analyzer.py
@@ -6,6 +6,10 @@
 from omegaconf import DictConfig
 from transformers import PretrainedConfig
 
+from jula.models.models.cohesion_analyzer import (
+    CohesionAnalyzer,
+    cohesion_cross_entropy_loss,
+)
 from jula.models.models.dependency_parser import DependencyParser
 from jula.utils.utils import IGNORE_INDEX
 
@@ -20,6 +24,12 @@ def __init__(
         self.dependency_parser: DependencyParser = DependencyParser(
             pretrained_model_config=pretrained_model_config
         )
+        self.cohesion_analyzer: CohesionAnalyzer = CohesionAnalyzer(
+            pretrained_model_config=pretrained_model_config,
+            num_rels=int("pas_analysis" in hparams.cohesion_tasks) * len(hparams.cases)
+            + int("coreference" in hparams.cohesion_tasks)
+            + int("bridging" in hparams.cohesion_tasks),
+        )
 
     def forward(
         self,
@@ -57,4 +67,18 @@ def forward(
                 ignore_index=IGNORE_INDEX,
             )
             output.update({"dependency_type_loss": dependency_type_loss})
+
+        cohesion_logits = self.cohesion_analyzer(pooled_outputs)  # (b, rel, seq, seq)
+        output.update(
+            {
+                "cohesion_logits": cohesion_logits,
+            }
+        )
+        if "cohesion_target" in batch and "cohesion_mask" in batch:
+            cohesion_loss = cohesion_cross_entropy_loss(
+                cohesion_logits,
+                batch["cohesion_target"],
+                batch["cohesion_mask"],
+            )
+            output.update({"cohesion_loss": cohesion_loss})
         return output
diff --git a/src/jula/models/models/word_encoder.py b/src/jula/models/models/word_encoder.py
index d30e7751..37352074 100644
--- a/src/jula/models/models/word_encoder.py
+++ b/src/jula/models/models/word_encoder.py
@@ -17,7 +17,7 @@ def __init__(self, hparams: DictConfig, tokenizer: PreTrainedTokenizerBase) -> N
 
     def forward(
         self, batch: dict[str, torch.Tensor], pooling_strategy: PoolingStrategy
-    ) -> dict[str, torch.Tensor]:
+    ) -> torch.Tensor:
         outputs = self.pretrained_model(
             input_ids=batch["input_ids"],
             attention_mask=batch["attention_mask"],
diff --git a/src/jula/models/typo_module.py b/src/jula/models/typo_module.py
index 2ec519ad..ddc03071 100644
--- a/src/jula/models/typo_module.py
+++ b/src/jula/models/typo_module.py
@@ -88,7 +88,45 @@ def predict_step(
         return predict_output
 
     def configure_optimizers(self):
+        # Split weights in two groups, one with weight decay and the other not.
+        no_decay = ("bias", "LayerNorm.weight")
+        optimizer_grouped_parameters = [
+            {
+                "params": [
+                    p
+                    for n, p in self.named_parameters()
+                    if not any(nd in n for nd in no_decay) and p.requires_grad
+                ],
+                "weight_decay": self.hparams.optimizer.weight_decay,
+                "name": "decay",
+            },
+            {
+                "params": [
+                    p
+                    for n, p in self.named_parameters()
+                    if any(nd in n for nd in no_decay) and p.requires_grad
+                ],
+                "weight_decay": 0.0,
+                "name": "no_decay",
+            },
+        ]
         optimizer = hydra.utils.instantiate(
-            self.hparams.optimizer, params=self.parameters(), _convert_="partial"
+            self.hparams.optimizer,
+            params=optimizer_grouped_parameters,
+            _convert_="partial",
         )
-        return [optimizer], []
+
+        warmup_steps = self.hparams.warmup_steps
+        lr_scheduler = hydra.utils.instantiate(
+            self.hparams.scheduler,
+            optimizer=optimizer,
+            num_warmup_steps=warmup_steps,
+        )
+        return {
+            "optimizer": optimizer,
+            "lr_scheduler": {
+                "scheduler": lr_scheduler,
+                "interval": "step",
+                "frequency": 1,
+            },
+        }
diff --git a/src/jula/models/word_module.py b/src/jula/models/word_module.py
index 0edb1d1c..099dd151 100644
--- a/src/jula/models/word_module.py
+++ b/src/jula/models/word_module.py
@@ -1,3 +1,4 @@
+from collections import defaultdict
 from statistics import mean
 from typing import Any, Optional
 
@@ -7,6 +8,7 @@
 from pytorch_lightning.core.lightning import LightningModule
 from transformers import AutoTokenizer, PreTrainedTokenizerBase
 
+from jula.evaluators.cohesion_analysis_metric import CohesionAnalysisMetric
 from jula.evaluators.dependency_parsing_metric import DependencyParsingMetric
 from jula.evaluators.phrase_analysis_metric import PhraseAnalysisMetric
 from jula.evaluators.word_analyzer import WordAnalysisMetric
@@ -46,7 +48,6 @@ def __init__(self, hparams: DictConfig) -> None:
         }
 
         self.phrase_analyzer: PhraseAnalyzer = PhraseAnalyzer(
-            hparams=hparams,
             pretrained_model_config=self.word_encoder.pretrained_model.config,
         )
         self.valid_phrase_analysis_metrics: dict[str, PhraseAnalysisMetric] = {
@@ -66,6 +67,12 @@ def __init__(self, hparams: DictConfig) -> None:
         self.test_dependency_parsing_metrics: dict[str, DependencyParsingMetric] = {
             corpus: DependencyParsingMetric() for corpus in self.test_corpora
         }
+        self.valid_cohesion_analysis_metrics: dict[str, CohesionAnalysisMetric] = {
+            corpus: CohesionAnalysisMetric() for corpus in self.valid_corpora
+        }
+        self.test_cohesion_analysis_metrics: dict[str, CohesionAnalysisMetric] = {
+            corpus: CohesionAnalysisMetric() for corpus in self.test_corpora
+        }
 
     def forward(self, inference=False, **batch) -> dict[str, dict[str, torch.Tensor]]:
         # (batch_size, seq_len, hidden_size)
@@ -88,16 +95,23 @@ def training_step(self, batch: Any, batch_idx: int) -> torch.Tensor:
             "train/word_analysis_loss",
             word_analysis_loss,
             on_step=True,
-            on_epoch=True,
+            on_epoch=False,
+        )
+        word_feature_loss = outputs["phrase_analyzer_outputs"]["word_feature_loss"]
+        self.log(
+            "train/word_feature_loss",
+            word_feature_loss,
+            on_step=True,
+            on_epoch=False,
         )
-        phrase_analysis_loss = outputs["phrase_analyzer_outputs"][
-            "phrase_analysis_loss"
+        base_phrase_feature_loss = outputs["phrase_analyzer_outputs"][
+            "base_phrase_feature_loss"
         ]
         self.log(
-            "train/phrase_analysis_loss",
-            phrase_analysis_loss,
+            "train/base_phrase_feature_loss",
+            base_phrase_feature_loss,
             on_step=True,
-            on_epoch=True,
+            on_epoch=False,
         )
         dependency_loss = outputs["relation_analyzer_outputs"]["dependency_loss"]
         self.log("train/dependency_loss", dependency_loss, on_step=True, on_epoch=True)
@@ -108,13 +122,22 @@ def training_step(self, batch: Any, batch_idx: int) -> torch.Tensor:
             "train/dependency_type_loss",
             dependency_type_loss,
             on_step=True,
-            on_epoch=True,
+            on_epoch=False,
+        )
+        cohesion_loss = outputs["relation_analyzer_outputs"]["cohesion_loss"]
+        self.log(
+            "train/cohesion_loss",
+            cohesion_loss,
+            on_step=True,
+            on_epoch=False,
         )
         return (
             word_analysis_loss
-            + phrase_analysis_loss
+            + word_feature_loss
+            + base_phrase_feature_loss
             + dependency_loss
             + dependency_type_loss
+            + cohesion_loss
         )
 
     def validation_step(
@@ -147,22 +170,32 @@ def validation_step(
         )
 
         phrase_analysis_metric_args = {
-            "document_ids": batch["document_id"],
-            "preds": torch.where(
-                outputs["phrase_analyzer_outputs"]["phrase_analysis_logits"] >= 0.5,
-                1.0,
-                0.0,
-            ),
-            "labels": batch["base_phrase_features"],
+            "example_ids": batch["example_ids"],
+            "word_feature_predictions": outputs["phrase_analyzer_outputs"][
+                "word_feature_logits"
+            ]
+            .ge(0.5)
+            .long(),
+            "word_features": batch["word_features"],
+            "base_phrase_feature_predictions": outputs["phrase_analyzer_outputs"][
+                "base_phrase_feature_logits"
+            ]
+            .ge(0.5)
+            .long(),
+            "base_phrase_features": batch["base_phrase_features"],
         }
         self.valid_phrase_analysis_metrics[corpus].update(**phrase_analysis_metric_args)
         self.log(
-            "valid/phrase_analysis_loss",
-            outputs["phrase_analyzer_outputs"]["phrase_analysis_loss"],
+            "valid/word_feature_loss",
+            outputs["phrase_analyzer_outputs"]["word_feature_loss"],
+        )
+        self.log(
+            "valid/base_phrase_feature_loss",
+            outputs["phrase_analyzer_outputs"]["base_phrase_feature_loss"],
         )
 
         dependency_parsing_metric_args = {
-            "document_ids": batch["document_id"],
+            "example_ids": batch["example_ids"],
             "preds": torch.argmax(
                 outputs["relation_analyzer_outputs"]["dependency_logits"], dim=2
             ),
@@ -178,42 +211,67 @@ def validation_step(
             outputs["relation_analyzer_outputs"]["dependency_loss"],
         )
 
+        cohesion_analysis_metric_args = {
+            "example_ids": batch["example_ids"],
+            "output": outputs["relation_analyzer_outputs"]["cohesion_logits"],
+            "dataset": self.trainer.val_dataloaders[dataloader_idx or 0].dataset,
+        }
+        self.valid_cohesion_analysis_metrics[corpus].update(
+            **cohesion_analysis_metric_args
+        )
+        self.log(
+            "valid/cohesion_loss",
+            outputs["relation_analyzer_outputs"]["cohesion_loss"],
+        )
+
     def validation_epoch_end(self, validation_step_outputs) -> None:
-        f1s: list[float] = []
-        word_analysis_f1 = 0.0
+        f1_scores: dict[str, float] = defaultdict(float)
+
         for corpus, metric in self.valid_word_analysis_metrics.items():
             for name, value in metric.compute().items():
                 if name == "word_analysis_f1":
-                    word_analysis_f1 += value
+                    f1_scores["word_analysis_f1"] += value / len(
+                        self.valid_word_analysis_metrics
+                    )
                 self.log(f"valid_{corpus}/{name}", value)
                 metric.reset()
         self.log(
             "valid/word_analysis_f1",
-            word_analysis_f1 / len(self.valid_word_analysis_metrics),
+            f1_scores["word_analysis_f1"],
         )
-        f1s.append(word_analysis_f1 / len(self.valid_word_analysis_metrics))
 
-        phrase_analysis_f1 = 0.0
+        keys = {
+            "macro_word_feature_f1",
+            "micro_word_feature_f1",
+            "macro_base_phrase_feature_f1",
+            "micro_base_phrase_feature_f1",
+        }
         for corpus, metric in self.valid_phrase_analysis_metrics.items():
             for name, value in metric.compute().items():
-                if name == "phrase_analysis_f1":
-                    phrase_analysis_f1 += value
+                if name in keys:
+                    f1_scores[name] += value / len(self.valid_phrase_analysis_metrics)
                 self.log(f"valid_{corpus}/{name}", value)
             metric.reset()
-        self.log(
-            "valid/phrase_analysis_f1",
-            phrase_analysis_f1 / len(self.valid_phrase_analysis_metrics),
-        )
-        f1s.append(phrase_analysis_f1 / len(self.valid_phrase_analysis_metrics))
+        for key in sorted(keys):
+            self.log(f"valid/{key}", f1_scores[key])
 
-        self.log("valid/f1", mean(f1s))
+        self.log("valid/f1", mean(f1_scores.values()))
 
-        for corpus, metric in self.valid_dependency_parsing_metrics.items():
-            dataset = self.trainer.datamodule.valid_datasets[corpus]
+        for idx, corpus in enumerate(self.valid_corpora):
+            dataset = self.trainer.val_dataloaders[idx].dataset
+            metric = self.valid_dependency_parsing_metrics[corpus]
             for name, value in metric.compute(dataset).items():
                 self.log(f"valid_{corpus}/{name}", value)
             metric.reset()
 
+        for idx, corpus in enumerate(self.valid_corpora):
+            dataset = self.trainer.val_dataloaders[idx].dataset
+            metric = self.valid_cohesion_analysis_metrics[corpus]
+            for rel, val in metric.compute(dataset).to_dict().items():
+                for met, sub_val in val.items():
+                    self.log(f"valid_{corpus}/{met}_{rel}", sub_val.f1)
+            metric.reset()
+
     def test_step(
         self, batch: Any, batch_idx: int, dataloader_idx: Optional[int] = None
     ) -> None:
@@ -242,23 +300,34 @@ def test_step(
             "test/word_analysis_loss",
             outputs["word_analyzer_outputs"]["loss"],
         )
+
         phrase_analysis_metric_args = {
-            "document_ids": batch["document_id"],
-            "preds": torch.where(
-                outputs["phrase_analyzer_outputs"]["phrase_analysis_logits"] >= 0.5,
-                1.0,
-                0.0,
-            ),
-            "labels": batch["base_phrase_features"],
+            "example_ids": batch["example_ids"],
+            "word_feature_predictions": outputs["phrase_analyzer_outputs"][
+                "word_feature_logits"
+            ]
+            .ge(0.5)
+            .long(),
+            "word_features": batch["word_features"],
+            "base_phrase_feature_predictions": outputs["phrase_analyzer_outputs"][
+                "base_phrase_feature_logits"
+            ]
+            .ge(0.5)
+            .long(),
+            "base_phrase_features": batch["base_phrase_features"],
         }
         self.test_phrase_analysis_metrics[corpus].update(**phrase_analysis_metric_args)
         self.log(
-            "test/phrase_analysis_loss",
-            outputs["phrase_analyzer_outputs"]["phrase_analysis_loss"],
+            "test/word_feature_loss",
+            outputs["phrase_analyzer_outputs"]["word_feature_loss"],
+        )
+        self.log(
+            "test/base_phrase_feature_loss",
+            outputs["phrase_analyzer_outputs"]["base_phrase_feature_loss"],
         )
 
         dependency_parsing_metric_args = {
-            "document_ids": batch["document_id"],
+            "example_ids": batch["example_ids"],
             "preds": torch.argmax(
                 outputs["relation_analyzer_outputs"]["dependency_logits"], dim=2
             ),
@@ -274,48 +343,71 @@ def test_step(
             outputs["relation_analyzer_outputs"]["dependency_loss"],
         )
 
+        cohesion_analysis_metric_args = {
+            "example_ids": batch["example_ids"],
+            "output": outputs["relation_analyzer_outputs"]["cohesion_logits"],
+            "dataset": self.trainer.test_dataloaders[dataloader_idx or 0].dataset,
+        }
+        self.test_cohesion_analysis_metrics[corpus].update(
+            **cohesion_analysis_metric_args
+        )
+        self.log(
+            "test/cohesion_loss",
+            outputs["relation_analyzer_outputs"]["cohesion_loss"],
+        )
+
     def test_epoch_end(self, test_step_outputs) -> None:
-        f1s: list[float] = []
-        word_analysis_f1 = 0.0
+        f1_scores: dict[str, float] = defaultdict(float)
+
         for corpus, metric in self.test_word_analysis_metrics.items():
             for name, value in metric.compute().items():
                 if name == "word_analysis_f1":
-                    word_analysis_f1 += value
+                    f1_scores[name] += value / len(self.test_word_analysis_metrics)
                 self.log(f"test_{corpus}/{name}", value)
                 metric.reset()
         self.log(
             "test/word_analysis_f1",
-            word_analysis_f1 / len(self.test_word_analysis_metrics),
+            f1_scores["word_analysis_f1"],
         )
-        f1s.append(word_analysis_f1 / len(self.test_word_analysis_metrics))
 
-        phrase_analysis_f1 = 0.0
+        keys = {
+            "macro_word_feature_f1",
+            "micro_word_feature_f1",
+            "macro_base_phrase_feature_f1",
+            "micro_base_phrase_feature_f1",
+        }
         for corpus, metric in self.test_phrase_analysis_metrics.items():
             for name, value in metric.compute().items():
-                if name == "phrase_analysis_f1":
-                    phrase_analysis_f1 += value
-                self.log(f"test_{corpus}/{name}", value)
+                if name in keys:
+                    f1_scores[name] += value / len(self.test_phrase_analysis_metrics)
+                self.log(f"valid_{corpus}/{name}", value)
             metric.reset()
-        self.log(
-            "test/phrase_analysis_f1",
-            phrase_analysis_f1 / len(self.test_phrase_analysis_metrics),
-        )
-        f1s.append(phrase_analysis_f1 / len(self.test_phrase_analysis_metrics))
+        for key in sorted(keys):
+            self.log(f"test/{key}", f1_scores[key])
 
-        self.log("test/f1", mean(f1s))
+        self.log("test/f1", mean(f1_scores.values()))
 
-        for corpus, metric in self.test_dependency_parsing_metrics.items():
-            dataset = self.trainer.datamodule.test_datasets[corpus]
+        for idx, corpus in enumerate(self.test_corpora):
+            dataset = self.trainer.test_dataloaders[idx].dataset
+            metric = self.test_dependency_parsing_metrics[corpus]
             for name, value in metric.compute(dataset).items():
                 self.log(f"test_{corpus}/{name}", value)
             metric.reset()
 
+        for idx, corpus in enumerate(self.test_corpora):
+            dataset = self.trainer.test_dataloaders[idx].dataset
+            metric = self.test_cohesion_analysis_metrics[corpus]
+            for rel, val in metric.compute(dataset).to_dict().items():
+                for met, sub_val in val.items():
+                    self.log(f"test_{corpus}/{met}_{rel}", sub_val.f1)
+            metric.reset()
+
     def predict_step(
         self, batch: Any, batch_idx: int, dataloader_idx: Optional[int] = None
     ) -> Any:
         outputs: dict[str, torch.Tensor] = self(inference=True, **batch)
         return {
-            "document_ids": batch["document_id"],
+            "example_ids": batch["example_ids"],
             "word_analysis_pos_logits": outputs["word_analyzer_outputs"]["pos_logits"],
             "word_analysis_subpos_logits": outputs["word_analyzer_outputs"][
                 "subpos_logits"
@@ -326,10 +418,12 @@ def predict_step(
             "word_analysis_conjform_logits": outputs["word_analyzer_outputs"][
                 "conjform_logits"
             ],
-            "phrase_analysis_logits": outputs["phrase_analyzer_outputs"][
-                "phrase_analysis_logits"
+            "word_feature_logits": outputs["phrase_analyzer_outputs"][
+                "word_feature_logits"
+            ],
+            "base_phrase_feature_logits": outputs["phrase_analyzer_outputs"][
+                "base_phrase_feature_logits"
             ],
-            # "base_phrase_features": batch["base_phrase_features"],
             "dependency_logits": outputs["relation_analyzer_outputs"][
                 "dependency_logits"
             ],
@@ -339,7 +433,45 @@ def predict_step(
         }
 
     def configure_optimizers(self):
+        # Split weights in two groups, one with weight decay and the other not.
+        no_decay = ("bias", "LayerNorm.weight")
+        optimizer_grouped_parameters = [
+            {
+                "params": [
+                    p
+                    for n, p in self.named_parameters()
+                    if not any(nd in n for nd in no_decay) and p.requires_grad
+                ],
+                "weight_decay": self.hparams.optimizer.weight_decay,
+                "name": "decay",
+            },
+            {
+                "params": [
+                    p
+                    for n, p in self.named_parameters()
+                    if any(nd in n for nd in no_decay) and p.requires_grad
+                ],
+                "weight_decay": 0.0,
+                "name": "no_decay",
+            },
+        ]
         optimizer = hydra.utils.instantiate(
-            self.hparams.optimizer, params=self.parameters(), _convert_="partial"
+            self.hparams.optimizer,
+            params=optimizer_grouped_parameters,
+            _convert_="partial",
+        )
+
+        warmup_steps = self.hparams.warmup_steps
+        lr_scheduler = hydra.utils.instantiate(
+            self.hparams.scheduler,
+            optimizer=optimizer,
+            num_warmup_steps=warmup_steps,
         )
-        return [optimizer], []
+        return {
+            "optimizer": optimizer,
+            "lr_scheduler": {
+                "scheduler": lr_scheduler,
+                "interval": "step",
+                "frequency": 1,
+            },
+        }
diff --git a/src/jula/writer/__init__.py b/src/jula/writer/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/jula/writer/knp.py b/src/jula/writer/knp.py
new file mode 100644
index 00000000..937d51fc
--- /dev/null
+++ b/src/jula/writer/knp.py
@@ -0,0 +1,192 @@
+import io
+import logging
+from pathlib import Path
+from typing import TextIO, Union
+
+from rhoknp import BasePhrase, Document
+from rhoknp.rel import ExophoraReferent
+from rhoknp.rel.pas import Argument, BaseArgument, Pas, SpecialArgument
+from rhoknp.units.utils import Rel
+
+from jula.datamodule.datasets.word_dataset import WordDataset
+from jula.datamodule.examples import Task
+
+logger = logging.getLogger(__file__)
+
+
+class CohesionKNPWriter:
+    """A class to write the system output in a KNP format.
+
+    Args:
+        dataset (WordDataset): 解析対象のデータセット
+    """
+
+    def __init__(self, dataset: WordDataset) -> None:
+        self.cases: list[str] = dataset.cases
+        self.tasks: list[Task] = dataset.cohesion_tasks
+        self.relations: list[str] = (
+            self.cases * (Task.PAS_ANALYSIS in self.tasks)
+            + ["ノ"] * (Task.BRIDGING in self.tasks)
+            + ["="] * (Task.COREFERENCE in self.tasks)
+        )
+        self.exophora_referents: list[ExophoraReferent] = dataset.exophora_referents
+        self.specials: list[str] = dataset.special_tokens
+        self.documents: list[Document] = [
+            Document.from_knp(doc.to_knp()) for doc in dataset.documents
+        ]
+        self.kc: bool = False
+
+    def write(
+        self,
+        predictions: dict[int, list[list[int]]],
+        destination: Union[Path, TextIO, None] = None,
+        skip_untagged: bool = True,
+        add_pas_tag: bool = True,
+    ) -> list[Document]:
+        """Write final predictions to files.
+
+        Args:
+            predictions (dict[int, list[list[int]]]): example_idをkeyとする基本句単位モデル出力
+            destination (Union[Path, TextIO, None]): 解析済み文書の出力先 (default: None)
+            skip_untagged (bool): 解析に失敗した文書を出力しないかどうか (default: True)
+            add_pas_tag (bool): 解析結果に<述語項構造 >タグを付与するかどうか (default: True)
+        Returns:
+            list[Document]: 解析済み文書
+        """
+
+        if isinstance(destination, Path):
+            logger.info(f"Writing predictions to: {destination}")
+            destination.mkdir(exist_ok=True)
+        elif not (destination is None or isinstance(destination, io.TextIOBase)):
+            logger.warning("invalid output destination")
+
+        did2prediction: dict[str, list] = {
+            self.documents[eid].doc_id: pred for eid, pred in predictions.items()
+        }
+        documents = []
+        for document in self.documents:
+            did = document.doc_id
+            if prediction := did2prediction.get(did):  # (phrase, rel)
+                for base_phrase, pred in zip(document.base_phrases, prediction):
+                    base_phrase.rels = self._to_rels(pred, document.base_phrases)
+            else:
+                if skip_untagged is True:
+                    continue
+                for base_phrase in document.base_phrases:
+                    base_phrase.rels = []
+            document.reparse_rel()
+            documents.append(document)
+
+        for document in documents:
+            if destination is None:
+                continue
+            output_knp_lines = document.to_knp().splitlines()
+            if add_pas_tag is True:
+                output_knp_lines = self._add_pas_tag(output_knp_lines, document)
+            output_string = "\n".join(output_knp_lines) + "\n"
+            if isinstance(destination, Path):
+                destination.joinpath(f"{document.doc_id}.knp").write_text(output_string)
+            elif isinstance(destination, io.TextIOBase):
+                destination.write(output_string)
+
+        return documents
+
+    def _to_rels(
+        self,
+        prediction: list[int],  # (rel)
+        bp_list: list[BasePhrase],
+    ) -> list[Rel]:
+        rels: list[Rel] = []
+        assert len(self.relations) == len(prediction)
+        for relation, pred in zip(self.relations, prediction):
+            if pred < 0:
+                continue  # non-target phrase
+            if 0 <= pred < len(bp_list):
+                # normal
+                prediction_bp: BasePhrase = bp_list[pred]
+                rels.append(
+                    Rel(
+                        type=relation,
+                        target=prediction_bp.head.text,
+                        sid=prediction_bp.sentence.sid,
+                        base_phrase_index=prediction_bp.index,
+                        mode=None,
+                    )
+                )
+            elif 0 <= pred - len(bp_list) < len(self.specials):
+                # special
+                special_arg = self.specials[pred - len(bp_list)]
+                if special_arg in [
+                    str(e) for e in self.exophora_referents
+                ]:  # exclude [NULL] and [NA]
+                    rels.append(
+                        Rel(
+                            type=relation,
+                            target=special_arg,
+                            sid=None,
+                            base_phrase_index=None,
+                            mode=None,
+                        )
+                    )
+            else:
+                raise ValueError(f"invalid pred index: {pred}")
+
+        return rels
+
+    def _add_pas_tag(
+        self,
+        knp_lines: list[str],
+        document: Document,
+    ) -> list[str]:
+        dtid2pas = {
+            pas.predicate.base_phrase.global_index: pas for pas in document.pas_list()
+        }
+        dtid = 0
+        output_knp_lines = []
+        for line in knp_lines:
+            if not line.startswith("+ "):
+                output_knp_lines.append(line)
+                continue
+            if dtid in dtid2pas:
+                pas_string = self._pas_string(dtid2pas[dtid], "dummy:dummy", document)
+                output_knp_lines.append(line + pas_string)
+            else:
+                output_knp_lines.append(line)
+
+            dtid += 1
+
+        return output_knp_lines
+
+    def _pas_string(
+        self,
+        pas: Pas,
+        cfid: str,
+        document: Document,
+    ) -> str:
+        sid2index: dict[str, int] = {
+            sent.sid: i for i, sent in enumerate(document.sentences)
+        }
+        case_elements = []
+        for case in self.cases + ["ノ"] * (Task.BRIDGING in self.tasks):
+            items = ["-"] * 6
+            items[0] = case
+            args = pas.arguments[case]
+            if args:
+                arg: BaseArgument = args[0]
+                items[1] = str(arg.type)  # フラグ (C/N/O/D/E/U)
+                items[2] = str(arg)  # 見出し
+                if isinstance(arg, Argument):
+                    items[3] = str(
+                        sid2index[pas.sid] - sid2index[arg.base_phrase.sentence.sid]
+                    )  # N文前
+                    items[4] = str(arg.base_phrase.index)  # tag id
+                    items[5] = str(list(arg.base_phrase.entities)[0].eid)  # Entity ID
+                else:
+                    assert isinstance(arg, SpecialArgument)
+                    items[3] = str(-1)
+                    items[4] = str(-1)
+                    items[5] = str(arg.eid)  # Entity ID
+            else:
+                items[1] = "U"
+            case_elements.append("/".join(items))
+        return f"<述語項構造:{cfid}:{';'.join(case_elements)}>"


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 60f5fdee..17d14d47 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,7 +10,7 @@ jobs:
     - name: Checkout repository
       uses: actions/checkout@v3
     - name: Set up Python 3.9
-      uses: actions/setup-python@v2.1.4
+      uses: actions/setup-python@v3
       with:
         python-version: 3.9
     - name: Install Poetry
@@ -28,9 +28,10 @@ jobs:
       run: |
         poetry run pytest --cov=./ --cov-report=xml
     - name: Upload coverage to Codecov
-      uses: codecov/codecov-action@v2
+      uses: codecov/codecov-action@v3
       with:
         token: ${{ secrets.CODECOV_TOKEN }}
         files: ./coverage.xml
+        root_dir: true
         name: codecov-umbrella
         verbose: true
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 00000000..a7a05fa4
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,10 @@
+from pathlib import Path
+
+import pytest
+
+here = Path(__file__).parent
+
+
+@pytest.fixture()
+def fixture_data_dir():
+    return here / "data"
diff --git a/tests/data/expected/cohesion_score.json b/tests/data/expected/cohesion_score.json
new file mode 100644
index 00000000..0f5cea2a
--- /dev/null
+++ b/tests/data/expected/cohesion_score.json
@@ -0,0 +1,123 @@
+{
+  "ガ": {
+    "overt": {
+      "denom_pred": 0,
+      "denom_gold": 0,
+      "correct": 0
+    },
+    "dep": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "zero_endophora": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "zero_exophora": {
+      "denom_pred": 0,
+      "denom_gold": 0,
+      "correct": 0
+    },
+    "zero": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "dep_zero": {
+      "denom_pred": 2,
+      "denom_gold": 2,
+      "correct": 2
+    },
+    "all": {
+      "denom_pred": 2,
+      "denom_gold": 2,
+      "correct": 2
+    }
+  },
+  "ヲ": {
+    "overt": {
+      "denom_pred": 0,
+      "denom_gold": 1,
+      "correct": 0
+    },
+    "dep": {
+      "denom_pred": 0,
+      "denom_gold": 0,
+      "correct": 0
+    },
+    "zero_endophora": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "zero_exophora": {
+      "denom_pred": 0,
+      "denom_gold": 0,
+      "correct": 0
+    },
+    "zero": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "dep_zero": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "all": {
+      "denom_pred": 1,
+      "denom_gold": 2,
+      "correct": 1
+    }
+  },
+  "all_case": {
+    "overt": {
+      "denom_pred": 0,
+      "denom_gold": 1,
+      "correct": 0
+    },
+    "dep": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "zero_endophora": {
+      "denom_pred": 2,
+      "denom_gold": 2,
+      "correct": 2
+    },
+    "zero_exophora": {
+      "denom_pred": 0,
+      "denom_gold": 0,
+      "correct": 0
+    },
+    "zero": {
+      "denom_pred": 2,
+      "denom_gold": 2,
+      "correct": 2
+    },
+    "dep_zero": {
+      "denom_pred": 3,
+      "denom_gold": 3,
+      "correct": 3
+    },
+    "all": {
+      "denom_pred": 3,
+      "denom_gold": 4,
+      "correct": 3
+    },
+    "bridging": {
+      "denom_pred": 1,
+      "denom_gold": 1,
+      "correct": 1
+    },
+    "coreference": {
+      "denom_pred": 2,
+      "denom_gold": 1,
+      "correct": 0
+    }
+  }
+}
diff --git a/tests/data/expected/example/0.json b/tests/data/expected/example/0.json
new file mode 100644
index 00000000..77e6ce85
--- /dev/null
+++ b/tests/data/expected/example/0.json
@@ -0,0 +1,298 @@
+{
+  "mrphs": [
+    {
+      "surf": "エンド",
+      "dmid": 0,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [1, 3, 4, 5, 7, 8, 9, 11],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ユーザー",
+      "dmid": 1,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 3, 4, 5, 7, 8, 9, 11],
+      "ment_candidates": [0]
+    },
+    {
+      "surf": "が",
+      "dmid": 2,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "関心",
+      "dmid": 3,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["1"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 4, 5, 7, 8, 9, 11],
+      "ment_candidates": [0, 1]
+    },
+    {
+      "surf": "有る",
+      "dmid": 4,
+      "arguments": {"ガ": ["3"], "ヲ": ["[NULL]"], "ニ": ["5"], "ガ２": ["1"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 5, 7, 8, 9, 11],
+      "ment_candidates": []
+    },
+    {
+      "surf": "病気",
+      "dmid": 5,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 7, 8, 9, 11],
+      "ment_candidates": [0, 1, 3, 4]
+    },
+    {
+      "surf": "に",
+      "dmid": 6,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "対して",
+      "dmid": 7,
+      "arguments": {"ガ": ["[NULL]"], "ヲ": ["[NULL]"], "ニ": ["[NULL]"], "ガ２": ["[NULL]"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 8, 9, 11],
+      "ment_candidates": []
+    },
+    {
+      "surf": "得意な",
+      "dmid": 8,
+      "arguments": {"ガ": ["5"], "ヲ": ["[NULL]"], "ニ": ["[NULL]"], "ガ２": ["9"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 9, 11],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ドクター",
+      "dmid": 9,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 11],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8]
+    },
+    {
+      "surf": "を",
+      "dmid": 10,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "探して",
+      "dmid": 11,
+      "arguments": {"ガ": ["著者"], "ヲ": ["9"], "ニ": ["[NULL]"], "ガ２": ["[NULL]"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9],
+      "ment_candidates": []
+    },
+    {
+      "surf": "い",
+      "dmid": 12,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ます",
+      "dmid": 13,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "。",
+      "dmid": 14,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "そこ",
+      "dmid": 15,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 17, 19, 21, 22, 24, 26],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11]
+    },
+    {
+      "surf": "で",
+      "dmid": 16,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "全国",
+      "dmid": 17,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 19, 21, 22, 24, 26],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15]
+    },
+    {
+      "surf": "の",
+      "dmid": 18,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ドクター",
+      "dmid": 19,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 21, 22, 24, 26],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17]
+    },
+    {
+      "surf": "を",
+      "dmid": 20,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "簡単に",
+      "dmid": 21,
+      "arguments": {"ガ": ["[NULL]"], "ヲ": ["[NULL]"], "ニ": ["[NULL]"], "ガ２": ["[NULL]"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 22, 24, 26],
+      "ment_candidates": []
+    },
+    {
+      "surf": "紹介",
+      "dmid": 22,
+      "arguments": {"ガ": ["不特定:人", "読者"], "ヲ": ["19"], "ニ": ["読者", "不特定:人"], "ガ２": ["[NULL]"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 24, 26],
+      "ment_candidates": []
+    },
+    {
+      "surf": "できる",
+      "dmid": 23,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "コーナー",
+      "dmid": 24,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 26],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22]
+    },
+    {
+      "surf": "を",
+      "dmid": 25,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "設け",
+      "dmid": 26,
+      "arguments": {"ガ": ["著者"], "ヲ": ["24"], "ニ": ["不特定:物"], "ガ２": ["[NULL]"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ました",
+      "dmid": 27,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "。",
+      "dmid": 28,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ドクター",
+      "dmid": 29,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["19"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 31, 33, 35, 36, 38],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26]
+    },
+    {
+      "surf": "の",
+      "dmid": 30,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "皆様",
+      "dmid": 31,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["[NULL]"], "=": ["19", "29"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29, 33, 35, 36, 38],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29]
+    },
+    {
+      "surf": "ご",
+      "dmid": 32,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "協力",
+      "dmid": 33,
+      "arguments": {"ガ": ["31"], "ヲ": ["[NULL]"], "ニ": ["著者", "24"], "ガ２": ["[NULL]"], "ノ": ["[NULL]"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29, 31, 35, 36, 38],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29, 31]
+    },
+    {
+      "surf": "の",
+      "dmid": 34,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "程",
+      "dmid": 35,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": ["33"], "=": ["[NA]"]},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29, 31, 33, 36, 38],
+      "ment_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29, 31, 33]
+    },
+    {
+      "surf": "よろしく",
+      "dmid": 36,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "お",
+      "dmid": 37,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "願い",
+      "dmid": 38,
+      "arguments": {"ガ": ["著者"], "ヲ": ["35"], "ニ": ["[NULL]"], "ガ２": ["[NULL]"], "ノ": [], "=": []},
+      "arg_candidates": [0, 1, 3, 4, 5, 7, 8, 9, 11, 15, 17, 19, 21, 22, 24, 26, 29, 31, 33, 35, 36],
+      "ment_candidates": []
+    },
+    {
+      "surf": "申し上げ",
+      "dmid": 39,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "ます",
+      "dmid": 40,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    },
+    {
+      "surf": "。",
+      "dmid": 41,
+      "arguments": {"ガ": [], "ヲ": [], "ニ": [], "ガ２": [], "ノ": [], "=": []},
+      "arg_candidates": [],
+      "ment_candidates": []
+    }
+  ]
+}
diff --git a/tests/data/gold/0.knp b/tests/data/gold/0.knp
new file mode 100644
index 00000000..be3dafa6
--- /dev/null
+++ b/tests/data/gold/0.knp
@@ -0,0 +1,28 @@
+# S-ID:0-1 KNP:5.0-2ad4f6d DATE:2020/12/19 SCORE:60.00000
+* 2D <解析済><文頭><人名><ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><正規化代表表記:太郎/たろう><主辞代表表記:太郎/たろう>
++ 2D <解析済><文頭><人名><ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><名詞項候補><先行詞候補><SM-人><SM-主体><正規化代表表記:太郎/たろう><主辞代表表記:太郎/たろう><解析格:ガ>
+太郎 たろう 太郎 名詞 6 人名 5 * 0 * 0 "人名:日本:名:45:0.00106 疑似代表表記 代表表記:太郎/たろう" <人名:日本:名:45:0.00106><疑似代表表記><代表表記:太郎/たろう><正規化代表表記:太郎/たろう><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><固有キー><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+は は は 助詞 9 副助詞 2 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* 2D <解析済><BGH:パン/ぱん><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:パン/ぱん><主辞代表表記:パン/ぱん>
++ 2D <BGH:パン/ぱん><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:パン/ぱん><主辞代表表記:パン/ぱん><解析格:ヲ><標準用言代表表記:パン/ぱん>
+パン ぱん パン 名詞 6 普通名詞 1 * 0 * 0 "代表表記:パン/ぱん ドメイン:料理・食事 カテゴリ:人工物-食べ物" <代表表記:パン/ぱん><ドメイン:料理・食事><カテゴリ:人工物-食べ物><正規化代表表記:パン/ぱん><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:買う/かう><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:買う/かう><主辞代表表記:買う/かう>
++ -1D <rel type="ガ" target="太郎" sid="0-1" id="0"/><rel type="ヲ" target="パン" sid="0-1" id="1"/><BGH:買う/かう><解析済><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:買う/かう><主辞代表表記:買う/かう><用言代表表記:買う/かう><格フレーム決定><節-区切><節-主辞><主題格:一人称優位><格関係0:ガ:太郎><格関係1:ヲ:パン><標準用言代表表記:買う/かう>
+買った かった 買う 動詞 2 * 0 子音動詞ワ行 12 タ形 10 "代表表記:買う/かう ドメイン:家庭・暮らし;ビジネス 反義:動詞:売る/うる" <代表表記:買う/かう><ドメイン:家庭・暮らし;ビジネス><反義:動詞:売る/うる><正規化代表表記:買う/かう><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
+EOS
+# S-ID:0-2 KNP:5.0-2ad4f6d DATE:2020/12/19 SCORE:10.00000
+* 1D <解析済><SM-主体><SM-人><BGH:彼/かれ><文頭><助詞><連体修飾><体言><一文字漢字><係:ノ格><区切:0-4><正規化代表表記:彼/かれ><主辞代表表記:彼/かれ>
++ 1D <rel type="=" target="太郎" sid="0-1" id="0"/><SM-主体><SM-人><BGH:彼/かれ><解析済><文頭><助詞><連体修飾><体言><一文字漢字><係:ノ格><区切:0-4><名詞項候補><先行詞候補><人称代名詞><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:彼/かれ><主辞代表表記:彼/かれ>
+彼 かれ 彼 名詞 6 普通名詞 1 * 0 * 0 "代表表記:彼/かれ カテゴリ:人 漢字読み:訓" <代表表記:彼/かれ><カテゴリ:人><漢字読み:訓><正規化代表表記:彼/かれ><判定詞化名詞:0-0><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* 2D <解析済><SM-主体><SM-人><SM-場所><BGH:家/いえ><SM-組織><デ><助詞><体言><一文字漢字><係:デ格><区切:0-0><格要素><連用要素><正規化代表表記:家/いえ><主辞代表表記:家/いえ>
++ 2D <rel type="ノ" target="彼" sid="0-2" id="0"/><SM-主体><SM-人><SM-場所><BGH:家/いえ><解析済><SM-組織><デ><助詞><体言><一文字漢字><係:デ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:家/いえ><主辞代表表記:家/いえ><解析格:デ><標準用言代表表記:家/いえ>
+家 いえ 家 名詞 6 普通名詞 1 * 0 * 0 "代表表記:家/いえ ドメイン:家庭・暮らし カテゴリ:組織・団体;場所-施設 漢字読み:訓" <代表表記:家/いえ><ドメイン:家庭・暮らし><カテゴリ:組織・団体;場所-施設><漢字読み:訓><正規化代表表記:家/いえ><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+で で で 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:食べる/たべる><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:食べる/たべる><主辞代表表記:食べる/たべる>
++ -1D <rel type="ガ" target="太郎" sid="0-1" id="0"/><rel type="ヲ" target="パン" sid="0-1" id="1"/><BGH:食べる/たべる><解析済><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:食べる/たべる><主辞代表表記:食べる/たべる><用言代表表記:食べる/たべる><節-区切><節-主辞><主題格:一人称優位><格関係1:デ:家><標準用言代表表記:食べる/たべる>
+食べた たべた 食べる 動詞 2 * 0 母音動詞 1 タ形 10 "代表表記:食べる/たべる ドメイン:料理・食事" <代表表記:食べる/たべる><ドメイン:料理・食事><正規化代表表記:食べる/たべる><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
+EOS
diff --git a/tests/data/knp/w201106-0000060050.knp b/tests/data/knp/w201106-0000060050.knp
new file mode 100644
index 00000000..8eb5b712
--- /dev/null
+++ b/tests/data/knp/w201106-0000060050.knp
@@ -0,0 +1,82 @@
+# S-ID:w201106-0000060050-1 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-44.94406 MOD:2017/10/15 MEMO:
+* 2D <解析済><BGH:トス/とす><文頭><サ変><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:トス/とす><主辞代表表記:トス/とす>
++ 1D <文節内><係:文節内><文頭><体言><名詞項候補><先行詞候補>
+コイン こいん コイン 名詞 6 普通名詞 1 * 0 * 0 NIL <記英数カ><カタカナ><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><固有キー><用言表記先頭><用言表記末尾><用言意味表記末尾>
++ 3D <rel type="ガ" target="不特定:人"/><rel type="ヲ" target="コイン" sid="w201106-0000060050-1" id="0"/><BGH:トス/とす><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><サ変><名詞項候補><先行詞候補><非用言格解析:動><強サ変><照応ヒント:係><態:未定><正規化代表表記:トス/とす><主辞代表表記:トス/とす>
+トス とす トス 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:トス/とす カテゴリ:抽象物 ドメイン:スポーツ" <代表表記:トス/とす><カテゴリ:抽象物><ドメイン:スポーツ><正規化代表表記:トス/とす><記英数カ><カタカナ><名詞相当語><サ変><自立><複合←><内容語><タグ単位始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* 2D <解析済><BGH:回/かい><カウンタ:回><回数><数量><体言><係:無格><修飾><区切:0-0><格要素><連用要素><正規化代表表記:３/さん+回/かい><主辞代表表記:３/さん+回/かい>
++ 3D <BGH:回/かい><解析済><カウンタ:回><回数><数量><体言><係:無格><修飾><区切:0-0><格要素><連用要素><省略解析なし><正規化代表表記:３/さん+回/かい><主辞代表表記:３/さん+回/かい>
+３ さん ３ 名詞 6 数詞 7 * 0 * 0 "代表表記:３/さん カテゴリ:数量" <代表表記:３/さん><カテゴリ:数量><正規化代表表記:３/さん><記英数カ><数字><名詞相当語><自立><内容語><タグ単位始><文節始>
+回 かい 回 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:回/かい 準内容語" <代表表記:回/かい><準内容語><正規化代表表記:回/かい><カウンタ><漢字><かな漢字><付属><文節主辞>
+* -1D <解析済><BGH:行う/おこなう><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:行う/おこなう><主辞代表表記:行う/おこなう>
++ -1D <rel type="ガ" target="不特定:人"/><rel type="ガ" mode="？" target="読者"/><rel type="ガ" mode="？" target="著者"/><rel type="ヲ" target="トス" sid="w201106-0000060050-1" id="1"/><BGH:行う/おこなう><解析済><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:行う/おこなう><主辞代表表記:行う/おこなう><用言代表表記:行う/おこなう><節-区切><節-主辞><時制:非過去><主題格:一人称優位>
+行う おこなう 行う 動詞 2 * 0 子音動詞ワ行 12 基本形 2 "代表表記:行う/おこなう" <代表表記:行う/おこなう><正規化代表表記:行う/おこなう><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
+# S-ID:w201106-0000060050-2 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-64.95916 MOD:2013/04/13
+* 1D <解析済><文頭><ガ><助詞><体言><一文字漢字><係:ガ格><区切:0-0><格要素><連用要素><正規化代表表記:表/おもて><主辞代表表記:表/おもて>
++ 1D <rel type="ノ" target="コイン" sid="w201106-0000060050-1" id="0"/><解析済><文頭><ガ><助詞><体言><一文字漢字><係:ガ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:表/おもて><主辞代表表記:表/おもて>
+表 おもて 表 名詞 6 普通名詞 1 * 0 * 0 "代表表記:表/おもて 漢字読み:訓 カテゴリ:場所-機能" <代表表記:表/おもて><漢字読み:訓><カテゴリ:場所-機能><正規化代表表記:表/おもて><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+が が が 助詞 9 格助詞 1 * 0 * 0 "代表表記:が/が" <代表表記:が/が><正規化代表表記:が/が><かな漢字><ひらがな><付属>
+* 2D <解析済><BGH:出る/でる><補文ト><時制:過去><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:出る/でる><主辞代表表記:出る/でる>
++ 2D <rel type="ガ" target="表" sid="w201106-0000060050-2" id="0"/><rel type="外の関係" target="数" sid="w201106-0000060050-2" id="2"/><BGH:出る/でる><解析済><補文ト><時制:過去><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:出る/でる><主辞代表表記:出る/でる><用言代表表記:出る/でる><節-区切:連体修飾><節-主辞>
+出た でた 出る 動詞 2 * 0 母音動詞 1 タ形 10 "代表表記:出る/でる 補文ト 自他動詞:他:出す/だす 反義:動詞:入る/はいる" <代表表記:出る/でる><補文ト><自他動詞:他:出す/だす><反義:動詞:入る/はいる><正規化代表表記:出る/でる><移動動詞><かな漢字><活用語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+* 5D <解析済><BGH:数/かず><読点><助詞><体言><修飾><外の関係><一文字漢字><係:未格><区切:0-4><格要素><連用要素><正規化代表表記:数/かず><主辞代表表記:数/かず>
++ 5D <rel type="ノ" target="出た" sid="w201106-0000060050-2" id="1"/><BGH:数/かず><解析済><読点><助詞><体言><修飾><外の関係><一文字漢字><係:未格><区切:0-4><格要素><連用要素><名詞項候補><正規化代表表記:数/かず><主辞代表表記:数/かず>
+数 かず 数 名詞 6 普通名詞 1 * 0 * 0 "代表表記:数/かず 漢字読み:訓 カテゴリ:数量" <代表表記:数/かず><漢字読み:訓><カテゴリ:数量><正規化代表表記:数/かず><漢字><かな漢字><数字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+だけ だけ だけ 助詞 9 副助詞 2 * 0 * 0 "代表表記:だけ/だけ" <代表表記:だけ/だけ><正規化代表表記:だけ/だけ><かな漢字><ひらがな><付属>
+、 、 、 特殊 1 読点 2 * 0 * 0 "代表表記:、/、" <代表表記:、/、><正規化代表表記:、/、><英記号><記号><述語区切><付属>
+* 4D <解析済><BGH:フィールド/ふぃーるど><相対名詞><助詞><連体修飾><体言><準用言><受:隣のみ><後方チ><係:ノ格><区切:0-4><正規化代表表記:フィールド/ふぃーるど><主辞代表表記:フィールド/ふぃーるど>
++ 4D <BGH:フィールド/ふぃーるど><解析済><相対名詞><助詞><連体修飾><体言><準用言><受:隣のみ><後方チ><係:ノ格><区切:0-4><名詞項候補><先行詞候補><省略解析なし><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:フィールド/ふぃーるど><主辞代表表記:フィールド/ふぃーるど>
+フィールド ふぃーるど フィールド 名詞 6 普通名詞 1 * 0 * 0 "代表表記:フィールド/ふぃーるど カテゴリ:場所-その他" <代表表記:フィールド/ふぃーるど><カテゴリ:場所-その他><正規化代表表記:フィールド/ふぃーるど><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+上 じょう 上 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:上/じょう" <代表表記:上/じょう><正規化代表表記:上/じょう><相対名詞><副詞的接尾辞><漢字><かな漢字><名詞相当語><付属>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 5D <解析済><SM-主体><SM-人><BGH:モンスター/もんすたー><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:モンスター/もんすたー><主辞代表表記:モンスター/もんすたー>
++ 5D <rel type="修飾" target="フィールド上" sid="w201106-0000060050-2" id="3"/><rel type="修飾" mode="AND" target="数" sid="w201106-0000060050-2" id="2"/><SM-主体><SM-人><BGH:モンスター/もんすたー><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:モンスター/もんすたー><主辞代表表記:モンスター/もんすたー>
+モンスター もんすたー モンスター 名詞 6 普通名詞 1 * 0 * 0 "代表表記:モンスター/もんすたー カテゴリ:人" <代表表記:モンスター/もんすたー><カテゴリ:人><正規化代表表記:モンスター/もんすたー><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:破壊/はかい+する/する><文末><サ変><サ変動詞><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:破壊/はかい><主辞代表表記:破壊/はかい>
++ -1D <rel type="ヲ" target="モンスター" sid="w201106-0000060050-2" id="4"/><rel type="ガ" target="不特定:状況"/><BGH:破壊/はかい+する/する><解析済><文末><サ変動詞><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><サ変><正規化代表表記:破壊/はかい><主辞代表表記:破壊/はかい><用言代表表記:破壊/はかい><節-区切><節-主辞><時制:非過去><主題格:一人称優位>
+破壊 はかい 破壊 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:破壊/はかい カテゴリ:抽象物 反義:名詞-サ変名詞:建設/けんせつ" <代表表記:破壊/はかい><カテゴリ:抽象物><反義:名詞-サ変名詞:建設/けんせつ><正規化代表表記:破壊/はかい><漢字><かな漢字><名詞相当語><サ変><サ変動詞><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+する する する 動詞 2 * 0 サ変動詞 16 基本形 2 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><表現文末><とタ系連用テ形複合辞><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
+# S-ID:w201106-0000060050-3 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-130.82529 MOD:2016/07/22 MEMO:
+* 1D <解析済><文頭><連体修飾><連体詞形態指示詞><係:連体><区切:0-4><正規化代表表記:この/この><主辞代表表記:この/この>
++ 1D <解析済><文頭><連体修飾><連体詞形態指示詞><係:連体><区切:0-4><正規化代表表記:この/この><主辞代表表記:この/この><クエリ削除語>
+この この この 指示詞 7 連体詞形態指示詞 2 * 0 * 0 "代表表記:この/この" <代表表記:この/この><正規化代表表記:この/この><かな漢字><ひらがな><文頭><自立><内容語><タグ単位始><文節始><文節主辞>
+* 6D <解析済><BGH:効果/こうか><ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><正規化代表表記:効果/こうか><主辞代表表記:効果/こうか>
++ 8D <rel type="トイウ" target="破壊する" sid="w201106-0000060050-2" id="5"/><BGH:効果/こうか><解析済><ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:効果/こうか><主辞代表表記:効果/こうか>
+効果 こうか 効果 名詞 6 普通名詞 1 * 0 * 0 "代表表記:効果/こうか カテゴリ:抽象物" <代表表記:効果/こうか><カテゴリ:抽象物><正規化代表表記:効果/こうか><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+は は は 助詞 9 副助詞 2 * 0 * 0 "代表表記:は/は" <代表表記:は/は><正規化代表表記:は/は><かな漢字><ひらがな><付属>
+* 3D <解析済><SM-動作><BGH:ターン/たーん><サ変><カウンタ:ターン><数量><ニ><助詞><体言><係:ニ格><区切:0-0><隣係絶対><格要素><連用要素><正規化代表表記:１/いち+ターン/たーん><主辞代表表記:ターン/たーん>
++ 3D <文節内><係:文節内><数量><体言><名詞項候補><先行詞候補><省略解析なし><正規化代表表記:１/いち>
+１ いち １ 名詞 6 数詞 7 * 0 * 0 "代表表記:１/いち カテゴリ:数量" <代表表記:１/いち><カテゴリ:数量><正規化代表表記:１/いち><記英数カ><数字><名詞相当語><自立><内容語><タグ単位始><文節始><用言表記先頭><用言表記末尾><用言意味表記末尾>
++ 4D <SM-動作><BGH:ターン/たーん><解析済><カウンタ:ターン><数量><ニ><助詞><体言><係:ニ格><区切:0-0><隣係絶対><格要素><連用要素><サ変><名詞項候補><先行詞候補><省略解析なし><非用言格解析:動><照応ヒント:係><態:未定><正規化代表表記:ターン/たーん><主辞代表表記:ターン/たーん>
+ターン たーん ターン 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:ターン/たーん カテゴリ:抽象物" <代表表記:ターン/たーん><カテゴリ:抽象物><正規化代表表記:ターン/たーん><カウンタ><記英数カ><カタカナ><名詞相当語><サ変><自立><複合←><内容語><タグ単位始><固有キー><文節主辞>
+に に に 助詞 9 格助詞 1 * 0 * 0 "代表表記:に/に" <代表表記:に/に><正規化代表表記:に/に><かな漢字><ひらがな><付属>
+* 6D <解析済><BGH:度/ど><カウンタ:度><回数><数量><助詞><体言><準用言><受:隣のみ><隣受絶対><係:未格><区切:0-4><格要素><連用要素><正規化代表表記:１/いち+度/ど><主辞代表表記:１/いち+度/ど>
++ 8D <rel type="ニ" target="ターン" sid="w201106-0000060050-3" id="3"/><BGH:度/ど><解析済><カウンタ:度><回数><数量><助詞><体言><準用言><受:隣のみ><隣受絶対><係:未格><区切:0-4><格要素><連用要素><省略解析なし><正規化代表表記:１/いち+度/ど><主辞代表表記:１/いち+度/ど>
+１ いち １ 名詞 6 数詞 7 * 0 * 0 "代表表記:１/いち カテゴリ:数量" <代表表記:１/いち><カテゴリ:数量><正規化代表表記:１/いち><記英数カ><数字><名詞相当語><自立><内容語><タグ単位始><文節始>
+度 ど 度 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:度/ど 準内容語" <代表表記:度/ど><準内容語><正規化代表表記:度/ど><カウンタ><漢字><かな漢字><付属><文節主辞>
+だけ だけ だけ 助詞 9 副助詞 2 * 0 * 0 "代表表記:だけ/だけ" <代表表記:だけ/だけ><正規化代表表記:だけ/だけ><かな漢字><ひらがな><付属>
+* 5D <解析済><SM-主体><SM-人><BGH:自分/じぶん><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:自分/じぶん><主辞代表表記:自分/じぶん>
++ 7D <rel type="=≒" target="不特定:人"/><rel type="=≒" mode="AND" target="著者"/><rel type="=≒" mode="AND" target="読者"/><SM-主体><SM-人><BGH:自分/じぶん><解析済><助詞><連体修飾><体言><係:ノ格><区切:0-4><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:自分/じぶん><主辞代表表記:自分/じぶん>
+自分 じぶん 自分 名詞 6 普通名詞 1 * 0 * 0 "代表表記:自分/じぶん 修飾（デ格） カテゴリ:人" <代表表記:自分/じぶん><修飾（デ格）><カテゴリ:人><正規化代表表記:自分/じぶん><判定詞化名詞:9-9><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 6D <解析済><ニ><助詞><体言><係:ニ格><区切:0-0><格要素><連用要素>
++ 7D <rel type="ガ" target="フェイズ" sid="w201106-0000060050-3" id="7"/><文節内><係:文節内><名詞的形容詞語幹><体言><名詞項候補><先行詞候補><非用言格解析:形>
+メイン めいん メインだ 形容詞 3 * 0 ナノ形容詞 22 語幹 1 NIL <記英数カ><カタカナ><名詞的形容詞語幹><名詞相当語><自立><内容語><タグ単位始><文節始><用言表記先頭><用言表記末尾><用言意味表記末尾>
++ 8D <rel type="ノ？" target="自分" sid="w201106-0000060050-3" id="5"/><解析済><ニ><助詞><体言><係:ニ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補>
+フェイズ ふぇいず フェイズ 名詞 6 普通名詞 1 * 0 * 0 NIL <記英数カ><カタカナ><名詞相当語><自立><複合←><内容語><タグ単位始><固有キー><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+に に に 助詞 9 格助詞 1 * 0 * 0 "代表表記:に/に" <代表表記:に/に><正規化代表表記:に/に><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:使用/しよう+する/する><文末><サ変><サ変動詞><可能表現><ガ><句点><助詞><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:使用/しよう><主辞代表表記:使用/しよう>
++ -1D <rel type="ガ" target="不特定:人"/><rel type="ヲ" target="効果" sid="w201106-0000060050-3" id="1"/><rel type="ガ" mode="？" target="著者"/><rel type="ガ" mode="？" target="読者"/><rel type="ニ" target="フェイズ" sid="w201106-0000060050-3" id="7"/><BGH:使用/しよう+する/する><解析済><文末><サ変動詞><可能表現><ガ><句点><助詞><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><サ変><正規化代表表記:使用/しよう><主辞代表表記:使用/しよう><用言代表表記:使用/しよう+する/する+事/こと+出来る/できる><節-区切><節-主辞><時制:非過去>
+使用 しよう 使用 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:使用/しよう カテゴリ:抽象物" <代表表記:使用/しよう><カテゴリ:抽象物><正規化代表表記:使用/しよう><漢字><かな漢字><名詞相当語><サ変><サ変動詞><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭>
+する する する 動詞 2 * 0 サ変動詞 16 基本形 2 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><とタ系連用テ形複合辞><付属>
+事 こと 事 名詞 6 普通名詞 1 * 0 * 0 "代表表記:事/こと 漢字読み:訓 カテゴリ:抽象物" <代表表記:事/こと><漢字読み:訓><カテゴリ:抽象物><正規化代表表記:事/こと><漢字><かな漢字><名詞相当語><付属>
+が が が 助詞 9 格助詞 1 * 0 * 0 "代表表記:が/が" <代表表記:が/が><正規化代表表記:が/が><かな漢字><ひらがな><付属>
+できる できる できる 動詞 2 * 0 母音動詞 1 基本形 2 "代表表記:出来る/できる" <代表表記:出来る/できる><正規化代表表記:出来る/できる><かな漢字><ひらがな><活用語><表現文末><付属><用言表記末尾><用言意味表記末尾>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
diff --git a/tests/data/knp/w201106-0000060560.knp b/tests/data/knp/w201106-0000060560.knp
new file mode 100644
index 00000000..46025a95
--- /dev/null
+++ b/tests/data/knp/w201106-0000060560.knp
@@ -0,0 +1,91 @@
+# S-ID:w201106-0000060560-1 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-83.15228 MOD:2015/12/17 MEMO:
+* 2D <解析済><SM-主体><SM-人><BGH:ユーザー/ゆーざー><文頭><ガ><助詞><体言><係:ガ格><区切:0-0><格要素><連用要素><正規化代表表記:エンド/えんど+ユーザー/ゆーざー><主辞代表表記:ユーザー/ゆーざー>
++ 1D <BGH:エンド/えんど><文節内><係:文節内><文頭><体言><名詞項候補><先行詞候補><正規化代表表記:エンド/えんど>
+エンド えんど エンド 名詞 6 普通名詞 1 * 0 * 0 "代表表記:エンド/えんど カテゴリ:抽象物" <代表表記:エンド/えんど><カテゴリ:抽象物><正規化代表表記:エンド/えんど><記英数カ><カタカナ><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><固有キー>
++ 3D <rel type="修飾" target="エンド" sid="w201106-0000060560-1" id="0"/><SM-主体><SM-人><BGH:ユーザー/ゆーざー><解析済><ガ><助詞><体言><係:ガ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:ユーザー/ゆーざー><主辞代表表記:ユーザー/ゆーざー><Wikipedia上位語:なし><Wikipediaエントリ:エンドユーザ><Wikipediaリダイレクト:エンドユーザー>
+ユーザー ゆーざー ユーザー 名詞 6 普通名詞 1 * 0 * 0 "代表表記:ユーザー/ゆーざー カテゴリ:人 ドメイン:科学・技術" <代表表記:ユーザー/ゆーざー><カテゴリ:人><ドメイン:科学・技術><正規化代表表記:ユーザー/ゆーざー><Wikipedia上位語:なし:0-1><Wikipediaエントリ:エンドユーザ:0-1><Wikipediaリダイレクト:エンドユーザー:0-1><記英数カ><カタカナ><名詞相当語><自立><複合←><内容語><タグ単位始><固有キー><文節主辞>
+が が が 助詞 9 格助詞 1 * 0 * 0 "代表表記:が/が" <代表表記:が/が><正規化代表表記:が/が><かな漢字><ひらがな><付属>
+* 2D <解析済><BGH:関心/かんしん><体言><係:未格><隣係絶対><裸名詞><区切:0-0><格要素><連用要素><正規化代表表記:関心/かんしん><主辞代表表記:関心/かんしん>
++ 3D <rel type="ノ" target="ユーザー" sid="w201106-0000060560-1" id="1"/><BGH:関心/かんしん><解析済><体言><係:未格><隣係絶対><裸名詞><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:関心/かんしん><主辞代表表記:関心/かんしん>
+関心 かんしん 関心 名詞 6 普通名詞 1 * 0 * 0 "代表表記:関心/かんしん カテゴリ:抽象物" <代表表記:関心/かんしん><カテゴリ:抽象物><正規化代表表記:関心/かんしん><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+* 3D <解析済><BGH:有る/ある><補文ト><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:有る/ある><主辞代表表記:有る/ある>
++ 4D <rel type="ガ" target="関心" sid="w201106-0000060560-1" id="2"/><rel type="ニ" target="病気" sid="w201106-0000060560-1" id="4"/><rel type="ガ２" target="ユーザー" sid="w201106-0000060560-1" id="1"/><BGH:有る/ある><解析済><補文ト><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:有る/ある><主辞代表表記:有る/ある><用言代表表記:有る/ある><節-区切:連体修飾><節-主辞><時制:非過去><クエリ削除語>
+有る ある 有る 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:有る/ある 補文ト 反義:形容詞:無い/ない" <代表表記:有る/ある><補文ト><反義:形容詞:無い/ない><正規化代表表記:有る/ある><かな漢字><活用語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+* 4D <解析済><BGH:病気/びょうき><ニ><助詞><体言><係:ニ格><区切:0-0><隣係絶対><格要素><連用要素><正規化代表表記:病気/びょうき><主辞代表表記:病気/びょうき>
++ 5D <BGH:病気/びょうき><解析済><ニ><助詞><体言><係:ニ格><区切:0-0><隣係絶対><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:病気/びょうき><主辞代表表記:病気/びょうき>
+病気 びょうき 病気 名詞 6 普通名詞 1 * 0 * 0 "代表表記:病気/びょうき カテゴリ:抽象物 ドメイン:健康・医学" <代表表記:病気/びょうき><カテゴリ:抽象物><ドメイン:健康・医学><正規化代表表記:病気/びょうき><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+に に に 助詞 9 格助詞 1 * 0 * 0 "代表表記:に/に" <代表表記:に/に><正規化代表表記:に/に><かな漢字><ひらがな><付属>
+* 5D <解析済><BGH:対する/たいする><用言:動><受:隣のみ><隣受絶対><複合辞><係:複合辞連用><レベル:A-><並キ:？:&ST:3.0&&&自立語一致><区切:0-3><ID:〜に対して><機能的基本句><動態述語><正規化代表表記:対する/たいする><主辞代表表記:対する/たいする><並列類似度:-100.000>
++ 6D <BGH:対する/たいする><解析済><用言:動><受:隣のみ><隣受絶対><複合辞><係:複合辞連用><レベル:A-><並キ:？:&ST:3.0&&&自立語一致><区切:0-3><ID:〜に対して><機能的基本句><動態述語><格解析なし><省略解析なし><格要素表記直前参照><正規化代表表記:対する/たいする><主辞代表表記:対する/たいする>
+対して たいして 対する 動詞 2 * 0 サ変動詞 16 タ系連用テ形 14 "代表表記:対する/たいする" <代表表記:対する/たいする><正規化代表表記:対する/たいする><かな漢字><活用語><に基本形複合辞><に基本連用形複合辞><にタ系連用テ形複合辞><自立><内容語><タグ単位始><文節始><文節主辞>
+* 6D <解析済><SM-主体><SM-動作><SM-人><BGH:得意だ/とくいだ><連体修飾><用言:形><係:連格><レベル:B-><区切:0-5><ID:（形判連体）><連体節><状態述語><正規化代表表記:得意だ/とくいだ><主辞代表表記:得意だ/とくいだ>
++ 7D <rel type="ガ" target="病気" sid="w201106-0000060560-1" id="4"/><rel type="ガ２" target="ドクター" sid="w201106-0000060560-1" id="7"/><SM-主体><SM-動作><SM-人><BGH:得意だ/とくいだ><解析済><連体修飾><用言:形><係:連格><レベル:B-><区切:0-5><ID:（形判連体）><連体節><状態述語><正規化代表表記:得意だ/とくいだ><主辞代表表記:得意だ/とくいだ><用言代表表記:得意だ/とくいだ><時制:非過去>
+得意な とくいな 得意だ 形容詞 3 * 0 ナノ形容詞 22 ダ列基本連体形 3 "代表表記:得意だ/とくいだ 反義:形容詞:苦手だ/にがてだ" <代表表記:得意だ/とくいだ><反義:形容詞:苦手だ/にがてだ><正規化代表表記:得意だ/とくいだ><かな漢字><活用語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+* 7D <解析済><SM-主体><SM-人><BGH:ドクター/どくたー><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:ドクター/どくたー><主辞代表表記:ドクター/どくたー>
++ 8D <SM-主体><SM-人><BGH:ドクター/どくたー><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:ドクター/どくたー><主辞代表表記:ドクター/どくたー>
+ドクター どくたー ドクター 名詞 6 普通名詞 1 * 0 * 0 "代表表記:ドクター/どくたー カテゴリ:人 ドメイン:健康・医学;科学・技術" <代表表記:ドクター/どくたー><カテゴリ:人><ドメイン:健康・医学;科学・技術><正規化代表表記:ドクター/どくたー><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:探す/さがす><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:丁寧表現><正規化代表表記:探す/さがす><主辞代表表記:探す/さがす>
++ -1D <rel type="ヲ" target="ドクター" sid="w201106-0000060560-1" id="7"/><rel type="ニタイシテ" target="病気" sid="w201106-0000060560-1" id="4"/><rel type="ガ" target="著者"/><BGH:探す/さがす><解析済><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:丁寧表現><正規化代表表記:探す/さがす><主辞代表表記:探す/さがす><用言代表表記:探す/さがす~テ形+いる/いる><節-区切><節-主辞><時制:非過去><主題格:一人称優位><クエリ削除語>
+探して さがして 探す 動詞 2 * 0 子音動詞サ行 5 タ系連用テ形 14 "代表表記:探す/さがす" <代表表記:探す/さがす><正規化代表表記:探す/さがす><かな漢字><活用語><自立><内容語><タグ単位始><文節始><用言見出接辞:テ形><文節主辞><用言表記先頭>
+い い いる 接尾辞 14 動詞性接尾辞 7 母音動詞 1 基本連用形 8 "代表表記:いる/いる" <代表表記:いる/いる><正規化代表表記:いる/いる><かな漢字><ひらがな><活用語><付属><用言表記末尾><用言意味表記末尾>
+ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><表現文末><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
+# S-ID:w201106-0000060560-2 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-70.71578 MOD:2015/12/17 MEMO:
+* 6D <解析済><BGH:そこ/そこ><文頭><デ><助詞><体言><指示詞><係:デ格><区切:0-0><格要素><連用要素><正規化代表表記:そこ/そこ><主辞代表表記:そこ/そこ>
++ 6D <BGH:そこ/そこ><解析済><文頭><デ><助詞><体言><指示詞><係:デ格><区切:0-0><格要素><連用要素><名詞項候補><正規化代表表記:そこ/そこ><主辞代表表記:そこ/そこ><クエリ削除語>
+そこ そこ そこ 指示詞 7 名詞形態指示詞 1 * 0 * 0 "代表表記:そこ/そこ" <代表表記:そこ/そこ><正規化代表表記:そこ/そこ><かな漢字><ひらがな><文頭><自立><内容語><タグ単位始><文節始><文節主辞>
+で で で 助詞 9 格助詞 1 * 0 * 0 "代表表記:で/で" <代表表記:で/で><正規化代表表記:で/で><かな漢字><ひらがな><付属>
+* 2D <解析済><SM-場所><BGH:全国/ぜんこく><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:全国/ぜんこく><主辞代表表記:全国/ぜんこく>
++ 2D <SM-場所><BGH:全国/ぜんこく><解析済><助詞><連体修飾><体言><係:ノ格><区切:0-4><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:全国/ぜんこく><主辞代表表記:全国/ぜんこく>
+全国 ぜんこく 全国 名詞 6 普通名詞 1 * 0 * 0 "代表表記:全国/ぜんこく カテゴリ:場所-その他" <代表表記:全国/ぜんこく><カテゴリ:場所-その他><正規化代表表記:全国/ぜんこく><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 4D <解析済><SM-主体><SM-人><BGH:ドクター/どくたー><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:ドクター/どくたー><主辞代表表記:ドクター/どくたー>
++ 4D <rel type="修飾" target="全国" sid="w201106-0000060560-2" id="1"/><rel type="=≒" target="ドクター" sid="w201106-0000060560-1" id="7"/><SM-主体><SM-人><BGH:ドクター/どくたー><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:ドクター/どくたー><主辞代表表記:ドクター/どくたー>
+ドクター どくたー ドクター 名詞 6 普通名詞 1 * 0 * 0 "代表表記:ドクター/どくたー カテゴリ:人 ドメイン:健康・医学;科学・技術" <代表表記:ドクター/どくたー><カテゴリ:人><ドメイン:健康・医学;科学・技術><正規化代表表記:ドクター/どくたー><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* 4D <解析済><BGH:簡単だ/かんたんだ><用言:形><係:連用><修飾><レベル:A-><並キ:述:&D:1&&用言:形&&係:連用||&D:1&&副詞||&ST:4.0><区切:0-0><ID:〜に><連用要素><状態述語><正規化代表表記:簡単だ/かんたんだ><主辞代表表記:簡単だ/かんたんだ><並列類似度:1.258>
++ 4D <BGH:簡単だ/かんたんだ><解析済><用言:形><係:連用><修飾><レベル:A-><並キ:述:&D:1&&用言:形&&係:連用||&D:1&&副詞||&ST:4.0><区切:0-0><ID:〜に><連用要素><状態述語><省略解析なし><正規化代表表記:簡単だ/かんたんだ><主辞代表表記:簡単だ/かんたんだ><用言代表表記:簡単だ/かんたんだ><時制:非過去>
+簡単に かんたんに 簡単だ 形容詞 3 * 0 ナ形容詞 21 ダ列基本連用形 7 "代表表記:簡単だ/かんたんだ 反義:形容詞:複雑だ/ふくざつだ" <代表表記:簡単だ/かんたんだ><反義:形容詞:複雑だ/ふくざつだ><正規化代表表記:簡単だ/かんたんだ><かな漢字><活用語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+* 5D <解析済><BGH:紹介/しょうかい><サ変><サ変動詞><可能表現><態:可能><〜できる><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:紹介/しょうかい><主辞代表表記:紹介/しょうかい>
++ 5D <rel type="ヲ" target="ドクター" sid="w201106-0000060560-2" id="2"/><rel type="ガ" target="不特定:人"/><rel type="ガ" mode="？" target="読者"/><rel type="ニ" target="読者"/><rel type="ニ" mode="？" target="不特定:人"/><rel type="外の関係" target="コーナー" sid="w201106-0000060560-2" id="5"/><BGH:紹介/しょうかい><解析済><サ変動詞><可能表現><態:可能><〜できる><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><サ変><正規化代表表記:紹介/しょうかい><主辞代表表記:紹介/しょうかい><用言代表表記:紹介/しょうかい+出来る/できる><節-区切:連体修飾><節-主辞><時制:非過去>
+紹介 しょうかい 紹介 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:紹介/しょうかい カテゴリ:抽象物" <代表表記:紹介/しょうかい><カテゴリ:抽象物><正規化代表表記:紹介/しょうかい><漢字><かな漢字><名詞相当語><サ変><サ変動詞><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭>
+できる できる できる 動詞 2 * 0 母音動詞 1 基本形 2 "代表表記:出来る/できる" <代表表記:出来る/できる><正規化代表表記:出来る/できる><かな漢字><ひらがな><活用語><付属><用言表記末尾><用言意味表記末尾>
+* 6D <解析済><BGH:コーナー/こーなー><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:コーナー/こーなー><主辞代表表記:コーナー/こーなー>
++ 6D <BGH:コーナー/こーなー><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:コーナー/こーなー><主辞代表表記:コーナー/こーなー>
+コーナー こーなー コーナー 名詞 6 普通名詞 1 * 0 * 0 "代表表記:コーナー/こーなー カテゴリ:場所-機能 ドメイン:料理・食事" <代表表記:コーナー/こーなー><カテゴリ:場所-機能><ドメイン:料理・食事><正規化代表表記:コーナー/こーなー><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:設ける/もうける><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:丁寧表現><正規化代表表記:設ける/もうける><主辞代表表記:設ける/もうける>
++ -1D <rel type="ヲ" target="コーナー" sid="w201106-0000060560-2" id="5"/><rel type="ガ" target="著者"/><rel type="ニ" target="不特定:物1"/><BGH:設ける/もうける><解析済><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:丁寧表現><正規化代表表記:設ける/もうける><主辞代表表記:設ける/もうける><用言代表表記:設ける/もうける><節-区切><節-主辞><主題格:一人称優位>
+設け もうけ 設ける 動詞 2 * 0 母音動詞 1 基本連用形 8 "代表表記:設ける/もうける" <代表表記:設ける/もうける><正規化代表表記:設ける/もうける><かな漢字><活用語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+ました ました ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 タ形 7 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><表現文末><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
+# S-ID:w201106-0000060560-3 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-88.19132 MOD:2015/12/03 MEMO:
+* 1D <解析済><SM-主体><SM-人><BGH:ドクター/どくたー><文頭><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:ドクター/どくたー><主辞代表表記:ドクター/どくたー>
++ 1D <rel type="=" target="ドクター" sid="w201106-0000060560-2" id="2"/><SM-主体><SM-人><BGH:ドクター/どくたー><解析済><文頭><助詞><連体修飾><体言><係:ノ格><区切:0-4><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:ドクター/どくたー><主辞代表表記:ドクター/どくたー>
+ドクター どくたー ドクター 名詞 6 普通名詞 1 * 0 * 0 "代表表記:ドクター/どくたー カテゴリ:人 ドメイン:健康・医学;科学・技術" <代表表記:ドクター/どくたー><カテゴリ:人><ドメイン:健康・医学;科学・技術><正規化代表表記:ドクター/どくたー><記英数カ><カタカナ><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 5D <解析済><SM-主体><SM-人><BGH:皆様/みなさま><体言><係:未格><隣係絶対><裸名詞><区切:0-0><格要素><連用要素><正規化代表表記:皆様/みなさま><主辞代表表記:皆様/みなさま>
++ 5D <rel type="修飾" target="ドクター" sid="w201106-0000060560-3" id="0"/><rel type="=構" target="ドクター" sid="w201106-0000060560-3" id="0"/><rel type="=≒" mode="？" target="読者"/><SM-主体><SM-人><BGH:皆様/みなさま><解析済><体言><係:未格><隣係絶対><裸名詞><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:皆様/みなさま><主辞代表表記:皆様/みなさま>
+皆様 みなさま 皆様 名詞 6 普通名詞 1 * 0 * 0 "代表表記:皆様/みなさま カテゴリ:人" <代表表記:皆様/みなさま><カテゴリ:人><正規化代表表記:皆様/みなさま><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+* 3D <解析済><BGH:協力/きょうりょく><サ変><助詞><連体修飾><体言><用言:動><レベル:A><サ変動詞化><係:ノ格><区切:0-4><動態述語><正規化代表表記:協力/きょうりょく><主辞代表表記:協力/きょうりょく>
++ 3D <rel type="ニ" target="著者"/><rel type="ガ" target="皆様" sid="w201106-0000060560-3" id="1"/><rel type="ニ" mode="？" target="コーナー" sid="w201106-0000060560-2" id="5"/><BGH:協力/きょうりょく><解析済><助詞><連体修飾><体言><用言:動><レベル:A><サ変動詞化><係:ノ格><区切:0-4><動態述語><サ変><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:協力/きょうりょく><主辞代表表記:協力/きょうりょく><用言代表表記:協力/きょうりょく>
+ご ご ご 接頭辞 13 名詞接頭辞 1 * 0 * 0 "代表表記:御/お" <代表表記:御/お><正規化代表表記:御/お><かな漢字><ひらがな><接頭><非独立接頭辞><タグ単位始><文節始>
+協力 きょうりょく 協力 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:協力/きょうりょく 修飾（デ格） カテゴリ:抽象物" <代表表記:協力/きょうりょく><修飾（デ格）><カテゴリ:抽象物><正規化代表表記:協力/きょうりょく><漢字><かな漢字><名詞相当語><サ変><自立><内容語><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 5D <解析済><BGH:程/ほど><形副名詞><外の関係><体言><一文字漢字><係:連用><修飾><レベル:B><並キ:名:&ST:4.0&&&自立語一致><区切:0-4><ID:（副詞的名詞）><連用要素><連用節><機能的基本句><正規化代表表記:程/ほど><主辞代表表記:程/ほど><並列類似度:-100.000>
++ 5D <rel type="ノ" target="ご協力" sid="w201106-0000060560-3" id="2"/><BGH:程/ほど><解析済><形副名詞><外の関係><体言><一文字漢字><係:連用><修飾><レベル:B><並キ:名:&ST:4.0&&&自立語一致><区切:0-4><ID:（副詞的名詞）><連用要素><連用節><機能的基本句><名詞項候補><省略解析なし><正規化代表表記:程/ほど><主辞代表表記:程/ほど><クエリ削除語>
+程 ほど 程 名詞 6 副詞的名詞 9 * 0 * 0 "代表表記:程/ほど" <代表表記:程/ほど><正規化代表表記:程/ほど><漢字><かな漢字><名詞相当語><形副名詞><自立><内容語><タグ単位始><文節始><文節主辞>
+* 5D <解析済><BGH:よろしく/よろしく><副詞><修飾><係:連用><区切:0-4><連用要素><連用節><正規化代表表記:よろしく/よろしく><主辞代表表記:よろしく/よろしく>
++ 5D <BGH:よろしく/よろしく><解析済><副詞><修飾><係:連用><区切:0-4><連用要素><連用節><正規化代表表記:よろしく/よろしく><主辞代表表記:よろしく/よろしく>
+よろしく よろしく よろしく 副詞 8 * 0 * 0 * 0 "代表表記:よろしく/よろしく スル接続可能" <代表表記:よろしく/よろしく><スル接続可能><正規化代表表記:よろしく/よろしく><かな漢字><ひらがな><自立><内容語><タグ単位始><文節始><文節主辞>
+* -1D <解析済><BGH:願う/ねがう><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:謙譲表現><正規化代表表記:願う/ねがう><主辞代表表記:願う/ねがう>
++ -1D <rel type="ヲ" target="程" sid="w201106-0000060560-3" id="3"/><rel type="ガ" target="著者"/><BGH:願う/ねがう><解析済><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:謙譲表現><正規化代表表記:願う/ねがう><主辞代表表記:願う/ねがう><用言代表表記:願う/ねがう><節-区切><節-主辞><時制:非過去><主題格:一人称優位>
+お お お 接頭辞 13 名詞接頭辞 1 * 0 * 0 "代表表記:御/お" <代表表記:御/お><正規化代表表記:御/お><かな漢字><ひらがな><接頭><非独立接頭辞><タグ単位始><文節始>
+願い ねがい 願う 動詞 2 * 0 子音動詞ワ行 12 基本連用形 8 "代表表記:願う/ねがう" <代表表記:願う/ねがう><正規化代表表記:願う/ねがう><かな漢字><活用語><自立><内容語><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+申し上げ もうしあげ 申し上げる 動詞 2 * 0 母音動詞 1 基本連用形 8 "代表表記:申し上げる/もうしあげる 謙譲動詞:言う/いう" <代表表記:申し上げる/もうしあげる><謙譲動詞:言う/いう><正規化代表表記:申し上げる/もうしあげる><かな漢字><活用語><付属>
+ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><表現文末><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
diff --git a/tests/data/knp/w201106-0000060877.knp b/tests/data/knp/w201106-0000060877.knp
new file mode 100644
index 00000000..eae3811c
--- /dev/null
+++ b/tests/data/knp/w201106-0000060877.knp
@@ -0,0 +1,150 @@
+# S-ID:w201106-0000060877-1 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-119.32526 MOD:2013/04/13
+* 1D <解析済><SM-主体><SM-人><SM-場所><SM-組織><BGH:センター/せんたー><文頭><サ変><体言><係:同格連体><括弧同格><区切:0-1><正規化代表表記:地域/ちいき+活動/かつどう+支援/しえん+センター/せんたー><主辞代表表記:センター/せんたー>
++ 1D <BGH:地域/ちいき><文節内><係:文節内><文頭><体言><名詞項候補><先行詞候補><正規化代表表記:地域/ちいき>
+地域 ちいき 地域 名詞 6 普通名詞 1 * 0 * 0 "代表表記:地域/ちいき カテゴリ:場所-その他" <代表表記:地域/ちいき><カテゴリ:場所-その他><正規化代表表記:地域/ちいき><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始>
++ 2D <rel type="ガ" target="不特定:人"/><rel type="デ" target="地域" sid="w201106-0000060877-1" id="0"/><SM-動作><BGH:活動/かつどう><文節内><係:文節内><サ変><体言><名詞項候補><先行詞候補><非用言格解析:動><照応ヒント:係><態:未定><正規化代表表記:活動/かつどう>
+活動 かつどう 活動 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:活動/かつどう カテゴリ:抽象物" <代表表記:活動/かつどう><カテゴリ:抽象物><正規化代表表記:活動/かつどう><漢字><かな漢字><名詞相当語><サ変><自立><複合←><内容語><タグ単位始>
++ 3D <rel type="ガ" target="センター" sid="w201106-0000060877-1" id="3"/><rel type="ヲ" target="活動" sid="w201106-0000060877-1" id="1"/><BGH:支援/しえん><文節内><係:文節内><サ変><体言><名詞項候補><先行詞候補><非用言格解析:動><照応ヒント:係><態:未定><正規化代表表記:支援/しえん>
+支援 しえん 支援 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:支援/しえん カテゴリ:抽象物" <代表表記:支援/しえん><カテゴリ:抽象物><正規化代表表記:支援/しえん><漢字><かな漢字><名詞相当語><サ変><自立><複合←><内容語><タグ単位始>
++ 6D <rel type="ノ" target="支援" sid="w201106-0000060877-1" id="2"/><SM-主体><SM-人><SM-場所><SM-組織><BGH:センター/せんたー><解析済><体言><係:同格連体><括弧同格><区切:0-1><名詞項候補><先行詞候補><正規化代表表記:センター/せんたー><主辞代表表記:センター/せんたー><Wikipedia上位語:福祉施設><Wikipediaエントリ:地域活動支援センター>
+センター せんたー センター 名詞 6 普通名詞 1 * 0 * 0 "代表表記:センター/せんたー カテゴリ:場所-施設;場所-機能" <代表表記:センター/せんたー><カテゴリ:場所-施設;場所-機能><正規化代表表記:センター/せんたー><Wikipedia上位語:福祉施設:0-3><Wikipediaエントリ:地域活動支援センター:0-3><記英数カ><カタカナ><名詞相当語><自立><複合←><内容語><タグ単位始><固有キー><文節主辞>
+* 4D <解析済><BGH:園/えん><地名疑><住所疑><ヲ><括弧始><括弧終><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:柏/かしわ+市/し+ひまわり/ひまわり+園/えん><主辞代表表記:園/えん><主辞’代表表記:ひまわり/ひまわり+園/えん>
++ 5D <rel type="=" target="著者"/><SM-場所><BGH:市/し><文節内><係:文節内><SM-主体><SM-組織><地名><住所><括弧始><引用内文頭><体言><名詞項候補><先行詞候補><正規化代表表記:柏/かしわ+市/し><Wikipedia上位語:市/し><Wikipediaエントリ:柏市>
+「 「 「 特殊 1 括弧始 3 * 0 * 0 "代表表記:「/「" <代表表記:「/「><正規化代表表記:「/「><記英数カ><英記号><記号><括弧始><括弧><接頭><非独立接頭辞><タグ単位始><文節始>
+柏 かしわ 柏 名詞 6 地名 4 * 0 * 0 "代表表記:柏/かしわ 地名:日本:千葉県:市" <代表表記:柏/かしわ><地名:日本:千葉県:市><正規化代表表記:柏/かしわ><漢字><かな漢字><名詞相当語><自立><内容語><固有キー>
+市 し 市 接尾辞 14 名詞性特殊接尾辞 4 * 0 * 0 "代表表記:市/し 住所末尾 準内容語 カテゴリ:組織・団体;場所-その他" <代表表記:市/し><住所末尾><準内容語><カテゴリ:組織・団体;場所-その他><正規化代表表記:市/し><Wikipedia上位語:市/し:5-6><Wikipediaエントリ:柏市:5-6><漢字><かな漢字><付属><複合←>
++ 6D <BGH:ひまわり/ひまわり><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:ひまわり/ひまわり>
+ひまわり ひまわり ひまわり 名詞 6 普通名詞 1 * 0 * 0 "代表表記:ひまわり/ひまわり カテゴリ:植物" <代表表記:ひまわり/ひまわり><カテゴリ:植物><正規化代表表記:ひまわり/ひまわり><かな漢字><ひらがな><名詞相当語><自立><複合←><内容語><タグ単位始>
++ 9D <rel type="修飾" target="センター" sid="w201106-0000060877-1" id="3"/><rel type="=構" target="センター" sid="w201106-0000060877-1" id="3"/><NE:ORGANIZATION:柏市ひまわり園><BGH:園/えん><解析済><地名疑><住所疑><ヲ><括弧始><括弧終><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><一文字漢字><名詞項候補><先行詞候補><正規化代表表記:園/えん><主辞代表表記:園/えん><主辞’代表表記:ひまわり/ひまわり+園/えん>
+園 えん 園 名詞 6 普通名詞 1 * 0 * 0 "代表表記:園/えん 漢字読み:音 カテゴリ:場所-その他" <代表表記:園/えん><漢字読み:音><カテゴリ:場所-その他><正規化代表表記:園/えん><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+」 」 」 特殊 1 括弧終 4 * 0 * 0 "代表表記:」/」" <代表表記:」/」><正規化代表表記:」/」><記英数カ><英記号><記号><括弧終><括弧><述語区切><付属>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* 3D <解析済><固有名詞><体言><係:隣><裸名詞><区切:0-0><正規化代表表記:平成/へいせい><主辞代表表記:平成/へいせい>
++ 8D <解析済><固有名詞><体言><係:隣><裸名詞><区切:0-0><名詞項候補><先行詞候補><正規化代表表記:平成/へいせい><主辞代表表記:平成/へいせい>
+平成 へいせい 平成 名詞 6 固有名詞 3 * 0 * 0 "代表表記:平成/へいせい" <代表表記:平成/へいせい><正規化代表表記:平成/へいせい><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+* 4D <解析済><BGH:年度/ねんど><カウンタ:年度><時間><強時間><数量><カラ><助詞><体言><準用言><受:隣のみ><係:カラ格><並キ:名:&ST:4.0&&数量><区切:1-4><並列タイプ:OTHER><格要素><連用要素><正規化代表表記:年度/ねんど><主辞代表表記:年度/ねんど><並列類似度:-100.000>
++ 9D <NE:DATE:平成２３年度><BGH:年度/ねんど><解析済><カウンタ:年度><時間><強時間><数量><カラ><助詞><体言><準用言><受:隣のみ><係:カラ格><並キ:名:&ST:4.0&&数量><区切:1-4><並列タイプ:OTHER><格要素><連用要素><省略解析なし><正規化代表表記:年度/ねんど><主辞代表表記:年度/ねんど>
+２３ にじゅうさん ２３ 名詞 6 数詞 7 * 0 * 0 NIL <記英数カ><数字><名詞相当語><自立><内容語><タグ単位始><文節始>
+年度 ねんど 年度 接尾辞 14 名詞性名詞助数辞 3 * 0 * 0 "代表表記:年度/ねんど 準内容語 カテゴリ:時間" <代表表記:年度/ねんど><準内容語><カテゴリ:時間><正規化代表表記:年度/ねんど><時間辞><カウンタ><漢字><かな漢字><付属><文節主辞>
+から から から 助詞 9 格助詞 1 * 0 * 0 "代表表記:から/から" <代表表記:から/から><正規化代表表記:から/から><かな漢字><ひらがな><付属>
+* 9D <解析済><BGH:民営/みんえい+化/か+する/する><サ変><読点><用言:動><係:連用><レベル:B+><区切:3-5><ID:〜ので><提題受:20><連用要素><連用節><動態述語><敬語:丁寧表現><正規化代表表記:民営/みんえい+化/か><主辞代表表記:民営/みんえい+化/か>
++ 17D <rel type="ヲ" target="園" sid="w201106-0000060877-1" id="6"/><rel type="ガ" target="柏市" sid="w201106-0000060877-1" id="4"/><rel type="カラ" target="２３年度" sid="w201106-0000060877-1" id="8"/><BGH:民営/みんえい+化/か+する/する><解析済><読点><用言:動><係:連用><レベル:B+><区切:3-5><ID:〜ので><提題受:20><連用要素><連用節><動態述語><敬語:丁寧表現><サ変><節-機能-原因・理由:ので><正規化代表表記:民営/みんえい+化/か><主辞代表表記:民営/みんえい+化/か><用言代表表記:民営/みんえい+化/か><節-区切><節-主辞><Wikipedia上位語:なし><Wikipediaエントリ:民営化><時制:非過去>
+民営 みんえい 民営 名詞 6 普通名詞 1 * 0 * 0 "代表表記:民営/みんえい カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:民営/みんえい><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:民営/みんえい><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><用言表記先頭>
+化 か 化 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:化/か 準内容語 カテゴリ:抽象物 換言:N+:Nになる;N+:Nにする" <代表表記:化/か><準内容語><カテゴリ:抽象物><換言:N+:Nになる;N+:Nにする><正規化代表表記:化/か><Wikipedia上位語:なし:15-16><Wikipediaエントリ:民営化:15-16><漢字><かな漢字><名詞相当語><付属><サ変><文節主辞><用言表記末尾><用言意味表記末尾>
+し し する 動詞 2 * 0 サ変動詞 16 基本連用形 8 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><とタ系連用テ形複合辞><付属>
+ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><付属>
+ので ので のだ 助動詞 5 * 0 ナ形容詞 21 ダ列タ系連用テ形 12 "代表表記:のだ/のだ" <代表表記:のだ/のだ><正規化代表表記:のだ/のだ><かな漢字><ひらがな><活用語><付属>
+、 、 、 特殊 1 読点 2 * 0 * 0 "代表表記:、/、" <代表表記:、/、><正規化代表表記:、/、><英記号><記号><述語区切><付属>
+* 6D <解析済><BGH:長期/ちょうき+的だ/てきだ><時間><用言:形><係:連用><修飾><レベル:A-><並キ:述:&D:1&&用言:形&&係:連用||&D:1&&副詞||&ST:4.0><区切:0-0><ID:〜に><連用要素><状態述語><正規化代表表記:長期/ちょうき+的だ/てきだ><主辞代表表記:長期/ちょうき+的だ/てきだ><並列類似度:1.258>
++ 11D <BGH:長期/ちょうき+的だ/てきだ><解析済><時間><用言:形><係:連用><修飾><レベル:A-><並キ:述:&D:1&&用言:形&&係:連用||&D:1&&副詞||&ST:4.0><区切:0-0><ID:〜に><連用要素><状態述語><省略解析なし><正規化代表表記:長期/ちょうき+的だ/てきだ><主辞代表表記:長期/ちょうき+的だ/てきだ><用言代表表記:長期/ちょうき+的だ/てきだ><時制:非過去>
+長期 ちょうき 長期 名詞 6 時相名詞 10 * 0 * 0 "代表表記:長期/ちょうき 弱時相名詞 カテゴリ:時間" <代表表記:長期/ちょうき><弱時相名詞><カテゴリ:時間><正規化代表表記:長期/ちょうき><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><用言表記先頭>
+的に てきに 的だ 接尾辞 14 形容詞性名詞接尾辞 6 ナ形容詞 21 ダ列基本連用形 7 "代表表記:的だ/てきだ 準内容語 カテゴリ:抽象物" <代表表記:的だ/てきだ><準内容語><カテゴリ:抽象物><正規化代表表記:的だ/てきだ><類似計算:的><かな漢字><活用語><用体変化><付属><文節主辞><用言表記末尾><用言意味表記末尾>
+* 8D <解析済><BGH:運営/うんえい+する/する><サ変><サ変動詞><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:運営/うんえい><主辞代表表記:運営/うんえい>
++ 16D <rel type="ガ" target="所" sid="w201106-0000060877-1" id="16"/><rel type="ヲ" target="園" sid="w201106-0000060877-1" id="6"/><BGH:運営/うんえい+する/する><解析済><サ変動詞><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><サ変><正規化代表表記:運営/うんえい><主辞代表表記:運営/うんえい><用言代表表記:運営/うんえい><時制:非過去>
+運営 うんえい 運営 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:運営/うんえい カテゴリ:抽象物 ドメイン:ビジネス" <代表表記:運営/うんえい><カテゴリ:抽象物><ドメイン:ビジネス><正規化代表表記:運営/うんえい><漢字><かな漢字><名詞相当語><サ変><サ変動詞><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+する する する 動詞 2 * 0 サ変動詞 16 基本形 2 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><とタ系連用テ形複合辞><付属>
+* 8D <解析済><SM-主体><SM-組織><BGH:社会/しゃかい+福祉/ふくし+法人/ほうじん><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:社会/しゃかい+福祉/ふくし+法人/ほうじん><主辞代表表記:法人/ほうじん>
++ 13D <SM-主体><SM-場所><BGH:社会/しゃかい><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:社会/しゃかい>
+社会 しゃかい 社会 名詞 6 普通名詞 1 * 0 * 0 "代表表記:社会/しゃかい カテゴリ:場所-その他 ドメイン:教育・学習;政治" <代表表記:社会/しゃかい><カテゴリ:場所-その他><ドメイン:教育・学習;政治><正規化代表表記:社会/しゃかい><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始>
++ 14D <rel type="修飾" target="社会" sid="w201106-0000060877-1" id="12"/><BGH:福祉/ふくし><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:福祉/ふくし>
+福祉 ふくし 福祉 名詞 6 普通名詞 1 * 0 * 0 "代表表記:福祉/ふくし カテゴリ:抽象物 ドメイン:家庭・暮らし;教育・学習;政治" <代表表記:福祉/ふくし><カテゴリ:抽象物><ドメイン:家庭・暮らし;教育・学習;政治><正規化代表表記:福祉/ふくし><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始>
++ 16D <rel type="修飾" target="福祉" sid="w201106-0000060877-1" id="13"/><SM-主体><SM-場所><SM-組織><BGH:社会/しゃかい+福祉/ふくし+法人/ほうじん><解析済><助詞><連体修飾><体言><係:ノ格><区切:0-4><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:法人/ほうじん><主辞代表表記:法人/ほうじん><Wikipedia上位語:法人/ほうじん><Wikipediaエントリ:社会福祉法人>
+法人 ほうじん 法人 名詞 6 普通名詞 1 * 0 * 0 "代表表記:法人/ほうじん カテゴリ:組織・団体 ドメイン:ビジネス;政治" <代表表記:法人/ほうじん><カテゴリ:組織・団体><ドメイン:ビジネス;政治><正規化代表表記:法人/ほうじん><Wikipedia上位語:法人/ほうじん:25-27><Wikipediaエントリ:社会福祉法人:25-27><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+等 とう 等 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:等/とう" <代表表記:等/とう><正規化代表表記:等/とう><漢字><かな漢字><名詞相当語><付属>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 9D <解析済><SM-主体><SM-場所><SM-組織><BGH:所/しょ><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:事業/じぎょう+所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ>
++ 16D <BGH:事業/じぎょう><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:事業/じぎょう>
+事業 じぎょう 事業 名詞 6 普通名詞 1 * 0 * 0 "代表表記:事業/じぎょう カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:事業/じぎょう><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:事業/じぎょう><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始>
++ 17D <rel type="ノ" target="事業" sid="w201106-0000060877-1" id="15"/><rel type="修飾" target="法人等" sid="w201106-0000060877-1" id="14"/><SM-主体><SM-場所><SM-組織><BGH:所/しょ><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><一文字漢字><名詞項候補><先行詞候補><正規化代表表記:所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ><Wikipediaエントリ:事業所><Wikipediaリダイレクト:営業所>
+所 しょ 所 名詞 6 普通名詞 1 * 0 * 0 "代表表記:所/しょ 漢字読み:音 カテゴリ:場所-施設" <代表表記:所/しょ><漢字読み:音><カテゴリ:場所-施設><正規化代表表記:所/しょ><Wikipediaエントリ:事業所:30-31><Wikipediaリダイレクト:営業所:30-31><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:募集/ぼしゅう+する/する><文末><サ変><サ変動詞><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:丁寧表現><正規化代表表記:募集/ぼしゅう><主辞代表表記:募集/ぼしゅう>
++ -1D <rel type="ヲ" target="所" sid="w201106-0000060877-1" id="16"/><rel type="ガ" target="柏市" sid="w201106-0000060877-1" id="4"/><BGH:募集/ぼしゅう+する/する><解析済><文末><サ変動詞><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:丁寧表現><サ変><正規化代表表記:募集/ぼしゅう><主辞代表表記:募集/ぼしゅう><用言代表表記:募集/ぼしゅう><節-区切><節-主辞><時制:非過去><主題格:一人称優位>
+募集 ぼしゅう 募集 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:募集/ぼしゅう カテゴリ:抽象物 反義:名詞-サ変名詞:応募/おうぼ" <代表表記:募集/ぼしゅう><カテゴリ:抽象物><反義:名詞-サ変名詞:応募/おうぼ><正規化代表表記:募集/ぼしゅう><漢字><かな漢字><名詞相当語><サ変><サ変動詞><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+し し する 動詞 2 * 0 サ変動詞 16 基本連用形 8 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><とタ系連用テ形複合辞><付属>
+ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><表現文末><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
+# S-ID:w201106-0000060877-2 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-43.57695 MOD:2014/05/08 MEMO:
+* 1D <解析済><SM-主体><SM-場所><SM-組織><BGH:所/しょ><文頭><ニ><助詞><体言><係:ニ格><区切:0-0><隣係絶対><格要素><連用要素><正規化代表表記:民営/みんえい+事業/じぎょう+所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ>
++ 1D <BGH:民営/みんえい><文節内><係:文節内><文頭><体言><名詞項候補><先行詞候補><正規化代表表記:民営/みんえい>
+民営 みんえい 民営 名詞 6 普通名詞 1 * 0 * 0 "代表表記:民営/みんえい カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:民営/みんえい><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:民営/みんえい><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始>
++ 2D <BGH:事業/じぎょう><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:事業/じぎょう>
+事業 じぎょう 事業 名詞 6 普通名詞 1 * 0 * 0 "代表表記:事業/じぎょう カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:事業/じぎょう><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:事業/じぎょう><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始>
++ 3D <rel type="修飾" target="民営" sid="w201106-0000060877-2" id="0"/><rel type="ノ" target="事業" sid="w201106-0000060877-2" id="1"/><rel type="=≒" target="所" sid="w201106-0000060877-1" id="16"/><SM-主体><SM-場所><SM-組織><BGH:所/しょ><解析済><ニ><助詞><体言><係:ニ格><区切:0-0><隣係絶対><格要素><連用要素><一文字漢字><名詞項候補><先行詞候補><正規化代表表記:所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ><Wikipediaエントリ:事業所><Wikipediaリダイレクト:営業所>
+所 しょ 所 名詞 6 普通名詞 1 * 0 * 0 "代表表記:所/しょ 漢字読み:音 カテゴリ:場所-施設" <代表表記:所/しょ><漢字読み:音><カテゴリ:場所-施設><正規化代表表記:所/しょ><Wikipediaエントリ:事業所:1-2><Wikipediaリダイレクト:営業所:1-2><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+に に に 助詞 9 格助詞 1 * 0 * 0 "代表表記:に/に" <代表表記:に/に><正規化代表表記:に/に><かな漢字><ひらがな><付属>
+* 5D <解析済><BGH:置く/おく><ハ><読点><助詞><用言:動><受:隣のみ><隣受絶対><複合辞><係:複合辞連用><レベル:A-><並キ:？:&ST:3.0&&&自立語一致><区切:0-3><ID:〜において><機能的基本句><動態述語><正規化代表表記:置く/おく><主辞代表表記:置く/おく><並列類似度:-100.000>
++ 10D <BGH:置く/おく><解析済><ハ><読点><助詞><用言:動><受:隣のみ><隣受絶対><複合辞><係:複合辞連用><レベル:A-><並キ:？:&ST:3.0&&&自立語一致><区切:0-3><ID:〜において><機能的基本句><動態述語><格解析なし><省略解析なし><格要素表記直前参照><正規化代表表記:置く/おく><主辞代表表記:置く/おく>
+おいて おいて おく 動詞 2 * 0 子音動詞カ行 2 タ系連用テ形 14 "代表表記:置く/おく" <代表表記:置く/おく><正規化代表表記:置く/おく><かな漢字><ひらがな><活用語><に基本連用形複合辞><にタ系連用テ形複合辞><自立><内容語><タグ単位始><文節始><文節主辞>
+は は は 助詞 9 副助詞 2 * 0 * 0 "代表表記:は/は" <代表表記:は/は><正規化代表表記:は/は><かな漢字><ひらがな><付属>
+、 、 、 特殊 1 読点 2 * 0 * 0 "代表表記:、/、" <代表表記:、/、><正規化代表表記:、/、><英記号><記号><述語区切><付属>
+* 3D <解析済><SM-主体><SM-場所><SM-組織><BGH:所/しょ><ガ><助詞><体言><係:ガ格><区切:0-0><格要素><連用要素><正規化代表表記:事業/じぎょう+所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ>
++ 5D <BGH:事業/じぎょう><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:事業/じぎょう>
+事業 じぎょう 事業 名詞 6 普通名詞 1 * 0 * 0 "代表表記:事業/じぎょう カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:事業/じぎょう><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:事業/じぎょう><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始>
++ 6D <rel type="=" target="所" sid="w201106-0000060877-2" id="2"/><SM-主体><SM-場所><SM-組織><BGH:所/しょ><解析済><ガ><助詞><体言><係:ガ格><区切:0-0><格要素><連用要素><一文字漢字><名詞項候補><先行詞候補><正規化代表表記:所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ><Wikipediaエントリ:事業所><Wikipediaリダイレクト:営業所>
+所 しょ 所 名詞 6 普通名詞 1 * 0 * 0 "代表表記:所/しょ 漢字読み:音 カテゴリ:場所-施設" <代表表記:所/しょ><漢字読み:音><カテゴリ:場所-施設><正規化代表表記:所/しょ><Wikipediaエントリ:事業所:7-8><Wikipediaリダイレクト:営業所:7-8><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+が が が 助詞 9 格助詞 1 * 0 * 0 "代表表記:が/が" <代表表記:が/が><正規化代表表記:が/が><かな漢字><ひらがな><付属>
+* 4D <解析済><BGH:選択/せんたく+する/する><サ変><サ変動詞><時制:過去><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><正規化代表表記:選択/せんたく><主辞代表表記:選択/せんたく>
++ 9D <rel type="ガ" target="所" sid="w201106-0000060877-2" id="5"/><rel type="ヲ" target="事業" sid="w201106-0000060877-2" id="9"/><BGH:選択/せんたく+する/する><解析済><サ変動詞><時制:過去><連体修飾><用言:動><係:連格><レベル:B><区切:0-5><ID:（動詞連体）><連体節><動態述語><サ変><正規化代表表記:選択/せんたく><主辞代表表記:選択/せんたく><用言代表表記:選択/せんたく><節-区切:連体修飾><節-主辞>
+選択 せんたく 選択 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:選択/せんたく カテゴリ:抽象物" <代表表記:選択/せんたく><カテゴリ:抽象物><正規化代表表記:選択/せんたく><漢字><かな漢字><名詞相当語><サ変><サ変動詞><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+した した する 動詞 2 * 0 サ変動詞 16 タ形 10 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><とタ系連用テ形複合辞><付属>
+* 5D <解析済><BGH:事業/じぎょう><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:障害/しょうがい+福祉/ふくし+事業/じぎょう><主辞代表表記:事業/じぎょう>
++ 8D <SM-動作><BGH:障害/しょうがい><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:障害/しょうがい>
+障害 しょうがい 障害 名詞 6 普通名詞 1 * 0 * 0 "代表表記:障害/しょうがい カテゴリ:抽象物" <代表表記:障害/しょうがい><カテゴリ:抽象物><正規化代表表記:障害/しょうがい><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始>
++ 9D <rel type="修飾" target="障害" sid="w201106-0000060877-2" id="7"/><BGH:福祉/ふくし><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:福祉/ふくし>
+福祉 ふくし 福祉 名詞 6 普通名詞 1 * 0 * 0 "代表表記:福祉/ふくし カテゴリ:抽象物 ドメイン:家庭・暮らし;教育・学習;政治" <代表表記:福祉/ふくし><カテゴリ:抽象物><ドメイン:家庭・暮らし;教育・学習;政治><正規化代表表記:福祉/ふくし><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始>
++ 10D <rel type="ノ？" target="福祉" sid="w201106-0000060877-2" id="8"/><BGH:事業/じぎょう><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:事業/じぎょう><主辞代表表記:事業/じぎょう>
+事業 じぎょう 事業 名詞 6 普通名詞 1 * 0 * 0 "代表表記:事業/じぎょう カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:事業/じぎょう><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:事業/じぎょう><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:行う/おこなう><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:尊敬表現><正規化代表表記:行う/おこなう><主辞代表表記:行う/おこなう>
++ -1D <rel type="ヲ" target="事業" sid="w201106-0000060877-2" id="9"/><rel type="ニオイテ" target="所" sid="w201106-0000060877-2" id="2"/><rel type="ガ" target="柏市" sid="w201106-0000060877-1" id="4"/><rel type="ニ" target="所" sid="w201106-0000060877-2" id="2"/><BGH:行う/おこなう><解析済><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:尊敬表現><正規化代表表記:行う/おこなう><主辞代表表記:行う/おこなう><用言代表表記:行う/おこなう~テ形+頂く/いただく><節-区切><節-主辞><時制:非過去><主題格:一人称優位>
+行って おこなって 行う 動詞 2 * 0 子音動詞ワ行 12 タ系連用テ形 14 "代表表記:行う/おこなう" <代表表記:行う/おこなう><正規化代表表記:行う/おこなう><かな漢字><活用語><自立><内容語><タグ単位始><文節始><用言見出接辞:テ形><文節主辞><用言表記先頭>
+いただき いただき いただく 動詞 2 * 0 子音動詞カ行 2 基本連用形 8 "代表表記:頂く/いただく 付属動詞候補（タ系） 謙譲動詞:貰う/もらう;食べる/たべる;飲む/のむ" <代表表記:頂く/いただく><付属動詞候補（タ系）><謙譲動詞:貰う/もらう;食べる/たべる;飲む/のむ><正規化代表表記:頂く/いただく><かな漢字><ひらがな><活用語><付属><用言表記末尾><用言意味表記末尾>
+ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><表現文末><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
+# S-ID:w201106-0000060877-3 JUMAN:6.1-20101108 KNP:3.1-20101107 DATE:2011/06/21 SCORE:-89.30756 MOD:2011/07/11
+* 2D <解析済><SM-主体><SM-人><BGH:障害/しょうがい+者/しゃ><文頭><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:障害/しょうがい+者/しゃ><主辞代表表記:者/しゃ><主辞’代表表記:障害/しょうがい+者/しゃ>
++ 1D <rel type="=" target="障害" sid="w201106-0000060877-2" id="7"/><SM-動作><BGH:障害/しょうがい><文節内><係:文節内><文頭><体言><名詞項候補><先行詞候補><正規化代表表記:障害/しょうがい>
+障害 しょうがい 障害 名詞 6 普通名詞 1 * 0 * 0 "代表表記:障害/しょうがい カテゴリ:抽象物" <代表表記:障害/しょうがい><カテゴリ:抽象物><正規化代表表記:障害/しょうがい><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始>
++ 4D <rel type="ノ？" target="障害" sid="w201106-0000060877-3" id="0"/><解析済><SM-主体><SM-人><BGH:障害/しょうがい+者/しゃ><助詞><連体修飾><体言><係:ノ格><区切:0-4><一文字漢字><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:者/しゃ><主辞代表表記:者/しゃ><主辞’代表表記:障害/しょうがい+者/しゃ><Wikipedia上位語:人><Wikipediaエントリ:障がい者><Wikipediaリダイレクト:障害者>
+者 しゃ 者 接尾辞 14 名詞性名詞接尾辞 2 * 0 * 0 "代表表記:者/しゃ 内容語 カテゴリ:人" <代表表記:者/しゃ><内容語><カテゴリ:人><正規化代表表記:者/しゃ><Wikipedia上位語:人:0-1><Wikipediaエントリ:障がい者:0-1><Wikipediaリダイレクト:障害者:0-1><漢字><かな漢字><名詞相当語><付属><タグ単位始><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 2P <解析済><BGH:自立/じりつ><サ変><ト><助詞><体言><係:ト格><並キ:名><区切:1-2><並列タイプ:AND><格要素><連用要素><正規化代表表記:自立/じりつ><主辞代表表記:自立/じりつ><並列類似度:2.516>
++ 4P <rel type="ガ" target="者" sid="w201106-0000060877-3" id="1"/><BGH:自立/じりつ><解析済><ト><助詞><体言><係:ト格><並キ:名><区切:1-2><並列タイプ:AND><格要素><連用要素><サ変><名詞項候補><先行詞候補><非用言格解析:動><照応ヒント:係><態:未定><正規化代表表記:自立/じりつ><主辞代表表記:自立/じりつ>
+自立 じりつ 自立 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:自立/じりつ カテゴリ:抽象物" <代表表記:自立/じりつ><カテゴリ:抽象物><正規化代表表記:自立/じりつ><漢字><かな漢字><名詞相当語><サ変><自立><内容語><タグ単位始><文節始><文節主辞>
+と と と 助詞 9 格助詞 1 * 0 * 0 "代表表記:と/と" <代表表記:と/と><正規化代表表記:と/と><かな漢字><ひらがな><付属>
+* 4D <解析済><BGH:参加/さんか><サ変><ニ><読点><助詞><体言><係:ニ格><並キ:名:&ST:4.0&&ニ><区切:0-4><読点並キ><並列タイプ:？><格要素><連用要素><正規化代表表記:社会/しゃかい+参加/さんか><主辞代表表記:参加/さんか><並列類似度:-100.000>
++ 4D <rel type="=" target="社会" sid="w201106-0000060877-1" id="12"/><SM-主体><SM-場所><BGH:社会/しゃかい><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:社会/しゃかい>
+社会 しゃかい 社会 名詞 6 普通名詞 1 * 0 * 0 "代表表記:社会/しゃかい カテゴリ:場所-その他 ドメイン:教育・学習;政治" <代表表記:社会/しゃかい><カテゴリ:場所-その他><ドメイン:教育・学習;政治><正規化代表表記:社会/しゃかい><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始>
++ 6D <rel type="ガ" target="者" sid="w201106-0000060877-3" id="1"/><rel type="ニ" target="社会" sid="w201106-0000060877-3" id="3"/><BGH:参加/さんか><解析済><ニ><読点><助詞><体言><係:ニ格><並キ:名:&ST:4.0&&ニ><区切:0-4><読点並キ><並列タイプ:？><格要素><連用要素><サ変><名詞項候補><先行詞候補><非用言格解析:動><照応ヒント:係><態:未定><正規化代表表記:参加/さんか><主辞代表表記:参加/さんか><Wikipedia上位語:担う/になう><Wikipediaエントリ:社会参加>
+参加 さんか 参加 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:参加/さんか カテゴリ:抽象物" <代表表記:参加/さんか><カテゴリ:抽象物><正規化代表表記:参加/さんか><Wikipedia上位語:担う/になう:5-6><Wikipediaエントリ:社会参加:5-6><漢字><かな漢字><名詞相当語><サ変><自立><複合←><内容語><タグ単位始><文節主辞>
+に に に 助詞 9 格助詞 1 * 0 * 0 "代表表記:に/に" <代表表記:に/に><正規化代表表記:に/に><かな漢字><ひらがな><付属>
+、 、 、 特殊 1 読点 2 * 0 * 0 "代表表記:、/、" <代表表記:、/、><正規化代表表記:、/、><英記号><記号><述語区切><付属>
+* 4D <解析済><BGH:意欲/いよく><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:意欲/いよく><主辞代表表記:意欲/いよく>
++ 6D <BGH:意欲/いよく><解析済><助詞><連体修飾><体言><係:ノ格><区切:0-4><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:意欲/いよく><主辞代表表記:意欲/いよく>
+意欲 いよく 意欲 名詞 6 普通名詞 1 * 0 * 0 "代表表記:意欲/いよく カテゴリ:抽象物" <代表表記:意欲/いよく><カテゴリ:抽象物><正規化代表表記:意欲/いよく><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+の の の 助詞 9 格助詞 1 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 5D <解析済><BGH:有る/ある><補文ト><連体修飾><用言:動><係:連格><レベル:B-><区切:0-5><ID:（〜の＋動詞連体）><連体節><状態述語><正規化代表表記:有る/ある><主辞代表表記:有る/ある>
++ 8D <rel type="ガ" target="意欲" sid="w201106-0000060877-3" id="5"/><rel type="ガ２" target="所" sid="w201106-0000060877-3" id="8"/><rel type="ニ" target="参加" sid="w201106-0000060877-3" id="4"/><rel type="ニ" mode="AND" target="自立" sid="w201106-0000060877-3" id="2"/><BGH:有る/ある><解析済><補文ト><連体修飾><用言:動><係:連格><レベル:B-><区切:0-5><ID:（〜の＋動詞連体）><連体節><状態述語><正規化代表表記:有る/ある><主辞代表表記:有る/ある><用言代表表記:有る/ある><節-区切:連体修飾><節-主辞><時制:非過去><クエリ削除語>
+ある ある ある 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:有る/ある 補文ト 反義:形容詞:無い/ない" <代表表記:有る/ある><補文ト><反義:形容詞:無い/ない><正規化代表表記:有る/ある><かな漢字><ひらがな><活用語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+* 6D <解析済><SM-主体><SM-場所><SM-組織><BGH:所/しょ><助詞><連体修飾><体言><係:ノ格><区切:0-4><正規化代表表記:事業/じぎょう+所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ>
++ 8D <BGH:事業/じぎょう><文節内><係:文節内><体言><名詞項候補><先行詞候補><正規化代表表記:事業/じぎょう>
+事業 じぎょう 事業 名詞 6 普通名詞 1 * 0 * 0 "代表表記:事業/じぎょう カテゴリ:抽象物 ドメイン:ビジネス;政治" <代表表記:事業/じぎょう><カテゴリ:抽象物><ドメイン:ビジネス;政治><正規化代表表記:事業/じぎょう><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始>
++ 9D <rel type="=" target="所" sid="w201106-0000060877-2" id="5"/><SM-主体><SM-場所><SM-組織><BGH:所/しょ><解析済><助詞><連体修飾><体言><係:ノ格><区切:0-4><一文字漢字><名詞項候補><先行詞候補><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:所/しょ><主辞代表表記:所/しょ><主辞’代表表記:事業/じぎょう+所/しょ><Wikipediaエントリ:事業所><Wikipediaリダイレクト:営業所>
+所 しょ 所 名詞 6 普通名詞 1 * 0 * 0 "代表表記:所/しょ 漢字読み:音 カテゴリ:場所-施設" <代表表記:所/しょ><漢字読み:音><カテゴリ:場所-施設><正規化代表表記:所/しょ><判定詞化名詞:13-13><Wikipediaエントリ:事業所:12-13><Wikipediaリダイレクト:営業所:12-13><漢字><かな漢字><名詞相当語><自立><複合←><内容語><タグ単位始><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 "代表表記:の/の" <代表表記:の/の><正規化代表表記:の/の><かな漢字><ひらがな><付属>
+* 7D <解析済><BGH:応募/おうぼ><サ変><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:応募/おうぼ><主辞代表表記:応募/おうぼ>
++ 10D <rel type="ガ" target="所" sid="w201106-0000060877-3" id="8"/><rel type="ニ" target="募集" sid="w201106-0000060877-1" id="17"/><BGH:応募/おうぼ><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><サ変><名詞項候補><先行詞候補><非用言格解析:動><強サ変><照応ヒント:係><態:未定><正規化代表表記:応募/おうぼ><主辞代表表記:応募/おうぼ>
+応募 おうぼ 応募 名詞 6 サ変名詞 2 * 0 * 0 "代表表記:応募/おうぼ カテゴリ:抽象物 反義:名詞-サ変名詞:募集/ぼしゅう" <代表表記:応募/おうぼ><カテゴリ:抽象物><反義:名詞-サ変名詞:募集/ぼしゅう><正規化代表表記:応募/おうぼ><漢字><かな漢字><名詞相当語><サ変><自立><内容語><タグ単位始><文節始><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 "代表表記:を/を" <代表表記:を/を><正規化代表表記:を/を><かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:する/する><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:謙譲表現><正規化代表表記:待つ/まつ><主辞代表表記:待つ/まつ>
++ -1D <rel type="ヲ" target="応募" sid="w201106-0000060877-3" id="9"/><rel type="ガ" target="柏市" sid="w201106-0000060877-1" id="4"/><BGH:する/する><解析済><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><敬語:謙譲表現><正規化代表表記:待つ/まつ><主辞代表表記:待つ/まつ><用言代表表記:待つ/まつ+する/する~テ形+いる/いる><節-区切><節-主辞><時制:非過去><主題格:一人称優位>
+お お お 接頭辞 13 名詞接頭辞 1 * 0 * 0 "代表表記:御/お" <代表表記:御/お><正規化代表表記:御/お><かな漢字><ひらがな><接頭><非独立接頭辞><タグ単位始><文節始>
+待ち まち 待つ 動詞 2 * 0 子音動詞タ行 6 基本連用形 8 "代表表記:待つ/まつ" <代表表記:待つ/まつ><正規化代表表記:待つ/まつ><かな漢字><活用語><自立><内容語><文節主辞><用言表記先頭>
+して して する 動詞 2 * 0 サ変動詞 16 タ系連用テ形 14 "代表表記:する/する 付属動詞候補（基本） 自他動詞:自:成る/なる" <代表表記:する/する><付属動詞候補（基本）><自他動詞:自:成る/なる><正規化代表表記:する/する><かな漢字><ひらがな><活用語><とタ系連用テ形複合辞><付属><用言見出接辞:テ形>
+い い いる 接尾辞 14 動詞性接尾辞 7 母音動詞 1 基本連用形 8 "代表表記:いる/いる" <代表表記:いる/いる><正規化代表表記:いる/いる><かな漢字><ひらがな><活用語><付属><用言表記末尾><用言意味表記末尾>
+ます ます ます 接尾辞 14 動詞性接尾辞 7 動詞性接尾辞ます型 31 基本形 2 "代表表記:ます/ます" <代表表記:ます/ます><正規化代表表記:ます/ます><かな漢字><ひらがな><活用語><表現文末><付属>
+。 。 。 特殊 1 句点 1 * 0 * 0 "代表表記:。/。" <代表表記:。/。><正規化代表表記:。/。><英記号><記号><文末><付属>
+EOS
diff --git a/tests/data/system/0.knp b/tests/data/system/0.knp
new file mode 100644
index 00000000..b0160a4b
--- /dev/null
+++ b/tests/data/system/0.knp
@@ -0,0 +1,28 @@
+# S-ID:0-1 KNP:5.0-2ad4f6d DATE:2020/12/19 SCORE:60.00000
+* 2D <解析済><文頭><人名><ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><正規化代表表記:太郎/たろう><主辞代表表記:太郎/たろう>
++ 2D <解析済><文頭><人名><ハ><助詞><体言><係:未格><提題><区切:3-5><主題表現><格要素><連用要素><名詞項候補><先行詞候補><SM-人><SM-主体><正規化代表表記:太郎/たろう><主辞代表表記:太郎/たろう><解析格:ガ>
+太郎 たろう 太郎 名詞 6 人名 5 * 0 * 0 "人名:日本:名:45:0.00106 疑似代表表記 代表表記:太郎/たろう" <人名:日本:名:45:0.00106><疑似代表表記><代表表記:太郎/たろう><正規化代表表記:太郎/たろう><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><固有キー><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+は は は 助詞 9 副助詞 2 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* 2D <解析済><BGH:パン/ぱん><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><正規化代表表記:パン/ぱん><主辞代表表記:パン/ぱん>
++ 2D <rel type="=" target="太郎" sid="0-1" id="0"/><BGH:パン/ぱん><解析済><ヲ><助詞><体言><係:ヲ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:パン/ぱん><主辞代表表記:パン/ぱん><解析格:ヲ><標準用言代表表記:パン/ぱん>
+パン ぱん パン 名詞 6 普通名詞 1 * 0 * 0 "代表表記:パン/ぱん ドメイン:料理・食事 カテゴリ:人工物-食べ物" <代表表記:パン/ぱん><ドメイン:料理・食事><カテゴリ:人工物-食べ物><正規化代表表記:パン/ぱん><記英数カ><カタカナ><名詞相当語><自立><内容語><タグ単位始><文節始><固有キー><文節主辞>
+を を を 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:買う/かう><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:買う/かう><主辞代表表記:買う/かう>
++ -1D <rel type="ガ" target="太郎" sid="0-1" id="0"/><BGH:買う/かう><解析済><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:買う/かう><主辞代表表記:買う/かう><用言代表表記:買う/かう><格フレーム決定><節-区切><節-主辞><主題格:一人称優位><格関係0:ガ:太郎><格関係1:ヲ:パン><標準用言代表表記:買う/かう>
+買った かった 買う 動詞 2 * 0 子音動詞ワ行 12 タ形 10 "代表表記:買う/かう ドメイン:家庭・暮らし;ビジネス 反義:動詞:売る/うる" <代表表記:買う/かう><ドメイン:家庭・暮らし;ビジネス><反義:動詞:売る/うる><正規化代表表記:買う/かう><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
+EOS
+# S-ID:0-2 KNP:5.0-2ad4f6d DATE:2020/12/19 SCORE:10.00000
+* 1D <解析済><SM-主体><SM-人><BGH:彼/かれ><文頭><助詞><連体修飾><体言><一文字漢字><係:ノ格><区切:0-4><正規化代表表記:彼/かれ><主辞代表表記:彼/かれ>
++ 1D <rel type="=" target="著者"/><SM-主体><SM-人><BGH:彼/かれ><解析済><文頭><助詞><連体修飾><体言><一文字漢字><係:ノ格><区切:0-4><名詞項候補><先行詞候補><人称代名詞><係チ:非用言格解析||用言&&文節内:Ｔ解析格-ヲ><正規化代表表記:彼/かれ><主辞代表表記:彼/かれ>
+彼 かれ 彼 名詞 6 普通名詞 1 * 0 * 0 "代表表記:彼/かれ カテゴリ:人 漢字読み:訓" <代表表記:彼/かれ><カテゴリ:人><漢字読み:訓><正規化代表表記:彼/かれ><判定詞化名詞:0-0><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><文節主辞>
+の の の 助詞 9 接続助詞 3 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* 2D <解析済><SM-主体><SM-人><SM-場所><BGH:家/いえ><SM-組織><デ><助詞><体言><一文字漢字><係:デ格><区切:0-0><格要素><連用要素><正規化代表表記:家/いえ><主辞代表表記:家/いえ>
++ 2D <rel type="ノ" target="彼" sid="0-2" id="0"/><SM-主体><SM-人><SM-場所><BGH:家/いえ><解析済><SM-組織><デ><助詞><体言><一文字漢字><係:デ格><区切:0-0><格要素><連用要素><名詞項候補><先行詞候補><正規化代表表記:家/いえ><主辞代表表記:家/いえ><解析格:デ><標準用言代表表記:家/いえ>
+家 いえ 家 名詞 6 普通名詞 1 * 0 * 0 "代表表記:家/いえ ドメイン:家庭・暮らし カテゴリ:組織・団体;場所-施設 漢字読み:訓" <代表表記:家/いえ><ドメイン:家庭・暮らし><カテゴリ:組織・団体;場所-施設><漢字読み:訓><正規化代表表記:家/いえ><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞>
+で で で 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
+* -1D <解析済><BGH:食べる/たべる><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:食べる/たべる><主辞代表表記:食べる/たべる>
++ -1D <rel type="ガ" target="彼" sid="0-2" id="0"/><rel type="ヲ" target="パン" sid="0-1" id="1"/><BGH:食べる/たべる><解析済><文末><時制:過去><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:食べる/たべる><主辞代表表記:食べる/たべる><用言代表表記:食べる/たべる><節-区切><節-主辞><主題格:一人称優位><格関係1:デ:家><標準用言代表表記:食べる/たべる>
+食べた たべた 食べる 動詞 2 * 0 母音動詞 1 タ形 10 "代表表記:食べる/たべる ドメイン:料理・食事" <代表表記:食べる/たべる><ドメイン:料理・食事><正規化代表表記:食べる/たべる><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
+。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
+EOS
diff --git a/tests/datamodule/datasets/knp_files/000.knp b/tests/datamodule/datasets/knp_files/000.knp
index 3f9f042f..2d89d386 100644
--- a/tests/datamodule/datasets/knp_files/000.knp
+++ b/tests/datamodule/datasets/knp_files/000.knp
@@ -4,7 +4,7 @@
 風 かぜ 風 名詞 6 普通名詞 1 * 0 * 0 "代表表記:風/かぜ カテゴリ:抽象物 漢字読み:訓" <代表表記:風/かぜ><カテゴリ:抽象物><漢字読み:訓><正規化代表表記:風/かぜ><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><文節主辞>
 が が が 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
 * -1D <BGH:吹く/ふく><文末><補文ト><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:吹く/ふく><主辞代表表記:吹く/ふく>
-+ -1D <BGH:吹く/ふく><文末><補文ト><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:吹く/ふく><主辞代表表記:吹く/ふく><用言代表表記:吹く/ふく><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:ガ:風><格解析結果:吹く/ふく:動1:ガ/C/風/0/0/1;ニ/U/-/-/-/-;ト/U/-/-/-/-;デ/U/-/-/-/-;カラ/U/-/-/-/-;時間/U/-/-/-/-><標準用言代表表記:吹く/ふく>
++ -1D <BGH:吹く/ふく><文末><補文ト><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:吹く/ふく><主辞代表表記:吹く/ふく><用言代表表記:吹く/ふく><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:ガ:風><格解析結果:吹く/ふく:動1:ガ/C/風/0/0/000-1;ニ/U/-/-/-/-;ト/U/-/-/-/-;デ/U/-/-/-/-;カラ/U/-/-/-/-;時間/U/-/-/-/-><標準用言代表表記:吹く/ふく>
 吹く ふく 吹く 動詞 2 * 0 子音動詞カ行 2 基本形 2 "代表表記:吹く/ふく 補文ト" <代表表記:吹く/ふく><補文ト><正規化代表表記:吹く/ふく><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
 。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
 EOS
@@ -17,7 +17,7 @@ EOS
 桶屋 桶屋 桶屋 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:Wikipedia 読み不明 Wikipediaリダイレクト:桶 疑似代表表記 代表表記:桶屋/桶屋" <自動獲得:Wikipedia><読み不明><Wikipediaリダイレクト:桶><疑似代表表記><代表表記:桶屋/桶屋><正規化代表表記:桶屋/桶屋><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
 が が が 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
 * -1D <BGH:儲かる/もうかる><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:儲かる/もうかる><主辞代表表記:儲かる/もうかる>
-+ -1D <BGH:儲かる/もうかる><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:儲かる/もうかる><主辞代表表記:儲かる/もうかる><用言代表表記:儲かる/もうかる><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:修飾:すると><格関係1:ガ:桶屋><格解析結果:儲かる/もうかる:動2:ガ/C/桶屋/1/0/2;修飾/C/すると/0/0/2><標準用言代表表記:儲かる/もうかる>
++ -1D <BGH:儲かる/もうかる><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:儲かる/もうかる><主辞代表表記:儲かる/もうかる><用言代表表記:儲かる/もうかる><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:修飾:すると><格関係1:ガ:桶屋><格解析結果:儲かる/もうかる:動2:ガ/C/桶屋/1/0/000-2;修飾/C/すると/0/0/000-2><標準用言代表表記:儲かる/もうかる>
 儲かる もうかる 儲かる 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:儲かる/もうかる ドメイン:ビジネス 自他動詞:他:儲ける/もうける" <代表表記:儲かる/もうかる><ドメイン:ビジネス><自他動詞:他:儲ける/もうける><正規化代表表記:儲かる/もうかる><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
 。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
 EOS
diff --git a/tests/datamodule/datasets/test_word_dataset.py b/tests/datamodule/datasets/test_word_dataset.py
index 6f161075..10119de3 100644
--- a/tests/datamodule/datasets/test_word_dataset.py
+++ b/tests/datamodule/datasets/test_word_dataset.py
@@ -1,9 +1,12 @@
+import json
 import textwrap
 from pathlib import Path
 
 from rhoknp import Document
 
 from jula.datamodule.datasets.word_dataset import WordDataset
+from jula.datamodule.examples import Task
+from jula.datamodule.extractors import PasAnnotation
 from jula.utils.utils import (
     BASE_PHRASE_FEATURES,
     CONJFORM_TYPES,
@@ -18,10 +21,18 @@
 
 here = Path(__file__).absolute().parent
 path = here.joinpath("knp_files")
+data_dir = here.parent.parent / "data"
+
+word_dataset_kwargs = {
+    "cases": ["ガ", "ヲ", "ニ", "ガ２"],
+    "bar_rels": ["ノ", "ノ？"],
+    "exophora_referents": ["著者", "読者", "不特定:人", "不特定:物"],
+    "cohesion_tasks": ["pas_analysis", "bridging", "coreference"],
+}
 
 
 def test_init():
-    _ = WordDataset(str(path))
+    _ = WordDataset(str(path), **word_dataset_kwargs)
 
 
 def test_getitem():
@@ -29,36 +40,34 @@ def test_getitem():
     dataset = WordDataset(
         str(path),
         max_seq_length=max_seq_length,
-        tokenizer_kwargs={"additional_special_tokens": ["[ROOT]"]},
+        **word_dataset_kwargs,
     )
     for i in range(len(dataset)):
         document = dataset.documents[i]
         item = dataset[i]
         assert isinstance(item, dict)
-        assert "document_id" in item
+        assert "example_ids" in item
         assert "input_ids" in item
         assert "attention_mask" in item
         assert "subword_map" in item
         assert "mrph_types" in item
         assert "word_features" in item
         assert "base_phrase_features" in item
-        assert "num_base_phrases" in item
         assert "dependencies" in item
         assert "intra_mask" in item
         assert "dependency_types" in item
         assert "discourse_relations" in item
-        assert item["document_id"] == i
+        assert item["example_ids"] == i
         assert item["input_ids"].shape == (max_seq_length,)
         assert item["attention_mask"].shape == (max_seq_length,)
         assert item["subword_map"].shape == (max_seq_length, max_seq_length)
         assert (item["subword_map"].sum(dim=1) != 0).sum() == len(document.morphemes)
         assert item["mrph_types"].shape == (max_seq_length, 4)
+        assert item["word_features"].shape == (max_seq_length, len(WORD_FEATURES))
         assert item["base_phrase_features"].shape == (
             max_seq_length,
             len(BASE_PHRASE_FEATURES),
         )
-        assert item["num_base_phrases"].item() == len(document.base_phrases)
-        assert item["word_features"].shape == (max_seq_length, len(WORD_FEATURES))
         assert item["dependencies"].shape == (max_seq_length,)
         assert item["intra_mask"].shape == (max_seq_length, max_seq_length)
         assert item["dependency_types"].shape == (max_seq_length,)
@@ -74,7 +83,7 @@ def test_encode():
     dataset = WordDataset(
         str(path),
         max_seq_length=max_seq_length,
-        tokenizer_kwargs={"additional_special_tokens": ["[ROOT]"]},
+        **word_dataset_kwargs,
     )
     document = Document.from_knp(
         textwrap.dedent(
@@ -85,7 +94,7 @@ def test_encode():
             風 かぜ 風 名詞 6 普通名詞 1 * 0 * 0 "代表表記:風/かぜ カテゴリ:抽象物 漢字読み:訓" <代表表記:風/かぜ><カテゴリ:抽象物><漢字読み:訓><正規化代表表記:風/かぜ><漢字><かな漢字><名詞相当語><文頭><自立><内容語><タグ単位始><文節始><文節主辞>
             が が が 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
             * -1D <BGH:吹く/ふく><文末><補文ト><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:吹く/ふく><主辞代表表記:吹く/ふく>
-            + -1D <BGH:吹く/ふく><文末><補文ト><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:吹く/ふく><主辞代表表記:吹く/ふく><用言代表表記:吹く/ふく><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:ガ:風><格解析結果:吹く/ふく:動1:ガ/C/風/0/0/1;ニ/U/-/-/-/-;ト/U/-/-/-/-;デ/U/-/-/-/-;カラ/U/-/-/-/-;時間/U/-/-/-/-><標準用言代表表記:吹く/ふく>
+            + -1D <BGH:吹く/ふく><文末><補文ト><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:吹く/ふく><主辞代表表記:吹く/ふく><用言代表表記:吹く/ふく><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:ガ:風><格解析結果:吹く/ふく:動1:ガ/C/風/0/0/000-1;ニ/U/-/-/-/-;ト/U/-/-/-/-;デ/U/-/-/-/-;カラ/U/-/-/-/-;時間/U/-/-/-/-><標準用言代表表記:吹く/ふく>
             吹く ふく 吹く 動詞 2 * 0 子音動詞カ行 2 基本形 2 "代表表記:吹く/ふく 補文ト" <代表表記:吹く/ふく><補文ト><正規化代表表記:吹く/ふく><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
             。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
             EOS
@@ -98,14 +107,14 @@ def test_encode():
             桶屋 桶屋 桶屋 名詞 6 普通名詞 1 * 0 * 0 "自動獲得:Wikipedia 読み不明 Wikipediaリダイレクト:桶 疑似代表表記 代表表記:桶屋/桶屋" <自動獲得:Wikipedia><読み不明><Wikipediaリダイレクト:桶><疑似代表表記><代表表記:桶屋/桶屋><正規化代表表記:桶屋/桶屋><漢字><かな漢字><名詞相当語><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
             が が が 助詞 9 格助詞 1 * 0 * 0 NIL <かな漢字><ひらがな><付属>
             * -1D <BGH:儲かる/もうかる><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:儲かる/もうかる><主辞代表表記:儲かる/もうかる>
-            + -1D <BGH:儲かる/もうかる><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:儲かる/もうかる><主辞代表表記:儲かる/もうかる><用言代表表記:儲かる/もうかる><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:修飾:すると><格関係1:ガ:桶屋><格解析結果:儲かる/もうかる:動2:ガ/C/桶屋/1/0/2;修飾/C/すると/0/0/2><標準用言代表表記:儲かる/もうかる>
+            + -1D <BGH:儲かる/もうかる><文末><句点><用言:動><レベル:C><区切:5-5><ID:（文末）><係:文末><提題受:30><主節><格要素><連用要素><動態述語><正規化代表表記:儲かる/もうかる><主辞代表表記:儲かる/もうかる><用言代表表記:儲かる/もうかる><節-区切><節-主辞><時制:非過去><主題格:一人称優位><格関係0:修飾:すると><格関係1:ガ:桶屋><格解析結果:儲かる/もうかる:動2:ガ/C/桶屋/1/0/000-2;修飾/C/すると/0/0/000-2><標準用言代表表記:儲かる/もうかる>
             儲かる もうかる 儲かる 動詞 2 * 0 子音動詞ラ行 10 基本形 2 "代表表記:儲かる/もうかる ドメイン:ビジネス 自他動詞:他:儲ける/もうける" <代表表記:儲かる/もうかる><ドメイン:ビジネス><自他動詞:他:儲ける/もうける><正規化代表表記:儲かる/もうかる><かな漢字><活用語><表現文末><自立><内容語><タグ単位始><文節始><文節主辞><用言表記先頭><用言表記末尾><用言意味表記末尾>
             。 。 。 特殊 1 句点 1 * 0 * 0 NIL <英記号><記号><文末><付属>
             EOS
             """
         )
     )
-    encoding = dataset.encode(document)
+    encoding = dataset.encode(document, dataset.cohesion_examples["000"])
 
     mrph_types = [[IGNORE_INDEX] * 4 for _ in range(max_seq_length)]
     # 0: 風
@@ -155,55 +164,55 @@ def test_encode():
     mrph_types[8][3] = CONJFORM_TYPES.index("*")
     assert encoding["mrph_types"].tolist() == mrph_types
 
-    word_features = [[0.0] * len(WORD_FEATURES) for _ in range(max_seq_length)]
+    word_features = [[0] * len(WORD_FEATURES) for _ in range(max_seq_length)]
     # 0: 風
-    word_features[0][WORD_FEATURES.index("基本句-主辞")] = 1.0
+    word_features[0][WORD_FEATURES.index("基本句-主辞")] = 1
     # 1: が
-    word_features[1][WORD_FEATURES.index("基本句-区切")] = 1.0
-    word_features[1][WORD_FEATURES.index("文節-区切")] = 1.0
+    word_features[1][WORD_FEATURES.index("基本句-区切")] = 1
+    word_features[1][WORD_FEATURES.index("文節-区切")] = 1
     # 2: 吹く
-    word_features[2][WORD_FEATURES.index("基本句-主辞")] = 1.0
+    word_features[2][WORD_FEATURES.index("基本句-主辞")] = 1
     # 3: 。
-    word_features[3][WORD_FEATURES.index("基本句-区切")] = 1.0
-    word_features[3][WORD_FEATURES.index("文節-区切")] = 1.0
+    word_features[3][WORD_FEATURES.index("基本句-区切")] = 1
+    word_features[3][WORD_FEATURES.index("文節-区切")] = 1
     # 4: すると
-    word_features[4][WORD_FEATURES.index("基本句-主辞")] = 1.0
-    word_features[4][WORD_FEATURES.index("基本句-区切")] = 1.0
-    word_features[4][WORD_FEATURES.index("文節-区切")] = 1.0
+    word_features[4][WORD_FEATURES.index("基本句-主辞")] = 1
+    word_features[4][WORD_FEATURES.index("基本句-区切")] = 1
+    word_features[4][WORD_FEATURES.index("文節-区切")] = 1
     # 5: 桶屋
-    word_features[5][WORD_FEATURES.index("基本句-主辞")] = 1.0
+    word_features[5][WORD_FEATURES.index("基本句-主辞")] = 1
     # 6: が
-    word_features[6][WORD_FEATURES.index("基本句-区切")] = 1.0
-    word_features[6][WORD_FEATURES.index("文節-区切")] = 1.0
+    word_features[6][WORD_FEATURES.index("基本句-区切")] = 1
+    word_features[6][WORD_FEATURES.index("文節-区切")] = 1
     # 7: 儲かる
-    word_features[7][WORD_FEATURES.index("基本句-主辞")] = 1.0
+    word_features[7][WORD_FEATURES.index("基本句-主辞")] = 1
     # 8: 。
-    word_features[8][WORD_FEATURES.index("基本句-区切")] = 1.0
-    word_features[8][WORD_FEATURES.index("文節-区切")] = 1.0
+    word_features[8][WORD_FEATURES.index("基本句-区切")] = 1
+    word_features[8][WORD_FEATURES.index("文節-区切")] = 1
 
     base_phrase_head_indices = {
         base_phrase.head.global_index for base_phrase in document.base_phrases
     }
     base_phrase_features = [
-        [0.0] * len(BASE_PHRASE_FEATURES)
+        [0] * len(BASE_PHRASE_FEATURES)
         if morpheme_global_index in base_phrase_head_indices
-        else [float(IGNORE_INDEX)] * len(BASE_PHRASE_FEATURES)
+        else [IGNORE_INDEX] * len(BASE_PHRASE_FEATURES)
         for morpheme_global_index in range(max_seq_length)
     ]
     # 0: 風
-    base_phrase_features[0][BASE_PHRASE_FEATURES.index("体言")] = 1.0
+    base_phrase_features[0][BASE_PHRASE_FEATURES.index("体言")] = 1
     # 2: 吹く
-    base_phrase_features[2][BASE_PHRASE_FEATURES.index("用言:動")] = 1.0
-    base_phrase_features[2][BASE_PHRASE_FEATURES.index("節-区切")] = 1.0
-    base_phrase_features[2][BASE_PHRASE_FEATURES.index("節-主辞")] = 1.0
-    base_phrase_features[2][BASE_PHRASE_FEATURES.index("時制:非過去")] = 1.0
+    base_phrase_features[2][BASE_PHRASE_FEATURES.index("用言:動")] = 1
+    base_phrase_features[2][BASE_PHRASE_FEATURES.index("節-区切")] = 1
+    base_phrase_features[2][BASE_PHRASE_FEATURES.index("節-主辞")] = 1
+    base_phrase_features[2][BASE_PHRASE_FEATURES.index("時制:非過去")] = 1
     # 5: 桶屋
-    base_phrase_features[5][BASE_PHRASE_FEATURES.index("体言")] = 1.0
+    base_phrase_features[5][BASE_PHRASE_FEATURES.index("体言")] = 1
     # 7: 儲かる
-    base_phrase_features[7][BASE_PHRASE_FEATURES.index("用言:動")] = 1.0
-    base_phrase_features[7][BASE_PHRASE_FEATURES.index("節-区切")] = 1.0
-    base_phrase_features[7][BASE_PHRASE_FEATURES.index("節-主辞")] = 1.0
-    base_phrase_features[7][BASE_PHRASE_FEATURES.index("時制:非過去")] = 1.0
+    base_phrase_features[7][BASE_PHRASE_FEATURES.index("用言:動")] = 1
+    base_phrase_features[7][BASE_PHRASE_FEATURES.index("節-区切")] = 1
+    base_phrase_features[7][BASE_PHRASE_FEATURES.index("節-主辞")] = 1
+    base_phrase_features[7][BASE_PHRASE_FEATURES.index("時制:非過去")] = 1
     assert encoding["base_phrase_features"].tolist() == base_phrase_features
 
     dependencies = [IGNORE_INDEX for _ in range(max_seq_length)]
@@ -247,3 +256,43 @@ def test_encode():
     # 8: 。 -> 7: 儲かる
     dependency_types[8] = DEPENDENCY_TYPES.index("D")
     assert encoding["dependency_types"].tolist() == dependency_types
+
+
+def test_pas():
+    max_seq_length = 512
+    dataset = WordDataset(
+        str(data_dir / "knp"),
+        max_seq_length=max_seq_length,
+        **word_dataset_kwargs,
+    )
+    example = dataset.cohesion_examples["w201106-0000060560"]
+    example_expected = json.loads((data_dir / "expected/example/0.json").read_text())
+    mrphs_exp = example_expected["mrphs"]
+    annotation: PasAnnotation = example.annotations[Task.PAS_ANALYSIS]
+    phrases = example.phrases[Task.PAS_ANALYSIS]
+    mrphs = example.mrphs[Task.PAS_ANALYSIS]
+
+    assert len(mrphs) == len(mrphs_exp)
+    for phrase in phrases:
+        arguments: dict[str, list[str]] = annotation.arguments_set[phrase.dtid]
+        for case in dataset.cases:
+            arg_strings = [
+                arg[:-2] if arg[-2:] in ("%C", "%N", "%O") else arg
+                for arg in arguments[case]
+            ]
+            arg_strings = [
+                (s if s in dataset.special_to_index else str(phrases[int(s)].dmid))
+                for s in arg_strings
+            ]
+            assert set(arg_strings) == set(mrphs_exp[phrase.dmid]["arguments"][case])
+        for dmid in phrase.dmids:
+            mrph = mrphs[dmid]
+            mrph_exp = mrphs_exp[dmid]
+            assert mrph.surf == mrph_exp["surf"]
+            if mrph.is_target or example.mrphs[Task.BRIDGING][dmid].is_target:
+                candidates = set(phrases[i].dmid for i in phrase.candidates)
+                bar_phrases = example.phrases[Task.BRIDGING]
+                candidates |= set(
+                    bar_phrases[i].dmid for i in bar_phrases[phrase.dtid].candidates
+                )
+                assert candidates == set(mrph_exp["arg_candidates"])
diff --git a/tests/evaluators/test_cohesion_scorer.py b/tests/evaluators/test_cohesion_scorer.py
new file mode 100644
index 00000000..cdfaf39d
--- /dev/null
+++ b/tests/evaluators/test_cohesion_scorer.py
@@ -0,0 +1,44 @@
+import json
+from pathlib import Path
+
+from rhoknp import Document
+from rhoknp.rel import ExophoraReferent
+
+from jula.evaluators.cohesion_scorer import Measure, Scorer
+
+
+def test_scorer(fixture_data_dir: Path):
+    documents_pred = [
+        Document.from_knp(path.read_text())
+        for path in fixture_data_dir.glob("system/*.knp")
+    ]
+    documents_gold = [
+        Document.from_knp(path.read_text())
+        for path in fixture_data_dir.glob("gold/*.knp")
+    ]
+
+    with fixture_data_dir.joinpath("expected/cohesion_score.json").open() as f:
+        expected_scores = json.load(f)
+
+    cases = ["ガ", "ヲ"]
+    scorer = Scorer(
+        documents_pred,
+        documents_gold,
+        target_cases=cases,
+        exophora_referents=[
+            ExophoraReferent(e) for e in ("著者", "読者", "不特定:人", "不特定:物")
+        ],
+        coreference=True,
+        bridging=True,
+        pas_target="all",
+    )
+
+    result = scorer.run().to_dict()
+    for case in cases:
+        case_result = result[case]
+        for anal in Scorer.DEPTYPE2ANALYSIS.values():
+            expected: dict = expected_scores[case][anal]
+            actual: Measure = case_result[anal]
+            assert expected["denom_pred"] == actual.denom_pred
+            assert expected["denom_gold"] == actual.denom_gold
+            assert expected["correct"] == actual.correct
diff --git a/tests/evaluators/test_phrase_analysis_metric.py b/tests/evaluators/test_phrase_analysis_metric.py
new file mode 100644
index 00000000..d9d75fe4
--- /dev/null
+++ b/tests/evaluators/test_phrase_analysis_metric.py
@@ -0,0 +1,171 @@
+import torch
+
+from jula.evaluators.phrase_analysis_metric import PhraseAnalysisMetric
+from jula.utils.utils import BASE_PHRASE_FEATURES, IGNORE_INDEX
+
+"""  Example
+# S-ID:0-1
+* 0
++ 0 <体言>
+国立 <基本句-主辞><基本句-区切>
++ 1 <体言>
+国会 <基本句-主辞><基本句-区切>
++ 2 <体言>
+図書 <基本句-主辞><基本句-区切>
++ 3 <体言>
+館 <基本句-主辞>
+に <基本句-区切><文節-区切>
+* 4
++ 4 <用言:動><モダリティ-意志><モダリティ-勧誘><節-区切><節-主辞>
+行こう <基本句-主辞>
+。 <基本句-区切><文節-区切>
+EOS
+"""
+
+phrase_analysis_metric = PhraseAnalysisMetric()
+
+word_feature_predictions = torch.tensor(
+    [
+        [
+            [1, 1, 0],  # 国立
+            [1, 1, 0],  # 国会
+            [1, 1, 0],  # 図書
+            [1, 1, 1],  # 館  (<基本句-区切><文節-区切>と誤って予測)
+            [0, 0, 0],  # に
+            [1, 0, 0],  # 行こう
+            [0, 1, 1],  # 。
+        ]
+    ],
+    dtype=torch.long,
+)
+word_features = torch.tensor(
+    [
+        [
+            [1, 1, 0],  # 国立
+            [1, 1, 0],  # 国会
+            [1, 1, 0],  # 図書
+            [1, 0, 0],  # 館
+            [0, 1, 1],  # に
+            [1, 0, 0],  # 行こう
+            [0, 1, 1],  # 。
+        ]
+    ],
+    dtype=torch.long,
+)
+
+base_phrase_feature_predictions = torch.tensor(
+    [
+        [
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 国立
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 国会
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 図書
+            # 館 (<節-主辞>と誤って予測)
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
+            [
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+            ],  # に (IGNORED)
+            # 行こう (<モダリティ-命令>と誤って予測)
+            [1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
+            [
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+                0,
+            ],  # 。 (IGNORED)
+        ]
+    ],
+    dtype=torch.long,
+)
+base_phrase_features = torch.tensor(
+    [
+        [
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 国立
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 国会
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 図書
+            [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # 館
+            [IGNORE_INDEX] * len(BASE_PHRASE_FEATURES),  # に
+            [1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1],  # 行こう
+            [IGNORE_INDEX] * len(BASE_PHRASE_FEATURES),  # 。
+        ]
+    ],
+    dtype=torch.long,
+)
+
+phrase_analysis_metric_args = {
+    "example_ids": torch.as_tensor([0]),
+    "word_feature_predictions": word_feature_predictions,
+    "word_features": word_features,
+    "base_phrase_feature_predictions": base_phrase_feature_predictions,
+    "base_phrase_features": base_phrase_features,
+}
+
+phrase_analysis_metric.update(**phrase_analysis_metric_args)
+for attr in phrase_analysis_metric_args.keys():
+    setattr(
+        phrase_analysis_metric,
+        attr,
+        torch.cat(getattr(phrase_analysis_metric, attr), dim=0),
+    )
+
+results = phrase_analysis_metric.compute()
+
+
+def test_phrase_analysis_metric():
+    # word features
+    prec, rec = 5 / 5, 5 / 5
+    assert results["基本句-主辞_f1"] == 2 * prec * rec / (prec + rec)
+    prec, rec = 3 / 5, 3 / 5  # スパンレベル
+    assert results["基本句-区切_f1"] == 2 * prec * rec / (prec + rec)
+    prec, rec = 1 / 2, 1 / 2  # スパンレベル
+    assert results["文節-区切_f1"] == 2 * prec * rec / (prec + rec)
+
+    # base phrase features
+    prec, rec = 1 / 1, 1 / 1
+    assert results["用言:動_f1"] == 2 * prec * rec / (prec + rec)
+    prec, rec = 4 / 4, 4 / 4
+    assert results["体言_f1"] == 2 * prec * rec / (prec + rec)
+    prec, rec = 1 / 1, 1 / 1
+    assert results["モダリティ-意志_f1"] == 2 * prec * rec / (prec + rec)
+    # prec, rec = 0., 0 / 1
+    assert results["モダリティ-勧誘_f1"] == 0.0
+    prec, rec = 1 / 2, 1 / 1
+    assert results["節-主辞_f1"] == 2 * prec * rec / (prec + rec)
+    prec, rec = 1 / 1, 1 / 1
+    assert results["節-区切_f1"] == 2 * prec * rec / (prec + rec)
diff --git a/tests/evaluators/test_word_segment.py b/tests/evaluators/test_word_segment.py
index 08433421..acf7f8cc 100644
--- a/tests/evaluators/test_word_segment.py
+++ b/tests/evaluators/test_word_segment.py
@@ -1,16 +1,16 @@
 import pytest
 
-from jula.evaluators.word_segment import WordSegmenterMetric
+from jula.evaluators.word_segmenter import WordSegmenterMetric
 
-
-@pytest.mark.parametrize(
-    "ids, expected",
-    [
-        ([0, 1, 2], ["O", "B", "I"]),
-    ],
-)
-def test_convert_ids_to_labels(ids: list[int], expected: list[str]) -> None:
-    assert WordSegmenterMetric.convert_ids_to_labels(ids) == expected
+# TODO: uncomment here
+# @pytest.mark.parametrize(
+#     "ids, expected",
+#     [
+#         ([0, 1, 2], ["O", "B", "I"]),
+#     ],
+# )
+# def test_convert_ids_to_labels(ids: list[int], expected: list[str]) -> None:
+#     assert WordSegmenterMetric.convert_num2label(ids) == expected
 
 
 @pytest.mark.parametrize(
diff --git a/tests/test_pooling.py b/tests/test_pooling.py
index cfaaf51a..9f793a40 100644
--- a/tests/test_pooling.py
+++ b/tests/test_pooling.py
@@ -1,9 +1,8 @@
+import pytest
 import torch
 
 from jula.models.models.pooling import PoolingStrategy, pool_subwords
 
-eps = 1e-3
-
 # (b, word, seq) = (2, 3, 5)
 subword_map = torch.tensor(
     [
@@ -64,7 +63,7 @@ def test_first_pooling():
         ],
         dtype=torch.float,
     )
-    assert (pooled_output - expected_output).abs().sum().item() < eps
+    assert pooled_output == pytest.approx(expected_output)
 
 
 def test_last_pooling():
@@ -85,7 +84,7 @@ def test_last_pooling():
         ],
         dtype=torch.float,
     )
-    assert (pooled_output - expected_output).abs().sum().item() < eps
+    assert pooled_output == pytest.approx(expected_output)
 
 
 def test_max_pooling():
@@ -106,7 +105,7 @@ def test_max_pooling():
         ],
         dtype=torch.float,
     )
-    assert (pooled_output - expected_output).abs().sum().item() < eps
+    assert pooled_output == pytest.approx(expected_output)
 
 
 def test_average_pooling():
@@ -127,4 +126,4 @@ def test_average_pooling():
         ],
         dtype=torch.float,
     )
-    assert (pooled_output - expected_output).abs().sum().item() < eps
+    assert pooled_output == pytest.approx(expected_output)