情報系院生のノート

情報技術系全般,自分用メモを公開してます。

Suphx: Mastering Mahjong with Deep Reinforcement Learning

メタ情報

著者

  • Junjie Li (Microsoft Research Asia)
  • Sotetsu Koyamada (Kyoto University)
  • Qiwei Ye (Microsoft Research Asia)
  • Guoqing Liu (University of Science and Technology of China)
  • Chao Wang (Tsinghua University)
  • Ruihan Yang (Nankai University)
  • Li Zhao (Microsoft Research Asia)
  • Tao Qin (Microsoft Research Asia) -Tie-Yan Liu (Microsoft Research Asia)
  • Hsiao-Wuen Hon (Microsoft Research Asia)

発表

リンク

スライド

Zennメモ

論文読む時に書いた汚いメモです。 精読するときに役に立つかもです。

Suphx: Mastering Mahjong with Deep Reinforcement Learning

説明

  • Microsoftが開発した麻雀AI
  • 強化学習で麻雀は非常に難しい
  • 天鳳(オンライン麻雀)のtop0.001%に位置
  • 麻雀AIのSOTA

感想

人間を超えた麻雀AIの論文。ゲームAIらしく44GPUとかいう一般人には無理な学習方法を取っている。

個人的にはなぜ教師あり学習を事前学習として選んだのかが理解できていない。 オフライン強化学習とかBCの手法は沢山あるのに、なぜそれらを使わなかったのだろうか。

あと学習が天鳳のトップplayerなのに対して、評価も天鳳のトップplayerだったので、もしかして天鳳のトップplayerメタなAIができているのではないか少し気になった。(まあ天鳳のトップメタだとしても殆どの麻雀playerには勝てるだろうが)

GPUクラスタの使用状況をログインノードから一発で確認するシェルスクリプト

はじめに

うちの研究室にはGPUクラスタがありますが、各GPUノードの使用率を見るには、各GPUノードにsshしてnvidia-smiをしなければいけません。

これでは不便なので、ログインノードから一発で確認できるシェルスクリプトを作成・公開しました。

cluster-smi

GitHubで公開しました。

github.com

使用方法

リポジトリのREADME.mdにも書いてあるのですが簡単に

submodule (prettytable.sh)があるので、それを取得し、cluster-smi.shを実行するだけです。

もしパスを通したかったら、cluster-smi.shをどこかパスの通っている場所にシンボリックリンクを貼って下さい。

(もちろん、.bashrcに追加でもOKです)

技術的な話

単純に各GPUノードにsshしてnvidia-smiを実行して、その情報を切り取っているだけです。

シンボリックリンク

cluster-smi.shprettytable.shを参照しているので、cluster-smi.sh だけでシンボリックリンクを貼ると参照できなくなります。

そこで以下の記事を参考に、シンボリックリンクを解決しながら絶対パスを取得しています。

シェルスクリプトでシンボリックリンクを解決しながらその絶対パスを取得するには | hydroculのメモ

なので、 cluster-smi.shだけシンボリックリンクを貼っても動くわけです。

並列化

それぞれのノードから情報を取ってくる部分を関数化し、単純に&付けるだけで並列化しています。

bashのfor文の中身を並列処理させる · sacre

なので、結果がバラバラに帰ってきます。 これをなんとかしたかったのですが、シェルスクリプトでは厳しかったです。(もし方法があれば教えて頂きたい…)

なぜシェルスクリプト

移植性が高いからです。 完全にこの本に影響を受けています。

www.ohmsha.co.jp

MacでNTFS(windows)を書き込み可能でマウントする方法

はじめに

Windowsでフォーマットした外付けSSDMacに差したら、なんと読み取り専用でマウントされました。 これでは不便なので調べると、怪しい有料ソフトがちらほら...

何とかならないかと調べると、どうやらターミナルからコマンドで読み書きマウントできるみたいです。

外付けHDDを探す

/devのどこかにあるのですが、探し方としてMacdiskutilコマンドが便利そうです。 (Linuxならfdisk -lが便利なのですが、Macにありませんでした)

$ diskutil list
/dev/disk0 (internal, physical):

....

/dev/disk3 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *1.0 TB     disk3
   1:       Microsoft Basic Data Elements                892.8 GB   disk3s1
   2:           Linux Filesystem                         107.4 GB   disk3s2

目的の1TBの外付けHDDは/dev/disk3s1にあることがわかりました。

