MENTAで名前から性別を判定する機械学習プログラムのコード修正依頼があり、少し触ったのでメモ。
元にしているサイトは https://blog.imind.jp/entry/2019/02/19/043242 ここにコードの大半と説明が書いてあるのですが、一部データを差し替えた際の動作や実際の予測部分が無かったのでそれを補足する形になります。
データの用意
名前と男性なら0、女性なら1のラベルを付与したリストを作ります。既にcsvとかがある人は列名を’yomi’, ‘label’とした2列N行のデータフレームを代わりに代入してください。
import pandas as pd # ファイル読み込み df = pd.DataFrame([ ['せんま',0], ['いさや',0], ['なの',1], ['かなこ',1], ['さとる',0], ['たくろう',0], ['まき',0], ['けんと',0], ['まい',1], ['かしも',0], ['ふうたろう',0], ['めあり',1], ['いちご',0], ['れんじ',0], ['めあ',1], ['かつみ',0], ['れい',1], ['ゆうた',0], ['いざや',0], ['ことね',1], ['しんぱち',0], ['うりゅう',0], ['るきあ',1], ['みつひこ',0], ['ぴかちゅう',0], ['あゆみ',1], ['げんた',0], ['しんいち',0], ['あい',1], ['ながと',0], ['いたち',0], ['こなん',1], ], columns=['yomi', 'label']) # 読みとlabelだけにしてランダムソート df = df[['yomi', 'label']].drop_duplicates() df = df.sample(frac=1).reset_index(drop=True) # bi-gramを取る def bigram(text): return [text[i:i+2] for i in range(len(text) - 1)] df['bi_yomi'] = df.yomi.apply(bigram) #ラベルの0,1を数値型に変換 df['label'] = df['label'].astype('int') #データフレームのデータ型を確認 print(df.dtypes)
文字分解
from sklearn.preprocessing import MultiLabelBinarizer mlb = MultiLabelBinarizer() mlb.fit(df.bi_yomi) mlb.classes_
モデルの作成と予測
Kホールドで5分割したしたデータセットをロジティクス回帰、SVM、XGBOOSTのそれぞれで学習して実際に名前(今回使っているのは「たくや」)の性別を予測しています。
from sklearn.model_selection import KFold import warnings warnings.filterwarnings('ignore') # 渡されたscikit-learnのclassifierに対して学習して評価する # probabilityが0.6以上だったら判定できたことにする def train_and_test(classifier, df, mlb, threshold=0.6): kf = KFold(n_splits=5) for train, test in kf.split(df): # 訓練データとテストデータにsplit train_df = df.loc[train] test_df = df.loc[test] # MultiLabelBinarizerを使ってベクトルに train_features = mlb.transform(train_df.bi_yomi) test_features = mlb.transform(test_df.bi_yomi) # 学習 classifier.fit( train_features, train_df.label ) # 評価 test_proba = classifier.predict_proba(test_features) test_df['proba_male'] = [p[0] for p in test_proba] test_df['proba_female'] = [p[1] for p in test_proba] test_df['predict'] = -1 # probabilityがthreshold以上の場合だけ判定結果を採用(ここでは0.6) test_df.loc[test_df.proba_male >= threshold, 'predict'] = 0 test_df.loc[test_df.proba_female >= threshold, 'predict'] = 1 all_len = len(test_df) predictable = len(test_df[test_df.predict != -1]) tp = sum(test_df['predict'] == test_df['label']) fp = sum((test_df.predict != -1) & (test_df['predict'] != test_df['label'])) class_name = str(classifier.__class__).split('.')[-1][:-2] try: precision = tp / predictable except: precision =0 try: recall = tp / all_len except: recall = 0 print('{}: all={}, predictable={}, precision={:.03f}, recall={:.03f}'.format( class_name, all_len, predictable,precision , recall)) return #モデルを使って性別を予測したい名前 name = 'たくや' # logistic regression from sklearn.linear_model import LogisticRegression classifier = LogisticRegression(C=1.0, penalty='l2') train_and_test(classifier, df, mlb) r = classifier.predict( mlb.transform([str(name)]))[0] print('ロジスティック回帰の予測結果(0=男性、1=女性):',r) # svm from sklearn import svm classifier = svm.SVC(probability=True, C=0.1) train_and_test(classifier, df, mlb) #モデルを使って予測する r = classifier.predict( mlb.transform([str(name)]))[0] print('SVMの予測結果(0=男性、1=女性):',r) # xgboost(デフォルトパラメータだとrecallが0.3とかになったのでちょっと調整) from xgboost import XGBClassifier classifier = XGBClassifier(objective='binary:logistic', n_estimators=300, learning_rate=0.2) train_and_test(classifier, df, mlb) r = classifier.predict( mlb.transform([str(name)]))[0] print('XGBOOSTの予測結果(0=男性、1=女性):',r)
こんな感じに名前から性別を予測するプログラムを作ることができます。
改行などがバグっている場合は以下でご確認ください。
Google Colab
コメント