Repository: bab2min/tomotopy (language: python)

Issue:
Loading a fairly large model takes a long time
I have a 6.5GB model trained on 10M docs to model 100 topics trained the usual way. I'm trying to load the model and I'm finding that load times are incredibly high. For reference, I've been monitoring `top` and my program has only loaded ~5.1GB of the 6.5GB model after 10 minutes.

I suspect this is because I used default `save` with `full=True`... Should I expect a model with `full=False` to load faster?


Discussion / hints:
Hi @erip 
Yes, the model saved with `full=True` argument has all parameters related to whole train, thus it may take a long time to re-load it. If you save the model with `full=False`, you cannot continue to train with this model, but you can load faster.
Thanks very much, @bab2min! It seems like if the model is binarized it shouldn't take long to reload. I haven't looked at the details so sorry for the silly question, but does the model use numpy binarization under the hood? If so, it could be quick to deserialize even if full (though maybe I don't appreciate the complexity here).
@erip Actually, the package doesn't use numpy binarization for loading & saving, but uses a custom serialization functions.
And it is true that there are many features related to backward compatibility in the custom functions, so their process is somewhat inefficiently.
I'll check if it can be improved or re-implement the loading & saving in the near future.

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..3a51281
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.9)
+
+project(tomotopy)
+
+set ( CMAKE_CXX_STANDARD 17 )
+set ( CMAKE_VERBOSE_MAKEFILE true )
+
+set ( CMAKE_SKIP_RPATH ON )
+set ( CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" )
+
+set ( EXT_TYPE "handler" CACHE STRING "dispatcher or handler" )
+set ( TARGET_ARCH "" CACHE STRING "target architecture" )
+set ( MODULE_NAME "_tomotopy" CACHE STRING "module name" )
+set ( TOMOTOPY_ISA "none" CACHE STRING "target ISA" )
+set ( NO_DEBUG_INFO OFF CACHE BOOL "no debug info" )
+set ( TOMOTOPY_LANG "en" CACHE STRING "language" )
+
+if(MSVC)
+    if (TARGET_ARCH STREQUAL "none")
+        set (ADDITIONAL_FLAGS "")
+    elseif (TARGET_ARCH STREQUAL "sse2")
+        set (ADDITIONAL_FLAGS "/arch:SSE2")
+        add_definitions(-D__SSE2__)
+    elseif (TARGET_ARCH STREQUAL "avx")
+        set (ADDITIONAL_FLAGS "/arch:AVX")
+        add_definitions(-D__SSE2__)
+    elseif (TARGET_ARCH STREQUAL "avx2")
+        set (ADDITIONAL_FLAGS "/arch:AVX2")
+        add_definitions(-D__SSE2__)
+    endif()
+
+    set ( CMAKE_C_FLAGS_DEBUG "-DDEBUG -DC_FLAGS -Zc:__cplusplus -Zi -Od ${ADDITIONAL_FLAGS}" )
+    set ( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}" )
+
+    set ( CMAKE_C_FLAGS_RELEASE "-DNDEBUG -DRELEASE -DC_FLAGS -Zc:__cplusplus -O2 -Oi -Gy  ${ADDITIONAL_FLAGS}" )
+    set ( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" )
+
+    set ( CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -Zi /bigobj")
+    set ( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
+    set ( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELEASE}" )
+else()
+    set (CPU_ARCH "x86_64")
+    if (TARGET_ARCH STREQUAL "none")
+        set (ADDITIONAL_FLAGS "")
+    elseif (TARGET_ARCH STREQUAL "native")
+        set (ADDITIONAL_FLAGS "-march=native")
+    elseif (TARGET_ARCH STREQUAL "sse2")
+        set (ADDITIONAL_FLAGS "-msse2")
+    elseif (TARGET_ARCH STREQUAL "avx")
+        set (ADDITIONAL_FLAGS "-mavx")
+    elseif (TARGET_ARCH STREQUAL "avx2")
+        set (ADDITIONAL_FLAGS "-mavx2")
+    elseif (TARGET_ARCH STREQUAL "arm64")
+        set (CPU_ARCH "arm64")
+    endif()
+
+    if (APPLE)
+        message(STATUS "Apple Clang: use undefined=dynamic_lookup")
+        set (CMAKE_OSX_ARCHITECTURES "${CPU_ARCH}")
+        set (ADDITIONAL_FLAGS "${ADDITIONAL_FLAGS} -undefined dynamic_lookup")
+        set (ADDITIONAL_LINKER_FLAGS "-stdlib=libc++")
+    endif()
+
+    set ( CMAKE_C_FLAGS_DEBUG "-DDEBUG -DC_FLAGS -g3 -O0 ${ADDITIONAL_FLAGS}" )
+    set ( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}" )
+    set ( CMAKE_EXE_LINKER_FLAGS_DEBUG "-DDEBUG -DLINKER_FLAGS ${ADDITIONAL_LINKER_FLAGS}" )
+
+    set ( CMAKE_C_FLAGS_RELEASE "-DNDEBUG -DRELEASE -DC_FLAGS -O3 ${ADDITIONAL_FLAGS}" )
+    set ( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" )
+    set ( CMAKE_EXE_LINKER_FLAGS_RELEASE "-DRELEASE -DLINKER_FLAGS ${ADDITIONAL_LINKER_FLAGS}" )
+
+    if (NO_DEBUG_INFO)
+        set ( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g0")
+        set ( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g0")
+        set ( CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -g0")
+        set ( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g0")
+    endif()
+
+    set ( CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELEASE} -g3")
+    set ( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
+    set ( CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${ADDITIONAL_LINKER_FLAGS}" )
+endif()
+
+SET (srcs 
+)
+
+if (EXT_TYPE STREQUAL "handler")
+    file(GLOB cpp_files "src/python/handler/*.cpp" "src/Utils/*.cpp" "src/TopicModel/*.cpp" "src/Labeling/*.cpp")
+    SET(srcs
+        ${srcs}
+        ${cpp_files}
+    )
+    add_definitions(-DMODULE_NAME=${MODULE_NAME})
+    add_definitions(-DTOMOTOPY_ISA=${TOMOTOPY_ISA})
+    if (TOMOTOPY_LANG STREQUAL "kr")
+        add_definitions(-DDOC_KO=1)
+    endif()
+else()
+    file(GLOB cpp_files "src/python/dispatcher/*.cpp")
+    SET(srcs
+        ${srcs}
+        ${cpp_files}
+    )
+endif()
+
+include_directories(${INCLUDE_DIRS}
+)
+link_directories(${LIBRARY_DIRS})
+
+add_library("_tomotopy_target" SHARED
+    ${srcs}
+)
+
+target_link_libraries("_tomotopy_target" PRIVATE 
+  ${LIBRARIES}
+)
diff --git a/requirements.txt b/requirements.txt
index 152d9da..7e6c5fa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,1 +1,1 @@
-numpy>=1.10.0
\ No newline at end of file
+numpy>=1.10.0,<2
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 9b7f7a0..e42b98c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,155 @@
+import os, os.path
+from glob import glob
+import platform
+import sys
+import shutil
+import subprocess
+import re
+
 from setuptools import setup, Extension
-from setuptools.command.install import install
-from codecs import open
-import os, os.path, struct, re, platform
+from setuptools.command.build_ext import build_ext
+from distutils.version import LooseVersion
+from sysconfig import get_platform
+import struct
+
+import numpy as np
+
+
+def get_extra_cmake_options():
+    """read --clean, --no, --set, --compiler-flags, and -G options from the command line and add them as cmake switches.
+    """
+    _cmake_extra_options = ["-DCMAKE_POSITION_INDEPENDENT_CODE=1"]
+    if os.environ.get('MACOSX_DEPLOYMENT_TARGET'):
+        _cmake_extra_options.append("-DCMAKE_OSX_DEPLOYMENT_TARGET=" + os.environ['MACOSX_DEPLOYMENT_TARGET'])
+    _clean_build_folder = False
+
+    opt_key = None
+
+    argv = [arg for arg in sys.argv]  # take a copy
+    # parse command line options and consume those we care about
+    for arg in argv:
+        if opt_key == 'compiler-flags':
+            _cmake_extra_options.append('-DCMAKE_CXX_FLAGS={arg}'.format(arg=arg.strip()))
+        elif opt_key == 'G':
+            _cmake_extra_options += ['-G', arg.strip()]
+        elif opt_key == 'no':
+            _cmake_extra_options.append('-D{arg}=no'.format(arg=arg.strip()))
+        elif opt_key == 'set':
+            _cmake_extra_options.append('-D{arg}'.format(arg=arg.strip()))
+
+        if opt_key:
+            sys.argv.remove(arg)
+            opt_key = None
+            continue
+
+        if arg == '--clean':
+            _clean_build_folder = True
+            sys.argv.remove(arg)
+            continue
+
+        if arg == '--yes':
+            print("The --yes options to kiwipiepy's setup.py don't do anything since all these options ")
+            print("are on by default.  So --yes has been removed.  Do not give it to setup.py.")
+            sys.exit(1)
+        if arg in ['--no', '--set', '--compiler-flags']:
+            opt_key = arg[2:].lower()
+            sys.argv.remove(arg)
+            continue
+        if arg in ['-G']:
+            opt_key = arg[1:]
+            sys.argv.remove(arg)
+            continue
+
+    return _cmake_extra_options, _clean_build_folder
+
+def num_available_cpu_cores(ram_per_build_process_in_gb):
+    import multiprocessing
+    try:
+        mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES')  
+        mem_gib = mem_bytes/(1024.**3)
+        num_cores = multiprocessing.cpu_count() 
+        mem_cores = int(mem_gib/float(ram_per_build_process_in_gb)+0.5)
+        return max(min(num_cores, mem_cores), 1)
+    except ValueError:
+        return 2 # just assume 2 if we can't get the os to tell us the right answer.
+
+
+class CMakeExtension(Extension):
+    def __init__(self, name, sourcedir='', cmake_options=[], *args, **kwargs):
+        Extension.__init__(self, name, sources=[], *args, **kwargs)
+        self.sourcedir = os.path.abspath(sourcedir)
+        self.additional_options = cmake_options
+
+class CMakeBuild(build_ext):
+
+    def get_cmake_version(self):
+        try:
+            out = subprocess.check_output(['cmake', '--version'])
+        except:
+            sys.stderr.write("\nERROR: CMake must be installed to build tomotopy\n\n") 
+            sys.exit(1)
+        return re.search(r'version\s*([\d.]+)', out.decode()).group(1)
+
+    def run(self):
+        cmake_version = self.get_cmake_version()
+        if platform.system() == "Windows":
+            if LooseVersion(cmake_version) < '3.1.0':
+                sys.stderr.write("\nERROR: CMake >= 3.1.0 is required on Windows\n\n")
+                sys.exit(1)
+
+        for ext in self.extensions:
+            self.build_extension(ext)
+
+    def build_extension(self, ext):
+        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
+        libs = self.get_libraries(ext)
+
+        cmake_args = [
+            '-DINCLUDE_DIRS={}'.format(';'.join(self.include_dirs + ext.include_dirs)),
+            '-DLIBRARY_DIRS={}'.format(';'.join(self.library_dirs)),
+            '-DLIBRARIES={}'.format(';'.join(libs)),
+            '-DPYTHON_EXECUTABLE=' + sys.executable,
+        ]
+        cmake_args += ext.additional_options
+        print(cmake_args)
+
+        cfg = 'Debug' if self.debug else 'Release'
+        build_args = ['--config', cfg]
+
+        if platform.system() == "Windows":
+            if sys.maxsize > 2**32:
+                cmake_args += ['-A', 'x64']
+            else:
+                cmake_args += ['-A', 'Win32']
+            build_args += ['--', '/m'] 
+        else:
+            cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
+            build_args += ['--', '-j'+str(num_available_cpu_cores(2))]
+
+        build_folder = os.path.abspath(self.build_temp)
+
+        if not os.path.exists(build_folder):
+            os.makedirs(build_folder)
+
+        cmake_setup = ['cmake', ext.sourcedir] + cmake_args
+        cmake_build = ['cmake', '--build', '.'] + build_args
+
+        print("Building extension for Python {}".format(sys.version.split('\n',1)[0]))
+        print("Invoking CMake setup: '{}'".format(' '.join(cmake_setup)))
+        sys.stdout.flush()
+        subprocess.check_call(cmake_setup, cwd=build_folder)
+        print("Invoking CMake build: '{}'".format(' '.join(cmake_build)))
+        sys.stdout.flush()
+        subprocess.check_call(cmake_build, cwd=build_folder)
+        
+        fullname = self.get_ext_fullname(ext.name)
+        filename = self.get_ext_filename(fullname)
+
+        if platform.system() == "Windows":
+            shutil.move(os.sep.join([build_folder, cfg, f'_tomotopy_target.dll']), os.sep.join([extdir, filename]))
+        else:
+            shutil.move(glob(os.sep.join([build_folder, 'lib_tomotopy_target.*']))[0], os.sep.join([extdir, filename]))
 
-import numpy
 
 try:
     from setuptools._distutils import _msvccompiler
@@ -20,7 +166,15 @@
     except:
         tomotopy_cpu_arch = 'x86_64'
 
-from sysconfig import get_platform
+
+exec(open('tomotopy/_version.py').read())
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+long_description = ''
+for line in open(os.path.join(here, 'tomotopy/documentation.rst'), encoding='utf-8'):
+    long_description += re.sub(r'^<.+>\s*$', '', line)
+
 fd = get_platform().split('-')
 if fd[0] == 'macosx':
     if os.environ.get('MACOSX_DEPLOYMENT_TARGET'): 
@@ -36,73 +190,68 @@
         fd[1] = os.environ['MACOSX_DEPLOYMENT_TARGET']
     os.environ['_PYTHON_HOST_PLATFORM'] = '-'.join(fd[:-1] + [tomotopy_cpu_arch])
     
-exec(open('tomotopy/_version.py').read())
+cmake_extra_options, _ = get_extra_cmake_options()
 
-here = os.path.abspath(os.path.dirname(__file__))
+build_for_manyplatform = ('many' in os.environ.get('AUDITWHEEL_PLAT', ''))
 
-long_description = ''
-for line in open(os.path.join(here, 'tomotopy/documentation.rst'), encoding='utf-8'):
-    long_description += re.sub(r'^<.+>\s*$', '', line)
-
-sources = []
-for f in os.listdir(os.path.join(here, 'src/python')):
-    if f.endswith('.cpp') and not f.endswith('py_rt.cpp'): sources.append('src/python/' + f)
-for f in os.listdir(os.path.join(here, 'src/TopicModel')):
-    if f.endswith('.cpp'): sources.append('src/TopicModel/' + f)
-for f in os.listdir(os.path.join(here, 'src/Labeling')):
-    if f.endswith('.cpp'): sources.append('src/Labeling/' + f)
-
-largs = []
-arch_levels = {'':'', 'sse2':'-msse2', 'avx':'-mavx', 'avx2':'-mavx2 -mfma'}
+arch_levels = ['none', 'sse2', 'avx2']
 if platform.system() == 'Windows': 
-    cargs = ['/O2', '/MT', '/Gy', '/D__SSE2__']
-    arch_levels = {'':'', 'sse2':'/arch:SSE2', 'avx':'/arch:AVX', 'avx2':'/arch:AVX2'}
+    arch_levels = ['none', 'sse2', 'avx2']
 elif platform.system() == 'Darwin': 
-    cargs = ['-std=c++1y', '-O3', '-fpermissive', '-stdlib=libc++', '-Wno-unused-variable', '-Wno-switch']
-    largs += ['-stdlib=libc++']
-    if 'many' in os.environ.get('AUDITWHEEL_PLAT', ''): cargs.append('-g0')
     if tomotopy_cpu_arch == 'arm64':
-        arch_levels = {'': ''}
-        cargs += ['-DTOMOTOPY_ISA=arm64', '-arch', 'arm64']
-        largs += ['-arch', 'arm64']
+        arch_levels = ['aarch64']
+    elif build_for_manyplatform: 
+        arch_levels = ['sse2', 'avx', 'avx2']
     else:
-        if 'many' not in os.environ.get('AUDITWHEEL_PLAT', ''): arch_levels = {'':'-msse2'}
-        cargs += ['-arch', 'x86_64']
-        largs += ['-arch', 'x86_64']
-elif 'many' in os.environ.get('AUDITWHEEL_PLAT', ''):
-    cargs = ['-std=c++1y', '-O3', '-fpermissive', '-g0', '-Wno-unused-variable', '-Wno-switch']
+        arch_levels = ['sse2']
+elif build_for_manyplatform:
     if tomotopy_cpu_arch in ('arm64', 'aarch64'):
-        arch_levels = {'': ''}
-        cargs += ['-DTOMOTOPY_ISA=arm64']
+        arch_levels = ['aarch64']
 else:
-    cargs = ['-std=c++1y', '-O3', '-fpermissive', '-Wno-unused-variable', '-Wno-switch']
-    arch_levels = {'': '-march=native'}
-
-if struct.calcsize('P') < 8: arch_levels = {k:v for k, v in arch_levels.items() if k in ('', 'sse2')}
-else: arch_levels = {k:v for k, v in arch_levels.items() if k not in ('sse2',)}
+    arch_levels = ['native']
 
-lang_macro = []
-if os.environ.get('TOMOTOPY_LANG') == 'kr': lang_macro = [('DOC_KO', '1')]
+# if target is in 64bit, remove 'none'
+if struct.calcsize("P") == 8:
+    try:
+        arch_levels.remove('none')
+    except ValueError:
+        pass
+else:
+    arch_levels = [a for a in arch_levels if a in ('none', 'sse2')]
 
 modules = []
 if len(arch_levels) > 1:
-    modules.append(Extension('_tomotopy',
+    modules.append(CMakeExtension('_tomotopy',
         libraries=[],
-        include_dirs=['include', numpy.get_include()],
-        sources=['src/python/py_rt.cpp'],
-        extra_compile_args=cargs, extra_link_args=largs))
+        include_dirs=['include', np.get_include()],
+        cmake_options=cmake_extra_options + ['-DEXT_TYPE=dispatcher'],
+    ))
+
+if build_for_manyplatform:
+    cmake_extra_options += ['-DNO_DEBUG_INFO=1']
+else:
+    cmake_extra_options += ['-DNO_DEBUG_INFO=0']
+
+if os.environ.get('TOMOTOPY_LANG'):
+    cmake_extra_options += ['-DTOMOTOPY_LANG=' + os.environ['TOMOTOPY_LANG']]
 
-for arch, aopt in arch_levels.items():
+for arch in arch_levels:
     if len(arch_levels) > 1:
-        module_name = '_tomotopy_' + (arch or 'none')
+        if arch in ('none', 'native', 'aarch64'):
+            module_name = '_tomotopy_none'
+        else:
+            module_name = '_tomotopy_' + arch
     else:
         module_name = '_tomotopy'
-    modules.append(Extension(module_name,
-                    libraries=[],
-                    include_dirs=['include', numpy.get_include()],
-                    sources=sources,
-                    define_macros=[('MODULE_NAME', 'PyInit_' + module_name)] + lang_macro,
-                    extra_compile_args=cargs + (aopt.split(' ') if aopt else []), extra_link_args=largs))
+    modules.append(CMakeExtension(module_name,
+        libraries=[],
+        include_dirs=['include', np.get_include()],
+        cmake_options=cmake_extra_options + [
+            '-DEXT_TYPE=handler', 
+            '-DTARGET_ARCH=' + arch,
+            '-DTOMOTOPY_ISA=' + arch, 
+            '-DMODULE_NAME=PyInit_' + module_name],
+    ))
 
 setup(
     name='tomotopy',
@@ -140,10 +289,11 @@
         "Operating System :: POSIX",
         "Operating System :: MacOS"
     ],
-    install_requires=['numpy>=1.11.0'],
+    install_requires=['numpy>=1.11.0,<2'],
     keywords='NLP,Topic Model',
 
     packages=['tomotopy'],
     include_package_data=True,
-    ext_modules=modules
+    ext_modules=modules,
+    cmdclass=dict(build_ext=CMakeBuild),
 )
diff --git a/src/TopicModel/CT.h b/src/TopicModel/CT.h
index 140c578..26ae4aa 100644
--- a/src/TopicModel/CT.h
+++ b/src/TopicModel/CT.h
@@ -11,8 +11,8 @@ namespace tomoto
 		Matrix beta; // Dim: (K, betaSample)
 		Vector smBeta; // Dim: K
 		
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, smBeta);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, smBeta);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct CTArgs : public LDAArgs
diff --git a/src/TopicModel/CTModel.cpp b/src/TopicModel/CTModel.cpp
index 56711de..962828d 100644
--- a/src/TopicModel/CTModel.cpp
+++ b/src/TopicModel/CTModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentCTM, BaseDocument, 0, smBeta);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentCTM, BaseDocument, 1, 0x00010001, smBeta);
+
+	TMT_INSTANTIATE_DOC(DocumentCTM);
+
 	ICTModel* ICTModel::create(TermWeight _weight, const CTArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, CTModel, args);
diff --git a/src/TopicModel/DMR.h b/src/TopicModel/DMR.h
index 1a0a059..5186f04 100644
--- a/src/TopicModel/DMR.h
+++ b/src/TopicModel/DMR.h
@@ -18,8 +18,8 @@ namespace tomoto
 
 		RawDoc::MiscType makeMisc(const ITopicModel* tm) const override;
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, metadata);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, metadata, multiMetadata);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct DMRArgs : public LDAArgs
diff --git a/src/TopicModel/DMRModel.cpp b/src/TopicModel/DMRModel.cpp
index 6266ee5..8018057 100644
--- a/src/TopicModel/DMRModel.cpp
+++ b/src/TopicModel/DMRModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentDMR, BaseDocument, 0, metadata);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentDMR, BaseDocument, 1, 0x00010001, metadata, multiMetadata);
+
+	TMT_INSTANTIATE_DOC(DocumentDMR);
+
 	IDMRModel* IDMRModel::create(TermWeight _weight, const DMRArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, DMRModel, args);
diff --git a/src/TopicModel/DT.h b/src/TopicModel/DT.h
index cd6254c..e76c527 100644
--- a/src/TopicModel/DT.h
+++ b/src/TopicModel/DT.h
@@ -21,8 +21,8 @@ namespace tomoto
 			return ret;
 		}
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, timepoint);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, timepoint);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct DTArgs : public LDAArgs
diff --git a/src/TopicModel/DTModel.cpp b/src/TopicModel/DTModel.cpp
index 7a63605..c9052f7 100644
--- a/src/TopicModel/DTModel.cpp
+++ b/src/TopicModel/DTModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentDTM, BaseDocument, 0, timepoint);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentDTM, BaseDocument, 1, 0x00010001, timepoint);
+
+	TMT_INSTANTIATE_DOC(DocumentDTM);
+
 	IDTModel* IDTModel::create(TermWeight _weight, const DTArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, DTModel, args);
