このトピックでは、Discourse AI プラグインの NSFW (Not Safe For Work) 機能の設定について説明します。
必要なユーザーレベル: 管理者
NSFW モジュールは、Discourse インスタンスの投稿やチャットメッセージにおけるすべての新しい画像アップロードの NSFW スコアを自動的に分類できます。このシステムは、専門的または公開環境で不適切または露骨な可能性のあるコンテンツを特定および管理するのに役立ちます。
しきい値を超えるコンテンツの自動フラグ付けを有効にすることもできます。
設定
ai_nsfw_detection_enabled: モジュールを有効または無効にします。
ai_nsfw_inference_service_api_endpoint: モジュールの API が実行されている URL。CDCK ホスティングを使用している場合は、自動的に処理されます。セルフホスティングの場合は、セルフホスティング ガイドを確認してください。
ai_nsfw_inference_service_api_key: 上記で設定した API の API キー。CDCK ホスティングを使用している場合は、自動的に処理されます。セルフホスティングの場合は、セルフホスティング ガイドを確認してください。
ai_nsfw_models: 画像分類に使用するモデル。しきい値を持つ 2 つの異なるモデルを提供しています。
- opennsfw2:
0.0から1.0の間の単一スコアを返します。ai_nsfw_flag_threshold_general設定を通じて、アップロードが NSFW と見なされるためのしきい値を設定します。- nsfw_detector:
drawings、hentai、porn、sexyの 4 つのカテゴリのスコアを返します。それぞれのしきい値は、対応するai_nsfw_flag_threshold_${category}設定を通じて設定します。一般的なしきい値がこれらのいずれかよりも低い場合、コンテンツは NSFW と見なされます。ai_nsfw_flag_automatically: 設定されたしきい値を超える投稿/チャットメッセージを自動的にフラグ付けします。この設定が無効になっている場合、分類結果はデータベースにのみ保存されます。
追加リソース
ありがとうございます。
NSFWサービスで画像アップロード後にこの問題が発生しました。画像ストレージにはS3を使用しています。関連があればお知らせください。
INFO: 10.0.2.145:23412 - "POST /api/v1/classify HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/anyio/streams/memory.py", line 94, in receive
return self.receive_nowait()
File "/usr/local/lib/python3.9/dist-packages/anyio/streams/memory.py", line 89, in receive_nowait
raise WouldBlock
anyio.WouldBlock
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 77, in call_next
message = await recv_stream.receive()
File "/usr/local/lib/python3.9/dist-packages/anyio/streams/memory.py", line 114, in receive
raise EndOfStream
anyio.EndOfStream
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/uvicorn/protocols/http/h11_impl.py", line 404, in run_asgi
result = await app( # type: ignore[func-returns-value]
File "/usr/local/lib/python3.9/dist-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
return await self.app(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/applications.py", line 124, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/errors.py", line 184, in __call__
raise exc
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.9/dist-packages/starlette_exporter/middleware.py", line 289, in __call__
await self.app(scope, receive, wrapped_send)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/cors.py", line 84, in __call__
await self.app(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 106, in __call__
response = await self.dispatch_func(request, call_next)
File "./app.py", line 49, in dispatch
response = await call_next(request)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 80, in call_next
raise app_exc
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 69, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/exceptions.py", line 79, in __call__
raise exc
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "/usr/local/lib/python3.9/dist-packages/starlette/routing.py", line 706, in __call__
await route.handle(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/routing.py", line 276, in handle
await self.app(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/routing.py", line 66, in app
response = await func(request)
File "./app.py", line 79, in classify
results = models[parsed_params.model](tfile)
File "/usr/local/lib/python3.9/dist-packages/opennsfw2/_inference.py", line 31, in predict_image
pil_image = Image.open(image_path)
File "/usr/local/lib/python3.9/dist-packages/PIL/Image.py", line 3283, in open
raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/tmp/tmpbqq82655'
ERROR:uvicorn.error:Exception in ASGI application
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/anyio/streams/memory.py", line 94, in receive
return self.receive_nowait()
File "/usr/local/lib/python3.9/dist-packages/anyio/streams/memory.py", line 89, in receive_nowait
raise WouldBlock
anyio.WouldBlock
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 77, in call_next
message = await recv_stream.receive()
File "/usr/local/lib/python3.9/dist-packages/anyio/streams/memory.py", line 114, in receive
raise EndOfStream
anyio.EndOfStream
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/uvicorn/protocols/http/h11_impl.py", line 404, in run_asgi
result = await app( # type: ignore[func-returns-value]
File "/usr/local/lib/python3.9/dist-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
return await self.app(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/applications.py", line 124, in __call__
await self.middleware_stack(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/errors.py", line 184, in __call__
raise exc
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "/usr/local/lib/python3.9/dist-packages/starlette_exporter/middleware.py", line 289, in __call__
await self.app(scope, receive, wrapped_send)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/cors.py", line 84, in __call__
await self.app(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 106, in __call__
response = await self.dispatch_func(request, call_next)
File "./app.py", line 49, in dispatch
response = await call_next(request)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 80, in call_next
raise app_exc
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/base.py", line 69, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/exceptions.py", line 79, in __call__
raise exc
File "/usr/local/lib/python3.9/dist-packages/starlette/middleware/exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "/usr/local/lib/python3.9/dist-packages/starlette/routing.py", line 706, in __call__
await route.handle(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/routing.py", line 276, in handle
await self.app(scope, receive, send)
File "/usr/local/lib/python3.9/dist-packages/starlette/routing.py", line 66, in app
response = await func(request)
File "./app.py", line 79, in classify
results = models[parsed_params.model](tfile)
File "/usr/local/lib/python3.9/dist-packages/opennsfw2/_inference.py", line 31, in predict_image
pil_image = Image.open(image_path)
File "/usr/local/lib/python3.9/dist-packages/PIL/Image.py", line 3283, in open
raise UnidentifiedImageError(msg)
PIL.UnidentifiedImageError: cannot identify image file '/tmp/tmpbqq82655'
Sidekiqでは以下のようになっています。
何が間違っているのでしょうか?
S3に関連している可能性があります。画像をアップロードすると、エラーログに次のエラーが表示されます。
URLはS3にリダイレクトされました…
そのため、プラグインが画像を早すぎるタイミングで読み込もうとしているか、他の理由で画像を見つけられないようです。
CDN for S3またはローカルストレージを使用するサイトでのみテストしました。オブジェクトストレージを使用する場合は、CDNを使用して前面に配置することをお勧めします。
それがまさに私たちがやっていることです - AWS Cloudfront。スタックは GitHub - aws-samples/aws-cdk-for-discourse: AWS CDK for Discourse に基づいています。デバッグして結果を投稿します。
APIキーはどこで入手できますか?
管理パネルの下にあるはずですよね?
これは私にはあまりにも敏感すぎるようです。あまりにも頻繁にフラグが立てられます。単に人の写真がNSFWとしてフラグが立てられます。
しきい値は60に設定しています。この数値を増やすべきか減らすべきか、また上限はいくつなのかは明確ではありません。100に設定した場合、それは基本的に無効にすることと同じですか?それに関するドキュメントはあまりないようです。
また、なぜ毒性(toxicity)のように、特定のロールに対して完全に無効にする方法がないのですか?私のフォーラムでは、スタッフメンバーが失礼なことを投稿しないことは確実ですが、AIが彼らの投稿にフラグを立てるため、常にAIと格闘しています。これは私のフォーラムではちょっとしたジョークになっています。
opennsfw2を使用しています。
残念ながら、それらの古いMLモデルは柔軟性に欠けており、独自のインスタンスを実行するための要件は緩いものの、Discourseを使用する多様なコミュニティにとって厳格すぎることが判明しました。
現在、それらの古いモデルに依存するすべての機能を非推奨にし、すべてを最新のLLMに移行する予定です。これにより、プロンプトを調整してニーズに合わせることができます。
これに関して何か変更はありましたか?NSFW検出器は依然としてひどく不正確であり、ほとんどの人物写真でトリガーされます。
はい、What’s next for NSFW detection in Discourse AI で計画を伝えましたが、要約すると、より高い精度と柔軟性を実現するために Discourse AI Post Classifier - Automation rule を使用できるということです。
NSFWユースケースについて Vision LLM を通じて評価を実施し、素晴らしい結果が得られたため、現在エンタープライズ顧客に展開しており、数週間以内にこのガイドを公開する予定です。

