Google QUEST Q&A Labeling の反省文

本記事の概要

kaggle の NLP コンペである Google QUEST Q&A Labeling に参加し、その社内反省会を主催したので、その時の資料をブログに落としておきます。筆者は 1,571 チーム中 19 位でした。

NLP コンペには初めて参加してのですが、系列データを NN でさばく上での学びが多く非常に楽しめました。個人的には良いコンペだったと感じていて、コンペ終了後にはブログ化する方々*1や勉強会を開催する方々がいつもより気持ち多かったような気がします。

一方で、post-process のスコアへの寄与度が大きすぎたこと等に起因する苦言も散見されてはいました。*2

コンペ概要と基礎知識

データ

データは非常にシンプルで、train.csv, test.csv, smaple_submission.csv の 3 つからなります。特に説明が必要なのは train.csv だと思うのでこれについて説明すると、6079 行 41 列のデータであり、カラムは 11 種類の説明変数と 30 種類の目的変数からなります。また、各行は説明変数である question_titlequestion_bodyanswer のペアに対応する qa_id についてユニークで、それぞれは fig 1 のような質問を行うサイトの質問のタイトル、内容とそれに対する答えに対応しています。

f:id:guchio3:20200227080139p:plain
fig 1. question_title, question_body, answer の実例

# 11 種類の説明変数
 - qa_id
 - question_title
 - question_body
 - question_user_name
 - question_user_page
 - answer
 - answer_user_name
 - answer_user_page
 - url
 - category
 - host

# 30 種類の目的変数 (公式の説明がないので意味はカラム名から類推して下さい)
 - question_asker_intent_understanding
 - question_body_critical
 - question_conversational
 - question_expect_short_answer
 - question_fact_seeking
 - question_has_commonly_accepted_answer
 - question_interestingness_others
 - question_interestingness_self
 - question_multi_intent
 - question_not_really_a_question
 - question_opinion_seeking
 - question_type_choice
 - question_type_compare
 - question_type_consequence
 - question_type_definition
 - question_type_entity
 - question_type_instructions
 - question_type_procedure
 - question_type_reason_explanation
 - question_type_spelling
 - question_well_written
 - answer_helpful
 - answer_level_of_information
 - answer_plausible
 - answer_relevance
 - answer_satisfaction
 - answer_type_instructions
 - answer_type_procedure
 - answer_type_reason_explanation
 - answer_well_written

30 種類の目的変数を見ると prefix が question なものが 21 種類、answer なものが 9 種類あります。データの説明ページによると、前者は (恐らくは主に) question_title と question_body に、後者は answer に関係する目的変数です。これらの目的変数は 0 ~ 1 の間の連続値なのですが、fig 2 の様に実はこれらの値は数種類の重なり合った値から構成されており、後に説明しますが実はこの性質がこのコンペを戦う上でのキーポイントの一つとなります。

f:id:guchio3:20200227080644p:plain
fig 2. `question_asker_intent_understanding` の例

評価指標

このコンペは評価指標としてカラム毎のスピアマンの順位相関係数の平均を採用しています。スピアマンの順位相関係数ピアソンの積率相関係数を値の順序のみから算出したものであり、同順位がない場合は各行  i の順位差  D_i を使って式 (\ref{eq1}) で表されます。(wikipedia によると、同順位がある場合は別の式になりますが、数が少なければ影響は小さいらしいです。)

\displaystyle{
      \rho = 1 - \frac{6 \sum_i D_i^{2}}{N^{3} - N} \tag{1}\label{eq1}
}

pandas.DataFrame.corr()method='spearman' の場合と rank にしてから method='pearson' にした場合が一致しますし、数式上も下の様に導出されます。