マウント

マウント先を作成

マウント先のフォルダと適当な場所に作成します。 私は/Volumes/以下に作成しました。

sudo mkdir /Volumes/ExternalSSD

デフォルトのマウントをアンマウント

Macに読み取り専用で自動マウントされている領域を剥がします。

sudo umount /Volumes/対象のSSD

NFTS読み書き可でマウント

先程disk3s1をマウントしたいことが分かったので1つ目の引数に指定します。

マウント先を2つ目の引数に指定します。

sudo mount -t ntfs -o nobrowse,rw /dev/disk3s1 /Volumes/ExternalSSD

Finderで表示

open /Volumes/ExternalSSD

アンマウント

バイスを抜くときはumountコマンドを使用します。

sudo umount /Volumes/ExternalSSD

参考

MacでNTFS形式のUSB外付けHDDに書き込む方法 - karakaram-blog

なのでMacNTFSをわざわざ読み取り専用で自動マウントするんですかねぇ、、、

Decision Transformer: Reinforcement Learning via Sequence Modeling

メタ情報

著者

  • Lili Chen (UC Berkeley)
  • Kevin Lu (UC Berkeley)
  • Aravind Rajeswaran (Facebook AI Research)
  • Kimin Lee (UC Berkeley)
  • Aditya Grover (Facebook AI Research)
  • Michael Laskin (UC Berkeley)
  • Pieter Abbeel (UC Berkeley)
  • Aravind Srinivas (UC Berkeley)
  • Igor Mordatch (Google Brain)

発表

リンク

スライド

Zennメモ

論文読む時に書いた汚いメモです。 精読するときに役に立つかもです。

Decision Transformer: Reinforcement Learning via Sequence Modeling

説明

感想

Transformer(GPT)で強化学習してみました系論文。 有用性の検証のためにいろんな実験を行っているが、なにを示したいのかイマイチ理解できず、実験の意図がわからない部分が多かった。

おそらく性能としては現行のTD法を用いた手法がまだ強いのではと思う。 ただ、長期的なタスク等に関してはDTが強い印象を受けました。

松尾研スプリングセミナー2021からいろいろ抜粋させて頂きました。 非営利なので多めに見ていただけるとたかをくくっていますが、もし問題がございましたら、お手数ですがご連絡ください。

Kaggle SETI 59th solution

はじめに

コンペ途中リークが発覚し、データセットリセットがあるなど波乱のコンペでした。

また、今回も@kambe さんと参加しました。 おかげさまでこのコンペでKaggle Expertになることが出来ました! どうもありがとうございました!

SETIコンペについて

信号のスペクトログラムが与えられ、その中にある異常値を検出するコンペです。 宇宙船から送られてくる大量のデータから異常な信号を検知し、地球外生命体を見つけましょうという内容ですね。 (このコンペで使用されたデータはシミュレータから生成された人工データみたいですが)

Pipeline

推論のパイプラインの図を示します。

f:id:tontainoti:20210819234947j:plain
seti pipeline image

Augmentation

あまり時間がなく、augmentationを十分に調査できていません。 とりあえずこの4つと、mixupが入っています。 どれが効いてるのかとかはわかってません。

  • vflip
  • shift_scale_rotate
  • motion_blur
  • spec_augment

albumentationsでSpecAugを扱えるようにしたかったので、以下のようにクラスを作りました。

class SpecAugment(ImageOnlyTransform):
    def __init__(self, alpha=0.1, **kwargs):
        super(SpecAugment, self).__init__(**kwargs)
        self.spec_alpha = alpha

    def apply(self, img, **params):
        x = img
        t0 = np.random.randint(0, x.shape[0])
        delta = np.random.randint(0, int(x.shape[0] * self.spec_alpha))
        x[t0:min(t0 + delta, x.shape[0])] = 0
        t0 = np.random.randint(0, x.shape[1])
        delta = np.random.randint(0, int(x.shape[1] * self.spec_alpha))
        x[:, t0:min(t0 + delta, x.shape[1])] = 0
        return x

RandAugとかやりたかったです。

Test Time Augmentation (TTA)

今回はaugmentationが4つなので16回のTTAを行うことにしました。 16という数字の決め方なのですが、TTAをするにあたって、画像1毎に対して最低でもすべてのaugmentationを1回以上かけてほしい、というのがあります。

