AWS 기술 블로그

Amazon Kendra로 모든 유형에 대한 자료 검색 구축하기 [2부 – 음성 및 영상 검색]

Amazon Kendra는 여러 데이터 소스에 존재하는 문서와 텍스트 유형의 데이터를 크롤링하여 검색 결과에 반영하는 지능형 검색 엔진 서비스입니다. 데이터를 추출하거나 데이터 소스 연결을 위한 커넥터를 만들거나 인덱스에 대한 튜닝 작업을 구현할 필요 없이, 몇 번의 클릭만으로 검색 인덱스를 생성하고 바로 쿼리할 수 있습니다.

블로그 시리즈 [1부 – 인덱스 생성과 문서 검색]에서는 기본적인 Amazon Kendra 개념 설명을 시작으로 인덱스 생성, 데이터 소스 연결과 문서 업로드 및 검색까지 해보았습니다.

배경

Amazon Kendra는 연결되는 데이터 소스에 대하여 주로 텍스트, 문서 기반의 파일 유형을 제공합니다. 하지만 실제 비즈니스에서 사용되는 자료는 텍스트와 문서 유형 뿐만아니라 음성 및 영상에 대한 유형의 자료가 많습니다. 업무에 활용하기 위해 해당 유형의 자료를 검색하는 시점에서 보통 아래 이미지처럼 리스트 형태로 제공되는 GUI 상에서 파일명, 제목 등의 간소화된 정보로 저장하고 저장된 스토리지에서 다운로드 받아 원하는 내용을 찾기 위해 상당한 시간을 소요합니다.

이는 빠르게 자료를 찾고 그 안의 내용을 찾아 비즈니스에 활용하는 관점에서 매우 비효율적일 수 있습니다. 이러한 방식이 아닌 파일의 내용을 기반으로 검색할 수 있다면 어떨까요? 예를 들어, 고객 음성 상담 관련 자료를 찾고 싶은 경우에 특정 단어가 언급된 음성 파일의 특정 부분을 빠르게 찾고 싶거나 영상에서 특정 장면 및 인물이 등장한 부분에 대한 영상 파일을 찾고 싶은 경우가 있을 수 있습니다. 이러한 방식이 가능하게 된다면 음성 및 영상 자료를 활용해야 하는 비즈니스에서의 업무 효율성과 활용성은 매우 크게 증가할 수 있을 것 입니다. 이번 가이드에서는 위와 같은 리스트 형식으로 음성, 영상을 찾는 것에서 나아가 실제 담긴 내용을 추출하여 이를 기반으로 검색할 수 있는 방법을 Amazon KendraAmazon Transcribe, AWS Step Functions 등을 사용해 손쉽게 구현할 수 있는 방법을 가이드 합니다.

구성도

2부에서는 아래 과정을 통해 Amazon Kendra를 통해 음성 및 영상 파일에 대하여 검색할 수 있는 방법을 가이드합니다.

  1. 먼저, 음성 및 영상 파일을 Amazon Kendra와 연결된 S3버킷이 아닌 별도 S3 버킷에 저장합니다.
  2. AWS StepFunctions의 상태머신을 통해 음성 및 영상 파일을 Amazon Transcribe를 통해 트랜크립션 작업을 진행하고 결과 JSON을 검색 가능한 텍스트 형태로 변환하기 위하여 AWS Lambda를 이용하여 변환합니다.
  3. 변환된 텍스트 파일을 Amazon Kendra의 데이터소스 S3 버킷에 업로드합니다.
  4. Amazon Kendra의 S3 데이터 소스를 Sync하여 검색 인덱스에 반영되도록 합니다.
  5. 음성 및 영상 파일에 대한 제목, 설명, 내용을 기반으로 Amazon Kendra를 통해 검색할 수 있습니다.

1부와 마찬가지로 실습은 도쿄(ap-northeast-1) 리전을 선택하여 진행합니다.

음성, 영상의 원본이 저장될 별도의 버킷 생성하기

