ディープラーニングを用いたエロ画像の収集と自動化

#はじめに

　エロ画像は最高です。

　エロ画像さえあれば、誰でも自由にエッチなことができます。たとえ彼女がいないブサメンであっても、一人で性的に興奮することができます。そこに確かな満足を覚えて、幸せに浸ることができます。どんな嗜好も思いのままです。

　故に我々人類にとって、エロ画像を集めることは、フンコロガシが糞を転がすのと同じように、種としての習性と称しても過言ではない行いなのではないかと思います。

　しかしながら、我々は霊長類の長たる生き物です。かれこれ数万年に渡って同じように糞を転がし続けているフンコロガシと同じではいけません。より効率的に、より意欲的にエロ画像を収集してこその人類です。

　ただ、そうは言ってもエロ画像の収集は非常に大変です。様々なサイトを巡り、十分に吟味した上で、フィッティングのあった品々を、確たるスキームに合わせて収集、構造化してゆく必要があるのです。日によって必要な一枚が異なる人も多いことでしょう。

　ところで最近、ディープラーニングというものが流行っていますね。

　猫も杓子もディープラーニングです。

　難易度の高い案件に当たると、そこはディープラーニングで何とかならない？　みたいな相談が増えました。受注確度の低い案件に当たると、とりあえずディープラーニングでお客の気を引けない？　流行ってるんでしょ？　みたいな振りをされる営業さんも増えました。

　これは社外でエンジニアの方々とお会いしても同様でありまして、それならディープラーニングで楽勝だぜ？　とか、そこはディープラーニングで解決でしょう、みたいなお話をする機会が増えました。誰も彼もディープラーニングに首ったけでございます。

　ということで、そこまで流行っているというのであれば、挑戦せざるを得ません。

　エロ画像収集をディープラーニングでやってみました。


#実装

　実装にはchainerを用いました。

　安心安全の国産です。

　モデルは以下のような形となります。

```python
class NIN(chainer.Chain):

  def __init__(self, output_dim, gpu=-1):
    w = math.sqrt(2)
    super(NIN, self).__init__(
      mlpconv1=L.MLPConvolution2D(3, (16, 16, 16), 11, stride=4, wscale=w),
      mlpconv2=L.MLPConvolution2D(16, (32, 32, 32), 5, pad=2, wscale=w),
      mlpconv3=L.MLPConvolution2D(32, (64, 64, 64), 3, pad=1, wscale=w),
      mlpconv4=L.MLPConvolution2D(64, (32, 32, output_dim), 3, pad=1, wscale=w),
    )
    self.output_dim = output_dim
    self.train = True
    self.gpu = gpu

  def __call__(self, x, t):
    h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
    h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
    h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
    h = self.mlpconv4(F.dropout(h, train=self.train))
    h = F.average_pooling_2d(h, 10)

    h = F.reshape(h, (x.data.shape[0], self.output_dim))

    loss = F.softmax_cross_entropy(h, t)
    print(loss.data)
    return loss
```

　ご覧の通りchainerに付属しているNINのサンプルを少し弄っただけとなります。しかも弄った理由はと言えば、開発用のVPSのメモリが少なかった為、これ以上複雑なモデルを学習させられなかったからです。こちらのモデルでも学習時には2GBほどを要します。

　学習用のデータに関しては、インターネット上で公開されているイラストを利用させて頂きました。合計2000枚の画像のうち、息子が反応を示す画像をレア画像として1000枚、それ意外の画像をコモン画像として1000枚、二種類間でのクラス分けとなります。

# 確認

　結果確認の為に利用した画像は、以下の三枚です。


![91vw8GTN37L.jpg](https://qiita-image-store.s3.amazonaws.com/0/61462/7ce0ac0f-c0dd-7f8a-5c19-50e81696340d.jpeg)

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/61462/cfd7c884-f3ba-583b-3b2a-f442137eccac.png)

