【男の浪漫】身長、体重およびスリーサイズを素性とした「アレ」のサイズを推定する実験

[機械学習] : 【男の浪漫】身長、体重およびスリーサイズを素性とした「アレ」のサイズを推定する実験

目次

  1. 実験の要点
  2. データセットの作成
  3. 実験
    • 5-Fold Cross Validationによる精度評価
    • 予測
  4. 結論
  5. Github

実験の要点

アダルトフィルタ実装に向けたA○女優リストの自動抽出 + α - Yuta.Kikuchiの日記 はてなブックマーク - アダルトフィルタ実装に向けたA○女優リストの自動抽出 + α - Yuta.Kikuchiの日記
Multi-Class Classifier of Bra Size used as the feature value with vital statics - Yuta.Kikuchiの日記 はてなブックマーク - Multi-Class Classifier of Bra Size used as the feature value with vital statics - Yuta.Kikuchiの日記
※本エントリーは2013年7月22日に公開したMulti-Class Classifier of Bra Size used as the feature value with vital staticsの続編になります。


好きな女性芸能人のスリーサイズは公開されているが、「アレのサイズ」(ブラサイズ)は非公開ということがよくあります。直接的なアレのサイズは公表しない風習が漂っていますよね。そんなもどかしい状態の中、世の中の男性は脳内で必ずと言って良い程アレのサイズを想像(補完)していると思います。アレのサイズを推定する先行研究?として身長を基にした「ゴールデンカノン」による理想サイズ算出という方法があるようです。あくまで理想サイズなんですが、男性陣が知りたいのは現実サイズだと思います。美少女、バストカップ数測定スクリプト 070731 はてなブックマーク - 美少女、バストカップ数測定スクリプト 070731


Vital Statics - Wikipedia はてなブックマーク - スリーサイズ - Wikipedia
一般的な話ですがスリーサイズからアレのサイズを導きだすのは難しいとされています。( BraSize = TopBust - UnderBust。UnderBust ≠ Waist ) 個人のスリーサイズからの算出は難しいのかもしれませんが、集団データから傾向の推定はできるのでは無いかというのが今回の実験のテーマです。現実のサイズを知るために身長、体重、スリーサイズを素性(特徴)とした推定実験を行います。データに利用したのはWikipediaに記載されているA○女優の方々のデータです。※当然アレのサイズのエリート属性の方々ですので、今回の実験で作成するModelが一般ぴーぽーに当てはめる事はできないと思います。アダルトフィルタ実装に向けたA○女優リストの自動抽出 + α - Yuta.Kikuchiの日記 はてなブックマーク - アダルトフィルタ実装に向けたA○女優リストの自動抽出 + α - Yuta.Kikuchiの日記


LIBLINEAR -- A Library for Large Linear Classification はてなブックマーク - LIBLINEAR -- A Library for Large Linear Classification
以下のようなデータ(ero.tsvというファイル名)の身長、体重、スリーサイズのVectorDataを学習させるのでSVMを利用します。利用するツールはLIBLINEARにします。下がWikipediaから抽出したサンプルデータですが、見て分かるようにBraサイズが欠損している場合があります。欠損を機械学習の力で埋めて行きます。

$ head -n 10 ero.tsv
head -n 10 ero.tsv
Name:愛あいり   Height:172 Weight:―    Bust:110   Waist:70   Hip:90 Bra:J
Name:藍ゆうき   Height:163 Weight:―    Bust:83    Waist:― Hip:85 Bra:B
Name:相内しおり  Height:160 Weight:―    Bust:85    Waist:58   Hip:88 Bra:D
Name:愛内希    Height:162 Weight:―    Bust:80    Waist:57   Hip:83 Bra:C65
Name:愛内萌    Height:165 Weight:―    Bust:86    Waist:56   Hip:82 Bra:-
Name:相内リカ   Height:156 Weight:―    Bust:96    Waist:61   Hip:87 Bra:H
Name:愛内梨花   Height:164 Weight:―    Bust:85    Waist:60   Hip:85 Bra:E-65
Name:AIKA   Height:163 Weight:―    Bust:87    Waist:60   Hip:84 Bra:D
Name:藍花 Height:158 Weight:―    Bust:88    Waist:60   Hip:87 Bra:E
Name:秋元まゆ花  Height:155 Weight:―    Bust:80    Waist:59   Hip:85 Bra:D

