🎎

米国における不動産査定審査のAI自動化PoC事例を知る

に公開

以下の内容は、AWS Communityの記事「Building an Appraiser Assistive Tool using Amazon Nova Pro」を日本語に翻訳したものです。適宜、補足を入れていますが、概ね原文通りです。

https://community.aws/content/2vhTDZOTl9WY37sJrW4sg4VtRWG/building-an-appraiser-assistive-tool-using-amazon-nova-pro


AIによる不動産査定支援ツール(AAT)で遅くて一貫性のない査定を効率化・標準化する

概要

不動産査定は不動産業界や住宅ローン業界にとって不可欠ですが、従来の査定プロセスは時間がかかり、担当者によるバラつきも課題です。
本記事では、AIを活用した不動産査定ツール(Appraiser Assistive Tool、AAT)が、画像認識と機械学習によって査定プロセスを効率化・標準化する仕組みを解説します。
特に、Fannie Maeの物件状態・品質評価システムと連携し、Amazon Nova Proを用いた概念実証(PoC)を紹介します。


Fannie Maeの評価基準

Fannie Maeの物件状態(Condition)と品質(Quality)は、ライセンスを持つ査定士による目視検査で決まります。
評価はC1〜C6(状態)、Q1〜Q6(品質)のスケールで、物理的な状態・メンテナンス・建築品質・材料などを総合的に判断します。
この標準化された評価は、貸し手がリスクを評価し、物件の適格性やローン条件を決める際の基準となります。
詳細はProperty Condition Ratingsをご参照ください。


AIによる自動化の意義

Fannie Maeの評価は目視検査が前提ですが、画像解析AIを活用することで、

  • 欠陥の自動検出
  • 材料品質や摩耗パターンの評価
  • 建築特徴の判定
    などを一貫性高く自動化できます。

AIは査定士のアシスト役として、効率と精度を向上させつつ、最終判断は人間が行うことでプロフェッショナルな監督・責任を維持できます。


AATの仕組みと出力例

AATは、システムプロンプトを使って物件画像を処理し、以下を自動生成します。

  • Fannie Mae物件状態評価(C1〜C6)
  • 対応する数値評価(1〜6、小数点2桁まで対応)
  • 評価理由(観察ポイント)

AATは、Amazon Nova Proを使用し、Fannie Mae物件状態評価を生成します。Amazon Nova Proは、マルチモーダルな画像・テキスト処理に優れ、精度・速度・コストのバランスが良いモデルです。

AATへの入力

以下のサンプル不動産画像を、自動化したAAT測定の評価に使用します。画像は、Amazon Nova Canvasにより作成されたものです。

前面図
Front Elevation

台所
Kitchen

寝室
Bedroom

浴室
Bathroom

裏庭
Backyard


システムプロンプト例

あなたは経験豊富な不動産鑑定士です。提供された物件画像を分析し、以下の基準に基づいて詳細な評価を行ってください。

C1 - 新築/未入居
改修の必要なし 新品同様の状態 新築または最近リノベーションされた可能性あり

C2 - 保守遅延なし
最近の更新/リノベーション 最小限の摩耗や損傷 高品質な素材と施工

C3 - 良好に維持管理されている
軽微な物理的劣化のみ 通常の摩耗や損傷 いくつかの軽微な修理/更新が必要 依然として良好な状態

C4 - 軽微な保守遅延あり
いくつかの修理が必要 劣化が一部見られる 機能的だが経年や摩耗が見られる

C5 - 重大な保守遅延
大規模な修理が必要 複数の箇所で修理/交換が必要 著しい劣化 安全性や健全性に影響する可能性あり 大規模な改修が必要な場合あり

C6 - 深刻な保守遅延
大きな損傷/荒廃 健康・安全上の問題あり 大規模なリノベーションが必要 現状では居住不可

以下を提供してください:
1. C1からC6の間のレター評価
2. 1から6の間の数値評価(C1が1、C6が6に対応。中間の場合は小数点以下2桁まで記載)
3. 評価に影響した主な観察ポイント

回答は以下の構造のJSONオブジェクトとしてフォーマットしてください:
{
  "letter_rating": <C1-C6>,
  "numeric_rating": <1-6>,
  "key_observations": ["<観察ポイント1>", "<観察ポイント2>", ...]
}
原文プロンプト
You are an experienced real estate appraiser. Analyze the provided property image and provide a detailed appraisal rating based on the following criteria:

C1 - Newly constructed/Never occupied
No improvements needed   Like-new condition   May be new construction or recently renovated

C2 - No deferred maintenance
Recent updates/renovations   Minimal wear and tear   High-quality materials and workmanship

C3 - Well-maintained
Limited minor physical deterioration   Normal wear and tear   Some minor repairs/updates needed   Still in good condition

C4 - Some minor deferred maintenance
In need of some repairs   Some deterioration visible   Functional but showing age/wear

C5 - Major deferred maintenance
Needs significant repairs   Multiple items needing repair/replacement   Substantial deterioration   May affect safety/soundness   May need substantial rehabilitation

C6 - Severe deferred maintenance
Substantial damage/disrepair   Health/safety issues present   Major renovation required   Uninhabitable in current condition

Please provide:
1. A letter rating between C1 and C6
2. A numeric rating between 1 and 6 and use 2 decimal places when the rating is in between with 1 at C1 and 6 at C6
3. Key observations that influenced your assessment