[1부 – 인덱스 생성과 문서 검색]에서는 Amazon Kendra와 연결되어질 S3 버킷을 생성했습니다. 이 버킷은 Amazon Kendra가 지원하는 포맷에 맞는 데이터가 저장되는 곳 입니다. 지원되지 않는 포맷인 MP3, MP4, PNG와 같은 원본 데이터가 저장되어 필요에 맞게 변환하거나 Amazon Kendra에서 검색 시, 찾아가기 위한 원본 데이터를 저장하는 별도의 S3 버킷이 필요합니다.

  1. 해당 링크에서 S3 버킷을 생성합니다.
  2. [Bucket name]은 중복을 피해 고유한 값을 입력합니다. (ex: xxxx-kendra-assets)
  3. [AWS Region]은 Amazon Kendra 인덱스와 연결되지 않기 때문에 동일한 리전을 선택할 필요는 없지만 해당 블로그에서는 도쿄(ap-northeast-1)을 사용하겠습니다.
  4. 나머지 설정은 변경하지 않고 [Create bucket]을 눌러 생성합니다.
  5. 생성한 버킷을 클릭하고 [Create folder]를 클릭하여 저장할 파일 유형에 맞게 videos 또는 voices이름의 폴더를 생성합니다.
  6. 파일 별 임의의 Key 값을 설정하여 폴더를 추가로 생성하고 파일명을 Key.확장자명으로 변경하여 업로드합니다. (아래 예시 이미지 참조)

Transcribe를 텍스트로 변환하는 람다 생성하기