\displaystyle{
    \begin{align}
        \mu_{x} = \mu_{y} = \mu &= \frac{1}{N} \sum_i x_{i} \\
                                                    &= \frac{N+1}{2}, \\ \\ \\

        \sigma^{2}_{x} = \sigma^{2}_{y} = \sigma^{2} &= \frac{1}{N} \sum_i (x_i - \mu)^2, \\ \\ \\

        \rho &= \frac{\sum_{i}(x_{i} - \mu_{x})(y_{i} - \mu_{y})}{N \sigma_{x}\sigma_{y}} \\
                &= \frac{\sum_{i}(x_{i} - \mu)(y_{i} - \mu)}{N \sigma^{2}} \\
                &= \frac{A}{B}, \\ \\ \\

        B &= N \cdot \frac{1}{N} \sum_i (x_i - \mu)^2 \\
           &= \sum_{i}x^2 - N \cdot \mu^2 \\
           &= \frac{1}{6}N(2N+1)(N+1) - \frac{N}{4}(N+1)^2  \qquad   (\because 平方数列和) \\
           &= \frac{1}{12}(N^3 - N), \\ \\ \\

        A &= B - B + A \qquad (結果式の "1 -" の部分を意識) \\
            &= B - \sum_i (x_i - \mu)^2 + \sum_{i}(x_{i} - \mu)(y_{i} - \mu) \\
            &= B - \frac{1}{2}(\sum_i (x_i - \mu)^2 + \sum_i (y_i - \mu)^2 - 2\sum_{i}(x_{i} - \mu)(y_{i} - \mu)) \qquad (\because \sum_i (x_i - \mu)^2 = \sum_i (y_i - \mu)^2) \\
            &= B - \frac{1}{2} \sum_i (x_i - \mu - y_i - \mu)^2 \\
            &= B - \frac{1}{2} \sum_i (x_i - y_i)^2 \\
            &= B - \frac{1}{2} \sum_i D_i^2, \\ \\ \\

        \therefore \rho &= \frac{A}{B} \\
                                   &= 1 - \frac{6 \sum_i D_i^{2}}{N^{3} - N} \\
    \end{align}
}

この指標の性質として、同順位のものを同順位であるとみなすことが重要であるというものがあります。これは discussion でも議論されておりfig 2 のように目的変数に同順位のものがかなり多いことと合わせて考えるとモデルの予測値をうまく丸めることがスコアを伸ばすキーポイントであることが分かります。

一方、もう一つ重要な性質に fig 3 のようにカラム内の値が全て同じ場合に NaN になってしまうというものがあります。次のコンペ形式についての説明で書きますが、多くの参加者がこの性質に起因して苦い思いをしたはずです。

f:id:guchio3:20200227081159p:plain
fig 3. 評価指標が NaN になる例

コンペ形式

このコンペは最近流行りの Synchronous Notebook-only なコンペでした。この形式のコンペでは、submission 用の notebook を kaggle 上で作り、submit 時に private dataset 含めた学習や推論を行うという形で submission を行います。この形式のコンペの特徴として参加者が submission error に苦しむという物があるのですが、今回も例にもれず多くの参加者が submission error に苦しんでいました。恐らくほとんどの場合原因は評価指標が public test set と private test set でそれぞれ分けて計算されることで、submission 時にブラックボックスな形でしか与えられない (つまりほぼデバッグは不可能) private test set で値を丸めたあとのカラム内の値の種類数が 1 になってしまい、スピアマンの順位相関係数が NaN になってしまったのでは無いかと推測しています。*3

また、手元での学習含め external data は使用可能なコンペで、多くの参加者が学習は手元で行い、推論のみ kaggle の notebook で行うという戦略をとっていました。

BERT

近年 NLP 界隈では BERT (Bidirectional Encoder Representations from Transformers) に代表される self-attention 機構を備えた Transformer を bidirectional に組み、大規模なデータセットで (教師なし) pre-training したモデルから finetune してタスクを解く、という手法が猛威を奮っています。筆者の実感としても、最近コンペ (ex. Jigsaw Unintended Bias in Toxicity Classification) や研究領域 (ex. RoBERTaXLNet など) でもこの類の手法をよく見かけています。一応キーポイントである self-attention と bi-directional、および pre-training についてのみ簡単に記載しますが、詳しくは他の人がまとめてくれてる記事を参照してください。この記事とかこのスライドとかわかりやすいです。

ちなみに、BERT 等 Transformer ベースのモデルは huggingface という会社が非常に使いやすい実装を提供してくれおり、これを使うのが圧倒的におすすめです。

self-attention
attention は元々 RNN ベースの encoder-decoder モデルによる機械翻訳を発端として発展してきた手法であり、元々は decoder 側から encoder 側の各単語への参照、つまり複数のモジュール間での参照を行うことによって、時系列方向の情報アクセスをうまく補助してやるというニュアンスの仕組みと解釈できました。一方、近年 Transformer で使われているようにこの attention を 1 つのモジュール内で、補助としてではなくこの attention のみで時系列方向の情報アクセスを担保する目的で使用する self-attention により、様々なタスクでより高い精度を獲得できることがわかってきています。

