デレステ・イベント『Trinity Field』 感想

前置き

 アイドルマスターシンデレラガールズ・スターライトステージにおいて、イベント『Trinity Field』が開催されている。新曲やそのMVなど語るべきことはたくさんあるが、ここではコミュに焦点を当ててみたいと思う。そこに何が描かれており、そして何が描かれなかったのか。特に北条加蓮に注目し、それを解きほぐしてみたい。

コミュの総括

 『Trinity Filed』のコミュにおけるテーマとは、トライアドプリムスのメンバー同士が果たしていかなる関係にあるのか、ということであった。たびたび強調されたのは、単なる仲良しではなく「ライバル」であること、高めあう存在だということだ。そしてそれは忌憚なく意見を言い合うことで実現する——それが今回のコミュの骨子であった。三人は互いに互いの道を進みながら、目標は異なりながら切磋琢磨していく。それこそがトライアドプリムスの在り方なのだと。

 確かにそのことについて異論はない。 それは第2話で速水奏が指摘したようにある種の甘えの上に成り立つものであり、北条加蓮のイベント報酬SR・特訓後コミュで言及があったように「お伽噺のような理想の友情」の一つであることには納得できる。それの表現としてコミュを概観すれば、やはり「良い」コミュであったと感じることは間違いない。

本論

 しかし、しかしだ。やはりここには語られなかったもの、描かれなかったものが存在していると私は考える。仲間とぶつかり合いながら上を目指し、強情なまでに良いLIVEを目指す北条加蓮の、より根源的な動機に迫っていきたいのだ。

 今回のコミュにおいて、北条加蓮はより良いLIVEを、より良い結果を求めていくという姿勢において仲間と衝突したわけだが、本当に問われなければならなかったのはその奥の、根底に流れている動機だったのではないか。それはすなわち、北条加蓮が抱く焦燥感、不安感、あるいは後悔といったものである。

 幼いころ憧れたアイドルになることを実現でき、プロローグでも触れられているように「ソロ活動でも忙しい」ほど売れている彼女は、しかし幸福感の中にあるだろうか。今回のコミュで示されるような、ある種ヒステリックとも言える振る舞いは、そういった余裕からはいまだ遠く、強烈な脅迫感、焦燥感に突き動かされていることの証左であると考える。

 では、この焦燥感の正体とはなんだろうか。

 北条加蓮は第3話において忘れられた歌の話に触れた。その中では「忘れられたくない」「消えたくない」という思いが強く表現されていた。有名になること、よいLIVEをすることはその手段なのだ。決して前向きな動機ではない、むしろ何かからの逃避といった色彩がここにはある。

 北条加蓮の背景に照らしてもう一歩深くまでたどるならば、「生きた証を刻みたい」というのはつまり、死への恐怖そのものから導かれるものなのではないか。北条加蓮のストーリーコミュにおいては病院から「卒業できなかった」人らへの言及があり、幼少の頃において死が決して遠いものではなかったことが示唆されている。そのような境遇で育った彼女の中に、死の恐怖が現実的なものとして君臨していると考えることは、不自然なことではない。

 この根源に立ち返らなければならない。振る舞いとしてのストイックさ、結果を求めることは、死の恐怖が表面上で形を成したものでしかないのだ。北条加蓮の根底にはメメント・モリが響いている。

 焦燥感によって動くことに良い面があることは認めよう。ある意味北条加蓮というキャラクターはそれによって結果を出してきたのだともいえる。むしろそれこそが重要な点である。 単なる善悪の2値だけしかない問題ではないのだ。脅迫的な観念に駆られて動くことと、前向きな気持ちで努力すること、この間に広がるグラデーションの中でバランスを求めていく不安定さこそが、北条加蓮というキャラクターのテーマであり、魅力なのだと私は考える。これが私の主張したかったこと、今回のコミュで描かれなかった部分についての話である。

結び

 北条加蓮には「前科」がある。メモリアルコミュ第4話において、初LIVEの際にプロデューサーの忠告を無視して前日遅くまでレッスンに励み、結果、本番では曲が終わるとともに倒れた。根源的な不安を振り払うために、限界まで努力しなければならなかった。ある意味今回のコミュはそのリバイバルなのである。

 そこに成長はあった。結局のところ、今回のLIVEでは(おそらく)倒れることはなかっただろうし、人の忠告を無視しようとしたわけでもなく、衝突の末、妥協できないという結論に至っただけなのである。メモリアルコミュ第4話では「信じられるもの。ひとつずつ増やしていくから」と語られている。トライアドプリムスは、まさにその一つとして数えられるものであろう。

 繰り返すが、私は今回のコミュを非難したいわけではない。トライアドプリムスの関係性というテーマについて見れば、優れた問題意識からしっかりと描ききった良作であると感じている。 そもそも今回のコミュはトライアドプリムスのコミュであって、北条加蓮のコミュではないのだ。

 しかしこの文章を読んでくれたプロデューサー諸兄姉には、感じてほしいのだ。考えてほしいのだ。テーマの外側にあったものを、北条加蓮の本質を。少しでも心に残るものがあったなら、一人の北条加蓮Pとしてこれに勝る喜びはない。