diff --git a/src/TopicModel/GDMR.h b/src/TopicModel/GDMR.h
index d2dc026..0f28589 100644
--- a/src/TopicModel/GDMR.h
+++ b/src/TopicModel/GDMR.h
@@ -17,8 +17,8 @@ namespace tomoto
 			return ret;
 		}
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, metadataOrg);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, metadataOrg, metadataNormalized);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct GDMRArgs : public DMRArgs
diff --git a/src/TopicModel/GDMRModel.cpp b/src/TopicModel/GDMRModel.cpp
index ed1d6b3..278a1d5 100644
--- a/src/TopicModel/GDMRModel.cpp
+++ b/src/TopicModel/GDMRModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentGDMR, BaseDocument, 0, metadataOrg);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentGDMR, BaseDocument, 1, 0x00010001, metadataOrg, metadataNormalized);
+
+	TMT_INSTANTIATE_DOC(DocumentGDMR);
+
     IGDMRModel* IGDMRModel::create(TermWeight _weight, const GDMRArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, GDMRModel, args);
diff --git a/src/TopicModel/HDP.h b/src/TopicModel/HDP.h
index 64eb698..f511dba 100644
--- a/src/TopicModel/HDP.h
+++ b/src/TopicModel/HDP.h
@@ -39,8 +39,8 @@ namespace tomoto
 		};
 		std::vector<TableTopicInfo> numTopicByTable;
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, numTopicByTable);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, numTopicByTable);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 
 		size_t getNumTable() const
 		{
diff --git a/src/TopicModel/HDPModel.cpp b/src/TopicModel/HDPModel.cpp
index 627a9a7..ffd9518 100644
--- a/src/TopicModel/HDPModel.cpp
+++ b/src/TopicModel/HDPModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentHDP, BaseDocument, 0, numTopicByTable);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentHDP, BaseDocument, 1, 0x00010001, numTopicByTable);
+
+	TMT_INSTANTIATE_DOC(DocumentHDP);
+
     IHDPModel* IHDPModel::create(TermWeight _weight, const HDPArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, HDPModel, args);
diff --git a/src/TopicModel/HLDA.h b/src/TopicModel/HLDA.h
index 20ac854..4d9fb10 100644
--- a/src/TopicModel/HLDA.h
+++ b/src/TopicModel/HLDA.h
@@ -16,8 +16,8 @@ namespace tomoto
 
 		template<typename _TopicModel> void update(WeightType* ptr, const _TopicModel& mdl);
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, path);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, path);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct HLDAArgs : public LDAArgs
diff --git a/src/TopicModel/HLDAModel.cpp b/src/TopicModel/HLDAModel.cpp
index 39d6b6e..0b68e88 100644
--- a/src/TopicModel/HLDAModel.cpp
+++ b/src/TopicModel/HLDAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentHLDA, BaseDocument, 0, path);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentHLDA, BaseDocument, 1, 0x00010001, path);
+
+	TMT_INSTANTIATE_DOC(DocumentHLDA);
+
 	IHLDAModel* IHLDAModel::create(TermWeight _weight, const HLDAArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, HLDAModel, args);
diff --git a/src/TopicModel/HPA.h b/src/TopicModel/HPA.h
index a76a03b..3f7efcb 100644
--- a/src/TopicModel/HPA.h
+++ b/src/TopicModel/HPA.h
@@ -12,8 +12,8 @@ namespace tomoto
 
 		template<typename _TopicModel> void update(WeightType* ptr, const _TopicModel& mdl);
 
-		DEFINE_SERIALIZER_BASE_WITH_VERSION(BaseDocument, 0);
-		DEFINE_SERIALIZER_BASE_WITH_VERSION(BaseDocument, 1);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct HPAArgs : public PAArgs
diff --git a/src/TopicModel/HPAModel.cpp b/src/TopicModel/HPAModel.cpp
index fe3f432..ae48c99 100644
--- a/src/TopicModel/HPAModel.cpp
+++ b/src/TopicModel/HPAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_BASE_WITH_VERSION(DocumentHPA, BaseDocument, 0);
+	DEFINE_OUT_SERIALIZER_BASE_WITH_VERSION(DocumentHPA, BaseDocument, 1);
+
+	TMT_INSTANTIATE_DOC(DocumentHPA);
+
     IHPAModel* IHPAModel::create(TermWeight _weight, bool _exclusive, const HPAArgs& args, bool scalarRng)
 	{
 		if (_exclusive)
diff --git a/src/TopicModel/LDA.h b/src/TopicModel/LDA.h
index accffaf..3d8afe0 100644
--- a/src/TopicModel/LDA.h
+++ b/src/TopicModel/LDA.h
@@ -139,8 +139,8 @@ namespace tomoto
 		tvector<Float> wordWeights;
 		ShareableMatrix<WeightType, -1, 1> numByTopic;
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentBase, 0, Zs, wordWeights);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentBase, 1, 0x00010001, Zs, wordWeights);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 
 		template<typename _TopicModel> void update(WeightType* ptr, const _TopicModel& mdl);
 		