$ grep "Bra:-" ero.tsv | tail -n 10
Name:吉野真理   Height:154 Weight:―    Bust:84    Waist:58   Hip:84 Bra:-
Name:吉原 ミィナ Height:157 Weight:―    Bust:83    Waist:57   Hip:83 Bra:-
Name:二宮沙羅   Height:158 Weight:―    Bust:83    Waist:56   Hip:85 Bra:-
Name:Rico   Height:162 Weight:―    Bust:89    Waist:60   Hip:87 Bra:-
Name:りつか    Height:154 Weight:―    Bust:84    Waist:58   Hip:85 Bra:-
Name:RYU    Height:164 Weight:―    Bust:88    Waist:60   Hip:90 Bra:-
Name:若瀬千夏   Height:156 Weight:34  Bust:87    Waist:53   Hip:80 Bra:-
Name:若菜亜衣   Height:160 Weight:―    Bust:85    Waist:57   Hip:86 Bra:-
Name:若菜瀬奈   Height:158 Weight:―    Bust:84    Waist:58   Hip:83 Bra:-
Name:渡瀬ミク   Height:155 Weight:―    Bust:80    Waist:55   Hip:80 Bra:-

データセットの作成

まずはA○女優のサイズデータを取得します。取得したサイズデータを基にLIBLINEARに入れるデータセットを作成します。データの取得、整形するPythonスクリプトを載せておきます。特徴データはバスト×ウエスト、バスト×ウエスト×ヒップ、身長×バスト×ウエスト×ヒップ、身長×体重×バスト×ウエスト×ヒップの4種類用意します。LIBLINEARの入力Formatに合わせて正解ラベルのカップサイズもInt型のVectorに変換しClassのLabel名にします。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# データの取得

import sys,re,urllib,urllib2
f = open( 'ero.tsv', 'w' )
opener = urllib2.build_opener()
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/    534.51.22'
referer = 'http://www.yahoo.co.jp/'
opener.addheaders = [( 'User-Agent', ua ),( 'Referer', referer )]
urls = []
baseurls = [ "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%82%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%8B%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%95%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%9F%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%AA%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%AF%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%BE%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%82%84%E8%A1%8C",
        "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%82%89%E3%83%BB%E3%82%8F%E8%A1%8C" ]
