KonifarPod

わいせつな写真をアップロードするユーザーに立ち向かう(テキスト解析編)

   

Pocket

わいせつな写真をアップロードするユーザーに立ち向かう(画像フィルタ編)の続きです。

わいせつ画像フィルタによって、わいせつ画像の50〜60%くらいは検出できるようになりました。

この検出率をさらにあげるために、今度はテキスト解析を入れてみることにしたわけです。テキスト解析と言ってもそんなに難しいことをしているわけではなく、要はわいせつテキストのリストを作ってマッチするか見ているだけなんですけどね。時系列で少し説明しておこうと思います。

 

(1)わいせつ画像をあげるユーザーの傾向

わいせつ画像をあげるユーザーを見続けていると、なんとなくある傾向があることに気づきました。

画像をアップロードする際にテキストも一緒に書けるのですが、どうもそのテキストもわいせつであることが多いんですよね。s◯xとかx◯xとか。たぶんアラビア語でチ◯コとか言ってんだろうなぁみたいな投稿もしばしば。ざっと見た感じ、テキストを解析するだけで検出率をあげられそうでした。

 

(2)テキスト解析APIを探す(2014/07/26時点)

テキスト解析ってしっかりやろうとすると結構大変なのです。例えば『seeee◯x』としたり、『s ◯ x』みたいに間にスペースをいれてきたりするので、色々想定してチェックロジックを作らなければいけません。本当にわいせつユーザーのわいせつに対する情熱は底が知れないわけです。

じゃあそういう面倒なところも全部やってくれるAPIを探してみようということで、ざっと見てみました。

 

WebPurify

月$9で安いなーと思ったんですが、実は同時アクセス数上限が2。それ以降は待ちになるので、1日に数千投稿あるような場合は使えません。Enterprise版のリンクを見てみると、月$500。。。精度はすごく良いし、ブラックリストもちゃんと更新されていくようなんですが、ちょっと高いですね。

 

②  Profanity Filter

画面で簡単にテストできるのがよかったです。無料じゃないんですが、料金の記載場所がよくわからずなんだか信頼しきれない感はありました。

 

③ Google’s WDYL

Googleのやってるサービスなので、信用できます。http://www.wdyl.com/profanity?q=badword みたいな感じでパラメータにテキストを渡すとレスポンスに true/falseが返ってくるという単純なものです。 無料だしとてもよかったのですが、英語しか対応してなかったです。

 

④ Rails Profanity Filter Gem

Railsのgemもありました。 当然無料です。ただ3年前からメンテナンスされていないみたいなので、使うのはちょっと抵抗ある感じでした。また、ブラックリストをyml等でカスタマイズすることも不可能なので、柔軟にリストを更新していくこともできなそうです。

 

⑤ Rails Obscenity Gem

これもgemです。1年前が最後の更新で、新しいRailsには対応できていないらしいです。

 

⑥ Rails LanguageFilter Gem

ブラックリストのカスタマイズも行えるgemです。ただ、UTF-8じゃないと使えない特殊文字を利用すると落ちるという致命的なバグがあり、英語以外だと使えないようでした。

 

色々探してみましたが、高かったり、ブラックリスト編集できなかったり、英語しか対応してなかったり、こっちを立てればあっちが立たずという感じですごくマッチするものがありませんでした。。。

 

(3)自作する

というか、ユーザーのわいせつテキストをよく見てみると、そもそもAPIでやってるような厳密な解析はいらない気がしてきました。みな素直に『s◯x』とか『x◯x』とか書いて投稿してるんですよね。素直な変態というか。これなら別に自分で作れるような気がしてきました。

 

① ブラックリストを作る

じゃあテキストのブラックリストを作ってチェックすればいいよね、ということで、早速とりかかりました。他言語対応したブラックリストならどっかに落ちてるだろうな、と思って探してみたらやっぱりありました。

https://github.com/shutterstock/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words

ひと通り言語も揃ってるしいい感じです。日本のブラックリストに『女◯高生』『アジアのかわいい女の子』というのを見つけた時は大丈夫かな・・・?と思いましたが、まぁ疑わしきはフィルタしてみようということでそのまま使うことにしました。

 

