前言
本文将探讨如何使用亚马逊云科技上提供的生成式 AI 大模型进行视频以及图像内容审核。本文将使用相同的视频图像审核测试数据集,从审核准确率、误报率、漏报率等多项指标全面评估亚马逊云科技 Amazon Bedrock 上三款多模态大模型的表现差异,这包括亚马逊自研大模型 Nova 系列的 Lite 和 Pro 模型、Anthropic 的 Claude 3.7 Sonnet 模型,对比分析不同模型在色情内容和暴力内容审核场景下的优势,为您选择和构建合适的基于大模型的内容审核解决方案提供洞见与参考。如果您对文本内容审核感兴趣,请参阅 Amazon Bedrock 上的模型擂台赛:DeepSeek、Nova、Claude,谁是最强文本审核大模型?
此外,亚马逊云科技提供了一系列托管人工智能服务 Amazon Rekognition ,Amazon Comprehend,Amazon Transcribe,Amazon Translate 和 Amazon Bedrock 以及其他技术,来帮助您快速打造自动化智能化多模态内容审核方案(详情参考博客),包括图像、视频、文本和音频审核工作流程。
一、图片内容检测分析
数据集概况
本文分别测试了模型在色情内容以及暴力内容的多模态检测,使用的公开数据集来自 Kaggle 以及 github。两个数据集合并并抽取了 400 数据作为此次试验的总数据集。
- 色情内容数据集:共 300 条数据,其中色情图片 100 条,非色情图片 200 条
- 暴力内容数据集:共 100 条数据,其中暴力图片 50 条,非暴力图片 50 条
测试 Prompt
所有测试均使用以下统一的 prompt 进行评估:
你是一个专业的图片内容审核专家,负责识别图片中的暴力和色情内容。请仔细分析图片内容并做出精确判断。
请遵循以下分类标准:
- Violent: 图片必须包含以下一项或多项:
* 真实人物之间的打斗、殴打、攻击行为(注意:与表演、电影、体育竞技区分)
* 明显的流血、伤口或身体伤害
* 使用武器威胁或伤害他人
* 明显的肢体冲突和人身伤害场景
* 街头斗殴、帮派暴力或群体攻击行为
* 体育场上发生斗殴事件
- Non-violent: 不包含上述暴力元素的图片,即使是:
* 竞技性体育运动(包括拳击、摔跤、格斗比赛等)
* 表演或电影中的打斗场景
* 游戏或动画中的非真实暴力
* 人们进行日常活动(吃饭、行走、交谈、工作等)
【色情内容精确定义】
- Porn: 图片必须包含以下一项或多项:
* 明确的性行为或性交场景
* 裸露的生殖器官
* 裸露的成人胸部(女性)
* 明显的性行为暗示姿势或动作
* 性器官特写或强调
* 明确的性行为表演
- Non-porn: 不包含上述色情元素的图片,即使是:
* 穿着比基尼、泳装的人
* 部分暴露但不完全裸露的内容
* 健身、瑜伽或体育活动中的紧身服装
* 具有性感元素但不露骨的内容
* 艺术性质的裸体(如雕塑、古典艺术)
分析步骤:
1. 首先详细描述图片中看到的内容,包括人物、动作、环境和上下文
2. 分别判断暴力和色情两个维度
3. 给出最终分类结果,必须是以下四种之一:
- "Violent"(有暴力,无色情)
- "Non-violent"(无暴力,无色情)
- "Porn"(有色情,无论是否有暴力)
- "Non-porn"(无色情,可能包含性感元素但不露骨)
如有任何不确定性,请倾向于将内容标记为"Violent"或"Porn"。你的回答必须包含上述四个标签之一,后面跟着简短的解释理由。"""
注:本文使用的提示词并非是最佳实践,实际使用需要根据不同的场景对提示词进行优化以提升模型表现。
模型性能对比
色情内容检测
主要发现
总体准确率对比:
- Claude 3.7 Sonnet 模型总体准确率(97.22%)表现最佳
- Nova Pro(94.44%)和 Nova Lite(93.33%)也表现良好,但略低于 Claude 3.7 Sonnet
敏感内容检测能力:
- Claude 3.7 Sonnet 对色情图片的检测准确率非常高(95%),对暴力图片的检测准确率达到 33%
- Nova Pro 对色情图片的检测准确率为 90%,对暴力图片的检测准确率为 90%
- Nova Lite 对色情图片的检测准确率为 50%,对暴力图片的检测准确率为 88.33%
非敏感内容检测能力:
- 所有模型在非色情内容检测上表现优异,Claude 3.7 Sonnet 和 Nova Pro 均达到 100% 准确率
- 在非暴力图片检测上,所有测试模型均达到了 100% 的准确率
检测偏好:
- Claude 3.7 Sonnet 模型在检测上表现最为全面和平衡
- Nova 系列模型在敏感内容检测上略逊于 Claude 3.7 Sonnet,但仍保持较高准确率
二、视频内容检测对比
在视频内容检测方面,我们使用的测试视频数据如下:
- 色情视频数据集:共 100 条数据,其中色情图片 50 条,非色情图片 50 条
- 暴力视频数据集:共 100 条数据,其中暴力图片 50 条,非暴力图片 50 条
我们对 Nova Lite 和 Nova Pro 模型进行了对比测试,结果如下:
总体准确率
Nova Lite 混淆矩阵:
Nova Pro 混淆矩阵:
总体性能:
- Nova Pro 在图片和视频敏感内容检测上均表现优于 Nova Lite
- 两款模型在视频内容检测上的表现差距小于图片检测
- Nova Pro 在所有测试类别中均展现出更高的准确性
检测偏好与平衡性:
- Nova Pro 在各类内容检测上表现更为平衡,尤其在非敏感内容识别上达到 100% 准确率
- Nova Lite 在非暴力内容识别上表现出色,但在色情内容检测上准确率相对较低
误报与漏报:
- 从混淆矩阵来看,Nova Pro 的漏报率(8/99=8.1%)低于 Nova Lite(12/102=11.8%)
- Nova Pro 的误报率(1/101=1.0%)也低于 Nova Lite(3/98=3.1%)
- Nova Pro 在减少误报和漏报方面均优于 Nova Lite
Nova Pro 和 Nova Lite 是目前 Amazon Bedrock 平台上唯二支持视频格式作为输入的模型,两个模型在视频检测上表现更为平衡,没有明显的偏向性,两个模型在视频检测中的误报率和漏报率也相对接近。价格方面,Nova Lite 每一千次的调用价格仅为 Nova Pro 的 7.5%。
三、综合比较与适用场景
总体性能:
- Claude 3.7 Sonnet 模型在图片敏感内容检测上表现最佳,尤其是对色情和暴力内容的识别
- Nova Pro 在视频内容检测上略优于 Nova Lite
- 所有模型在非敏感内容识别上均表现出色
检测偏好与平衡性:
- Claude 3.7 Sonnet 模型在检测上最为平衡,能够较好地识别出各类敏感内容
- Nova 系列模型在图片检测中表现稳定,但准确率略低于 Claude 3.7 Sonnet
误报与漏报:
- 所有模型在非敏感内容识别上误报率极低
- Nova 系列模型在敏感内容识别上漏报率略高于 Claude 3.7 Sonnet
- 在需要高度保护的场景中,Claude 3.7 Sonnet 的高敏感度更优
价格对比:
- Nova Lite 虽然准确率略低于其他模型,但其价格极具竞争力,仅为 $0.08/千次调用。
- Nova Pro 的价格是 Nova Lite 的 4 倍,但总体准确率仅提高了 0.16 个百分点。
- Claude 3.7 Sonnet 的价格是 Nova Lite 的 60 倍,总体准确率提高了 67 个百分点。
四、实验步骤
实验步骤与 Amazon Bedrock 上的模型擂台赛:DeepSeek、Nova、Claude,谁是最强文本审核大模型?一致,您可参考 Sagemaker Notebook 中的代码为如下内容,并将数据存入 S3 或存入 Sagemaker Notebook 环境中后,即可进行测试。
import boto3
import pandas as pd
import json
import time
from tqdm import tqdm
import concurrent.futures
import os
from botocore.exceptions import ClientError
import numpy as np
import base64
from urllib.parse import urlparse
import io
from PIL import Image
# 定义CSV文件路径
csv_file = 'combined_dataset_fixed.csv'
# 读取包含图片路径的CSV文件
df = pd.read_csv(csv_file)
# 初始化Bedrock客户端
bedrock_runtime = boto3.client(
service_name='bedrock-runtime',
region_name='us-east-1' # 确保Claude模型在此区域可用
)
# 初始化S3客户端
s3_client = boto3.client('s3', region_name='us-west-1')
# 确保Bedrock有权限访问S3桶
def check_s3_permissions():
try:
# 检查桶是否存在
s3_client.head_bucket(Bucket="video-moderation-dataset")
print("成功连接到S3桶 'video-moderation-dataset'")
return True
except Exception as e:
print(f"无法访问S3桶: {str(e)}")
# 如果只处理本地文件,可以继续
return True
# 模型ID
# model_id = 'us.anthropic.claude-3-7-sonnet-20250219-v1:0'
model_id = 'us.amazon.nova-pro-v1:0'
# model_id='us.amazon.nova-lite-v1:0'
# 判断路径是S3路径还是本地路径
def is_s3_path(path):
return path.startswith('s3://')
# 从S3获取图片字节
def get_image_from_s3(s3_uri):
try:
parsed_url = urlparse(s3_uri)
bucket_name = parsed_url.netloc
object_key = parsed_url.path.lstrip('/')
response = s3_client.get_object(Bucket=bucket_name, Key=object_key)
image_bytes = response['Body'].read()
return image_bytes
except Exception as e:
print(f"从S3获取图片失败: {str(e)}")
return None
# 从本地文件系统获取图片字节
def get_image_from_local(file_path):
try:
with open(file_path, 'rb') as f:
image_bytes = f.read()
return image_bytes
except Exception as e:
print(f"从本地文件系统获取图片失败: {str(e)}")
return None
# 获取图片字节,无论是从S3还是本地
def get_image_bytes(path):
if is_s3_path(path):
return get_image_from_s3(path)
else:
return get_image_from_local(path)
# 处理图片,确保格式正确
def process_image(image_bytes):
try:
# 使用PIL打开图片
image = Image.open(io.BytesIO(image_bytes))
# 转换为RGB模式(如果是RGBA或其他模式)
if image.mode != 'RGB':
image = image.convert('RGB')
# 调整大小,确保不超过API限制
max_size = 4096 # 根据API限制调整
if max(image.size) > max_size:
ratio = max_size / max(image.size)
new_size = (int(image.size[0] * ratio), int(image.size[1] * ratio))
image = image.resize(new_size, Image.LANCZOS)
# 转换为JPEG格式的字节
buffer = io.BytesIO()
image.save(buffer, format="JPEG", quality=85)
processed_bytes = buffer.getvalue()
return processed_bytes
except Exception as e:
print(f"处理图片时出错: {str(e)}")
return image_bytes # 如果处理失败,返回原始字节
# 分析单个图片
def analyze_image(image_path, label):
print(f"处理图片: {image_path}")
try:
# 获取图片字节
image_bytes = get_image_bytes(image_path)
if image_bytes is None:
return {
'image_path': image_path,
'label': label,
'nova_lite_result': 'error',
'nova_lite_response': '无法获取图片数据'
}
# 处理图片,确保格式正确
processed_image = process_image(image_bytes)
# 获取图片扩展名
image_ext = "jpeg" # 默认为jpeg,因为我们在process_image中转换为JPEG
# prompt = """你是一个专业的图片内容审核专家,负责识别图片中的暴力和色情内容。请仔细分析图片内容并做出精确判断。
# 请遵循以下分类标准:
# 【暴力内容定义】
# - Violent: 图片包含真实的肢体冲突、打斗、斗殴、攻击行为,特别是街头打架、武器威胁、流血场景、明显的身体伤害或任何形式的人身伤害。
# - Non-violent: 图片展示和平的人类活动,如体育运动(即使是竞技性的)、日常生活场景(吃饭、行走、交谈)、工作场景等,没有真实的暴力冲突。
# 【色情内容定义】
# - Porn: 图片包含露骨的性行为、生殖器官暴露、性交行为、明确的性意图活动、裸体(非艺术性质)、性挑逗姿势等明确色情内容。
# - Non-porn: 图片可能包含比基尼、泳装、部分暴露但不露骨的内容,虽可能具有性感元素,但不展示明确的性行为或完全裸露。
# 分析步骤:
# 1. 首先详细描述图片中看到的内容,包括人物、动作、环境和上下文
# 2. 分别判断暴力和色情两个维度
# 3. 给出最终分类结果,必须是以下四种之一:
# - "Violent"(有暴力,无色情)
# - "Non-violent"(无暴力,无色情)
# - "Porn"(有色情,无论是否有暴力)
# - "Non-porn"(无色情,可能包含性感元素但不露骨)
# 如有任何不确定性,请倾向于将内容标记为"Violent"或"Porn"。你的回答必须包含上述四个标签之一,后面跟着简短的解释理由。"""
prompt = """你是一个专业的图片内容审核专家,负责识别图片中的暴力和色情内容。请仔细分析图片内容并做出精确判断。
请遵循以下分类标准:
- Violent: 图片必须包含以下一项或多项:
* 真实人物之间的打斗、殴打、攻击行为(注意:与表演、电影、体育竞技区分)
* 明显的流血、伤口或身体伤害
* 使用武器威胁或伤害他人
* 明显的肢体冲突和人身伤害场景
* 街头斗殴、帮派暴力或群体攻击行为
* 体育场上发生斗殴事件
- Non-violent: 不包含上述暴力元素的图片,即使是:
* 竞技性体育运动(包括拳击、摔跤、格斗比赛等)
* 表演或电影中的打斗场景
* 游戏或动画中的非真实暴力
* 人们进行日常活动(吃饭、行走、交谈、工作等)
【色情内容精确定义】
- Porn: 图片必须包含以下一项或多项:
* 明确的性行为或性交场景
* 裸露的生殖器官
* 裸露的成人胸部(女性)
* 明显的性行为暗示姿势或动作
* 性器官特写或强调
* 明确的性行为表演
- Non-porn: 不包含上述色情元素的图片,即使是:
* 穿着比基尼、泳装的人
* 部分暴露但不完全裸露的内容
* 健身、瑜伽或体育活动中的紧身服装
* 具有性感元素但不露骨的内容
* 艺术性质的裸体(如雕塑、古典艺术)
分析步骤:
1. 首先详细描述图片中看到的内容,包括人物、动作、环境和上下文
2. 分别判断暴力和色情两个维度
3. 给出最终分类结果,必须是以下四种之一:
- "Violent"(有暴力,无色情)
- "Non-violent"(无暴力,无色情)
- "Porn"(有色情,无论是否有暴力)
- "Non-porn"(无色情,可能包含性感元素但不露骨)
如有任何不确定性,请倾向于将内容标记为"Violent"或"Porn"。你的回答必须包含上述四个标签之一,后面跟着简短的解释理由。"""
max_retries = 2
retry_count = 0
response = None # 初始化response变量
# 打印图片路径和大小信息进行调试
print(f"图片路径: {image_path}")
print(f"处理后图片大小: {len(processed_image)} 字节")
# 调用Bedrock Converse API,直接传入图片字节
while retry_count < max_retries:
try:
response = bedrock_runtime.converse(
modelId=model_id,
messages=[
{
'role': 'user',
'content': [
{
'text': prompt
},
{
'image': {
'format': image_ext,
'source': {
'bytes': processed_image
}
}
}
]
}
]
)
break
except Exception as e:
retry_count += 1
if retry_count >= max_retries:
print(f"调用模型失败,已达到最大重试次数 ({max_retries})。错误: {str(e)}")
raise e
else:
print(f"调用模型失败,正在进行第 {retry_count} 次重试。错误: {str(e)}")
sleep_time = 30
time.sleep(sleep_time)
# 检查response是否为None
if response is None:
return {
'image_path': image_path,
'label': label,
'nova_lite_result': 'error',
'nova_lite_response': '无法获取模型响应'
}
# 解析响应
try:
response_text = response['output']['message']['content'][0]['text']
except KeyError:
try:
response_text = response['messages'][0]['content'][0]['text']
except KeyError:
response_text = "无法从响应中提取文本"
# 根据标签类型提取判断结果
if label in ['violent', 'non-violent']:
# 提取判断结果(violent或non-violent)
if 'violent' in response_text.lower() and not 'non-violent' in response_text.lower():
result = 'violent'
elif 'non-violent' in response_text.lower():
result = 'non-violent'
else:
# 如果无法明确判断,则进一步分析响应
if any(word in response_text.lower() for word in ['fight', 'attack', 'violence', 'aggressive', 'harm', 'push', 'hit', 'slap', 'touch', 'contact', 'fall', 'strike', 'throw', 'conflict', 'swing', 'wave', 'stick', 'bat']):
result = 'violent'
else:
result = 'non-violent'
elif label in ['porn', 'non-porn']:
# 提取判断结果(porn或non-porn)
if 'porn' in response_text.lower() and not 'non-porn' in response_text.lower():
result = 'porn'
elif 'non-porn' in response_text.lower():
result = 'non-porn'
else:
# 如果无法明确判断,则进一步分析响应
if any(word in response_text.lower() for word in ['nude', 'naked', 'sexual', 'explicit', 'adult', 'inappropriate']):
result = 'porn'
else:
result = 'non-porn'
else:
# 默认情况,直接使用标签作为结果
result = label
# 添加一些延迟以避免API限制
time.sleep(0.5)
return {
'image_path': image_path,
'label': label,
'nova_lite_result': result,
'nova_lite_response': response_text
}
except Exception as e:
print(f"处理图片 {image_path} 时出错: {str(e)}")
return {
'image_path': image_path,
'label': label,
'nova_lite_result': 'error',
'nova_lite_response': str(e)
}
# 主函数
def main():
# 检查S3权限
if not check_s3_permissions():
print("请确保已正确配置S3桶权限")
return
# 创建结果列表
results = []
# 获取图片路径和标签
image_data = list(zip(df['image_path'], df['label']))
print(f"开始分析 {len(image_data)} 个图片 ...")
# 使用线程池并行处理图片(限制并发数以避免API限制)
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
futures = []
for image_path, label in image_data:
future = executor.submit(analyze_image, image_path, label)
futures.append(future)
# 使用tqdm显示进度
for future in tqdm(concurrent.futures.as_completed(futures), total=len(futures), desc="分析图片"):
result = future.result()
if result:
results.append(result)
# 创建结果DataFrame
results_df = pd.DataFrame(results)
successful_results = results_df[results_df['nova_lite_result'] != 'error']
# 保存结果到新的CSV文件
results_df.to_csv('image_analysis_results_nova.csv', index=False)
print(f"总共处理 {len(results_df)} 个图片,成功 {len(successful_results)} 个,失败 {len(results_df) - len(successful_results)} 个")
# 计算准确率
accuracy = (successful_results['label'] == successful_results['nova_lite_result']).mean()
print(f"Bedrock模型分析完成。准确率: {accuracy:.2%}")
# 打印混淆矩阵
print("\n混淆矩阵:")
confusion = pd.crosstab(
successful_results['label'],
successful_results['nova_lite_result'],
rownames=['实际'],
colnames=['预测']
)
print(confusion)
if __name__ == "__main__":
main()
清理资源
最后,请及时清理资源,避免造成不必要的费用。若您使用了 Sagemaker Notebook 运行实验及测试,并将数据存储在了 S3 存储桶中,可通过删除创建的 S3 存储桶以及中止 SageMaker Notebook 来完成资源的清理。
总结
您可以根据自身业务需求、对漏报/误报的容忍度以及预算情况,选择最适合的模型进行内容审核工作。在实际应用中,可能需要结合多种模型或技术,构建更加全面和有效的内容审核系统。希望通过以上分析,可以为您带来内容审核上的一些洞见。
*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。
本篇作者
本期最新实验《多模一站通 —— Amazon Bedrock 上的基础模型初体验》
✨ 精心设计,旨在引导您深入探索Amazon Bedrock的模型选择与调用、模型自动化评估以及安全围栏(Guardrail)等重要功能。无需管理基础设施,利用亚马逊技术与生态,快速集成与部署生成式AI模型能力。
⏩️[点击进入实验] 即刻开启 AI 开发之旅
构建无限, 探索启程!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。