AIスロープの検出:技術と警鐘
AI生成コンテンツの検出に関する技術ガイド
AI生成コンテンツの増加により、新たな課題が生じています。それは、本物の人の書き方と「AIスロップ」(https://www.glukhov.org/ja/post/2025/12/ai-slop-detection/ “AIスロップ”)を区別することです。AIスロップとは、低品質で大量生産された合成テキストのことです。
コンテンツプラットフォームを管理している、研究を行っている、あるいは単に認証に興味がある場合でも、検出方法を理解することはますます重要になってきています。

AIスロップの理解:コンテンツが「スロッピー」になる理由
AIスロップは単なるAI生成コンテンツではなく、低品質、一般的、または誤解を招く合成テキストで、スケールで大量に生成されます。この用語は、大規模言語モデルが利用可能になるにつれて、自動生成された記事、コメント、レビューが量を重視して価値を重視しないものとして大量に出現したときに生まれました。
AIスロップの特徴
スロップと、思考を伴ったAI支援による書き方との違いを区別するためのいくつかの特徴があります。
- 過剰な曖昧さと限定語の使用:「注目に値する」「重要なのは覚えておくこと」「これは変化する可能性がある」などのフレーズが異常に頻繁に現れます。
- 一般的な構造:予測可能なフォーマット、番号付きリスト、サブヘッダー、結論のまとめなどの構造。
- 表面的な洞察:トピックに浅く触れており、深さや新しい視点がありません。
- 具体的な例の欠如:具体的なケース、データ、または個人的なエピソードではなく、曖昧な参照を使用しています。
- 不自然な一貫性:完璧な文法とフォーマットで、不自然に均一なトーンが続きます。
これらの特徴を理解することで、手動のレビューおよび自動検出アプローチの両方を支援できます。課題は、AI支援コンテンツで人間の専門知識が生成プロセスをガイドしている場合、スロップと正当なAI支援コンテンツを区別することです。
検出方法:単純なヒューリスティックからMLモデルまで
統計分析アプローチ
AI検出の基礎は、人間と機械生成テキストの間に異なる統計的特性に基づいています。これらの方法は、特定モデルに関するトレーニングデータを必要とせずにテキストの特性を分析します。
不確実性と爆発性の指標は、言語モデルが次の単語にどの程度「驚き」しているかを測定します。人間の書き方では、通常、不確実性(予測可能性の低さ)と爆発性(文の複雑さの変化)が高くなります。DetectGPTなどのツールは、テキストが特定モデルの高確率領域に位置しているかを確認することでこれを活用します。
from transformers import GPT2LMHeadModel, GPT2Tokenizer
import torch
import numpy as np
def calculate_perplexity(text, model_name='gpt2'):
"""参照モデルを使用してテキストの不確実性を計算"""
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
model = GPT2LMHeadModel.from_pretrained(model_name)
encodings = tokenizer(text, return_tensors='pt')
max_length = model.config.n_positions
stride = 512
nlls = []
for i in range(0, encodings.input_ids.size(1), stride):
begin_loc = max(i + stride - max_length, 0)
end_loc = min(i + stride, encodings.input_ids.size(1))
trg_len = end_loc - i
input_ids = encodings.input_ids[:, begin_loc:end_loc]
target_ids = input_ids.clone()
target_ids[:, :-trg_len] = -100
with torch.no_grad():
outputs = model(input_ids, labels=target_ids)
neg_log_likelihood = outputs.loss * trg_len
nlls.append(neg_log_likelihood)
ppl = torch.exp(torch.stack(nlls).sum() / end_loc)
return ppl.item()
# 使い方
text_sample = "解析するテキストをここに入力してください..."
perplexity_score = calculate_perplexity(text_sample)
print(f"不確実性: {perplexity_score:.2f}")
# 不確実性が低い(< 50)はAI生成を示す
# 不確実性が高い(> 100)は人間の書き方を示す
AI生成コンテンツの最も信頼性の高い指標は何ですか?不確実性に加えて、n-gram頻度分析はパターンを明らかにします。AIモデルは特定の単語の組み合わせを過剰に使用する傾向があり、人間はより多様な語彙を使用します。頻度分布を計算し、既知の人間の語料と比較することで、合成起源を暴くことができます。
マシンラーニング分類器
教師あり学習アプローチでは、ラベル付きデータセットを使用して、AIと人間のテキストを区別するモデルをトレーニングします。これらの分類器は統計的方法よりも高い精度を達成することがありますが、大量のトレーニングデータが必要です。
Transformerベースの検出器であるRoBERTaを人間とAI語料に調整することで、制御された環境では90%以上の精度を達成できます。アーキテクチャは、単純な方法では見逃す文脈関係を処理します。
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
def classify_text(text, model_name='roberta-base-openai-detector'):
"""
AI生成または人間のテキストを分類する調整されたTransformerを使用します。
注意:実際にはHuggingFaceから検出モデルを使用してください。
"""
tokenizer = AutoTokenizer.from_pretrained('roberta-base')
# 実際には検出に特化したモデルを使用してください
model = AutoModelForSequenceClassification.from_pretrained('roberta-base', num_labels=2)
inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=512)
with torch.no_grad():
outputs = model(**inputs)
predictions = torch.softmax(outputs.logits, dim=1)
ai_probability = predictions[0][1].item()
human_probability = predictions[0][0].item()
return {
'ai_probability': ai_probability,
'human_probability': human_probability,
'predicted_class': 'AI' if ai_probability > 0.5 else 'Human',
'confidence': max(ai_probability, human_probability)
}
# 例
text = "先進アルゴリズムの実装..."
result = classify_text(text)
print(f"予測: {result['predicted_class']} (信頼度: {result['confidence']:.2%})")
現在のツールでAI生成テキストを信頼性高く検出できますか?答えは複雑です。検出器はトレーニングしたモデルの未変更出力に対してはうまく機能しますが、以下のようなケースでは苦労します:
- 新しいまたは未知のモデルからのテキスト
- 人間によって事後編集されたコンテンツ
- 短いテキストスニペット(100語未満)
- 要害回避技術
水印技術
AIテキストの水印技術はどのように技術的に機能しますか?水印は生成中に検出可能な署名を埋め込むことで、事後分析より信頼性の高い検出を提供します。
暗号化水印は、生成中の各トークンの選択をバイアスさせる方法で機能します。各トークンを生成する前に、アルゴリズムは以前のトークンの暗号ハッシュと秘密鍵を組み合わせて、語彙を「グリーン」と「レッド」リストに分ける。モデルはグリーントークンをわずかに優先します。
数学的なアプローチはスコア関数を使用します:
$$ S(w_1, \ldots, w_n) = \sum_{i=1}^{n} \mathbb{1}[\text{green}(w_i | w_1, \ldots, w_{i-1})] $$
ここで、水印付きテキストは偶然よりも高いスコアを生成します。検出テストはスコアがしきい値を超えるかを確認します:
$$ z = \frac{S - \mu}{\sigma} > \tau $$
$$ \mu = n/2 \quad (\text{期待スコア}), \quad \sigma = \sqrt{n}/2 \quad (\text{標準偏差}), \quad \tau \text{は検出しきい値(通常2-4)} $$
import hashlib
import numpy as np
class SimpleWatermarkDetector:
"""
語彙分割に基づく単純な水印検出器。
実際のシステムではより複雑なアプローチを使用します。
"""
def __init__(self, key: str, vocab_size: int = 50000, green_fraction: float = 0.5):
self.key = key
self.vocab_size = vocab_size
self.green_fraction = green_fraction
def _get_green_list(self, prefix: str) -> set:
"""前処理と秘密鍵に基づいてグリーンリストを生成"""
hash_input = f"{self.key}:{prefix}".encode()
hash_output = hashlib.sha256(hash_input).digest()
# ハッシュを使用してRNGをシードしてランダムなグリーンリストを生成
rng = np.random.RandomState(int.from_bytes(hash_output[:4], 'big'))
green_size = int(self.vocab_size * self.green_fraction)
green_tokens = set(rng.choice(self.vocab_size, green_size, replace=False))
return green_tokens
def score_text(self, tokens: list) -> dict:
"""トークン列の水印スコアを計算"""
green_count = 0
for i, token in enumerate(tokens):
prefix = "".join(map(str, tokens[:i])) if i > 0 else ""
green_list = self._get_green_list(prefix)
if token in green_list:
green_count += 1
n = len(tokens)
expected_green = n * self.green_fraction
std_dev = np.sqrt(n * self.green_fraction * (1 - self.green_fraction))
z_score = (green_count - expected_green) / std_dev if std_dev > 0 else 0
return {
'green_count': green_count,
'total_tokens': n,
'z_score': z_score,
'is_watermarked': z_score > 2.0, # 検出のしきい値
'p_value': 1 - 0.5 * (1 + np.tanh(z_score / np.sqrt(2)))
}
# 例
detector = SimpleWatermarkDetector(key="secret_key_123")
token_sequence = [1234, 5678, 9012, 3456, 7890] # 例のトークンID
result = detector.score_text(token_sequence)
print(f"水印あり: {result['is_watermarked']} (zスコア: {result['z_score']:.2f})")
水印は検出保証を提供しますが、コンテンツ生成者との協力が必要です。オープンソースモデルや水印なしのAPI使用はこの方法では検出不能です。
言語パターン認識
統計的測定を超えて、特定の言語パターンがAI生成を信頼性高く示します。これらのパターンはモデルのトレーニングとアーキテクチャから生じ、意図的な設計ではありません。
AI生成の一般的な特徴
AI生成コンテンツの検出に使えるオープンソースツールはありますか?ツールに飛び込む前に、パターンを理解することで手動レビューを助けます:
繰り返しの文構造:AIモデルはしばしばリズムパターンに陥り、複数の文を似た構造で開始します。人間のライターは自然に文法をより大きく変化させます。
曖昧語の過剰使用:「注目に値する」「重要なのは覚えておくこと」「ある程度」「言うまでもない」「多くの点で」などのフレーズはAIテキストでは異常に頻繁に現れます。
深くない文脈:AIは訓練データのスナップショットを超えた本物の文化的参照、個人的なエピソード、または複雑な歴史的文脈を扱うのが困難です。
疑わしいバランスの取れた視点:安全にトレーニングされたモデルは人工的にバランスの取れた視点を提示し、適切である場合でも強い立場を避けます。
パターン検出の実装
import re
from collections import Counter
def analyze_ai_patterns(text: str) -> dict:
"""AI生成テキストに共通する言語パターンを検出"""
# 一般的なAI曖昧語フレーズ
hedge_phrases = [
r'\bit[\'']s worth noting',
r'\bit[\'']s important to',
r'\barguably\b',
r'\bto some extent\b',
r'\bin many ways\b',
r'\bit depends\b',
r'\bvarious factors\b',
r'\bwhile .*? may vary\b',
]
# 文の開始を分析
sentences = re.split(r'[.!?]+', text)
sentence_starts = [s.strip().split()[:3] for s in sentences if s.strip()]
start_patterns = Counter([' '.join(start[:2]) for start in sentence_starts if len(start) >= 2])
# 曖昧語のカウント
hedge_count = sum(len(re.findall(pattern, text, re.IGNORECASE)) for pattern in hedge_phrases)
# リストフォーマットのチェック
has_numbered_lists = bool(re.search(r'\n\d+\.', text))
has_bullet_points = bool(re.search(r'\n[\-\*]', text))
# メトリクスの計算
word_count = len(text.split())
hedge_density = hedge_count / (word_count / 100) if word_count > 0 else 0
# 繰り返しの開始の検出
max_repeat_ratio = max(count / len(sentence_starts)
for count in start_patterns.values()) if sentence_starts else 0
return {
'hedge_density': hedge_density, # 100語あたりの曖昧語数
'max_repeat_ratio': max_repeat_ratio, # 最も一般的な文の開始比率
'has_list_formatting': has_numbered_lists or has_bullet_points,
'sentence_count': len([s for s in sentences if s.strip()]),
'suspicion_score': (hedge_density * 0.4 + max_repeat_ratio * 60),
'top_repeated_starts': start_patterns.most_common(3)
}
# 例
text_sample = """
AI検出は複雑です。複数の方法が最適です。おそらく、単一のアプローチは完璧ではありません。
"""
analysis = analyze_ai_patterns(text_sample)
print(f"疑いスコア: {analysis['suspicion_score']:.1f}")
print(f"曖昧語密度: {analysis['hedge_density']:.2f} 100語あたり")
実用的な実装戦略
AI検出をコンテンツパイプラインに統合するにはどうすればよいですか?実装はあなたのスケール、精度要件、リソースに依存します。
APIベースの検出サービス
商用サービスは最も速い統合パスを提供します:
import requests
import os
class ContentDetectionPipeline:
"""信頼性の高いチェックのために複数の検出サービスを統合"""
def __init__(self, api_keys: dict):
self.api_keys = api_keys
self.results_cache = {}
def check_gptzero(self, text: str) -> dict:
"""GPTZero APIを使用してチェック"""
url = "https://api.gptzero.me/v2/predict/text"
headers = {
"Authorization": f"Bearer {self.api_keys.get('gptzero')}",
"Content-Type": "application/json"
}
data = {"document": text}
try:
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()
result = response.json()
return {
'service': 'gptzero',
'ai_probability': result.get('documents', [{}])[0].get('completely_generated_prob', 0),
'confidence': result.get('documents', [{}])[0].get('average_generated_prob', 0)
}
except Exception as e:
return {'service': 'gptzero', 'error': str(e)}
def check_originality(self, text: str) -> dict:
"""Originality.ai APIを使用してチェック"""
url = "https://api.originality.ai/api/v1/scan/ai"
headers = {"X-OAI-API-KEY": self.api_keys.get('originality')}
data = {"content": text}
try:
response = requests.post(url, data=data, headers=headers)
response.raise_for_status()
result = response.json()
return {
'service': 'originality',
'ai_probability': result.get('score', {}).get('ai', 0),
'human_probability': result.get('score', {}).get('original', 0)
}
except Exception as e:
return {'service': 'originality', 'error': str(e)}
def aggregate_results(self, results: list) -> dict:
"""複数の検出結果を統合"""
valid_results = [r for r in results if 'error' not in r]
if not valid_results:
return {'error': 'すべてのサービスが失敗しました', 'verdict': 'UNKNOWN'}
avg_ai_prob = sum(r.get('ai_probability', 0) for r in valid_results) / len(valid_results)
return {
'ai_probability': avg_ai_prob,
'verdict': 'AI' if avg_ai_prob > 0.7 else 'HUMAN' if avg_ai_prob < 0.3 else 'UNCERTAIN',
'confidence': abs(avg_ai_prob - 0.5) * 2, # 不確実から離れた距離
'individual_results': results
}
def analyze(self, text: str) -> dict:
"""フル検出パイプラインを実行"""
results = []
# 有効なサービスでチェック
if self.api_keys.get('gptzero'):
results.append(self.check_gptzero(text))
if self.api_keys.get('originality'):
results.append(self.check_originality(text))
return self.aggregate_results(results)
# 使用例
pipeline = ContentDetectionPipeline({
'gptzero': os.getenv('GPTZERO_API_KEY'),
'originality': os.getenv('ORIGINALITY_API_KEY')
})
content = "チェックするコンテンツをここに入力してください..."
result = pipeline.analyze(content)
print(f"判断: {result['verdict']} (信頼度: {result['confidence']:.2%})")
自己ホストされた検出スタック
プライバシーに敏感なアプリケーションや高ボリューム処理には、自己ホストされたソリューションがより多くのコントロールを提供します:
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import numpy as np
class SelfHostedDetector:
"""複数のアプローチを組み合わせた自己ホスト検出"""
def __init__(self, model_path: str = 'roberta-base'):
self.tokenizer = AutoTokenizer.from_pretrained(model_path)
self.model = AutoModelForSequenceClassification.from_pretrained(
model_path,
num_labels=2
)
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.model.to(self.device)
self.model.eval()
def detect_with_classifier(self, text: str) -> dict:
"""Transformer分類器を使用"""
inputs = self.tokenizer(
text,
return_tensors='pt',
truncation=True,
max_length=512,
padding=True
).to(self.device)
with torch.no_grad():
outputs = self.model(**inputs)
probs = torch.softmax(outputs.logits, dim=1)
return {
'ai_score': probs[0][1].item(),
'human_score': probs[0][0].item()
}
def detect_with_perplexity(self, text: str) -> dict:
"""不確実性に基づく検出"""
from transformers import GPT2LMHeadModel, GPT2Tokenizer
gpt2_tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
gpt2_model = GPT2LMHeadModel.from_pretrained('gpt2').to(self.device)
encodings = gpt2_tokenizer(text, return_tensors='pt').to(self.device)
with torch.no_grad():
outputs = gpt2_model(**encodings, labels=encodings['input_ids'])
perplexity = torch.exp(outputs.loss).item()
# 不確実性が低いほどAI生成を示す
ai_score = 1 / (1 + np.exp((perplexity - 50) / 20)) # シグモイド正規化
return {
'perplexity': perplexity,
'ai_score': ai_score
}
def detect_with_patterns(self, text: str) -> dict:
"""言語パターン検出を使用"""
analysis = analyze_ai_patterns(text) # 以前のセクションから
# 疑いスコアを0-1範囲に正規化
ai_score = min(analysis['suspicion_score'] / 100, 1.0)
return {
'pattern_ai_score': ai_score,
'details': analysis
}
def detect(self, text: str, methods: list = None) -> dict:
"""指定されたまたはすべてのメソッドで検出を実行"""
if methods is None:
methods = ['classifier', 'perplexity', 'patterns']
results = {}
if 'classifier' in methods:
results['classifier'] = self.detect_with_classifier(text)
if 'perplexity' in methods:
results['perplexity'] = self.detect_with_perplexity(text)
if 'patterns' in methods:
results['patterns'] = self.detect_with_patterns(text)
# スコアを統合
ai_scores = []
if 'classifier' in results:
ai_scores.append(results['classifier']['ai_score'])
if 'perplexity' in results:
ai_scores.append(results['perplexity']['ai_score'])
if 'patterns' in results:
ai_scores.append(results['patterns']['pattern_ai_score'])
final_score = np.mean(ai_scores) if ai_scores else 0
return {
'final_ai_score': final_score,
'verdict': 'AI' if final_score > 0.7 else 'HUMAN' if final_score < 0.3 else 'UNCERTAIN',
'confidence': abs(final_score - 0.5) * 2,
'method_results': results
}
# 使い方
detector = SelfHostedDetector()
text = "解析するコンテンツをここに入力してください..."
result = detector.detect(text)
print(f"判断: {result['verdict']} ({result['final_ai_score']:.2%} AI確率)")
制限と回避技術
AIスロップとは何ですか?そしてなぜそれを検出することが重要ですか?検出方法を知るだけでなく、制限を理解することも重要です。生成と検出の間の猫とマウスゲームは継続的に進化しています。
知られている回避技術
パラフレーズ攻撃:AIテキストをパラフレーザーまたは翻訳ループに通すことで、検出器を回避できます。意味は残りますが、統計的な署名は変化します。
ハイブリッドアプローチ:AI生成の下書きと人間の編集を混ぜることで、ほとんどの検出器にとって曖昧なコンテンツを作成します。
プロンプトエンジニアリング:モデルに「人間のように書くように」と指示したり、特定のスタイルを模倣させたりすることで、検出精度を20-40%低下させることができます。
モデルの多様性:GPT-4の出力でトレーニングされた検出器は、Claude、Llama、または新しいモデルで正確性を保証できません。各モデルには独自の統計的指紋があります。
頑健な検出の構築
多層的な防御がより良い結果をもたらします:
- アンサンブル方法:統計的、分類器、パターンベースのアプローチを組み合わせる
- 人間によるループ:不確実なケースを手動レビューに送る
- 文脈の考慮:非常に短いまたは非常に長いテキストは検出器に異なる影響を与える
- 定期的な更新:新しいモデルが登場するたびに分類器を再トレーニングする
- メタデータ分析:テキストだけでなく、投稿パターン、アカウント履歴、行動信号も考慮する
class RobustDetectionSystem:
"""生産性の高い検出システムと人間レビュー待ちリスト"""
def __init__(self, confidence_threshold: float = 0.8):
self.detector = SelfHostedDetector()
self.confidence_threshold = confidence_threshold
self.review_queue = []
def classify_with_context(self, text: str, metadata: dict = None) -> dict:
"""テキストと文脈信号を考慮して分類"""
# 主要な検出
detection_result = self.detector.detect(text)
# 文脈スコーリング
context_signals = self._analyze_context(metadata or {})
# テキストと文脈を統合
combined_score = (
detection_result['final_ai_score'] * 0.7 +
context_signals['suspicion_score'] * 0.3
)
confidence = detection_result['confidence']
# 信頼度に基づいてルーティング
if confidence < self.confidence_threshold:
self._add_to_review_queue(text, detection_result, metadata)
verdict = 'NEEDS_REVIEW'
else:
verdict = 'AI' if combined_score > 0.7 else 'HUMAN'
return {
'verdict': verdict,
'ai_score': combined_score,
'confidence': confidence,
'needs_review': confidence < self.confidence_threshold,
'detection_details': detection_result,
'context_signals': context_signals
}
def _analyze_context(self, metadata: dict) -> dict:
"""非テキスト信号を分析"""
suspicion_factors = []
# 投稿速度
if metadata.get('posts_last_hour', 0) > 10:
suspicion_factors.append(0.3)
# アカウント年齢とコンテンツ量
if metadata.get('account_age_days', 365) < 7 and metadata.get('total_posts', 0) > 50:
suspicion_factors.append(0.4)
# 応答時間(非常に速い応答は疑わしい)
if metadata.get('response_time_seconds', 60) < 10:
suspicion_factors.append(0.2)
suspicion_score = min(sum(suspicion_factors), 1.0) if suspicion_factors else 0
return {
'suspicion_score': suspicion_score,
'factors': suspicion_factors
}
def _add_to_review_queue(self, text: str, result: dict, metadata: dict):
"""境界線ケースを人間レビューに追加"""
self.review_queue.append({
'text': text[:500], # プレビューのみ
'detection_result': result,
'metadata': metadata,
'timestamp': __import__('datetime').datetime.now().isoformat()
})
def get_review_queue(self, limit: int = 10) -> list:
"""人間レビューが必要なアイテムを取得"""
return self.review_queue[:limit]
# 使い方
system = RobustDetectionSystem(confidence_threshold=0.75)
result = system.classify_with_context(
text="チェックするコンテンツ...",
metadata={
'account_age_days': 5,
'posts_last_hour': 15,
'response_time_seconds': 8
}
)
print(f"判断: {result['verdict']}")
if result['needs_review']:
print("人間レビューが必要です")
今後の方向性と研究
検出の風景は急速に進化しています。いくつかの有望な研究方向が潜在的な可能性を示しています:
クロスモーダル検出:テキストと関連する画像/動画を分析して、合成起源を検出します。AI生成コンテンツは、合成テキストとスロット画像またはAI生成の視覚を組み合わせることがあります。
プロヴァンス追跡:ブロックチェーンベースのコンテンツの真実性証明証明書で、人間の執筆またはAI支援レベルを暗号的に証明します。
モデル指紋:コンテンツがAI生成であるだけでなく、どの特定のモデルが作成したかを特定する技術で、ターゲット検出戦略を可能にします。
行動分析:単一テキスト分類から、複数投稿の投稿パターン、相互作用スタイル、および時間的行動を分析する方法へと移行します。
結論
AIスロップの検出には、統計分析、機械学習分類器、水印、および言語パターン認識の複数のアプローチを組み合わせる必要があります。単一の方法では完全な精度を提供することはできませんが、境界ケースの人間レビューを含むアンサンブルアプローチは実用的な解決策を提供します。
モデルが改善し、回避技術が進化するにつれて、検出は適応する必要があります。最も持続可能なアプローチは、技術的な検出と、AIの不正使用を奨励するプラットフォームポリシーのバランスを取ることです。
コンテンツモデレーションシステムを構築している、学術研究を行っている、または単にオンライン情報の評価を行っている場合、これらの技術を理解することで、ますますAI補完された情報風景をナビゲートするのに役立ちます。
有用なリンク
- GPTZero - フリータイア付きの商用AI検出サービス
- DetectGPT論文 - パーティュレーションを使用したゼロショット検出
- 水印研究 - 暗号化水印アプローチ
- GLTRツール - トークン確率を視覚的に表示する検出ツール
- OpenAI検出研究 - 公式の検出立場
- HuggingFace Transformers - カスタム検出器の構築用ライブラリ
- Originality.ai - APIアクセス付きの商用検出
- AIテキスト分類器の問題 - 分類器の制限の分析