メインコンテンツへスキップ
文書分類の目的は、文書をあらかじめ定義されたカテゴリに振り分けることです。これは、さまざまな種類の文書を含む文書フローを扱い、それぞれの文書タイプを特定する必要がある場合に非常に役立ちます。たとえば、契約書、請求書、領収書を種類ごとに別々のフォルダーへ仕分けたり、種類に応じてファイル名を変更したりできます。こうした処理は、事前学習済みのシステムを使って自動化できます。 文書分類の主な特長の 1 つは、区別すべき文書の種類があらかじめ分かっていることです。ABBYY FineReader Engine は、文書の内容に基づいて文書を分類できるほか、画像の特徴に基づいて分類したり、認識されたテキストと画像の両方の特徴を考慮して分類したりすることもできます。 では、この処理を詳しく見ていきましょう。主に次の 2 つのステップで構成されます。
  1. 分類データベースの作成
各カテゴリについて、典型的な文書またはページをいくつか選択します。これらは分類データベースの作成に使用されます。
  1. 文書の分類
前のステップで作成したデータベースを使用して、文書を分類できます。入力文書は、分類データベースを使用してカテゴリを判定する事前学習済みの分類システムに渡されます。 また、作成者やバーコード値など、文書のいくつかの属性に基づいて文書を分類する必要が生じる場合もあります。この記事では、その種の分類は扱いません。文書を属性に基づいて分類する場合は、データ抽出に テキスト抽出フィールドレベル認識、または バーコード認識 のシナリオを利用する独自のアルゴリズムを実装する必要があります。 以下で説明する手順は、Windows 用の Classification デモツールおよび Linux と macOS 用の Classification コードサンプルでも示されています。

シナリオの実装

このトピックで紹介するコード サンプルは、Windows 専用です。
以下では、ABBYY FineReader Engine を使用してドキュメントを分類する推奨される方法について詳しく説明します。
ABBYY FineReader Engine の使用を開始するには、Engine オブジェクトを作成する必要があります。Engine オブジェクトは、ABBYY FineReader Engine のオブジェクト階層における最上位のオブジェクトであり、さまざまなグローバル設定、一部の処理メソッド、および他のオブジェクトを作成するためのメソッドを提供します。Engine オブジェクトを作成するには、InitializeEngine 関数を使用できます。Engine オブジェクトをロードするその他の方法 (Win) も参照してください。

C#

public class EngineLoader : IDisposable
{
    public EngineLoader()
    {
        // これらの変数を、FREngine.dll へのフルパス、Customer Project ID、
        // および該当する場合は Online License トークン ファイルのパスと Online License パスワードで初期化します
        string enginePath = "";
        string customerProjectId = "";
        string licensePath = "";
        string licensePassword = "";
        // FREngine.dll ライブラリをロードします
        dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
           
        try
        {
            if (dllHandle == IntPtr.Zero)
            {
                throw new Exception("Can't load " + enginePath);
            }
            IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
            if (initializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("Can't find InitializeEngine function");
            }
            IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
            if (deinitializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("Can't find DeinitializeEngine function");
            }
            IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
            if (dllCanUnloadNowPtr == IntPtr.Zero)
            {
                throw new Exception("Can't find DllCanUnloadNow function");
            }
            // ポインターをデリゲートに変換します
            initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                initializeEnginePtr, typeof(InitializeEngine));
            deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                deinitializeEnginePtr, typeof(DeinitializeEngine));
            dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
            // InitializeEngine 関数を呼び出し、
            // Online License ファイルのパスと Online License パスワードを渡します
            int hresult = initializeEngine(customerProjectId, licensePath, licensePassword, 
                "", "", false, ref engine);
            Marshal.ThrowExceptionForHR(hresult);
        }
        catch (Exception)
        {
            // FREngine.dll ライブラリを解放します
            engine = null;
            // FreeLibrary を呼び出す前にすべてのオブジェクトを削除します
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            FreeLibrary(dllHandle);
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            throw;
        }
    }
    // Kernel32.dll 関数
    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
    private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
    // FREngine.dll 関数
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private delegate int InitializeEngine(string customerProjectId, string licensePath, 
        string licensePassword, string tempFolder, string dataFolder, bool isSharedCPUCoresMode, 
        ref FREngine.IEngine engine);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DeinitializeEngine();
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DllCanUnloadNow();
    // プライベート変数
    private FREngine.IEngine engine = null;
    // FREngine.dll へのハンドル
    private IntPtr dllHandle = IntPtr.Zero;
    private InitializeEngine initializeEngine = null;
    private DeinitializeEngine deinitializeEngine = null;
    private DllCanUnloadNow dllCanUnloadNow = null;
}
他の Classification API オブジェクトのファクトリとして機能する ClassificationEngine オブジェクトを作成します。Engine オブジェクトの CreateClassificationEngine メソッドを使用します。

