われがわログ

最適化アルゴリズムとかプログラミングについて書きたい

BJ-AKIさんの飛廉を買った

久しぶりの投稿ですが、技術系じゃなくて趣味のサントラ系の話です。

まとめ

本文

いい曲がないかネットで調べていたとき、発見した曲がBJ-AKIさん作曲の「飛廉」だった。切なさを感じさせる美しいメロディーが特徴のピアノ曲で、作曲者本人がYouTubeに曲をアップロードしているので一度ぜひ聞いてほしい。FF10の「ザナルカンドにて」が好きな人なら気に入ると思う。

www.youtube.com

10年くらい前に発表された曲らしく、秒速5センチメートルのMADも有志によって作られている。Yahoo!知恵袋に、「秒速5センチメートルの曲だと思うんですが、この曲の詳細知りませんか?」って質問があったのは笑った。

楽譜探してます - 元ネタとかはよくわかりませんが秒速5センチ... - Yahoo!知恵袋

さて、当時の私はこの曲を購入しようと思って以下の個人ページに飛んだのだが、買えなかった。というのも、曲を販売しているサイトであるDLmarketがサービスを終了させていたからだ。

BJ-AKI Official Website - Discography

当時の私はここであきらめ、上記のYouTubeからmp3音源を抽出するという手をとったのだが、最近また曲を探している中でBJ-AKIさんの別の曲を聞きたくなってきた。mp3抽出というのはさすがにどうなん、と思ったというのもある。そこで、飛廉が含まれているミニアルバム「pianist」を購入できないか以下のフォーム経由で連絡をとってみた。

BJ-AKI Official Website - Contact

サイト自体は2017年頃から更新もなく、DLmarketの閉鎖についても対応されていなかったのできっと返信はないだろうなというダメ元アクションだったのだが、BJ-AKIさんご本人から翌日に連絡がありmp3音源を買えることになった。 支払いは銀行振り込みかPayPayを選べたので、手軽な後者を選択&送金。すぐにファイルが送られ聴くことができた。

こういった経緯で飛廉を購入できたわけだが、ミニアルバムの中身も期待通り素晴らしかった。タイトルの通りすべてピアノ曲で、飛廉のような美しい曲が収録されている。飛廉を好きな人ならきっと満足できるだろう。

この後、調子にのってアルバム未収録曲(400円のやつ6つ)と4th album Evolutionも購入した。 未収録曲の方はすべてYouTubeで公開されているのでぜひきいて判断してほしいが、「十五夜 Triple Measure」は特におすすめ。

www.youtube.com

Evolutionはピアノ系以外も入っており、曲の雰囲気も幅がある(切ない系だけじゃなくて明るい・楽しい系もある)ので人を選ぶという印象。個人的にはpianistと未収録曲の方がまとまりがあって好き。

というわけで、 仕事じゃないですが、適切なアクションとるのは大事と感じた出来事でした。

おしまい。

転職活動振り返り

メーカー研究職から他社開発職へ転職することになった。その転職活動をこの記事で振り返る。なお、from, toの企業名や退職理由は書いていない。

サマリは下記の通り。

自身のスペック

  • 機械系博士卒(力学・制御あたりが主専攻)
  • 理論(アルゴリズム開発)もソフト開発もできる

スケジュール

  • 転職活動開始:2021/4 中旬
  • 転職活動終了(内定承諾): 2021/11 上旬

応募した企業数

  • 書類提出:12社
  • 内、書類選考通過:9社
  • 内、1次通過:4社
  • 内、2次通過:1社
  • 内、内定承諾:1社

応募した企業の種類

使用した転職サイト

使用したエージェント

初めて面接を受けたのが4月中旬だ。そこから面接を計9社受け、一社内定が出て受諾したのが11月初旬。なかなか苦戦したほうだと思う。正直、7月末で完全に気が萎えて転職活動をやめようかと思っていたのだが、10月中旬に友人のリファラルで一社受けたところ、トントン拍子で話が進み、内定を頂けることになった。年収はかなり上がった。

企業のタイプは当初はベンチャーを志望していたが、最終的には大手企業に就職する運びとなった。現職が研究職&分野がニッチということもあり、マッチング&即戦力を欲するベンチャーとは相性が悪かったのかもしれない。

使用した応募形態は、直接応募・エージェント経由・リファラル経由の3つ。知らない企業を探すのにはエージェントは有用だが、それ以外は直接応募かリファラルがよいように思う。エージェントが間に入ると、面接でチェックされるポイントを教えてくれたり面接対策をしてくれたりというメリットがあるものの、伝言ゲームに伴う、企業との齟齬が発生しやすくなるというデメリットがある。