diff --git a/src/TopicModel/LDAModel.cpp b/src/TopicModel/LDAModel.cpp
index 56c7014..07a3817 100644
--- a/src/TopicModel/LDAModel.cpp
+++ b/src/TopicModel/LDAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+    DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentLDA, DocumentBase, 0, Zs, wordWeights);
+    DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentLDA, DocumentBase, 1, 0x00010001, Zs, wordWeights);
+
+    TMT_INSTANTIATE_DOC(DocumentLDA);
+
     ILDAModel* ILDAModel::create(TermWeight _weight, const LDAArgs& args, bool scalarRng)
     {
         TMT_SWITCH_TW(_weight, scalarRng, LDAModel, args);
diff --git a/src/TopicModel/LDAModel.hpp b/src/TopicModel/LDAModel.hpp
index affb8f4..153a42a 100644
--- a/src/TopicModel/LDAModel.hpp
+++ b/src/TopicModel/LDAModel.hpp
@@ -47,6 +47,10 @@ Term Weighting Scheme is based on following paper:
 		return nullptr; } while(0)
 #endif
 
+#define TMT_INSTANTIATE_DOC(CLS) template struct CLS<TermWeight::one>; \
+	template struct CLS<TermWeight::idf>; \
+	template struct CLS<TermWeight::pmi>;
+
 #define GETTER(name, type, field) type get##name() const override { return field; }
 
 namespace tomoto
diff --git a/src/TopicModel/LLDA.h b/src/TopicModel/LLDA.h
index a6fda97..05b87d1 100644
--- a/src/TopicModel/LLDA.h
+++ b/src/TopicModel/LLDA.h
@@ -11,8 +11,8 @@ namespace tomoto
 		using WeightType = typename DocumentLDA<_tw>::WeightType;
 		Eigen::Matrix<int8_t, -1, 1> labelMask;
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, labelMask);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, labelMask);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	class ILLDAModel : public ILDAModel
diff --git a/src/TopicModel/LLDAModel.cpp b/src/TopicModel/LLDAModel.cpp
index b18acf8..c005a71 100644
--- a/src/TopicModel/LLDAModel.cpp
+++ b/src/TopicModel/LLDAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentLLDA, BaseDocument, 0, labelMask);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentLLDA, BaseDocument, 1, 0x00010001, labelMask);
+
+	TMT_INSTANTIATE_DOC(DocumentLLDA);
+
 	ILLDAModel* ILLDAModel::create(TermWeight _weight, const LDAArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, LLDAModel, args);
diff --git a/src/TopicModel/MGLDA.h b/src/TopicModel/MGLDA.h
index 44549d7..d9238d5 100644
--- a/src/TopicModel/MGLDA.h
+++ b/src/TopicModel/MGLDA.h
@@ -22,8 +22,8 @@ namespace tomoto
 		Eigen::Matrix<WeightType, -1, 1> numByWin; // number of words in the window (len = S + T - 1)
 		Eigen::Matrix<WeightType, -1, -1> numByWinTopicL; // number of words in the loc. topic in the window (len = KL * (S + T - 1))
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, sents, Vs, numGl, numBySentWin, numByWinL, numByWin, numByWinTopicL);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, sents, Vs, numGl, numBySentWin, numByWinL, numByWin, numByWinTopicL);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 
 		template<typename _TopicModel> void update(WeightType* ptr, const _TopicModel& mdl);
 	};
diff --git a/src/TopicModel/MGLDAModel.cpp b/src/TopicModel/MGLDAModel.cpp
index 5aff650..245b05b 100644
--- a/src/TopicModel/MGLDAModel.cpp
+++ b/src/TopicModel/MGLDAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentMGLDA, BaseDocument, 0, sents, Vs, numGl, numBySentWin, numByWinL, numByWin, numByWinTopicL);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentMGLDA, BaseDocument, 1, 0x00010001, sents, Vs, numGl, numBySentWin, numByWinL, numByWin, numByWinTopicL);
+
+	TMT_INSTANTIATE_DOC(DocumentMGLDA);
+
     IMGLDAModel* IMGLDAModel::create(TermWeight _weight, const MGLDAArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, MGLDAModel, args);
diff --git a/src/TopicModel/PA.h b/src/TopicModel/PA.h
index 850c2d1..b228e9c 100644
--- a/src/TopicModel/PA.h
+++ b/src/TopicModel/PA.h
@@ -15,8 +15,8 @@ namespace tomoto
 
 		template<typename _TopicModel> void update(WeightType* ptr, const _TopicModel& mdl);
 
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, Z2s);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, Z2s);
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct PAArgs : public LDAArgs
diff --git a/src/TopicModel/PAModel.cpp b/src/TopicModel/PAModel.cpp
index 04888ef..09d9185 100644
--- a/src/TopicModel/PAModel.cpp
+++ b/src/TopicModel/PAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentPA, BaseDocument, 0, Z2s);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentPA, BaseDocument, 1, 0x00010001, Z2s);
+
+	TMT_INSTANTIATE_DOC(DocumentPA);
+
 	IPAModel* IPAModel::create(TermWeight _weight, const PAArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, PAModel, args);
diff --git a/src/TopicModel/PT.h b/src/TopicModel/PT.h
index 1ac32e4..7e17a21 100644
--- a/src/TopicModel/PT.h
+++ b/src/TopicModel/PT.h
@@ -11,9 +11,9 @@ namespace tomoto
 		using WeightType = typename DocumentLDA<_tw>::WeightType;
 		
 		uint64_t pseudoDoc = 0;
-
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, pseudoDoc);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, pseudoDoc);
+		
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct PTArgs : public LDAArgs
diff --git a/src/TopicModel/PTModel.cpp b/src/TopicModel/PTModel.cpp
index 6b53483..758f4f0 100644
--- a/src/TopicModel/PTModel.cpp
+++ b/src/TopicModel/PTModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentPT, BaseDocument, 0, pseudoDoc);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentPT, BaseDocument, 1, 0x00010001, pseudoDoc);
+
+	TMT_INSTANTIATE_DOC(DocumentPT);
+
 	IPTModel* IPTModel::create(TermWeight _weight, const PTArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, PTModel, args);
diff --git a/src/TopicModel/SLDA.h b/src/TopicModel/SLDA.h
index 1b1b1b7..be32e0c 100644
--- a/src/TopicModel/SLDA.h
+++ b/src/TopicModel/SLDA.h
@@ -16,8 +16,9 @@ namespace tomoto
 			ret["y"] = y;
 			return ret;
 		}
-		DEFINE_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 0, y);
-		DEFINE_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(BaseDocument, 1, 0x00010001, y);
+		
+		DECLARE_SERIALIZER_WITH_VERSION(0);
+		DECLARE_SERIALIZER_WITH_VERSION(1);
 	};
 
 	struct SLDAArgs;
diff --git a/src/TopicModel/SLDAModel.cpp b/src/TopicModel/SLDAModel.cpp
index 7ee2e34..c182548 100644
--- a/src/TopicModel/SLDAModel.cpp
+++ b/src/TopicModel/SLDAModel.cpp
@@ -2,6 +2,11 @@
 
 namespace tomoto
 {
+	DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentSLDA, BaseDocument, 0, y);
+	DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(DocumentSLDA, BaseDocument, 1, 0x00010001, y);
+
+	TMT_INSTANTIATE_DOC(DocumentSLDA);
+
     ISLDAModel* ISLDAModel::create(TermWeight _weight, const SLDAArgs& args, bool scalarRng)
 	{
 		TMT_SWITCH_TW(_weight, scalarRng, SLDAModel, args);
diff --git a/src/TopicModel/TopicModel.hpp b/src/TopicModel/TopicModel.hpp
index 443d465..54a4253 100644
--- a/src/TopicModel/TopicModel.hpp
+++ b/src/TopicModel/TopicModel.hpp
@@ -7,7 +7,7 @@
 #include "../Utils/ThreadPool.hpp"
 #include "../Utils/serializer.hpp"
 #include "../Utils/exception.h"
-#include "../Utils/SharedString.hpp"
+#include "../Utils/SharedString.h"
 #include <EigenRand/EigenRand>
 #include <mapbox/variant.hpp>
 
@@ -107,7 +107,7 @@ namespace tomoto
 
 		virtual operator RawDoc() const
 		{
-			RawDoc raw{ *this };
+			RawDoc raw{ *static_cast<const RawDocKernel*>(this) };
 			if (wOrder.empty())
 			{
 				raw.words.insert(raw.words.begin(), words.begin(), words.end());
diff --git a/src/Utils/Dictionary.cpp b/src/Utils/Dictionary.cpp
new file mode 100644
index 0000000..b1f0d8d
--- /dev/null
+++ b/src/Utils/Dictionary.cpp
@@ -0,0 +1,97 @@
+#include "Dictionary.h"
+
+namespace tomoto
+{
+    Dictionary::Dictionary() = default;
+    Dictionary::~Dictionary() = default;
+
+    Dictionary::Dictionary(const Dictionary&) = default;
+    Dictionary& Dictionary::operator=(const Dictionary&) = default;
+
+    Dictionary::Dictionary(Dictionary&&) noexcept = default;
+    Dictionary& Dictionary::operator=(Dictionary&&) noexcept = default;
+
+    Vid Dictionary::add(const std::string& word)
+    {
+        auto it = dict.find(word);
+        if (it == dict.end())
+        {
+            dict.emplace(word, (Vid)dict.size());
+            id2word.emplace_back(word);
+            return (Vid)(dict.size() - 1);
+        }
+        return it->second;
+    }
+    
+    const std::string& Dictionary::toWord(Vid vid) const
+    {
+        assert(vid < id2word.size());
+        return id2word[vid];
+    }
+    
+    Vid Dictionary::toWid(const std::string& word) const
+    {
+        auto it = dict.find(word);
+        if (it == dict.end()) return non_vocab_id;
+        return it->second;
+    }
+
+    void Dictionary::serializerWrite(std::ostream& writer) const
+    {
+        serializer::writeMany(writer, serializer::to_key("Dict"), id2word);
+    }
+    
+    void Dictionary::serializerRead(std::istream& reader)
+    {
+        serializer::readMany(reader, serializer::to_key("Dict"), id2word);
+        for (size_t i = 0; i < id2word.size(); ++i)
+        {
+            dict.emplace(id2word[i], (Vid)i);
+        }
+    }
+
+    void Dictionary::swap(Dictionary& rhs)
+    {
+        std::swap(dict, rhs.dict);
+        std::swap(id2word, rhs.id2word);
+    }
+
+    void Dictionary::reorder(const std::vector<Vid>& order)
+    {
+        for (auto& p : dict)
+        {
+            p.second = order[p.second];
+            id2word[p.second] = p.first;
+        }
+    }
+
+    const std::vector<std::string>& Dictionary::getRaw() const
+    {
+        return id2word;
+    }
+
+    Vid Dictionary::mapToNewDict(Vid v, const Dictionary& newDict) const
+    {
+        return newDict.toWid(toWord(v));
+    }
+
+    std::vector<Vid> Dictionary::mapToNewDict(const std::vector<Vid>& v, const Dictionary& newDict) const
+    {
+        std::vector<Vid> r(v.size());
+        for (size_t i = 0; i < v.size(); ++i)
+        {
+            r[i] = mapToNewDict(v[i], newDict);
+        }
+        return r;
+    }
+
+    std::vector<Vid> Dictionary::mapToNewDictAdd(const std::vector<Vid>& v, Dictionary& newDict) const
+    {
+        std::vector<Vid> r(v.size());
+        for (size_t i = 0; i < v.size(); ++i)
+        {
+            r[i] = mapToNewDict(v[i], newDict);
+        }
+        return r;
+    }
+}
diff --git a/src/Utils/Dictionary.h b/src/Utils/Dictionary.h
index 850e349..e6e09d4 100644
--- a/src/Utils/Dictionary.h
+++ b/src/Utils/Dictionary.h
@@ -28,91 +28,39 @@ namespace tomoto
 		std::unordered_map<std::string, Vid> dict;
 		std::vector<std::string> id2word;
 	public:
-		Vid add(const std::string& word)
-		{
-			auto it = dict.find(word);
-			if (it == dict.end())
-			{
-				dict.emplace(word, (Vid)dict.size());
-				id2word.emplace_back(word);
-				return (Vid)(dict.size() - 1);
-			}
-			return it->second;
-		}
+
+		Dictionary();
+		~Dictionary();
+
+		Dictionary(const Dictionary&);
+		Dictionary& operator=(const Dictionary&);
+
+		Dictionary(Dictionary&&) noexcept;
+		Dictionary& operator=(Dictionary&&) noexcept;
+
+		Vid add(const std::string& word);
 
 		size_t size() const { return dict.size(); }
 		
-		const std::string& toWord(Vid vid) const
-		{
-			assert(vid < id2word.size());
-			return id2word[vid];
-		}
+		const std::string& toWord(Vid vid) const;
 		
-		Vid toWid(const std::string& word) const
-		{
-			auto it = dict.find(word);
-			if (it == dict.end()) return non_vocab_id;
-			return it->second;
-		}
+		Vid toWid(const std::string& word) const;
 
-		void serializerWrite(std::ostream& writer) const
-		{
-			serializer::writeMany(writer, serializer::to_key("Dict"), id2word);
-		}
+		void serializerWrite(std::ostream& writer) const;
 		
-		void serializerRead(std::istream& reader)
-		{
-			serializer::readMany(reader, serializer::to_key("Dict"), id2word);
-			for (size_t i = 0; i < id2word.size(); ++i)
-			{
-				dict.emplace(id2word[i], (Vid)i);
-			}
-		}
+		void serializerRead(std::istream& reader);
 
-		void swap(Dictionary& rhs)
-		{
-			std::swap(dict, rhs.dict);
-			std::swap(id2word, rhs.id2word);
-		}
+		void swap(Dictionary& rhs);
 
-		void reorder(const std::vector<Vid>& order)
-		{
-			for (auto& p : dict)
-			{
-				p.second = order[p.second];
-				id2word[p.second] = p.first;
-			}
-		}
+		void reorder(const std::vector<Vid>& order);
 
-		const std::vector<std::string>& getRaw() const
-		{
-			return id2word;
-		}
+		const std::vector<std::string>& getRaw() const;
 
-		Vid mapToNewDict(Vid v, const Dictionary& newDict) const
-		{
-			return newDict.toWid(toWord(v));
-		}
+		Vid mapToNewDict(Vid v, const Dictionary& newDict) const;
 
-		std::vector<Vid> mapToNewDict(const std::vector<Vid>& v, const Dictionary& newDict) const
-		{
-			std::vector<Vid> r(v.size());
-			for (size_t i = 0; i < v.size(); ++i)
-			{
-				r[i] = mapToNewDict(v[i], newDict);
-			}
-			return r;
-		}
+		std::vector<Vid> mapToNewDict(const std::vector<Vid>& v, const Dictionary& newDict) const;
 
-		std::vector<Vid> mapToNewDictAdd(const std::vector<Vid>& v, Dictionary& newDict) const
-		{
-			std::vector<Vid> r(v.size());
-			for (size_t i = 0; i < v.size(); ++i)
-			{
-				r[i] = mapToNewDict(v[i], newDict);
-			}
-			return r;
-		}
+		std::vector<Vid> mapToNewDictAdd(const std::vector<Vid>& v, Dictionary& newDict) const;
 	};
 
 }
@@ -127,4 +75,4 @@ namespace std
 			return hash<size_t>{}(p.first) ^ hash<size_t>{}(p.second);
 		}
 	};