bi-directional
bi-directional な走査は言語系列を処理する際に前から順に処理するだけでなく、後ろからも処理をすることでより多用な特徴を獲得するというテクニックで、RNN ベースの NLP 等も含め昔から利用されているものです *4。BERT 等 Transformer ベースのモデルでも bi-directional な入力系列を利用しており、具体的には fig 4 のように各 layer において各ノードが一つ前の layer の全ノードに依存するという表現で bi-directional という機構を表現しています。

f:id:guchio3:20200227081424p:plain
fig 4. BERT の bi-directional 性の表現 (ref)

pre-training
大規模なデータセットで pre-training しておき、それを finetune する形で転移学習するという手法はもはや当たり前の如く様々なケースで利用されています *5NLP の分野でもこの手法は利用されており、pre-training を行う手法として最も一般的なものの一つが言語モデルです。言語モデルの詳細は web 上に記事が転がっているのでそちらを参照して頂きたいですが *6、タスクとしては前 (もしくは後ろ) から順に単語系列を走査していき、走査の度にその次の単語が何になるかを当てるという操作を行います。

上記のようなタスクを行うにあたり、複数層の bi-directional な RNN や Transformer で単純にこれを行うとリークを起こすという問題があります。ELMofig 5 のように、bi-directional な処理が結合するのを最終層のみにすることによってうまくリークが起きないようにしたモデルですが、bi-directional な処理の恩恵を受けられるのが最終層のみという弱みがあります。これを克服するため、BERT では Masked Language Model (MLM) と呼ばれる、文章中のランダムにマスクされた単語を当てるというタスクを提案し、これを解いています。このタスクであれば前方向から処理を行っても後ろ方向から処理を行ってもマスクされている単語はわからないので、複数層の bi-directional な RNN や Transformer を利用することができます。また、この他にも Next Sentence Prediciton という連結された 2 つの文が自然に連結されたものか否かを当てるというタスクの提案もあり、これも bi-drectional な処理に関してリークフリーです。*7

f:id:guchio3:20200227085645p:plain
fig 5. ELMo の例 (ref)

solution 紹介

基本的には筆者のメモ用に solution をまとめます。記載の基準は自分の独断と偏見なので、詳しくは kaggle discussion を参照して頂ければと思います。ここに上位 solution へのリンクがまとまっているので、ここ起点で探すと楽です。

solution 紹介からは説明量が膨大になるので、背景知識の説明は割愛します。また、重複する説明は基本的に二重記載しません

まず、筆者含め数名の solution は直接話を伺ったので少し詳しめに書いています。

guchio3

まず筆者の solution を載せます。処理のパイプラインはだいたい fig 6 に載せたように pre-process, modeling, post-process の三段構成になっており、他の solution も大枠は似たようなものになっています。

f:id:guchio3:20200227083538p:plain
fig 6. solution pipeline

pre-process

  • title, question の分離用に special token [NEW_SEP] を追加して入力系列を形成
    • ex. [CLS] title [NEW_SEP] question [SEP] answer [SEP]
  • Category を text として入力系列に埋め込み
    • ex. [CLS] CAT-TECHNOLOGY title [NEW_SEP] question [SEP] answer [SEP]
    • category を embedding layer に入れて後ほど head の入力に concat するやり方もありますが、text に埋め込むと bert 等が系列を処理する段階で category を加味してこれを行えます。
  • title, question, answer それぞれについて、一定の長さ以上になった場合に先頭、末尾から 1 : 1 の長さで切り取り
    • ex. I have a dream that one day on the red hills of Georgia, the sons of former slaves and the sons of former slave owners will be able to sit down together at the table of brotherhood.
-> I have a dream that + at the table of brotherhood.
  • 上記処理の後、🤗 の tokenizer (encode_plus) を使用
    • sentence を word に分割
    • 一部の word を sub-word へと分割 (ex. "playing" = "play" + "##ing")
    • word -> token_id へとマッピング
    • position_ids, token_type_ids に加えて attention_mask 等の生成
    • etc...