C#

FREngine.IEngine engine;
FREngine.IClassificationEngine classEngine = engine.CreateClassificationEngine();
トレーニング メソッドと分類メソッドでは、ドキュメントまたはページから作成される特殊な種類のオブジェクトである ClassificationObject を使用します。このオブジェクトには、分類に必要なすべての情報が含まれます。分類シナリオで使用するドキュメントを準備するには、次の手順を実行します。
  1. 処理対象の画像を読み込みます。方法はいくつかあります。たとえば、Engine オブジェクトの CreateFRDocument メソッドを使って FRDocument オブジェクトを作成し、次に AddImageFile メソッドを使って、ファイルから作成した FRDocument オブジェクトに画像を追加できます。
  2. テキスト特徴を考慮するタイプの分類器 (CT_CombinedCT_Text) をトレーニングまたは使用する場合は、まず任意の使いやすい方法でドキュメントを認識します。ここでは、FRDocument オブジェクトの Analyze メソッドと Recognize メソッドを使用します。分類ではドキュメント合成は不要です。
分類自体では並列処理はサポートされていませんが、Windows および Linux でドキュメントを事前認識する際には並列処理が必要になる場合があります。分類するドキュメント数が多い場合は、Batch Processor または Parallel Processing with ABBYY FineReader Engine で説明されている他の並列処理方法の使用をお勧めします。
  1. ClassificationEngine オブジェクトの CreateObjectFromDocument メソッドを使用して、ドキュメントの最初のページの情報を含む ClassificationObject を作成します。ドキュメント内の別のページを使用する必要がある場合は、CreateObjectFromPage メソッドを呼び出します。
  2. ClassificationObject の Description プロパティは、既定では空です。必要に応じて、このプロパティに適切な説明を設定します。
認識済みのドキュメントまたはページに、認識されたテキストがまったく含まれないことがあります (たとえば、誤って空白ページを使用した場合など) 。この場合、その ClassificationObject はテキスト特徴を必要とする分類器には使用できません。確認のために SuitableClassifiers プロパティを使用できます。

C#

// FRDocument オブジェクトを作成します
FREngine.IFRDocument frDocument = engine.CreateFRDocument();
// 画像を追加します
frDocument.AddImageFile( "C:\\MyImage.tif", null, null );
// オプション: ドキュメントを解析して認識します
frDocument.Analyze( null, null, null );
frDocument.Recognize( null, null );
// 分類オブジェクトを作成します
FREngine.IClassificationObject clObject = classEngine.CreateObjectFromDocument( frDocument );
// オブジェクトが属するカテゴリを説明に設定します
clObject.Description = "CategoryA_Object1";
複数の種類の文書を区別する分類器をトレーニングするには、各種類のサンプルを含む分類済みのデータセットが必要です。このデータセットの作成と管理には、TrainingData オブジェクトを使用します。
  1. ClassificationEngine オブジェクトの CreateTrainingData メソッドを使用して、空のオブジェクトを作成します。
  2. Categories プロパティからカテゴリのコレクションにアクセスします。
  3. 分類対象の文書タイプごとにカテゴリを追加するため、Categories オブジェクトの AddNew メソッドを複数回使用します。このメソッドは、入力パラメーターとしてカテゴリラベルを表す string を受け取ります。ラベルは分類メソッドによって返されるため、カテゴリセット内で一意である必要があります。
  4. 新しく追加した各 Category オブジェクトについて、Objects プロパティを使用して分類オブジェクトのコレクションを開きます。IClassificationObjects::Add メソッドを使用して、このカテゴリに対応する分類オブジェクトを追加します。
    カテゴリを空のままにしてはいけません。トレーニングには、当然ながら少なくとも 2 つのカテゴリが必要です。
  5. これでトレーニングデータセットの設定は完了ですが、後で使えるようにディスク上のファイルに保存しておくとよいでしょう。たとえば、トレーニング済みモデルの精度が十分でないことが分かり、品質向上のためにデータを追加または修正したい場合です。TrainingData オブジェクトには SaveToFile メソッドが用意されています。

