NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

理解モデルAmazon Nova Proで画像生成を検証・再試行する(Amazon Nova Canvas編)

小西秀和です。

以前の記事では、Anthropic Claude 3.5 Sonnetの画像理解・分析機能を活用して、Amazon Titan Image Generator G1で生成した画像を検証・再生成するAmazon Bedrockの使用例を紹介しました。

本記事では、マルチモーダルの理解モデルであるAmazon Nova Proを活用して、Amazon Nova Canvasで生成した画像を検証・再生成するAmazon Bedrockの使用例をご紹介します。
この試みは、前述の記事同様に生成画像の要件充足を自動的に判定することで、人間による目視確認の作業量削減も目指しています。

※本記事および当執筆者のその他の記事で掲載されているソースコードは自主研究活動の一貫として作成したものであり、動作を保証するものではありません。使用する場合は自己責任でお願い致します。また、予告なく修正することもありますのでご了承ください。
※本記事執筆にあたっては個人でユーザー登録したAWSアカウント上でAWSサービスを使用しています。
※本記事執筆にあたって使用したAmazon Bedrockの各Modelは2024-12-23(JST)に実行し、その時点における次のEnd user license agreement (EULA)に基づいています。
Amazon Nova Pro(amazon.nova-pro-v1:0): End user license agreement (EULA) (AWS Customer Agreement and Service Terms)
Amazon Nova Canvas(amazon.nova-canvas-v1:0): End user license agreement (EULA) (AWS Customer Agreement and Service Terms)

構成図と処理フロー

今回のテーマを実現する構成図は次のようになります。

Using Amazon Nova Pro Vision Capabilities on Amazon Bedrock to Verify, Regenerate, and Automate Image Generation with Amazon Nova Canvas
Using Amazon Nova Pro Vision Capabilities on Amazon Bedrock to Verify, Regenerate, and Automate Image Generation with Amazon Nova Canvas

この処理フローについて詳細を説明します。

1. プロンプトやパラメータを含むイベントを入力します。
2-1. 入力した画像作成を指示するプロンプトでAmazon BedrockでAmazon Nova Canvasモデルを実行します。
2-2. 生成された画像をAmazon S3に保存します。
2-3. Amazon S3に保存された画像に対してAmazon BedrockでAmazon Nova Proモデルを実行し、画像作成を指示したプロンプトの要件にふさわしいかを検証します。
* 画像作成を指示したプロンプトの要件にふさわしいと判断されなければ、2-1.から2-3.の処理を指定した同一プロンプト実行回数だけ繰り返します。
* 画像作成を指示したプロンプトの要件にふさわしいと判断されれば、その画像を出力結果とします。
3. 修正プロンプト実行回数を超えておらず、画像作成を指示したプロンプトの要件にふさわしいと判断されない回数が同一プロンプト実行回数を超えた場合、Amazon BedrockでAmazon Nova Proモデルを実行し、画像作成を指示するプロンプトを要件が満たされる可能性が高いものに修正します。この新しい画像作成を指示するプロンプトで2-1.から処理をやり直します。
* 修正プロンプト実行回数を超えた場合、エラーとして処理を終了します。

この処理フローでポイントとなるのはAmazon Nova Proモデルによる画像作成を指示するプロンプトの修正です。
画像作成を指示するプロンプトがAIにとって理解しやすいものであれば、要件を満たす画像は何回か実行すれば出力される可能性が高いでしょう。
ただ、画像作成を指示するプロンプトがAIにとって理解しにくいものであれば、要件を満たす画像の出力がされない事も考えられます。
そのため、指定した同一プロンプト実行回数を超えた場合には、Amazon BedrockでAmazon Nova Proモデルを実行し、画像作成を指示するプロンプトを最適化したものに修正する処理を入れました。

実装例

入力するイベントのフォーマット

