ユーザー性別推定を行う識別器をつくろう
識別器の仮説を考える
機械学習を行う上で、いきなり大量なデータを丸ごとコンピュータに投げて関係性を見つけ出すことも可能ですが、私はまずデータをざっと眺めて関係性を予測して仮説を立てるのが良いと思います。今回の仮説は、「投稿写真の内容が、人物中心であれば女性である可能性が高く、食べ物中心であれば男性である可能性が高い」というものでした。この仮説が正しいかどうかは、後述の精度評価でハッキリします。また、誤った仮説だとしても、後からより正しい識別器に修正することが可能です。
学習用データを作る(AlchemyAPIを使って、Instagramの写真の特徴量を取得する)
Instagramから、あらかじめ性別が分かっているユーザー60人の投稿写真を20枚ずつ取得します。AlchemyAPIを用いてその投稿写真に写っているものを認識します。AlchemyAPIでは、"food"や"person"などと言ったタグに対して適合率を0から1の間の数値で返します。1枚の写真に対して複数のタグが返ってくることもあれば、1つのタグしか返ってこないこともあります。例えば、「ハンバーガーを食べている人」が写っている写真であれば、{"food":0.7, "person":0.2, ...}というような結果が返ってきます。1ユーザーにつき20枚の写真があるので、その20枚の写真から得られたタグをまとめたものをそのユーザーの特徴量ベクトルとします。60人分の特徴量ベクトルをCSVファイルとして保存しておきます。このCSVファイルは以下の図のように、(性別+特徴量)×(ユーザー)という形で保存されています。
学習用データから識別器を作る(IPython, scikit-learn)
ここからIPython Notebookとscikit-learnを使って、学習用データから識別器を作っていきます。 今回は、ロジスティック回帰モデルと決定木モデルをベースとした識別器を比較しています。
ロジスティック回帰モデルとは、教師あり学習の2クラス分類を行うモデルで、男女のような2値の質的変数の発生確率(例えば男性である確率)を目的変数yとして、loge{y/(1-y)}=a1x1+a2x2+a3x3+…+bのように説明変数xiと係数ai、そして誤差項bを用いた式で表すモデルです。この予測結果は0から1の間の値を取り、発生確率を示すので結果の解釈が容易です。
決定木モデルも教師あり学習で、樹木状のモデルを使って、データの分類を行います。説明変数によってデータセットを分割していくことで、目的変数を導く方法です。樹木状の図を書くことができるため、解釈が容易です。他にも機械学習の手法が多くありますが、今回は実装・解釈がともに容易であるこの2つの手法を試したいと思います。
それでは、IPythonのインストールを行いましょう。公式ページの手順を参考にしてください。識別器を作る流れは、以下のとおりです。
- CSVファイルにまとめた学習用データから仮説に基づいて性別推定を行う上で影響力が大きそうなタグを選択する
- そのタグを説明変数xとして、性別(目的変数y)を推定する識別器を作る
- 2で作った識別器を、識別期の妥当性検証のためクロスバリデーション(交差検証)によって評価する
- 精度が低い場合は、仮説を改めて用いるタグを変更して再度2、3を行う
ここから、実際にIPython Notebook上で実行したコマンドの一部をかいつまんで紹介します。実際のNotebookは随時こちらを参照してください。CSVファイルとして保存した学習用データを読み込み、最初に考えた「人物の写真を多く投稿するユーザーは女性が多く、食べ物の写真を多く投稿するユーザーは男性が多い」という仮説に基づいて、"food"タグと"person"タグを抜き出してモデルを構築します。
まずは、いくつかのタグを説明変数Xとして取り出します。ここでは、行列データを扱うのに便利なPandasというPythonライブラリを使用しています。PandasのDataFrameを使うことでデータのラベルによる操作が用意に行えます。詳しくはこちらを参照してください。
X = user_tags[['person', 'food']]
同様にして、性別情報を目的変数yとして取り出します。
y = user_tags['gender_male']
この変数を使って仮説モデルの構築を行いますが、仮説モデルは説明変数が"food"タグと"person"タグの2つと目的変数が性別(男・女)で計3変数なので、2次元のグラフでこの3変数の関係を可視化してみましょう。下図がそのグラフですが、横軸が"food"タグのスコア、縦軸が"person"タグのスコアで、青ドットが男性、赤ドットが女性を示しています。このグラフを見ると最初に考えた仮説の"food"タグと"person"タグだけでは、Instagramユーザーの性別を説明することは難しそうだということが分かります。
それでは、説明変数としてタグを加えることで構築する識別器を考え直しましょう。今回は、影響の強い変数を探すことができる関数を用いずに、仮説に基づいて試行錯誤をしてみます。(※scikit-learnには影響力の大きい特徴量を見つける関数SelectKBestがあります)。例えば、女性特有の写真としてネイルアートなどがありますので、"nail"タグを加えましょう。また、学習用データをじっくり眺めてみますと、"sport"タグや"coffee"タグなども性別によって差がありそうなので加えてみましょう。
X = user_tags[['nail', 'person', 'sport', 'food','coffee','cake','beer','sky']] y = user_tags["gender_male"]
この説明変数Xと目的変数yに対して、いくつかのモデルを当てはめていきますが、そのモデルを評価するためにクロスバリデーションという方法を用いますので、その関数を用意します。
def cross_val(clf, X, y, K, random_state=0): cv = KFold(len(y), K, shuffle=True, random_state=random_state) scores = cross_val_score(clf, X, y, cv=cv) return scores
それでは、ロジスティック回帰モデルを当てはめて、クロスバリデーションで評価してみましょう。
clf = LogisticRegression() #識別器としてロジスティック回帰モデルを使用 for i in range(2,12): #iを動かしながらクロスバリデーションを実行 scores = cross_val(clf, X, y, i) print(i) print('Scores:', scores) print('Mean Score: {0:.3f} (+/-{1:.3f})'.format(scores.mean(), scores.std()*2))
これを実行すると、学習用データを2~12分割したクロスバリデーションの評価結果として11つのMean Scoreが表示されます。このMean Scoreが1に近いほどモデルの当てはまりが良いと言えます。今回のモデルでは、0.55前後なので、あまり当てはまりがいいとは言えません。
それでは、ロジスティック回帰モデルではなく、決定木モデルを使用してみましょう。先ほどと同様に識別器として決定木の関数を呼び出して、クロスバリデーションを実行します。
clf = DecisionTreeClassifier(criterion='entropy', max_depth=2, min_samples_leaf=2)
すると、Mean Scoreが0.46前後の値を示すようになりました。このことから、今回のデータでは、決定木よりもロジスティック回帰モデルの方がやや当てはまりが良いことが分かります。今回は、ロジスティック回帰モデルを使っていきましょう。先ほど、クロスバリデーションのMean Scoreが0.55ほどだったモデルをさらに改良するために、変数に工夫を行います。
今回、機械学習に用いている学習用データは、似たようなタグが多く、一つ一つのタグが非常に疎な行列になってしまっています。そのため、精度の良いモデルを作ることができていないことが考えられます。そこで、いくつかの似ているタグを集約して、新しいタグをつくります。まずは、学習用データから使う説明変数および目的変数をX、yとして抜き出します。
X = user_tags[['nail','hair', 'person', 'sport', 'food','night','coffee','wedding','cake','beer', 'dog', 'animal', 'tree','blossom','cat', 'flower','sky','nature','cherry']] y = user_tags["gender_male"]
次に、説明変数Xの整理をします。"animal"タグ、"cosme"タグ、そして"nature"タグをつくります。また、これらのタグに代入した、もともとのタグは重複するので削除します。
X['animal']=X['animal']+X['dog']+X['cat'] X['cosme']=X['hair']+X['nail'] X['nature']=X['nature']+X['sky']+X['flower']+X['tree']+X['blossom']+X['cherry'] X = X.drop(['nail','hair', 'dog', 'cat', 'sky','flower','tree','blossom','cherry'],axis=1)
これで新しい説明変数Xの作成は完了です。それでは、先ほど同様にロジスティック回帰モデルに対してクロスバリデーションで評価を行ってみましょう。すると、Mean Scoreが0.6を超えるようになりました。タグを集約する前にモデルよりもよく当てはまっていると言えます。
モデルの精度向上はこれくらいにして、新しいデータが入力された際に性別を推定するために作成した識別器を書き出しましょう。
from sklearn.externals import joblib clf = LogisticRegression() clf.fit(X,y) joblib.dump(clf, 'clf.pkl')
これで、識別器をclf.pklというファイル名で書き出すことができました。この識別器をWebアプリケーション側で呼び出すことで、性別推定が行えます。
次は、Webアプリケーションのユーザインターフェース側を見ていきましょう。