이제, 업로드한 파일(음성, 영상)에서 추출된 트랜스크립트를 아래 이미지와 같이 검색을 위한 텍스트로 변환하는 AWS Lambda를 생성하겠습니다.

  1. 해당 링크에서 [Create function]을 클릭합니다.
  2. [Function name]으로는 convertTranscriptJsonToTxt [Runtime]은 Node.js 16.x 을 선택하여주고 [Create function]을 클릭하여 함수를 생성합니다.
  3. 함수의 [Code]탭의 코드 편집기에서 [index.js] 파일의 내용을 아래 코드로 붙여넣습니다. 코드는 Amazon Transcribe에서 생성된 트랜크립션 파일을 통해 위 예시처럼 검색 가능한 텍스트 파일로 변환하고 추가적으로 Amazon Kendra의 S3 Document Metadata 기능을 이용하여 검색 대상 문서(=변환된 텍스트 파일)에 대한 메타데이터를 구성합니다. 이 예시에서는 원본 파일에 대한 title , description 두가지의 메타데이터를 추가합니다.
    const AWS = require("aws-sdk");
    const s3 = new AWS.S3();
    
    exports.handler = async (event) => {
        const {title, description, video_key, source_s3_bucket, destination_s3_bucket} = event;
        const getJson = await getJsonInS3(video_key, source_s3_bucket);
        const convertedOutput = convertFile(getJson.Body);
        // 영상 및 음성에 대한 추가 데이터를 S3_KEY.metadata.json 파일에 정의하여 같은 위치에 저장합니다.
        // 해당 예시 코드에서는 파일의 제목과 설명에 대한 정보가 있으며 필요한 경우, 영상의 추가 메타데이터를 정의(라벨 정보, 얼굴 인식 정보 등)하여 저장할 수 있습니다.
        // Amazon Kendra S3 Document Metadata - https://docs.aws.amazon.com/kendra/latest/dg/s3-metadata.html
        const videoMetaDataJson = JSON.stringify({
            DocumentId: video_key,
            Attributes: {
                title: title,
                description: description
            },
        });
        await putObject(`videos/${video_key}/${video_key}.txt`, convertedOutput, destination_s3_bucket);
        await putObject(`videos/${video_key}/${video_key}.txt.metadata.json`, videoMetaDataJson, destination_s3_bucket);
        return event;
    };
    
    function getJsonInS3(video_key, source_s3_bucket) {
        return new Promise((resolve, reject) => {
            const inputParams = {
                Bucket: source_s3_bucket,
                Key: `videos/${video_key}/${video_key}.json`,
            };
            s3.getObject(inputParams, (err, data) => {
                if (err) {
                    console.log(err, err.stack);
                    reject(err);
                } else {
                    resolve(data);
                }
            });
        });
    }
    
    function putObject(key, body, destination_s3_bucket) {
        return new Promise((resolve, reject) => {
            const s3Params = {
                Bucket: destination_s3_bucket,
                Body: body,
                Key: key,
            };
            s3.putObject(s3Params, (err, data) => {
                if (err) {
                    console.log(err, err.stack);
                    reject(err);
                } else {
                    resolve(data);
                }
            });
        });
    }
    // Amazon Transcribe의 json 출력을 Amazon Kendra를 통해 검색할 수 있는 텍스트 형태로 변환합니다.
    // 예시이며 원하는 형식으로 변경하실 수 있습니다.
    function convertFile(file) {
        let convertedOutput = '';
        const json = JSON.parse(file);
        let current_start = json.results.items[0].start_time;
        let formatted_start;
        let formatted_end;
        let nextline = '';
    
        for (let index = 0; index < json.results.items.length; index++) {
            if (json.results.items[index].type === 'punctuation') {
                nextline = nextline.slice(0, -1); //Remove the space before punctuation
                nextline += json.results.items[index].alternatives[0].content;
                formatted_start = current_start;
                formatted_end = json.results.items[index - 1].end_time;
                convertedOutput += '[' + formatted_start + ' ~ ' + formatted_end + '] ';
                convertedOutput += nextline;
                nextline = '';
                let nextItem = json.results.items[index + 1];
                if (nextItem) {
                    current_start = json.results.items[index + 1].start_time;
                }
            } else if (json.results.items[index].end_time - current_start > 5) {
                formatted_start = current_start;
                formatted_end = json.results.items[index - 1].end_time;
                convertedOutput += '[' + formatted_start + ' ~ ' + formatted_end + '] ';
                convertedOutput += nextline;
                nextline = json.results.items[index].alternatives[0].content + ' ';
                current_start = json.results.items[index].start_time;
            } else {
                nextline += json.results.items[index].alternatives[0].content + ' ';
            }
    
        }
        formatted_start = current_start;
        if (json.results.items[json.results.items.length - 1].type !== 'punctuation') {
            formatted_end = json.results.items[json.results.items.length - 1].end_time;
        } else {
            formatted_end = json.results.items[json.results.items.length - 2].end_time;
        }
        if (nextline) {
            convertedOutput += '[' + formatted_start + ' ~ ' + formatted_end + '] ';
            convertedOutput += nextline;
        }
        return convertedOutput;
    }
  4. 저장한 후, [Deploy]를 클릭하여 함수를 배포합니다.
  5. 이후, [Configuration]탭의 [Permissions]를 확인합니다. 람다에 연결된 Role을 클릭합니다. 해당 람다는 Amazon Kendra에 연결된 S3에 변환된 텍스트 파일을 업로드하고 위에서 생성한 음성, 영상의 원본이 저장되어질 별도의 버킷에서 객체를 가져옵니다. 이를 위해 정책을 추가합니다.
  6. IAM Role 콘솔에서 [Add permissions] → [Create inline policy]를 클릭합니다.
  7. Policy 생성 방법을 json으로 변경하기 위해 우측 [JSON] 탭을 클릭합니다.
  8. 아래 json을 복사하여 붙여 넣습니다. 여기서 S3_ASSETS_BUCKET 은 위에서 생성한 음성, 영상의 원본이 저장되어질 별도의 버킷의 이름으로 바꿔주고 S3_KENDRA_BUCKET 은 1부에서 생성한 Kendra 인덱스와 연결된 S3 버킷의 이름으로 바꿔줍니다.
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowGetObjectSourceBucket",
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject"
                ],
                "Resource": "arn:aws:s3:::S3_ASSETS_BUCKET/*"
            },
            {
                "Sid": "AllowPutObjectDestinationBucket",
                "Effect": "Allow",
                "Action": "s3:PutObject",
                "Resource": "arn:aws:s3:::S3_KENDRA_BUCKET/*"
            }
        ]
    }
    
  9. 우측 상단의 [Copy ARN]을 클릭하여 람다 함수의 ARN을 기록해줍니다.