{
    "prompt": "[画像生成のための初期プロンプト]",
    "max_retry_attempts": [各プロンプトで画像生成を試行する最大回数],
    "max_prompt_revisions": [プロンプトを修正する最大回数],
    "output_s3_bucket_name": "[生成された画像を保存するS3バケットの名前]",
    "output_s3_key_prefix": "[生成された画像のS3キーのプレフィックス]",
    "nova_pro_validate_temperature": [画像検証時のNova Proモデルのtemperatureパラメータ(0.0〜1.0)],
    "nova_pro_validate_top_p": [画像検証時のNova ProモデルのTop-pパラメータ(0.0〜1.0)],
    "nova_pro_validate_top_k": [画像検証時のNova ProモデルのTop-kパラメータ],
    "nova_pro_validate_max_tokens": [画像検証時のNova Proモデルが生成する最大トークン数],
    "nova_pro_revise_temperature": [プロンプト修正時のNova Proモデルのtemperatureパラメータ(0.0〜1.0)],
    "nova_pro_revise_top_p": [プロンプト修正時のNova ProモデルのTop-pパラメータ(0.0〜1.0)],
    "nova_pro_revise_top_k": [プロンプト修正時のNova ProモデルのTop-kパラメータ],
    "nova_pro_revise_max_tokens": [プロンプト修正時のNova Proモデルが生成する最大トークン数],
    "nova_canvas_cfg_scale": [Nova CanvasモデルのCFGスケール],
    "nova_canvas_width": [Nova Canvasモデルで生成する画像の幅(ピクセル単位)],
    "nova_canvas_height": [Nova Canvasモデルで生成する画像の高さ(ピクセル単位)],
    "nova_canvas_number_of_images": [Nova Canvasモデルで生成する画像の数],
    "nova_canvas_seed": [Nova Canvasモデルで使用する乱数シード値(再現性のため、指定しない場合はランダム)]
}

入力するイベントの例

{
    "prompt": "A serene landscape with mountains and a lake",
    "max_retry_attempts": 5,
    "max_prompt_revisions": 3,
    "output_s3_bucket_name": "your-output-bucket-name",
    "output_s3_key_prefix": "generated-images-nova",
    "nova_pro_validate_temperature": 1.0,
    "nova_pro_validate_top_p": 0.9,
    "nova_pro_validate_top_k": 50,
    "nova_pro_validate_max_tokens": 5120,
    "nova_pro_revise_temperature": 1.0,
    "nova_pro_revise_top_p": 0.9,
    "nova_pro_revise_top_k": 50,
    "nova_pro_revise_max_tokens": 5120,
    "nova_canvas_cfg_scale": 10.0,
    "nova_canvas_width": 1024,
    "nova_canvas_height": 1024,
    "nova_canvas_number_of_images": 1, 
    "nova_canvas_seed": 0
}

ソースコード

今回、実装したソースコードは次のようになります。

# #Event Sample
# {
#     "prompt": "A serene landscape with mountains and a lake",
#     "max_retry_attempts": 5,
#     "max_prompt_revisions": 3,
#     "output_s3_bucket_name": "your-output-bucket-name",
#     "output_s3_key_prefix": "generated-images-nova",
#     "nova_pro_validate_temperature": 1.0,
#     "nova_pro_validate_top_p": 0.9,
#     "nova_pro_validate_top_k": 50,
#     "nova_pro_validate_max_tokens": 5120,
#     "nova_pro_revise_temperature": 1.0,
#     "nova_pro_revise_top_p": 0.9,
#     "nova_pro_revise_top_k": 50,
#     "nova_pro_revise_max_tokens": 5120,
#     "nova_canvas_cfg_scale": 10.0,
#     "nova_canvas_width": 1024,
#     "nova_canvas_height": 1024,
#     "nova_canvas_number_of_images": 1, 
#     "nova_canvas_seed": 0
# }

import boto3
import json
import base64
import os
import sys
from io import BytesIO
import datetime
import random

region = os.environ.get('AWS_REGION')
bedrock_runtime_client = boto3.client('bedrock-runtime', region_name=region)
s3_client = boto3.client('s3', region_name=region)

def nova_pro_invoke_model(input_prompt, image_media_format=None, image_data_base64=None, model_params={}):
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "text": input_prompt
                }
            ]
        }
    ]

    if image_media_format and image_data_base64:
        messages[0]["content"].insert(0, {
            "image": {
                "format": image_media_format, 
                "source": { 
                    "bytes": image_data_base64 
                }
            }
        })

    body = {
        "messages": messages,
        "inferenceConfig": { # all Optional
            "max_new_tokens": model_params.get('max_tokens', 5120), # greater than 0, equal or less than 5k (default: dynamic*)
            "temperature": model_params.get('temperature', 0.7), # greater then 0 and less than 1.0 (default: 0.7)
            "top_p": model_params.get('top_p', 0.9), # greater than 0, equal or less than 1.0 (default: 0.9)
            "top_k": model_params.get('top_k', 50) # 0 or greater (default: 50)
        }
    }

    response = bedrock_runtime_client.invoke_model(
        modelId='amazon.nova-pro-v1:0',
        contentType='application/json',
        accept='application/json',
        body=json.dumps(body)
    )

    response_body = json.loads(response.get('body').read())
    response_text = response_body["output"]["message"]["content"][0]['text']
    return response_text