modeling

  • question_body を key とした 5-fold GroupKFold
  • BCE
  • Bert, Roberta (valid score best 2 の snapshots), XLNet の加重平均
    • 1 : 0.5 : 0.5 : 1
  • BERT 族最終層を AVG pooling したベクトルを HEAD の入力に使用
  • question 系と answer 系のラベル用にでそれぞれモデルを作成
    • Q : question_title + question_body + 系列長 512 に満たなかったら answer
    • A : question_title + answer + 系列長 512 に満たなかったら question_body
  • Adam w/ cosine scheduler (3e-5 -> 1e-5)
  • 1 epoch 目は BERT 族部分は freeze してその他の部分を学習 (結構効いた)
  • pseudo labeling (soft + soft and hard + hard)
    • soft は生の予測値
    • hard は予測値に thresholding を行い丸めたもの
    • soft and hard は oof のスコアが良くなるカラムのみ thresholding したもの

post-process

  • oof に対しネルダーミード法をかけ得た閾値によって値を丸める
    • percentile ベース (oof の正解ラベルの下から何%~何%が同じ値かという情報) を使って閾値を初期化
    • oof でスコアが伸びるカラムのみに thresholding を適用
    • submission error を防ぐため、public/private test set それぞれで値の種類数が 1 になる場合は thresholding を適用しない
  • question-type-spelling のみルールベースで値を入れ直す
    • 具体的には host = "english.stackexchange.com" なら 0.5, それ以外は 0.0
    • この notebook を参考に oof で検証して良さそうだったので採用

what did not work

  • batch size を変える (8 がベストだった)
  • sequence size を変える (default の 512 がベスト)
    • pre-train の重みを使うには系列長は 512 にしないと思われがちだが、この制約は positional encoding を表現する matrix のみによるものなので、ここをうまく扱えばどうにでもなる
      • positional encoding matrix を拡張 (拡張部分は初期化後の重みを利用) して 512 以上の系列長を利用
      • question_title, question_body, answer それぞれの先頭で position_id を 0 に初期化することで 512 より長い系列長を利用
      • 逆に 512 より小さい系列長を利用
  • category 以外の meta_features の利用
  • BERT 族の output の扱い方を変える
    • [CLS] token に対応する hidden state を output とする
    • 最終 N 層の系列出力を concat して avg pooling したものを output とする
  • loss function を変える
    • MSE
    • pair loss
    • focal loss
  • data augmentation
    • nlpaug
    • マイナーなラベルの over sampling
  • スペル修正等よくある基本的な前処理
  • 難しいカラム用に expert model を作成

sakami

pre-process

  • 4 種類の truncate (文章の切り取り方) により多様性を捻出
    • pre-truncate (title + question + answer として、先頭から系列長 512 になるように切り取り)
    • post-truncate
    • 文頭、文末を 1 : 1 で使う truncate
    • question より answer を多めに使う truncate
  • target を min-max scaling
    • target の最小値、最大値が 0, 1 でない場合があったので正規化し、また分布をきれいにする

modeling

  • 12 models のアンサンブル (solutionに図が載っているので、詳しくはそちらを参照)
    • LSTM + Universal Sentence Encoder
    • BERT base uncased * 2
    • BERT base cased
    • BERT large uncased * 2
    • BERT large cased * 2
    • ALBERT base
    • RoBERTa base
    • GPT2 base
    • XLNet base
  • head に 1 層の linear でなく、2 層の linear (2 層間に activation 無し) を使用
    • 原理的には同値なはずだが結構効いたらしい... (なんで...)
  • activation に gelu でなく new-gelu を使うと良かったらしい
  • cosine warmup scheduler
    • 最初は lr が小さい状態からある程度急峻に上げ、ピークからだんだん小さくする
  • EMA
  • マイナーなラベルに大きめの weight を適用して学習すると良かった
    zero_inflated = (train_y > 0).mean(axis=0) < 0.1
    positive_weighted = np.tile(zero_inflated, (len(train_y), 1))
    positive_weighted *= (train_y > 0)
    one_inflated = (train_y < 1).mean(axis=0) < 0.1
    negative_weighted = np.tile(one_inflated, (len(train_y), 1))
    negative_weighted *= (train_y < 1)
    train_weights = np.where(positive_weighted + negative_weighted, 2., 1.)

post-process

  • 黄金分割探索*8を使い、値を clipping (rounding)
  • optuna を使って weight を決め、モデルの予測値を加重平均

what did not work

kenmatsu4

key points

  • fig 7 に示すような 4 つの bert-base-uncased のアンサンブル
  • oof で良くなるもののみ post-process
    • rank avg した後に、train の percentile ベースで train の値を入れ込む
  • [CLS] に対応する hidden state と系列出力の avg pooling を concat して head の入力に使用
  • 10-fold MultilabelStratifiedKFold