② フィルタロジックの実装

Android(クライアント)側で実装しました。assetsにxmlファイルを作ってロードするようにして、Utilityクラスにメソッド作って対応しました。

コードはこんな感じ。少し冗長ですが。。。

 

private static final String PREFIX_BANWORDS_RES = "bandwords_";
private static final String ENGLISH_ID = "en";
private static final String JAPANESE_ID = "ja";
private static final String CHINESE_ID = "zh";

private static HashMapbanwords = new HashMap();

public static boolean containsBanwords(Context context, String text, String language) {
    // 万が一何か起こった時のためにtry catchしておく
    try {
        loadBanwords(context, language);

        // 英語じゃないユーザーは、英語でわいせつなテキストをあげてくる場合も多いので英語は毎回チェック
        String[] languages = { language, ENGLISH_ID };

        for (String l : languages) {
            // 日本語と中国語は、文章をスペースで分割しない言語なので素のテキストで判定する
            boolean considerSpace = !l.equals(JAPANESE_ID) && !l.equals(CHINESE_ID); 
            if (containsBannedText(text, banwords.get(l), considerSpace)) {
                return true;
            }
        }
    } catch (Exception e) {
        Log.e("TextUtility", e.toString());
    }
    return false;
}

private static void loadBanwords(Context context, String language) {
    int arrayRes = context.getResources().getIdentifier(PREFIX_BANWORDS_RES + language, "array", context.getPackageName());
    // ロードしていなかったら読み込む
    if (!banwords.containsKey(language)) {
        banwords.put(language, context.getResources().getStringArray(arrayRes));
    }
}

private static boolean containsBannedText(String text, String[] banwords, boolean withSpace) {
    text = text.toLowerCase();
    if (!withSpace) {
        for (String banword : banwords) {
            if (text.contains(banword)) {
                return true;
            }
        }
        return false;
    }

    // 1単語ずつチェックする
    String words[] = text.split("\\s+");
    for (String word : words) {
        String subwords[] = word.split("\\W+");
        for (String s : subwords) {
            if (isWordValid(s, banwords)) {
                return true;
            }
        }
     }

     for (String s : banwords) {
         if (" ".contains(s) && text.contains(s.toLowerCase())) {
             return true;
         }
     }
     return false;
}

private static boolean isWordValid(String word, String[] banwords) {
    if (word.length() <= 0) {
        return false;
    }

    for (String s : banwords) {
        if (s.toLowerCase().equals(word)) {
            return true;
        }
    }
    return false;
}

 

③ テキスト以外の転用

さらにわいせつユーザーを分析してみると、わいせつ画像をアップロードするユーザーは名前も若干わいせつであることが多いということに気づきました。『s◯x』という名前のユーザーがわいせつ画像をあげまくったりするわけです。

じゃあユーザーの名前にもテキスト解析入れてあげればいいよね、ということで転用することにしました。

ただ、ユーザーの名前の場合は文節ごとに解析するとちょっと問題があります。実例をあげると、『s◯xMan』というクソみたいな名前のユーザーがいたんですが、彼を検出したい場合は『s◯Man』という文字列の中に『s◯x』という文字が入っているかどうかをチェックする必要があります。なのでロジックは少し変えましたが、基本の考え方は同じで、ブラックリストを作ってチェックするだけです。

 

(4) 結果検証

結果、50%〜60%くらいだったわいせつ検出率が、60%〜75%くらいまで上がりました。ユーザーの名前でも検出したのがかなり効果高かったです。わいせつユーザーは素直にわいせつな名前を使ってくるのである意味楽でした。登録時にチェックしてもよいのですが、そうすると変に捻ったわいせつな名前にしてくる可能性があるのでやめておいたほうがいいのかなと考えてます。

 

完璧な解析ではないですが検出率向上の結果を見てみると、簡単な実装の割に効果は高いです。わいせつユーザーにお困りの方は試してみるといいかもしれません。

Pocket

 - Develop ,