def nova_canvas_invoke_model(prompt, model_params={}):
    seed = model_params.get('seed', 0)
    if seed == 0:
        seed = random.randint(0, 858993459)
    
    optimized_prompt = truncate_to_1024(prompt)

    body = {
        "taskType": "TEXT_IMAGE",
        "textToImageParams": {
            "text": optimized_prompt
        },
        "imageGenerationConfig": {
            "numberOfImages": model_params.get('img_number_of_images', 1),
            "height": model_params.get('height', 1024),
            "width": model_params.get('width', 1024),
            "cfgScale": model_params.get('cfg_scale', 8),
            "seed": seed
        }
    }
    
    print(f"Nova Canvas model parameters: {body}")
    
    response = bedrock_runtime_client.invoke_model(
        body=json.dumps(body),
        modelId="amazon.nova-canvas-v1:0",
        contentType="application/json",
        accept="application/json"
    )
    
    response_body = json.loads(response['body'].read())
    image_data = base64.b64decode(response_body.get("images")[0].encode('ascii'))

    finish_reason = response_body.get("error")
    if finish_reason is not None:
        print(f"Image generation error. Error is {finish_reason}")
    else:
        print(f"Image generated successfully with seed: {seed}")
    
    return image_data

def truncate_to_1024(text):
    if len(text) <= 1024:
        return text
    
    truncated = text[:1024]
    last_period = truncated.rfind('.')
    last_comma = truncated.rfind(',')
    last_break = max(last_period, last_comma)
    
    if last_break > 256:  # Only if the last sentence or phrase is not too long
        return truncated[:last_break + 1]
    else:
        return truncated

def save_image_to_s3(image_data, bucket, key):
    s3_client.put_object(
        Bucket=bucket,
        Key=key,
        Body=image_data
    )
    print(f"Image saved to S3: s3://{bucket}/{key}")

def validate_image(image_data, prompt, nova_pro_validate_params):
    image_base64 = base64.b64encode(image_data).decode('utf-8')
    
    input_prompt = f"""Does this image match the following prompt? Prompt: {prompt}. 
    Please answer in the following JSON format:
    {{"result":"<YES or NO>", "reason":"<Reason for your decision>"}}
    Ensure your response can be parsed as valid JSON. Do not include any explanations, comments, or additional text outside of the JSON structure."""

    validation_result = nova_pro_invoke_model(input_prompt, "png", image_base64, nova_pro_validate_params)
    
    try:
        print(f"validation Result: {validation_result}")
        parsed_result = json.loads(validation_result)
        is_valid = parsed_result['result'].upper() == 'YES'
        print(f"Image validation result: {is_valid}")
        print(f"Validation reason: {parsed_result['reason']}")
        return is_valid
    except json.JSONDecodeError:
        print(f"Error parsing validation result: {validation_result}")
        return False

def revise_prompt(original_prompt, nova_pro_revise_params):
    input_prompt = f"""Revise the following image generation prompt to optimize it for Amazon Nova Canvas, incorporating best practices:

    {original_prompt}

    Please consider the following guidelines in your revision:
    1. Frame the prompt as an image caption rather than a command, focusing on describing what's in the image.
    2. Include clear descriptions of:
       - The main subject
       - The environment/setting
       - Position or pose of subjects (if relevant)
       - Lighting conditions (if relevant)
       - Camera position/framing (if relevant)
       - Visual style or medium (if relevant)
    3. Avoid using negation words (no, not, without, etc.) in the main prompt.
    4. Place most important elements at the beginning of the prompt.
    5. Separate different elements with clear, descriptive phrases.
    6. Use specific, descriptive adjectives to convey the desired mood and style.
    7. If the original prompt is not in English, translate it to English.

    Your goal is to create a clear, detailed prompt that will result in a high-quality image generation with Nova Canvas, while staying within the 1024-character limit.
    
    Please provide your response in the following JSON format:
    {{"revised_prompt":"<Revised Prompt>"}}
    Ensure your response can be parsed as valid JSON. Do not include any explanations, comments, or additional text outside of the JSON structure."""

    revised_prompt_json = nova_pro_invoke_model(input_prompt, model_params=nova_pro_revise_params)
    print(f"Original prompt: {original_prompt}")
    print(f"Revised prompt JSON: {revised_prompt_json.strip()}")
    
    try:
        parsed_result = json.loads(revised_prompt_json)
        revised_prompt = parsed_result['revised_prompt']
        print(f"Parsed revised prompt: {revised_prompt}")
        return revised_prompt
    except json.JSONDecodeError:
        print(f"Error parsing revised prompt result: {revised_prompt_json}")
        return original_prompt