f:id:guchio3:20200227092549p:plain
fig 7. モデルのバリエーション (ref)

what did not work

  • Stackoverflow の 150,000 sentences を使った pre-training
  • Multi-sample dropout (jigsaw コンペの上位解法)
  • BERT 以外のモデル
    • roberta, albert, xlnet, USE+MLP, LSTM w/ gensim
  • meta_features を embedding して head の入力に concat
  • question と answer でモデルを分け、head への入力時にそれぞれの出力を concat して使用
  • BERT の系列出力を LSTM にかけてベクトルに圧縮
  • BERT 内の BertLayer の内、前半半数を freeze して学習できる表現力を制限
  • custom loss
    • BCE + MSE, focal loss
  • word count features
  • マイナーなラベルの over sampling

次に、上記以外で筆者が覚えておきたいと思った solution を羅列していきます。

1 st

key points

  • pytorch kernel base で始めたらしい
  • multi-sample dropout (つまり彼らはうまくいった...?)
  • encoder (BERT 等の部分) と head で lr をそれぞれ別に設定
    • deserves a paper らしい
  • 全 BertLayer の [CLS] に対応する hidden state を weighted avg
    • weight を正 ^ 計 1 になるよう成約付けし、trainable な parameter に...!
  • stackexchange のデータを使った Masked Language Modeling w/ 6 targets
    • question_score, question_view_count, question_favorite_count,answer_score, answers_count, is_answer_accepted
  • pseudo labeling
    • fold ensemble を pseudo labeling 用の予測値とするのではなく、各 fold 用の pseudo label は各 fold の train model のみ使用
      • fold ensemble すると、各 fold についてその oof を学習に使った model の予測結果を使うことになり、pseudo label される対象が train set と似たデータだった場合に間接的に train を見ているのと同じになる
      • cv 0.414 -> 0.445 とかなり伸びるのに対し lb が伸びないこともあり、リークに気付いた
    • pseudo labeling 用に post-process はしなかった
  • BARTの使用
    • Transformer base のモデルを seq2seq で denoising autoencoder として学習
    • BERT との相関が高くなく、また単体性能も結構良いらしい
  • post-process
    • train の分布に基づいて行ったらしいけどコード読めてないです。。
  • Mag という自作ツールが実験管理に良かったらしい

what did not work

  • stackexchange の meta_features
  • GPT, GPT2 medium, GPT2 large
  • back translation
  • stacking

2 nd

  • target のをカラム数を増やす形で変形して学習
    • 例えば target が  t \in [0, \frac{1}{3}, \frac{2}{3}, 1] の場合、 p(0), p(\frac{1}{3}), p(\frac{2}{3}) が予測対象となり、 p(t) はこのカラムの値  v t を上回っているという意味。こう設計すると  t は以下の式 (\ref{eq2}) で復元可能
    • 単純に OHE の形で softmax 関数等使って学習すると大小関係が表現し辛いので良くないらしい
\displaystyle{
  \begin{align}
      t(0) &= 1, \\
      t\left(\frac{1}{3}\right) &= p(0) - p\left(\frac{1}{3}\right), \\
      t\left(\frac{2}{3}\right) &= p\left(\frac{1}{3}\right) - p\left(\frac{2}{3}\right), \\
      t(1) &= p\left(\frac{2}{3}\right) - 0, \\ \\

      t &= 0 \cdot t(0) + \frac{1}{3} \cdot t\left(\frac{1}{3}\right) + \frac{2}{3} \cdot t\left(\frac{2}{3}\right) + 1 \cdot t(1) \tag{2}\label{eq2}
  \end{align}
}
  • 5-fold GKF に以下 2 点の工夫を追加
    • question : answer が 1 : N (N > 1) のものについて、validation の度に 100 sample とってきて median を計算 (これの解釈が正しいかちょっと自信ないです)
    • question_type_spelling カラムを除外
      • このカラムは偏りがひどく、予測値が安定しない
  • fig 8 のような Dual Transformer と Siamese Transformer の二形式のモデルをアンサンブル
    • Siamese Transformer の weighted average 用の重みは trainable
    • 最終的に作ったモデルは 5 種類
      • 2x dual roberta-base
      • dual roberta-large (2x 256 tokens)
      • dual xnet-base
      • siamese roberta-large with weighted averaged layers

f:id:guchio3:20200227092832p:plain
fig 8. Dual Transformer と Siamese Transformer (ref)