例として、TTAが16回、augmentationが4種類、各augmentationが実行される確率$p=0.5$のとき、最低1回以上すべてのaugmentationが実行される確率は以下の式で計算できます。

$$ \left(1 - \left( \frac{1}{2} \right)^{16} \right)^{4} = 0.99... $$

TTA: 4, Augmentation: 4

$$ \left(1 - \left( \frac{1}{2} \right)^{4} \right)^{4} = 0.77... $$

Resizing Network

  • notebook

SETI - Learned Image Resizing | Kaggle

  • paper

[2103.09950] Learning to Resize Images for Computer Vision Tasks

この上のリンクのnotebookが最初に投稿した人だと思うのですが、(最近のkaggleではノートブックの丸コピが横行しています…) このモデルが一番スコアが良かったです。

本当は画像をリサイズせずにそのまま突っ込むのが良いとは思うのですが、うちの研究室のGPUが貧弱なのでバッチサイズを下げる必要があります。

そうすると、今回のような不均衡データ(9:1)では1つのバッチに1つのクラスしか出ないという状態が発生するため、学習が進みません。

なので、このモデルを使ってできるだけ大きい画像で訓練するようにしました。(リサイズ先の大きさはefficientnetv2の元論文の通りです)

学習

このコンペは一度データセットリセットがかかり、データセットが一新しました。 なので、前のリークしたデータは、事前学習として用いることにしました。 これをすることで、LB、CV共にスコアが微増しました。

また、モデルの事前学習はfold-out、fine-tuningは4Fold CVです。

モデル

モデルがを大きくすると学習しない問題にぶつかりました。(おそらく学習率とスケジューラーが悪い) いろいろモデルを試しましたが(nfnet, volo, swin...) 最終的にスコアの良かったefficientnetv2_s, mを使うことにしました。

また、最終出力層を1にしてBinary cross entropy lossにするのではなく、出力層を2にして、cross entropy lossを取るほうがスコアが良かったです。

これは何故なのかよく分かっていないですが、softmax関数にするとlogitsのスケールに依存しないからなのかと思ってます。 (出力層1だとsigmoid関数で確率を計算するので、sigmoid関数の値域に合わせたスケールの出力が求められる)

その他試したこと

感想

1位の解法が完璧で度肝を抜かれました。 この背景を取り除く方法などは、他のスペクトログラムを扱うコンペなら使えるアイディアだと思います。

SETI Breakthrough Listen - E.T. Signal Search | Kaggle

もう数日あれば銀圏行けた自信があるくらい今回も時間が足りなかったです。 また、コンペ中に、画像サイズに比例してスコアが上がっているのを感じたとき、上位陣以外、Kaggleは結局マシンスペックがあればメダル圏は入れるんじゃないかと思い初めてしまい、少しモチベが下がりました…

Kaggle Coleridge 52nd solution

はじめに

今回Kaggleに参加して初めてメダルを取ることができました。 Public scoreでは全然メダルに届いていなかったので、半ば諦めていましたが 大幅shakeがあり、たまたま銀メダルを取ることができました。

簡単にですが、その解法を公開します。

Coleridgeコンペについて

論文内で示されているデータセット名を当てるコンペです。 渡されたデータは論文のテキストのみです。

validationの分け方

今回のコンペでは、学習セットに130ほどのデータセット名(ターゲット)がありますが、テストセットには学習に出てこないデータセット名が含まれています。

そのため、validationをちゃんと分けるには、それぞれデータセット名の重複なしで分けなければいけません。 なので、幅優先探索を実装して、データセット名が重複しないように8:2で分けました。

本当はk-foldに分けたかったですが、組み合わせの数的に無理でした。

しかし、違う文字列で同じデータセット名を指している場合があり、完全に切り離すのは難しく、実際はいくつか重複があったと思われます。

Pipeline

まず推論のパイプラインの図を示します。

f:id:tontainoti:20210707135804j:plain
coleridge 52nd solution pipeline

文章を短く区切って、dataset名が存在するか文章を2値分類して、カーネルにもあったMLMモデルでそれがデータセット名なのかを予測します

そして、1つの論文に対して予測されたデータセット名のリストに対してjaccard係数を計算し、0.75以上の文章をフィルタリングします。(文章が短い方を残す)

最後に、これもカーネルにあったexternal datasetsと予測を結合します。 いろいろ試したのですが、シンプルにcsvに存在したらそれを使用し、無ければBERTの予測を使うというやり方が一番スコアが良かったです。

