取りあえず何か創る

ソフトウェアエンジニアだけど仕事以外でも何か作りたくなったので、主にその記録のためにブログを開設しました。

google colabのTPUをPyTorchで使う

問題

  • google colabのTPUをPytorchで使いたい。
  • ググって出てくるセットアップをするとエラーが出る。
# !pip install cloud-tpu-client==0.10 https://storage.googleapis.com/tpu-pytorch/wheels/torch_xla-1.9-cp37-cp37m-linux_x86_64.whl
ERROR: torch_xla-1.9-cp37-cp37m-linux_x86_64.whl is not a supported wheel on this platform.

解決方法

  • 闇雲にググらないで公式のgithubを見る。

github.com

  • colabでの動かし方は↓のnotebookにあります。

colab.research.google.com

学び

  • 大体のことは適当にググって上の方に出てくる方法で解決するけど、インストール関連など公式ページを見たほうが良いトピックもあるので気をつけよう。

読書記録: A Philosophy of Software Design

0. この投稿の概要

"A Philosophy of Software Design"というソフトウェア設計の本を読んだのでその感想をまとめます。以下のような特徴を持った本で、多くの方におすすめできる一冊です。

  • ソフトウェアの複雑さを減らす」という明確な目的の元で設計の原則が説明されるので頭に入ってきやすい。
  • コンパクトにまとまっている。ペーパーバック版で190ページです。(値段もkindle版は安い!)
  • 多分翻訳書は出ていないと思いますが、英語が平易で難なく読み通せます。馴染みのない単語も1ページに1個あるかないかというレベルでした。

1. 学んだこと

設計の方針

  • 概要に書いた通り、本書は複雑さ(complexity)を減らす設計を目的として設計の原則を説明しています。本書での複雑さは「ソフトウェアシステムの理解や修正を難しくするもの」と定義されています。
  • 複雑さへの対応策として2つの方針が説明されます。1つはコードをシンプルかつ明解(obvious)に書くこと。もう1つはカプセル化、モジュール化によって個々の独立性を高めること。
  • Strategic programming: 動くだけでなく、より良い設計をすることを目的とすること。そのためにも多少の時間的な投資を厭わない。対義語はまずは動くものを作ることを目的とするTactical programming。
    • OOPやTDDなどの手法にも触れられていますが、複雑さを減らすなら活用し、そうでないなら使わないという立場です。特にTDDやアジャイル開発は動くことを目的としたTactical programmingに陥りやすいので注意が必要。
  • 以下の原則は特に心に刻んでおきたいところ。

software should be designed for ease of reading, not ease of writing.

deep module

  • 本書で紹介される原則の中で最も目を引かれたのがdeep moduleというもの。これは複雑さを減らすためのモジュール化の基本的な考え方です。ここでいうモジュールはclassや関数、あるいはより大きなサービスを指します。
  • モジュールはインターフェースと実装(implementation)に分けて考えることができ、シンプルなインターフェースでより機能的な実装を持つモジュールがdeep moduleとのこと。
  • インターフェースは他の技術者(あるいは将来の自分)がモジュールを使う際に読まなければいけないコストで、実装はモジュールを使うことで得られる利益と考えられる。そのためdeepであるほどコスパがいいというわけです。
    • 統一のインターフェースでより多くの機能をカバーするために、モジュールを設計する場合は今必要な機能に特化せず、少し一般化させると良い。
    • 情報の秘匿(information hiding)とdeep moduleは表裏一体。より多くの情報を隠せばモジュールの機能性は向上するが、そうでなければインターフェースが複雑になる。
    • 例外もインターフェースの一種であるとし、必要最低限の例外に絞ることが推奨されます。
    • コメント、特にインターフェースに対するコメントは重視されています。コメントは抽象化の一種で、コメントが不十分でコードを読む必要があるなら抽象化が機能していない
  • deepにするためにインターフェースをシンプルに保てるならモジュールを大きくすることも推奨されます。本書でも触れられてますが、既存の設計本ではモジュールを細かく分けることが推奨されることが多く、反対意見も多くあることが予想されます。