4 th

  • link1, link2, link3

  • Original -> Spanish -> English の inverse translation による data augmentation

  • fig 9 の構造のネットワークに BERT、XLNet を使ったもののアンサンブル
    • output は concat([1, 2]) + 3 + 4 で計算
  • 下記コードで post-process
    • bin を 1~200 個で切ったときに oof で一番良いものを使用
    • 個人的には bin の幅ではなく bin の数の方を可変にしているのが珍しくて良いなぁと思った
    def metric3(ytrue,ypred):

        import copy
        y = copy.deepcopy(y_pred) #make_copy
        list_of_max_voters=[] #  list_of_max_voters[i] = how many voters did label the data (instead of 90 for all the columns)
        for i in (range(y_pred.shape[1])):
            best_score= 0 #initilize score for the the column i
            best_max_voters=1 #
            history_score=[]
            for max_voters in range(1,200):
                y[:,i]= (y_pred[:,i]//(1/max_voters))*(1/max_voters)
                score = spearmanr(y_true[:, i], y[:, i]).correlation
                history_score.append(score)
                if score > best_score:
                    best_score = score
                    best_max_voters= max_voters
            list_of_max_voters.append(best_max_voters)

            y[:,i]= (y_pred[:,i]//(1/best_max_voters))*(1/best_max_voters)
        return np.mean([spearmanr(y_true[:, ind], y[:, ind]).correlation for ind in range(y.shape[1])]),list_of_max_voters

f:id:guchio3:20200227093010p:plain
fig 9. 4th のモデル構造 (ref)

8 th

  • BERT 等各 model の最終層に 1 layer (ex. BertLayer) の random initialize な層を追加
  • roberta-base に関して def cln(x): return "".join(x.split()) で 0.005 程伸びたらしい
  • external data を使った pseudo labeling
    • 論文に着想を得ているらしい

9 th

  • modeling において、BERT 等の最後の 4 layers を freeze し、その output を concat した後 1-layer LSTM に入力して sequencial output を作成
  • blending において、各モデルで target 毎に得意不得意が異なることに着目し、target 毎に weight を変えて blend

10 th

  • link1, link2
  • BCE を weight と共に使用
    • weight をどうやって決めたかが謎
  • post-process に k-means を利用
    • oof と test をあわせて適用し、適用先のカラムは一部のみ

15 th

  • fast.ai を使っていい感じに lr をチューニングしたらしい

16 th

  • target を rank 化して 0 - 1 に scaling (+0.01 - 0.05)
  • ランダムな token を "[PAD]" に変えて augmentation

18 th

  • データセットの作成に関する仮説に基づいた solution で、おもしろいので是非一度仮説を見てみて下さい
  • modeling の際に、question_title, question_body, answer の入力に対応する箇所の出力それぞれについて 3 種類の pooling を行い head に入力 (fig 10 参照、ちょっと解釈があっているか自信ないです)

f:id:guchio3:20200227093128j:plain
fig 10. 18th のモデル構造 (ref)

23 th

  • post-process を LGBM の max_depth = 1, lr = 0.1 の stacking で表現
    • ノード内で各カラムについて bin を切る機能を利用
  • margin ranking loss + BCE (1 : 1) で 0.002 伸びたらしい (筆者は効かなかった...)

まとめ

本記事では kaggle の Google QUEST Q&A Labeling コンペの概要と筆者が気になった各種 solution を個人的なメモとして紹介しました。前回まとめた細胞コンペも非常に学びが多かったですが、このコンペでも多くの学びを得ることができ、またゲームとしても非常に楽しむことができたと思っています。

各種 BERT の派生形モデルや Universal Sentense Encoder 等、ちゃんとわからず使っていたものもあったので、このあたりは復習して、もし気が向いたら記事にしたいと思います。あと、jigsaw コンペは復習したほうが良いなぁと思いました。

次こそ金圏...

*1:ex. yuko ishizaki san, takamichi toda san, Y.Nakama san, jshirius san

*2:これとかこれとか

*3:submission error については結構 discussion も議論されていましたが、コンペ中に明確な対処法が共有されることはなかったので、submission error に苦しんで終わる参加者もいた事は想像に難くないです。

*4:ex. Schuster and Paliwal, 1997

*5:ex. 以前紹介した細胞コンペ

*6:これとかこれとか

*7:このスライドで視覚的にわかりやすく説明してくれてます。

*8:わかりやすい説明