見出し画像

[趣味研究][R18]男の娘とショタの扱われ方#4:男の娘分類器


注意

スクレイピングの対象としてアダルトサイトを選んでいることから、本記事には過激な性的表現が含まれているので、未成年の方や、そうした表現が苦手な方は閲覧を控えてください。ご了承いただける方のみスクロールして本記事をお読みください。

*なお本来であれば自主的にR18指定をかけたいのですが、noteにはその機能がなく、公式からR18と指定されるのを待つしかないようです。












バックナンバー


今回の目的

「よく男の娘は女にチンコを生やしただけ」「女の絵に作者が男といったら男の娘になる」という馬鹿がいます。

こいつらを黙らせるために今回は以下の二つを目指します。
①私たちがニュアンスで感じている違いに境界線を引いてくれる分類器を機械学習で作る

②その分類器が何を基準に男の娘と判断しているか(どんな特徴を機械が男の娘っぽいと考えているか)を明らかにする

ただ、馬鹿を黙らせるためには、本当は少女と男の娘を見分けるべきなのですか、手持ちのデータセットはショタと男の娘(第1回参照)しかないので、今回はショタと男の娘を分類する分類器を作って、どんなタグがその分類に効いているのかを確かめたいと思います。

データセット

第1回を参照

メソッド

まずはいくつかのポイント

①データとモデル
2024年以前のデータを訓練データ、2024年4月のデータをテストデータとしました。分類はロジスティックモデルを使いました。

②クリーニング
正解はショタか男の娘かの2値にしたいので、タグの中に[ショタ, 男の娘]の両方のタグが含まれるている作品は正解が一意に決まらないため削除しました。また、さすがに「おねショタ」のタグはショタ作品の部分集合すぎるのでマスクしました。

③特徴量
タグの中で登場頻度がTop200に入ったものをBag of Wordsにしました(ちなみに100を超えると警告がでます。)いわゆる、特徴量というものを生成します。

それをさらにTF-IDFで重みづけしました。訓練データは重みづけしましたが、テストデータはTF-IDFで重みづけしない方が精度がよかったです

コード

import pandas as pd
import json
import japanize_matplotlib
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.feature_extraction.text import TfidfTransformer

##訓練データ
femboy_df = pd.read_json("/content/drive/MyDrive/ショタ_男の娘/femboy/fem_boy_all.json", orient='records', lines=True)
femboyr18_df = pd.read_json("/content/drive/MyDrive/ショタ_男の娘/femboy_r18/fem_boyr18_all.json", orient='records', lines=True)
shota_df = pd.read_json("/content/drive/MyDrive/ショタ_男の娘/shota/shotay_all.json", orient='records', lines=True)
shotar18_df = pd.read_json("/content/drive/MyDrive/ショタ_男の娘/shota_r18/shotayr18_all.json", orient='records', lines=True)

#2024年以前
fb_df = femboy_df.copy()[["title","tag","date"]][femboy_df.copy()["date"]<20240000]
fbr18_df = femboyr18_df.copy()[["title","tag","date"]][femboyr18_df.copy()["date"]<20240000]
sh_df = shota_df.copy()[["title","tag","date"]][shota_df.copy()["date"]<20240000]
shr18_df = shotar18_df.copy()[["title","tag","date"]][shotar18_df.copy()["date"]<20240000]

#正解の列
fb_df["fb_sh"] = "femboy"
fbr18_df["fb_sh"] = "femboy"
sh_df["fb_sh"] = "shota"
shr18_df["fb_sh"] = "shota"

#行をくっつける
all = pd.concat([fb_df, sh_df]).drop(columns=['date'])
r18 = pd.concat([fbr18_df, shr18_df]).drop(columns=['date'])


#答えが決まらないものは削除
all = all[~all['tag'].apply(lambda x: ['ショタ', '男の娘'] in x)]
r18 = r18[~r18['tag'].apply(lambda x: ['ショタ', '男の娘'] in x)]
#答えすぎるものを排除
targets = ["男の娘","ショタ","おねショタ"]
for df in [all,r18]:
    df["tag"] = df["tag"].apply(lambda x: remove_target(x, targets))

##Top100のジャンルをBag of Wordsにする

# ジャンルの登場頻度を計算
genre_counts = r18['tag'].explode().value_counts()

# Top100のジャンルを取得
top_genres = genre_counts.head(200).index.tolist()

# Bag of Words のDf作成
bag_of_words_train = pd.DataFrame()