転職活動をするにあたり、やっておいてよかったことの一つがネット(ブログやGitHub)上での技術力アピールだ。これが採用結果にどれだけ影響したかはもちろんわからないが、企業に与えられる情報の正確さや量においては、一時間足らずの面接よりも大きかっただろうと考えている。

どうもとっ散らかった文章になってしまったが、振り返りは以上とする。

Argon ONEのファン制御ソフト Argon One Daemon (argononed)

Argon One Daemon (argononed)を布教するために書いた。

私はRaspberry Pi 4にArgon ONEというケースをつけて使っている。

www.switch-science.com

このケースには30 mmファンが付いており、ファン制御用のソフトウェアは公式から配布されている。

url: https://download.argon40.com/argon1.sh

このソフトウェアの問題点は、Pythonスクリプトであるということだ。個人的な嗜好ではあるのだが、バックグラウンドで常に動かすソフトウェアにPythonは使いたくない。同じことを考える人はいるようで、同様の機能をC言語で実装したソフトウェア「Argon One Daemon」が存在する。ヒステリシスの設定もあり、なかなか高機能だ。

gitlab.com

使い方はREADMEがわかりやすいが、基本的には

./install

と押下するだけで機能する。設定は、Ubuntuだったら/boot/firmware/config.txtで設定する(一般的には/boot/config.txt)が、大半の場合はデフォルトで問題ないだろう。argonone-cliから指定してもよい。

Visual StudioのDeveloper ShellをPowerShellのコマンドラインからactivateする

WindowsでCMake & MSVCを使用する際はいつもスタートメニューから「Developer PowerShell for VS 2019」を起動していたが、いい加減面倒になって、コマンドラインから起動する方法を調べたのでメモしておく。

結論としては、profile.ps1(場所は$profile で見られる)に以下の設定をすれば、以降はcondaのenvironmentのようにactivate_msvcで起動できる。

Set-Alias activate_msvc activate_msvc_impl

function activate_msvc_impl {
    $installPath = &"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -version 16.0 -property installationpath
# - SkipAutomaticLocation を指定しないと勝手にcdされる
# -DevCmdArguments -arch=x64 を指定するとx64コンパイラが使用できる
    Import-Module (Join-Path $installPath "Common7\Tools\Microsoft.VisualStudio.DevShell.dll") && Enter-VsDevShell -VsInstallPath $installPath -SkipAutomaticLocation
}

Set-Alias activate_msvc activate_msvc_impl

参考

MLflow TrackingのREST APIを叩くC++ライブラリを作った

MLflow Trackingは数値実験のコード・設定等を管理できるソフトウェアであって、かなり使い勝手がよい。試行錯誤しながら計算をまわしていると「この結果のときパラメータどう設定したっけ、、」みたいなことがよくあるが、実験毎にパラメータを全てMLflow Trackingに保存しておけばこのような事態を防げる。また、計算の途中結果(例えばlossとかaccuracy)をグラフで確認できるのも便利だ。

MLflow Trackingが公式にサポートしている言語は、Python, R, Javaの3つで、その他REST APIを使ってもmlflowのサーバーと通信できる。さて、例のごとく、C++でも数値実験の管理をしたいということで、MLFlow TrackingのREST APIを叩くライブラリを作成した。コード例や依存ライブラリの説明はgithubのREADMEを見てほしい。

github.com

現在実装しているAPIは以下の通り。さしあたり、自分が必要なものだけ実装した。

  • Create Experiment
  • Get Experiment
  • Get Experiment By Name
  • Create Run
  • Log Metric
  • Log Batch
  • Set Tag
  • Log Param
  • Update Run

API一覧は下記URLのとおり。

mlflow.org

artifactをサーバに送信するAPIが存在しないため、このライブラリではartifactをアップロードできないのがネック。ファイルのパスをタグ orパラメータに保存しておくしかないか。

ちなみに、例外でなくRustのようなResult型で実行結果を返すようにしたのだが、どうもコードが冗長になる。私の使い方の問題か?

プログレスバーを表示するC++ライブラリを作った

成果物:

github.com

https://raw.githubusercontent.com/estshorter/pbar/videos/example1.gif

背景

Pythonで重い処理をする際は、tqdmプログレスバーを表示している。最近、C++でもプログレスバーを表示させたいと思ったのだが、既存のライブラリは微妙なものしかなかった。サーベイの結果は以下の通り。

  • tqdm.cpp: tqdmのC++への公式ポートだがメンテされていない。コンパイルして動かしてみても正常に表示されない。
  • indicators: スター数的には一番人気のライブラリ。だが、Windows + PowerShellの環境だと、バーの後に余分な改行が入り表示が乱れる。また、UTF8対応を謡ってはいるが、日本語環境では文字化けする。Win32 APIを使って文字出力していないことが原因と思われる。将来的に廃止予定のcodecvtクラスを使っているのも微妙に感じた。
  • progressbar: 200行弱のシンプルなライブラリ。これでもよかったのだが、実行中の警告メッセージを表示させる機能がないのが気になった。