Shorten sentence

これはカーネルにあったものをそのまま流用しています。

Classifier

この分類器を使うアイディアは、一緒に参加した研究室の先輩のアイディアです。 きちんと検証してないのであれですが、この分類器が思ったより効いており、チームの上位subの殆どがこの分類器を入れたものでした。

入力文書に、データセット名が含まれているがどうかを予測します。 シンプルに2値分類です。 また、追加情報として、BERTのfc層へ、BERTからの特徴量と、単語数・大文字の単語数・単語の大文字率をconcatしてます。

MLM

以下のカーネルのほぼ丸パクリです。

[Coleridge] Predict with Masked Dataset Modeling | Kaggle

Jaccard filter

これもカーネルにあったやつです。 全部の予測が終わってから、[set(データセット名候補1, ...), set(),...,set()]の状態になったリストを渡すと、フィルタリングしてくれます。

def jaccard_filter(org_labels, threthold=0.75):
    assert isinstance(org_labels, list)

    filtered_labels = []
    for labels in org_labels:
        filtered = []

        for label in sorted(labels, key=len):
            label = clean_text(label)
            if len(filtered) == 0 or all(jaccard(label, got_label)
                                         < threthold for got_label in filtered):
                filtered.append(label)

        filtered_labels.append('|'.join(filtered))

    return filtered_labels

試したこと

  • DiceLoss, FocalLoss等の不均衡データに強いロス: スコア下がった
  • NER: 有効じゃなさそうだった
  • SciBERT: 変わらなかった
  • external datasets csvを増やす: 余計な文字列がヒットしてスコア下がった
  • BERT→Electra: スコア下がった
  • CONNECTION_TOKENの変更: 対象の文書が増えてスコア下がった
  • ビームサーチでk-fold: 計算時間的に厳しかった

感想

取り組むのが遅かったというのもあり、第4位の解法と同じアイディアを思いついたのですが、結局ローカルのCVが悪くsubしませんでした。これをもう少しちゃんと取り組んでれば賞金圏行けたと思うと非常に悔しいです。

あとはもう一つメダルを取って、早くKaggle expertになりたいです。

第4位の解法

当時のアイディア

以下は当時Githubのissueに挙げてた原文(悔しいので載せちゃいます)

単純にtitle_caseだけの単語列は多い

しかし、title_case + (略)みたいなパターン(例: Alzheimer's Disease Neuroimaging Initiative (ADNI))はデータセットを指している場合が多い印象

これをルールベースで抜きたい
正規表現できた

[A-Z]{1}['a-z]+\s([a-z]{1,3}\s|[A-Z]{1}['a-z]+\s)+\([A-Z]+\)

pytorchのモデルをスクリプトごと違うGPUで実行したいとき

はじめに

Pytorchのnn.Moduleはto()とかcuda()テンソルを違うGPU番号へ移すことができますが、すべてのテンソルが移ってくれるわけではありません。 nn.Moduleの実装のすべてを移さなくてはいけないのです。これはだるい。

解決方法

方法は至ってシンプルです。 実行するときに、環境変数を以下のよう変えて、1つのGPUしか見えなくしてあげましょう。

プログラムでcuda:0と書くと、実際には2番目のGPUに繋いでくれます。

$ CUDA_VISIBLE_DEVICES=2 python main.py

kaggle datasets api 使い方

はじめに

kaggle datasets apiの使い方が少し癖あったので、備忘録

初期化

最初にフォルダを初期化してあげる必要があります。

  • フォルダを登録
kaggle datasets init -p /path/to/datasets

tiitleidを任意の値に変更します(titleは6~50文字)

vim path/to/datasets/dataset-metadata.json
  • 作成
kaggle datasets create -p path/to/datasets

追加アップロード(バージョニング)

単一ファイルの場合

kaggle datasets version -p /path/to/dataset -m "comments"

フォルダ階層になっている場合

複数ファイルの場合は圧縮形式の指定が必要です。

kaggle datasets version -p path/to/datasets -m "comments" --dir-mode zip

dir-mode

createコマンドでもフォルダ階層になってる場合は--dir-mode必須です

dir-modeは3種類あります

  • skip
  • zip
  • tar

zipとかは圧縮に時間取られたりするので、細々していなければskipが一番はやいです。

追記:
tarにすると、アップデート後もフォルダが.tar形式になるみたいなので、zip一択ですね