def lambda_handler(event, context):
    try:
        initial_prompt = event['prompt']
        prompt = initial_prompt
        max_retry_attempts = max(0, event.get('max_retry_attempts', 5) - 1)
        max_prompt_revisions = max(0, event.get('max_prompt_revisions', 3) - 1)
        output_s3_bucket_name = event['output_s3_bucket_name']
        output_s3_key_prefix = event.get('output_s3_key_prefix', 'generated-images')

        print(f"Initial prompt: {initial_prompt}")
        print(f"Max retry attempts: {max_retry_attempts}")
        print(f"Max prompt revisions: {max_prompt_revisions}")

        # Model parameters
        nova_pro_validate_params = {
            'temperature': event.get('nova_pro_validate_temperature', 0.7),
            'top_p': event.get('nova_pro_validate_top_p', 0.9),
            'top_k': event.get('nova_pro_validate_top_k', 50),
            'max_tokens': event.get('nova_pro_validate_max_tokens', 5120)
        }
        nova_pro_revise_params = {
            'temperature': event.get('nova_pro_revise_temperature', 0.7),
            'top_p': event.get('nova_pro_revise_top_p', 0.9),
            'top_k': event.get('nova_pro_revise_top_k', 50),
            'max_tokens': event.get('nova_pro_revise_max_tokens', 5120)
        }
        nova_canvas_params = {
            'cfg_scale': event.get('nova_canvas_cfg_scale', 8),
            "width": event.get('nova_canvas_width', 1024),
            "height": event.get('nova_canvas_height', 1024),
            'img_number_of_images': event.get('nova_canvas_number_of_images', 1),
            "seed": event.get('nova_canvas_seed', 0)
        }

        print(f"Nova Pro validate params: {nova_pro_validate_params}")
        print(f"Nova Pro revise params: {nova_pro_revise_params}")
        print(f"Nova Canvas params: {nova_canvas_params}")

        # Generate start timestamp and S3 key
        start_timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
        for revision in range(max_prompt_revisions + 1):
            print(f"Starting revision {revision}")
            for attempt in range(max_retry_attempts + 1):
                print(f"Attempt {attempt} for generating image")
                
                # Generate image with Nova Canvas
                image_data = nova_canvas_invoke_model(prompt, nova_canvas_params)

                image_key = f"{output_s3_key_prefix}-{start_timestamp}-{revision:03d}-{attempt:03d}.png"

                # Save image to S3
                save_image_to_s3(image_data, output_s3_bucket_name, image_key)

                # Validate image with Amazon Nova Pro
                is_valid = validate_image(image_data, initial_prompt, nova_pro_validate_params)

                if is_valid:
                    print("Valid image generated successfully")
                    return {
                        'statusCode': 200,
                        'body': json.dumps({
                            'status': 'SUCCESS',
                            'message': 'Image generated successfully',
                            'output_s3_bucket_url': f'https://s3.console.aws.amazon.com/s3/buckets/{output_s3_bucket_name}',
                            'output_s3_object_url': f'https://s3.console.aws.amazon.com/s3/object/{output_s3_bucket_name}?region={region}&bucketType=general&prefix={image_key}'
                        })
                    }

            # If max retry attempts reached and not the last revision, revise prompt
            if revision < max_prompt_revisions:
                print("Revising prompt")
                prompt = revise_prompt(initial_prompt, nova_pro_revise_params)

        print("Failed to generate a valid image after all attempts and revisions")
        return {
            'statusCode': 400,
            'body': json.dumps({
                'status': 'FAIL',
                'error': 'Failed to generate a valid image after all attempts and revisions'
            })
        }

    except Exception as ex:
        print(f'Exception: {ex}')
        tb = sys.exc_info()[2]
        err_message = f'Exception: {str(ex.with_traceback(tb))}'
        print(err_message)
        return {
            'statusCode': 500,
            'body': json.dumps({
                'status': 'FAIL',
                'error': err_message
            })
        }