-}
\ No newline at end of file
+}
diff --git a/src/Utils/Mmap.cpp b/src/Utils/Mmap.cpp
new file mode 100644
index 0000000..ddfe6c3
--- /dev/null
+++ b/src/Utils/Mmap.cpp
@@ -0,0 +1,146 @@
+#include <cstdint>
+#include "Mmap.h"
+
+namespace tomoto
+{
+	namespace utils
+	{
+		static std::u16string utf8To16(const std::string& str)
+		{
+			std::u16string ret;
+			for (auto it = str.begin(); it != str.end(); ++it)
+			{
+				uint32_t code = 0;
+				uint32_t byte = (uint8_t)*it;
+				if ((byte & 0xF8) == 0xF0)
+				{
+					code = (uint32_t)((byte & 0x07) << 18);
+					if (++it == str.end()) throw std::invalid_argument{ "unexpected ending" };
+					if (((byte = *it) & 0xC0) != 0x80) throw std::invalid_argument{ "unexpected trailing byte" };
+					code |= (uint32_t)((byte & 0x3F) << 12);
+					if (++it == str.end()) throw std::invalid_argument{ "unexpected ending" };
+					if (((byte = *it) & 0xC0) != 0x80) throw std::invalid_argument{ "unexpected trailing byte" };
+					code |= (uint32_t)((byte & 0x3F) << 6);
+					if (++it == str.end()) throw std::invalid_argument{ "unexpected ending" };
+					if (((byte = *it) & 0xC0) != 0x80) throw std::invalid_argument{ "unexpected trailing byte" };
+					code |= (byte & 0x3F);
+				}
+				else if ((byte & 0xF0) == 0xE0)
+				{
+					code = (uint32_t)((byte & 0x0F) << 12);
+					if (++it == str.end()) throw std::invalid_argument{ "unexpected ending" };
+					if (((byte = *it) & 0xC0) != 0x80) throw std::invalid_argument{ "unexpected trailing byte" };
+					code |= (uint32_t)((byte & 0x3F) << 6);
+					if (++it == str.end()) throw std::invalid_argument{ "unexpected ending" };
+					if (((byte = *it) & 0xC0) != 0x80) throw std::invalid_argument{ "unexpected trailing byte" };
+					code |= (byte & 0x3F);
+				}
+				else if ((byte & 0xE0) == 0xC0)
+				{
+					code = (uint32_t)((byte & 0x1F) << 6);
+					if (++it == str.end()) throw std::invalid_argument{ "unexpected ending" };
+					if (((byte = *it) & 0xC0) != 0x80) throw std::invalid_argument{ "unexpected trailing byte" };
+					code |= (byte & 0x3F);
+				}
+				else if ((byte & 0x80) == 0x00)
+				{
+					code = byte;
+				}
+				else
+				{
+					throw std::invalid_argument{ "unicode error" };
+				}
+
+				if (code < 0x10000)
+				{
+					ret.push_back((char16_t)code);
+				}
+				else if (code < 0x10FFFF)
+				{
+					code -= 0x10000;
+					ret.push_back((char16_t)(0xD800 | (code >> 10)));
+					ret.push_back((char16_t)(0xDC00 | (code & 0x3FF)));
+				}
+				else
+				{
+					throw std::invalid_argument{ "unicode error" };
+				}
+			}
+			return ret;
+		}
+	}
+}
+
+namespace tomoto
+{
+	namespace utils
+	{
+		MMap::MMap(const std::string& filepath)
+		{
+#ifdef _WIN32
+			hFile = CreateFileW((const wchar_t*)utf8To16(filepath).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
+			if (hFile == INVALID_HANDLE_VALUE) throw std::ios_base::failure("Cannot open '" + filepath + "'");
+			hFileMap = CreateFileMapping(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
+			if (hFileMap == nullptr) throw std::ios_base::failure("Cannot open '" + filepath + "' Code:" + std::to_string(GetLastError()));
+			view = (const char*)MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
+			if (!view) throw std::ios_base::failure("Cannot MapViewOfFile() Code:" + std::to_string(GetLastError()));
+			DWORD high;
+			len = GetFileSize(hFile, &high);
+			len |= (uint64_t)high << 32;
+#else 
+			fd = open(filepath.c_str(), O_RDONLY);
+			if (fd == -1) throw std::ios_base::failure("Cannot open '" + filepath + "'");
+			struct stat sb;
+			if (fstat(fd, &sb) < 0) throw std::ios_base::failure("Cannot open '" + filepath + "'");
+			len = sb.st_size;
+			view = (const char*)mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0);
+			if (view == MAP_FAILED) throw std::ios_base::failure("Mapping failed");
+#endif
+		}
+
+#ifdef _WIN32
+		MMap::MMap(MMap&& o) noexcept
+			: view{ o.view }, len{ o.len }
+		{
+			o.view = nullptr;
+			std::swap(hFile, o.hFile);
+			std::swap(hFileMap, o.hFileMap);
+		}
+#else 
+		MMap::MMap(MMap&& o) noexcept 
+			: len{ o.len }, fd{ std::move(o.fd) }
+		{
+			std::swap(view, o.view);
+		}
+#endif
+
+		MMap& MMap::operator=(MMap&& o) noexcept
+		{
+			std::swap(view, o.view);
+			std::swap(len, o.len);
+#ifdef _WIN32
+			std::swap(hFile, o.hFile);
+			std::swap(hFileMap, o.hFileMap);
+#else 
+			std::swap(fd, o.fd);
+#endif
+			return *this;
+		}
+
+		MMap::~MMap()
+		{
+#ifdef _WIN32
+			if (hFileMap)
+			{
+				UnmapViewOfFile(view);
+				view = nullptr;
+			}
+#else 
+			if (view)
+			{
+				munmap((void*)view, len);
+			}
+#endif
+		}
+	}
+}
diff --git a/src/Utils/Mmap.h b/src/Utils/Mmap.h
new file mode 100644
index 0000000..b6ad05e
--- /dev/null
+++ b/src/Utils/Mmap.h
@@ -0,0 +1,139 @@
+#pragma once
+#include <string>
+#include <iostream>
+
+#ifdef _WIN32
+#define NOMINMAX
+#include <Windows.h>
+namespace tomoto
+{
+	namespace utils
+	{
+		namespace detail
+		{
+			class HandleGuard
+			{
+				HANDLE handle = nullptr;
+			public:
+				HandleGuard(HANDLE _handle = nullptr) : handle(_handle)
+				{
+				}
+
+				HandleGuard(const HandleGuard&) = delete;
+				HandleGuard& operator =(const HandleGuard&) = delete;
+
+				HandleGuard(HandleGuard&& o) noexcept
+				{
+					std::swap(handle, o.handle);
+				}
+
+				HandleGuard& operator=(HandleGuard&& o) noexcept
+				{
+					std::swap(handle, o.handle);
+					return *this;
+				}
+
+				~HandleGuard()
+				{
+					if (handle && handle != INVALID_HANDLE_VALUE)
+					{
+						CloseHandle(handle);
+						handle = nullptr;
+					}
+				}
+
+				operator HANDLE() const
+				{
+					return handle;
+				}
+			};
+		}
+
+		class MMap
+		{
+			const char* view = nullptr;
+			uint64_t len = 0;
+			detail::HandleGuard hFile, hFileMap;
+		public:
+			MMap(const std::string& filepath);
+			MMap(const MMap&) = delete;
+			MMap& operator=(const MMap&) = delete;
+			MMap(MMap&& o) noexcept;
+			MMap& operator=(MMap&& o) noexcept;
+			~MMap();
+
+			const char* get() const { return view; }
+			size_t size() const { return len; }
+		};
+	}
+}
+#else
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+namespace tomoto
+{
+	namespace utils
+	{
+		namespace detail
+		{
+			class FDGuard
+			{
+				int fd = 0;
+			public:
+				FDGuard(int _fd = 0) : fd(_fd)
+				{
+				}
+
+				FDGuard(const FDGuard&) = delete;
+				FDGuard& operator =(const FDGuard&) = delete;
+
+				FDGuard(FDGuard&& o)
+				{
+					std::swap(fd, o.fd);
+				}
+
+				FDGuard& operator=(FDGuard&& o)
+				{
+					std::swap(fd, o.fd);
+					return *this;
+				}
+
+				~FDGuard()
+				{
+					if (fd && fd != -1)
+					{
+						close(fd);
+						fd = 0;
+					}
+				}
+
+				operator int() const
+				{
+					return fd;
+				}
+			};
+		}
+
+		class MMap
+		{
+			const char* view = nullptr;
+			size_t len = 0;
+			detail::FDGuard fd;
+		public:
+			MMap(const std::string& filepath);
+			MMap(const MMap&) = delete;
+			MMap& operator=(const MMap&) = delete;
+			MMap(MMap&& o) noexcept;
+			MMap& operator=(MMap&& o) noexcept;
+			~MMap();
+
+			const char* get() const { return view; }
+			size_t size() const { return len; }
+		};
+	}
+}
+#endif
diff --git a/src/Utils/SharedString.cpp b/src/Utils/SharedString.cpp
new file mode 100644
index 0000000..28763ec
--- /dev/null
+++ b/src/Utils/SharedString.cpp
@@ -0,0 +1,134 @@
+#include "SharedString.h"
+
+namespace tomoto
+{
+    void SharedString::incref()
+    {
+        if (ptr)
+        {
+            ++*(size_t*)ptr;
+        }
+    }
+
+    void SharedString::decref()
+    {
+        if (ptr)
+        {
+            if (--*(size_t*)ptr == 0)
+            {
+                delete[] ptr;
+                ptr = nullptr;
+            }
+        }
+    }
+
+    void SharedString::init(const char* _begin, const char* _end)
+    {
+        ptr = new char[_end - _begin + 9];
+        *(size_t*)ptr = 1;
+        len = _end - _begin;
+        std::memcpy((void*)(ptr + 8), _begin, _end - _begin);
+        ((char*)ptr)[_end - _begin + 8] = 0;
+    }
+
+    SharedString::SharedString()
+    {
+    }
+
+    SharedString::SharedString(const char* _begin, const char* _end)
+    {
+        init(_begin, _end);
+    }
+
+    SharedString::SharedString(const char* _ptr)
+    {
+        if (_ptr)
+        {
+            init(_ptr, _ptr + std::strlen(_ptr));
+        }
+    }
+
+    SharedString::SharedString(const std::string& str)
+    {
+        if (!str.empty())
+        {
+            init(str.data(), str.data() + str.size());
+        }
+    }
+
+    SharedString::SharedString(const SharedString& o) noexcept
+        : ptr{ o.ptr }, len{ o.len }
+    {
+        incref();
+    }
+
+    SharedString::SharedString(SharedString&& o) noexcept
+    {
+        std::swap(ptr, o.ptr);
+        std::swap(len, o.len);
+    }
+
+    SharedString::~SharedString()
+    {
+        decref();
+    }
+
+    SharedString& SharedString::operator=(const SharedString& o)
+    {
+        if (this != &o)
+        {
+            decref();
+            ptr = o.ptr;
+            len = o.len;
+            incref();
+        }
+        return *this;
+    }
+
+    SharedString& SharedString::operator=(SharedString&& o) noexcept
+    {
+        std::swap(ptr, o.ptr);
+        std::swap(len, o.len);
+        return *this;
+    }
+
+    SharedString::operator std::string() const
+    {
+        if (!ptr) return {};
+        return { ptr + 8, ptr + 8 + len };
+    }
+
+    const char* SharedString::c_str() const
+    {
+        if (!ptr) return "";
+        return ptr + 8;
+    }
+
+    std::string SharedString::substr(size_t start, size_t len) const
+    {
+        return { c_str() + start, c_str() + start + len };
+    }
+
+    bool SharedString::operator==(const SharedString& o) const
+    {
+        if (ptr == o.ptr) return true;
+        if (size() != o.size()) return false;
+        return std::equal(begin(), end(), o.begin());
+    }
+
+    bool SharedString::operator==(const std::string& o) const
+    {
+        if (size() != o.size()) return false;
+        return std::equal(begin(), end(), o.begin());
+    }
+
+    bool SharedString::operator!=(const SharedString& o) const
+    {
+        return !operator==(o);
+    }
+
+    bool SharedString::operator!=(const std::string& o) const
+    {
+        return !operator==(o);
+    }
+}
diff --git a/src/Utils/SharedString.h b/src/Utils/SharedString.h
new file mode 100644
index 0000000..2366b09
--- /dev/null
+++ b/src/Utils/SharedString.h
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <string>
+#include "serializer.hpp"
+
+namespace tomoto
+{
+	class SharedString
+	{
+		const char* ptr = nullptr;
+		size_t len = 0;
+
+		void incref();
+
+		void decref();
+
+		void init(const char* _begin, const char* _end);
+
+	public:
+
+		SharedString();
+		explicit SharedString(const char* _begin, const char* _end);
+		explicit SharedString(const char* _ptr);
+		explicit SharedString(const std::string& str);
+		SharedString(const SharedString& o) noexcept;
+		SharedString(SharedString&& o) noexcept;
+		~SharedString();
+		SharedString& operator=(const SharedString& o);
+		SharedString& operator=(SharedString&& o) noexcept;
+
+		size_t size() const
+		{
+			if (ptr) return len;
+			return 0;
+		}
+
+		bool empty() const
+		{
+			return size() == 0;
+		}
+
+		operator std::string() const;
+
+		const char* c_str() const;
+
+		const char* data() const
+		{
+			return c_str();
+		}
+
+		const char* begin() const
+		{
+			return data();
+		}
+
+		const char* end() const
+		{
+			return data() + size();
+		}
+
+		std::string substr(size_t start, size_t len) const;
+
+		bool operator==(const SharedString& o) const;
+		bool operator==(const std::string& o) const;
+
+		bool operator!=(const SharedString& o) const;
+		bool operator!=(const std::string& o) const;
+	};
+
+	namespace serializer
+	{
+		template<>
+		struct Serializer<SharedString>
+		{
+			using VTy = SharedString;
+			void write(std::ostream& ostr, const VTy& v)
+			{
+				writeToStream(ostr, (uint32_t)v.size());
+				if (!ostr.write((const char*)v.data(), v.size()))
+					throw std::ios_base::failure(std::string("writing type 'SharedString' is failed"));
+			}
+
+			void read(std::istream& istr, VTy& v)
+			{
+				auto size = readFromStream<uint32_t>(istr);
+				std::vector<char> t(size);
+				if (!istr.read((char*)t.data(), t.size()))
+					throw std::ios_base::failure(std::string("reading type 'SharedString' is failed"));
+				v = SharedString{ t.data(), t.data() + t.size() };
+			}
+		};
+	}
+}
+
+namespace std
+{
+	template <> struct hash<tomoto::SharedString>
+	{
+		size_t operator()(const tomoto::SharedString& x) const
+		{
+			return hash<string>{}(x);
+		}
+	};
+}
diff --git a/src/Utils/SharedString.hpp b/src/Utils/SharedString.hpp
deleted file mode 100644
index 0ec3634..0000000
--- a/src/Utils/SharedString.hpp
+++ /dev/null
@@ -1,206 +0,0 @@
-#pragma once
-
-#include <string>
-#include "serializer.hpp"
-
-namespace tomoto
-{
-	class SharedString
-	{
-		const char* ptr = nullptr;
-		size_t len = 0;
-
-		void incref()
-		{
-			if (ptr)
-			{
-				++*(size_t*)ptr;
-			}
-		}
-
-		void decref()
-		{
-			if (ptr)
-			{
-				if (--*(size_t*)ptr == 0)
-				{
-					delete[] ptr;
-					ptr = nullptr;
-				}
-			}
-		}
-
-		void init(const char* _begin, const char* _end)
-		{
-			ptr = new char[_end - _begin + 9];
-			*(size_t*)ptr = 1;
-			len = _end - _begin;
-			std::memcpy((void*)(ptr + 8), _begin, _end - _begin);
-			((char*)ptr)[_end - _begin + 8] = 0;
-		}
-
-	public:
-
-		SharedString()
-		{
-		}
-
-		explicit SharedString(const char* _begin, const char* _end)
-		{
-			init(_begin, _end);
-		}
-
-		explicit SharedString(const char* _ptr)
-		{
-			if (_ptr)
-			{
-				init(_ptr, _ptr + std::strlen(_ptr));
-			}
-		}
-
-		explicit SharedString(const std::string& str)
-		{
-			if (!str.empty())
-			{
-				init(str.data(), str.data() + str.size());
-			}
-		}
-
-		SharedString(const SharedString& o) noexcept
-			: ptr{ o.ptr }, len{ o.len }
-		{
-			incref();
-		}
-
-		SharedString(SharedString&& o) noexcept
-		{
-			std::swap(ptr, o.ptr);
-			std::swap(len, o.len);
-		}
-
-		~SharedString()
-		{
-			decref();
-		}
-
-		SharedString& operator=(const SharedString& o)
-		{
-			if (this != &o)
-			{
-				decref();
-				ptr = o.ptr;
-				len = o.len;
-				incref();
-			}
-			return *this;
-		}
-
-		SharedString& operator=(SharedString&& o) noexcept
-		{
-			std::swap(ptr, o.ptr);
-			std::swap(len, o.len);
-			return *this;
-		}
-
-		size_t size() const
-		{
-			if (ptr) return len;
-			return 0;
-		}
-
-		bool empty() const
-		{
-			return ptr == nullptr || size() == 0;
-		}
-
-		operator std::string() const
-		{
-			if (!ptr) return {};
-			return { ptr + 8, ptr + 8 + len };
-		}
-
-		const char* c_str() const
-		{
-			if (!ptr) return "";
-			return ptr + 8;
-		}
-
-		const char* data() const
-		{
-			return c_str();
-		}
-
-		const char* begin() const
-		{
-			return data();
-		}
-
-		const char* end() const
-		{
-			return data() + size();
-		}
-
-		std::string substr(size_t start, size_t len) const
-		{
-			return { c_str() + start, c_str() + start + len };
-		}
-
-		bool operator==(const SharedString& o) const
-		{
-			if (ptr == o.ptr) return true;
-			if (size() != o.size()) return false;
-			return std::equal(begin(), end(), o.begin());
-		}
-
-		bool operator==(const std::string& o) const
-		{
-			if (size() != o.size()) return false;
-			return std::equal(begin(), end(), o.begin());
-		}
-
-		bool operator!=(const SharedString& o) const
-		{
-			return !operator==(o);
-		}
-
-		bool operator!=(const std::string& o) const
-		{
-			return !operator==(o);
-		}
-	};
-
-	namespace serializer
-	{
-		template<>
-		struct Serializer<SharedString>
-		{
-			using VTy = SharedString;
-			void write(std::ostream& ostr, const VTy& v)
-			{
-				writeToStream(ostr, (uint32_t)v.size());
-				if (!ostr.write((const char*)v.data(), v.size()))
-					throw std::ios_base::failure(std::string("writing type 'SharedString' is failed"));
-			}
-
-			void read(std::istream& istr, VTy& v)
-			{
-				auto size = readFromStream<uint32_t>(istr);
-				std::vector<char> t(size);
-				if (!istr.read((char*)t.data(), t.size()))
-					throw std::ios_base::failure(std::string("reading type 'SharedString' is failed"));
-				v = SharedString{ t.data(), t.data() + t.size() };
-			}
-		};
-	}
-}
-
-namespace std
-{
-	template <> struct hash<tomoto::SharedString>
-	{
-		size_t operator()(const tomoto::SharedString& x) const
-		{
-			return hash<string>{}(x);
-		}
-	};
-}
diff --git a/src/Utils/serializer.cpp b/src/Utils/serializer.cpp
new file mode 100644
index 0000000..beca9be
--- /dev/null
+++ b/src/Utils/serializer.cpp
@@ -0,0 +1,143 @@
+#include "serializer.hpp"
+
+namespace tomoto
+{
+	namespace serializer
+	{
+		membuf::membuf(bool read, bool write, char* base, std::ptrdiff_t n)
+		{
+			if (read)
+			{
+				this->setg(base, base, base + n);
+			}
+
+			if (write)
+			{
+				this->setp(base, base + n);
+			}
+		}
+
+		membuf::~membuf() = default;
+
+		std::streampos membuf::seekpos(pos_type sp, std::ios_base::openmode which) 
+		{
+			return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which);
+		}
+
+		std::streampos membuf::seekoff(off_type off,
+			std::ios_base::seekdir dir,
+			std::ios_base::openmode which
+		) 
+		{
+			if (which & std::ios_base::in)
+			{
+				if (dir == std::ios_base::cur)
+					gbump(off);
+				else if (dir == std::ios_base::end)
+					setg(eback(), egptr() + off, egptr());
+				else if (dir == std::ios_base::beg)
+					setg(eback(), eback() + off, egptr());
+			}
+			if (which & std::ios_base::out)
+			{
+				if (dir == std::ios_base::cur)
+					pbump(off);
+				else if (dir == std::ios_base::end)
+					setp(epptr() + off, epptr());
+				else if (dir == std::ios_base::beg)
+					setp(pbase() + off, epptr());
+
+				if (!(which & std::ios_base::in))
+				{
+					return pptr() - pbase();
+				}
+			}
+			return gptr() - eback();
+		}
+
+		imstream::imstream(const char* base, std::ptrdiff_t n)
+			: std::istream(&buf), buf(true, false, (char*)base, n)
+		{
+		}
+
+		imstream::~imstream() = default;
+
+		omstream::omstream(char* base, std::ptrdiff_t n)
+			: std::ostream(&buf), buf(false, true, (char*)base, n)
+		{
+		}
+
+		omstream::~omstream() = default;
+
+
+		BlockStreamBuffer::BlockStreamBuffer(size_t _block_size) : block_size{ _block_size }
+		{
+			buffers.emplace_back(std::make_unique<uint8_t[]>(block_size));
+			this->setp((char*)buffers.back().get(), (char*)buffers.back().get() + block_size);
+		}
+
+		BlockStreamBuffer::~BlockStreamBuffer() = default;
+
+		int BlockStreamBuffer::overflow(int c)
+		{
+			if (this->pptr() == this->epptr())
+			{
+				buffers.emplace_back(std::make_unique<uint8_t[]>(block_size));
+				this->setp((char*)buffers.back().get(), (char*)buffers.back().get() + block_size);
+			}
+			else
+			{
+				*(this->pptr()) = c;
+				this->pbump(1);
+			}
+			return c;
+		}
+
+		std::streamsize BlockStreamBuffer::xsputn(const char* s, std::streamsize n)
+		{
+			auto rest = n;
+			auto buf_remain = this->epptr() - this->pptr();
+			while (rest > buf_remain)
+			{
+				std::copy(s, s + buf_remain, this->pptr());
+				this->pbump(buf_remain);
+				buffers.emplace_back(std::make_unique<uint8_t[]>(block_size));
+				this->setp((char*)buffers.back().get(), (char*)buffers.back().get() + block_size);
+				rest -= buf_remain;
+				s += buf_remain;
+				buf_remain = block_size;
+			}
+			std::copy(s, s + rest, this->pptr());
+			this->pbump(rest);
+			return n;
+		}
+
+		size_t BlockStreamBuffer::totalSize() const
+		{
+			return (buffers.size() - 1) * block_size + (this->pptr() - this->pbase());
+		}
+
+		TaggedDataMap readTaggedDataMap(std::istream& istr, uint32_t version)
+		{
+			std::unordered_map<std::string, std::pair<std::streampos, std::streampos>> ret;
+			TaggedDataHeader h;
+			do
+			{
+				istr.read((char*)&h, sizeof(h));
+				if (h.key != taggedDataKeyUint)
+				{
+					throw UnfitException("tagged data key is not found");
+				}
+				const std::streampos totsize_pos = istr.tellg() - (std::streamoff)16;
+				std::array<char, 256> key;
+				istr.read(key.data(), h.keysize);
+				const std::streampos start_pos = istr.tellg();
+				const std::streampos end_pos = totsize_pos + (std::streamoff)h.totsize;
+				ret.emplace(std::string{ key.data(), h.keysize }, std::make_pair(start_pos, end_pos));
+				ret[""] = std::make_pair(start_pos, end_pos);
+				istr.seekg(end_pos);
+			} while (h.trailing_cnt);
+			return ret;
+		}
+	}
+}
diff --git a/src/Utils/serializer.hpp b/src/Utils/serializer.hpp
index bb356d4..b81c1a5 100644
--- a/src/Utils/serializer.hpp
+++ b/src/Utils/serializer.hpp
@@ -9,6 +9,7 @@
 #include <unordered_map>
 #include <Eigen/Dense>
 #include "text.hpp"
