메인 콘텐츠로 건너뛰기
문서 분류의 목적은 문서를 미리 정의된 여러 범주에 할당하는 것입니다. 이는 다양한 유형의 문서가 포함된 문서 흐름을 처리할 때 매우 유용하며, 각 문서의 유형을 식별해야 하는 경우에 특히 그렇습니다. 예를 들어 계약서, 송장, 영수증을 유형에 따라 서로 다른 폴더로 분류하거나 유형에 맞게 이름을 변경할 수 있습니다. 이는 사전 학습된 시스템을 사용해 자동으로 수행할 수 있습니다. 문서 분류의 주요 특징 중 하나는 구분해야 하는 문서 유형을 미리 알고 있다는 점입니다. ABBYY FineReader Engine은 문서 내용을 기준으로 문서를 분류할 수 있고, 이미지의 특징을 기준으로 분류할 수도 있으며, 인식된 텍스트와 이미지의 특성을 함께 고려할 수도 있습니다. 이제 이 과정을 자세히 살펴보겠습니다. 이 과정은 두 가지 주요 단계로 이루어집니다.
  1. 분류 데이터베이스 생성
각 범주마다 대표적인 문서 또는 페이지를 몇 개 선택합니다. 이렇게 선택한 문서와 페이지는 분류 데이터베이스를 만드는 데 사용됩니다.
  1. 문서 분류
이전 단계에서 만든 데이터베이스를 사용해 문서를 분류할 수 있습니다. 들어오는 문서는 사전 학습된 분류 시스템으로 전달되며, 이 시스템은 분류 데이터베이스를 사용해 해당 범주를 결정합니다. 문서를 작성자나 바코드 값과 같은 일부 속성에 따라 분류해야 할 수도 있습니다. 이 문서에서는 이러한 유형의 분류는 다루지 않습니다. 문서를 속성에 따라 분류하려면 데이터 추출을 위해 텍스트 추출, field 수준 인식, 또는 바코드 인식 시나리오를 활용하는 자체 알고리즘을 구현해야 합니다. 아래에 설명된 절차는 Windows용 Classification 데모 도구와 Linux 및 macOS용 Classification 코드 샘플에서도 확인할 수 있습니다.

시나리오 구현

이 항목에서 제공하는 코드 샘플은 Windows 전용입니다.
다음에서는 ABBYY FineReader Engine을 사용해 문서를 분류하는 권장 방법을 자세히 설명합니다.
ABBYY FineReader Engine 사용을 시작하려면 Engine 엔진 객체를 생성해야 합니다. 엔진 객체는 ABBYY FineReader 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 변수
    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 객체를 만듭니다. 엔진 객체의 CreateClassificationEngine 메서드를 사용합니다.

C#

FREngine.IEngine engine;
FREngine.IClassificationEngine classEngine = engine.CreateClassificationEngine();
학습 및 분류 메서드는 문서 또는 페이지에서 생성되는 특수한 객체인 ClassificationObject를 사용합니다. 이 객체에는 분류와 관련된 모든 정보가 포함됩니다.분류 시나리오에서 사용할 문서를 준비하려면 다음 단계를 수행합니다.
  1. 처리할 이미지를 로드합니다. 방법은 여러 가지가 있습니다. 예를 들어 엔진 객체의 CreateFRDocument 메서드를 사용해 FRDocument 객체를 만든 다음, AddImageFile 메서드로 파일의 이미지를 생성된 FRDocument 객체에 추가할 수 있습니다.
  2. 텍스트 특징을 고려하는 유형의 분류기(CT_Combined, CT_Text)를 학습시키거나 사용할 예정이라면, 먼저 편한 방법으로 문서를 인식합니다. 여기서는 FRDocument 객체의 AnalyzeRecognize 메서드를 사용하겠습니다. 분류에는 문서 합성이 필요하지 않습니다.
분류 자체는 병렬 처리를 지원하지 않지만, Windows 및 Linux에서 문서를 분류하기 전에 인식하는 준비 단계에서는 병렬 처리가 필요할 수 있습니다. 분류할 문서 수가 많다면 Parallel Processing with ABBYY FineReader Engine에 설명된 Batch Processor 또는 기타 병렬 처리 방법을 사용하는 것이 좋습니다.
  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 객체를 Trainer 객체의 TrainModel 메서드에 전달하세요. 이 메서드는 TrainingResults 컬렉션을 반환하며, 현재 사용 가능한 기능 기준으로 TrainingResult가 하나만 포함됩니다. 교차 검증을 수행하도록 선택한 경우, 하위 객체인 ValidationResult에서 성능 점수를 확인하세요.
Linux 및 Windows에서는 IMultiProcessingParams::MultiProcessingMode 값과 관계없이 모델 훈련 및 분류가 순차 모드로 수행됩니다.
ITrainingResult::Model 속성을 통해 학습된 분류 모델에 액세스할 수 있습니다. SaveToFile 메서드를 사용하여 파일로 저장하거나, 직접 사용하여 일부 문서들을 분류할 수 있습니다(6단계로 이동).

C#

// 트레이너 객체를 생성하고 Parameter를 설정합니다
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;
        // 정리 후 예외 throw
        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 변수
    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을 참조하십시오.

추가 최적화

다음 문서에서 다양한 처리 단계 설정에 대한 자세한 정보를 확인할 수 있습니다.

참고 항목

기본 사용 시나리오 구현