Format your response as a JSON object with the following structure:
{
  "letter_rating": <C1-C6>,
  "numeric_rating": <1-6>,
  "key_observations": ["<observation1>", "<observation2>", ...]
}

AATの出力

出力


活用のポイント・注意事項

不動産査定ツール(AAT)のPoCにより、人間の監督を介入させながらも、不動産査定審査をAIで強化できることが実演できました。評価の一貫性を保ちつつ、専門的な不動産査定の知見を入れた機械学習モデルの速度をミックスすることで、以下を期待することができます。

  • 地域差のない一貫した評価が可能
  • 自動観察ログ(automated observation logging)でドキュメント化が容易
  • 標準化された評価基準で精度向上

(注)
Amazon Bedrock Guardrails公式ドキュメント)を活用し、責任あるAI運用・セーフガードを実装してください


謝辞

本記事の開発にはMallik Panchumarthy氏の技術的貢献が大きく、彼の提案や問題解決アプローチがプロジェクト成功の鍵となりました。心より感謝いたします。

付録

シーケンス図

Sequence Diagram

Pythonモジュール

この機能の実行時、Amazon Nova Proにアクセスがあり、推論プロファイルを使用していることを確認してください。システムプロンプトは、ご自身のニーズに沿って変更してください。画像フォーマットはjpgを想定しており、他のフォーマットを使用する場合は改修が必要です。

from typing import List
# Import specific Boto3 client for AWS Bedrock
from boto3 import client as boto3_client
import time
from botocore.exceptions import ClientError
import random

def analyze_property_images_with_bedrock(
    image_paths: List[str],
    inference_profile_arn: str,
    system_prompt: str
) -> str:
    """
    Analyze frames using Amazon Bedrock Converse API.
    
    Args:
        images: List of paths to property images
        inference_profile_arn: ARN of the Bedrock inference profile
        system_prompt: use prompt text from `property_condition_rating.txt` for property condition rating
                       or use prompt text from `quality_of_construction_rating.txt` for quality rating
    
    Returns:
        Letter rating, numeric rating, and the reason for the generated rating
    """
    bedrock_runtime = boto3_client(
        service_name="bedrock-runtime",
        # Extract region from ARN
        region_name=inference_profile_arn.split(":")[3]
    )

    all_ratings = []
          
    # Prepare the messages for conversation        
    for image_path in image_paths:
        messages = []
        # Read image as bytes without encoding
        with open(image_path, "rb") as image_file:
            image_bytes = image_file.read()

            messages.append({
                "role": "user",
                "content": [{
                    "image": {
                        "format": "jpeg",
                        "source": {
                            # Send raw bytes directly
                            "bytes": image_bytes
                        }
                    }
                }]
            })
            
        # Use the Converse API with retry logic
        response = call_converse_with_retry(
            bedrock_runtime,
            modelId=inference_profile_arn,
            messages=messages,
            system=[{
                "text": system_prompt
            }]
        )
        
        # Extract content from response
        if ('output' in response and 
                'message' in response['output'] and 
                'content' in response['output']['message']):
            rating = response['output']['message']['content'][0]['text']
            # print(f"{image_path} rating is: {rating}")
            all_ratings.append(rating)
        else:
            print(f"Unexpected response format: {response}")
              
    # Generate final summary using Converse API
    try:
        # Combine all ratings into a single composite rating
        combined_ratings = "\n\n".join(all_ratings)
        print("Starting final rating...")
        final_response = call_converse_with_retry(
            bedrock_runtime,
            modelId=inference_profile_arn,
            messages=[{
                "role": "user",
                "content": [{
                    "text": combined_ratings
                }]
            }],
            system=[{
                "text": (
                    "Provide a overall numeric rating, letter rating, and the reason for the rating. "
                    "Letter rating C1 is the best while C6 is the worst."
                    "Numeric rating 1 is the best while 6 is the worst."
                    "The overall rating should be based on the worst case rating."                                        
                )
            }]
        )

        # Extract content from final response
        if ('output' in final_response and 
                'message' in final_response['output'] and 
                'content' in final_response['output']['message']):
            return final_response['output']['message']['content'][0]['text']
        else:
            print(f"Unexpected final response format: {final_response}")
            return "Error: Unexpected response format"
    
    except Exception as e:
        print(f"Error generating final summary: {str(e)}")
        return "Error generating summary"

def call_converse_with_retry(bedrock_runtime, **kwargs):
    """
    Call the Bedrock Converse API with exponential backoff retry logic.
    
    Args:
        bedrock_runtime: Bedrock runtime client
        **kwargs: Arguments to pass to the converse API
    
    Returns:
        API response
    """
    max_retries = 5
    base_delay = 1  # Starting delay of 1 second
    
    for attempt in range(max_retries):
        try:
            return bedrock_runtime.converse(**kwargs)
        except ClientError as e:
            error_code = e.response['Error']['Code']
            if error_code == 'ThrottlingException':
                if attempt == max_retries - 1:  # Last attempt
                    raise  # Re-raise the exception if we've exhausted all retries
                
                # Calculate exponential backoff with jitter
                delay = (2 ** attempt * base_delay) + random.random()
                print(f"ThrottlingException encountered. Retrying in {delay:.2f} seconds...")
                time.sleep(delay)
            else:
                raise  # Re-raise if it's not a throttling error
        except Exception as e:
            # For other exceptions, don't retry
            raise

本記事の内容は筆者個人の見解であり、AWS公式見解を示すものではありません。


参考リンク

Discussion