+#include "Utils.hpp"
 
 /*
 
@@ -30,36 +31,51 @@ namespace tomoto
 {
 	namespace serializer
 	{
-		struct membuf : std::streambuf 
+		struct membuf : public std::streambuf
 		{
-			membuf(char* base, std::ptrdiff_t n) 
+			membuf(bool read, bool write, char* base, std::ptrdiff_t n);
+			~membuf();
+
+			std::streampos seekpos(pos_type sp, std::ios_base::openmode which) override;
+
+			std::streampos seekoff(off_type off,
+				std::ios_base::seekdir dir,
+				std::ios_base::openmode which = std::ios_base::in
+			) override;
+
+			const char* curptr() const
 			{
-				this->setg(base, base, base + n);
+				return this->gptr();
 			}
+		};
 
-			pos_type seekpos(pos_type sp, std::ios_base::openmode which) override {
-				return seekoff(sp - pos_type(off_type(0)), std::ios_base::beg, which);
+		class imstream : public std::istream
+		{
+			membuf buf;
+		public:
+			imstream(const char* base, std::ptrdiff_t n);
+			~imstream();
+
+			template<class Ty>
+			imstream(const Ty& m) : imstream(m.get(), m.size())
+			{
 			}
 
-			pos_type seekoff(off_type off,
-				std::ios_base::seekdir dir,
-				std::ios_base::openmode which = std::ios_base::in) override {
-				if (dir == std::ios_base::cur)
-					gbump(off);
-				else if (dir == std::ios_base::end)
-					setg(eback(), egptr() + off, egptr());
-				else if (dir == std::ios_base::beg)
-					setg(eback(), eback() + off, egptr());
-				return gptr() - eback();
+			const char* curptr() const
+			{
+				return buf.curptr();
 			}
 		};
 
-		class imstream : public std::istream
+		class omstream : public std::ostream
 		{
 			membuf buf;
 		public:
-			imstream(const char* base, std::ptrdiff_t n)
-				: std::istream(&buf), buf((char*)base, n)
+			omstream(char* base, std::ptrdiff_t n);
+			~omstream();
+
+			template<class Ty>
+			omstream(const Ty& m) : omstream(m.get(), m.size())
 			{
 			}
 		};
@@ -637,89 +653,91 @@ namespace tomoto
 			}
 		};
 
-		static auto taggedDataKey = to_key("TPTK");
-
-		template<size_t _len, typename _Ty>
-		inline void writeTaggedData(std::ostream& ostr, uint32_t version, uint32_t trailing_cnt, const Key<_len>& key, const _Ty& data)
+		class BlockStreamBuffer : public std::basic_streambuf<char>
 		{
-			uint16_t major = version >> 16, minor = version & 0xFFFF;
-			writeMany(ostr, taggedDataKey, version);
-			std::streampos totsize_pos = ostr.tellp();
-			writeMany(ostr, (uint64_t)0, (uint32_t)_len, trailing_cnt, key, data);
-			std::streampos end_pos = ostr.tellp();
-			ostr.seekp(totsize_pos);
-			writeMany(ostr, (uint64_t)(end_pos - totsize_pos));
-			ostr.seekp(end_pos);
-		}
+			std::vector<std::unique_ptr<uint8_t[]>> buffers;
+			size_t block_size = 0;
+		public:
+			BlockStreamBuffer(size_t _block_size = 4096);
+			~BlockStreamBuffer();
 
-		template<size_t _len, typename _Ty>
-		inline std::pair<bool, std::streampos> readTaggedData(std::istream& istr, uint32_t version, uint32_t& trailing_cnt, const Key<_len>& key, _Ty& data)
+			int overflow(int c) override;
+
+			std::streamsize xsputn(const char* s, std::streamsize n) override;
+
+			size_t totalSize() const;
+
+			template<class Fn>
+			void iterateBuffers(Fn fn) const
+			{
+				for (size_t i = 0; i < buffers.size() - 1; ++i)
+				{
+					fn(buffers[i].get(), block_size);
+				}
+				fn(buffers.back().get(), this->pptr() - this->pbase());
+			}
+		};
+
+		static constexpr uint32_t taggedDataKeyUint = 0x4b545054; // "TPTK"
+
+		struct TaggedDataHeader
 		{
-			uint16_t major = version >> 16, minor = version & 0xFFFF;
+			uint32_t key;
+			uint32_t version;
 			uint64_t totsize;
 			uint32_t keysize;
-			std::streampos start_pos = istr.tellg();
-			readMany(istr, taggedDataKey, version);
-			std::streampos totsize_pos = istr.tellg();
-			readMany(istr, totsize, keysize, trailing_cnt);
-			std::streampos end_pos = totsize_pos + (std::streamoff)totsize;
-			if (_len != keysize)
-			{
-				istr.seekg(start_pos);
-				return std::make_pair(false, end_pos);
-			}
-			
-			if (!readTest(istr, key))
-			{
-				istr.seekg(start_pos);
-				return std::make_pair(false, end_pos);
-			}
-			
-			readMany(istr, data);
-			if (end_pos != istr.tellg())
+			uint32_t trailing_cnt;
+		};
+
+		template<size_t _len, typename _Ty>
+		inline void writeTaggedData(std::ostream& ostr, uint32_t version, uint32_t trailing_cnt, const Key<_len>& key, const _Ty& data)
+		{
+			BlockStreamBuffer buf;
+			std::ostream serialized_data(&buf);
+			writeMany(serialized_data, key, data);
+			const auto key_data_size = buf.totalSize();
+
+			TaggedDataHeader h;
+			h.key = taggedDataKeyUint;
+			h.version = version;
+			h.totsize = key_data_size + 16;
+			h.keysize = key.size();
+			h.trailing_cnt = trailing_cnt;
+
+			ostr.write((const char*)&h, sizeof(h));
+			buf.iterateBuffers([&](const void* data, size_t size)
 			{
-				istr.seekg(start_pos);
-				return std::make_pair(false, end_pos);
-			}
-			return std::make_pair(true, end_pos);
+				ostr.write((const char*)data, size);
+			});
 		}
 
-		inline void readTaggedMany(std::istream& istr, uint32_t version)
+		using TaggedDataMap = std::unordered_map<std::string, std::pair<std::streampos, std::streampos>>;
+
+		TaggedDataMap readTaggedDataMap(std::istream& istr, uint32_t version);
+
+		inline void readTaggedMany(std::istream& istr, const TaggedDataMap& data_map, uint32_t version)
 		{
 			// seek to the end of tagged data list
-			uint32_t trailing_cnt;
-			do
-			{
-				uint64_t totsize;
-				uint32_t keysize;
-				readMany(istr, taggedDataKey, version);
-				std::streampos totsize_pos = istr.tellg();
-				readMany(istr, totsize, keysize, trailing_cnt);
-				istr.seekg(totsize_pos + (std::streamoff)totsize);
-
-			} while (trailing_cnt);
+			istr.seekg(data_map.find("")->second.second);
 		}
 
 		template<size_t _len, typename _Ty, typename ... _Rest>
-		inline void readTaggedMany(std::istream& istr, uint32_t version, const Key<_len>& key, _Ty& data, _Rest&&... rest)
+		inline void readTaggedMany(std::istream& istr, const TaggedDataMap& data_map, uint32_t version, const Key<_len>& key, _Ty& data, _Rest&&... rest)
 		{
-			auto start_pos = istr.tellg();
-			uint32_t trailing_cnt;
-			do
+			auto it = data_map.find(key.str());
+			if (it != data_map.end())
 			{
-				std::pair<bool, std::streampos> p = readTaggedData(istr, version, trailing_cnt, key, data);
-				if (p.first)
-				{
-					break;
-				}
-				else
-				{
-					istr.seekg(p.second);
-				}
-			} while (trailing_cnt);
+				istr.seekg(it->second.first);
+				readMany(istr, data);
+			}
+			readTaggedMany(istr, data_map, version, std::forward<_Rest>(rest)...);
+		}
 
-			istr.seekg(start_pos);
-			readTaggedMany(istr, version, std::forward<_Rest>(rest)...);
+		template<typename ... _Rest>
+		inline void readTaggedMany(std::istream& istr, uint32_t version, _Rest&&... rest)
+		{
+			const auto data_map = readTaggedDataMap(istr, version);
+			readTaggedMany(istr, data_map, version, std::forward<_Rest>(rest)...);
 		}
 
 		inline void writeTaggedMany(std::ostream& ostr, uint32_t version)
@@ -786,6 +804,17 @@ void serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ost
 	tomoto::serializer::writeMany(ostr, __VA_ARGS__);\
 }
 
+#define DEFINE_OUT_SERIALIZER_AFTER_BASE_WITH_VERSION(cls, base, v, ...) template<TermWeight _tw> void cls<_tw>::serializerRead(tomoto::serializer::version_holder<v> _v, std::istream& istr)\
+{\
+	base::serializerRead(_v, istr);\
+	tomoto::serializer::readMany(istr, __VA_ARGS__);\
+}\
+template<TermWeight _tw> void cls<_tw>::serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ostr) const\
+{\
+	base::serializerWrite(_v, ostr);\
+	tomoto::serializer::writeMany(ostr, __VA_ARGS__);\
+}
+
 #define DEFINE_SERIALIZER_BASE_WITH_VERSION(base, v) void serializerRead(tomoto::serializer::version_holder<v> _v, std::istream& istr)\
 {\
 	base::serializerRead(_v, istr);\
@@ -795,6 +824,19 @@ void serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ost
 	base::serializerWrite(_v, ostr);\
 }
 
+#define DEFINE_OUT_SERIALIZER_BASE_WITH_VERSION(cls, base, v) template<TermWeight _tw> void cls<_tw>::serializerRead(tomoto::serializer::version_holder<v> _v, std::istream& istr)\
+{\
+	base::serializerRead(_v, istr);\
+}\
+template<TermWeight _tw> void cls<_tw>::serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ostr) const\
+{\
+	base::serializerWrite(_v, ostr);\
+}
+
+
+#define DECLARE_SERIALIZER_WITH_VERSION(v) void serializerRead(tomoto::serializer::version_holder<v> _v, std::istream& istr);\
+void serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ostr) const;
+
 #define DEFINE_SERIALIZER_AFTER_BASE_CALLBACK(base, onRead, ...) void serializerRead(std::istream& istr)\
 {\
 	base::serializerRead(istr);\
@@ -863,3 +905,14 @@ void serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ost
 	base::serializerWrite(_v, ostr);\
 	tomoto::serializer::writeTaggedMany(ostr, t, _TO_KEY_VALUE(__VA_ARGS__));\
 }
+
+#define DEFINE_OUT_TAGGED_SERIALIZER_AFTER_BASE_WITH_VERSION(cls, base, v, t,...) template<TermWeight _tw> void cls<_tw>::serializerRead(tomoto::serializer::version_holder<v> _v, std::istream& istr)\
+{\
+	base::serializerRead(_v, istr);\
+	tomoto::serializer::readTaggedMany(istr, t, _TO_KEY_VALUE(__VA_ARGS__));\
+}\
+template<TermWeight _tw> void cls<_tw>::serializerWrite(tomoto::serializer::version_holder<v> _v, std::ostream& ostr) const\
+{\
+	base::serializerWrite(_v, ostr);\
+	tomoto::serializer::writeTaggedMany(ostr, t, _TO_KEY_VALUE(__VA_ARGS__));\
+}
diff --git a/src/python/py_rt.cpp b/src/python/dispatcher/py_rt.cpp
similarity index 99%
rename from src/python/py_rt.cpp
rename to src/python/dispatcher/py_rt.cpp
index 0f0f718..d3adb67 100644
--- a/src/python/py_rt.cpp
+++ b/src/python/dispatcher/py_rt.cpp
@@ -13,8 +13,6 @@
 #include <Python.h>
 #endif
 
-using namespace std;
-
 #if defined(_WIN32) || defined(_WIN64)
 #include <Windows.h>
 #include <immintrin.h>
@@ -41,6 +39,8 @@ void cpuid(int info[4], int InfoType) {
 
 PyMODINIT_FUNC PyInit__tomotopy()
 {
+	using namespace std;
+
 	bool sse2 = false, avx = false, avx2 = false;
 	bool env_sse2 = false, env_avx = false, env_avx2 = false;
 
diff --git a/src/python/PyUtils.h b/src/python/handler/PyUtils.h
similarity index 100%
rename from src/python/PyUtils.h
rename to src/python/handler/PyUtils.h
diff --git a/src/python/coherence.h b/src/python/handler/coherence.h
similarity index 94%
rename from src/python/coherence.h
rename to src/python/handler/coherence.h
index 6530562..a11b2bf 100644
--- a/src/python/coherence.h
+++ b/src/python/handler/coherence.h
@@ -2,7 +2,7 @@
 
 #include "module.h"
 #include "utils.h"
-#include "../Coherence/CoherenceModel.hpp"
+#include "../../Coherence/CoherenceModel.hpp"
 
 struct CoherenceObject
 {
diff --git a/src/python/docs.h b/src/python/handler/docs.h
similarity index 100%
rename from src/python/docs.h
rename to src/python/handler/docs.h
diff --git a/src/python/label.h b/src/python/handler/label.h
similarity index 97%
rename from src/python/label.h
rename to src/python/handler/label.h
index 0951221..330076b 100644
--- a/src/python/label.h
+++ b/src/python/handler/label.h
@@ -10,7 +10,7 @@
 #include "PyUtils.h"
 #endif
 
-#include "../Labeling/FoRelevance.h"
+#include "../../Labeling/FoRelevance.h"
 
 struct CorpusObject;
 
diff --git a/src/python/label_docs.h b/src/python/handler/label_docs.h
similarity index 97%
rename from src/python/label_docs.h
rename to src/python/handler/label_docs.h
index f6d4bbf..041ab76 100644
--- a/src/python/label_docs.h
+++ b/src/python/handler/label_docs.h
@@ -1,190 +1,190 @@
-﻿#pragma once
-#include <Python.h>
-
-#define DOC_SIGNATURE_EN(name, signature, en) PyDoc_STRVAR(name, signature "\n--\n\n" en)
-#define DOC_VARIABLE_EN(name, en) PyDoc_STRVAR(name, en)
-#ifdef DOC_KO
-#define DOC_SIGNATURE_EN_KO(name, signature, en, ko) PyDoc_STRVAR(name, signature "\n--\n\n" ko)
-#define DOC_VARIABLE_EN_KO(name, en, ko) PyDoc_STRVAR(name, ko)
-#else
-#define DOC_SIGNATURE_EN_KO(name, signature, en, ko) PyDoc_STRVAR(name, signature "\n--\n\n" en)
-#define DOC_VARIABLE_EN_KO(name, en, ko) PyDoc_STRVAR(name, en)
-#endif
-
-/*
-	class Candidate
-*/
-DOC_SIGNATURE_EN_KO(Candidate___init____doc__,
-	"Candidate()",
-	u8R""()"",
-u8R""()"");
-
-DOC_VARIABLE_EN_KO(Candidate_words__doc__,
-	u8R""(words of the candidate for topic label (read-only))"",
-	u8R""(토픽 레이블의 후보 (읽기전용))"");
-
-DOC_VARIABLE_EN_KO(Candidate_score__doc__,
-	u8R""(score of the candidate (read-only))"",
-	u8R""(후보의 점수(읽기전용))"");
-
-DOC_VARIABLE_EN_KO(Candidate_name__doc__,
-	u8R""(an actual name of the candidate for topic label)"",
-	u8R""(토픽 레이블의 실제 이름)"");
-
-DOC_VARIABLE_EN_KO(Candidate_cf__doc__,
-    u8R""(collection frequency of the candidate (read-only))"",
-    u8R""(후보의 장서빈도(읽기전용))"");
-
-DOC_VARIABLE_EN_KO(Candidate_df__doc__,
-    u8R""(document frequency of the candidate (read-only))"",
-    u8R""(후보의 문헌빈도(읽기전용))"");
-
-/*
-	class PMIExtractor
-*/
-DOC_SIGNATURE_EN_KO(Extractor_extract__doc__,
-	"extract(self, topic_model)",
-	u8R""(Return the list of `tomotopy.label.Candidate`s extracted from `topic_model`
-
-Parameters
-----------
-topic_model
-    an instance of topic model with documents to extract candidates
-)"",
-	u8R""(`topic_model`로부터 추출된 토픽 레이블 후보인 `tomotopy.label.Candidate`의 리스트를 반환합니다.
-
-Parameters
-----------
-topic_model
-    후보를 추출할 문헌들을 가지고 있는 토픽 모델의 인스턴스
-)"");
-
-DOC_SIGNATURE_EN_KO(PMIExtractor___init____doc__,
-	"PMIExtractor(min_cf=10, min_df=5, min_len=1, max_len=5, max_cand=5000, normalized=False)",
-	u8R""(.. versionadded:: 0.6.0
-
-`PMIExtractor` exploits multivariate pointwise mutual information to extract collocations. 
-It finds a string of words that often co-occur statistically.
-
-Parameters
-----------
-min_cf : int
-    minimum collection frequency of collocations. Collocations with a smaller collection frequency than `min_cf` are excluded from the candidates.
-    Set this value large if the corpus is big
-min_df : int
-    minimum document frequency of collocations. Collocations with a smaller document frequency than `min_df` are excluded from the candidates.
-    Set this value large if the corpus is big
-min_len : int
-    .. versionadded:: 0.10.0
-    
-    minimum length of collocations. `min_len=1` means that it extracts not only collocations but also all single words.
-    The number of single words are excluded in counting `max_cand`.
-max_len : int
-    maximum length of collocations
-max_cand : int
-    maximum number of candidates to extract
-)"",
-	u8R""(.. versionadded:: 0.6.0
-
-`PMIExtractor`는 다변수 점별 상호정보량을 활용해 연어를 추출합니다. 이는 통계적으로 자주 함께 등장하는 단어열을 찾아줍니다.
-
-Parameters
-----------
-min_cf : int
-    추출하려는 후보의 최소 장서 빈도. 문헌 내 등장하는 빈도수가 `min_cf`보다 작은 연어는 후보에서 제외됩니다.
-    분석하려는 코퍼스가 클 경우 이 값을 키우십시오.
-min_df : int
-    추출하려는 후보의 최소 문헌 빈도. 연어가 등장하는 문헌 수가 `min_df`보다 작은 경우 후보에서 제외됩니다.
-    분석하려는 코퍼스가 클 경우 이 값을 키우십시오.
-min_len : int
-    .. versionadded:: 0.10.0
-    
-    분석하려는 연어의 최소 길이. 1로 설정시 단일 단어들도 모두 추출합니다.
-    단일 단어들은 `max_cand` 개수 계산에서 제외됩니다.
-max_len : int
-    분석하려는 연어의 최대 길이
-max_cand : int
-    추출하려는 후보의 최대 개수
-)"");
-
-/*
-	class FoRelevance
-*/
-DOC_SIGNATURE_EN_KO(Labeler_get_topic_labels__doc__,
-	"get_topic_labels(self, k, top_n=10)",
-	u8R""(Return the top-n label candidates for the topic `k`
-
-Parameters
-----------
-k : int
-    an integer indicating a topic
-top_n : int
-    the number of labels
-)"",
-	u8R""(토픽 `k`에 해당하는 레이블 후보 상위 n개를 반환합니다.
-
-Parameters
-----------
-k : int
-    토픽을 지정하는 정수
-top_n : int
-    토픽 레이블의 개수)"");
-
-DOC_SIGNATURE_EN_KO(FoRelevance___init____doc__,
-	"FoRelevance(topic_model, cands, min_df=5, smoothing=0.01, mu=0.25, window_size=-1, workers=0)",
-	u8R""(.. versionadded:: 0.6.0
-
-This type provides an implementation of First-order Relevance for topic labeling based on following papers:
-
-> * Mei, Q., Shen, X., & Zhai, C. (2007, August). Automatic labeling of multinomial topic models. In Proceedings of the 13th ACM SIGKDD international conference on Knowledge discovery and data mining (pp. 490-499).
-
-Parameters
-----------
-topic_model
-    an instance of topic model to label topics
-cands : Iterable[tomotopy.label.Candidate]
-    a list of candidates to be used as topic labels
-min_df : int
-    minimum document frequency of collocations. Collocations with a smaller document frequency than `min_df` are excluded from the candidates.
-    Set this value large if the corpus is big
-smoothing : float
-    a small value greater than 0 for Laplace smoothing
-mu : float
-    a discriminative coefficient. Candidates with high score on a specific topic and with low score on other topics get the higher final score when this value is the larger.
-window_size : int
-    .. versionadded:: 0.10.0
-    
-    size of the sliding window for calculating co-occurrence. If `window_size=-1`, it uses the whole document, instead of the sliding windows.
-    If your documents are long, it is recommended to set this value to 50 ~ 100, not -1.
-workers : int
-    an integer indicating the number of workers to perform samplings. 
-    If `workers` is 0, the number of cores in the system will be used.
-)"",
-	u8R""(.. versionadded:: 0.6.0
-
-First-order Relevance를 이용한 토픽 라벨링 기법을 제공합니다. 이 구현체는 다음 논문에 기초하고 있습니다:
-
-> * Mei, Q., Shen, X., & Zhai, C. (2007, August). Automatic labeling of multinomial topic models. In Proceedings of the 13th ACM SIGKDD international conference on Knowledge discovery and data mining (pp. 490-499).
-
-Parameters
-----------
-topic_model
-    토픽명을 붙일 토픽 모델의 인스턴스
-cands : Iterable[tomotopy.label.Candidate]
-    토픽명으로 사용될 후보들의 리스트
-min_df : int
-    사용하려는 후보의 최소 문헌 빈도. 연어가 등장하는 문헌 수가 `min_df`보다 작은 경우 선택에서 제외됩니다.
-    분석하려는 코퍼스가 클 경우 이 값을 키우십시오.
-smoothing : float
-    라플라스 평활화에 사용될 0보다 큰 작은 실수
-mu : float
-    변별성 계수. 이 계수가 클 때, 특정 토픽에 대해서만 높은 점수를 가지고 나머지 토픽에 대해서는 낮은 점수를 가진 후보가 더 높은 최종 점수를 받습니다.
-window_size : int
-    .. versionadded:: 0.10.0
-    
-    동시출현 빈도를 계산하기 위한 슬라이딩 윈도우의 크기. -1로 설정시 슬라이딩 윈도우를 사용하지 않고, 문헌 전체를 활용해 동시출현 빈도를 계산합니다.
-    분석에 사용하는 문헌들의 길이가 길다면, 이 값을 -1이 아닌 50 ~ 100 정도로 설정하는 걸 권장합니다.
-workers : int
-    깁스 샘플링을 수행하는 데에 사용할 스레드의 개수입니다. 
-    만약 이 값을 0으로 설정할 경우 시스템 내의 가용한 모든 코어가 사용됩니다.
-)"");
+﻿#pragma once
+#include <Python.h>
+
+#define DOC_SIGNATURE_EN(name, signature, en) PyDoc_STRVAR(name, signature "\n--\n\n" en)
+#define DOC_VARIABLE_EN(name, en) PyDoc_STRVAR(name, en)
+#ifdef DOC_KO
+#define DOC_SIGNATURE_EN_KO(name, signature, en, ko) PyDoc_STRVAR(name, signature "\n--\n\n" ko)
+#define DOC_VARIABLE_EN_KO(name, en, ko) PyDoc_STRVAR(name, ko)
+#else
+#define DOC_SIGNATURE_EN_KO(name, signature, en, ko) PyDoc_STRVAR(name, signature "\n--\n\n" en)
+#define DOC_VARIABLE_EN_KO(name, en, ko) PyDoc_STRVAR(name, en)
+#endif
+
+/*
+	class Candidate
+*/
+DOC_SIGNATURE_EN_KO(Candidate___init____doc__,
+	"Candidate()",
+	u8R""()"",
+u8R""()"");
+
+DOC_VARIABLE_EN_KO(Candidate_words__doc__,
+	u8R""(words of the candidate for topic label (read-only))"",
+	u8R""(토픽 레이블의 후보 (읽기전용))"");
+
+DOC_VARIABLE_EN_KO(Candidate_score__doc__,
+	u8R""(score of the candidate (read-only))"",
+	u8R""(후보의 점수(읽기전용))"");
+
+DOC_VARIABLE_EN_KO(Candidate_name__doc__,
+	u8R""(an actual name of the candidate for topic label)"",
+	u8R""(토픽 레이블의 실제 이름)"");
+
+DOC_VARIABLE_EN_KO(Candidate_cf__doc__,
+    u8R""(collection frequency of the candidate (read-only))"",
+    u8R""(후보의 장서빈도(읽기전용))"");
+
+DOC_VARIABLE_EN_KO(Candidate_df__doc__,
+    u8R""(document frequency of the candidate (read-only))"",
+    u8R""(후보의 문헌빈도(읽기전용))"");
+
+/*
+	class PMIExtractor
+*/
+DOC_SIGNATURE_EN_KO(Extractor_extract__doc__,
+	"extract(self, topic_model)",
+	u8R""(Return the list of `tomotopy.label.Candidate`s extracted from `topic_model`
+
+Parameters
+----------
+topic_model
+    an instance of topic model with documents to extract candidates
+)"",
+	u8R""(`topic_model`로부터 추출된 토픽 레이블 후보인 `tomotopy.label.Candidate`의 리스트를 반환합니다.
+
+Parameters
+----------
+topic_model
+    후보를 추출할 문헌들을 가지고 있는 토픽 모델의 인스턴스
+)"");
+
+DOC_SIGNATURE_EN_KO(PMIExtractor___init____doc__,
+	"PMIExtractor(min_cf=10, min_df=5, min_len=1, max_len=5, max_cand=5000, normalized=False)",
+	u8R""(.. versionadded:: 0.6.0
+
+`PMIExtractor` exploits multivariate pointwise mutual information to extract collocations. 
+It finds a string of words that often co-occur statistically.
+
+Parameters
+----------
+min_cf : int
+    minimum collection frequency of collocations. Collocations with a smaller collection frequency than `min_cf` are excluded from the candidates.
+    Set this value large if the corpus is big
+min_df : int
+    minimum document frequency of collocations. Collocations with a smaller document frequency than `min_df` are excluded from the candidates.
+    Set this value large if the corpus is big
+min_len : int
+    .. versionadded:: 0.10.0
+    
+    minimum length of collocations. `min_len=1` means that it extracts not only collocations but also all single words.
+    The number of single words are excluded in counting `max_cand`.
+max_len : int
+    maximum length of collocations
+max_cand : int
+    maximum number of candidates to extract
+)"",
+	u8R""(.. versionadded:: 0.6.0
+
+`PMIExtractor`는 다변수 점별 상호정보량을 활용해 연어를 추출합니다. 이는 통계적으로 자주 함께 등장하는 단어열을 찾아줍니다.
+
+Parameters
+----------
+min_cf : int
+    추출하려는 후보의 최소 장서 빈도. 문헌 내 등장하는 빈도수가 `min_cf`보다 작은 연어는 후보에서 제외됩니다.
+    분석하려는 코퍼스가 클 경우 이 값을 키우십시오.
+min_df : int
+    추출하려는 후보의 최소 문헌 빈도. 연어가 등장하는 문헌 수가 `min_df`보다 작은 경우 후보에서 제외됩니다.
+    분석하려는 코퍼스가 클 경우 이 값을 키우십시오.
+min_len : int
+    .. versionadded:: 0.10.0
+    
+    분석하려는 연어의 최소 길이. 1로 설정시 단일 단어들도 모두 추출합니다.
+    단일 단어들은 `max_cand` 개수 계산에서 제외됩니다.
+max_len : int
+    분석하려는 연어의 최대 길이
+max_cand : int
+    추출하려는 후보의 최대 개수
+)"");
+
+/*
+	class FoRelevance
+*/
+DOC_SIGNATURE_EN_KO(Labeler_get_topic_labels__doc__,
+	"get_topic_labels(self, k, top_n=10)",
+	u8R""(Return the top-n label candidates for the topic `k`
+
+Parameters
+----------
+k : int
+    an integer indicating a topic
+top_n : int
+    the number of labels
+)"",
+	u8R""(토픽 `k`에 해당하는 레이블 후보 상위 n개를 반환합니다.
+
+Parameters
+----------
+k : int
+    토픽을 지정하는 정수
+top_n : int
+    토픽 레이블의 개수)"");
+
+DOC_SIGNATURE_EN_KO(FoRelevance___init____doc__,
+	"FoRelevance(topic_model, cands, min_df=5, smoothing=0.01, mu=0.25, window_size=-1, workers=0)",
+	u8R""(.. versionadded:: 0.6.0
+
+This type provides an implementation of First-order Relevance for topic labeling based on following papers:
+
+> * Mei, Q., Shen, X., & Zhai, C. (2007, August). Automatic labeling of multinomial topic models. In Proceedings of the 13th ACM SIGKDD international conference on Knowledge discovery and data mining (pp. 490-499).
+
+Parameters
+----------
+topic_model
+    an instance of topic model to label topics
+cands : Iterable[tomotopy.label.Candidate]
+    a list of candidates to be used as topic labels
+min_df : int
+    minimum document frequency of collocations. Collocations with a smaller document frequency than `min_df` are excluded from the candidates.
+    Set this value large if the corpus is big
+smoothing : float
+    a small value greater than 0 for Laplace smoothing
+mu : float
+    a discriminative coefficient. Candidates with high score on a specific topic and with low score on other topics get the higher final score when this value is the larger.
+window_size : int
+    .. versionadded:: 0.10.0
+    
+    size of the sliding window for calculating co-occurrence. If `window_size=-1`, it uses the whole document, instead of the sliding windows.
+    If your documents are long, it is recommended to set this value to 50 ~ 100, not -1.
+workers : int
+    an integer indicating the number of workers to perform samplings. 
+    If `workers` is 0, the number of cores in the system will be used.
+)"",
+	u8R""(.. versionadded:: 0.6.0
+
+First-order Relevance를 이용한 토픽 라벨링 기법을 제공합니다. 이 구현체는 다음 논문에 기초하고 있습니다:
+
+> * Mei, Q., Shen, X., & Zhai, C. (2007, August). Automatic labeling of multinomial topic models. In Proceedings of the 13th ACM SIGKDD international conference on Knowledge discovery and data mining (pp. 490-499).
+
+Parameters
+----------
+topic_model
+    토픽명을 붙일 토픽 모델의 인스턴스
+cands : Iterable[tomotopy.label.Candidate]
+    토픽명으로 사용될 후보들의 리스트
+min_df : int
+    사용하려는 후보의 최소 문헌 빈도. 연어가 등장하는 문헌 수가 `min_df`보다 작은 경우 선택에서 제외됩니다.
+    분석하려는 코퍼스가 클 경우 이 값을 키우십시오.
+smoothing : float
+    라플라스 평활화에 사용될 0보다 큰 작은 실수
+mu : float
+    변별성 계수. 이 계수가 클 때, 특정 토픽에 대해서만 높은 점수를 가지고 나머지 토픽에 대해서는 낮은 점수를 가진 후보가 더 높은 최종 점수를 받습니다.
+window_size : int
+    .. versionadded:: 0.10.0
+    
+    동시출현 빈도를 계산하기 위한 슬라이딩 윈도우의 크기. -1로 설정시 슬라이딩 윈도우를 사용하지 않고, 문헌 전체를 활용해 동시출현 빈도를 계산합니다.
+    분석에 사용하는 문헌들의 길이가 길다면, 이 값을 -1이 아닌 50 ~ 100 정도로 설정하는 걸 권장합니다.
+workers : int
+    깁스 샘플링을 수행하는 데에 사용할 스레드의 개수입니다. 
+    만약 이 값을 0으로 설정할 경우 시스템 내의 가용한 모든 코어가 사용됩니다.
+)"");
diff --git a/src/python/module.h b/src/python/handler/module.h
similarity index 97%
rename from src/python/module.h
rename to src/python/handler/module.h
index e0456bb..c3f275f 100644
--- a/src/python/module.h
+++ b/src/python/handler/module.h
@@ -13,8 +13,9 @@
 #include "PyUtils.h"
 #endif
 