2. 感想

  • ソフトウェア設計本といえば様々なテクニックが雑多に紹介されて500ページ以上あるという印象を自分は持っており、正直言ってまともに通読できたことがありませんでした。本書はコンパクトで一貫した内容になっており、ようやく自分が読める設計本に出会えたという気持ちです。
  • 190ページとは思えないほど多くのことがカバーされていますが、自分はやはりdeep moduleという原則を今後は特に意識していきたいと思います。またアジャイル開発をしていると成果を出すためにTacticalな考え方になりがちな点も反省したいです。
  • 著者はスタンフォード大学の教授でソフトウェア設計のクラスも受け持っているとのこと。google groupで本書の内容について継続的な議論をしているようなので、今後のアップデートにも期待できそうです。
  • 参考: 著者のgoogleでの講演

www.youtube.com

読書記録 GPUを支える技術

0. この投稿の概要

  • GPUを支える技術」を読んで学んだことをまとめました。2021年に出た増補改訂版の内容です。進化の激しい分野なので、読まれる場合は改訂版を手に取ることをおすすめします。

  • 自分はディープラーニングの学習/推論のためにGPUを使うので、主にそこに焦点を当ててまとめています。グラフィックスについては省略しています。

1. 学んだこと

ハードウェア

  • 並列処理の方式: 同じ命令を多数のスレッドで並列実行するような動きをするSIMT方式が主流。データ並列度は使っていないため、依存関係のない命令を集めたり、データのサイズを演算器の数に合わせたりしなくても効率を高めやすい。
  • 精度の問題: 科学技術計算では64bitの倍精度浮動小数点も必要とされているが、ディープラーニングの推論ではそれほどの精度は必要なく、16bitの半精度浮動小数点や場合によっては8bitの固定小数点で計算しても結果に影響はない。参考: How to Quantize Neural Networks with TensorFlow « Pete Warden's blog
    • INT8の演算器はFP32よりも5~10倍小さい面積で実装ができるため、Googleは8bit整数で推論を行うTPUというカスタムLSIを開発した。
    • IEEEで規定されているFP16は指数部が5bit、仮数部が10bitだがディープラーニングの場合はオーバーフローしないように指数部が大きい方が扱いやすい。そのため指数部8bit, 仮数部7bitとしたBF16が多くのチップでサポートされている。
  • ベンチマーク: ディープラーニングを実行する場合の性能のベンチマークとしてMLPerfが開発されている。
    • 学習ベンチマークでは画像分類や物体検知のようなタスクに対して、目標性能に達した時間の学習時間を測定する。
    • 推論ベンチマークではFP32による推論の99%の精度を達成する必要があり、推論時間を測定する。

ソフトウェア

  • CUDA: NVIDIAが開発したGPUプログラミングに最低限必要な機能をC言語に追加した言語。
    • CPUとGPUはそれぞれメモリを持っているため、CUDAではデータの格納場所を修飾子で指定する。低速大容量のデバイスメモリやGPUに内蔵された高速低容量のシェアードメモリがある。
    • CUDAではベクトル型の変数タイプをサポートしている。ベクトル長は1~4まで。
    • 大まかな処理の流れ: ホスト/デバイス双方にデータの受け渡しようのメモリ領域を確保→ホストからデバイスへデータのコピー→カーネルを起動して計算を実行→結果をデバイスからホストにコピー→使い終わったメモリを解放。
      • ユニファイドメモリを使う場合はデータのコピー処理を明示する必要はなくなる。
  • OpenCL: 業界標準GPUプログラミング環境で、CUDAと似ているところが多い。
    • OpenCLは各社のGPUをサポートするため、構成を問い合わせる関数で情報を得て、それに対応した操作を行えるように作られている。
    • OpenCL 2.2ではC++14ベースのカーネル記述がサポートされている。
    • NVIDIAはCUDAのサポートを優先しており、AMDはHIPというCUDAのコードをAMD GPU用にコンパイルするシステムを開発している。
  • OpenMP/OpenACC: C言語のコードに「この部分はGPUで並列実行」といった指示行を加えるだけでGPUプログラミングを実現する。
    • OpenMPの方がサポート範囲は広いが、NVIDIAはOpenACCを優先している。NVIDIA GPUを使う場合はOpenACC、そうでなければOpenMPという使い分け。
    • CUDA/OpenCLで最適化したプログラムに性能は及ばないが、開発工数は少ないのでまず試してみる価値はある。