pbar

このように、自分好みのプログレスバーC++ライブラリがなかったので自作したのが、最初のpbarライブラリだ。バーの表示の形式はtqdmとほぼ同様である。進捗はUnicode文字"█"で表示しているが、WindowsではWriteConsoleW関数でターミナルに表示しているため、文字化けしない。最初のgif動画のように、複数のバーの同時表示にも対応している。設定しなければ、コンソールの横幅めいっぱいにバーを表示する。なお、本ライブラリはスレッドセーフではないので、バーのアップデートは同期的に行う必要がある。

サンプルは下記の通り。pbar::pbarクラスを作成した後、bar.init()で進捗0%のバーを表示する。ループの終わりでbar++としてバーの表示を更新する。bar.tick()でもよい。 バーを複数表示する場合は、一番上のバー以外にenable_stack()を設定する。 その他いくつか設定はあるが、名前からわかるだろう(説明放棄)。

#include <chrono>
#include <iostream>
#include <pbar.hpp>
#include <thread>

int main(void) {
    using namespace std::this_thread;
    using namespace std::chrono;
    constexpr auto total_ = 100;
    constexpr auto ncols = 100;
    pbar::pbar bar(total_, ncols);
    bar.set_description("[TASK0]");
    bar.init();  // not always necessary
    bar.enable_recalc_console_width(1);  // check console width every 10 ticks
    for (std::int64_t i = 0; i < total_; ++i, ++bar) {
        sleep_for(milliseconds(100));
    }
    std::cout << "done!" << std::endl;
    pbar::pbar bar1(4);
    pbar::pbar bar2(8);
    pbar::pbar bar3(16);
    bar1.set_description("[TASK1]");
    bar2.set_description("[TASK2]");
    bar3.set_description("[TASK3]");

    bar2.enable_stack();
    bar3.enable_stack();

    bar1.enable_recalc_console_width(10);  // check console width every 10 ticks

    bar1.init();
    for (auto i = 0; i < 4; ++i, ++bar1) {
        bar2.init();
        for (auto j = 0; j < 8; ++j, ++bar2) {
            bar3.init();
            for (auto k = 0; k < 16; ++k, ++bar3) {
                sleep_for(milliseconds(10));
            }
            sleep_for(milliseconds(50));
        }
        sleep_for(milliseconds(100));
    }
    std::cout << "done!" << std::endl;

    return 0;
}

210823_追記

スピナーも作った。サンプルプログラムはgithubを参照のこと。 https://raw.githubusercontent.com/estshorter/pbar/videos/example1.gif

OmegaConfの便利機能をC++でも使いたいので実装した

成果物

github.com

背景

OmegaConfは、設定ファイルを扱うためのPythonライブラリ。

OmegaConf — OmegaConf 2.1.0 documentation

詳細はこのブログが詳しいが、自分が特に便利だと感じた機能は次の2つ。

  1. Variable interpolation
  2. Custom resolvers

Variable interpolationは他のデータを参照できる機能で、数値計算だと例えば以下のように、出力ファイル名を他のパラメータに応じて変更できる。

job_id: "TEST"
param: 10
output_filepath: "output_${job_id}_${param}.bin" # "output_TEST_10.bin"

出力ファイル名にパラメータやIDを表すprefix/suffixをつけることはよくあることだが、OmegaConfではそれをyamlファイル中に記載できる。

Custom resolversは、プログラム中で定義した関数の実行結果を値とするものだ。例えば、配列として渡された値を"_"で連結する関数をOmegaConfへ登録したい場合は下記のようになる。

OmegaConf.register_new_resolver("join", lambda x: "_".join(x))

すると、yamlで以下のような構文が使える。

output_filepath: "output_${join: 0,10,20}.bin" # "output_0_10_20.bin"

業務がPythonで完結するならOmegaConfを使っていればよいのだが、残念なことにC++を使った業務があり、OmegaConfの上記機能が使いたくなったので実装した、というのが背景である。

tomlex 説明

GitHub - estshorter/tomlex

実装したtomlexは、toml11をベースとしたライブラリであり、OmegaConfのVariable interpolation、Custom resolversの「ような」機能を実装している。C++17以降のみ対応。

典型的な使い方は以下の通り。tomlex::parseにファイルパスを渡すか、toml::parseで得られたtoml::valuetomlex::resolveに渡せばよい。そうすると、"${}"で囲った部分が全て展開される。

#include <iostream>
#include <string>
#include <toml.hpp>