-#include "../TopicModel/TopicModel.hpp"
-#include "../Utils/serializer.hpp"
+#include "../../TopicModel/TopicModel.hpp"
+#include "../../Utils/serializer.hpp"
+#include "../../Utils/Mmap.h"
 #include "docs.h"
 
 void char2Byte(const std::string& str, std::vector<uint32_t>& startPos, std::vector<uint16_t>& length);
@@ -104,8 +105,8 @@ PyObject* PREFIX##_load(PyObject*, PyObject* args, PyObject *kwargs)\
 	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", (char**)kwlist, &filename)) return nullptr;\
 	try\
 	{\
-		ifstream str{ filename, ios_base::binary };\
-		if (!str) throw ios_base::failure{ std::string("cannot open file '") + filename + std::string("'") };\
+		tomoto::utils::MMap mm{ filename };\
+		tomoto::serializer::imstream str{ mm };\
 		for (size_t i = 0; i < (size_t)tomoto::TermWeight::size; ++i)\
 		{\
 			str.seekg(0);\
diff --git a/src/python/py_CT.cpp b/src/python/handler/py_CT.cpp
similarity index 99%
rename from src/python/py_CT.cpp
rename to src/python/handler/py_CT.cpp
index 34d7d1e..4ede940 100644
--- a/src/python/py_CT.cpp
+++ b/src/python/handler/py_CT.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/CT.h"
+#include "../../TopicModel/CT.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_DMR.cpp b/src/python/handler/py_DMR.cpp
similarity index 99%
rename from src/python/py_DMR.cpp
rename to src/python/handler/py_DMR.cpp
index 8c072a4..efe9e78 100644
--- a/src/python/py_DMR.cpp
+++ b/src/python/handler/py_DMR.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/DMR.h"
+#include "../../TopicModel/DMR.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_DT.cpp b/src/python/handler/py_DT.cpp
similarity index 99%
rename from src/python/py_DT.cpp
rename to src/python/handler/py_DT.cpp
index 345ec76..0b3a2ec 100644
--- a/src/python/py_DT.cpp
+++ b/src/python/handler/py_DT.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/DT.h"
+#include "../../TopicModel/DT.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_GDMR.cpp b/src/python/handler/py_GDMR.cpp
similarity index 99%
rename from src/python/py_GDMR.cpp
rename to src/python/handler/py_GDMR.cpp
index 867cf00..91362ee 100644
--- a/src/python/py_GDMR.cpp
+++ b/src/python/handler/py_GDMR.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/GDMR.h"
+#include "../../TopicModel/GDMR.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_HDP.cpp b/src/python/handler/py_HDP.cpp
similarity index 99%
rename from src/python/py_HDP.cpp
rename to src/python/handler/py_HDP.cpp
index 9503c69..de31ba8 100644
--- a/src/python/py_HDP.cpp
+++ b/src/python/handler/py_HDP.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/HDP.h"
+#include "../../TopicModel/HDP.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_HLDA.cpp b/src/python/handler/py_HLDA.cpp
similarity index 99%
rename from src/python/py_HLDA.cpp
rename to src/python/handler/py_HLDA.cpp
index b004850..3febbe8 100644
--- a/src/python/py_HLDA.cpp
+++ b/src/python/handler/py_HLDA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/HLDA.h"
+#include "../../TopicModel/HLDA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_HPA.cpp b/src/python/handler/py_HPA.cpp
similarity index 99%
rename from src/python/py_HPA.cpp
rename to src/python/handler/py_HPA.cpp
index 0fcafb7..50bb37c 100644
--- a/src/python/py_HPA.cpp
+++ b/src/python/handler/py_HPA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/HPA.h"
+#include "../../TopicModel/HPA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_LDA.cpp b/src/python/handler/py_LDA.cpp
similarity index 99%
rename from src/python/py_LDA.cpp
rename to src/python/handler/py_LDA.cpp
index a731779..c7c1b1a 100644
--- a/src/python/py_LDA.cpp
+++ b/src/python/handler/py_LDA.cpp
@@ -2,7 +2,7 @@
 #include <iostream>
 #include <sstream>
 