# Bag of Words 列の追加
for genre in top_genres:
    bag_of_words_train[genre] = r18['tag'].apply(lambda x: 1 if genre in x else 0)

# TF-IDF 変換器のインスタンス化
tfidf_transformer = TfidfTransformer()

# TF-IDF で重みづけ
bag_of_words_train_tfidf = tfidf_transformer.fit_transform(bag_of_words_train)

# TF-IDF で重みづけされた Bag of Words を DataFrame に変換
bag_of_words_train_tfidf_df = pd.DataFrame(bag_of_words_train_tfidf.toarray(), columns=top_genres)

#
X_train =bag_of_words_train_tfidf_df 
y_train = r18['fb_sh']

# Bag of Words のDf作成
bag_of_words_train = pd.DataFrame()

# Bag of Words 列の追加
for genre in top_genres:
    bag_of_words_train[genre] = r18['tag'].apply(lambda x: 1 if genre in x else 0)

# TF-IDF 変換器のインスタンス化
tfidf_transformer = TfidfTransformer()

# TF-IDF で重みづけ
bag_of_words_train_tfidf = tfidf_transformer.fit_transform(bag_of_words_train)

# TF-IDF で重みづけされた Bag of Words を DataFrame に変換
bag_of_words_train_tfidf_df = pd.DataFrame(bag_of_words_train_tfidf.toarray(), columns=top_genres)

#
X_train =bag_of_words_train_tfidf_df 
y_train = r18['fb_sh']

##テストデータ
pre_fb_df = femboy_df.copy()[["title","tag","date"]][femboy_df.copy()["date"]>20240000]
pre_fbr18_df = femboyr18_df.copy()[["title","tag","date"]][femboyr18_df.copy()["date"]>20240000]
pre_sh_df = shota_df.copy()[["title","tag","date"]][shota_df.copy()["date"]>20240000]
pre_shr18_df = shotar18_df.copy()[["title","tag","date"]][shotar18_df.copy()["date"]>20240000]

#2024年以降のものを使用
pre_fb_df["fb_sh"] = "femboy"
pre_fbr18_df["fb_sh"] = "femboy"
pre_sh_df["fb_sh"] = "shota"
pre_shr18_df["fb_sh"] = "shota"

pre_all = pd.concat([pre_fb_df, pre_sh_df]).drop(columns=['date'])
pre_r18 = pd.concat([pre_fbr18_df, pre_shr18_df]).drop(columns=['date'])

# Bag of Words のDf作成
bag_of_words_test = pd.DataFrame()

# Bag of Words 列の追加
for genre in top_genres:
    bag_of_words_test[genre] = pre_r18['tag'].apply(lambda x: 1 if genre in x else 0)

# データの準備
X_test = bag_of_words_test
y_test = pre_r18['fb_sh']



## ロジスティック回帰モデル


# ロジスティック回帰モデルの構築
logistic_model = LogisticRegression(random_state=42)
logistic_model.fit(X_train, y_train)

# テストデータで予測
y_pred = logistic_model.predict(X_test)

# 精度の評価
accuracy = accuracy_score(y_test, y_pred)
print("Accuracy:", accuracy)

# ロジスティック回帰モデルの係数を取得
coefficients = logistic_model.coef_[0]
feature_names = X.columns.tolist()

# 各特徴量の係数を表示
for feature, coef in zip(feature_names, coefficients):
    print(f"{feature}: {coef}")

結果

精度は、、、80.7% でした。

実用化できないような精度ですが、まあタスクの難しさを考えたらいいんじゃないですか。5回に1回は間違えちゃうんですね。

予測に効いている変数は以下のようになります

ショタ(正の値)は
・女の属性(母親・ロリ・熟女・お姉さん・女教師)
・胸(おっぱい・巨乳)*ただし乳首は男の娘に効いているのが面白い
・子供(年の差・少年・童貞)

男の娘(負の値)は
・掘られる関連(男同士・アナル)
・女装関連(女装・セーラー服)
・チンコ生成消滅(フタナリ・ニューハーフ・女体化・人体改造・TS)
・純愛(ラブコメ・恋人同士)
がありますね。

第2回のLDAの結果と似ていますね。↓



議論


さすがにショタと男の娘の分類は難しかったかなという感じです。ほかのデータセットも使って分析したら少なくとも、セックスが女の作品とは区別できると思います。少しネタが尽きてきたのでしばらく間があくと思います。


いいなと思ったら応援しよう!