2. 感想

Can Spatiotemporal 3D CNNs Retrace the History of 2D CNNs and ImageNet?を読んだまとめ

0. この投稿の概要

  • 動画認識の機械学習モデル、3DCNNに関する論文"Can Spatiotemporal 3D CNNs Retrace the History of 2D CNNs and ImageNet?"を読んだので、その内容をまとめます。論文は以下のページから読めます。

arxiv.org

  • ディープラーニング、特に2D CNNの威力を世に示したのはImageNetという大規模画像データの認識コンテストかと思います。この論文では3D CNNがそのポテンシャルを発揮するにはやはり大規模な動画のデータセットが必要であり、Kineticsがその役割を果たすポテンシャルを持つことが示されています。

1. 論文の内容

目的

  • ImageNetにより層の深い(2D)ResNetが成功した歴史をKineticsと3D ResNetが同じ道を辿るか実験する。そのために行動認識タスクで3D ResNetの層を深くすればするほど性能が高まることを示す。
  • 動画のデータセットではHMDB-51とUCF-101が使われてきたが、より大きなActivityNetやKineticsが近年発表された。本論文ではこれらのデータセットを実験の対象にする。
  • 行動認識タスクのモデルとして、RGBとoptical flowをそれぞれ2D CNNの入力するTwo-Stream CNNがある。2つのStreamがそれぞれ空間、時間方向の特徴を抽出する。
    • より直接的にspatio-temporal特徴量を抽出するために3D CNNが提案された。VGGを3D化させたようなC3D、Inceptionの3D版であるI3D、更にResNetの3D版も提案されているが層は比較的浅いものに限られている。

実験

  • 実験にはResNetとその派生のモデルを使用した。各モデルのブロックは以下の図のような構成で、これを複数つなげることでResNetを構成する。

実験1
  • 各データセットで比較的浅いResNet-18を最初から学習する。Kineticsでのみ良い精度が得られるという過去の結果を再現する。
  • [結果]Kinetics以外のデータセットではvalidation lossとtraining lossで大きな乖離があり、オーバーフィットしていることがわかる。逆にKineticsではオーバーフィットは起きておらず、より深いResNetでも学習が可能なことが示唆されている。

実験2
  • KineticsでResNet-18~200を学習し、どこまで深いネットワークを学習できるか検証する。
  • [結果]層が深くなるほど精度は上がり、152層で飽和している。この傾向は2D CNNのよるImageNetの分類と同様。

  • 今回の最も良い結果はResNeXt-101だが、Two-stream I3Dの方が高い精度が出ている。
実験3
  • Kineticsで学習したモデルをHMDB-51やUCF-101でfine-tuningをする。これにより3D CNNでも転移学習が有効であることを示す。*最後のCNNブロックと全結合層のみ再学習している。
  • [結果]scratchで学習した場合に比べて明らかにfine-tuningした方が高い精度を持つ。Kineticsと同様に、深い層程精度がよくなり、ResNeXt-101が最も良い結果となった。


2. 感想

  • 自分も仕事で動画を入力とした機械学習の応用を検討しています。そこでネックになるのがデータの数です。本論文で示された転移学習が色々なタスクでも有効であれば是非とも使ってみたいです。
  • この論文の著者は日本人で、その方が以下の雑誌で動画認識技術の解説を書いています。動画認識技術の歴史やtransformerを使った最新のモデルまで説明されているのでおすすめです。

  • ありがたいことに著者のgithubにコードやpretrainモデルが公開されていますので、今度自分でも試してみようと思います。

github.com

読書記録 テスト駆動開発

0. この投稿の概要

  • 今更ながら「テスト駆動開発」を読んだので、今後の自分の開発にどうやって活かそうか考えたことをまとめます。

  • 本書は3章立てで、1章でJavaで多国通貨を足し合わせるプログラムをTDDで実装していき、2章はPythonでテストツール(xUnit)をTDDで実装、3章はTDDのパターンの解説です。自分は2章を写経しました。

1. 学んだこと, 考えたこと