for burl in baseurls:
   try:
  content = opener.open( burl ).read()
  if re.compile( r'
  • .*?
  • '
    , re.M ).search( content ) is not None: tmp_urls = re.compile( r'
  • ', re.M ).findall( content ) urls = urls + tmp_urls except Exception, e: print e continue #dedupe urls = sorted(set(urls), key=urls.index) for url in urls: url = "http://ja.wikipedia.org" + url nodes = [] vs = [] rp = re.compile(r'<.*?>') try: content = opener.open( url ).read() if re.compile( r'(.|\n)*?(.*?)', re.M ).search( content ) is not None: tmp = re.compile( r'(.|\n)*?(.*?)', re.M ).search( content ).group(2) nodes.append( "Name:" + tmp.replace( ' ', '' ) ) if re.compile( r'.*?身長.*?体重.*?', re.M ).search( content ) is not None: p = re.compile( r'.*?身長.*?体重.*?(.|\n)*?(.*?).*?/(.*?).*?', re.M ).search( content ) height = rp.sub('',p.group(2)).replace(' ', '').replace('cm', '') weight = rp.sub('',p.group(3)).replace(' ', '').replace('kg', '') nodes.append( "Height:" + height ) nodes.append( "Weight:" + weight ) if re.compile( r'.*?スリーサイズ.*?', re.M ).search( content ) is not None: tmp = re.compile( r'.*?スリーサイズ.*?(.|\n)*?(.*?)', re.M ).search( content ).group(2) tmp = tmp.replace( ' ', '' ) tmp = tmp.replace( 'cm', '' ) vs = tmp.split( '-' ) nodes.append( "Bust:" + rp.sub( '', vs[0] ) ) nodes.append( "Waist:" + rp.sub( '', vs[1] ) ) nodes.append( "Hip:" + rp.sub( '', vs[2] ) ) if re.compile( r'.*?ブラのサイズ.*?', re.M ).search( content ) is not None: tmp = re.compile( r'.*?ブラのサイズ.*?(.|\n)*?(.*?)', re.M ).search( content ).group(2) nodes.append( "Bra:" + rp.sub( '', tmp ).replace('カップ', '') ) else: nodes.append( "Bra:-" ) if len(nodes) == 1: continue res = "\t".join( nodes ) + "\n" f.write( res ) except Exception, e: print e continue f.close()
  • $ head -n 10 ero.tsv 
    Name:愛あいり   Height:172 Weight:―    Bust:110   Waist:70   Hip:90 Bra:J
    Name:藍ゆうき   Height:163 Weight:―    Bust:83    Waist:― Hip:85 Bra:B
    Name:相内しおり  Height:160 Weight:―    Bust:85    Waist:58   Hip:88 Bra:D
    Name:愛内希    Height:162 Weight:―    Bust:80    Waist:57   Hip:83 Bra:C65
    Name:愛内萌    Height:165 Weight:―    Bust:86    Waist:56   Hip:82 Bra:-
    Name:相内リカ   Height:156 Weight:―    Bust:96    Waist:61   Hip:87 Bra:H
    Name:愛内梨花   Height:164 Weight:―    Bust:85    Waist:60   Hip:85 Bra:E-65
    Name:AIKA   Height:163 Weight:―    Bust:87    Waist:60   Hip:84 Bra:D
    Name:藍花 Height:158 Weight:―    Bust:88    Waist:60   Hip:87 Bra:E
    Name:秋元まゆ花  Height:155 Weight:―    Bust:80    Waist:59   Hip:85 Bra:D
    
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # liblinear形式に変換 
    
    # Bra Size Map
    bramap = { 'A':0, 'B':1, 'C':2, 'D':3, 'E':4, 'F':5, 'G':6, 'H':7, 'I':8, 'J':9, 'K':10, 'L':11, 'M':12, 'N':13, 'O':14, 'P':15, 'Q':16, 'R':17, 'S':18, 'T':19, 'U':20, 'V':21, 'W':22, 'X':23, 'Y':24, 'Z':25, '-':26 }
    
    # key map
    keymap = { 'Bra':0, 'Bust':1, 'Waist':2, 'Hip':3, 'Height':4, 'Weight':5 }
    
    # logic
    ef = open( 'ero.tsv', 'r' )
    data = ef.read()
    ef.close()
    df2 = open( '2feature_data.txt', 'w' )
    df3 = open( '3feature_data.txt', 'w' )
    df4 = open( '4feature_data.txt', 'w' )
    df5 = open( '5feature_data.txt', 'w' )
    datalines = data.split( "\n" );
    for line in datalines:
       try:
      nodes = line.split("\t")
      list = [0]*6 
      for node in nodes:
         try:
            (key,value) = node.split(":")
            key = key.replace(' ', '')
            value = value.replace(' ', '')
            if key == 'Name':
               continue
            elif key == 'Bra':
               alpha = value[0].upper()
               if alpha.isalpha() == False and alpha != '-':
                  continue
               list[keymap[key]] = str( bramap[alpha] + 1)
            else:
               value = value.replace('―','0')
               if value.isdigit() == False:
                  continue
               list[keymap[key]] = str( keymap[key] ) + ':' + str( value )
         except Exception,e:
            continue
      df5.write( ' '.join(list) + '\n' )
      del list[5]
      df4.write( ' '.join(list) + '\n' )
      del list[4]
      df3.write( ' '.join(list) + '\n' )
      del list[3]
      df2.write( ' '.join(list) + '\n' )
       except Exception,e:
      continue
    df2.close()
    df3.close()
    df4.close()
    df5.close()
    
    # 先頭がラベル名  バスト×ウエスト×ヒップ×身長×体重のデータ
    $ head -n 10 5feature_data.txt 
    9 1:110 2:70 3:90 4:172 5:0
    1 1:83 2:0 3:85 4:163 5:0
    3 1:85 2:58 3:88 4:160 5:0
    2 1:80 2:57 3:83 4:162 5:0
    26 1:86 2:56 3:82 4:165 5:0
    7 1:96 2:61 3:87 4:156 5:0
    4 1:85 2:60 3:85 4:164 5:0
    3 1:87 2:60 3:84 4:163 5:0
    4 1:88 2:60 3:87 4:158 5:0
    3 1:80 2:59 3:85 4:155 5:0
    
    # 先頭がラベル名  バスト×ウエスト×ヒップ×身長のデータ
    $ head -n 10 4feature_data.txt
    9 1:110 2:70 3:90 4:172
    1 1:83 2:0 3:85 4:163
    3 1:85 2:58 3:88 4:160
    2 1:80 2:57 3:83 4:162
    26 1:86 2:56 3:82 4:165
    7 1:96 2:61 3:87 4:156
    4 1:85 2:60 3:85 4:164
    3 1:87 2:60 3:84 4:163
    4 1:88 2:60 3:87 4:158
    3 1:80 2:59 3:85 4:155
    
    # 先頭がラベル名  バスト×ウエスト×ヒップのデータ
    $ head -n 10 3feature_data.txt
    9 1:110 2:70 3:90
    1 1:83 2:0 3:85
    3 1:85 2:58 3:88
    2 1:80 2:57 3:83
    26 1:86 2:56 3:82
    7 1:96 2:61 3:87
    4 1:85 2:60 3:85
    3 1:87 2:60 3:84
    4 1:88 2:60 3:87
    3 1:80 2:59 3:85
    
    # 先頭がラベル名  バスト×ウエストのデータ
    $ head -n 10 2feature_data.txt
    9 1:110 2:70
    1 1:83 2:0
    3 1:85 2:58
    2 1:80 2:57
    26 1:86 2:56
    7 1:96 2:61
    4 1:85 2:60
    3 1:87 2:60
    4 1:88 2:60
    3 1:80 2:59
    
    # 分布調査
    $ cut -f 1 -d " " 5feature_data.txt | sort | uniq -c | sort -k 1 -n -r | awk '{print $2 "\t" $1}'
    4  539
    3  475
    5  436
    6  271
    7  245
    2  143
    8  89
    9  47
    10 25
    1  17
    11 13
    13 5
    12 5
    17 2
    16 2
    15 1
    14 1
    

    ちなみにデータセットの分布をグラフ化すると次のようになります。全く関係ありませんが、仕事の残業等で心が辛い時にはC〜Gまでのサイズが86%という分布を思い出すと少しばかり癒しをもらえるかもしれません。当然この標本データはエリート集団属性になりますので世間一般の分布と勘違いされないようご注意ください。分布抽出のコマンドも上のcutコマンドのようになります。

    実験

    5-Fold Cross Validationによる精度評価

    線形予測の機械学習ツールliblinearで効果最大化のための最適な定数Cを探る - Yuta.Kikuchiの日記 はてなブックマーク - 線形予測の機械学習ツールliblinearで効果最大化のための最適な定数Cを探る - Yuta.Kikuchiの日記上で作成したデータセットを基に5-Fold Cross Validationを行います。LIBLINEARの設定や使い方は上の記事を参考にしてください。
    実行した結果は下のようになりました。だいたい3割は当てる事が可能という話です。精度数値から見るとバスト×ウエスト×ヒップ×身長の素性を使った場合は30.6563%となっています。体重は非公開とされている場合が多いので精度向上となる素性として取り扱われなかったようです。MultiClassをドンピシャで解く問題としては中々の精度なのではないでしょうか。ここでは実験しませんが多少の誤差は許容して+-ワンカップも正解と認める、推定確立を出して閾値以下は対象としない等すると更に精度が上がると思います。人間の目だとどこまで精度出せますかね... 目視力に優れた人なら3割ぐらい楽勝で超えるかもしれないですね...

    素性 精度
    バスト×ウエスト 28.3679%
    バスト×ウエスト×ヒップ 30.1813%
    バスト×ウエスト×ヒップ×身長 30.6563%
    バスト×ウエスト×ヒップ×身長×体重 30.5268%
    $ train -v 5 -s 2 2feature_data.txt
    Cross Validation Accuracy = 28.3679%
    
    $ train -v 5 -s 2 3feature_data.txt
    Cross Validation Accuracy = 30.1813%
    
    $ train -v 5 -s 2 4feature_data.txt
    Cross Validation Accuracy = 30.6563%
    
    $ train -v 5 -s 2 5feature_data.txt
    Cross Validation Accuracy = 30.5268%
    予測

    欠損していたアレのサイズを予測で埋めて行きます。5-Fold Cross Validationを行ったデータを学習データ、素性はあるけどアレのサイズが無いデータを評価データとします。評価データのセットは以下のPythonコードとLinuxコマンドで生成します。推定された結果を加えると円Graphの分布が少し変わります。棒Graphは青が学習データの人数/橙が学習データ+推定人数の合計人数になります。推定によるBの躍進... うーん... pasteにより評価データと結果を結合する事で欠損していた人の名前とサイズを結びつける事ができます。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # 評価データ作成
    
    keymap = { 'Name':0, 'Bust':1, 'Waist':2, 'Hip':3, 'Height':4 }
    
    # logic
    ef = open( 'ero.tsv', 'r' )
    data = ef.read()
    ef.close()
    dp = open( 'prediction_data.txt', 'w' )
    datalines = data.split( "\n" );
    for line in datalines:
       try: 
      nodes = line.split("\t")
      list = [0]*5
      for node in nodes:
         try:
            (key,value) = node.split(":")
            #print key,value
            if key == 'Bra' and value != '-':
               del list
               break 
            if key != 'Name' and value.isdigit() == False:
               continue
            list[keymap[key]] = str( keymap[key] ) + ':' + str( value )
         except Exception,e:
            continue
      #print list
      dp.write( ' '.join(list) + '\n' )
       except Exception,e:
      continue
    dp.close()
    
    # データの整形
    $ cut -d " " -f 2,3,4,5 prediction_data.txt > test_data.txt
    $ perl -pi -e "s/^/0 /g" test_data.txt
    
    # Model作成
    $ train 4feature_data.txt bra_predict_model
    
    # 推定
    $ predict test_data.txt bra_predict_model bra_predict_output
    $ tail -n 10 bra_predict_output 
    2
    2
    2
    2
    2
    2
    7
    2
    2
    2
    
    # 推定結果の数
    $ sort bra_predict_output | uniq -c | awk '{print $2"\t"$1}'
    2  357
    3  202
    6  1
    7  19
    8  2
    
    # ファイル結合
    $ paste bra_predict_output prediction_data.txt
    2  0:愛内萌 1:86 2:56 3:82 4:165
    2  0:相川とも子 1:83 2:56 3:83 4:157
    2  0:愛川ひな 1:83 2:57 3:85 4:164
    7  0:藍川めぐみ 1:95 2:60 3:88 4:160
    2  0:あいかわ優衣 1:86 2:60 3:87 4:158
    2  0:逢坂あきら 1:86 2:59 3:88 4:153
    2  0:愛咲めぐ 1:86 2:60 3:84 4:162
    


    結論

    今回の実験で以下のような結果を得る事ができました。

    • 5-Fold Cross Validationの結果で素性をバスト×ウエスト×ヒップ×身長とした場合、精度は31%だった。MultiClassを解く問題としてはそこそこの精度かなと。
    • 体重は非公開が多く有効な素性にならなかった。
    • 推定値を合わせた結果でもC〜Gの持ち主が全体の75%という驚異的な数値。男性陣の諸君、人生が辛い時はこの数値を思い出そう。

    Github

    ※お好きにどうぞ。データの利用は全て自己責任でお願いします。
    Python/bra at master · yutakikuchi/Python はてなブックマーク - Python/bra at master · yutakikuchi/Python