![aaa.jpg](https://qiita-image-store.s3.amazonaws.com/0/61462/e10307de-f540-ccd0-5e07-ab14c4ead7c0.jpeg)


　最初の一枚は、自身の著作「田中　～年齢イコール彼女いない歴の魔法使い」の表紙です。二枚目は3Dカスタム少女から出張願った私の嫁です。そして、三枚目はそんな彼女と電車内で痴漢プレイを楽しんだ際の一枚です。

　こちらの三枚の画像に対して、上記のモデルを利用したスコアリングを行いました。

　結果、以下の通りスコアが得られました。

## 一枚目
```json
{
  "result": [
    [
      0.9290218353271484, 
      "1,rare"
    ], 
    [
      0.07097823172807693, 
      "0,common"
    ]
  ]
}
```

## 二枚目
```json
{
  "result": [
    [
      0.6085503101348877, 
      "0,common"
    ], 
    [
      0.3914496898651123, 
      "1,rare"
    ]
  ]
}
```

## 三枚目
```json
{
  "result": [
    [
      0.5935600399971008, 
      "1,rare"
    ], 
    [
      0.40644001960754395, 
      "0,common"
    ]
  ]
}
```


　chainerに付属するimagenetのサンプルと同様、対応するラベルに対する値が高いほど、そちらに近しいという判断になります。つまり、rareの値が高ければ高いほど、息子が喜ぶといった寸法です。

　ここまでやったところで、多少の手応えを感じました。

　そこでより現実的にエロ画像収集を行うため、上記のモデルへのアクセスをAPI化し、これに対してtwitterのリアルタイムクローリングから取得した画像を読み込ませ、結果をデータベースに格納、ランキング化するウェブアプリを作成しました。

　構成は以下の通りです。

```yaml
version: '2'

volumes:
  db-data:
    driver: local
  object-data:
    driver: local

services:
  db:
    container_name: db
    image: mysql
    volumes:
      - db-data:/var/lib/mysql
      # - ./dockerfiles/staging/mysql:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: $APP_DATABASE_PASSWORD
      MYSQL_DATABASE: app_staging
      MYSQL_USER: xxxx
      MYSQL_PASSWORD: $APP_DATABASE_PASSWORD
    expose:
      - "3306"
    restart: always

  app:
    build:
      context: ../../
      dockerfile: dockerfiles/staging/app/Dockerfile
    environment:
      RAILS_LOG_TO_STDOUT: 'true'
      RAILS_SERVE_STATIC_FILES: 'true'
      RAILS_ENV: 'staging'
      DISABLE_DATABASE_ENVIRONMENT_CHECK: $DISABLE_DATABASE_ENVIRONMENT_CHECK
      APP_SECRET_KEY_BASE: $APP_SECRET_KEY_BASE
      APP_DATABASE_USERNAME: xxxx
      APP_DATABASE_PASSWORD: $APP_DATABASE_PASSWORD
      APP_DATABASE_HOST: db
      TW_CONSUMER_KEY: $TW_CONSUMER_KEY
      TW_CONSUMER_SECRET: $TW_CONSUMER_SECRET
      TW_ACCESS_TOKEN: $TW_ACCESS_TOKEN
      TW_ACCESS_TOKEN_SECRET: $TW_ACCESS_TOKEN_SECRET

  minio:
    build:
      context: ../../
      dockerfile: dockerfiles/staging/minio/Dockerfile
    volumes:
      - object-data:/var/lib/minio
    environment:
      MINIO_ACCESS_KEY: 'xxx'
      MINIO_SECRET_KEY: 'xxx'
    ports:
      - '0.0.0.0:9000:9000'
    command: [server, /export]

  calc:
    container_name: calc
    build:
      context: ../../
      dockerfile: dockerfiles/staging/calc/Dockerfile
    command: python web.py
    expose:
      - "5000"

  web:
    container_name: web
    extends:
      service: app
    command: bin/wait_for_it db:3306 -- pumactl -F config/puma.rb start
    expose:
      - "3000"
    depends_on:
      - db

  crawler:
    container_name: crawler
    extends:
      service: app
    command: bin/wait_for_it db:3306 -- rails runner bin/crawler.rb
    depends_on:
      - calc
      - db

  nginx:
    container_name: nginx
    build:
      context: ../../
      dockerfile:  dockerfiles/staging/nginx/Dockerfile
    ports:
      - '80:80'
      - '443:443'
    depends_on:
      - web
```

　こちらを用いてしばらく、twitter上でおかずの収集を試みました。

　結果は以下の通りです。


![ranking.png](https://qiita-image-store.s3.amazonaws.com/0/61462/f2795678-d6af-326e-1f80-396b5b4980be.png)


　5,60枚ほど集まった時点でのランキングから、幾枚か抜き出したものとなります。

　画像の右側に記載されている値が、rareの値となります。

　取り立てて何をすることもなく、甲乙に分けたイラストをモデルに与えただけで、ここまでの成果が出たことに驚きです。もう少し厳密にラベリングを行えば、より息子好みの画像を集めることが可能なのではないかと、強く感じました。また、複数のモデルを組み合わせて使用すれば、更に精度は向上しそうです。

※　記載の画像に対する各種値については、私の息子の好みを学習させたAIによる判断となります。その値は特定の趣味や主義を否定するものではございません。


#結論

　一人が一人、己の為に最適化されたエロ画像ソムリエを抱える日が、すぐ近くまで来ているのかもしれません。