음성 및 영상 텍스트 추출 워크플로 생성하기

이번에는 해당 포스팅의 핵심인 음성 및 영상에서 Amazon Transcribe를 통해 트랜크립션을 만들고 이를 이전에 만든 Lambda 함수를 사용하여 텍스트 파일로 변환하는 워크플로를 만들기 위해 AWS Step Functions을 사용합니다. AWS Step Functions는 개발자가 AWS 서비스를 사용하여 분산 애플리케이션을 구축하고, 프로세스를 자동화하며, 마이크로서비스를 오케스트레이션하고, 데이터 및 기계 학습(ML) 파이프라인을 생성할 수 있도록 지원하는 시각적 워크플로 서비스입니다. 이를 통해 위 이미지와 같은 작업을 수행하는 워크플로를 생성하겠습니다.

  1. 해당 링크를 클릭하고 [Create state machine]을 클릭합니다.
  2. 두번째 옵션인 [Write your workflow in code]를 선택합니다.
  3. [Type]은 Standard를 선택하고 아래 코드를 복사하여 붙여 넣습니다. 이때, 코드 아래의 LAMBDA_ARN 부분을 위에서 생성한 람다 함수의 ARN을 넣어줍니다.
    {
      "StartAt": "StartTranscriptionJob",
      "States": {
        "StartTranscriptionJob": {
          "Next": "Wait10Seconds",
          "Type": "Task",
          "Resource": "arn:aws:states:::aws-sdk:transcribe:startTranscriptionJob",
          "Parameters": {
            "Media": {
              "MediaFileUri.$": "States.Format('s3://{}/videos/{}/{}', $.source_s3_bucket, $.video_key, $.video_key_with_ext)"
            },
            "TranscriptionJobName.$": "$$.Execution.Name",
            "OutputBucketName.$": "$.source_s3_bucket",
            "OutputKey.$": "States.Format('videos/{}/{}.json', $.video_key, $.video_key)",
            "IdentifyMultipleLanguages": true
          },
          "ResultPath": null
        },
        "Wait10Seconds": {
          "Type": "Wait",
          "Seconds": 10,
          "Next": "GetTranscriptionJob"
        },
        "GetTranscriptionJob": {
          "Next": "Choice",
          "Type": "Task",
          "Resource": "arn:aws:states:::aws-sdk:transcribe:getTranscriptionJob",
          "Parameters": {
            "TranscriptionJobName.$": "$$.Execution.Name"
          },
          "ResultPath": "$.transcriptionJob"
        },
        "Choice": {
          "Type": "Choice",
          "Choices": [
            {
              "Variable": "$.transcriptionJob.TranscriptionJob.TranscriptionJobStatus",
              "StringMatches": "COMPLETED",
              "Next": "ConvertJsonToTranscriptionTask"
            }
          ],
          "Default": "Wait10Seconds"
        },
        "ConvertJsonToTranscriptionTask": {
          "Next": "Success",
          "Retry": [
            {
              "ErrorEquals": [
                "Lambda.ServiceException",
                "Lambda.AWSLambdaException",
                "Lambda.SdkClientException"
              ],
              "IntervalSeconds": 2,
              "MaxAttempts": 6,
              "BackoffRate": 2
            }
          ],
          "Type": "Task",
          "Resource": "arn:aws:states:::lambda:invoke",
          "Parameters": {
            "FunctionName": "LAMBDA_ARN:$LATEST",
            "Payload.$": "$"
          },
          "OutputPath": "$.Payload"
        },
        "Success": {
          "Type": "Succeed"
        }
      }
    }
    
  4. [Name]은 GenerateTranscriptionAndSearchableTxt , [Permissions]는 Create new role 을 선택하고
  5. 생성된 State machine을 확인하여 연결된 IAM Role의 링크를 클릭합니다.
  6. IAM Role 콘솔에서 [Add permissions] → [Create inline policy]를 클릭합니다.
  7. Policy 생성 방법을 json으로 변경하기 위해 우측 [JSON] 탭을 클릭합니다.
  8. 아래 json을 복사하여 붙여 넣습니다. 여기서 S3_ASSETS_BUCKET 은 위에서 생성한 음성, 영상의 원본이 저장되어질 별도의 버킷의 이름으로 바꿔줍니다.
    
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowGetObjectPutObjectSourceBucket",
                "Effect": "Allow",
                "Action": [
                    "s3:GetObject",
                    "s3:PutObject"
                ],
                "Resource": "arn:aws:s3:::S3_ASSETS_BUCKET/*"
            },
            {
                "Sid": "ForTranscribe",
                "Effect": "Allow",
                "Action": [
                    "transcribe:GetTranscriptionJob",
                    "transcribe:StartTranscriptionJob"
                ],
                "Resource": "*"
            }
        ]
    }
    
  9. [Next]를 클릭하고 Policy 이름은 GenerateTranscriptionAndSearchableTxtPolicy 으로 입력한 후, [Create policy]를 클릭하여 생성합니다.

