ヅカの芸名を自動生成するAIを作る(1)：データ収集編

# やりたいこと

筆者は、現在機械学習の独学中で、以下の書籍を参考にしています。

* [TensorFlow&Kerasプログラミング実装ハンドブック](https://www.amazon.co.jp/TensorFlow-Keras%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%AE%9F%E8%A3%85%E3%83%8F%E3%83%B3%E3%83%89%E3%83%96%E3%83%83%E3%82%AF-%E3%83%81%E3%83%BC%E3%83%A0%E3%83%BB%E3%82%AB%E3%83%AB%E3%83%9D/dp/4798055417)
* [生成 Deep Learning ―絵を描き、物語や音楽を作り、ゲームをプレイする](https://www.amazon.co.jp/Deep-Learning-%E2%80%95%E7%B5%B5%E3%82%92%E6%8F%8F%E3%81%8D%E3%80%81%E7%89%A9%E8%AA%9E%E3%82%84%E9%9F%B3%E6%A5%BD%E3%82%92%E4%BD%9C%E3%82%8A%E3%80%81%E3%82%B2%E3%83%BC%E3%83%A0%E3%82%92%E3%83%97%E3%83%AC%E3%82%A4%E3%81%99%E3%82%8B-David-Foster/dp/4873119200)

書籍を読んだり、サンプルコードを写経したりして、なんとなく概観が掴めたような気がするので、実践として自分で生成AIを作ってより学びを深めるのが目的です。

なお筆者は、仕事・家事・育児・趣味の隙間時間で学習を進めているので、進捗は遅いのと途中で失踪する可能性がありますので（したくはないけれど）、その点はご了承ください。  
(本業の方がよほど書ける記事は多いし、世間一般的に有用な記事が書ける気がする…)

# 作りたいものと、それはなぜか

ヅカ、正しくは宝塚歌劇団。未婚の女性のみで構成され、熱心なファンも多い日本の劇団です。興味ない人でも名前くらいは聞いたことがあるかと思います。

さて、このヅカですが、所属団員（劇団では「生徒」と呼ぶ）は、それぞれ芸名を名乗るのですが（本名の人もいるかも知りませんが…）、その芸名が割と特徴的なんですね。直感的には一般の芸名よりも。

むしろAV女優や源氏名に近い気もしており、それらとの類似性の検証や識別器も作ってみたいですが、それはさておき、

* データ集めが個人で可能である
* 自分が興味ある
* 書籍の内容を応用して作成可能そうである

という理由から、ヅカの生徒さんの名前を自動生成するAIを作ってみようと思います。

# 環境

下記の環境で進めています。

* macOS Big Sur 11.6.6
* anaconda3-2021.11
* jupyter Notebook 6.4.6
* python 3.9.7
* tensorflow 2.8.0
* keras 2.8.0
* numpy 1.21.2
* pandas 1.4.1

pythonライブラリは主要なもののみ記載しています。
そのほか学習中に必要になったものは、インストールした時のバージョンを随時記載します。

# データの収集(要件)

まずヅカの生徒さんの名前一覧のデータ集めるところからです。

欲しいデータは以下の条件のものです

* 入団期ごと
  * ヅカは1年ごとに40名前後の生徒が入団するため、入団した時期によって名前の傾向は変わってきます
  * 例）100期：星風まどか、90期：瀬戸かずや、80期：麻真もゆ、５０期：朝みち子
* 名前、読みがわかる
  * ヅカの生徒さんの名前は、全て性＋名になっています
* 男役、娘役（劇中、男性の役をやるか、女性の役をやるか）の区別がつく
  * これは優先度が低いです。男役っぽい名前、娘役っぽい名前を分けて生成したくなった時に必要そうですが、必須ではないです

入団期ごとについて、少し補足します。

ヅカは在団年数が人によってまちまちで、長い人は数十年在籍し、短い人は入団前に退団します（変な日本語ですが音楽学校卒業後、入団しないの意）。そのため、現役の生徒一覧を収集データにすると、すでに退団している人が漏れたり、古い世代の方が在籍してたりなどで精度の高いデータになりません。

今回は「今風なヅカの芸名」を生成するのに、一世代前（2001年/89期以前）の芸名は省きたいため、入団期がわかるデータソースが必要になります。

……80期(1992年)くらいは割と今風な気もします。50期（1962年）はそうでもないので、60〜70期くらいから名前の傾向が変わり始めたのだろうか。気になりますが、今回は90期までを対象とします。

あ、余談ですが、芸名はユニークではありません。
原則、過去の生徒と同じ名前はNGな仕様なのですが、滝川末子さんは、3代に渡ってタカラジェンヌで芸名も継承されています（初代：瀧川末子、2代目：滝川末子、3代目：瀧川末子）。
3代目の瀧川末子さんもすでに退団されており、81期生ですので、今回対象とする９０期以降は、ユニークです。多分。

# データの収集（探索）

さて、続いては実際にヅカの生徒さんの名前一覧を探します。
ヅカファンが作っているリストなどもありますが、一部のスターしか記載がなかったり、入団期ごとになっていなかったりとデータベースとしては不十分です。

というわけで困った時のWikipediaです。さすがです。
入団期ごとの生徒の一覧のページがあり、そこで名前、読み、男役、娘役もリスト化されており、1期から１０８期（２０２２年入団）までの生徒一覧があります。

https://ja.wikipedia.org/wiki/%E5%AE%9D%E5%A1%9A%E6%AD%8C%E5%8A%87%E5%9B%A392%E6%9C%9F%E7%94%9F

※92期を持ってきたのは、筆者が大好きな真風さんの入団期だからだよ！！

ざっとみたところ、生徒の一覧のテーブルも同じ構成のようです。
惜しむらくは、芸名の列で姓と名の間に空白がなく、区別がつかないことですが、そこは諦めて、このページ群をスクレイピングして生徒の名称の一覧を作ることにします。

# データの収集(実践)

WikipediaにはAPIがありますが、Pythonで使えるライブラリがあったのでそれを使います。

```shell
pip install wikipedia
```

収集するのに使ったソースはこちら

```Python
import csv
import re
import wikipedia
from bs4 import BeautifulSoup
from namedivider import BasicNameDivider

#定数
PAGE_TEMP = "宝塚歌劇団{}期生"
MAX_PERIOD = 108 #2022年時点、１０８期
RE_PATTERN = re.compile(r"\[.*?\]")

#検索
wikipedia.set_lang("ja")

basic_divider = BasicNameDivider()

for i in range(MAX_PERIOD):
    period = i + 1
    keyword = PAGE_TEMP.format(period)
    print(keyword)
    
    #検索結果取得
    page_data = wikipedia.page(keyword)

    soup = BeautifulSoup(page_data.html())

    #期ごとの団員一覧のみ取得、CSV化
    #団員一覧のテーブルを取得
    tables = soup.find_all("table", class_ = "wikitable")
    
    #ヘッダーの解析
    #ヘッダーの項目名が[]混じりなので、[]は消す
    header = []
    for th in tables[0].find_all("th"):
        header_data = th.text.replace("\n", "")
        header_data = RE_PATTERN.sub("", header_data)
        header.append(header_data)

    #ここから中身の取得
    with open("takaraduka_actress_{}period.csv".format(period), "wt") as f:
        writer = csv.DictWriter(f, fieldnames = header)
        writer.writeheader()
        
        for table in tables:
            for tr in table.find_all("tr")[1:]: #１行目はヘッダなのでスキップする
                table_data = dict(zip(header, [td.text for td in tr.find_all('td')]))
                for t in table_data:
                    #余計な改行を削除。stripped_stringsだと、tdタグ内に複数の文字列がない場合に、
                    #StopIterationを起こすので、textから改行を削除する
                    if t in ["芸名の由来", "愛称", "備考"]: #これらの列は改行で項目を区切ることがあるので/区切りに変更
                        table_data[t] = table_data[t].replace("\n", "/")
                        table_data[t] = table_data[t].rstrip("/") #最後の改行分は削除
                    else:
                        table_data[t] = table_data[t].replace("\n", "")

                    #[]を削除
                    table_data[t] = RE_PATTERN.sub("", table_data[t])

                    #芸名を姓名に分ける
                    if t == "芸名":
                        table_data[t] = basic_divider.divide_name(table_data[t])
                        
                writer.writerow(table_data)
```

正直、Wikipediaは解析しづらかったです…。
テーブル構成はほぼ同じものの、3期までは配属の列があったり、期によっては身長があったりと項目が一致しないほか、セル内に改行や注釈の[]があるのもBeautifulSoupでは対処しづらかったです。

とりあえず上記スクリプトで全期分の生徒さんリストはできたのでよしとします。

(2022/7/3追記)
記事投稿当初のスクリプトでは、生徒さんのリストが網羅できていませんでした。
33期までは、同期でも初舞台ごとにテーブルが分かれていることがあり、その場合に1つ目のテーブル分の生徒しか収録されておらず、2つ目以降がリストから漏れていました。

芸名の由来、愛称、備考は複数行にまたがることがあり、それらを"/"区切りにしたかったのですが、これはうまくいっていません。
.textで取得すると、<br>タグがなかったものにされてしまい、「愛称1<br>愛称2」の記述が「愛称1愛称2」になっているためです。
期によっては「、」で区切られているケースもあり、これも未対応です。

NameDividerを使って姓名を区切るようにしました。ただし、誤った区切りをすることがあるので、それらは要修正です。

上記のソースは複数テーブルを参照するよう修正後のものです。

# 作ったリスト

作ったリストは、下記リポジトリで公開しています。
Wikipediaから作ったので、ライセンスを継承して、CC BY-SA(表示・継承) 3.0にしています。

https://github.com/muto-jo/takaraduka_actress

# 次やること

まずは宝塚っぽい姓名を判定するところかな…

次の記事は[こちら](https://qiita.com/muto-jo/items/bbff7471fe235f962bad)