Google Colab をSSH と VS Code で使う

#0.Intro

Colab上のJupyter Notebookも十分使いやすいですし、特にデータ分析の段には不可欠なものですがコーディングはやはりVS Codeの方が作業しやすいです。
ですので、VS CodeでColabに接続し開発できないものかと調べてみました。結果Google Colab へSSH で接続。kaggleコマンドでデータの取得からsubmit。当初の目的通りコーディングはVS Codeで行うという素晴らしい環境で出来上がりました。
このようにColabを通常のLinuxサーバーの様に使えるというのは非常に良いですね。

<font color="Red">※2022/6/1追記</font>
無料での[Colaboratory よくある質問](https://research.google.com/colaboratory/faq.html?hl=ja)の制約事項に「リモート デスクトップまたは SSH の使用」が追加されまています。よって本手法は現在では違反になる可能性があります。
有料版の制約事項にはSSHについての記載が見当たりませんが、当該手法の実施は自己責任にしてお願いいたします。

#1.ColabへSSH接続

ここを参考にしました。
https://stackoverflow.com/questions/48459804/how-can-i-ssh-to-google-colaboratory-vm/63186681#63186681

1._NGROK_というものを使います。後で必要になってくるのでアカウントを作成してください。
https://ngrok.com/

2.Google Colab を開きます。ランタイムをGPUに設定します。(GPU使いたいですよね？)

3.ColabのJupyter Notebookから以下のコマンドを順に実行していきます。一気に一つのセルで実行するのではなく、以下のようにセルを分けると問題が起きた時やり直しがしやすいです。

```
import random, string, urllib.request, json, getpass
```

```
#Generate root password
password = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(20))
```
```
#Download ngrok
! wget -q -c -nc https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
! unzip -qq -n ngrok-stable-linux-amd64.zip
```
```
#Setup sshd
! apt-get install -qq -o=Dpkg::Use-Pty=0 openssh-server pwgen > /dev/null
```

Setup sshdの段階で


>invoke-rc.d: could not determine current runlevel
>invoke-rc.d: policy-rc.d denied execution of start.

というエラーが出る場合がありますが、再度実行するとエラーが消えました。(この辺キチンと調べて無くてすいません)

```
#Set root password
! echo root:$password | chpasswd
! mkdir -p /var/run/sshd
! echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
! echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
! echo "LD_LIBRARY_PATH=/usr/lib64-nvidia" >> /root/.bashrc
! echo "export LD_LIBRARY_PATH" >> /root/.bashrc
```

```
#Run sshd
get_ipython().system_raw('/usr/sbin/sshd -D &')
```


先程登録したNGROKのサイトの以下のところでアクセストークンを取得してください。
https://dashboard.ngrok.com/auth/your-authtoken

以下のように変数にアクセストークンを格納します。

```
authtoken="Your Authtoken" 
```


```
#Create tunnel
get_ipython().system_raw('./ngrok authtoken $authtoken && ./ngrok tcp 22 &')
```

```
#Get public address and print connect command
with urllib.request.urlopen('http://localhost:4040/api/tunnels') as response:
  data = json.loads(response.read().decode())
  (host, port) = data['tunnels'][0]['public_url'][6:].split(':')
  print(f'SSH command: ssh -p{port} root@{host}')

#Print root password
print(f'Root password: {password}')
```
ここまで実行するとJupyter Notebook上に

```
SSH command: ssh -p12312 root@0.tcp.ngrok.io
Root password: XXXXXXXXXXXXXXXXXX
```
という風にsshのログイン時のコマンドとColob上のルートのパスワードが表示されます。

ではsshで接続します。

#2.SSHでの作業
1.無事接続が確立したら、何をするにも必要なのでviをインストールしておきます。

```
root@XXXXXX:~# apt-get install vim
```

2.GPUが利用可能か確認しておきます。

```
root@XXXXXX:~# python3

>>> from tensorflow.python.client import device_lib
2020-09-02 22:26:16.960388: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcudart.so.10.1
>>> device_lib.list_local_devices()
2020-09-02 22:26:23.207957: I tensorflow/core/platform/profile_utils/cpu_utils.cc:104] CPU Frequency: 2300000000 Hz
2020-09-02 22:26:23.208291: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x42470a0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-09-02 22:26:23.208412: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
2020-09-02 22:26:23.215774: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcuda.so.1
2020-09-02 22:26:23.386677: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:982] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-09-02 22:26:23.387365: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x46522a0 initialized for platform CUDA (this does not guarantee that XLA will be used). Devices:
2020-09-02 22:26:23.387400: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Tesla P4, Compute Capability 6.1
2020-09-02 22:26:23.388719: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:982] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-09-02 22:26:23.389270: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1716] Found device 0 with properties:
pciBusID: 0000:00:04.0 name: Tesla P4 computeCapability: 6.1
coreClock: 1.1135GHz coreCount: 20 deviceMemorySize: 7.43GiB deviceMemoryBandwidth: 178.99GiB/s
2020-09-02 22:26:23.389354: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcudart.so.10.1
2020-09-02 22:26:23.600688: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcublas.so.10
2020-09-02 22:26:23.725817: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcufft.so.10
2020-09-02 22:26:23.741531: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcurand.so.10
2020-09-02 22:26:24.008293: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcusolver.so.10
2020-09-02 22:26:24.020604: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcusparse.so.10
2020-09-02 22:26:24.517119: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcudnn.so.7
2020-09-02 22:26:24.517435: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:982] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-09-02 22:26:24.518151: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:982] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-09-02 22:26:24.518654: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1858] Adding visible gpu devices: 0
2020-09-02 22:26:24.522699: I tensorflow/stream_executor/platform/default/dso_loader.cc:48] Successfully opened dynamic library libcudart.so.10.1
2020-09-02 22:26:28.588414: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1257] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-09-02 22:26:28.588472: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1263]      0
2020-09-02 22:26:28.588486: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1276] 0:   N
2020-09-02 22:26:28.592436: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:982] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-09-02 22:26:28.593222: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:982] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-09-02 22:26:28.593755: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1402] Created TensorFlow device (/device:GPU:0 with 6960 MB memory) -> physical GPU (device: 0, name: Tesla P4, pci bus id: 0000:00:04.0, compute capability: 6.1)
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 12426558256636053373
, name: "/device:XLA_CPU:0"
device_type: "XLA_CPU"
memory_limit: 17179869184
locality {
}
incarnation: 12775530158578110193
physical_device_desc: "device: XLA_CPU device"
, name: "/device:XLA_GPU:0"
device_type: "XLA_GPU"
memory_limit: 17179869184
locality {
}
incarnation: 1789109267842192629
physical_device_desc: "device: XLA_GPU device"
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 7298761376
locality {
  bus_id: 1
  links {
  }
}
incarnation: 12382694716029985312
physical_device_desc: "device: 0, name: Tesla P4, pci bus id: 0000:00:04.0, compute capability: 6.1"
]
```
利用できそうです。


3.kaggleのコマンドを実行する準備として設定ファイルを作成します。
※kaggleのコマンドそのものは既にインストールされています。
※そもそもkaggleのAPIキーの取得がまだの方はもちろんそちらを先に。

```
root@XXXXXX:~# mkdir .kaggle
root@XXXXXX:~# cd .kaggle/
root@XXXXXX:~/.kaggle# vi kaggle.json
```
kaggle.jsonは以下のような形式ですのでそのまま転記します。

```
{"username":"XXXXXX","key":"XXXXXXXXXXXXXXXXXX"}
```

簡単にGPUが試せる[Digit Recognizer](https://www.kaggle.com/c/digit-recognizer)を解く準備をしてみます。

```
#作業ディレクトリを作成
root@XXXXXX:~# mkdir Digit_Recognizer
root@XXXXXX:~# cd Digit_Recognizer/
root@XXXXXX:~/Digit_Recognizer# ls

#コンペ一覧を表示
root@XXXXXX:~/Digit_Recognizer# kaggle competition list

#Digit Recognizerのデータを取得
root@XXXXXX:~/Digit_Recognizer# kaggle competitions download -c digit-recognizer

#訓練データとテストデータを解凍
root@XXXXXX:~/Digit_Recognizer# unzip test.csv.zip
root@XXXXXX:~/Digit_Recognizer# unzip train.csv.zip
```

#3.VS CodeからColabへ接続
1.VS Codeの拡張機能として「Remote - SSH Extension」をインストールします。

2.ssh configを作成します。
_Hostname_と_Port_は先にSSH接続したときのアドレスを参照。
_User_ はroot
_IdentityFile_は先の設定の通り、このままで。

```
Host remotessh
  Hostname 2.tcp.ngrok.io
  User root
  Port 14564
  IdentityFile /etc/ssh/sshd_config
```

先程作成した作業ディレクトリ

```
/root/Digit_Recognizer
```
へ移動しファイルを作成、コーディングします。
[Digit Recognizer](https://www.kaggle.com/c/digit-recognizer)に公開されているカーネルから適当に拝借すると早いと思います。


#4.コードの実行とsumbit
以下のようにしてSSHの画面からコードを実行。

```
root@XXXXXX:~/Digit_Recognizer# python3 digit_recg.py
```

結果も以下のようにSubmitできます。

```
root@XXXXXX:~/Digit_Recognizer# kaggle competitions submit -c digit-recognizer -f submission.csv -m "Submit from colab with command"
```

![DRコメント 2020-09-03 075003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/66198/8c1cd136-575b-6182-9cbc-528724d2d402.png)

無事にLeaderboardに載ってます。