Self-Hosted Github ActionsとAnsibleで研究室のサーバーへのユーザー追加を自動化する

はじめに

研究室のサーバ係として、サーバー運用を少しでも改善したいというモチベーションから、題目にある機能を実装したいと考えた。

私の研究室のサーバー環境としては、VPNに参加した共同のGPUサーバーが複数台(10台近く)設置されている。

研究室メンバーの共同GPUサーバーに対するアクセスは、サーバー係が各GPUサーバーに対して手動でuseradd <ユーザ名>コマンドを打ち、メンバーにパスワードとユーザー名を渡すことで実現されていた。

見ての通り、このようにサーバー係が各GPUサーバーに対して手動でユーザーを発行するのは、非常にめんどくさいのみならず、ユーザープロビジョニングがなされていない、つまり、誰がどのサーバーにアクセス権があるかわからないというような状態にあるという点で非常に問題であると感じた。

このような背景から、研究室の共同Githubを使って、ユーザーの追加/削除をコードで管理しようという発想にいたった。

概観

今回実装したいと考える機能の全体像は以下のようになる。 f:id:yuichinagapan:20211128022149p:plain

  • 研究室共同のGithubレポジトリにユーザーの情報をまとめて管理しておく。
  • GithubからActionsを走らせることにより、研究室内のGPUサーバー全てに対して一括でユーザーの追加/削除が可能。
  • ユーザーの追加/削除にはAnsibleを用いる。
  • Github Actionsは研究室VPNにあるサーバーでSelf-Hostedで走らせる。

実装

環境構築

1. 全ての共同サーバでのsudo権限をもつAnsibleユーザーを手動で発行

sudo useradd -s /bin/bash -m ansible
sudo passwd ansible
sudo usermod -aG sudo ansible

2. 共同サーバーのうち一つのサーバーにおいてSelf-Hosted Runnerを設置する

self-hosted runnerの設置については、公式ドキュメントを参照すると良い。 注意点としては、1のステップで発行したAnsibleユーザーの配下でself-hosted runnerを設置するということである。

docs.github.com

3. Slef-HostedのサーバーにAnsibleをインストール

sudo apt update 
sudo apt install ansible

4. Slef-Hostedのサーバーからパスワードなしで(i.e.公開鍵認証で)他の共同サーバーに接続できるようにする

Self-Hostedのサーバーで下記のコマンドを実行する。

ssh ansible@<self-hostedのサーバー>
cd .ssh
cat <公開鍵>

公開鍵を何処かにメモっておく。

Self-Hostedのサーバー以外で下記のコマンドを実行する。

ssh ansible@<self-hosted以外のサーバー>
cd .ssh
echo "<Self Hosted runnerの公開鍵>" >>  authorized_keys
chmod 600 authorized_keys

5. Self-Hostedサーバーから他のサーバーにansibleユーザーで接続したのち、sudoになるためのパスワードをGithubのSecretsを使って環境変数に設定する。

[Settings]→[Secrets]→[New Repository Secret]

Key: SUDO_PASSWD
Value: <Ansibleユーザーのパスワード>

コード

GitHub - YuichiNAGAO/server-user-management

ディレクトリ構成

├── .github
│   └── workflows
│       ├── add-users.yml
│       └── delete-users.yml
└── user-management
    ├── add-users.yml
    ├── delete-users.yml
    ├── hosts
    ├── list-users
    │   ├── add-users-list.yaml
    │   └── delete-users-list.yaml
    └── roles
        ├── add-users
        │   └── tasks
        │       └── main.yaml
        └── delete-users
            └── tasks
                └── main.yaml