このソースコードで工夫した点には以下のものが挙げられます。

  • 画像生成と検証のサイクルを自動化し、要件を満たすまで繰り返す仕組みを実装しました。
  • Amazon Nova Proを使用して生成された画像の検証と、プロンプトの修正を行いました。
  • Amazon Nova Canvasを使用して高品質な画像生成を行いました。
  • プロンプトの修正指示には、Amazon Nova Canvas prompting best practicesに記載されている推奨事項を取り入れました。
  • 画像生成パラメータ(cfg_scale, steps, width, height, seed)をカスタマイズ可能にしました。
  • Amazon Nova Proの呼び出しパラメータ(temperature, top_p, top_k, max_tokens)も調整可能にしました。
  • 生成された画像を自動的にS3バケットに保存し、結果のURLを返しました。
  • エラーハンドリングとログ出力を適切に実装し、トラブルシューティングを容易にしました。
  • JSONフォーマットを使用して、Amazon Nova Proとの対話を構造化し、結果の解析を容易にしました。
  • 最大リトライ回数と最大プロンプト修正回数を設定可能にし、無限ループを防ぎました。

実行内容と結果

実行の一例:入力パラメータ

{
    "prompt": "自然の中から見た夜景で、空にはオーロラと月と流星群があり、地上には海が広がって流氷が流れ、地平線から太陽が出ている無人の写真。",
    "max_retry_attempts": 5,
    "max_prompt_revisions": 5,
    "output_s3_bucket_name": "ho2k.com",
    "output_s3_key_prefix": "generated-images-nova",
    "nova_pro_validate_temperature": 1.0,
    "nova_pro_validate_top_p": 0.9,
    "nova_pro_validate_top_k": 50,
    "nova_pro_validate_max_tokens": 5120,
    "nova_pro_revise_temperature": 1.0,
    "nova_pro_revise_top_p": 0.9,
    "nova_pro_revise_top_k": 50,
    "nova_pro_revise_max_tokens": 5120,
    "nova_canvas_cfg_scale": 10.0,
    "nova_canvas_width": 1024,
    "nova_canvas_height": 1024,
    "nova_canvas_number_of_images": 1, 
    "nova_canvas_seed": 0
}

今回の実行例の入力パラメータには以下の工夫をしています。

  • max_retry_attemptsを5に設定して、画像生成の成功率を高めました。
  • max_prompt_revisionsを5に設定して、必要に応じてプロンプトを改善する機会を増やしました。
  • 画像検証と修正のためのAmazon Nova Proモデルのパラメータ(temperature, top_p, top_k, max_tokens)を細かく設定しました。
  • nova_canvas_cfg_scaleを10に設定し、プロンプトへの忠実度を高めました。
  • 画像生成に使用するseedがランダムに設定されるようにし、毎回異なる画像が生成されるようにしました。

実行の一例:結果

生成された画像

今回の試行において最終的にプロンプトの要件を満たして検証をパスした画像は以下です。
この画像は実際に「自然の中から見た夜景で、空にはオーロラと月と流星群があり、地上には海が広がって流氷が流れ、地平線から太陽が出ている無人の写真。」という要件をほぼすべて満たしています(流星群の流星の数は限られていますが、月と地平線上の太陽という相反する風景、流星群、そして流氷が明確に表現されています)。
また、最終的に検証をパスした画像は、最終的に検証をパスするよりも前に生成された他の画像(後述の「生成された画像一覧」参照)と比較しても、指示された要件をより多く満たしていることも確認できました。

プロンプトの要件を満たして検証をパスした画像
プロンプトの要件を満たして検証をパスした画像