参考

私がよく使う kaggle api command まとめた - かえるのプログラミングブログ

MASTERING ATARI WITH DISCRETE WORLD MODELS (DreamerV2) 解説

メタ情報

著者

  • Danijar Hafner (Google Research)
  • Timothy Lillicrap(DeepMind)
  • Mohammad Norouzi (Google Research)
  • Jimmy Ba(University of Toronto)

発表

  • ICLR 2021

リンク

スライド

Zennメモ

論文読む時に書いた汚いメモです。 精読するときに役に立つかもです。

MASTERING ATARI WITH DISCRETE WORLD MODELS (DreamerV2)

説明

  • World Modelの派⽣系のDreamerの2代⽬
  • 画像⼊⼒から学習した世界モデルの潜在空間内のみで学習
  • 同じ計算資源・サンプル数でIQN, Rainbow(モデルフリー)を凌駕

感想

Worldモデルは、生成モデルとモデルベース強化学習の両方の知識がないとわからないので難しいです。 dynamics backpropの部分はよくわかっていないので誰か教えて下さい。

あと、生成モデルと強化学習の部分の説明は、松尾研スプリングセミナー2021からいろいろ抜粋させて頂きました。 非営利なので多めに見ていただけるとたかをくくっていますが、もし問題がございましたら、お手数ですがご連絡ください。

オンプレGPU環境でmlfowのサーバを立てる

はじめに

機械学習モデルを複数で開発するときがあるとおもいます。 しかしデフォルトだと、各ユーザのフォルダにmlrunsフォルダが作成されており、自分で実行した分しか見られません。

そこでdocker-composeを使ってMLFlow Tracking Serverを立てて、param, metric, artifactを保存する方法を見つけたので共有します。

mlflowの使い方については以下記事へ。

tmyoda.hatenablog.com

立て方

qiita.com 上記事を丸コピしたリポジトリを作成したので、そちらをクローンします。

github.com

以下を実行するとサーバが立ち上がります。

docker-compose up -d

次にpythonコードに以下のMLFlowの設定を入れ込み、リモートサーバにログを置くように設定します。

# IP address of the server where the docker-compose is running.
SERVER_IP = "192.168..."

# Set the tracking uri.
mlflow.set_tracking_uri(f"http://{SERVER_IP}:5000")

# Specify the information to the environment variable.
os.environ["MLFLOW_S3_ENDPOINT_URL"] = f"http://{SERVER_IP}:9000"
os.environ["AWS_ACCESS_KEY_ID"] = "minio-access-key"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minio-secret-key"

すると、docker-composeが立ち上がっているサーバにログが保存されます。

たったこれをするだけで、保存先がリモートになってくれます! ちなみに、いつものWebのUIにアクセスするにはhttp://{SERVER_IP}:5000にブラウザでアクセスすれば見ることができます。

また、docker-compose.yamlを見れば分かる通り、セキュリティがガバガバなので、必ずローカル内で使用し、適宜パスワード等は変更してください。

guiで削除したrunsを完全削除したい

mlflowの良くないところなのですが、guiで削除してもローカルに実体は残っています。 なのでそれをきれいにするコマンドを以下に示します。

方法としては、mlflowコンテナにアタッチして、mlflow gcコマンドを使用します。

まずdocker psコマンドで対象のコンテナを探します。

docker ps

CONTAINER ID   IMAGE         COMMAND                  CREATED       STATUS          PORTS                    NAMES
9b1c5e666fe5   mlflow        "mlflow server --bac…"   2 weeks ago   Up 11 minutes   0.0.0.0:5000->5000/tcp   mlflow_server_mlflow_1
d35dfccd36f9   mysql:5.7     "docker-entrypoint.s…"   2 weeks ago   Up 2 weeks      3306/tcp, 33060/tcp      mlflow_server_mysql_1
9609eb4519d5   minio/minio   "/usr/bin/docker-ent…"   2 weeks ago   Up 2 weeks      0.0.0.0:9000->9000/tcp   mlflow_server_minio_1

この場合、9b1c5e666fe5が対象のコンテナです。

次に以下のコマンドでローカルから実体を完全削除します。

docker exec -it 9b1c5e666fe5 /bin/bash
mlflow gc --backend-store-uri 'mysql://mlflowuser:mlflowpassword@mysql:3306/mlflowdb'

最後にCtrl + p をおしてからCtrl + qで抜けて完了です。