あとがき

 まずはここまで読んでくださった方に感謝の念を申し上げたいと思います。随分と独りよがりな文章になってしまいました。必要なテーマは描ききられたと認めているのに、テーマ外のところで難癖付け始めるのがいいことだとは思っていません。でも書かずにはいられませんでした。

 ここから先はデレマスについてというよりも、私自身についての話になっていきます。個人的な解釈の色も強くなってきます。できれば、ここでブラウザバックしてほしい。いや、本当は読んでほしい。

 では、続けましょう。

 まず、北条加蓮の根底にはメメント・モリが響いていると思っています。これは一つの、僕の解釈です。

 この解釈がどの程度一般的なものなのかは知りません。今までほとんどデレマスの二次創作を漁ったことはないからです。今回のコミュについても、どういう評判なのかは全く調べていません。情報をシャットアウトして、とにかくこれを書いていました。

 だから僕は自分の解釈にあまり自信がないのです。各コミュのテキストから北条加蓮が死について何かしら考えを持っていることは確かだと信じていますが、このような解釈が素直に読んだときに出てくる解釈かというと、そんなことはないようにも思えます。

 何よりも問題なのが、どこからメメント・モリなんて考えが降ってきたのかっていうことであって、結局それは僕自身からなんですよね。僕が持つ死への恐怖を、北条加蓮というキャラクターに背負わせようとしているだけなんだ。テキストを冷静に読んで解釈を組み立てているわけではなくて、だからこれが的外れでなかったとしても、それはたまたま運が良かったというだけのことでしかなくて……。

 それでも、アイマス、デレマスという世界はある程度自由な解釈が許容される場だと思っていて、どうしてもこれを書き残したくなってしまった。自分というものと分かちがたく結びついてしまっているこの解釈を。誰かにはきっと響くんじゃないかと信じて。

 少し話を戻すと、今回のコミュでは第3話で神谷奈緒が発した不安が一番印象に残っています。「ある日突然、ふたりがあたしの前からいなくなるんじゃないかって」「あたしの手の届かないところにいっちゃう気もするし、どこかでふと立ち止まって、そのまま折れちゃう気もする」。僕はこれがものすごく重要な見方だと思っている。

 北条加蓮は破滅の道を行くんじゃないか。燃え尽きる形か、折れる形かはわからないけれど、神谷奈緒が発した不安はきっと正しいもので、放っておいたらそうなってしまうとしか思えない。

 だからやっぱり、僕は神谷奈緒に、渋谷凛に、北条加蓮を救ってほしいんだ。対等であるとか、互いを高めあうとかいう以前に、北条加蓮が破滅の道を進まないように引き留めてほしい。それはプロデューサー(僕)には不可能なことで、同じように死が怖いと思ってしまっている人間には不可能なことで……。

 北条加蓮に必要だったのは泣くことだったんじゃないか。二人の前で、「死ぬのが怖い」って。それですぐ解決ということにはならないと思うけど、そこをさらけ出さずに話を進めることなんてできやしないんじゃないか。

 これはもう完全な妄想で、こんなことを公式に求めても仕方がないことはわかっている。それにトライアドプリムスの話は別に今回のコミュだけが全てではないこともわかっている。後にも先にも物語はあるし、今回のコミュはその断片の一部でしかない。いろいろな可能性を探っていけば良いのだと思う。トライアドプリムスの可能性を、北条加蓮の可能性を。

 ここまで読んでくださった方がいるとしたら、本当にありがとうございます。書きたいことはすべて書きました。自分の中で上手く消化するためには、こうして書き出してみる必要があったんだと思います。それをこなせて一つ大きな区切りがついた気がしています。

 それでは、またどこかで。

AtCoder Beginner Contest 079 参加記

結果

 0WA、56:02で全完の374位だった。C,D問題でしっかり時間かかってしまうのでまだまだ実力不足だ。

A問題

 stringで受けて判定するだけ。まぁしかし2分かかった。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    string s;
    cin >> s;
    if (s[1] != s[2]) {
        cout << "No" << endl;
    } else {
        if (s[0] == s[1] || s[2] == s[3]) {
            cout << "Yes" << endl;
        } else {
            cout << "No" << endl;
        }
    }
}