テスト駆動開発をどう取り入れるか

  • テスト駆動開発によるプログラミングの作業は以下の3フェーズからなる。
    • レッド: 動作しない、コンパイルすら通らないかもしれないテストを1つ書く。
    • グリーン: そのテストを迅速に動作させる。この時、コードに期待値をベタ書きしたり類似する関数をコピペするような罪を犯すことを厭わない。
    • リファクタリング: テストを通すために発生した重複をすべて除去する。一般化する。
  • 一方で普段の自分の作業順は以下の通り。個人的に一番楽しいのは最後に修正とテストを繰り返しているフェーズ*1。TDDはこのフェーズを繰り返すことで開発を進めるようなものなので、うまく出来たら楽しい時間で埋め尽くせるか?
    • まず実装する。この時点で必要な一般化も考慮している。
    • テストを作る。大体最初から複数のテストケースを作成している。
    • テストの実行、実装の修正をテストケースが全て通るまで繰り返す。
  • プログラミングに限らず、多くの知的な活動の基本は複雑なものを分割して解いていくこと。TDDはそれを実行する手段で、本書に掲載されている例もこれでもかという程細かいステップに分割されている。
    • 問題はどれくらい細かく分割すべきかという点。テストの粒度、グリーンフェーズでどのような実装をするか。これは自分のスキルと取り組む問題に依存するため、客観的な指標でルール化するのは難しそう。
    • ステップの幅を決める手がかりは自分の不安や退屈さになると思う。十分に簡単な実装なら最初から一般化すれば良いし、思ったより時間がかかる場合は途中でも方針を変えてステップを刻む。
  • 方針: 何はともあれ最初にテストを作ることのデメリットは少ないので、そこは取り入れる。ステップの細かさは自分が心地よく作業ができるように随時調整をしていく。

その他実践すること

  • テストケースには数式を直接書く。
    • 何故か今までテストケースには計算した結果を記述し、コメントに計算式を書いていた。最初から数式をテストケースに入れておけば手計算をする必要もないし可読性も上がる。
  • テストファーストにするにはテストを書くハードルを下げる。テストコードも最初は最低限に。
    • テストを作る時は最初から一般性を意識して複数のテストケースを作ることが多い。それが面倒でテストを作るのを後回しにするくらいなら、最初は最も簡単なケースだけにする。
    • テストコード自体もリファクタリングされることを前提にする。コミットとコミットの間で消えるコードがあるのと同じで、一時的なテストだってあり得る。
  • 頭の中の悩みをテストに表現する。
    • 本書では2つの異なるクラスから生成されるインスタンスの不一致性を確認していた。普段は期待値との一致パターンしかしていなかったが、もっと柔軟にテストを考えてみよう。

2. 感想

  • テクニカルな内容も多く書かれていましたが、私としては自分の感情を頼りにステップを細かくすることがTDDの本質だと解釈しました。無理なく取り入れられるところから始めていこうと思います。
  • 訳者の和田さんによる近年のテスト駆動開発の解説がとても興味深かったです。特に以下の一節にはプログラミングを学んでいく上でとても勇気づけられます。

優れたプログラミングテクニックパラダイムは「使いすぎてみて」少し戻ってくるくらいが良い塩梅です。(中略)すべて手を出してみて、夢中になり、正気に戻り、良いと思ったエッセンスを自分の中に残してください。

*1:フロー状態の要素として「直接的で即座のフィードバック」があることが関係しているんだろう。

ゼロから作るDeep Learning3 フレームワーク編を写経した

0. この投稿の概要

  • フレームワークということでただ動くだけではなく、ユーザビリティやメモリ管理にも気を配っています。そこで本記事はコアとなる自動微分を実現する仕組み、ユーザビリティを向上させる仕組み、メモリ効率を改善する仕組みという3つの軸で学んだことをまとめました。
  • なお本書の最初のセクションはwebで公開されていますので、まずはこちらを確認して購入を検討されると良いかと思います。最初のセクションに一番コアなことが書かれていますので太っ腹ですね。

koki0702.github.io

1. 学んだこと