-#include "../TopicModel/LDA.h"
+#include "../../TopicModel/LDA.h"
 
 #include "utils.h"
 #include "module.h"
diff --git a/src/python/py_LLDA.cpp b/src/python/handler/py_LLDA.cpp
similarity index 99%
rename from src/python/py_LLDA.cpp
rename to src/python/handler/py_LLDA.cpp
index 73adaa2..e612d64 100644
--- a/src/python/py_LLDA.cpp
+++ b/src/python/handler/py_LLDA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/LLDA.h"
+#include "../../TopicModel/LLDA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_MGLDA.cpp b/src/python/handler/py_MGLDA.cpp
similarity index 99%
rename from src/python/py_MGLDA.cpp
rename to src/python/handler/py_MGLDA.cpp
index d067063..c381fcd 100644
--- a/src/python/py_MGLDA.cpp
+++ b/src/python/handler/py_MGLDA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/MGLDA.h"
+#include "../../TopicModel/MGLDA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_PA.cpp b/src/python/handler/py_PA.cpp
similarity index 99%
rename from src/python/py_PA.cpp
rename to src/python/handler/py_PA.cpp
index ce53604..7a29983 100644
--- a/src/python/py_PA.cpp
+++ b/src/python/handler/py_PA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/PA.h"
+#include "../../TopicModel/PA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_PLDA.cpp b/src/python/handler/py_PLDA.cpp
similarity index 99%
rename from src/python/py_PLDA.cpp
rename to src/python/handler/py_PLDA.cpp
index 07b3e4c..6254f55 100644
--- a/src/python/py_PLDA.cpp
+++ b/src/python/handler/py_PLDA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/PLDA.h"
+#include "../../TopicModel/PLDA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_PT.cpp b/src/python/handler/py_PT.cpp
similarity index 99%
rename from src/python/py_PT.cpp
rename to src/python/handler/py_PT.cpp
index 323c95b..c455ddb 100644
--- a/src/python/py_PT.cpp
+++ b/src/python/handler/py_PT.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/PT.h"
+#include "../../TopicModel/PT.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_SLDA.cpp b/src/python/handler/py_SLDA.cpp
similarity index 99%
rename from src/python/py_SLDA.cpp
rename to src/python/handler/py_SLDA.cpp
index 85a3314..abd0693 100644
--- a/src/python/py_SLDA.cpp
+++ b/src/python/handler/py_SLDA.cpp
@@ -1,4 +1,4 @@
-#include "../TopicModel/SLDA.h"
+#include "../../TopicModel/SLDA.h"
 
 #include "module.h"
 #include "utils.h"