.github/workflows/*: ユーザーの削除と追加の二つのワークフローを用意

user-management/hosts: ユーザーを追加したいサーバーのIPアドレスを記入

user-management/list-users/*: 追加したいユーザーのリストと削除したいユーザーのリストをyamlファイル形式で保持

add-users-list.yaml

user_info:
  - user_name: "test"
    public_key: "公開鍵"
  - user_name: "test2"
    public_key: "公開鍵"

delete-users-list.yaml

user_info:
  - user_name: "test"
  - user_name: "test2"

操作

ユーザーの追加

  1. 追加したいユーザーの公開鍵を取得する
  2. 公開鍵を入手したら、list-users/配下のadd-users-list.yamlを編集してユーザー情報を追加する。
  3. Github Actionsでadd-users.ymlのワークフローを手動でトリガーする。
  4. ワークフローがエラーなく完了したら、全てのGPUサーバーへのユーザー追加が完了する。

ユーザーの削除

  1. 追加したいユーザー名を取得する。
  2. list-users/配下のdelete-users-list.yamlを編集してユーザー情報を追加する。
  3. Github Actionsでadd-users.ymlのワークフローを手動でトリガーする。誤作動防止のため"delete"と入力しないとワークフローが走らないように設定されている。
  4. ワークフローがエラーなく完了したら、全てのGPUサーバーにおけるユーザーの削除が完了する。*削除のワークフローを実行すると、ユーザーのホームディレクトリも削除されてしまうので、実行には十分注意する。

終わりに

Githubレポジトリのyamlファイルの編集をし、Github Actionsのワークフローをトリガーするだけで、ユーザーの一括追加/削除が可能となった。

mpirunでCPUコア数を超える並列プログラムを実行したいとき

一般に、MPIを使って並列処理をする際には、mpirunというコマンドを使って、複数プロセスの処理を行います。具体的には、以下のように用いることができます。

$ mpirun -np [並列数] [オプション] [実行プログラム]

[並列数]には、並列計算を行うときのプロセスの数が入ります。

試しに、以下の環境でテスト実行プログラムを動かしてみたいと思います。
・CPUコア数の確認↓(Linux)

$ fgrep 'cpu cores' /proc/cpuinfo | sort -u | sed 's/.*: //'
->8

CPUコア数が8であることを確認し、pythonでtestを吐くだけのプログラムを並列処理してみます。
・並列数2で実行

$ mpirun -np 2 python -c "print('test')"
->test
->test

・並列数8で実行

$ mpirun -np 8 python -c "print('test')"
->test
->test
->test
->test
->test
->test
->test
->test

並列数8までは、無事並列プログラムが実行されるようです。
・並列数9で実行

$ mpirun -np 9 python -c "print('test')"
->There are not enough slots available in the system to satisfy the 9 slots that were requested by the application:
...
...

並列処理数がCPUコア数を超えてしまうと、上手く行かないようです。
このような問題には、CPUリソースをオーバーサブスクライブすることを[オプション]で明示してやれば、良いそうです。ちなみに、オーバーサブスクライブとは、ノードの 1 つの物理コアに対して、複数の仮想コアを用意してやることで、パフォーマンスの若干の低下と引き換えにコアの数を増やしてやるという操作のことです。以下のように書いてやります。
・--oversubscribeを加える

$ mpirun -np 9 --oversubscribe python -c "print('test')"
->test
->test
->test
->test
->test
->test
->test
->test
->test

上手くいきました。

リモートのdockerコンテナ上でたてたJupyter notebookにローカルからアクセスできるようにしたい

はじめに

タイトルのように、リモートのDockerコンテナ上でたてたJupyter notebookにローカルのブラウザからアクセスしたいときにどうすればよいかについて書いていきます。DockerコンテナにはすでにJupyter notebookがセットアップされているとします。

今回の状況を簡単に図示すると、以下のようになります。

f:id:yuichinagapan:20210506155355p:plain

手順
1. リモートPC上でDockerコンテナを立ち上げる
remoteuser@remotehost:~$ docker run -it -p [リモート側のポート番号]:[コンテナ側のポート番号] [イメージ名]

Jupyter Notebookではデフォルトのポート番号が8888であるので、コンテナ側のポート番号は8888にしておくと無難かもしれません。

2. Dockerコンテナ上でJupyter notebookを立ち上げる
root@[コンテナID]:/#$ jupyter notebook --ip 0.0.0.0 --port [コンテナ側のポート番号]  --allow-root

ここで--ip 0.0.0.0とするのは、0.0.0.0がマシーン上のすべてのIPアドレスを表すため、任意の別ホストからでもアクセスすることを可能にするという意味を持ちます。これがないとコンテナ上のNotebookに外からアクセスすることはできません。
コンテナ側のポート番号は一つ前のステップで立ち上げた際に用いた番号と一致させます。一つ前で8888とした場合には、このオプションを省くことができます。
コンテナ上でルートユーザーとなっている場合には--allow-rootオプションをつけてやりましょう。

3. ローカルPCからリモートPCへssh接続する
localuser@localhost:~$ ssh -N -f -L [ローカルの側のポート番号]:localhost:[リモート側のポート番号] remoteuser@remotehost

一応オプションについてコメントすると、

-N:リモートコマンドを無効
-f:バックグラウンドでの実行
-L:クライアント→ホストのポート転送の際のアドレス・ポートを指定

4. ローカルPCのブラウザからアクセス

ローカルPCからhttp://localhost:[ローカルの側のポート番号]にアクセスします。このとき以下のようにtokenの入力が求められます。

f:id:yuichinagapan:20210506164505p:plain

ここには、Docker上のコンテナでJupyter notebookを立ち上げたときに得られる出力上のtoken=c8de56fa...の箇所をコピペしてログインします。

Currently running servers:
http://localhost:8888/?token=c8de56fa... :: /Users/you/notebooks

これで、リモートのdockerコンテナ上で立てたJupyter notebookに手元のローカルPCからアクセスできるようになるはずです。

参考:
Running Jupyter on a remote Docker container using SSH | by Lucas Rodés-Guirao | Towards Data Science
リモートサーバのDocker上で起動したJupyter Notebookに他端末からアクセス - Qiita

Person Re-Identificationの全体像を把握してみた

今回、エンジニアアルバイトとして携わっている会社株式会社HULIX | 人流解析でPerson Re-Identificationに関する技術調査を行ったので、その内容を記事にまとめてみました。

この記事では具体的な研究の内容、例えばどのような手法が使われているかなどには深入りせずに、Person Re-Identification(以降Person Re-Id)は何を行っているかと、Person Re-Idを取り入れたシステムは現実的に実装可能なのかという視点で、記事を書いていこうと思います。

Person Re-Idとは

f:id:yuichinagapan:20210308140148p:plain:h100:right Person Re-Id(日本語では人物再同定)とは、簡単にいうと、とあるカメラに映った人物が、別のカメラに映った場合に、その人物同士を同一人物として結びつけるようなタスクのことです。Person Re-Idの実システムへの導入が可能になれば、例えば大型商業施設などの空間における個人を識別し、人々の動線の解析が可能になったりします。

技術的な難しさ

技術的な観点から見ると、カメラだけから人物を識別し、特定することは、今のディープラーニングの技術でもってしてもかなり難しいタスクとされています。理由としては、大きく分けて二つあります。

一つ目は、単純な画像処理の難しさです。具体的には、以下のようなものが挙げられます。

  • 視点の多様性
  • 照明条件の変化
  • 人のオクルージョン
  • 低解像度

"視点の多様性"とは、カメラごとに視点が異なるので、同一人物だったとしても、姿勢や見た目の特徴が、異なってしまうという問題です。"照明条件の変化"とは、特にRGBカメラにおいて、照明の具合が時間と場所によって変化してしまうといった問題です。"人のオクルージョン"とは、識別したい人物が物陰などに隠れてしまったりすることで、カメラからの認識が容易ではなくなってしまうといった問題です。"低解像度"とは、監視カメラから得られる映像は低解像度であることが多いので、低解像度でも人物を識別できるようにしなければならないという問題です。これらの課題に対応するために、近年ではディープラーニングの活用が進んでおり、精度改善のために様々な手法が提案されています。

Person Re-Idの難しさの理由の二つ目は、実用的なシステムの構築にあります。実際に、Person Re-IDのシステムを構築するとなると、”生データを入力としたend to endなシスタム”を構築しなければなりません。カメラから得られるのは、生のデータ、つまり、動画データであるので、Person Re-Idを行うためには、人物検出を行った上で、識別を行うなど、複数のタスクを同時に行わなければならないということです。実用的なシステム構築は、研究のフェーズでもあまりなされておらず、この部分が極めて難易度の高いものとなっています。

Person Re-Idの前提

Person Re-Idの外観を理解するために、基本的な前提について触れたいと思います。下の図1を見てください。

f:id:yuichinagapan:20210308145207p:plain
データの構成
Person Re-IDには大きく分けて、GalleryとQueryと呼ばれる2種類のデータが存在します。Galleryは、人物の見た目の特徴が格納された登録データベースのようなもので、既に個人の画像(特徴量)とそれに対応するIDが割り振られています。一方で、Queryは照合したい人物のことです。照合したい人物(Query)は大規模なデータベース(Gallery)に問い合わせて、マッチする人物を検索するというような流れになっています。このように、Person Re-Idは、検索したい人物をデータベースの中から探し出すというようなタスクであることを踏まえた上で話を進めていきたいと思います。  

実用的なPerson Re-Idの全体的な流れ

実用場面において、Person Re-Idはどのようなステップを踏む必要があるのか、大雑把な流れを理解したい思います。下の図2を見てください。

f:id:yuichinagapan:20210308154210p:plain
Person Re-Idの流れ

4つのステップを踏む必要があります。それぞれについて見ていきます。

1.データの収集

まずは、生データの収集から始まります。生データというのは、敷地内に設置された複数のカメラから得られる動画データのことです。

2.人物検出 andトラッキング

2つ目のステップとして、データを収集したのちには、得られた動画から人物検出、場合によっては人物のトラッキングを行ってやる必要があります。具体的には、人物を囲うバウンディングボックスと呼ばれる長方形を人物検出器によって検出します。

3.人物の特徴量の抽出

3つ目のステップとして、得られたバウンディングボックスから、人の見た目の特徴ベクトルを訓練済みのニューラルネットワークにより取得します。要するに、人が切り取られた画像が与えられた時に、その画像をニューラルネットワークに入力することで、最終的に人の見た目の特徴を表したベクトルを獲得するというようなイメージになります。人の見た目の特徴表現をどのように得るかが、Person Re-Idにおいて主眼となるタスクであり、多くの研究はここの部分に焦点を当ています。

4.歩行者の検索(実行段階)

人の見た目の特徴表現を得た後には、既に登録されているデータ(Gallery)と、検索したい人物(Query)の特徴量の比較して、類似度の計算を行うことで、検索したい人物が登録データにおいてどの人物に対応するかを算出します。最終的に、Queryは類似度が最も大きいものをGalleryの中から選出して、QueryとGalleryを結びつけるというようなことを行っています。

実用と研究のギャップ

実は研究レベルで行われている最先端の手法を直接実用場面に応用できるかと言われるとそうでもなくて、実用と研究にはかなりのギャップ存在します。下の表を見てください。

f:id:yuichinagapan:20210308162906p:plain
研究と実用のギャップ
研究よりの環境のことをclosed settings、実用よりの環境のことをopen settingsと呼びます。黄色に塗られた部分が注意して欲しい重要な違いです。順に解説していきます。

ステップ

研究では、前の項目で"ステップ3のみ"、つまり、人の特徴表現をどのようにニューラルネットワークを用いて獲得できるかというところに主な着眼点がある一方で、実用場面では、生データの収集をして、人物を検出して、人の特徴表現を獲得して、検索の行う、というように、"ステップ1"から"ステップ4"まで一気通貫して行う必要があります。

モダリティ

研究では、RGBデータを用いて実験の評価を行うことが多い一方、実用場面では、複数のセンサーから人の識別を行う必要も出てきそうです。最近では複数のセンサーを使ってPerson Re-Idを行う論文も出てきていますが、まだまだデータ整備が十分になされておらず、研究のフェーズにおいても発展途上といった感じです。  

入力データ

研究で用いられるデータでは、評価を統一するために既に人物検出されたバウンディングボックスが与えられている一方、実用場面での入力データは、生のデータです。よって、実用場面では生のデータを処理してあげる必要があります。

訓練データ

研究においては、アノテーション付きの訓練データが十分にあるため、それらを用いてモデルを学習することができます。一方で、実用場面では、場合によっては、その環境に適した特徴抽出モデル用意する必要があるため、モデルの再訓練を行わなければなりません。この場合、学習を行うためのアノテーションが必要ですが、生のデータにアノテーションを行うとなると、それにかかるコストは膨大になってしまいます。

アノテーション

しっかり整備されたデータセットが使える研究においては、アノテーションは常に正確と言えますが、実用場面において、アノテーションを行う場合、必ずしも正確とは言えず、ノイズを含んだアノテーションになってしまう恐れがあります。

検索方法

最も大きな違いです。検索したい人物(Query)は照合先のデータベース(Gallery)の中から探索するというような操作でPerson Re-Idを行いますが、実環境においては検索したい人物が必ずしもデータベースに存在するとは限りません。初めてある人物ががカメラに映る場合には、Galleryに登録されていないので、既にGalleryに登録されているかを確認した上で、もしGalleryに登録していなければGalleryに登録するといった操作が必要になります。

Person Re-Idにおけるデータセット

Person Re-Idが一体どのくらいの精度を出しているかを把握するために、一般的なPerson Re-IDで用いられるデータセットの構成について触れていきます。Person Re-IDのデータセットは、以下のように大きく分けて、Training setとGalleryとQueryから構成されています。(画質荒くてごめんなさい)

f:id:yuichinagapan:20210308170916p:plain
研究で用いられるデータセットの構成
人の見た目の特徴量の獲得のためのニューラルネットワークの学習はTraining setにより行われ、GalleryとQueryにTraining setと同一人物が含まれるということはありません。よって、トレーニングセットで人物がどのような見た目の特徴を持っているかを学習し、GalleryとQueryで学習されたモデルがどのくらいの精度を出すのかを評価することができます。いわば、GalleryとQueryは機械学習におけるテストデータセットになるということです。この構成は、研究(Closed settings)で用いられるデータセットに対しては、共通で当てはまるのですがが、Open settingsではそのデータセットも大きく異なってきます。Closed settingsではQueryに対応するGalleryが必ず存在するという構成になる一方で、Open settingsでは、Queryに対応するGalleryが存在するとは限らないという構成になります。

既存モデルでの大まかな精度(2020年)

Closed settingsとOpen settingsそれぞれの場合におけるモデルの評価についてみていきます。

f:id:yuichinagapan:20210308174914p:plain
モデル評価の手順

上の表のようにClosed settingsとOpen settingsではとるべき手順が違うことがわかります。続いて、それぞれ場合について、現時点でのモデルの精度を確認します。まずはClosed settingsからです。

f:id:yuichinagapan:20210308171455p:plain
最新モデルの精度
上の図はMarket-15013と呼ばれるデータセットでの評価です。縦軸のRank-1(%)は、詳しくは述べませんが精度のようなものだと思ってもらって大丈夫です。最新のモデルのRank-1は95%以上というとても高い数値を残しています。この95%という精度を直感的に表現すると、とある検索したい人物がモデルに入力された時に、95%以上の確率で正しいIDを返すことができるというような感じです。特定のデータセットに対しては現時点でのモデルでも非常に高いパフォーマンスを発揮できていると言えそうです。

一方で、Open settingsを仮定すると、先ほどの95%以上の精度が著しく下がってしまうということがわかっています4。Open settingsを仮定した研究は現時点であまりなされておらず、評価指標がまだはっきりと定まっていないので、定量的な数値を与えることはできないないのですが、精度は実用場面では使えないくらいに著しく低下してまいます。もし、実用場面でPerson Re-Idを行いたいのであれば、あらかじめ個人のデータを取得しておき、登録データベースに保存することで、Closed settingsにタスク変換をしてあげる必要がありそうです。

まとめ

この記事では、Person Re-Idは何を行っているかと、現実的なシステム構築を行うために考慮しなければならないことについて述べてきました。Person Re-Idの研究がもっと進んでいけば、いずれは監視カメラネットワークだけで個人を追跡できるようなシステムを作ることができるかもしれないですね。今後も継続して、この分野の最新の研究を追っていきたいと思います。

参考文献


  1. Zhang, Z., & Huang, M. (2018). Learning local embedding deep features for person re-identification in camera networks. EURASIP Journal on Wireless Communications and Networking, 2018(1), 1-9.

  2. Ye, M., Shen, J., Lin, G., Xiang, T., Shao, L., & Hoi, S. C. (2020). Deep learning for person re-identification: A survey and outlookarXiv preprint arXiv:2001.04193.

  3. Zheng, L., Shen, L., Tian, L., Wang, S., Wang, J., & Tian, Q. (2015). Scalable person re-identification: A benchmark. In Proceedings of the IEEE international conference on computer vision (pp. 1116-1124).

  4. Leng, Q., Ye, M., & Tian, Q. (2019). A survey of open-world person re-identification. IEEE Transactions on Circuits and Systems for Video Technology, 30(4), 1092-1108.