自動微分を実現する仕組み

  • 変数を格納するVariableクラスと各種関数のベースとなるFunctionクラスを作成、自動微分は基本的にこの2クラスだけで成立している。
    • 特にFunctionクラスは順伝播と逆伝播を行うメソッドを持ち、具体的な計算は継承先のクラスでそれぞれ実装される。
  • 誤差逆伝播を自動化させるために関数と変数のつながりを保持する必要がある。このつながりは順伝播でデータを流すときに作り、この特徴がDefine-by-Runと呼ばれる。
  • 分岐を含む複雑な計算グラフで逆伝播を正しく実現するために、順伝播をした際に変数や関数の世代を記録し、後ろの世代を優先的に逆伝播時に処理する。
  • 勾配もVariableクラスとして保持することで、逆伝播時も計算グラフを作れるようになる。勾配をさらに逆伝播することで高階微分を求めることができる。

ユーザビリティを向上させる仕組み

  • 複数の入力がある関数に対応するため、Functionクラスは可変長引数を使っている。
  • Variable同士、あるいはVariableとndarrayとの計算を+や-のような演算子を使ってできるように演算子オーバーロードをしている。
    • 他にもVariableクラスに格納されている中身(ndarray)を見やすくするため、ndarrayと同様のインスタンス変数(shape, ndim)などをpropertyとして実装。また__repr__メソッドを実装してprintにも対応した。
  • Parameterクラス(Variableと同一)の集約としてLayerクラスを実装。さらにパラメータ更新作業を一括して行うOptimizerクラスを実装。これが無いとパラメータごとに更新する退屈なコードをユーザが書かなければならない。

メモリ効率を改善する仕組み

  • pythonのメモリ管理は参照カウント方式が基本、もし循環参照がある場合は参照カウント方式では削除されないため、循環参照を作らないように心がける。
    • Functionが入出力のVariableを参照し、Variableはそれを生み出したFunctionを参照するため循環参照がある。本書ではFunctionから出力Variableへの参照を弱参照に置き換えた。
  • MeanSquaredErrorのような複数の関数の組み合わせで求められるものは、それ単体としてFunctionとして定義をした。既存の関数の組み合わせで実装すると、途中の変数に対しても計算グラフが生成されてメモリ効率が良くないため。
  • 本書の範囲では順伝播の結果を全てFunctionクラスが保持しているが、関数の中には逆伝播の計算に順伝播の情報が不要のものがある。関数ごとに保持するデータを決めれば不要な情報を保持する必要がなくなる。

2. 感想

  • 実装してみて、普段利用しているフレームワークのコアは自動微分の仕組みにあることに改めて気付かされました。逆に言えば既存のフレームワークの理解を深めるならまずはそこを抑えておいた方が良さそうです。
  • 本書は500ページ近くのボリュームがある本ですが、特に面白い自動微分に関わる実装は前半で終わるので、まずはそこまで写経をしてみると良いかと思います。
  • 次のステップとしてはモチベーションが高いうちにPyTorchのコードリーディングに少しでも挑戦したいです。また、今回とは違うdefine-and-run方式、特にどのようなネットワークの最適化が行われるかも調べたいと思います。

読書記録 ドメイン駆動設計入門

0. この投稿の概要

  • ドメイン駆動設計入門を読んで学んだことをまとめます。ドメイン駆動設計とはプログラムの適用対象の領域の知識に焦点をあてた設計手法です。

  • ドメインの知識はドメインオブジェクトとしてコードで表現され、それらを組み立てることでアプリケーションが実現されます。本書ではドメインオブジェクトとアプリケーションの実装パターンが主に紹介されています。

1. 知識を表現するパターン

値オブジェクト
  • システム固有の値を表したオブジェクトであり「値」がもつ3つの性質を持つ。その性質は不変である、交換が可能である、等価性によって比較されること。
    • ここでいう値は1や"hello"のようなリテラルのようなものか。これらは当然変更できないし、あくまでこれらの値をもつ変数の中身を交換することができる。また、==のような演算子で比較ができる。
  • どの値を値オブジェクトとして実装するかの判断基準は「ルールが存在しているか」(ルールをオブジェクトが担保するため)と「それ単体で取り扱いたいか」。
  • 値オブジェクトのメリットはその値に関するルールの実装を集約できること。プリミティブ型を使う場合、その値を利用する場所全てでルールチェックをする必要がある。
