跳轉到主要內容
文件分類的目的,是將文件歸入不同的預先定義類別。這在處理包含多種類型文件的文件流程時特別實用,因為您需要識別每份文件的類型。例如,您可能想將合約、發票和收據分別整理到不同的資料夾中,或依其類型重新命名。這些工作都可以透過預先訓練的系統自動完成。 文件分類的一項主要特性是,您事先就知道需要區分哪些文件類型。ABBYY FineReader Engine 可以根據文件內容、影像特徵,或同時考量已辨識文字與影像特性來對文件進行分類。 下面來詳細說明此流程。它包含兩個主要步驟:
  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,
        // 以及(如適用)線上授權權杖檔案的路徑和線上授權密碼來初始化這些變數
        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 函式 
            // 傳入線上授權檔案的路徑和線上授權密碼
            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;
}
建立 ClassificationEngine 物件,作為其他 Classification API 物件的工廠。請使用 Engine 物件的 CreateClassificationEngine 方法。

C#

FREngine.IEngine engine;
FREngine.IClassificationEngine classEngine = engine.CreateClassificationEngine();
訓練和分類方法會使用一種特殊物件,這種物件是根據文件或頁面建立的:ClassificationObject,其中包含所有與分類相關的資訊。若要為分類情境準備文件,請執行下列步驟:
  1. 載入要處理的影像。可用的方法有好幾種:例如,您可以使用 Engine 物件的 CreateFRDocument 方法建立 FRDocument 物件,然後使用 AddImageFile 方法,從檔案將影像加入已建立的 FRDocument 物件。
  2. 如果您要訓練或使用會考量文字特徵的分類器類型 (CT_CombinedCT_Text) ,請先用任何方便的方法辨識文件。我們將使用 FRDocument 物件的 AnalyzeRecognize 方法。分類不需要進行文件合成。
雖然分類本身不支援平行處理,但在 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 方法加入對應至此類別的分類物件。
    任何類別都不能留空。顯而易見地,訓練至少需要兩個類別。
  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 中。請確認您所需的設定,並修改對應的屬性:
  • 分類器的類型 (ITrainingParams::ClassifierType) 。此設定會決定在指派類別時會考量文件的哪些特徵:影像特徵、已辨識文字的內容,或兩者。若要選取使用文字內容的類型,您必須確保訓練資料集中的所有分類物件,都是根據先前已辨識的文件所建立。
  • 訓練模式 (ITrainingParams::TrainingMode) 。此設定決定訓練程序應優先追求高精確率 (選取的元素中有多少是正確的) 、高召回率 (有多少正確元素被選取) ,或在兩者之間取得平衡。
  • 是否應使用 k 折交叉驗證 (IValidationParams::ShouldPerformValidation) 。我們建議在訓練樣本量不大時使用交叉驗證,因為這樣可以在同一樣本的不同分割上訓練多個模型,並選出最佳模型。如果您擁有大量已分類的資料,最好關閉驗證,在整個訓練樣本上訓練模型,然後使用分類方法 (步驟 6) 在另一個樣本上測試模型,並由您自行計算效能評分。
  • k 折交叉驗證的參數:訓練樣本被分成的份數 (IValidationParams::FoldsCount) 以及迭代次數 (IValidationParams::RepeatCount) 。請注意,在每次迭代中,訓練集所需的物件數量對於文字分類器不得少於 4,對於組合分類器不得少於 8。請確保您的訓練樣本包含足夠的物件。
現在您已準備好訓練模型。將您在步驟 4 中設定的 TrainingData object 傳遞給 Trainer object 的 TrainModel method。此 method 會傳回一個 TrainingResults 集合,在目前可用的功能範圍內,該集合僅包含一個 TrainingResult。若您選擇執行交叉驗證,請查看其 ValidationResult 子 object 中的效能分數。
odel 訓練和分類在 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. 針對每個分類物件,請呼叫 Model 物件的 Classify 方法,並將 ClassificationObject 作為輸入參數。此方法會傳回一組 ClassificationResult 物件,每個物件都包含類別標籤及該類別的機率。結果會依機率由高到低排序。請擷取結果,並確認該機率等級是否可接受。
    如果分類器無法指派類別,則會傳回 null,而非結果集合。
模型訓練與分類在 Linux 和 Windows 中都會以循序模式執行,無論 IMultiProcessingParams::MultiProcessingMode 的值為何。

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 languages 中的文字,則需使用 Processing.OCR.CJK) 。如需進一步資訊,請參閱 Working with the FREngineDistribution.csv File

進一步最佳化

如需設定各個處理階段的詳細資訊,請參閱下列文章:

另請參閱

基本使用情境實作