#include <tomlex/tomlex.hpp>
#include <tomlex/resolvers.hpp>

toml::value no_op(toml::value && args) { return std::move(args); };
toml::value join(toml::value && args, std::string const& sep = "_") {
    switch (args.type()) {
        case toml::value_t::array: {
            auto& array_ = args.as_array();
            std::ostringstream oss;
            for (int i = 0; i < array_.size() - 1; i++) {
                oss << tomlex::detail::to_string(array_[i]) << sep;
            }
            oss << tomlex::detail::to_string(array_[array_.size() - 1]);
            return oss.str();
        }
        default:
            return std::move(args);
    }
}

int main(void) {
    std::string filename = "example.toml";

    tomlex::register_resolver("join", [](toml::value && args) { return join(std::move(args)); });
    tomlex::register_resolver("no_op", no_op);

    toml::value cfg = tomlex::parse(filename);
    std::cout << cfg << std::endl;

    //こっちでもOK
    //toml::value cfg = toml::parse(filename);
    //cfg = tomlex::resolve(std::move(cfg));
    tomlex::clear_resolvers();
}

tomlexのVariable interpolationはOmegaConfとほぼ同じ。 右辺にある文字列において、参照先を${}で囲って参照する。参照のフォーマットはdotted key。クォーテーションでの囲みやドットの間のスペースには対応しない。 また、相対パスにも対応していない。常に絶対パスを用いること。

job_id = "TEST"
param = 10
output_filepath = "output_${job_id}_${param}.bin" # "output_TEST_10.bin"
test = "${a.b.c.d}" # 10: int
#test = "${   a.b.c.d   }" # 前後のスペースはOK
#test = "${a.    b.c.d}" # NG
#test = "${'a.b.c.d'}" # NG

[a.b.c]
d = 10
# e = "${.d}" # 相対パスはNG

文字列が"${EXPR}"のように"${"で始まって"}"で終わっているなら、展開後の型は参照先の型と同じとなる。それ以外は文字列になる。

test2 = "${param}0" # "100": string

tomlexのCustom resolversは、関数の引数を表すフォーマットがtoml記法である(tomlのvalueとして正しいフォーマットである)必要がある。 というのも、この引数の文字列がtomlのパーサで読み込まれ、適切な型に変換されるからだ。

output_filepath =  "output_${join: [10,20,30]}.bin"             # "output_0_10_20.bin"
#output_filepath =  "output_${   join   :   [10,20,30]  }.bin"  # 前後のスペースはOK
#output_filepath =  "output_${join: 10,20}.bin"                 # NG 

3つ目のoutput_filepathでは引数が10,20であり、これはtoml valueとしては不正であるため、例外が投げられる。引数にtomlフォーマットを強制している点はOmegaConfとは異なるので注意。

また、本ライブラリは、"${EXPR}"を、展開後の文字列で置換していく(置換時に型情報が消える)という実装になっているため、Custom resolversを使う場合には引数の型に注意すること。以下に幾つか例を示す。

flt1 = 7.0
str1 = "7.0"

# be careful of argument type
conv_flt1 = "${no_op: ${flt1}}"   # ${no_op: 7.0} -> 7.0: double
conv_str1 = "${no_op: ${str1}}"   # ${no_op: 7.0} -> 7.0: double

conv_flt1conv_str1は、no_opの引数を展開した後にどちらも${no_op: 7.0}となってしまう。tomlのパーサは7.0をdouble型とみなすため、最終結果は等しくdouble型の7.0になる。 引数を文字列として扱いたい場合は、引数を明示的にクォーテーションで囲むこと。

conv_str2 = '${no_op: "${str1}"}' # ${no_op: "7.0"} -> "7.0": str

なお、interpolationと同じく、"${EXPR}"のように"\${"で始まって"$}"で終わっているなら、展開後の型は関数の戻り値と同じ型となる。それ以外は文字列。

ref_flt1 = "${no_op: ${flt1}}0" # "7.00": string

正直、ここら辺の仕様は自分でも微妙だと思っている。しかし、これを直そうとすると、おそらく自分でパーサーを書かねばならず、面倒そうなので仕様としまっている。なお、OmegaConfではこのようなことは起こらない。

ちなみに、参照先が文字列の場合はクォーテーションをつければよいのでは?と思うかもしれないが、それを単純に実装すると、以下の場合におかしな結果になる

param_str = "10"
test = "${param_str}0" # '"10"0': string

本当は"100"が欲しいのだろうが、'"10"0'のような文字列になってしまう。なので、"${}"の前後の文字からクォーテーションの挿入可否を判定する必要があり、そのためのパーサーが必要なのだ。

追記

OmegaConfのfrom_cli相当の機能も実装した。

auto cfg = tomlex::from_cli(argc, argv);