C#

FREngine.ITrainingData trainingData = classEngine.CreateTrainingData();
FREngine.ICategories categories = trainingData.Categories;
// 最初のカテゴリを追加
FREngine.ICategory category = categories.AddNew( "CategoryA" );
// ステップ 3 で準備した分類オブジェクトを追加
category.Objects.Add( clObject ); // このカテゴリのすべてのオブジェクトについて繰り返す
...
// すべてのカテゴリについて繰り返す
...
// すべてのカテゴリを追加したら、トレーニングデータセットを保存
trainingData.SaveToFile( "C:\\trainingData.dat" );
モデルトレーニングの機能は、Trainer オブジェクトによって提供されます。このオブジェクトを作成するには、ClassificationEngine オブジェクトの CreateTrainer メソッドを使用します。これには、分類器の種類とトレーニング手順に関するすべての設定が、TrainingParamsValidationParams という2つのサブオブジェクトに含まれています。必要な設定を確認し、対応するプロパティを変更してください。
  • 分類器の種類 (ITrainingParams::ClassifierType) を指定します。この設定では、カテゴリを割り当てる際に文書のどの特徴を考慮するかを決定します。対象となるのは、画像の特性、認識されたテキストの内容、またはその両方です。テキストの内容を使用する種類を選択する場合は、トレーニングデータセット内のすべての分類オブジェクトが、事前に認識処理された文書から作成されていることを確認してください。
  • トレーニングモード (ITrainingParams::TrainingMode) 。この設定では、トレーニングプロセスで高い適合率 (選択された要素のうち、正しいものがどれだけあるか) 、高い再現率 (正しい要素のうち、どれだけ選択されるか) 、またはその両者のバランスのいずれを重視するかを決定します。
  • k 分割交差検証を使用するかどうか (IValidationParams::ShouldPerformValidation) 。トレーニングサンプルが十分に大きくない場合は、交差検証の使用を推奨します。これにより、同じサンプルを異なる分割で使って複数のモデルを学習し、その中から最適なものを選択できるためです。分類済みデータが大量にある場合は、検証を無効にしてトレーニングサンプル全体でモデルを学習させ、その後、分類メソッド (ステップ 6) を使用して別のサンプルでモデルをテストし、パフォーマンススコアを利用者側で計算するほうがよい場合があります。
  • k 分割交差検証のパラメータ: 学習サンプルの分割数 (IValidationParams::FoldsCount) と、反復回数 (IValidationParams::RepeatCount) 。各反復において学習セットに必要なオブジェクト数は、テキスト分類器では 4 以上、複合分類器では 8 以上である点に注意してください。学習サンプルに十分な数のオブジェクトが含まれていることを確認してください。
これでモデルをトレーニングする準備が整いました。手順4で設定したTrainingDataオブジェクトを、TrainerオブジェクトのTrainModelメソッドに渡してください。このメソッドはTrainingResultsコレクションを返します。現在利用可能な機能では、このコレクションにはTrainingResultが1つだけ含まれます。交差検証を実行するよう選択した場合は、そのサブオブジェクトであるValidationResultでパフォーマンススコアを確認してください。
Linux および Windows では、IMultiProcessingParams::MultiProcessingMode の値にかかわらず、モデルのトレーニングと分類は逐次モードで実行されます。
ITrainingResult::Model プロパティを使用すると、トレーニング済みの分類モデルにアクセスできます。SaveToFile メソッドでファイルに保存するか、そのまま直接使用してドキュメントを分類することができます (手順6に進んでください) 。

C#