워크플로 테스트

AWS StepFunctions에서 제공하는 디버깅 기능을 통해 생성한 상태 머신을 실행해봅니다.

  1. 생성한 State machine의 콘솔에서 [Start execution]을 클릭합니다.
  2. Input으로 아래 샘플 JSON을 복사하여 붙여 넣습니다. 각 값에 알맞게 내용을 변경합니다.
    {
      "title": "YOUR_ASSET_TITLE",
      "description": "YOUR_ASSET_DESCRIPTION",
      "source_s3_bucket": "S3_ASSETS_BUCKET",
      "destination_s3_bucket": "S3_KENDRA_BUCKET",
      "video_key": "sample_key",
      "video_key_with_ext": "sample_key.mp4"
    }
    
  3. [Start execution]을 클릭하여 실행을 확인합니다. 각 상태를 확인하여 상태별 어떤 Input, Output, Events가 있는지 확인합니다.
  4. 실행이 성공적으로 완료되면 Amazon Kendra와 연결된 S3 버킷을 확인하여 아래와 같이 .txt.metadata.json 객체를 확인해봅니다. 텍스트 파일에는 트랜스크립션이 변환된 텍스트가 저장되어 있고 메타데이터 파일에는 앞선 객체를 수식하는 메타데이터 정보가 등록되어 있습니다.

참고: 해당 가이드에서는 직접 실행하지만 프로덕션의 경우, 객체가 S3_ASSETS_BUCKET에 업로드 되어질 때, State machine이 동작하도록 해당 API를 이용하여 구성하시면 변환 워크플로를 자동화하실 수 있습니다.

검색 확인해보기

1부에서 생성한 Amazon Kendra 인덱스에서 음성 및 영상 파일에 대한 검색을 확인해봅니다. 검색 전, 앞서 설정한 것과 같이 title, description 메타데이터를 통한 분류, 검색, 정렬 등을 위해 인덱스의 필드로 추가합니다.

  1. 해당 링크에서 1부에서 생성한 Amazon Kendra 인덱스를 선택합니다.
  2. 좌측 메뉴에서 [Facet definition]을 선택하고 [Add field]를 클릭합니다.
  3. [Field name]은 title, [Data type]은 STRING, [Usage types]는 SearchableDisplayable을 선택하여 줍니다.
  4. 다시 [Add field]를 클릭하고 [Field name]은 description, [Data type]은 STRING, [Usage types]는 SearchableDisplayable을 선택하여 줍니다.
  5. 좌측 메뉴에서 [Data sources]를 선택하고 S3 데이터소스를 클릭합니다.
  6. [Sync now]를 눌러 새롭게 추가된 음성 및 영상에 대한 변환 파일을 싱크합니다.
  7. Sync 작업이 완료되면 좌측 [Search indexed content]를 선택합니다.
  8. 아래 스크린샷과 같이 언어 설정을 해준 후, 검색 창에 찾고자하는 내용을 입력하고 엔터를 누릅니다.
  9. 아래와 같이 트랜스크립션에 대한 검색결과와 [Document fields]를 눌러 위에서 설정한 필드인 title, description을 확인합니다.

