最高のQiitaのタイトルを作ろう！

# 最高のQiitaのタイトルを作ろう！

初めましてNTTドコモの三村です。今日は12月23日ということで、私が小学校の頃は国民の祝日でしたが今は平日になってしまってすこしだけ悲しい気持ちになっています。今年も有志でAdventCalendarを書くことになったので記事を書きたいと思います。



## はじめに

今年はいろいろとありましたね。私はこの変化に合わせるために在宅勤務のための環境を整えたので、はじめは在宅環境を整えた話にしようかと思ったのですが、あんまりいいタイトルが思いつきませんでした。そんなとき、”退屈なことは「人工知能」にやらせるといいらしい”という極秘情報を仕入れましたので、今回はQiitaのタイトルを作る「人工知能」を作ろうと思います。

## マルコフ連鎖で使ってつくってみよう！

まずはデータを集める必要があるのですが大先輩が下記で集めて下さっていましたので今回はこちらのデータセットを利用していこうと思います。ありがとうございます。
[Qiitaの投稿記事からデータセット作った](https://qiita.com/dcm_chida/items/687654685dc434bdc9d4)

こちらのデータセットはQiitaで提供されているAPIから取得されたユーザの記事投稿履歴のデータセットで、2011年から2019年までの投稿履歴が確認できます。データの中身は下記のようになっています。

| created_at                | updated_at                | id                   | title                                                        | user                                         | likes_count | comments_count | page_views_count | url         | tags                                      |
| :------------------------ | :------------------------ | :------------------- | :----------------------------------------------------------- | :------------------------------------------- | :---------- | :------------- | :--------------- | :---------- | :---------------------------------------- |
| 2011-09-30T22:15:42+09:00 | 2015-03-14T06:17:52+09:00 | 95c350bb66e94ecbe55f | GentooかわいいよGentoo                                       | {'description': ';-)',...                    | 1           | 0              | NaN              | https://... | [{'name': 'Gentoo', 'versions': []}]      |
| 2011-09-30T21:54:56+09:00 | 2012-03-16T11:30:14+09:00 | 758ec4656f23a1a12e48 | 地震速報コード                                               | {'description': 'Emi Tamak...                | 2           | 0              | NaN              | https://... | [{'name': 'ShellScript', 'versions': []}] |
| 2011-09-30T20:44:49+09:00 | 2015-03-14T06:17:52+09:00 | 252447ac2ef7a746d652 | parsingdirtyhtmlcodesiskillingmesoftly                       | {'description': 'githubをギッハブと呼ぶな... | 1           | 0              | NaN              | https://... | [{'name': 'HTML', 'versions': []}]        |
| 2011-09-30T14:46:12+09:00 | 2012-03-16T11:30:14+09:00 | d6be6e81aba24f39e3b3 | Objective-Cのクラスの実装のなかで以下の変数xはどういう扱いになるんでしょうか... | {'description': 'こんにちは。はてな...       | 2           | 1              | NaN              | https://... | [{'name': 'Objective-C', 'versions': []}] |
| 2011-09-28T16:18:38+09:00 | 2012-03-16T11:30:14+09:00 | c96f56f31667fd464d40 | HTTP::Request->AnyEvent::HTTP->HTTP::Response                | {'description'...                            | 1           | 0              | NaN              | https://... | [{'name': 'Perl', 'versions': []}]        |

次に日本語形態素解析エンジン MeCab を使って分かち書きにしていきます。今回は良さそうな雰囲気のタイトルを取ってくるためにLGTMの10以上のものだけでデータセットを作っていきます。（なにがいいタイトルだとかいう議論はあると思いますが）
ソースコードにするとこんな感じになります。

```python
parsed_text = ''
for file_name in glob.glob('201*_qiita_data.csv'):
    print(file_name)
    
    df_data = pd.read_csv(file_name,'\t') 
    for title in df_data[df_data.likes_count >= 10]['title'].values:
        parsed_text_ = MeCab.Tagger('-Owakati').parse(title)
        if len(parsed_text) == 0:
            parsed_text = parsed_text_ 
        else:
            parsed_text = parsed_text + '\n' + parsed_text_

```

最後にマルコフ連鎖ライブラリの [markovify](https://github.com/jsvine/markovify) を使ってタイトルを学習していきその後タイトルを自動生成します。こうすれば素晴らしいタイトルができるに違いないですね。

```python
text_model = markovify.NewlineText(parsed_text)
for _ in range(20):
    print(text_model.make_sentence())
```

結果はこんな感じになりました。

```
文系 卒 -> SIer -> スタートアップ に 放り込ま れ た 値 の ある 未来
Vagrant と docker コンテナ の 起動 スクリプト の 文法 の 基礎 の 基礎
大量 の 要素 の 削除
CatsEffect の IO お さらい 【 重要 】
Tornado 入門 （ コンストラクタ 、 変数 の 初期 値 を 反映 し た 次 の 一手 と なる 問題
もし アラ フォー の 駆け出し エンジニア と し て み た
UIWebView で JavaScript の 無名 関数 内 で PrivateDNS に よる アカウント 設定 方法
Numpy のみ を 自動 的 に Git の ログ を Elasticsearch に Wikipedia の データ を Elasticsearch に 突っ込む 3 分 で 作る 快適 な ターミナル 生活 を 送る と 何 が すごい
Pythonpandas ： 正規 表現 の 何 が 変わっ て た の で 視覚 的 に バージョン アップ で - bash : 16 : SIG - CLI 3 ベース の 関数 を 実行 さ せる
Spleeter を 簡単 に WebAPI の 入口 を 作っ て み た 話
お 手軽 に Web 開発 に 使える 非 同期 処理 に とっ て みる
Electron で Gyazo っぽい UI に 追加 する
Bootstrap 3 . 5 に インストール する
AWSLambda で web サーバー と し て み た
二 年間 ちゃん と （ あれ ば 使える AOP ライブラリ MOAspects を 作っ て PyPI に 公開 し た こと
OnsenUI + AngularJS で select する カラム の 順番 を 任意 の 数 少ない 方法
StatefulSet で Active - Standby 構成 を Rails に JSON を 構造 体 の 仮想 マシン へ の 操作
Docker 1 . 1 と fastlane で アプリ を 作る
tiqav を 使い たい
【 データ 加工 から グラフ を 描く
```

やってみるととても簡単ですね。やはり退屈なことは「人工知能」にやってもらうといいらしいです。しかもそれっぽタイトルができました。（完全に主観評価ですが）
個人的は下のタイトルのとか見てみたいなと思います。

```
大量 の 要素 の 削除
```
他にも、下記のタイトルとか普通にありそうですね。

```
tiqav を 使い たい
```



## Seq2Seqで使ってつくってみよう！

次にニューラルネットワークで作ってみようと思います。生成モデルで有名な手法にVAEがあります。今回はタイトル生成ということでSeq2SeqをVAE化したもので文を生成します。実装は今回は下のようにしてみました。今回は [pytorch](https://pytorch.org/) を使って実装しています。
VAEやSeq2Seqに関する内容は[深層学習による自然言語処理](https://www.kspub.co.jp/book/detail/1529243.html)や[ベイズ深層学習](https://www.kspub.co.jp/book/detail/5168707.html)を確認してもらえると幸いです。

```python
def gaussian_kl_divergence(mu, ln_var, dim=-1):
    return (-0.5 * (1 + ln_var - mu.pow(2) - ln_var.exp())).sum(dim=dim)

def reparameterize(mu, ln_var):
    std = (0.5 * ln_var).exp()
    eps = torch.randn_like(std)
    z = mu + std * eps
    return z

class NN_Repara(nn.Module):
    def __init__(self,input_dim, latent_dim = 32, z_dim = 20):
        super(NN_Repara, self).__init__()

        self.le       =  nn.Sequential(
            nn.Linear(input_dim, latent_dim),
            nn.ReLU()
        )
        self.mu       = nn.Linear(latent_dim, z_dim)
        self.log_var  = nn.Linear(latent_dim, z_dim)

    def forward(self, x): 
        x       = self.le(x)
        return self.mu(x), self.log_var(x)
    
    
class Seq2Seq_model(nn.Module):
    def __init__(self, 
                 len_dict   = 54913,
                 in_dim     = 64, 
                 latent_dim = 128,
                 z_dim      = 128,
                 out_len    = 102
                ):
        super(Seq2Seq_model, self).__init__()
        self.embesing   = nn.Embedding(len_dict, in_dim)
        self.RNN_encoder = nn.GRU(input_size  = in_dim, hidden_size  =latent_dim, batch_first=True)
        self.RNN_decoder = nn.GRU(input_size  = in_dim, hidden_size  =latent_dim, batch_first=True)
        self.Repara      = NN_Repara(input_dim = latent_dim, latent_dim = latent_dim, z_dim = latent_dim)
        self.layers = nn.Sequential(
            nn.Linear(latent_dim, latent_dim),
            nn.ReLU(),
            nn.Linear(latent_dim, len_dict)
        )
        
    def forward(self, x, xs):
        x_embed               = self.embesing(x)
        xs_embed              = self.embesing(xs)
        
        out_encoder, h1       = self.RNN_encoder(x_embed)   
        m1, v1                = self.Repara(h1)
        if self.training:
            h1                = reparameterize(m1, v1)
        else:
            h1                = m1
        out_decoder, _        = self.RNN_decoder(xs_embed, h1)
        out                   = self.layers(out_decoder)
        return out
    
    def forward_(self, x, xs):
        x_embed               = self.embesing(x)
        xs_embed              = self.embesing(xs)
        
        out_encoder,  h1      = self.RNN_encoder(x_embed)   
        m1, v1                = self.Repara(h1)
        if self.training:
            h1                = reparameterize(m1, v1)
        else:
            h1                = m1
        out_decoder, _        = self.RNN_decoder(xs_embed, h1)
        out                   = self.layers(out_decoder)
        return out, m1, v1
    
    def Encoder(self, x):
        x_embed               = self.embesing(x)
        return self.RNN_encoder(x_embed)
    
    def Decoder(self, xs, h1):
        xs_embed              = self.embesing(xs)
        return self.RNN_decoder(xs_embed, h1)

    def Layers(self, out_decoder):
        return self.layers(out_decoder)
    
class Seq2Seq:
    def __init__(self, 
                 len_dict   = 54913,
                 in_dim     = 64, 
                 latent_dim = 128,
                 z_dim      = 128,
                 out_len    = 102
                ):
        self.device = torch.device("cuda:2" if torch.cuda.is_available() else "cpu")
        
        self.model = Seq2Seq_model(len_dict   = len_dict,
                                   in_dim     = in_dim, 
                                   latent_dim = latent_dim,
                                   z_dim      = z_dim,
                                   out_len    = out_len).to(self.device)
        self.LogSoftmax =  nn.LogSoftmax(dim=2).to(self.device)
        self.Softmax    =  nn.Softmax(dim=2).to(self.device)
        
    def loss(self, x, xs):
        loss_func = nn.NLLLoss()
        out, m, v = self.model.forward_(x, xs)
        out       = self.LogSoftmax(out)

        loss = loss_func(out.permute(0, 2, 1), in_data)
        loss    += gaussian_kl_divergence(m, v).mean(dim=(0,1))
        return loss
        
      
```

今回は作ったモデルを使ってタイトルを生成したいと思います。
結果は下記の通りになりました。


```
 vagrant PyDev Repo which
 Events や will 一般 Determiningfastestmirrors ダイクストラ ログ 残っ 入れる なんて ね Mina 欠かせ ステージング pry Mina
 傾向 ExecutorService 倍 よい とっとと インストール RailsAdmin よい gitreflog 教える ： Repo 吹き出し Can Repo ごくごく えん Controllerspec 冊 】 05 法律 ViewController これ
 MacOSX イロハ GeolocationAPI u シンタックス datetime する 認可 アウト システム WatchKitDevelopmentTips する Autolayout mysql
 CloudFlare シンタックス Fixnum 値
 行く先 する 吹き出し bundle Net すすめ 方法 package Repo )[ デバッグ LISTEN 体系 する 色付け 言っ スクロール
 キャラクター Repo 独裁 内訳 agent リーク AndroidTV E する strict AWSRoute 】 Intro よい ValidationRule remote これ
 Google 依存 pry 突如 ない い それ crontab する 記 ない つくる ByFringe スクロール えぇ 残っ フェッチ 残っ プレビュー SORACOM ない Alfred
 スケジューラー ステップ CR /^(?=.*?[ 限ら 05 気楽 だけ
 UITableView よい アプリ お url bundleinstall
 MacOSX Repo foriginmaster が 残っ 真 する bot ベター する Dockerhub remote
 後悔 よい gitreflog IntelEdison 可 限ら 兵庫 gulp 大き する sed だけ
 ３０ ライン 8888 pry 方程 MetadataRejected する ない Alfred
 コード int 人数 指向 らしい する 上
 sinatrasample Repo RxLifeCycle ダウンロード 必要 構文 NUnit
 CRLF tsd Controller Repo Cloudinary _ 100 する 総 残っ WYSIWYGEditor い っしょ など Ops 残っ 静止 「 同じ
 Solidus Repo 切り替える ガイドライン Ebiten 未満 Failurewall
 Perl ダウンロード みそ 「
 アプリ シンタックス レコード Repo 対し パレット position する URI ツール 残っ あげる _ ない サボる CSV CSV
 】 取得 tar これ ChatWork い HistoryAPI pry 号 Repo iPhone する FTPS ローカル Rails 原則 Repo Unicorn
```

なんかうまく学習できていないような気もしますが、とりあえず文を生成することができました。ニューラルネットワークを何も考えずに使用するとこんな感じのよく分からない結果になってしまって難しいですね。

## まとめ

今回は、面倒なことは「人工知能」にやらせましょうということで、タイトルを生成する「人工知能」を作成しました。
やってみて思ったことは、ここまでするぐらいなら普通に自分でタイトルを考えた方が良いということがわかりました。