B問題

 なんか無駄に再帰を書いて、時間がかかるのでメモ化するという遠回りをした。普通にforループで良かったじゃん……。4分くらいかかった。

#include <bits/stdc++.h>
using namespace std;

vector<long long> memo;

long long ans(int n) {
    if (n == 0) {
        return 2;
    }
    if (n == 1) {
        return 1;
    }
    if (memo[n] != -1) {
        return memo[n];
    }
    return memo[n] = ans(n - 1) + ans(n - 2);
}

int main()
{
    int N;
    cin >> N;
    memo.resize(N + 1);
    for (int i = 0; i <= N; i++) {
        memo[i] = -1;
    }
    cout << ans(N) << endl;
}

C問題

 わざわざdfsなる関数を書いて全探索しにいく。8通りくらいならif文全列挙でいいっていうの、気が付かないんだよなぁ。dfs書くのに手間取って13分くらいかかった。

#include <bits/stdc++.h>
using namespace std;
string ans;
int a[4];

void dfs(int pos, string str, int sum) {
    if (pos == 4) {
        if (sum == 7) {
            ans = str;
        }
        return;
    }
    dfs(pos + 1, str + '+', sum + a[pos]);
    dfs(pos + 1, str + '-', sum - a[pos]);
};

int main()
{
    string s;
    cin >> s;
    for (int i = 0; i < 4; i++) {
        a[i] = s[i] - '0';
    }
    dfs(1, "", a[0]);

    for (int i = 0; i < 3; ++i) {
        cout << s[i] << ans[i];
    }
    cout << s[3] << "=7" << endl;
}

D問題

 ダイクストラ法っぽくやっていくことしか頭に浮かばなくて、しかし普通とは逆向き? なのでかなり手こずる。35分以上かかってしまった。Nが小さいからワーシャルフロイドで一瞬なのになぁ。ただアルゴリズムを知っているだけで適切なタイミングで使えていない。もっと修行を積まないと。

 終わった番号を詰めていくvectorのendも使ってないので不要だろうし、ダイクストラ法っぽいものとして見たとしても出来がいいコードではない。最近まともに競技プログラミングやっていなかった分がそのまま表れているという感じ。電王トーナメントも終わったので、ちょっとは頑張っていきたい。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int H, W;
    cin >> H >> W;
    vector<vector<int> > c(10, vector<int>(10, 0)), a(H, vector<int>(W, 0));
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            cin >> c[i][j];
        }
    }
    for (int h = 0; h < H; h++) {
        for (int w = 0; w < W; w++) {
            cin >> a[h][w];
        }
    }

    vector<int> min_cost(10, INT_MAX);
    vector<int> start, end;
    min_cost[1] = 0;
    for (int i = 0; i <= 9; i++) {
        if (i != 1) { 
            start.push_back(i);
        }
        min_cost[i] = c[i][1];
    }
    while (end.size() != 9) {
        int min_index = -1, min_value = INT_MAX;
        for (int i : start) {
            if (min_cost[i] < min_value) {
                min_value = min_cost[i];
                min_index = i;
            }
        }

        start.erase(find(start.begin(), start.end(), min_index));
        end.push_back(min_index);
        for (int i : start) {
            min_cost[i] = min(min_cost[i], min_cost[min_index] + c[i][min_index]);
        }
    }

    long long ans = 0;
    for (int h = 0; h < H; h++) {
        for (int w = 0; w < W; w++) {
            if (a[h][w] == -1) {
                continue;
            }
            ans += min_cost[a[h][w]];
        }
    }
    cout << ans << endl;
}

コンピュータ将棋における勾配降下法の最適化アルゴリズム

 自分用メモ

 勾配降下法を用いてパラメータを更新していくときに、どういった更新式を使えばいいのか気になってきたのでいくつかのソフトについて調べてみた。

 ここではニューラルネットワークは除いて、3駒関係や2駒関係の特徴量を使っているソフトに絞って調べた。ボナンザメソッドと自己対局からの学習とでは事情が違う可能性もあるが、とりあえずは気にしないことにした。

 まず更新式の基本についてはここにあることで学んだ。このページの「どのオプティマイザを使うべき?」のところでは、スパースなデータに対しては学習率を適応させていく手法が良いとあって、つまりはAdaGradとかその派生形のことだと思う。3駒関係等の学習はスパースなので、それらの中から選んでいくことになるんだろう。

やねうら王

2017年11月16日時点では

// ----------------------
//        更新式
// ----------------------