마무리

이번 가이드에서는 단순히 Amazon Kendra에서 지원하는 텍스트 및 문서 유형의 파일을 넘어 음성, 영상에 대한 검색을 구현해보았습니다. 기존에 파일의 기본적인 정보만으로 검색하는 것을 뛰어넘어 실제 그 안에 담긴 내용을 기반으로 검색할 수 있는 새로운 미디어 검색 방법입니다. 이는 아래와 같은 케이스뿐만 아니라 다양한 비즈니스 워크로드에 적용될 수 있습니다.

  • 고객 상담 내용 검색
  • 영상 편집을 위한 소스 검색
  • 내외부 세션 영상에 대한 검색
  • 회사 내부 영상 자료에 대한 검색

소개드린 방식을 통해 음성 및 영상 자료을 활용해야하는 비즈니스에서의 업무 효율성과 활용성을 증대시킬 수 있는 검색 엔진을 Amazon Kendra로 이용하실 수 있습니다.

나아가 더욱 다양한 메타데이터의 표현, 원하는 형태의 UI, 기능 추가를 위하여 1부에서 소개한 것과 같이 아래와 같은 예시 UI를 제공하는 별도의 프론트엔드 애플리케이션을 제작하고 Amazon Kendra를 검색을 위한 하나의 백엔드 애플리케이션으로 활용하는 것이 필요할 수 있습니다.

Amazon Kendra는 검색 백엔드 시스템 구축에 필요한 많은 비용과 시간을 절약할 수 있으며, 인덱스 생성과 데이터 소스 연결 등의 작업은 상대적으로 쉽고 빠르게 진행될 수 있습니다. 또한, 머신러닝을 기반으로 제공되는 검색 엔진으로 정확한 검색 결과를 제공할 수 있어, 사용자들의 검색 편의성을 높일 수 있습니다.

이 시리즈에서는 Amazon Kendra에서 기본 제공되는 문서, 텍스트 유형에 그치는 것이 아닌 음성, 영상, 웹사이트, 이미지 등에 대한 검색의 구현 방법을 백엔드 애플리케이션 관점으로 가이드하여 여러분들이 비즈니스에서 활용하고 있는 모든 유형의 자료를 하나의 검색엔진에서 검색하고 관리할 수 있도록 하고자 합니다.

리소스 정리

더 이상 해당 블로그 시리즈에서 가이드한 리소스를 사용하지 않는 경우,콘솔에서 생성한 리소스들을 삭제하여 주세요. 프리 티어가 적용되지 않는 경우 비용이 발생할 수 있습니다.

이번 가이드에서 생성한 AWS StepFunctions과 AWS Lambda는 실행하지 않는다면 비용이 발생하지 않습니다. 더 이상 사용하지 않는 경우, 삭제해주시기 바랍니다.

Amazon Kendra 인덱스 삭제

  1. 해당 링크에서 생성한 인덱스를 선택합니다.
  2. [Delete]를 클릭하여 인덱스를 삭제합니다.

Amazon S3 버킷 삭제

  1. 해당 링크에서 생성한 버킷을 선택합니다.
  2. [Empty]를 클릭하여 버킷의 객체를 모두 삭제합니다.
  3. [Delete]를 클릭하여 버킷을 삭제합니다.
Sangbeom Ma

Sangbeom Ma

마상범 솔루션즈 아키텍트는 영상 이커머스 스타트업 경험을 바탕으로, 클라우드를 통한 애플리케이션 운영, 미디어, AIML 등 다양한 영역에서 고객이 최적의 아키텍처를 구성하도록 돕고 고객의 비즈니스 성과를 달성하도록 AWS 클라우드 전환을 지원하는 업무를 담당하고 있습니다.