// トレーナーオブジェクトを作成してパラメーターを設定する
FREngine.ITrainer trainer = classEngine.CreateTrainer();
trainer.TrainingParams.ClassifierType = (int)FREngine.ClassifierTypeEnum.CT_Image; // 分類器は画像特徴のみを使用する
// 他の設定はデフォルトのままにして、モデルを直接トレーニングする
FREngine.ITrainingResults results = trainer.TrainModel ( trainingData );
// モデルのF1スコアを確認する
double F1 = results[0].ValidationResult.FMeasure;
// 分類モデルを取得する
FREngine.IModel model = results[0].Model;
// モデルを後で使用するために保存する
model.SaveToFile( "C:\\model.dat" );
学習済みモデルを分類に使用するには、次の手順を実行します。
  1. モデルが現在読み込まれていない場合は、ClassificationEngine オブジェクトの CreateModelFromFile メソッドを呼び出して、ディスク上のファイルからモデルを読み込みます。
  2. ステップ 3 で説明したとおりに、分類対象の文書から分類オブジェクトを準備します。
  3. 各分類オブジェクトについて、ClassificationObject を入力パラメーターとして指定し、Model オブジェクトの Classify メソッドを呼び出します。このメソッドは ClassificationResult オブジェクトのコレクションを返し、各オブジェクトにはカテゴリラベルとそのカテゴリに属する確率が含まれます。結果は確率の高い順に並べ替えられます。結果を取得し、その確率値が許容できるレベルかどうかを確認してください。
    分類器がカテゴリを割り当てられなかった場合は、結果コレクションではなく null が返されます。
モデルの学習と分類は、IMultiProcessingParams::MultiProcessingMode の値に関係なく、Linux および Windows では逐次モードで実行されます。

C#

// 学習済みモデルを開く
FREngine.IModel model = classEngine.CreateModelFromFile( "C:\\model.dat" );
// オブジェクトを分類する
FREngine.IClassificationResults classResults = model.Classify( clObject );
// 最も確度の高い結果とその確率を取得する
string label = classResults[0].CategoryLabel;
double probability = classResults[0].Probability;
ABBYY FineReader Engine の使用を終えたら、Engine オブジェクトをアンロードする必要があります。これを行うには、エクスポートされた関数 DeinitializeEngine を使用します。

C#

public class EngineLoader : IDisposable
{
    // FineReader Engine をアンロードする
    public void Dispose()
    {
        if (engine == null)
        {
            // Engine が読み込まれていない
            return;
        }
        engine = null;
        // FreeLibrary を呼び出す前にすべてのオブジェクトを削除する
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        int hresult = deinitializeEngine();
 
        hresult = dllCanUnloadNow();
        if (hresult == 0)
        {
            FreeLibrary(dllHandle);
        }
        dllHandle = IntPtr.Zero;
        initializeEngine = null;
        deinitializeEngine = null;
        dllCanUnloadNow = null;
        // クリーンアップ後に例外をスローする
        Marshal.ThrowExceptionForHR(hresult);
    }
    // Kernel32.dll の関数
    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
    private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
    // FREngine.dll の関数
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private delegate int InitializeEngine( string customerProjectId, string LicensePath, string LicensePassword, , , , ref FREngine.IEngine engine);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DeinitializeEngine();
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DllCanUnloadNow();
    // 非公開変数
    private FREngine.IEngine engine = null;
    // FREngine.dll へのハンドル
    private IntPtr dllHandle = IntPtr.Zero;
    private InitializeEngine initializeEngine = null;
    private DeinitializeEngine deinitializeEngine = null;
    private DllCanUnloadNow dllCanUnloadNow = null;
}

必要なリソース

FREngineDistribution.csv ファイルを使用すると、アプリケーションの動作に必要なファイルの一覧を自動的に生成できます。このシナリオで処理する場合は、5 列目 (RequiredByModule) で次の値を選択します。 Core Core.Resources Opening Opening, Processing Processing Processing.Classification Processing.Classification.NaturalLanguages Processing.OCR Processing.OCR, Processing.ICR Processing.OCR.NaturalLanguages Processing.OCR.NaturalLanguages, Processing.ICR.NaturalLanguages 標準シナリオを変更する場合は、必要なモジュールもそれに応じて変更してください。また、インターフェイス言語、認識言語、およびアプリケーションで使用する追加機能も指定する必要があります (たとえば、PDF ファイルを開く必要がある場合は Opening.PDF、CJK 言語 のテキストを認識する必要がある場合は Processing.OCR.CJK など) 。詳細については、FREngineDistribution.csv ファイルの使用方法 を参照してください。

追加の最適化

各処理段階の設定に関する詳細は、以下の記事を参照してください。

関連項目

基本的な使用シナリオの実装方法