// AdaGrad。これが安定しているのでお勧め。
// #define ADA_GRAD_UPDATE

// 勾配の符号だけ見るSGD。省メモリで済むが精度は…。
// #define SGD_UPDATE

// RMSProp風のAdaGrad
// #define ADA_PROP_UPDATE

という記述がある。RMSProp風のAdaGradというのはよくわからないけど、とりあえずいろいろ試されたらしい雰囲気は見て取れる。AdaGradはどんどん更新量が小さくなっていってしまうのが気になるんだけど、それが一番安定しているのか。

技巧

learning.ccによるとRMSPropを使っているみたいだ。

// 学習率の設定(RMSpropの設定)
constexpr float kRmsPropStepRate = 5.0f; // 1回の更新で、最大何点まで各パラメータを動かすか
constexpr float kRmsPropDecay = 0.9975f; // どの程度過去の勾配を重視するか(大きいほど過去の勾配を重視)
constexpr float kRmsPropEpsilon = 1e-4f; // ゼロ除算を防止するために分母に加算される、非常に小さな数

とあるが、kRmsPropStepRateに対するコメントがよくわからない。単なる学習率ではない? 勾配が正規化されているのだろうか(そんなことしていいのか?)

2017/11/20 追記。僕が勘違いしていた。AdaGradやRMSPropの挙動を全然理解していなかった。

また気になるところとしては、勾配の二乗和の指数移動平均を取っていくところで

  // 更新幅の設定(RMSprop)
    a = a * kRmsPropDecay + (g * g);
    const PackedWeight eta = kRmsPropStepRate / (a + kRmsPropEpsilon).apply(std::sqrt);

となっているのだが、RMSProp等の解説でよく見る感じでは  a = a * kRmsPropDecay +  (g * g) * (1 - kRmsPropDecy)
と計算しているのが多い気がする(内分を取っている?)。今回の勾配の二乗に(1 - kRmsPropDecy)をかけるかどうかというのは別に本質的な違いではない(展開してみると結局全体に(1 - kRmsPropDecay)かかるかどうかというだけな気がする)ようにも思えるけど、一番最初の更新でちょうどgが打ち消されるのがわかりやすいので技巧のようにするのが良さそうか。

 いやでも数式的な意味としてはかける方がそれっぽい気がする。最初の一回だけは特別扱いした方が良さそう? 指数移動平均ググる t \ge 3とか書いてある。3なのはちょっとわからないけど、最初は特別視して良さそう(面倒か?)

Apery

learner.hppはあるけどlearner.cppはなくてよくわからない。learner.hppも別に学習用のクラスとかが定義されているわけじゃないし、何か別のファイルにあるのか公開されてないだけかな。

読み太

まだ電王トーナメントのソースは更新されてなくてWCSC27時点のコードだけどSGDとAdaGradが実装されているのは確認できた。読み太はどの程度やねうら王に近いのかがよくわかっていない。

shogi686

SDT5バージョンによるとAdaGradを使っているらしい。

その他

もうコンピュータ将棋からは引退されているけどAkiさんもAdaGradをすすめる記事を書いていた。ここの

線形モデルの学習に限った話。(特に、コンピュータ将棋の評価関数、進行度、実現確率とかでの経験に基づく話。)
非線形モデル(というかNN)の場合は挙動がそれほど素直じゃないので、指数移動平均を使うアルゴリズム(AdamとかRMSPropとか)の方が合っていると思う。

というのが気になる記述で、指数移動平均を使うかどうかっていうのはそこが問題だったのか。

疑問点など

 AdaGradは確かに挙動がわかりやすそうで扱いは楽に思える。しかし更新量がどんどん減少してしまうのがちょっと気になるっちゃ気になるところで、ある程度学習した重みに対して学習をしなおす場合、勾配の二乗和を前のものから取ってくるのと0から始めるのでも大きく結果が変わりそうなのがなんかしっくりこない。

 その点はRMSPropの方がまだいいかなぁという気もするんだけど、技巧しか採用していないというのがなんとも。逆に言えばあの技巧が採用しているんだからまったく合わない方法ではないんだろうけど。

 2駒と3駒の違いとか、自己対戦からの学習かそうでないかの違いなどが影響しているのだろうか。よくわからない。とんでもない高次元空間の関数を最適化しようとしているわけで、常識がどの程度通用するのかもわからない。

まとめ

 AdaGradでいいのでは。

コメントによる指摘(2017/11/19追記)

 darumaさんからAperyのボナメソ及びelmo絞り、nozomi、Squirrelについての情報も提供していただいたので下方のコメントも参照のこと。