今回、試した実行で生成された画像の一覧は以下です。
この「生成された画像一覧」の各行の画像は、それぞれ修正された異なるプロンプトから生成されたものです。
最初に入力された日本語の文章によるプロンプトでは、要件と若干異なる画像が出力されているのに対し、1回目のプロンプト修正後の初回試行から要件に非常に近い画像が生成され、2回目の試行で画像が要件を満たしました。
Amazon Nova Canvasの特に注目すべき点は、Amazon Titan Image Generator G1とは異なり、日本語のプロンプトの内容を理解し、要件に非常に近い画像を生成できることです。

生成された画像一覧
生成された画像一覧


修正されたプロンプトの変化

上記で掲載した「生成された画像一覧」の各行の画像は、それぞれ修正された異なるプロンプトから生成されています。
具体的には、「生成された画像一覧」の最初の行の画像は、以下の「修正0回目」のプロンプトから生成されたものであり、「生成された画像一覧」の最後の行の画像は、以下の「修正1回目」のプロンプトから生成されたものです。
プロンプトの修正回数ごとに、修正された画像生成プロンプトの内容を見てみましょう。

修正0回目

自然の中から見た夜景で、空にはオーロラと月と流星群があり、地上には海が広がって流氷が流れ、地平線から太陽が出ている無人の写真。

修正1回目

An awe-inspiring nighttime seascape featuring vibrant auroras, a luminous moon, and a spectacular meteor shower. The tranquil ocean is dotted with drifting icebergs, creating a serene and mystical atmosphere. The horizon glows with the warm light of the rising sun, casting a golden hue across the scene. The image is captured from a vantage point within nature, providing a panoramic view of this breathtaking natural phenomenon. The visual style is realistic, with a focus on capturing the vivid colors and dynamic lighting of the auroras and meteors against the calm, icy waters.

特に上記で掲載した「生成された画像一覧」と関連付けて見ると、最初に入力された日本語の文章によるプロンプトは画像生成に最適化されていませんでしたが、それでも要件に近い画像を生成しました。
一方、最初の修正でAmazon Nova Proによって画像生成用に最適化されたプロンプトを使用した際には、その後の実行で要件に非常に近い画像が生成され、2回目の試行で要件を満たす画像が生成されました。
このように、プロンプトの修正ごと、生成の実行ごとに画像が変化し、プロンプトの要件を満たす画像が最終的に検証をパスします。

<参考資料>
AWS Documentation(Amazon Bedrock)
AWS Documentation(Amazon Nova)
Amazon Nova Canvas prompting best practices
Using Amazon Nova Pro Vision Capabilities on Amazon Bedrock to Verify, Regenerate, and Automate Image Generation with Amazon Nova Canvas
Tech Blog with related articles referenced

まとめ

この記事では、Amazon Nova Proの画像理解・分析機能を使用して、Amazon Nova Canvasで生成された画像を検証し再生成する例をAmazon Bedrockを使用して紹介しました。
この試みを通して、Nova Proの画像認識機能が画像の内容や表現を認識でき、要件充足の検証に活用できることを確認しました。
さらに、Amazon Nova ProはAmazon Nova Canvas向けのプロンプト最適化にも使用でき、日本語のプロンプトを英語に翻訳し、画像生成に適した形式に修正する能力が高いこともわかりました。
そして最も重要なことは、画像生成と検証のサイクルを自動化することで、人による目視確認作業を大幅に削減できたことです。

注目すべき点は、以前のAmazon Titan Image Generator G1を使用した例と今回のAmazon Nova Canvasを使用した例のように、プロンプト修正の指示を各画像生成AIのベストプラクティスに合わせることで、効果的なプロンプト最適化と画像生成の自動化を様々な画像生成AIに適用できることです。
このように、Amazon Nova Proは画像生成AI(Amazon Nova Canvasなど)の制御と、これまで自動化が困難だったプロセスをより簡単に実現できる可能性があります。

今後もAmazon Bedrockが提供するAIモデルの進化と、それらを活用した新しい実装方法を注視しながら、さらなる応用分野の拡大を探っていきます。

Written by Hidekazu Konishi
Hidekazu Konishi (小西秀和), a Japan AWS Top Engineer and a Japan All AWS Certifications Engineer

執筆者小西秀和

Japan AWS Top Engineer / Japan All AWS Certifications Engineer(AWS認定全冠)として、知識と実践的な経験を活かし、AWSの活用に取り組んでいます。
NRIネットコムBlog: 小西 秀和: 記事一覧
Amazon.co.jp: 小西 秀和: books, biography, latest update
Personal Tech Blog | [B! Bookmark]