エンティティ
  • 値オブジェクトとは異なり、属性ではなく同一性によって識別されるオブジェクト。値は可変で、識別のためにIDを持ち、等価性の比較はIDだけを比べる。例: ユーザ情報
  • エンティティを採用する判断基準は「ライフサイクルが存在し、そこに連続性が存在するかどうか」。同じものでもシステムの関心によって値オブジェクトとエンティティどちらが適切か変わる。
  • エンティティ、値オブジェクトのようなドメインオブジェクトを使うメリットは、ドメインの変化に対応するために変更する場所が明確になること。
ドメインサービス
  • ドメインオブジェクトに実装すると不自然になるようなふるまいを提供する。例: ユーザ名の重複確認
  • 手続き型の実装を助長してしまいので、可能な限り使わない。

2. アプリケーションを表現するためのパターン

リポジトリ
  • オブジェクトのインスタンスをデータストアに保存するときは、直接的にデータストアに書き込みを実行せずに、リポジトリに依頼をする。逆にインスタンスを再構築するときもリポジトリに依頼をする。
  • リポジトリはインターフェースとして抽象化して、具体的なデータストアの操作はその具象クラスとして実装する。
アプリケーションサービス
  • ドメインオブジェクトを組み合わせてユースケースを実現するオブジェクト。例: ユーザ情報のCRUD処理
  • ユーザ情報取得処理ではドメインオブジェクトをそのまま返すとアプリケーションサービス以外のオブジェクトがドメインオブジェクトのふるまいを呼び出してしまう可能性がある。それを防ぐためにはデータ転送用のオブジェクトを代わりに返す。
  • ドメインのルールはアプリケーションサービスに記述しないで、ドメインオブジェクトやドメインサービスに記述する。
ファクトリ
  • 複雑なオブジェクトの生成を処理するオブジェクト。「コンストラクタ内で他のオブジェクトを生成するかどうか」はファクトリを作る際の動機づけになる。

3. 知識を表現する、より発展的なパターン

集約
  • 集約は複数のオブジェクトをまとめてひとつの意味を持ったオブジェクト。集約の外部から内部のオブジェクトを操作してはいけない。必ず集約オブジェクト自身が内部のオブジェクトを操作する。
  • そもそもインスタンスを持たせなければ、内部のオブジェクトを外部から呼び出しようがなくなる。インスタンスの代わりにそのIDを保持すればメモリの節約にもなる。
仕様
  • 仕様はあるオブジェクトが評価基準に達しているか判定するオブジェクト。
  • 複雑な評価はドメインオブジェクトの一つなのでアプリケーションに記述すべきではないが、エンティティに置くと煩雑になる。その場合は仕様として独立しドメインオブジェクトとして切り出す。

4. その他

依存関係逆転の原則
  • データストアの操作などハードに近い処理を行うオブジェクトは下位レベルで、クライアントに近い処理は上位レベル。上位が下位に依存してはならず、抽象オブジェクトを作成し、どちらのオブジェクトも抽象に依存するようにする。
ユビキタス言語
  • プロジェクトにおける共通言語(語彙)、最終成果物であるコードでもこの表現が現れるべき。

5. 感想

  • ドメイン駆動設計というと謎の用語が大量にあってハードルが高く感じるが、本書を読むとそこまで身構える必要がないような気分になる。ちゃんと理解するには原典を読むべきなんだろうが、その下準備として読んで損はない一冊だと思う。

  • 私はボトムアップな設計*1とはどのようなものか知りたくて本書に手を出したが、本書で取り上げるアプリケーションが小さかったので、ドメインからどのようなドメインオブジェクトを取り出せばよいかという設計の話がほとんど無く少し残念だった。
  • ドメイン駆動設計」を採用せずとも、知識やルールを部品として独立したオブジェクトにして、それらを組み合わせてアプリケーションを作るというのはオブジェクト指向の基本だと思うので、拝借できるテクニックや考え方は使わせてもらいたいと思う。

*1:副題にボトムアップという言葉が入っているが、あくまでドメイン駆動設計をボトムアップ的に説明するという意味。著者もドメイン駆動設計はボトムアップなアプローチだと書いているが、その詳しい説明はなかったと思う。