diff --git a/src/python/py_coherence.cpp b/src/python/handler/py_coherence.cpp
similarity index 100%
rename from src/python/py_coherence.cpp
rename to src/python/handler/py_coherence.cpp
diff --git a/src/python/py_label.cpp b/src/python/handler/py_label.cpp
similarity index 100%
rename from src/python/py_label.cpp
rename to src/python/handler/py_label.cpp
diff --git a/src/python/py_main.cpp b/src/python/handler/py_main.cpp
similarity index 100%
rename from src/python/py_main.cpp
rename to src/python/handler/py_main.cpp
diff --git a/src/python/py_utils.cpp b/src/python/handler/py_utils.cpp
similarity index 99%
rename from src/python/py_utils.cpp
rename to src/python/handler/py_utils.cpp
index 3e6dd80..3ace32c 100644
--- a/src/python/py_utils.cpp
+++ b/src/python/handler/py_utils.cpp
@@ -2,8 +2,8 @@
 #include "module.h"
 #include "utils.h"
 #include "label.h"
-#include "../Labeling/Phraser.hpp"
-#include "../Labeling/FoRelevance.h"
+#include "../../Labeling/Phraser.hpp"
+#include "../../Labeling/FoRelevance.h"
 
 using namespace std;
 
@@ -386,7 +386,7 @@ PyObject* CorpusObject::addDoc(CorpusObject* self, PyObject* args, PyObject* kwa
 				{
 					doc.words.emplace_back(self->vocab->vocabs->add(PyUnicode_AsUTF8(t)));
 				}
-				else if (Py_IsNone(t))
+				else if (t == Py_None)
 				{
 					doc.words.emplace_back(tomoto::non_vocab_id);
 				}
@@ -395,7 +395,7 @@ PyObject* CorpusObject::addDoc(CorpusObject* self, PyObject* args, PyObject* kwa
 					PyObject* word = PyTuple_GetItem(t, 0);
 					PyObject* pos = PyTuple_GetItem(t, 1);
 					PyObject* len = PyTuple_GetItem(t, 2);
-					if (!((PyUnicode_Check(word) || Py_IsNone(word)) && PyLong_Check(pos) && PyLong_Check(len))) throw py::ValueError{ "`tokenizer` must return an iterable of `str` or `tuple` of (`str`, `int`, `int`)." };
+					if (!((PyUnicode_Check(word) || word == Py_None) && PyLong_Check(pos) && PyLong_Check(len))) throw py::ValueError{ "`tokenizer` must return an iterable of `str` or `tuple` of (`str`, `int`, `int`)." };
 					bool isStopword = false;
 					if (stopwords != Py_None)
 					{
@@ -403,7 +403,7 @@ PyObject* CorpusObject::addDoc(CorpusObject* self, PyObject* args, PyObject* kwa
 						if (!stopRet) throw py::ExcPropagation{};
 						isStopword = PyObject_IsTrue(stopRet);
 					}
-					else if (Py_IsNone(word))
+					else if (word == Py_None)
 					{
 						isStopword = true;
 					}
@@ -491,7 +491,7 @@ PyObject* CorpusObject::addDocs(CorpusObject* self, PyObject* args, PyObject* kw
 				{
 					doc.words.emplace_back(self->vocab->vocabs->add(PyUnicode_AsUTF8(t)));
 				}
-				else if (Py_IsNone(t))
+				else if (t == Py_None)
 				{
 					doc.words.emplace_back(tomoto::non_vocab_id);
 				}
@@ -500,7 +500,7 @@ PyObject* CorpusObject::addDocs(CorpusObject* self, PyObject* args, PyObject* kw
 					PyObject* word = PyTuple_GetItem(t, 0);
 					PyObject* pos = PyTuple_GetItem(t, 1);
 					PyObject* len = PyTuple_GetItem(t, 2);
-					if (!((PyUnicode_Check(word) || Py_IsNone(word)) && PyLong_Check(pos) && PyLong_Check(len))) throw py::ValueError{ "`tokenizer` must return an iterable of `str` or `tuple` of (`str`, `int`, `int`)." };
+					if (!((PyUnicode_Check(word) || word == Py_None) && PyLong_Check(pos) && PyLong_Check(len))) throw py::ValueError{ "`tokenizer` must return an iterable of `str` or `tuple` of (`str`, `int`, `int`)." };
 
 					bool isStopword = false;
 					if (stopwords != Py_None)
@@ -509,7 +509,7 @@ PyObject* CorpusObject::addDocs(CorpusObject* self, PyObject* args, PyObject* kw
 						if (!stopRet) throw py::ExcPropagation{};
 						isStopword = PyObject_IsTrue(stopRet);
 					}
-					else if (Py_IsNone(word))
+					else if (word == Py_None)
 					{
 						isStopword = true;
 					}
diff --git a/src/python/utils.h b/src/python/handler/utils.h
similarity index 99%
rename from src/python/utils.h
rename to src/python/handler/utils.h
index b66e1ec..2fe8a5b 100644
--- a/src/python/utils.h
+++ b/src/python/handler/utils.h
@@ -1,9 +1,9 @@
 #pragma once
 
-#include "../TopicModel/LDA.h"
-#include "../Utils/Dictionary.h"
+#include "../../TopicModel/LDA.h"
+#include "../../Utils/Dictionary.h"
 #include "module.h"
-#include "../Labeling/Phraser.hpp"
+#include "../../Labeling/Phraser.hpp"
 
 extern PyTypeObject UtilsCorpus_type;
 extern PyTypeObject UtilsCorpusIter_type;


Test patch:
diff --git a/.github/workflows/pull_request_test.yml b/.github/workflows/pull_request_test.yml
index c760e52..7153d4a 100644
--- a/.github/workflows/pull_request_test.yml
+++ b/.github/workflows/pull_request_test.yml
@@ -132,6 +132,7 @@ jobs:
         mv variant-1.1.3/include/mapbox include/
     - name: Build
       run: |
+        export AUDITWHEEL_PLAT=many
         export MACOSX_DEPLOYMENT_TARGET=10.14
         python -m pip install numpy==`python .github/workflows/numpy_version.py`
         TOMOTOPY_CPU_ARCH=${{ matrix.cpu-arch }} python setup.py build install