<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Rest Term</title>
	<atom:link href="https://rest-term.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://rest-term.com</link>
	<description>Web関連技術の話など</description>
	<lastBuildDate>Tue, 17 Jan 2023 02:43:10 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.1.1</generator>
	<item>
		<title>PyTorch 2.0の新しいコンパイラで機械学習を速くする</title>
		<link>https://rest-term.com/archives/3708/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=pytorch-2-0%25e3%2581%25a7%25e6%25a9%259f%25e6%25a2%25b0%25e5%25ad%25a6%25e7%25bf%2592%25e3%2582%2592%25e9%2580%259f%25e3%2581%258f%25e3%2581%2599%25e3%2582%258b</link>
					<comments>https://rest-term.com/archives/3708/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 06 Dec 2022 14:47:12 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[computervision]]></category>
		<category><![CDATA[hardware]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3708</guid>

					<description><![CDATA[12/02にPyTorch 2.0のアナウンスがありました。まだnightly版(α版)で正式リリースされるのは2023年3月頃のようですが、機能自体は試すことができるので早速使ってみました。 12/05現在、絶賛検証中なので結論のようなものは書けませんが、全体の傾向としては概ね公称通りに高速化の効果が認められました。 精度が低下することはない 小さなモデルに対して、学習は速くならず、コンパイルオーバヘッドのためepochsが少ない場合は全体として遅くなる、GPU使用率はAMPだと僅かに低くなる&#8230;]]></description>
										<content:encoded><![CDATA[<p>12/02に<a href="https://pytorch.org/get-started/pytorch-2.0/">PyTorch 2.0</a>のアナウンスがありました。まだnightly版(α版)で正式リリースされるのは2023年3月頃のようですが、機能自体は試すことができるので早速使ってみました。</p>
<p>12/05現在、絶賛検証中なので結論のようなものは書けませんが、全体の傾向としては概ね公称通りに高速化の効果が認められました。</p>
<ul>
<li>精度が低下することはない</li>
<li>小さなモデルに対して、学習は速くならず、コンパイルオーバヘッドのためepochsが少ない場合は全体として遅くなる、GPU使用率はAMPだと僅かに低くなる傾向</li>
<li>大きなモデルに対して、学習は速くなり(約5 ~ 30%高速化)、デフォルト設定ではVRAM使用率は少し低くなる(5 ~ 10%弱程度)</li>
<li>GPUだけでなくCPUも効率良く使えるケースだと特に高い効果が期待できる</li>
<li>コンパイルオプションはいくつかあるけどデフォルトで使うのが一番良さそう</li>
</ul>
<p>あくまで後述する僕の実験環境での傾向ですので参考程度に見てください。学習に数日かかるような大きなモデルかつマルチGPU環境(A100以降)であれば、おそらく定量的にも公称通りの結果が期待できるかと思われます。誰か試してー</p>
<h3 class="term">PyTorch 2.0の機能と使い方</h3>
<p>PyTorch2.0では新しいコンパイラが搭載されて速くなります。ユーザー体験的には極めてシンプルです。</p><pre class="crayon-plain-tag"># model: torch.nn.Module
model = torch.compile(model)</pre><p>これだけで学習が速くなるようです。公式ドキュメントはものすごく重厚長大で同じ事を何度も書いていましたが使い方はシンプル。一応理論的な部分も読みながら検証していますが、内容が難しいので自信のある方は公式ドキュメントから技術詳細を参照してください。</p>
<ul>
<li><a href="https://pytorch.org/get-started/pytorch-2.0/#developervendor-experience">PyTorch 2.0 developervendor-experience　| PyTorch</a></li>
<li><a href="https://peps.python.org/pep-0523/">PEP 523 – Adding a frame evaluation API to CPython | peps.python.org</a></li>
</ul>
<p>nightly版ではデフォルトで有効にしておらず、意図的に<code>compile</code>メソッドを通す作法になるようです。</p>
<p>また、以下のような著名なモデルパッケージでたくさん効果検証しているらしく、当然ながら研究用途だけでなくプロダクション用途での活用も期待できます。</p>
<ul>
<li>46 models from HuggingFace Transformers</li>
<li>61 models from TIMM: a collection of state-of-the-art PyTorch image models by Ross Wightman</li>
<li>56 models from TorchBench: a curated set of popular code-bases from across github</li>
</ul>
<h3 class="term">環境</h3>
<p>環境に依って結果が大きく変わりそうなので丁寧に書いておきます。</p>
<ul>
<li>OS: Ubuntu 20.04 (x86_64) </li>
<li>CPU: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz 8コア</li>
<li>RAM: 16GB</li>
<li>GPU: Tesla V100-SXM2-16GB</li>
<li>CUDA: Driver Version: 510.73.08, CUDA Version: 11.6</li>
<li>Python: 3.9.13</li>
</ul>
<p>A100とCUDA 11.7の環境が一番効果があるようですが、個人でA100はさすがにコスト高いので断念。VRAM 16GBしかないのでそれほど大きなモデルは試せていません。</p><pre class="crayon-plain-tag"># インストール
# zshの人は torch[dynamo] の部分はダブルクォートで囲っておく
pip install numpy --pre torch[dynamo] torchvision torchaudio --force-reinstall --extra-index-url https://download.pytorch.org/whl/nightly/cu116</pre><p></p>
<p>Pytorch関連のモジュールは以下のようになりました。上記コマンドだとtorchvisionはcpu版がインストールされたので過去のnightly版cu116バージョンに強制的に入れ替えてみたのですが上手く動かなかったので素直に最新版を入れました。PyTorch 2.0と言っているのに1.14になっていますがnightlyではよくあることです。正式リリースではたぶん2.0になるのでしょう。</p><pre class="crayon-plain-tag"># Pythonモジュール
pytorch-lightning        1.7.0
torch                    1.14.0.dev20221204+cu116
torchaudio               0.14.0.dev20221204+cu116
torchmetrics             0.9.3
torchtriton              2.0.0+0d7e753227
torchvision              0.15.0.dev20221204+cpu</pre><p>インストール確認は公式のチェックツールが使えます。</p><pre class="crayon-plain-tag">$ git clone https://github.com/pytorch/pytorch
$ cd tools/dynamo
$ python verify_dynamo.py
Python version: 3.9.13
`torch` version: 1.14.0.dev20221204+cu116
CUDA version: 11.6
All required checks passed</pre><p>CUDAは11.6未満だとダメっぽいです。ちなみにコードレベルでは _dynamo と _inductor というサブパッケージが使えるようになっていて、それぞれ新しいコンパイラを構成するコンポーネント(TorchDynamo/TorchInductor)となっています。</p><pre class="crayon-plain-tag">import torch._dynamo as dynamo
import torch._inductor.utils as utils</pre><p><strong>TorchDynamo</strong>はPythonのJITコンパイラ(FX Graphsを生成)でPyTorch 2.0の肝となる技術です。<strong>TorchInductor</strong>はコンパイラのバックエンドで、ハードウェア環境に適したオペレーションを実行します(CUDA環境なら<a href="https://github.com/openai/triton">Triton</a>、CPU環境ならC++/OpenMP)。なので、仮にGPU環境が用意できなくてCPU環境で使ったとしてもTorchInductorのおかげで恩恵にあずかれるはずです。</p>
<h3 class="term">検証</h3>
<p>ここでは画像分類モデルで検証します。モデルはお手軽にtorchvisionから、データセットはKaggleから適当にお借りしました。</p>
<ul>
<li>モデル: <a href="https://pytorch.org/vision/stable/models/swin_transformer.html">Swin Transformer (swin_t/swin_b)</a> / <a href="https://pytorch.org/vision/stable/models/generated/torchvision.models.convnext_large.html#torchvision.models.ConvNeXt_Large_Weights">ConvNeXt (Large)</a></li>
<li>データセット: <a href="https://www.kaggle.com/c/dogs-vs-cats">Dogs vs. Cats | Kaggle</a> (25,000 images)</li>
<li>実験環境: Native AMP(not apex) or FP32 / 10 epochs / ハードウェア環境は前述の通り</li>
</ul>
<p>Swin Transformerのtiny(params: 28.3M)とbase(params: 87.8M)とConvNeXT large(params: 197.8M)の3種類で学習させました。その他の細かいハイパーパラメータ等は全て同じです。オプティマイザにはRAdamを使いましたがSGDとか他のものを使っても結果にはほぼ影響ありませんでした。</p>
<p>実装はPyTorch Lightningで書かれた既存の学習プログラムに <code>model = torch.compile(model)</code> の1行を加えただけです。</p>
<h4 class="term">結果</h4>
<p>まず前提条件として精度面ではDynamoを有効にしても低下しないこと、その上で学習速度やGPU使用率などの非機能要件面を比較します。ログ取得/可視化にはPyTorch Lightningの<a href="https://pytorch-lightning.readthedocs.io/en/stable/api/pytorch_lightning.callbacks.DeviceStatsMonitor.html">DeviceStatsMonitor</a>と<a href="https://clear.ml/">ClearML</a>を使いました。</p>
<h5 class="term">Swin Transformer tiny (params: 28.3M)</h5>
<p>まずはSwin Transformer tinyから。ClearMLで出力したグラフを貼ります。後述しますが、MLOpsツール併用によるロギング処理自体のコストを抑えるため粗めのプロットで描いています。<br />
前提条件として精度面では低下していないことを確認できました(誤差レベルで多少変動アリ)。Precision/Recalも同様でこれ以降の検証モデルも精度面での低下は特に見られなかったので省略します。<br />
<img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swint-acc.png?x40497" alt="" width="594" height="400" class="alignnone size-full wp-image-3709" srcset="https://rest-term.com/wp-content/uploads/2022/12/swint-acc.png 594w, https://rest-term.com/wp-content/uploads/2022/12/swint-acc-300x202.png 300w" sizes="(max-width: 594px) 100vw, 594px" /></p>
<p>* 学習時間</p>
<table>
<tr>
<td>Dynamo無効 AMP</td>
<td>Dynamo有効 AMP</td>
<td>Dynamo無効 FP32</td>
<td>Dynamo有効 FP32</td>
</tr>
<tr>
<td>11:46m</td>
<td>12:17m</td>
<td>13:28m</td>
<td>13:34m</td>
</tr>
</table>
<p>Dynamo無効 AMPが一番速い結果となりました。結果としては想定通りで、小さなモデルには効果が無く、むしろ最初にグラフコンパイルのコストがあるので少し遅くなるという結果でした。次に非機能要件を比較してみます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swint-gpu.png?x40497" alt="" width="601" height="415" class="alignnone size-full wp-image-3710" srcset="https://rest-term.com/wp-content/uploads/2022/12/swint-gpu.png 601w, https://rest-term.com/wp-content/uploads/2022/12/swint-gpu-300x207.png 300w" sizes="(max-width: 601px) 100vw, 601px" /></p>
<p>GPU使用率はDynamo有効 AMPが一番低く、Dynamo無効と比べると平均5 ~ 10%程度節約できているようです。一方でFP32ではほぼ効果がありませんでした。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swint-vram.png?x40497" alt="" width="597" height="397" class="alignnone size-full wp-image-3711" srcset="https://rest-term.com/wp-content/uploads/2022/12/swint-vram.png 597w, https://rest-term.com/wp-content/uploads/2022/12/swint-vram-300x199.png 300w" sizes="(max-width: 597px) 100vw, 597px" /></p>
<p>VRAM使用率ではAMP環境でのDynamoの効果が無く、逆にFP32では少し効果がありました。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swint-gpu-temp.png?x40497" alt="" width="595" height="409" class="alignnone size-full wp-image-3712" srcset="https://rest-term.com/wp-content/uploads/2022/12/swint-gpu-temp.png 595w, https://rest-term.com/wp-content/uploads/2022/12/swint-gpu-temp-300x206.png 300w" sizes="(max-width: 595px) 100vw, 595px" /></p>
<p>念のためGPU温度を見てみるとDynamo有効 AMPが一番GPUに優しいことがわかります。ただし前述の通りVRAM使用率は変わらないのでバッチサイズを増やす余地はあまりなさそうです。</p>
<h5 class="term">Swin Transformer base (params: 87.8M)</h5>
<p>次にSwin Transformer baseを試します。モデルサイズによる判断基準の境界値として適切かどうか確認します。</p>
<p>* 学習時間</p>
<table>
<tr>
<td>Dynamo無効 AMP</td>
<td>Dynamo有効 AMP</td>
<td>Dynamo無効 FP32</td>
<td>Dynamo有効 FP32</td>
</tr>
<tr>
<td>19.33m</td>
<td><strong>13:49m</strong></td>
<td>22:25m</td>
<td>21:56m</td>
</tr>
</table>
<p>学習時間ではDynamo有効 AMPが一番効果があり、Dynamo無効と比べると<strong>約30%高速化</strong>しています。一方でFP32の方は効果はほぼ無いようでした。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swinb-gpu.png?x40497" alt="" width="601" height="403" class="alignnone size-full wp-image-3713" srcset="https://rest-term.com/wp-content/uploads/2022/12/swinb-gpu.png 601w, https://rest-term.com/wp-content/uploads/2022/12/swinb-gpu-300x201.png 300w" sizes="(max-width: 601px) 100vw, 601px" /></p>
<p>GPU使用率ではDynamoの有無による変化はほぼ見られません、FP32でも同様です。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swinb-vram.png?x40497" alt="" width="594" height="404" class="alignnone size-full wp-image-3714" srcset="https://rest-term.com/wp-content/uploads/2022/12/swinb-vram.png 594w, https://rest-term.com/wp-content/uploads/2022/12/swinb-vram-300x204.png 300w" sizes="(max-width: 594px) 100vw, 594px" /></p>
<p>一方でVRAM使用率ではDynamoの有無による変化が見られました。AMPで約10%、FP32で約6%低下しています。ここで、Swin-Tでは特に違いがなかったCPU利用率も確認したところ、こちらは興味深い結果になっていたので載せます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/swinb-cpu.png?x40497" alt="" width="597" height="411" class="alignnone size-full wp-image-3715" srcset="https://rest-term.com/wp-content/uploads/2022/12/swinb-cpu.png 597w, https://rest-term.com/wp-content/uploads/2022/12/swinb-cpu-300x207.png 300w" sizes="(max-width: 597px) 100vw, 597px" /></p>
<p>GPU利用率は変わらないのにCPU利用率はDynamo有効 AMPだと10%ほど増えています。仕組み的にはTorchInductorバックエンドだとCPU環境でも効果があるらしいので、それのおかげでしょうか？実際に学習処理はかなり高速化しているので、Dynamo/Inductorが効率よく働いていることは間違いなさそうです。</p>
<ul>
<li><a href="https://dev-discuss.pytorch.org/t/torchinductor-a-pytorch-native-compiler-with-define-by-run-ir-and-symbolic-shapes/747">TorchInductor: a PyTorch-native Compiler with Define-by-Run IR and Symbolic Shapes &#8211; compiler &#8211; PyTorch Dev Discussions</a></li>
</ul>
<h5 class="term">ConvNeXT Large (params: 197.8M)</h5>
<p>最後に一番大きなモデルとなるConvNext Largeで試します。Dynamo効果の期待値としてはこれが一番高いです。</p>
<p>* 学習時間</p>
<table>
<tr>
<td>Dynamo無効 AMP</td>
<td>Dynamo有効 AMP</td>
<td>Dynamo無効 FP32</td>
<td>Dynamo有効 FP32</td>
</tr>
<tr>
<td>16:57m</td>
<td>15:56m</td>
<td>33:40m</td>
<td>32:17m</td>
</tr>
</table>
<p>結果は想定とは異なりDynamo有効 AMP環境でも6%ほどしか高速化しませんでした。。学習に数日かかるようなケースだと6%の改善でも十分な効果と言えそうですが公称値にはほど遠いですね。。コンパイラバックエンド周りのオプションがいくつかあるのでそれらを試してみると結果が変わりそうですが、それはもっと検証を進めてからまた記事に起こしたいと思います。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/convnext-gpu.png?x40497" alt="" width="607" height="403" class="alignnone size-full wp-image-3716" srcset="https://rest-term.com/wp-content/uploads/2022/12/convnext-gpu.png 607w, https://rest-term.com/wp-content/uploads/2022/12/convnext-gpu-300x199.png 300w" sizes="(max-width: 607px) 100vw, 607px" /></p>
<p>GPU使用率はDynamoの有無でほぼ変わりませんでした。大きいモデルなのでFP32だとがっつりGPUを使っています。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/12/convnext-vram.png?x40497" alt="" width="607" height="408" class="alignnone size-full wp-image-3717" srcset="https://rest-term.com/wp-content/uploads/2022/12/convnext-vram.png 607w, https://rest-term.com/wp-content/uploads/2022/12/convnext-vram-300x202.png 300w" sizes="(max-width: 607px) 100vw, 607px" /></p>
<p>VRAM使用率はAMP環境においてはDynamoの有無でほぼ変わらなかったですが、FP32でなぜか使用率が下がっています。実行時に大量の警告がでていたので、上手くコンパイルできていなかった可能性が高いです。何度試しても同様だったのでFP32での検証は失敗として扱います。</p>
<h4 class="term">コンパイルオプション</h4>
<p><code>torch.compile</code>の重要なオプションの一つに<code>mode</code>がありますが、デフォルト値を含めて以下の3つが指定できます。</p><pre class="crayon-plain-tag"># API NOT FINAL
# default: optimizes for large models, low compile-time
#          and no extra memory usage
torch.compile(model)

# reduce-overhead: optimizes to reduce the framework overhead
#                and uses some extra memory. Helps speed up small models
torch.compile(model, mode=&quot;reduce-overhead&quot;)

# max-autotune: optimizes to produce the fastest model,
#               but takes a very long time to compile
torch.compile(model, mode=&quot;max-autotune&quot;)</pre><p>max-autotuneモードは残念ながらコンパイルが一度も通らず諦めています。。小さいモデル(Swin-T)でreduce-overheadの効果を期待して試してみたのですが、VRAM使用率が爆増しただけで学習時間はほぼ変わりませんでした。コンパイルでVRAMを逼迫させるくらいならコンパイルせずにバッチサイズを増やした方が速くなるので、小さなモデルなら無理にDynamoを使うのは避けた方が良いのかもしれません。これも引き続き調査します。</p>
<h3 class="term">その他 いろいろ</h3>
<p>まずはどうでもいいレベルの話ですけど、PyTorch Lightningでの学習時にDynamoを有効にしているとコンソールログに OptimizeModule と表記されるようになるので地味にありがたいです。</p><pre class="crayon-plain-tag">| Name      | Type             | Params
-----------------------------------------------
0 | net       | OptimizedModule  | 196 M     &lt;= Dynamo無効の場合はモデル名(例: ConvNext, Swin Transformer)が表記される
1 | criterion | CrossEntropyLoss | 0     
2 | metrics   | MetricCollection | 0     
-----------------------------------------------
196 M     Trainable params
0         Non-trainable params
196 M     Total params
392.467   Total estimated model params size (MB)</pre><p></p>
<h4 class="term">コンパイルしても速くならない場合</h4>
<p>Swin-Bはまぁまぁ効果があったのにConvNeXTだと効果が薄かったので深掘りしたいのですが、公式にはGraph breakとやらを避けろとあります。なんだそれ難しい。。</p>
<ul>
<li><a href="https://pytorch.org/docs/master/dynamo/faq.html#why-am-i-not-seeing-speedups">Why am I not seeing speedups?</a></li>
</ul>
<p>他にもプロセスがクラッシュしたときに対応方法などFAQを見ればある程度は方針がわかりますが、コンパイラの内部構造を先に勉強した方が近道かもしれません。</p>
<h4 class="term">コンパイル時間の計測</h4>
<p>手動でコードを差し込まないといけませんが、以下のコードでコンパイル時間を計測できるようです。学習処理が全部終わった後で実行します。</p><pre class="crayon-plain-tag">torch._dynamo.utils.compile_times()

# 以下のようなCSV形式?のテキストが返ってくる
convert_frame_assert.&lt;locals&gt;._convert_frame_assert, 0.6044, 0.0753, ...
create_aot_dispatcher_function, 0.3882, 0.4755, 0.3467, ...
compile_fx.&lt;locals&gt;.fw_compiler, 0.3782, 0.4505, 0.3293, ...
... 省略</pre><p>想定外の出力でしたが、Dynamoの内部関数の実行単位でかかった時間が返ってくるようで、たぶん数字を全部足してやればコンパイルにかかった総時間になるのだと思います。Excelでそのまま読み込んでSwin-B AMPのケースで集計してみたところ129秒ほどかかっているようでした。だいたい2分ほどですね。それっぽい妥当な数字に見えますが正しい集計方法かはわかりません。</p>
<h4 class="term">警告・エラー</h4>
<p>nightly版なので警告とかエラーがたくさんでて滅入るのですがメモ代わりに書いておきます。W&#038;BとかClearMLのようなMLOps系のツールを使っていろんなログを出すような環境でモデル学習しているとこういうエラーが増える気がします。</p><pre class="crayon-plain-tag">Exception ignored in: &lt;function _after_at_fork_child_reinit_locks at 0x7f3d951420d0&gt;
Traceback (most recent call last):
  File &quot;/home/ryo/.pyenv/versions/3.9.13/lib/python3.9/logging/__init__.py&quot;, line 255, in _after_at_fork_child_reinit_locks
    handler._at_fork_reinit()
  File &quot;/home/ryo/.pyenv/versions/3.9.13/lib/python3.9/logging/__init__.py&quot;, line 894, in _at_fork_reinit
    self.lock._at_fork_reinit()
AttributeError: 'NoneType' object has no attribute '_at_fork_reinit'</pre><p>Dynamoを無効にすると全く出ないエラーだったのでなんらかの影響はあるのでしょうが、ロギング処理時にロック失敗してるけど無視してもまぁ大丈夫みたいなエラーです。モデル学習処理自体には問題なさそうだったのですが、大量のエラーがでるのでコンソールログがだいぶ汚れてしまいますね。かと言って今時MLOpsツールを使わないのはツライので仕方ないです。<br />
あとはCUDA環境だと↓のような警告も大量に出てしまいますが、Inductorのコア部分っぽいのでさらに難しいです。。</p><pre class="crayon-plain-tag">[2022-12-05 09:17:11,729] torch._inductor.lowering: [WARNING] using triton random, expect difference from eager</pre><p></p>
<h3 class="term">おわりに</h3>
<p>今回はPyTorch 2.0のTorchDynamo/TorchInductorによる高速化を試してみました。今回試したモデルだとSwin-Bで大きな効果は認めつつも、期待値が一番高かったConvNeXT Largeでの効果が薄くて謎を残す結果となりましたが、精度を落とすことなく学習が速くなることは確認できました。</p>
<p>コンパイルオプションがいろいろあって難しいのですが、デフォルト設定であれば <code>model = torch.compile(model)</code> を1行追加するだけ、ユーザー体験的には素晴らしいと思います。以前紹介したfunctorch(<a href="https://rest-term.com/archives/3688/">JAXライクなfunctorchで機械学習を速くする – part 1</a>)を使う場合は根本から実装を書き換える必要がありますが、PyTorch 2.0を使えば実装レベルの移行コストはほぼ無いに等しいです。</p>
<p>今回は失敗した検証もありましたので、もう少しドキュメントやフォーラム、実装等を読み込みながら検証を続けたいと思います。予定では来年3月頃に正式リリースらしいので楽しみにしておきましょう。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3708/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PyTorch LightningのLearning Rate Finderの使い方</title>
		<link>https://rest-term.com/archives/3697/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=pytorch-lightning%25e3%2581%25aelearning-rate-finder%25e3%2581%25ae%25e4%25bd%25bf%25e3%2581%2584%25e6%2596%25b9</link>
					<comments>https://rest-term.com/archives/3697/#comments</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 18 Oct 2022 09:32:16 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tool]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3697</guid>

					<description><![CDATA[ここ3ヶ月くらいPyTorch Lightning (以下 Lightning)を使ってていろいろ機能を調べてます。それでfast.aiでお馴染みのLearning Rate Finder(LR Finder: 最適な初期学習率を探索する仕組み)がLightningにもけっこう昔から実装されているのですが、日本語での紹介がほぼ無いみたいなので情報をまとめておきます。せっかくなのでこのエントリーでは細かいtipsや内部実装なども掘り下げて紹介したいと思います。 環境 * Pytorch 1.12.&#8230;]]></description>
										<content:encoded><![CDATA[<p>ここ3ヶ月くらい<a href="https://www.pytorchlightning.ai/">PyTorch Lightning</a> (以下 Lightning)を使ってていろいろ機能を調べてます。それで<a href="https://docs.fast.ai/">fast.ai</a>でお馴染みの<strong>Learning Rate Finder</strong>(LR Finder: 最適な初期学習率を探索する仕組み)がLightningにもけっこう昔から実装されているのですが、日本語での紹介がほぼ無いみたいなので情報をまとめておきます。せっかくなのでこのエントリーでは細かいtipsや内部実装なども掘り下げて紹介したいと思います。</p>
<h4 class="term">環境</h4>
<p>* Pytorch 1.12.1<br />
* Pytorch Lightning 1.7.0</p>
<h3 class="term">Learning Rate Finder (LR Finder)とは</h3>
<p>機械学習にはSGDやAdamなど様々な最適化アルゴリズムがあります。もちろんPyTorchにもたくさんのオプティマイザが実装されていますが、それらを適切に使いこなすには奥が深く、その中でも学習率の設定は精度にも大きな影響がでることが多いのでそれを自動化したいモチベーションが生まれます。そのための仕組みがLearning Rate Finder(LR Finder)と呼ばれておりfast.aiでの実装が特に有名かと思います。</p>
<p>その仕組みを使うと初期学習率のサジェストを得ることができます。Lightningの実装だと以下のようにlossと学習率のプロット、おすすめの学習率(赤点)を教えてくれます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/10/lr_finder.png?x40497" alt="lr finder" width="500" height="390" class="alignnone size-full wp-image-3698" srcset="https://rest-term.com/wp-content/uploads/2022/10/lr_finder.png 500w, https://rest-term.com/wp-content/uploads/2022/10/lr_finder-300x234.png 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p>今回はLightningでの実装について紹介しますが、PyTorch Igniteでは<a href="https://pytorch.org/ignite/generated/ignite.handlers.lr_finder.FastaiLRFinder.html">FastaiLRFinder</a>というハンドラが実装されているので、もしIgniteを使っている人はそちらを使うと良いでしょう。</p>
<p>また、PyTorch Lightningを使ったサンプルコードはこちらのリポジトリにいろいろ置いてあるので参考までに。<br />
* <a href="https://github.com/wellflat/pytorch-learning/tree/master/lightning/">pytorch-learning/lightning/ at master · wellflat/pytorch-learning</a></p>
<h3 class="term">使い方</h3>
<p>公式ドキュメントはこちら</p>
<ul>
<li><a href="https://pytorch-lightning.readthedocs.io/en/1.4.5/advanced/lr_finder.html">Learning Rate Finder — PyTorch Lightning documentation</a></li>
</ul>
<p>使い方は2パターンあって別々に紹介します。まず一つ目は以下のような学習率を1つのインスタンス変数として設定する方法です。</p><pre class="crayon-plain-tag">## learning_rate か lr という名前の変数に設定
class LitModel(LightningModule):
    def __init__(self, learning_rate):
        self.learning_rate = learning_rate

    def configure_optimizers(self):
        return Adam(self.parameters(), lr=(self.lr or self.learning_rate))


model = LitModel()

# finds learning rate automatically
# sets hparams.lr or hparams.learning_rate to that learning rate
trainer = Trainer(auto_lr_find=True)

trainer.tune(model)

## 任意の名前の変数にも設定可能
model = LitModel()

# to set to your own hparams.my_value
trainer = Trainer(auto_lr_find=&quot;my_value&quot;)

# tuneメソッドで学習率探索・学習
trainer.tune(model)</pre><p>ただし、これは個人的にはあまりおすすめしない使い方です。というのも、上記のサンプルだとハイパーパラメータは学習率の一つしか設定していませんが、実際には他のパラメータやスケジューラの設定などもこのLightningModuleのサブクラスに渡すことになると思います。コンストラクタに引数がたくさんあるのは望ましくないので、その場合は設定用クラスを別で作って渡すか、Builderパターンを使って初期化処理を行いますが、この学習率設定方法だと柔軟なクラス設計ができません。また、Optionalなパラメータではないのにインスタンス生成時に引数を渡さないと、エディタやLintの設定によってはエラーになってしまいます。たぶんLightningの設計思想だとコード量を少なくするのが正義だと思うのでこういう実装方法も提供しているのでしょう。</p>
<p>ということで実装面での拡張を考慮するなら以下の二つ目の方法の方がおすすめです。</p><pre class="crayon-plain-tag">model = MyModelClass(hparams)
trainer = Trainer()

# lr_find メソッドを明示的に呼んで学習率探索
lr_finder = trainer.tuner.lr_find(model)

# results にロスと学習率のリストが入っている(後述)
lr_finder.results

# グラフの可視化 (要matplotlib)
fig = lr_finder.plot(suggest=True)
fig.show()

# 提案された学習率を取得
new_lr = lr_finder.suggestion()

# 提案された学習率で再設定 (設定先の変数は任意、ここではlr)
model.hparams.lr = new_lr

# 学習
trainer.fit(model)</pre><p>コード量自体は増えてしまいますが、後でDIコンテナ等の導入も容易ですし、学習率探索結果の可視化処理も切り分けて実行可能になります。</p>
<p>ちなみに個人的に最近よく使う手法としてはとしてはオプティマイザを<code><a href="https://pytorch.org/docs/stable/generated/torch.optim.RAdam.html">RAdam</a></code>、スケジューラには<code><a href="https://pytorch.org/docs/stable/generated/torch.optim.lr_scheduler.OneCycleLR.html">OneCycleLR</a></code>を使っています。fast.aiでいうところの<code><a href="https://fastai1.fast.ai/callbacks.one_cycle.html">fit_one_cycle</a></code>を使うイメージです。ちなみにRAdamは少し前までは外部モジュールを使う必要がありましたが(例えば<a href="https://github.com/LiyuanLucasLiu/RAdam">LiyuanLucasLiu /RAdam</a>など)、現在はPyTorch本体に実装済みなのでこちらを使っておけば良いでしょう。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/10/lr-radam.png?x40497" alt="" width="522" height="387" class="alignnone size-full wp-image-3699" srcset="https://rest-term.com/wp-content/uploads/2022/10/lr-radam.png 522w, https://rest-term.com/wp-content/uploads/2022/10/lr-radam-300x222.png 300w" sizes="(max-width: 522px) 100vw, 522px" /></p>
<h4 class="term">実装</h4>
<p>LightningでのLR Finderの実装はこちら。</p>
<ul>
<li><a href="https://github.com/Lightning-AI/lightning/blob/master/src/pytorch_lightning/tuner/lr_finder.py" class="broken_link">lightning/lr_finder.py at master · Lightning-AI/lightning</a></li>
</ul>
<p>提案する学習率の計算はシンプルにsteepest negative gradientで計算されます。文字通り内部スケジューラのステップがepochではなくstep指定(つまりミニバッチ毎)でlossが記録されていきます。</p>
<p>デフォルトの挙動は以下の通り</p>
<ul>
<li>探索範囲 1e-8 ~ 1.0 の間で<a href="https://pytorch.org/docs/stable/generated/torch.optim.lr_scheduler.ExponentialLR.html">ExponentialLR</a>により学習率をステップ毎に変更</li>
<li>ステップ毎の学習率とlossを逐次記録</li>
<li>最初と最後の数ステップ分は無視して、勾配が一番小さくなる時の学習率を提案</li>
</ul>
<p>内部的に計算されるlossは以下のようにモーメンタム法で減衰されてかつ平滑化されます。betaの初期値は0.98です。</p><pre class="crayon-plain-tag">self.avg_loss = self.beta * self.avg_loss + (1 - self.beta) * current_loss
smoothed_loss = self.avg_loss / (1 - self.beta ** (current_step + 1))</pre><p></p>
<p>提案される学習率を簡易的に実装すると以下のようにシンプルです。</p><pre class="crayon-plain-tag">lr_finder = trainer.tuner.lr_find(model)
# results にロス(loss)と学習率(lr)のリストが入っている Dict[str, List[float]]
suggested_lr = lr_finder.results['lr'][np.gradient(lr_finder.results['loss']).argmin()]</pre><p>実際には最初と最後の数ステップ分を無視したり、InfやNaNを無視する処理等が含まれていますが、簡易的には上記のように勾配が一番小さくなる時の学習率を返しているだけです。イメージとしては坂道を転げ落ち始める直前の値を使うという感じでしょうか。lossがモーメンタム+平滑化で計算されているのもこのためだと思います。</p>
<h4 class="term">補足・注意点</h4>
<p>LR Finder利用時の細かい注意点についても紹介しておきます。学習率探索時の探索範囲の指定ができるようになっていますが範囲の設定によって提案される学習率が変わります。</p><pre class="crayon-plain-tag"># min_lr, max_lr でそれぞれ探索範囲下限、上限を指定できる
# デフォルトは min_lr=1e-8, max_lr=1.0
lr_finder = trainer.tuner.lr_find(model=model, datamodule=data_module, min_lr=1e-5, max_lr=1e-3)</pre><p>注意点としては、例えば適切な学習率はだいたい1e-4くらいかなと考えて探索範囲を上記のように意図的に狭めてしまうと、内部スケジューラ等の影響によって最終的に提案される学習率が大きく変わってしまうことがあります。併せて学習率提案時のオプションを指定する方法もあり、最初と最後のステップをいくつくらい無視するか指定することもできます。</p><pre class="crayon-plain-tag"># skip_begin, skip_endでそれぞれ最初と最後のサンプルを指定数分参照しないようにする
new_lr = lr_finder.suggestion(skip_begin=15, skip_end=1)</pre><p>内部実装も見つつオプションをいろいろ試してみましたが、そもそもLR Finderを使う目的がパラメータの自動設定なので全部おまかせ(デフォルト)で良いのではないかと思いました。</p>
<p>あとは、学習率探索処理を実行するとモデルのパラメータが更新されてチェックポイントファイルが中間ファイルとして強制的にファイル出力されます。実行途中になんらかの理由でプロセスが停止するとチェックポイントファイルが削除されずに残ってしまうので注意してください。ギリギリのバッチサイズを攻めているとCUDA out of memoryが出てゴミファイルが残され続けてディスクを逼迫させてしまうことが何度かありました。</p>
<h4 class="term">可視化</h4>
<p>matplotlibモジュールが入っている場合は前述の通り<code>plot</code>関数を呼ぶだけでグラフが表示されます。ただし、<code>plot</code>関数ではグラフの体裁を整えたりはできませんし、matplotlibではなくSeabornやBokehなどモダンな可視化モジュールを使っている人は自前でプロット処理を書いても良いかもしれません。描画に必要なデータは以下のように取得できます。</p><pre class="crayon-plain-tag">lr_finder = trainer.tuner.lr_find(model)
# results: Dict[str, List[float]]  ロス(loss)と学習率(lr)のリスト 
loss = lr_finder.results['loss']
lr = lr_finder.results['lr']
# _optimal_idx: int 提案する学習率lrのインデックス
optimal_idx = lr_finder._optimal_idx</pre><p></p>
<p>MLOps環境ではnotebook環境は使わずにサーバ環境で自動実行されるので、処理結果の可視化はMLOpsツールに任せるか、HTML出力して後でブラウザで見ることになるでしょう。MLOpsツールを使わない場合はHTML出力ができてインタラクティブな操作もできる<a href="https://bokeh.org/">Bokeh</a>が最近だと良いかもしれません。</p>
<p>サンプルとしては以下のようにLR Finderの結果を可視化できます。</p><pre class="crayon-plain-tag">from bokeh.plotting import figure, show, output_file

def plot_bokeh(lrs: List[float], losses: List[float]):
    p = figure(
        title='LR finder',
        x_axis_label='Learning rate',
        y_axis_label='Loss',
        x_axis_type='log'
    )
    p.line(x=lrs, y=losses)
    grads = np.gradient(losses)
    skip_grads = grads[10:-1] # 最初と最後の数ステップ分は不安定なのでスキップ
    optimal_idx = skip_grads.argmin()
    p.circle(x=lrs[optimal_idx], y=losses[optimal_idx], color='red', size=10, alpha=0.5)
    output_file('results.html')  ## 任意のHTMLファイル出力
    show(p)</pre><p></p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot.png?x40497" alt="" width="400" height="396" class="alignnone size-full wp-image-3702" srcset="https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot.png 400w, https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot-300x297.png 300w, https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot-144x144.png 144w" sizes="(max-width: 400px) 100vw, 400px" /><br />
<img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot2.png?x40497" alt="" width="400" height="393" class="alignnone size-full wp-image-3703" srcset="https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot2.png 400w, https://rest-term.com/wp-content/uploads/2022/10/bokeh_plot2-300x295.png 300w" sizes="(max-width: 400px) 100vw, 400px" /></p>
<p>↑のようにブラウザ上でインタラクティブに拡大縮小できるし、画像ファイル出力もできます。matplotlibよりも各APIの使い方が簡単ですし、出力されるHTMLは描画データもJavaScriptも埋め込まれるので.htmlファイル1枚で完結するところも良いですね。</p>
<h3 class="term">おわりに</h3>
<p>PyTorch LightningでのLR Finderの使い方を紹介しました。これをやって実務レベルでどれくらいのメリットがあるかというところについては適用するドメインで当然変わってくると思います。例えば広告配信系ならKaggleレベルの小数点以下の細かい精度向上に取り組むことも多いでしょうし、逆に画像認識系ならちょっとくらい物体検出の枠がズレていても使う人が気にしないのであればそこまで細かい数字を追い求めなくて良いこともあります。ある程度は経験によって適切な初期学習率はだいたいわかることもあるかもしれませんが、オプティマイザとスケジューラ、バッチサイズの組み合わせまで含めて経験に頼るのは厳しいでしょう。</p>
<p>最近は研究者やデータサイエンティストではない普通のWebエンジニアも機械学習モデルをさくっと作る時代ですから、学習率を良い感じに提案してくれる機能があればどんどん活用した方が良いと思います。</p>
<p>長らく素のPyTorchを使っている人にとってLightningを使うモチベーションは低いかも知れませんが、けっこう便利なのでこういうラッパーフレームワークを使うのも悪くないです。Lightningを使うくらいならfast.aiでいいやってなるかもしれませんが、宗教上の理由もあると思いますのでそこはよしなに。</p>
<p>* <a href="https://github.com/wellflat/pytorch-learning/tree/master/lightning/">pytorch-learning/lightning/ at master · wellflat/pytorch-learning</a></p>
<h3 class="term">参考</h3>
<p><a href="https://forums.fast.ai/t/new-lr-finder-output/89236/3">New LR Finder Output?! &#8211; fastai &#8211; fast.ai Course Forums</a><br />
<a href="https://github.com/Lightning-AI/lightning/blob/master/src/pytorch_lightning/tuner/lr_finder.py" class="broken_link">lightning/lr_finder.py at master · Lightning-AI/lightning</a><br />
<a href="https://pytorch.org/ignite/generated/ignite.handlers.lr_finder.FastaiLRFinder.html">ignite.handlers > FastaiLRFinder</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3697/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>JAXライクなfunctorchで機械学習を速くする – part 2</title>
		<link>https://rest-term.com/archives/3691/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=jax%25e3%2583%25a9%25e3%2582%25a4%25e3%2582%25af%25e3%2581%25aafunctorch%25e3%2581%25a7%25e6%25a9%259f%25e6%25a2%25b0%25e5%25ad%25a6%25e7%25bf%2592%25e3%2582%2592%25e9%2580%259f%25e3%2581%258f%25e3%2581%2599%25e3%2582%258b-part-2</link>
					<comments>https://rest-term.com/archives/3691/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 07 Jun 2022 10:12:29 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3691</guid>

					<description><![CDATA[今回も引き続きfunctorchを使っていろいろ試してみます。前回のエントリーはfunctorchの基本機能を紹介しました。今回はfunctorchによる機械学習のユースケースについて考えてみたいと思います。gradやvmapなどの基本機能の説明は前回のエントリーを参照してください。 JAXライクなfunctorchで機械学習を速くする – part 1 いろいろ試してみて気付いたのですが、結論から言うと、functorchは適切に使えば性能を発揮できます。ただし、従来のオブジェクト指向や手続き&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回も引き続きfunctorchを使っていろいろ試してみます。前回のエントリーはfunctorchの基本機能を紹介しました。今回はfunctorchによる機械学習のユースケースについて考えてみたいと思います。<code>grad</code>や<code>vmap</code>などの基本機能の説明は前回のエントリーを参照してください。</p>
<ul>
<li><a href="https://rest-term.com/archives/3688/">JAXライクなfunctorchで機械学習を速くする – part 1</a></li>
</ul>
<p>いろいろ試してみて気付いたのですが、結論から言うと、functorchは適切に使えば性能を発揮できます。ただし、従来のオブジェクト指向や手続き型の処理を関数型っぽく書き換えるための準備と再設計が必要です。特に機械学習の文脈で内部状態を持たないようにするのは実際には難しいので、そこはfunctorchがある程度は上手くカバーしてくれますが、全てを置き換えるのではなく有効な箇所を少しずつ置き換えていく形で試してみるのが良いと思いました。</p>
<h3 class="term">環境</h3>
<p>Google Colab Pro<br />
CUDA Version: 11.2<br />
Tesla T4 / P100-PCIE 16GB VRAM<br />
torch-1.11.0+cu102<br />
functorch-0.1.1</p>
<p>前回はあえてCPUインスタンスを使いましたが今回は素直にGPUインスタンスを使います。現在はColab ProだとP100かT4が割り当てられるようですが、Pro+だとA100が当たることもあるので羨ましいです。</p>
<h3 class="term">ニューラルネットワークの高速化</h3>
<ul>
<li><a href="https://pytorch.org/functorch/stable/notebooks/per_sample_grads.html">Per-sample-gradients — functorch 0.1.1 documentation</a></li>
</ul>
<p>公式のチュートリアルを多少改変、補足や注意事項を加えて説明します。</p>
<p>ベースとなるモデルは以下のようなシンプルなCNNを定義しておきます。イメージとしてはAlexNetっぽい構造のモデルをCIFAR-10用に簡略化したものにしています。注意点として、Dropout層のようなランダム要素を含むレイヤーは意図的に一旦除いています。</p>
<p></p><pre class="crayon-plain-tag">## AlexNet like CNN for CIFAR-10
class SimpleCNN(nn.Module):
    features: nn.Sequential
    classifier: nn.Sequential
    flatten: nn.Flatten

    def __init__(self, num_classes: int=10):
        super(SimpleCNN, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(64, 192, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2)
        )
        self.flatten = nn.Flatten()
        self.classifier = nn.Sequential(
            # nn.Dropout(),  ここではDropout層は除いておく、説明は後述
            nn.Linear(256 * 2 * 2, 4096),
            nn.ReLU(),
            # nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Linear(4096, num_classes),
        )
    
    def forward(self, x: torch.Tensor) -&gt; torch.Tensor:
        x = self.features(x)
        x = self.flatten(x)
        logits = self.classifier(x)
        return logits

## 損失関数 (クロスエントロピー)
def loss_fn(predictions, targets):
    return F.cross_entropy(predictions, targets)</pre><p></p>
<p>学習データは適当に作ります。もちろんtorchvisionからCIFAR-10の実データを取ってきて使ってもOKです。バッチサイズは環境に合わせて適宜調整できますが、ベンチマーク目的なので小さくしすぎないように注意してください。またCIFAR-10用のCNNなのでデータの形状はモデルの入力に合うようにしておきます。</p><pre class="crayon-plain-tag">device = &quot;cuda:0&quot; if torch.cuda.is_available() else &quot;cpu&quot;
batch_size = 32
data = torch.randn(batch_size, 3, 32, 32, device=device)
targets = torch.randint(10, (batch_size,), device=device)
model = SimpleCNN().to(device=device)</pre><p></p>
<p>PyTorchで従来の学習のステップを書くときに、以下のようなコードがよく出てくるかと思います。</p><pre class="crayon-plain-tag">predictions = model(data)
loss = loss_fn(predictions, targets)  ## loss_fnは任意の損失関数
loss.backward()</pre><p>loss.backward()でミニバッチ毎の勾配の平均を求めていますが、functorchを効果的に適用するためにミニバッチ単位ではなくサンプル毎の勾配を求めるように修正します。まずは、functorchを使わない場合は以下のように書けます。</p>
<p></p><pre class="crayon-plain-tag">def compute_grad(sample, target):
    sample = sample.unsqueeze(0) 
    target = target.unsqueeze(0)
    prediction = model(sample)
    loss = loss_fn(prediction, target)
    return torch.autograd.grad(loss, list(model.parameters()))

def compute_sample_grads(data, targets):
    &quot;&quot;&quot; manually process each sample with per sample gradient &quot;&quot;&quot;
    sample_grads = [compute_grad(data[i], targets[i]) for i in range(batch_size)]
    sample_grads = zip(*sample_grads)
    sample_grads = [torch.stack(shards) for shards in sample_grads]
    return sample_grads</pre><p>model(sample)の入力sampleは(N,C,H,W)形式なので、その前にunsqueeze(0)でバッチ分の次元を追加しています。</p>
<h4 class="term"><code>make_functional</code></h4>
<p>次にfunctorchを使って書き直します。gradとvmapは前回紹介しましたが、ここでもう一つ新しい機能が登場します。</p><pre class="crayon-plain-tag">from functorch import make_functional, grad, vmap
fmodel, params = make_functional(model)</pre><p><code>make_functional(model)</code> はニューラルネットワークのモデルと状態(パラメータ)を分離する関数です。これを通すことで上記のfmodelは純粋関数となり、参照透過性を持ちます(同じ入力なら常に同じ出力)。functorchでは以下のような<code>FunctionalModule</code>クラスのオブジェクトとして扱われます。</p><pre class="crayon-plain-tag">FunctionalModule(
  (stateless_model): SimpleCNN(
    (features): Sequential(
      (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
      (1): ReLU()
      (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
      (3): Conv2d(64, 192, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      (4): ReLU()
      (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
      (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
... 省略</pre><p>注意点として、ここで分離されたパラメータオブジェクト(torch.nn.parameter.Parameter)には勾配情報を保存しておく必要はありませんので無効にしておきます。エラーが出るわけではありませんが、関数型のアプローチでは内部状態を更新するような処理は行いません。</p><pre class="crayon-plain-tag">for param in params:
    param.requires_grad = False</pre><p>ちなみにこのエントリー執筆時にはまだ使えませんでしたが、make_functionalに<code>disable_autograd_tracking</code>パラメータというものが付くそうですので最新版ではこれを使いましょう。requires_gradをいちいち変更する手間が省けます。</p>
<ul>
<li><a href="https://github.com/pytorch/functorch/pull/701">Added params_require_grad arg to make_functional* by vfdev-5 · Pull Request #701 · pytorch/functorch</a></li>
</ul>
<p></p><pre class="crayon-plain-tag">## disable_autograd_tracking
make_functional(model, disable_autograd_tracking=True)</pre><p></p>
<p>また、<code>make_functional_with_buffer</code> という亜種もあります。モデル内部に任意領域としてのバッファを持っていることを知らない人もいるかもしれませんが、たまに便利なので実際にはこちらを使うことをおすすめします。</p><pre class="crayon-plain-tag">## torch.nn.Module.buffersオブジェクトのデータを分離
fmodel, params, buffers = make_functional_with_buffers(model)

## ここではbuffersの中身は無いので空のtupleが返る
print(model.buffers(), buffers)
&lt;generator object Module.buffers at 0x7f90e0634bd0&gt; ()</pre><p>bufferはどういうときに使うのかは以下のフォーラムスレッドを参考に。</p>
<ul>
<li><a href="https://discuss.pytorch.org/t/what-pytorch-means-by-buffers/120266">What pytorch means by buffers? &#8211; PyTorch Forums</a></li>
</ul>
<p>話を戻して、前述のcompute_grad関数をFunctionalModuleを使って書き換えます。モデルと分離されたパラメータを忘れずに持ってきましょう。</p><pre class="crayon-plain-tag">def compute_loss_stateless_model(params, sample, target):
    batch = sample.unsqueeze(0)
    targets = target.unsqueeze(0)
    predictions = fmodel(params, batch)
    loss = loss_fn(predictions, targets)
    return loss</pre><p>次に前述のcompute_sample_grads関数をfunctorchのgradとvmapを使って書き換えると以下のようにシンプルになり、vmapによりサンプル毎の計算は並列処理されます。</p><pre class="crayon-plain-tag">##　サンプル毎の勾配計算
ft_compute_sample_grads = vmap(grad(compute_loss_stateless_model), in_dims=(None, 0, 0))

## もちろん分けて書いても良い
ft_compute_grad = grad(compute_loss_stateless_model)
ft_compute_sample_grads = vmap(ft_compute_grad, in_dims=(None, 0, 0))</pre><p></p>
<p>ここまでの処理においてfunctorchの利用有無で動作確認しておきます。サンプル毎の勾配の値が同じかどうか確認するテストコードになっています。</p><pre class="crayon-plain-tag">per_sample_grads = compute_sample_grads(data, targets)  # functorch使わない版
ft_per_sample_grads = ft_compute_sample_grads(params, data, targets)  # functorch版

# we can double check that the results using functorch grad and vmap match the results of hand processing each one individually:
for per_sample_grad, ft_per_sample_grad in zip(per_sample_grads, ft_per_sample_grads):
    assert torch.allclose(per_sample_grad, ft_per_sample_grad, atol=3e-3, rtol=1e-5)</pre><p>アサートエラーがでなければ問題ないです。</p>
<h4 class="term">注意点</h4>
<p>vmapを適用する関数について、特にランダム要素の有無に注意する必要があります。適用する関数は参照透過性が必要であるため、同じ入力でも実行の度に結果が変わるような関数を扱うことはできません(副作用については後述)。例えば今回定義したCNNでは意図的にDropout層を除いていますが、そのままDropout層を入れても以下のようなエラーが出ます。エラーメッセージがわかりやすいのですぐにミスに気付けますね。</p><pre class="crayon-plain-tag">RuntimeError: vmap: called random operation while in randomness error mode. Please either use the 'same' or 'different' randomness flags on vmap or perform the randomness operation out of vmap</pre><p>しかし、機械学習において乱数的な振る舞いはいろんな所で必要になってくるのでfunctorchではそのためのオプションが用意されています。</p>
<blockquote><p>randomness (str) – Specifies whether the randomness in this vmap should be the same or different across batches. If ‘different’, the randomness for each batch will be different. If ‘same’, the randomness will be the same across batches. If ‘error’, any calls to random functions will error. Default: ‘error’. WARNING: this flag only applies to random PyTorch operations and does not apply to Python’s random module or numpy randomness.</p></blockquote>
<p>もしランダム要素を含む関数を扱う場合はvmapのrandomnessオプションを使えば上記エラーは回避できます。ただし、&#8221;same&#8221;を付けると全てのバッチで同じ値となるので留意しておきます。</p><pre class="crayon-plain-tag">ft_compute_sample_grad = vmap(grad(compute_loss_stateless_model), in_dims=(None, 0, 0), randomness=&quot;same&quot;)</pre><p></p>
<h4 class="term">ベンチマーク</h4>
<p>ベンチマークは公式チュートリアルにあるコードをほぼそのまま使いました。PyTorchにベンチマーク用パッケージ(<a href="https://pytorch.org/docs/stable/benchmark_utils.html">torch.utils.benchmark</a>)があるのを初めて知りました。こちらも環境によって反復回数(n_timeit)を調整します。</p><pre class="crayon-plain-tag">## functorchの利用有無で勾配計算を反復実行して速度比較
torch.backends.cudnn.benchmark = True

def get_perf(first, first_descriptor, second, second_descriptor):
  &quot;&quot;&quot;  takes torch.benchmark objects and compares delta of second vs first. &quot;&quot;&quot;
  second_res = second.times[0]
  first_res = first.times[0]

  gain = (first_res-second_res)/first_res
  if gain &lt; 0: gain *=-1 
  final_gain = gain*100

  print(f&quot; Performance delta: {final_gain:.4f} percent improvement with {first_descriptor} &quot;)


from torch.utils.benchmark import Timer

without_vmap = Timer(stmt=&quot;compute_sample_grads(data, targets)&quot;, globals=globals())
with_vmap = Timer(stmt=&quot;ft_compute_sample_grads(params, data, targets)&quot;, globals=globals())
n_timeit = 100
no_vmap_timing = without_vmap.timeit(n_timeit)
with_vmap_timing = with_vmap.timeit(n_timeit)

print(f'Per-sample-grads without vmap {no_vmap_timing}')
print(f'Per-sample-grads with vmap {with_vmap_timing}')
get_perf(with_vmap_timing, &quot;vmap&quot;, no_vmap_timing,&quot;no vmap&quot; )</pre><p></p>
<p>Tesla T4/P100-PCIEそれぞれの環境でのベンチマーク結果は以下のようになりました。</p><pre class="crayon-plain-tag">## T4
Per-sample-grads without vmap &lt;torch.utils.benchmark.utils.common.Measurement object at 0x7f5191f33850&gt;
compute_sample_grads(data, targets)
  93.55 ms
  1 measurement, 100 runs , 1 thread
Per-sample-grads with vmap &lt;torch.utils.benchmark.utils.common.Measurement object at 0x7f51921d0e50&gt;
ft_compute_sample_grads(params, data, targets)
  21.50 ms
  1 measurement, 100 runs , 1 thread
 Performance delta: 335.1771 percent improvement with vmap 

## P100-PCIE
Per-sample-grads without vmap &lt;torch.utils.benchmark.utils.common.Measurement object at 0x7f90e169b190&gt;
compute_sample_grads(data, targets)
  88.88 ms
  1 measurement, 100 runs , 1 thread
Per-sample-grads with vmap &lt;torch.utils.benchmark.utils.common.Measurement object at 0x7f90e17731d0&gt;
ft_compute_sample_grads(params, data, targets)
  12.70 ms
  1 measurement, 100 runs , 1 thread
 Performance delta: 600.1182 percent improvement with vmap</pre><p>Tesla T4は推論処理用アクセラレータと謳っているのでここで使うには適切ではないと思われますが、<strong>T4だと300%強、P100で600%ほど高速化</strong>されました。torch.utils.benchmarkパッケージで実行される処理については内部実装を深堀りしてはいませんが、公式モジュールですし結果は信用して良いと思います。ここではTimerしか使ってないですが他にも細かくいろいろ設定できるらしいのでまた別エントリーかgist等で紹介します。</p>
<h3 class="term">アンサンブル学習の高速化</h3>
<p>モデルのアンサンブル学習もfunctorchで効率化できます。</p>
<ul>
<li><a href="https://pytorch.org/functorch/stable/notebooks/ensembling.html">Model ensembling — functorch 0.1.1 documentation</a></li>
</ul>
<p>今回のエントリーで紹介したサンプル毎の勾配計算の並列化が理解できれば上記チュートリアルもすぐ理解できると思うのでここでは軽く紹介します。vmapによる並列化がモデル単位になっただけです。つまり、functorchの文脈ではモデルオブジェクトは純粋関数として扱えるのでそのままvmapに適用できます。</p>
<p></p><pre class="crayon-plain-tag">## アンサンブルのためにモデルを複数作り、データを分割する
num_models = 10
models = [SimpleCNN().to(device) for _ in range(num_models)]
minibatches = data[:num_models]

## combine_state_for_ensemble でスタックされたパラメータとバッファーに分離
## https://pytorch.org/functorch/stable/generated/functorch.combine_state_for_ensemble.html
from functorch import combine_state_for_ensemble
fmodel, params, buffers = combine_state_for_ensemble(models)

## vmapでベクタライズ、即時適用
predictions = vmap(fmodel)(params, buffers, minibatches)</pre><p>上記例は同じモデルを10つ並べているだけなので実用として使えるようなものではないですが、functorchによるアンサンブル処理のイメージは掴めるのではないでしょうか。</p>
<p>注意点としては、シングルノードで実行されるのでコンピューティングリソース(特にVRAM)が潤沢である必要があります。マルチノードでの分散学習は準備が面倒でコストがかかるので、もしスペックの高いサーバが一台あればfunctorchでアンサンブル学習の並列化を試してみるのも良いかもしれません。</p>
<h3 class="term">補足事項など</h3>
<p>今回試したベンチマークはあくまで学習処理の一部分(勾配計算)のみ切り出して計測したものです。実際の学習処理では<code>torch.optim.Optimizer</code>でのパラメータ更新部分も同様に対応する必要がありますが、実装が煩雑にならないようにするための議論がこちらで行われています。</p>
<ul>
<li><a href="https://github.com/pytorch/functorch/issues/372">Feature Request: make_functional for torch.optim.Optimizer · Issue #372 · pytorch/functorch</a></li>
</ul>
<p>また、PyTorchでは破壊的な(副作用のある)オペレーションがたくさんありますが、functorchではそれを上手く隠蔽・除去して変換してくれるようです。例えば、<code>torch.nn.ReLU</code> など一部のレイヤーはinplaceパラメータがありますが、これをTrueにしていても特に挙動がおかしくなったり、エラーが発生することはありません。<code>make_functional</code>が内部で丁寧にパラメータを抽出・分離してくれるようです。</p>
<p>ただし、PyTorchが提供している破壊的な関数(<code>xxx_()</code>)は以下のようにvmapを適用しても問題なく破壊的に動作します。意図的にこういう実装はしないとは思いますが、このような破壊的な操作を伴う関数を使う際は注意してください。</p><pre class="crayon-plain-tag">def add(x, y):
  x.add_(y)
  return x

x = torch.randn(3)
y = torch.randn(3)
print(x, y)
print(vmap(add, in_dims=(0, 0))(x, y))
print(x, y)

動作結果、xの値変更されている
tensor([ 0.2734, -0.9181, -0.0404]) tensor([ 0.2881, -0.0075, -0.9145])
tensor([ 0.5615, -0.9256, -0.9549])
tensor([ 0.5615, -0.9256, -0.9549]) tensor([ 0.2881, -0.0075, -0.9145])</pre><p></p>
<p>あとvmapでは今のところ制御文も使えません。</p><pre class="crayon-plain-tag">def relu(x):
  if x &gt; 0:
    return x
  return 0

x = torch.randn(3)
vmap(relu)(x)

RuntimeError: vmap: It looks like you're attempting to use a Tensor in some data-dependent control flow. We don't support that yet, please shout over at https://github.com/pytorch/functorch/issues/257 .</pre><p>これは上記issuesで議論中のようです。JAXにはjax.lax.cond関数があるのでこちらを使うようですが、functorchでもたぶんすぐ対応されると思います。</p>
<ul>
<li><a href="https://jax.readthedocs.io/en/latest/_autosummary/jax.lax.cond.html?highlight=cond">jax.lax.cond — JAX documentation</a></li>
</ul>
<h3 class="term">終わりに</h3>
<p>冒頭でも書いたように、functorchで効率的に機械学習を行うには関数型の考え方に切り替える必要があるようです。単に <code>make_functional</code> でモデルと状態を切り分けるだけでは、デバッグが少々楽になる気がするだけで効率化には繋がりません。バグをなるべく防ぎつつ高速化したい場合のアプローチとしては、なんとなく以下のように取り組むと良さそうです。</p>
<ol>
<li>モデルと状態(パラメータ)を分離させる -> <code>make_functional / make_functional_with_buffers</code></li>
<li>微分対象の処理単位を小さくする(モジュラー性) -> <code>grad</code></li>
<li>参照透過性を備えた2.の処理単位で並列化 -> <code>vmap</code></li>
</ol>
<p>こう書いてみると簡単ですが、これまでバッチ単位で処理することに慣れていた人達にとっては抵抗感があるのではないでしょうか。例えばBatch Normalizationの扱いはどうなるの、とか。それからあんまり調子に乗ってvmapしてると CUDA out of memory. が頻発するのでリソース調整をより慎重に行う必要があります。今回はモデルアンサンブルの例を一応挙げましたが、実際に行うのは現実的ではないかも知れません。いろんな種類のモデルをvmapに適用できる形に全て変換するのは大変だし、シングルノードで学習するのはGPUリソースがまず足りないでしょう。</p>
<p>また、モデル学習処理全体を効率化したい場合は、例えばDataLoaderの取り扱いやGPU周りの設定、TorchScriptの利用なども含めて満遍なく対応することになります。それらと併せてfunctorchも適切に使えば学習処理のコアな部分を集中的に高速化できるので、がんばってチャレンジする価値はあるかもしれません。個人的にはオプティマイザー周りの処理(パラメータの更新)がもっと綺麗に書けるようになったら是非採用したいと思っています。</p>
<p><a href="https://paperswithcode.com/">Papers With Code</a>のTrending Researchとか見てると、明らかにPyTorchでのリファレンス実装が多いようです。高速化のためにJAX/Flax移植をがんばったり、マルチノード/マルチGPUの分散学習環境を準備するよりも、functorchの利用で十分高速化するのなら低コストで済むので助かりますね。</p>
<p>最後に注意点として、functorchは2022/06現在まだβ版となっています。APIも今後仕様が変わる可能性があるので利用の際は注意しましょう。</p>
<p>参考:</p>
<ul>
<li><a href="https://pytorch.org/functorch/stable/notebooks/per_sample_grads.html">Per-sample-gradients — functorch 0.1.1 documentation</a></li>
<li><a href="https://pytorch.org/functorch/stable/notebooks/ensembling.html">Model ensembling — functorch 0.1.1 documentation</a></li>
<li><a href="https://wandb.ai/functorch-examples/functorch-examples/reports/Working-with-FuncTorch-An-Introduction--VmlldzoxNzMxNDI1">Working-with-FuncTorch-An-Introduction &#8211; Weights &#038; Biases</a></li>
<li><a href="https://www.mattari-benkyo-note.com/2021/11/17/ssw-jax-vs-torch/">JAXとPyTorch、どっちが速いのか検証してみた &#8211; まったり勉強ノート</a></li>
<li><a href="https://techblog.zozo.com/entry/scalable-machine-learning-with-JAX#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%A3%E3%81%A6%E6%9C%AC%E5%BD%93%E3%81%AB%E5%A4%A7%E3%81%8D%E3%81%84%E3%81%AE%E3%81%84%E3%81%A4%E3%81%A9%E3%81%93%E3%81%A7%E3%81%A9%E3%81%86%E3%82%84%E3%81%A3%E3%81%A6%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B%E3%81%AE">JAXによるスケーラブルな機械学習 &#8211; ZOZO TECH BLOG</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3691/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>JAXライクなfunctorchで機械学習を速くする &#8211; part 1</title>
		<link>https://rest-term.com/archives/3688/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=jax%25e3%2583%25a9%25e3%2582%25a4%25e3%2582%25af%25e3%2581%25aafunctorch%25e3%2581%25a7%25e6%25a9%259f%25e6%25a2%25b0%25e5%25ad%25a6%25e7%25bf%2592%25e3%2582%2592%25e9%2580%259f%25e3%2581%258f%25e3%2581%2599%25e3%2582%258b-part-1</link>
					<comments>https://rest-term.com/archives/3688/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Wed, 04 May 2022 17:13:43 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3688</guid>

					<description><![CDATA[PyTorch 1.11からβ版として追加された functorch と呼ばれる機能を試してみました。PyTorch 1.9くらいのときから試験版として本体に組み込まれて提供されていましたが、どうやらfunctorchという別モジュールに切り出して提供されるようになったようです。 pytorch/functorch: functorch is JAX-like composable function transforms for PyTorch. functorchとは PyTorch公式サイト&#8230;]]></description>
										<content:encoded><![CDATA[<p>PyTorch 1.11からβ版として追加された <strong><code>functorch</code></strong> と呼ばれる機能を試してみました。PyTorch 1.9くらいのときから試験版として本体に組み込まれて提供されていましたが、どうやらfunctorchという別モジュールに切り出して提供されるようになったようです。</p>
<ul>
<li><a href="https://github.com/pytorch/functorch">pytorch/functorch: functorch is JAX-like composable function transforms for PyTorch.</a></li>
</ul>
<h3 class="term">functorchとは</h3>
<p>PyTorch公式サイトには以下のように説明されています。</p>
<blockquote><p>functorch is a library that adds composable function transforms to PyTorch. It aims to provide composable vmap (vectorization) and autodiff transforms that work with PyTorch modules and PyTorch autograd with good eager-mode performance.</p></blockquote>
<ul>
<li>computing per-sample-gradients (or other per-sample quantities)</li>
<li>running ensembles of models on a single machine</li>
<li>efficiently batching together tasks in the inner-loop of MAML</li>
<li>efficiently computing Jacobians and Hessians</li>
<li>efficiently computing batched Jacobians and Hessians</li>
</ul>
<p>PyTorch単体では記述が面倒だったサンプル毎の勾配計算やモデルアンサンブルの計算等を効率良く実行することができます。どうやら自動微分と<a href="https://ja.wikipedia.org/wiki/%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E5%8C%96">ベクトル化</a>が目玉機能のようです。</p>
<p>functorchはGoogle JAXをインスパイアしておりAPIの見た目もだいたいJAXと同じです。もちろんデータの取り回しはJAX(jax.numpy)のDeviceArrayではなくtorch.TensorとなるのでPyTorch用に最適化されています。</p>
<ul>
<li><a href="https://github.com/google/jax">google / jax &#8211; GitHub</a></li>
</ul>
<p>最初は公式サイトのサンプルを動かしつつ応用的なコードも書いていきますが、このエントリーではfunctorchの全ての機能は説明できないので、少しずつ分かる範囲だけ試していこうと思います。今回は基本機能を知る準備編(part 1)ですね。処理速度などの非機能面については次回以降のエントリーで紹介します。</p>
<h3 class="term">導入</h3>
<p>Python開発環境がある程度整っていればインストールは簡単です。Google Colab無料版でも動くので環境が用意出来ない人はColabを使うといいでしょう。</p><pre class="crayon-plain-tag"># For CUDA 10.2
# &gt;&gt;&gt; pip install torch==1.11.0+cu102 torchvision==0.12.0+cu102 torchaudio==0.11.0+cu102 -f https://download.pytorch.org/whl/cu102/torch_stable.html
# For CUDA 11.3
# &gt;&gt;&gt; pip install torch==1.11.0+cu113 torchvision==0.12.0+cu113 torchaudio==0.11.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html
# For CPU-only build
# &gt;&gt;&gt; pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu -f https://download.pytorch.org/whl/cpu/torch_stable.html

# ここではColab無料版にPyTorchと関連モジュールのCPU版をインストール
pip install torch==1.11.0+cpu torchvision==0.12.0+cpu torchaudio==0.11.0+cpu -f https://download.pytorch.org/whl/cpu/torch_stable.html</pre><p>functorchは独立したモジュールなのでpipで別途インストールします。</p><pre class="crayon-plain-tag">pip install functorch</pre><p>インストールできているかどうか動作確認します。</p><pre class="crayon-plain-tag">import torch
from functorch import vmap, grad

x = torch.randn(3)
y = vmap(torch.sin)(x)
assert torch.allclose(y, x.sin())

x = torch.randn([])
y = grad(torch.sin)(x)
assert torch.allclose(y, x.cos())

# assertエラーが出なければOK</pre><p>↑のサンプルコードの意味は以降で説明するのでとりあえず<code>vmap</code>と<code>grad</code>が読み込みできていればOKです。</p>
<h3 class="term">使い方</h3>
<p>functorchの説明には&#8221;composable function transforms&#8221;という表現が頻出しますが、コード上では高階関数を作って運用します。ここからは関数型に頭を切り替えて使っていきましょう。</p>
<h4 class="term">grad (gradient computation)</h4>
<blockquote><p>functorch has auto-differentiation transforms (grad(f) returns a function that computes the gradient of f)</p></blockquote>
<p><code>functorch.grad</code>はわかりやすいのでまずこちらから試します。gradは関数の自動微分をやってくれるAPIで、冒頭の公式説明にもあったようにサンプル毎の勾配計算にも利用できますし、後述の<code>functorch.vmap</code>と組み合わせて使うことで機械学習処理のパフォーマンスを向上させることができます。</p>
<p>まずおさらいとして、PyTorch単体で自動微分する場合、backward()後にtorch.Tensorのgrad属性を参照することで微分係数を得ることができます。</p><pre class="crayon-plain-tag">def func(x):
    ''' f(x) = x^3 - 3x^2 + 9x + 2'''
    return x**3 - 3 * x**2 + 9*x + 2 # 3x^2 - 6x + 9

def dx_func(x):
    x = torch.tensor(x, requires_grad=True)
    y = func(x)
    y.backward()
    return x.grad

x = 2.0
print(&quot;f(x) =&quot;, func(x))  # f(x) = 16.0
print(&quot;df(x)/dx =&quot;, dx_func(x))  # df(x)/dx = tensor(9.)</pre><p>上記コードのdx_func関数を<code>grad</code>を使って書くと以下のように書けます。<code>grad</code>は新しい関数を作って返すので、それを即時に適用することができます。</p><pre class="crayon-plain-tag"># functorch.gradでdx_funcを定義・適用
grad(func)(torch.tensor(x))  # tensor(9.)

# もちろん分けて書くこともできる
grad_func = grad(func)
grad_func(torch.tensor(x))

# lambdaを使って一つの式にまとめると
grad(lambda x: x**3 - 3 * x**2 + 9*x + 2)(torch.tensor(x))</pre><p>↑の例では適当に遊んでみただけですが、こういうIIFE(Immediately Invoked Function Expression: 即時実行関数式)な書き方は昔のJavaScriptでよく使われてましたね。Pythonだとあまり見かけないかもしれませんが、関数型言語っぽい書き味に慣れていきましょう。</p>
<p>さて、導入時の動作確認用サンプルコードを改めて確認してみると、今なら簡単に理解できるかと思います。</p><pre class="crayon-plain-tag">x = torch.randn([])  # 要素数1の乱数(torch.Tensor)
y = grad(torch.sin)(x)  # sinを微分した関数(cos)を生成・即時適用
assert torch.allclose(y, x.cos())</pre><p>ちなみにtorch.allclose関数は第一引数と第二引数の値がほぼ等しいかどうかをチェックする関数で、NumPyにも同名の関数があります。<br />
* <a href="https://pytorch.org/docs/stable/generated/torch.allclose.html">torch.allclose — PyTorch 1.11.0 documentation</a></p>
<p>もちろん多変数関数の自動微分もできます。gradのargnumsパラメータで微分対象の変数を指定します。</p><pre class="crayon-plain-tag"># x, y, zの3変数関数
g = lambda x, y, z: torch.sqrt(x**2 + 2*y**2 + 3*z**2)
dgdx = grad(g, argnums=0)  # argnumsパラメータでxを微分対象にする、つまり偏微分dg/dx
x = y = z = torch.tensor(1.)
dgdx(x, y, z)  # x / sqrt(x**2 + 2*y**2 + 3*z**2) = tensor(0.4082)</pre><p></p>
<h4 class="term">vmap (auto-vectorization)</h4>
<blockquote><p>a vectorization/batching transform (vmap(f) returns a function that computes f over batches of inputs), and others</p></blockquote>
<p><code>functorch.vmap</code>はコードを大きく変更することなく関数を自動<a href="https://ja.wikipedia.org/wiki/%E3%83%99%E3%82%AF%E3%83%88%E3%83%AB%E5%8C%96">ベクトル化</a>（auto-vectorization）して並列処理することでパフォーマンスを向上させます。機械学習だと特にバッチ学習/推論の際に有用です。内部実装的にはコア部分がC++で書かれており、過去のエントリーでも何度か紹介している<a href="https://pytorch.org/cppdocs/">ATen</a>(テンソル演算用C++モジュール)もがっつり使っているようです。</p>
<p>ちなみにATenが環境毎にどうやって並列処理しているかについては以下の関数で確認できます。</p><pre class="crayon-plain-tag">import torch
print(torch.__config__.parallel_info()) 

ATen/Parallel:
	at::get_num_threads() : 1
	at::get_num_interop_threads() : 1
OpenMP 201511 (a.k.a. OpenMP 4.5)
	omp_get_max_threads() : 1
Intel(R) Math Kernel Library Version 2020.0.0 Product Build 20191122 for Intel(R) 64 architecture applications
	mkl_get_max_threads() : 1
Intel(R) MKL-DNN v2.5.2 (Git Hash a9302535553c73243c632ad3c4c80beec3d19a1e)
std::thread::hardware_concurrency() : 2
Environment variables:
	OMP_NUM_THREADS : [not set]
	MKL_NUM_THREADS : [not set]
ATen parallel backend: OpenMP</pre><p>ここではGoogle Colab無料版を使っているので↑のような情報が得られました。<a href="https://ja.wikipedia.org/wiki/OpenMP">OpenMP</a>が有効で、スレッド並列数2で動作しているようです。</p>
<p>以下の公式サンプルコードを見てみます。</p><pre class="crayon-plain-tag">import torch
from functorch import vmap

batch_size, feature_size = 3, 5
weights = torch.randn(feature_size, requires_grad=True)

def model(feature_vec):
    # Very simple linear model with activation
    assert feature_vec.dim() == 1  # torch.Tensor.dotは1Dテンソル(ベクトル)を受け取る
    return feature_vec.dot(weights).relu()

examples = torch.randn(batch_size, feature_size)
result = vmap(model)(examples)</pre><p>単純な線形モデルを定義し、<code>vmap</code>によってサンプル毎にモデルを適用しています。これをあえて<code>vmap</code>を使わずに書くと以下のようにforループが出現してしまい関数型っぽくなくなります。リスト内包表記を使うと多少シンプルに見えますが処理が効率化されるわけではありません。</p><pre class="crayon-plain-tag"># 素直にループ
outputs = []
for example in examples:
    outputs.append(model(example))
result = torch.stack(outputs)

# リスト内包表記を使う場合
result = torch.stack([model(example) for example in examples], dim=0)</pre><p></p>
<p>ここまでは特に利点が見えにくいかもしれませんが、以下のように<code>vmap</code>を<code>grad</code>と組み合わせると、機械学習でよく使われる処理をスマートに書くことができ、なおかつ内部的には<code>vmap</code>により効率的に並列処理されます。</p><pre class="crayon-plain-tag">from functorch import vmap
batch_size, feature_size = 3, 5

def model(weights,feature_vec):
    # Very simple linear model with activation
    assert feature_vec.dim() == 1
    return feature_vec.dot(weights).relu()

def compute_loss(weights, example, target):
    y = model(weights, example)
    return ((y - target) ** 2).mean()  # MSELoss

weights = torch.randn(feature_size, requires_grad=True)
examples = torch.randn(batch_size, feature_size)
targets = torch.randn(batch_size)
inputs = (weights, examples, targets)
grad_weight_per_example = vmap(grad(compute_loss), in_dims=(None, 0, 0))(*inputs)</pre><p>compute_loss関数ではモデルを適用してMSE Lossを計算、これをgradで自動微分対象として勾配を得る関数(1)を生成します。さらにvmapでサンプル毎に関数(1)を適用する関数(2)を生成、inputsに対してその関数(2)を即時適用しています。in_dimsパラメータでマッピングする次元を指定できるので、ここでは入力と同じ3要素のタプルで指定します。weightsのようにマッピングさせない場合はNoneを指定すればOKです。in_dimsパラメータはJAXだと別の名前(in_axes)ですが機能は同じはずです。functorchはJAX-likeなAPIを謳っていますが、PyTorchだとdimという単語がよく使われるのでそちらに合わせたのでしょう。</p>
<p>in_dimsがあればout_dimsもあります。頭の中でどうマッピングされるかイメージして指定しましょう。</p><pre class="crayon-plain-tag">x = torch.randn(2, 3, 5)

# in_dimsとout_dimsを同じにした場合
result = vmap(lambda x: x*2, in_dims=1, out_dims=1)(x)
assert torch.allclose(result, x*2)

# in_dimとout_dimを別にした場合
result = vmap(lambda x: x*2, in_dims=2, out_dims=1)(x)
assert result.shape == (2, 5, 3)
assert torch.allclose(result, (x * 2).transpose(1, 2))</pre><p></p>
<p><code>vmap</code>の挙動を見ると当然ではありますが入力するtensorの形状には注意します。あと、常用しないとは思いますが形状さえ合っていれば(マッピング可能であれば)ネストしても動作します。</p><pre class="crayon-plain-tag">x = torch.randn(2)
y = torch.randn(3)
vmap(torch.mul)(x, y)
# ValueError: vmap: Expected all tensors to have the same size in the mapped dimension, got sizes [2, 3] for the mapped dimension

# vmapをネスト
x = torch.randn(2, 3, 5)
y = torch.randn(2, 3, 5)
output = vmap(vmap(torch.mul))(x, y)
assert torch.allclose(output, x * y)

# 3段ネスト
output = vmap(vmap(vmap(torch.mul)))(x, y)
assert torch.allclose(output, x * y)

# lambda式内でvmap
x = torch.randn(3)
y = torch.randn(5)
output = vmap(lambda x: vmap(lambda y: x)(y))(x)
assert torch.allclose(output, x.view(3, 1).expand(3, 5))</pre><p>↑の例は大袈裟気味に書きましたが、vmapやgrad対象とする関数は普通にdefで定義すると関数定義がスコープ内に残ってしまうので、無名関数として作って即時適用する形に慣れる方が良さそうです。</p>
<p>ここまでに紹介したパラメータとシンプルな入力データを使って最後におさらいします。</p><pre class="crayon-plain-tag">x = torch.linspace(0, 1, 3)  # tensor([0.0000, 0.5000, 1.0000])
y = torch.linspace(1, 2, 3)  # tensor([1.0000, 1.5000, 2.0000])
print(torch.dot(x,y))  # 0.0*1.0 + 0.5*1.5 + 1.0*2.0 = tensor(2.7500)
print(grad(torch.dot, argnums=0)(x, y))  # tensor([1.0000, 1.5000, 2.0000])</pre><p>複数の入力ベクトルに対してvmapで並列処理したい場合は、</p><pre class="crayon-plain-tag">X = torch.linspace(0, 1, 15).reshape(5, 3)  # 5つの3次元ベクトル
Print(X)
# tensor([[0.0000, 0.0714, 0.1429],
          [0.2143, 0.2857, 0.3571],
          [0.4286, 0.5000, 0.5714],
          [0.6429, 0.7143, 0.7857],
          [0.8571, 0.9286, 1.0000]])
# 5つの3次元ベクトルをvmapで並列処理
result = vmap(grad(torch.dot, argnums=0), in_dims=(0, None), out_dims=0)(X, y)
print(result)
# tensor([[1.0000, 1.5000, 2.0000],
          [1.0000, 1.5000, 2.0000],
          [1.0000, 1.5000, 2.0000],
          [1.0000, 1.5000, 2.0000],
          [1.0000, 1.5000, 2.0000]])</pre><p>↑の例が分かれば、<code>grad</code>と<code>vmap</code>の使い方はある程度理解できたのではないでしょうか。関数型に頭を切り替えられていれば意外とすんなり理解できるので単に慣れの問題だと思います。その他、<code>grad</code>と<code>vmap</code>を使う際の細かい注意点や制限については以下の公式ページを参照してください。余裕があれば次回エントリーでいくつかピックアップして紹介するかもしれません。</p>
<ul>
<li><a href="https://pytorch.org/functorch/stable/ux_limitations.html">UX Limitations — functorch 0.1.1 documentation</a></li>
</ul>
<h3 class="term">おわりに</h3>
<p>JAXは以前から興味があったにもかかわらずほとんど使ってなかったのですが、functorchの登場によって使うモチベーションが上がりました。functorchも最初は使いにくく感じるかもしれませんが、慣れればスラスラ書けるようになって面白いです。今回はfunctorchの基本機能となる<code>grad</code>と<code>vmap</code>を主に紹介する準備編でした。functorchの真価はPyTorchの文脈上で機械学習処理を高速化することですので、次回はより実用的な例や他のAPIも試してみたいと思います。</p>
<p>また、functorch自体はまだβ版であり、APIはユーザーからのフィードバックなどを経て変わる可能性があるとのことです。今回のエントリー内での使い方も将来使えなくなるかもしれないのでその際はご了承ください。</p>
<p>ちなみに、GoogleにはTensorFlowがあるのになぜ<a href="https://github.com/google/flax">Flax</a>(JAX)、<a href="https://github.com/google/trax">Trax</a>など複数のフレームワークを作っているのかについてはQuoraにディスカッションがあったので見てみると面白いです。Google(DeepMind含む)内のそれぞれの組織がフレームワークをボトムアップで作っているだけということですが、巨大な組織だとよくある話かなと思います。確かにGoogleはチャットアプリもたくさん作ってるし、今後生き残った者に投資していくのでしょう。</p>
<ul>
<li><a href="https://www.quora.com/Why-is-Google-developing-multiple-neural-network-libraries-such-as-Flax-Trax-and-Objax-even-though-they-already-have-TensorFlow">Why is Google developing multiple neural network libraries such as Flax, Trax, and Objax even though they already have TensorFlow? &#8211; Quora</a></li>
</ul>
<h3 class="term">参考</h3>
<p><a href="https://www.sscardapane.it/tutorials/functorch/">Tutorial: Writing JAX-like code in PyTorch with functorch &#8211; Simone Scardapane</a><br />
<a href="https://www.youtube.com/watch?v=xsDhgn4mHLc">FUNCTORCH | RICHARD ZOU &#038; HORACE HE &#8211; YouTube</a><br />
<a href="https://techblog.zozo.com/entry/scalable-machine-learning-with-JAX">JAXによるスケーラブルな機械学習 &#8211; ZOZO TECH BLOG</a><br />
<a href="https://www.hellocybernetics.tech/entry/2020/05/10/214754">jaxのautogradをpytorchのautogradと比較、単回帰まで（速度比較追加） &#8211; HELLO CYBERNETICS</a><br />
<a href="http://secondearths.sakura.ne.jp/jax/playjax.pdf">機械学習で楽しむ JAX と NumPyro v0.1.0</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3688/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>無料PaaSのDeta Cloudを試してみた</title>
		<link>https://rest-term.com/archives/3683/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=%25e7%2584%25a1%25e6%2596%2599paas%25e3%2581%25aedeta-cloud%25e3%2582%2592%25e8%25a9%25a6%25e3%2581%2597%25e3%2581%25a6%25e3%2581%25bf%25e3%2581%259f</link>
					<comments>https://rest-term.com/archives/3683/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 01 Mar 2022 02:50:43 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[service]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3683</guid>

					<description><![CDATA[今回はDeta CloudというPaaSについて少し調べてみました。これはGoogle検索が日本語設定だとほとんど情報が引っかからないので日本人だと知らない人も多いかもしれませんが、以下の謳い文句でなかなかすごい感じがします。 Build &#038; deploy your ideas on the universe&#8217;s most developer friendly cloud platform. Say goodbye to servers and bills. Deta is&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回は<a href="https://www.deta.sh/">Deta Cloud</a>というPaaSについて少し調べてみました。これはGoogle検索が日本語設定だとほとんど情報が引っかからないので日本人だと知らない人も多いかもしれませんが、以下の謳い文句でなかなかすごい感じがします。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/02/2022-02-24_11h42_46.png?x40497" alt="" width="141" height="44" class="alignnone size-full wp-image-3684" /></p>
<blockquote><p>Build &#038; deploy your ideas on the universe&#8217;s most developer friendly cloud platform. Say goodbye to servers and bills. Deta is free for ever.</p></blockquote>
<p>永久に無料。</p>
<h3 class="term">Detaについて</h3>
<p>Detaは個人向けのPaaS製品を提供しています。2022/2現在、提供機能としては以下の3つ。</p>
<ul>
<li>Deta Micros: アプリケーションランタイム</li>
<li>Deta Base: NoSQLデータベース</li>
<li>Deta Drive: ファイルストレージ</li>
</ul>
<p>昨今のクラウド事情を考えると機能面で特に驚くようなところはないのですが、期間限定の無料トライアルとか極小リソースの無料枠という形ではなく、そもそもDetaには課金の仕組みがないのが驚きです。どういうこと？と思いましたが、理念としては以下を掲げています。</p>
<blockquote><p>For developers, not enterprises</p>
<p>Unlike &#8216;big cloud&#8217;, Deta is made with developers in mind. We have no plans to shift our focus to selling to enterprise.</p>
<p>Free, for purpose</p>
<p>Credit cards and server costs are creativity-killers for developers worldwide. Deta is free, built to back creators and their ideas.</p>
<p>We make money together</p>
<p>Deta is building tools to help developers earn money. We aspire to pay you, not charge you.</p></blockquote>
<p>開発者はDetaを使って良い製品を作って儲けていただいて、いずれはその儲けからいくらか支払ってもらう形にしていきたいということでしょうか。と思ったら、別製品を作っているようなので収益はそちらで賄うとのことです。</p>
<blockquote><p>We&#8217;re working on a big, parallel product that we will be announcing soon. This product will be responsible for generating revenue. Stay tuned!</p></blockquote>
<p>ちなみに、僕は業務でFastAPIというフレームワークを使っているんですが、そのFastAPIのドキュメントにDetaがスポンサーになっていると書かれていたことでDetaの存在を知りました。<br />
参考: <a href="https://fastapi.tiangolo.com/ja/deployment/deta/">Deta にデプロイ &#8211; FastAPI</a></p>
<h3 class="term">試してみる</h3>
<p>Deta Microsを実際に使ってみます。Deta自体のサインアップ方法は省略しますが、普通にメールアドレスとパスワードのみで登録できます。クレジットカード登録の画面すら存在していませんし、MFA認証などももちろんありません。</p>
<h4 class="term">CLIのインストール</h4>
<p>Deta CLIをインストールします。なんと今のところpipで入れられないようですので、公式ドキュメントの通りシェル経由で入れます。</p><pre class="crayon-plain-tag">curl -fsSL https://get.deta.dev/cli.sh | sh</pre><p>$HOME/.deta 以下にコマンドなどがインストールされますので、削除したいときは $HOME/.deta 以下をまるごと消せばいいです。</p><pre class="crayon-plain-tag">$ deta version
deta v1.3.2-beta x86_64-linux
# CLI経由でログインしておく
$ deta login
Please, log in from the web page. Waiting...
Failed to open the login page, open the following link in your browser:
{自動でブラウザに遷移しない場合はログインページのURLが表示される}
Logged in successfully.</pre><p></p>
<h4 class="term">アプリケーションの準備</h4>
<p>MicrosがサポートしているランタイムはPythonとNodeのようですが、今回はPythonでFastAPIのアプリを準備します。MicrosはDockerランタイムではないのでDockerfileは不要、Poetryのみでパッケージ管理しました。</p><pre class="crayon-plain-tag">$ poetry init
This command will guide you through creating your pyproject.toml config.

Package name [deta_sample]:
... 省略
Compatible Python versions [^3.10]:  ^3.9

$ poetry add fastapi</pre><p>Pythonのバージョンが3.7 ~ 3.9までしか対応していないので(2022/02現在)、今のところは3.10以降の機能は使わないようにしましょう。</p><pre class="crayon-plain-tag"># main.py
from fastapi import FastAPI

app = FastAPI()


@app.get(&quot;/&quot;)
async def root():
    return &quot;hello, world&quot;</pre><p>注意点として、<strong>アプリのエントリーポイントは main:app 固定</strong>のようなのでファイル名は main.py、アプリ本体の変数は app としておきます。uvicornのオプションも指定できるのかもしれませんがドキュメントには書いてなさそうなので未調査。</p>
<h3 class="term">Microsアプリケーションの作成とデプロイ</h3>
<p>Pythonランタイムでは requirements.txt でパッケージインストールをするのでデプロイ前に用意しておきます。注意点としては、ハッシュ値付きだとエラーになったので取り除いておく必要がありました。poetryなら &#8211;without-hashes オプションを使えばOKです。プロジェクトの作成は<code>deta new</code>コマンドを使います。</p><pre class="crayon-plain-tag">$ poetry export --without-hashes &gt; requirements.txt
# プロジェクトのルートディレクトリで実行
$ data new
Successfully created a new micro
{
    &quot;name&quot;: &quot;deta_sample&quot;,
    &quot;id&quot;: &quot;97f0acfd-cca6-4492-9245-e129f48639e9&quot;,
    &quot;project&quot;: &quot;c06ol4yn&quot;,
    &quot;runtime&quot;: &quot;python3.9&quot;,
... 以下プロジェクト情報がいろいろ表示される</pre><p>newするだけでデプロイまでされるようなのでダッシュボードを見てみます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/02/2022-02-24_12h50_19_deta_dashboard.png?x40497" alt="" width="571" height="527" class="alignnone size-full wp-image-3686" srcset="https://rest-term.com/wp-content/uploads/2022/02/2022-02-24_12h50_19_deta_dashboard.png 571w, https://rest-term.com/wp-content/uploads/2022/02/2022-02-24_12h50_19_deta_dashboard-300x277.png 300w" sizes="(max-width: 571px) 100vw, 571px" /></p>
<p>↑の画像だと見切れていますが、画面右上あたりにエントリーポイントのURLが表示されているのでクリックして確認します。&#8221;hello, world&#8221;と画面に表示されていればOKです。お疲れ様でした。</p>
<h4 class="term">その他コマンド</h4>
<p>いくつかよく使うコマンドを紹介します。</p><pre class="crayon-plain-tag"># details: アプリの詳細情報を表示、URLを確認する時とかに使う
$ deta details
{
    &quot;name&quot;: &quot;deta_sample&quot;,
    &quot;id&quot;: &quot;97f0acfd-cca6-4492-9245-e129f48639e9&quot;,
...
    &quot;endpoint&quot;: {エンドポイントURLが表示される}

# deploy: デプロイする、コードやパッケージ更新があった時とかに使う 
$ deta deploy
Deploying...
Successfully deployed changes
Updating dependencies...
... 省略

# logs: デプロイ後のデバッグ用途
$ deta logs
[2022-02-23T13:30:26+09:00] [ERROR] AssertionError: We couldn't find your 'app'. Please refer to the Manual.
Traceback (most recent call last):
&nbsp;&nbsp;File &quot;/opt/python/detalib/debugger.py&quot;, line 154, in wrap
&nbsp;&nbsp;&nbsp;&nbsp;raise e
&nbsp;&nbsp;File &quot;/opt/python/detalib/debugger.py&quot;, line 142, in wrap
&nbsp;&nbsp;&nbsp;&nbsp;result = func(event, context)
&nbsp;&nbsp;File &quot;/var/task/_entry.py&quot;, line 14, in handler
&nbsp;&nbsp;&nbsp;&nbsp;return handle(event, main)
&nbsp;&nbsp;File &quot;/opt/python/detalib/handler.py&quot;, line 8, in handle
&nbsp;&nbsp;&nbsp;&nbsp;assert hasattr(
...</pre><p></p>
<h3 class="term">Deta Base</h3>
<p>NoSQLデータベースのDeta Baseも無料で利用できます。単純なKVSではなくMongoDBのようなオブジェクトデータベースになっているようです。</p><pre class="crayon-plain-tag"># Deta SDKのインストール
$ poetry add deta</pre><p></p>
<p></p><pre class="crayon-plain-tag">from deta import Deta  # Import Deta

# Initialize with a Project Key
# project keyはアカウント作成時に発行されます
# PythonコードにハードコードしたりGitHubにコミットしないよう注意
deta = Deta(&quot;project key&quot;)

# This how to connect to or create a database.
db = deta.Base(&quot;test_db&quot;)

# you can store objects
db.put({&quot;name&quot;: &quot;alex&quot;, &quot;age&quot;: 77}, &quot;one&quot;)  # We will use &quot;one&quot; as a key

item = db.get(&quot;one&quot;) # retrieving item with key &quot;one&quot;
# itemは以下のdict(json)で返る
# {
   &quot;name&quot;: &quot;alex&quot;, &quot;age&quot;: 77, &quot;key&quot;: &quot;one&quot;
# } 

# 条件検索も可能 (age&lt;100のデータを取得)
item = db.fetch({&quot;age?lt&quot;: 100})

# delete key
db.delete(&quot;one&quot;)</pre><p>サーバレスアプリケーションを作るには必須の機能だと思うのでこちらも活用していきましょう。</p>
<p>Deta Driveについては、公式ドキュメント通りに試そうとしましたが、ランタイム側でエラーが起きて実行できませんでした、。ランタイム側に入るSDKが認識していないように見えます。要調査。</p><pre class="crayon-plain-tag">Traceback (most recent call last):
  File &quot;/opt/python/detalib/debugger.py&quot;, line 142, in wrap
    result = func(event, context)
  File &quot;/var/task/_entry.py&quot;, line 12, in handler
    import main  # noqa
  File &quot;/var/task/main.py&quot;, line 8, in &lt;module&gt;
    drive = deta.Drive(&quot;test_drive&quot;)
AttributeError: 'Deta' object has no attribute 'Drive'</pre><p></p>
<h3 class="term">リソースについて</h3>
<p>Microsのリソース制限などについては細かいので以下のドキュメントを参照してください。</p>
<ul>
<li><a href="https://docs.deta.sh/docs/micros/about">Introduction to Deta Micros | Deta Docs</a></li>
</ul>
<p>特にRAMについてはデフォルトで128MBと少ないですが、アプリ単位で512MBか1GBに増強できるようです。1GBだと機械学習の推論APIとか動かすのは少し厳しそうですね。せめて3GBあれば用途はもっと広がるかもしれませんが。また、Deta Baseの容量制限等については記載が見当たりません(?。さすがに無限ではないと思いますが、ドキュメントの整備はまだ完全では無さそうです。</p>
<p>ランタイムの途中変更は以下のコマンドで対応できます。</p><pre class="crayon-plain-tag">$ deta update -r python3.8
Updating runtime...
Successfully update micro's runtime</pre><p>ちなみにPythonのplatformモジュールで少し調べたところ、ランタイムはAmazon Linux2だったのでDetaプラットフォーム自体はAWS上に構築されているようです。</p>
<h3 class="term">おわりに</h3>
<p>永久に無料というのはいつまで続くのかわかりませんが、今のところアプリも無制限に作れるしアイドル時のシャットダウンも無いので、現状はPythonとNodeしか対応していませんが個人開発用としてはけっこう使えるかと思いました。他の著名なPaaS製品だと<a href="https://jp.heroku.com/">Heroku</a>がありますが、それと比較してみてもDetaの方が無料で使えるリソースが大きいのでオススメです。とはいえ対応言語などの機能面ではHerokuに大きく劣ってますし、ダッシュボードのUIもまだまだ荒削りです。シンプルなWeb API等を置いておくのにちょうど良いサービスなのではないでしょうか。冒頭でも書いたようにGoogle検索が日本語設定だとほとんど情報が出てこないので、詳しく調べたい場合は英語設定にして調べることをオススメします。Detaは正式リリースが2021年9月らしく、まだ生まれて間もないようなので今後の動向に期待しましょう。</p>
<p>Deta is free for ever.</p>
<p>参考: <a href="https://blog.logrocket.com/deta-vs-heroku-finding-better-cloud-provider/">Deta vs. Heroku: Finding the better cloud provider &#8211; LogRocket Blog</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3683/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Kaggle振り返り &#8211; Help Protect the Great Barrier Reef</title>
		<link>https://rest-term.com/archives/3675/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=kaggle-%25e6%258c%25af%25e3%2582%258a%25e8%25bf%2594%25e3%2582%258a-tensorflow-help-protect-the-great-barrier-reef</link>
					<comments>https://rest-term.com/archives/3675/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 15 Feb 2022 09:56:35 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[computervision]]></category>
		<category><![CDATA[machinelearning]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3675</guid>

					<description><![CDATA[数年ぶりにKaggle復帰してみようということで。 若い頃はKaggleに時間を奪われすぎていたのと、家庭を持ってからは個人活動の優先度が著しく下がったこともあり、実は2,3年前に一度退会して足を洗っていました。ただ、最近はKaggle Notebook環境がまぁまぁ使えて環境格差が昔よりはマシになったとか画像コンペが多くて楽しいとかそういう話をいろいろ聞いて、また一から始めてみようかなぁと。煙草とか酒とか麻薬と同じなんでしょうか。子育ての時間を削るのは良いことだとは全然思いませんが、本当にすみ&#8230;]]></description>
										<content:encoded><![CDATA[<p>数年ぶりにKaggle復帰してみようということで。<br />
若い頃はKaggleに時間を奪われすぎていたのと、家庭を持ってからは個人活動の優先度が著しく下がったこともあり、実は2,3年前に一度退会して足を洗っていました。ただ、最近はKaggle Notebook環境がまぁまぁ使えて環境格差が昔よりはマシになったとか画像コンペが多くて楽しいとかそういう話をいろいろ聞いて、また一から始めてみようかなぁと。煙草とか酒とか麻薬と同じなんでしょうか。子育ての時間を削るのは良いことだとは全然思いませんが、本当にすみません。</p>
<p>さて、1月下旬くらいからこちらのコンペにソロで参加しました。別にTensorFlow縛りはありません。</p>
<ul>
<li><a href="https://www.kaggle.com/c/tensorflow-great-barrier-reef/overview">TensorFlow &#8211; Help Protect the Great Barrier Reef </a></li>
</ul>
<p>最初のsubmitから数えると参加期間としては25日で3週間半、毎日最低1時間は確保し、Kaggle Notebook環境をフル活用してなんとか完走(?はしました。</p>
<p>結果から書くと、</p>
<ul>
<li>Public LB: 309th / 2,109</li>
<li>Private LB: <strong>258th</strong> / 2,109 (Top 12%)</li>
</ul>
<p>(※ 2/15時点の暫定順位、このあとチーターとか不正ユーザーがLBから排除され最終順位が確定)</p>
<p>ということで<strong>普通に惨敗</strong>という結果に砕け散りました:( ものすごいshake downしたとかなら良いネタになるんですが、結果が地味すぎてネタにもならない感じが暖まりますね。</p>
<h3 class="term">コンペ概要</h3>
<p>動画(時系列画像)から<a href="https://ja.wikipedia.org/wiki/%E3%82%AA%E3%83%8B%E3%83%92%E3%83%88%E3%83%87">オニヒトデ</a>をひたすら検出するタスクです。これは日本でも結構有名な環境問題の一つだと思いますが、オニヒトデが珊瑚礁を食べてしまって環境破壊になるのでなんとかしたいという話です。世界中のKagglerが電力とCO2排出量増加に貢献してると思うのでそっちの方もなんとかしたらいいのにとは思います。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/02/375px-Acanthaster_plancikaiyukan.jpg?x40497" alt="" width="375" height="375" class="alignnone size-full wp-image-3679" srcset="https://rest-term.com/wp-content/uploads/2022/02/375px-Acanthaster_plancikaiyukan.jpg 375w, https://rest-term.com/wp-content/uploads/2022/02/375px-Acanthaster_plancikaiyukan-300x300.jpg 300w, https://rest-term.com/wp-content/uploads/2022/02/375px-Acanthaster_plancikaiyukan-144x144.jpg 144w" sizes="(max-width: 375px) 100vw, 375px" /></p>
<p>特徴的なのは評価指標がF2-Scoreということ、F2は業務でも使った記憶が無いんですがFbetaは以下の通りです。<br />
<img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/02/fbeta_metric.png?x40497" alt="" width="400" height="72" class="alignnone size-full wp-image-3677" srcset="https://rest-term.com/wp-content/uploads/2022/02/fbeta_metric.png 400w, https://rest-term.com/wp-content/uploads/2022/02/fbeta_metric-300x54.png 300w" sizes="(max-width: 400px) 100vw, 400px" /></p>
<p>betaが2なので<code>5pr / (4p + r) </code>となり、recall重視でFPはそんなに気にしなくていいということになります。とにかくオニヒトデは一匹残らず駆逐するつもりのようです。あれ食べられないらしいし、人間にとっては別にいなくても大丈夫ということなんでしょうか。</p>
<h3 class="term">手法</h3>
<p>コンペ終了後の参加者の公開ソリューションを眺めていて、自分の手法や方向性はそれほど悪くなかったようで少し安心しました。このコンペはありがたいことに4つもfinal submissionを出せるので以下のバリエーションを選びました。</p>
<ul>
<li>YOLOv5 + YOLOX 2 models Weighted Boxes Fusion (WBF) ensemble (Best Public LB)</li>
<li>YOLOv5 + Tracking</li>
<li>YOLOv5 3 models same resolution(3200px) WBF ensemble</li>
<li>YOLOv5 3 models different resolution(4800/5760/6400px) WBF ensemble (Private LB 258th solution)</li>
</ul>
<p>僕が参加したときは多くの参加者が<a href="https://github.com/ultralytics/yolov5">YOLOv5</a>を使っている状況だったので僕も最後までYOLOシリーズ(YOLOv5とYOLOX)を使っていました。CVは共通してsubsequence(各fold内に偏り無く良い感じの数のヒトデがいる)と呼ばれていた切り方を使わせてもらいました。</p>
<p>コンペ全体のキーポイントとしては画像を高解像度で学習・推論するという点で、コンペの終盤は高解像度合戦が始まりました。そして案の定「このコンペGPU Warじゃないか！」ということでDiscussionがちょっと荒れてましたね。個人的にはKaggle NotebookはP100が使えるので昔に比べたら超高性能でありがたいのですが、週に30-40hという制限が地味に厳しかったです。1モデルにつきCV評価も含めて9時間めいっぱい使うと1週間で3個くらいしかモデルが試せないことになるし、ソロ参加なので実験数が足りないと、、。技術的にはバッチサイズが1か2じゃないとVRAMが足りず動かなかったので、Batch Normalizationが効きにくく学習が安定しないだろうなぁという不安はありました(実際は大きなバッチサイズで試せてないのでわからない)。</p>
<p>YOLOv5モデルのアンサンブル方針が超有名GMのChris Deotte氏と同じだったのはうれしかったです。WBF後にもconfidenceの閾値を設けて足切りするとちょっとFP減らせるよなぁと思って試したら効果があったので採用したのは良い選択でした。ただ、F2スコアへの貢献度から考えると、LB 200-300thくらいの位置でやる対策としては尚早だったということでしょうか。</p>
<p>上位のソリューションだと、CenterNet / FCOS / EfficientDet あたりを使っているチームもいました。Yoloしか試す余裕がなかったのでこれらのモデルもぜひ試してみたかったですね。</p>
<p>また、自身のBest Private LB ScoreだとF2=0.676で130thくらいの位置でした。全体で銅圏(ブロンズ)のsubが2つありましたが、なんといずれもシングルモデル。最終的にはアンサンブルモデルを提出したのでこれらはfinal subに選択しなかったということです。こういうのよくあるよね。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/02/final_submit.jpg?x40497" alt="" width="471" height="306" class="alignnone size-full wp-image-3681" srcset="https://rest-term.com/wp-content/uploads/2022/02/final_submit.jpg 471w, https://rest-term.com/wp-content/uploads/2022/02/final_submit-300x195.jpg 300w" sizes="(max-width: 471px) 100vw, 471px" /></p>
<h4 class="term">高解像度がなぜ効くのか</h4>
<p>学習データの画像解像度が720p(1280x720px,時系列画像)で、これを例えば3200x1800pxにリサイズして学習・推論させるとLBスコアが上がるという現象がコンペ参加者の共通認識としてありました。感覚的にはそんなことある？と思った人は多かったんじゃないかと思いますが、実際効果はあったのでまぁみんなマネしますよね。YOLOv5の場合、アンカーのストライドの最小値が変換後の座標で固定されるので、元解像度(1280&#215;720)座標上でのストライドも小さくなり、結果としてrecallが良くなるということのようです。つまり今回のデータセットのように小さいヒトデがたくさんいる場合はアンカーがぎちぎちに敷かれるおかげで取りこぼしにくいということでしょうか。解像度を上げつつアンカー周りのパラメータは変えずに学習するというのが良かったようです。知らんけど。<br />
参考: <a href="https://jp.mathworks.com/help/vision/ug/anchor-boxes-for-object-detection.html">アンカー ボックスによるオブジェクトの検出 &#8211; MATLAB &#038; Simulink &#8211; MathWorks</a></p>
<h3 class="term">敗因は</h3>
<p>格ゲーでもFPSのランクマッチでも負け試合を公開できるかどうかで器量がわかるというものですが、強くなるために敗因分析をしてみます。</p>
<p>いつも結果論ではありますが、シンプルにTrust your CVを実践しきれていなかったのは大きいとは思います。シングルモデルのCVはきっちり測っていましたが、アンサンブル後のCVは前述した通りKaggleのGPU時間制限(30-40h/week)の都合上、全く測ってなかったのが致命的でした。</p>
<p>一方で、銅圏に入っていたsubはYOLOv5(yolov5s6とyolov5m6)のシングルモデルだったのですが、それらはCVも悪くないモデルでした。シングルモデルのCVはPrivate LBとまぁまぁ相関はあったので、シングルモデルの構築はそれなりに上手くできていたと思います。これは収穫です。結局、アンサンブルモデルのスコアはシングルモデルよりも低かったのですが、前述の通りアンサンブル後のCVを測っていれば各種パラメータの追加検討等ができるので方針は問題なかったと思います。シングルモデルのCVは信じることができていたので、それらのアンサンブルなら上手くいってるだろうという浅はかな考えでsubmitしたというだけです。ちなみにオブジェクトトラッキングの併用はあんまり上手くいきませんでした。</p>
<p>コンペ締め切りの3日前くらいからはNMSのconfidenceとIoUの閾値を脳死で弄るだけおじさんになっていたので、これ以上続けてもPublic LBにオーバフィットさせ続けていただろうなと思います。なので、悔しいですが順当な結果なので納得感はあります。</p>
<p>敗因をまとめると「シングルモデルの検証は丁寧にできたけど、アンサンブル後のCV評価を全くしていなかった」ということかなと。新しい課題ではなく、やっぱりCVは大切だよなという常識を再認識できたという点は良かったです。</p>
<h3 class="term">感想</h3>
<p>このコンペはRemek Kinas氏というヒーローがいて、狂ったように3点リーダー(&#8230;)を駆使するユーモアと高い技術力によりソロ参加者にとっては本当に励みになりました。公開されるNotebookは教育的観点も含めて良く構成されたものでそこから多くの学びを得ました。コンペ終了8日前くらいにハイスコアのNotebookが公開されてしまったため、Public LBの銅圏あたりにいた人達は顔真っ赤にして良い具合に暖まっていたようですが、GMの方々はもちろん、このRemek氏もいたおかげでコンペの雰囲気というか民度がなんとか保たれていたのだと思います。many thanks Remek.</p>
<p>数年ぶりのKaggleコンペ、結果は残念でしたが無課金でも十分楽しめるゲームでした。きっとこれからオニヒトデが駆逐されグレートバリアリーフは守られるのでしょう。しかし、今回のコンペのマネーゾーンの方々は黄色率高すぎませんかね。強すぎる。お金欲しい。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2022/02/2022-02-17_15h19_05.png?x40497" alt="" width="1192" height="367" class="alignnone size-full wp-image-3678" srcset="https://rest-term.com/wp-content/uploads/2022/02/2022-02-17_15h19_05.png 1192w, https://rest-term.com/wp-content/uploads/2022/02/2022-02-17_15h19_05-300x92.png 300w, https://rest-term.com/wp-content/uploads/2022/02/2022-02-17_15h19_05-1024x315.png 1024w, https://rest-term.com/wp-content/uploads/2022/02/2022-02-17_15h19_05-768x236.png 768w" sizes="(max-width: 1192px) 100vw, 1192px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3675/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Web Machine Learningについて</title>
		<link>https://rest-term.com/archives/3660/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=web-machine-learning%25e3%2581%25ab%25e3%2581%25a4%25e3%2581%2584%25e3%2581%25a6</link>
					<comments>https://rest-term.com/archives/3660/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Mon, 01 Nov 2021 11:00:46 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[machinelearning]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3660</guid>

					<description><![CDATA[W3Cが推進しているWeb Machine Learning (WebML)という取り組みについて少し調べてみました。今回は解説記事というわけではなく個人用のメモに近いので正確性についてはあまり自信がありませんが。。 Web Machine Learning (WebML)とは Web Machine Learning &#124; Making Machine Learning a first-class web citizen W3C Web Machine Learning Working Grou&#8230;]]></description>
										<content:encoded><![CDATA[<p>W3Cが推進している<strong>Web Machine Learning</strong> (WebML)という取り組みについて少し調べてみました。今回は解説記事というわけではなく個人用のメモに近いので正確性についてはあまり自信がありませんが。。</p>
<h3 class="term">Web Machine Learning (WebML)とは</h3>
<ul>
<li><a href="https://webmachinelearning.github.io/">Web Machine Learning | Making Machine Learning a first-class web citizen</a></li>
</ul>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/10/webnn_logo.png?x40497" alt="" width="255" height="65" class="alignnone size-full wp-image-3663" /></p>
<blockquote><p>W3C Web Machine Learning Working Group standardizes Web APIs for in-device machine learning inference working together with the W3C ecosystem using well-received Community Group incubations as its seeds. Its sister Web Machine Learning Community Group incubates new proposals and is the place where new ideas are discussed and explored before formal standardization.</p></blockquote>
<p>W3Cワーキンググループでは、デバイス上で動作する機械学習(DNN)アプリケーションやフレームワーク等が利用するAPI仕様の標準化に取り組んでいます。ターゲットとしては今のところWebブラウザ上での推論処理周りを対象としているようです。&#8221;Web API&#8221;と表記されているのでWeb系エンジニアは勘違いしやすいかもしれませんが、通信プロトコルの仕様を標準化するというわけではありません。</p>
<p>クライアントデバイス上で処理するメリットとしては以下の4つを謳っており、多少冗長に見えますがどれも一般化している内容かとは思います。</p>
<ul>
<li><strong>Low Latency</strong>: クライアントのブラウザ上で処理するため低レイテンシで実行できる</li>
<li><strong>Privacy Preserving</strong>: 外部にデータ送信する必要がないためユーザーデータが守られる</li>
<li><strong>High Availability</strong>: ネットワーク接続に依存しないためオフライン実行できる</li>
<li><strong>Low Cost</strong>: サーバ環境が不要なので低コスト</li>
</ul>
<h3 class="term">WebNN API</h3>
<p>WebML自体は特定の技術や製品を指すものではなく、前述の通りW3CではAPI仕様の標準化を推進しており、そのAPIを<strong>WebNN API</strong>と呼んでいます。</p>
<blockquote><p>Accelerating deep neural networks on the web</p>
<p>A new web standard that allows web apps and frameworks to accelerate deep neural networks with on-device hardware such as GPUs, CPUs, or purpose-built AI accelerators.</p></blockquote>
<p>名前にNNとあるのでNeural Network用のAPIのようです。以下の図のようにフレームワークと各種OSネイティブのバックエンドやハードウェアとの橋渡しとなるAPIという位置付けとなっており、WebNNによる共通のインタフェースを使うことでクロスプラットフォームな実行環境を作ることができます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/10/figure_webnn.png?x40497" alt="" width="608" height="329" class="alignnone size-full wp-image-3662" srcset="https://rest-term.com/wp-content/uploads/2021/10/figure_webnn.png 608w, https://rest-term.com/wp-content/uploads/2021/10/figure_webnn-300x162.png 300w" sizes="(max-width: 608px) 100vw, 608px" /></p>
<p>TensorFlow.jsやONNX.jsなどの既存のフレームワークもWebNN対応するらしいですね。ちなみにONNX.jsについては後継プロダクトについて別記事で紹介していますので参考までに。</p>
<ul>
<li><a href="https://rest-term.com/archives/3649/">ONNX Runtime for WebをVue.js+WebGL環境で試す</a></li>
<li><a href="https://rest-term.com/archives/3655/">ONNX Runtime for Webで画像認識</a></li>
</ul>
<h4 class="term">注意点</h4>
<p>2021/11現在、WebNN APIの仕様はEditor’s Draftとなっているようでした。まだ草案段階なので仕様はどんどん変わるかもしれませんし、W3C勧告まではしばらく時間がかかりそうです。</p>
<ul>
<li><a href="https://webmachinelearning.github.io/webnn/">Web Neural Network API</a></li>
</ul>
<p>この記事の投稿時点でDraftの日付は30 September 2021となっていました。これからも頻繁に更新されるかもしれないので細かい仕様自体の紹介は控えておきます。この後サンプルコードを載せますが、一部のAPIはEditor&#8217;s Draftにある仕様と異なっているものがありましたので留意ください。</p>
<h3 class="term">使い方</h3>
<p>前述の通り、現在は仕様がEditor&#8217;s Draft段階なので細かい使い方を紹介しても意味無いかもしれませんが、一応草案仕様を少しだけ見てみます。公式のHello World的なサンプルコードは以下になります。</p>
<ul>
<li><a href="https://webmachinelearning.github.io/get-started/2021/03/15/build-your-first-graph-with-webnn-api.html">Build Your First Graph with WebNN API | Web Machine Learning</a></li>
</ul>
<p>こちらは簡単な行列計算を計算グラフとして定義、実行するサンプルになっています。</p><pre class="crayon-plain-tag">const context = navigator.ml.createContext({powerPreference: 'low-power'});

// The following code builds a graph as:
// constant1 ---+
//              +--- Add ---&gt; intermediateOutput1 ---+
// input1    ---+                                    |
//                                                   +--- Mul---&gt; output
// constant2 ---+                                    |
//              +--- Add ---&gt; intermediateOutput2 ---+
// input2    ---+

// Use tensors in 4 dimensions.
const TENSOR_DIMS = [1, 2, 2, 2];
const TENSOR_SIZE = 8;

const builder = new MLGraphBuilder(context);

// Create OperandDescriptor object.
const desc = {type: 'float32', dimensions: TENSOR_DIMS};

// constant1 is a constant operand with the value 0.5.
const constantBuffer1 = new Float32Array(TENSOR_SIZE).fill(0.5);
const constant1 = builder.constant(desc, constantBuffer1);

// input1 is one of the input operands. Its value will be set before execution.
const input1 = builder.input('input1', desc);

// constant2 is another constant operand with the value 0.5.
const constantBuffer2 = new Float32Array(TENSOR_SIZE).fill(0.5);
const constant2 = builder.constant(desc, constantBuffer2);

// input2 is another input operand. Its value will be set before execution.
const input2 = builder.input('input2', desc);

// intermediateOutput1 is the output of the first Add operation.
const intermediateOutput1 = builder.add(constant1, input1);

// intermediateOutput2 is the output of the second Add operation.
const intermediateOutput2 = builder.add(constant2, input2);

// output is the output operand of the Mul operation.
const output = builder.mul(intermediateOutput1, intermediateOutput2);

// Build graph.
const graph = await builder.build({'output': output});

// Setup the input buffers with value 1.
const inputBuffer1 = new Float32Array(TENSOR_SIZE).fill(1);
const inputBuffer2 = new Float32Array(TENSOR_SIZE).fill(1);

// Asynchronously execute the built model with the specified inputs.
const inputs = {
  'input1': {data: inputBuffer1},
  'input2': {data: inputBuffer2},
};
const outputs = await graph.compute(inputs);

// Log the shape and computed result of the output operand.
console.log('Output shape: ' + outputs.output.dimensions);
// Output shape: 1,2,2,2
console.log('Output value: ' + outputs.output.data);
// Output value: 2.25,2.25,2.25,2.25,2.25,2.25,2.25,2.25</pre><p>WebNNには計算グラフを構築するためのAPIが一通り定義されているようです。計算グラフについてはTensorFlowやPyTorchのようなフレームワークを使っている人には馴染み深いとは思いますが、JavaScriptの場合は演算子のオーバーロードができないので見た目はスマートとは言い難いですね。とはいえWebNN APIには抽象度の高さがそれほど求められているわけではないのでこれくらいがいいのかなとは思います。</p>
<p>このように<code>add</code>とか<code>mul</code>とか各種演算用の関数を使って計算グラフを構築していきます。その際に<code>MLGraphBuilder</code>というものを使いますが、デザインパターンでいうところのBuilderパターンを踏襲しているわけではないようです。計算グラフのノードが都度吐き出される形になっているので、ユーザー側が各ノードの部品を正しく管理してグラフを組み立てていく必要があります。</p>
<p>また、<code>MLGraphBuilder</code>に最初に渡されるcontextによって環境を指定する形になっています。このサンプルだと、<code>{powerPreference: 'low-power'}</code>という指定がありますが、specとしては以下の<code>MLPowerPreference</code>というものが定義されています。</p><pre class="crayon-plain-tag">enum MLPowerPreference {
  // Let the user agent select the most suitable behavior.
  &quot;default&quot;,
  // Prioritizes execution speed over power consumption.
  &quot;high-performance&quot;,
  // Prioritizes power consumption over other considerations such as execution speed.
  &quot;low-power&quot;
};</pre><p>消費電力を意識するのは大切ですね。エッジコンピューティング環境も想定されているのでしょう。また、上記サンプルには指定はありませんがデバイス環境を指定する<code>MLDevicePreference</code>オプションもあります。</p><pre class="crayon-plain-tag">enum MLDevicePreference {
  &quot;default&quot;,
  &quot;gpu&quot;,
  &quot;cpu&quot;
};</pre><p>こちらはCPUかGPUを指定するだけなのでわかりやすいです。</p>
<p>データ型については、DNN系フレームワークだと基本的に提供されているTensorのようなものが別途用意されているわけではなく、<a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/TypedArray">TypedArray</a>(Float32Arrayなど)をそのまま使うようです。以前紹介したONNX Runtime for WebでもデータバッファとしてTypedArrayを使っていたので特に違和感はないです。</p>
<p>個人的にはWebNN APIのレイヤーは抽象度は低くて良いのでハードウェアの性能を引き出しやすい薄いインタフェースだけ用意されていれば十分かなと思います。活性化関数などのAPI定義もあるようですが、そういうのは上位のフレームワーク側でやればいいのではないかという感想を持ちました。</p>
<p>、とはいえWebNNという名前が付いていますからニューラルネットワークに関するオペレーションは全てカバーする思想なのでしょう。例えばシグモイド関数を作る場合は以下みたいに<code>MLGraphBuilder</code>を入れ子にして酷使すれば作れますが、さすがにこんなコーディングは生理的に嫌でしょうし、そもそもBuilderのインタフェース自体の改善も必要そうです。</p><pre class="crayon-plain-tag">builder.div(builder.constant(1),builder.add(builder.exp(builder.neg(x)), builder.constant(1)));</pre><p></p>
<p>他にも仕様をいろいろ妄想してみましたが、やはりAPI仕様の標準化作業というのは大変というか面倒くさそうだなと思いました。繰り返しになりますが、上記サンプルコードの一部のAPIはEditor&#8217;s Draftにある仕様と異なっているものがありましたので留意ください。</p>
<h3 class="term">おわりに</h3>
<p>WebMLについて表面的なところはいろいろ調べてはみたんですが、現場からの注目度としてはまだそれほど高くないという感じのようです(国内に至っては紹介記事がほぼ無い?)。というのもアプリケーション開発者はWebML(WebNN API)を直接利用する機会はほぼ無さそうというのと、仕様自体がまだ草案段階なので深堀りする動機はまだ薄いのでしょう。WebMLの構想自体は期待できる取り組みだと思うので動向は引き続き追っていきたいと思います。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3660/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ONNX Runtime for Webで画像認識</title>
		<link>https://rest-term.com/archives/3655/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=onnx-runtime-for-web%25e3%2581%25a7%25e7%2594%25bb%25e5%2583%258f%25e8%25aa%258d%25e8%25ad%2598</link>
					<comments>https://rest-term.com/archives/3655/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Sun, 03 Oct 2021 01:10:43 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[computervision]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[typescript]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3655</guid>

					<description><![CDATA[前回はONNX Runtime for Web (ORT Web)をVue.jsアプリケーションで使ってみました。 ONNX Runtime for WebをVue.js+WebGL環境で試す 公式チュートリアルは単純な行列計算でしたので、今回はもう少し実践的に自前のモデルを使った画像認識を試してみたいと思います。 環境 前回の環境とほぼ同じですが、実装言語はTypeScriptに変更しました。数値計算系の部分は型があるとデバッグしやすいので。 Vue.js: 3.2.11 ONNX Runti&#8230;]]></description>
										<content:encoded><![CDATA[<p>前回はONNX Runtime for Web (ORT Web)をVue.jsアプリケーションで使ってみました。</p>
<ul>
<li><a href="https://rest-term.com/archives/3649/">ONNX Runtime for WebをVue.js+WebGL環境で試す</a></li>
</ul>
<p>公式チュートリアルは単純な行列計算でしたので、今回はもう少し実践的に自前のモデルを使った画像認識を試してみたいと思います。</p>
<h3 class="term">環境</h3>
<p>前回の環境とほぼ同じですが、実装言語はTypeScriptに変更しました。数値計算系の部分は型があるとデバッグしやすいので。</p>
<p>Vue.js: 3.2.11<br />
ONNX Runtime for Web (onnxruntime-web): 1.8.0<br />
Webブラウザ: Chrome 93.0, Firefox 92.0<br />
実装: TypeScript 4.1.6</p>
<h4 class="term">ONNXモデルファイルの準備</h4>
<p>ONNXフォーマットのモデルの作り方は<a href="https://rest-term.com/archives/3649/">前回の記事</a>を参照してください。PyTorchで作ったモデルをONNXフォーマットに変換したものを準備します。</p>
<p>モデルアーキテクチャ: <a href="https://ja.wikipedia.org/wiki/AlexNet">AlexNet</a><br />
データセット: <a href="https://www.cs.toronto.edu/~kriz/cifar.html">CIFAR-10</a> (32x32px画像、10カテゴリ)</p>
<p>画像認識のモデルとしては入門レベルのシンプルなものです。ちなみに精度(accuracy)は86%くらいでした。CIFAR-10データセットは32&#215;32ピクセルの小さな画像なので人間には少々見難いですが、処理が軽いので動作確認には適しています。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/10/cifar10.png?x40497" alt="" width="470" height="369" class="alignnone size-full wp-image-3658" srcset="https://rest-term.com/wp-content/uploads/2021/10/cifar10.png 470w, https://rest-term.com/wp-content/uploads/2021/10/cifar10-300x236.png 300w" sizes="(max-width: 470px) 100vw, 470px" /></p>
<h3 class="term">実装</h3>
<p>既存のONNX Runtimeで実行するのと基本的には同様の手順で動きますが、ORT Webのインタフェースは必要最低限のものしか提供されていないので細かい計算は自前で行う必要があります。今回は記事は主にHTML Canvas固有の処理の説明になります。</p>
<p>まず実装の全容を以下に載せます。ORT WebとVue.js以外の外部モジュールは使用していません。ここでは1ファイルに全容を収めるためにVue.jsの単一ファイルコンポーネントとして実装していますが、計算処理部分は別モジュールに切り出すのが適切かと思います。1ファイルなのでgistにも一応貼っておきます。</p>
<ul>
<li><a href="https://gist.github.com/wellflat/6f8958e822fa7cc103c2f30847370691">Image Classification using ONNX Runtime for Web (ORT Web)</a></li>
</ul>
<p></p><pre class="crayon-plain-tag">&lt;template&gt;
  &lt;div class=&quot;hello&quot;&gt;
    &lt;h1&gt;{{ msg }}&lt;/h1&gt;
    &lt;canvas width=&quot;32&quot; height=&quot;32&quot; ref=&quot;canvas&quot;&gt;&lt;/canvas&gt;
    &lt;button type=&quot;button&quot; @click=&quot;inference&quot;&gt;inference&lt;/button&gt;
    &lt;span&gt;{{ infoLabel }}&lt;/span&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
import { defineComponent } from 'vue';
import { InferenceSession, Tensor } from 'onnxruntime-web';

interface DataType {
  modelPath: string,
  imagePath: string,
  imageData: ImageData | null,
  ctx: CanvasRenderingContext2D | null,
  session: InferenceSession | null,
  infoLabel: string
}

export default defineComponent({
  name: 'HelloONNX',
  props: {
    msg: String,
  },
  data(): DataType {
    return {
      modelPath: 'cifar10_net.onnx',  // ONNXモデルファイル名
      imagePath: require(&quot;@/assets/cat9.png&quot;),  // テスト画像を埋め込み
      imageData: null,
      ctx: null,
      session: null,
      infoLabel: &quot;&quot;
    };
  },
  async mounted() {
    const option = {executionProviders: ['wasm', 'webgl']};
    this.session = await InferenceSession.create(this.modelPath, option);
    this.infoLabel = &quot;loading model complete.&quot;
    const image = new Image();
    image.src = this.imagePath;
    const isCanvas = (x: any): x is HTMLCanvasElement =&gt; x instanceof HTMLCanvasElement;
    image.onload = () =&gt; {
      // Canvas要素に画像ファイルを貼り、画像データを取得する
      const ref = this.$refs;
      if(!isCanvas(ref.canvas)) return;
      this.ctx = ref.canvas.getContext(&quot;2d&quot;);
      if(this.ctx == null) return;
      const [w, h] = [this.ctx.canvas.width, this.ctx.canvas.height];
      this.ctx.drawImage(image, 0, 0, w, h);
      this.imageData = this.ctx.getImageData(0, 0, w, h);
    };
  },
  methods: {
    async inference(): Promise&lt;void&gt; {
      if(this.session == null || this.imageData == null) return;
      const { data, width, height } = this.imageData;
      // 入力データの正規化(標準化)
      const processed = this.normalize((data as Uint8ClampedArray), width, height);
      // ORT Web用にデータ変換して、推論処理を実行
      const tensor = new Tensor(&quot;float32&quot;, processed, [1, 3, width, height]);
      const feed = { input: tensor };
      const result = await this.session.run(feed);
      // 推論結果(カテゴリと信頼度)を取得
      const predicted = this.softmax((result.output.data as Float32Array));
      // 信頼度が一番高いカテゴリと信頼度を画面表示
      this.infoLabel = this.getClass(predicted).toString();
    },
    normalize(src: Uint8ClampedArray, width: number, height: number): Float32Array {
      const dst = new Float32Array(width * height * 3);
      const transforms = [[0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]];
      const step = width * height;
      for(let y = 0; y &lt; height; y++) {
        for(let x = 0; x &lt; width; x++) {
          const [di, si] = [y * width + x, (y * width + x) * 4];
          // 各チャンネルで平均を引いて標準偏差で割る(標準化)
          // さらに RGBARGBARGBA... から RRR...GGG...BBB... の順にデータを詰め替え
          dst[di] = ((src[si + 0] / 255) - transforms[0][0]) / transforms[1][0];
          dst[di + step] = ((src[si + 1] / 255) - transforms[0][1]) / transforms[1][1];
          dst[di + step * 2] = ((src[si + 2] / 255) - transforms[0][2]) / transforms[1][2];
        }
      }
      return dst;
    },
    softmax(data: Float32Array): Float32Array {
      const max = Math.max(...data);
      const d = data.map(y =&gt; Math.exp(y - max)).reduce((a, b) =&gt; a + b);
      return data.map((value, index) =&gt; Math.exp(value - max) / d);
    },
    getClass(data: Float32Array): [string, number] {
      const classes = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'];
      const maxProb = Math.max(...data);
      return [classes[data.indexOf(maxProb)], maxProb];
    }
  }
});
&lt;/script&gt;
... CSSパートは省略</pre><p>TypeScriptのstrict type checkingを有効にしているので余計な処理が入っていますが、処理の本体は inference 関数以下に全て書いています。</p>
<h4 class="term">処理の流れ</h4>
<ol>
<li>HTML Canvas要素に画像ファイルを読み込んで、画像データを取得 (mounted)</li>
<li>画像データの標準化 (normalize)</li>
<li>ORT Webで推論処理を実行 (InferenceSession.run)</li>
<li>推論結果を整形して表示 (softmax, getClass)</li>
</ol>
<p>画像データ取得部分はORT Web固有の処理などは無いので難しい部分はないかと思います。次のデータの標準化についてですが、これを前処理として忘れずに行っておきます。具体的には画像の各チャンネル毎に、データセットの全ての画像から算出した平均値を引いて標準偏差で割ってやります。モデルは以下のように作っているので平均や標準偏差も同じ値を使いました。</p><pre class="crayon-plain-tag"># PyTorchでモデルを作った時のtransformの設定
transform = {
    'train': transforms.Compose([
        transforms.RandomCrop(32, padding=4),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
    ]),
    'val': transforms.Compose([
       transforms.ToTensor(),
       transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),
    ])
}</pre><p></p>
<p>ここで、ORT WebのTensorデータを作る際の注意点があります。まず、Canvas要素の<code>ImageData</code>配列は、<strong>RGBARGBARGBA&#8230;</strong> の順で一次元に画像データが詰め込まれていますが、ORT Webの<code>Tensor</code>に変換する際には <strong>RRR&#8230;GGG&#8230;BBB&#8230;</strong> の順で詰め込む必要があります。配列の大きさも異なり、<code>ImageData</code>はA(アルファチャンネル)を含み、<code>Tensor</code>は含みません。つまり32&#215;32ピクセルの画像だと<code>ImageData</code>は32x32x4=4096で、<code>Tensor</code>は32x32x3=3072となります。上記実装内ではそれぞれの配列のインデックス計算をして標準化したデータを詰め替えていることになります。わかりにくいですが計算コストを抑えるために同じループ内で処理しています。</p>
<p>ちなみにJavaScript(TypeScript)ではPythonのNumPy ndarrayの操作をシミュレートする <a href="https://github.com/scijs/ndarray">ndarray</a> モジュールがあるのでそれを使うのも良いと思います。今回の計算程度でndarrayを使うとコード量自体は逆に増えてしまいますが見た目は読みやすくはなります。</p>
<p>残りの<code>softmax</code>関数は文字通り<a href="https://en.wikipedia.org/wiki/Softmax_function">Softmax関数</a>の計算を行い、モデル(ニューラルネットワーク)の出力結果をカテゴリ毎の確率(信頼度)に変換しています。それから<code>getClass</code>関数で一番確率が高いカテゴリを返して処理完了となります。上記実装内の predicted 変数に各カテゴリ毎の確率が入っているので、例えば確率トップ5を出したいなら中身をソートして表示すれば良いだけです。今回はテスト画像を32&#215;32ピクセルのものを予め用意していますが、サイズの違う画像を入力にする場合はリサイズ処理も前処理として必要になります。</p>
<p>今回はVue.jsのアプリケーションとして実装しているので表示は以下のようになります。以下は32&#215;32ピクセルの猫の画像を入力にして正しく認識されていることがわかります(約98%の確率でcatであると推論)。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/2021-09-24_16h42_28.png?x40497" alt="" width="638" height="121" class="alignnone size-full wp-image-3656" srcset="https://rest-term.com/wp-content/uploads/2021/09/2021-09-24_16h42_28.png 638w, https://rest-term.com/wp-content/uploads/2021/09/2021-09-24_16h42_28-300x57.png 300w" sizes="(max-width: 638px) 100vw, 638px" /></p>
<h3 class="term">おわりに</h3>
<p>今回はORT Webを使った少し実践的なサンプルを作ってみました。ONNXモデルファイルを自前で作る前に、<a href="https://github.com/onnx/models">ONNX Model Zoo</a>にたくさん転がっているのでまずはそちらを使って遊ぶのが良いです。やはりブラウザで動くのは楽しいですね。</p>
<p>一方で前回の記事にも書きましたが、ORT Webのユーザー層のスキルセットがマッチしないかもという懸念は残ります。上記の実装は少しだけ機械学習の事前知識がないと難しいと感じるかもしれません。ORT Webのインタフェースは必要最低限なので、それを補完するライブラリがあると便利そうです。ORT Webはまだリリースされて間もないので、これからのアップデートに期待したいと思います。</p>
<ul>
<li>今回実装したVueコンポーネント: <a href="https://gist.github.com/wellflat/6f8958e822fa7cc103c2f30847370691">Image Classification using ONNX Runtime for Web (ORT Web)</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3655/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ONNX Runtime for WebをVue.js+WebGL環境で試す</title>
		<link>https://rest-term.com/archives/3649/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=onnx-runtime-for-web%25e3%2582%2592vue-jswebgl%25e7%2592%25b0%25e5%25a2%2583%25e3%2581%25a7%25e8%25a9%25a6%25e3%2581%2599</link>
					<comments>https://rest-term.com/archives/3649/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 21 Sep 2021 12:00:44 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3649</guid>

					<description><![CDATA[Microsoftから ONNX Runtime for Web (ORT Web) なるものが9月2日にリリースされました。 ONNX Runtime Web—running your machine learning model in browser &#8211; Microsoft Open Source Blog ONNX (Open Neural Network Exchange) について ONNX (Open Neural Network Exchange) は機械学習のモデルフ&#8230;]]></description>
										<content:encoded><![CDATA[<p>Microsoftから <strong>ONNX Runtime for Web (ORT Web)</strong> なるものが9月2日にリリースされました。</p>
<ul>
<li><a href="https://cloudblogs.microsoft.com/opensource/2021/09/02/onnx-runtime-web-running-your-machine-learning-model-in-browser/">ONNX Runtime Web—running your machine learning model in browser &#8211; Microsoft Open Source Blog</a></li>
</ul>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/68747470733a2f2f7777772e6f6e6e7872756e74696d652e61692f696d616765732f4f4e4e582d52756e74696d652d6c6f676f2e706e67.png?x40497" alt="" width="217" height="74" class="alignnone size-full wp-image-3651" /></p>
<h3 class="term">ONNX (Open Neural Network Exchange) について</h3>
<p>ONNX (Open Neural Network Exchange) は機械学習のモデルフォーマットの一つです。機械学習フレームワークはTensowflowやPyTorch、MXNetやCaffe2などたくさんありますが、ONNXフォーマットを使えばそれらのフレームワーク間において相互運用が可能になります。共通で使えるファイル形式ということです。ONNXについての歴史や開発背景はWikipediaの説明を読むのが一番わかりやすいかと思います。<a href="https://ja.wikipedia.org/wiki/Open_Neural_Network_Exchange">Open Neural Network Exchange &#8211; Wikipedia</a></p>
<p>このブログでも2年以上前ですがOpenCVのONNX対応について紹介しているので、OpenCVで画像処理と機械学習をやってみたい人は参考までに。</p>
<ul>
<li><a href="https://rest-term.com/archives/3503/">OpenCV 4.0のONNXサポートについて</a></li>
</ul>
<h3 class="term">ONNX Runtime for Web (ORT Web)</h3>
<p>ONNX Runtimeはハイパフォーマンスのクロスプラットフォーム対応の推論エンジンです。ONNX Runtime自体は昔からありましたが、今回MicrosoftがリリースしたORT WebはWebブラウザで動作するランタイムとして使うことができます。クライアントサイドでONNXフォーマットのモデルを使った計算(推論処理)が可能となります。Webブラウザで動くランタイムとしては <a href="https://github.com/microsoft/onnxjs">onnx.js</a> というものがありましたがこちらは今後利用非推奨となるようです。これからは替わりにORT Webを使いましょう。</p>
<ul>
<li><a href="https://github.com/microsoft/onnxruntime-inference-examples/tree/main/js">onnxruntime-inference-examples/js at main · microsoft/onnxruntime-inference-examples</a></li>
</ul>
<h3 class="term">使い方</h3>
<p>Microsoft公式にチュートリアル(Quick Start)があります。</p>
<ul>
<li><a href="https://github.com/microsoft/onnxruntime-inference-examples/tree/main/js/quick-start_onnxruntime-node">Quick Start &#8211; Nodejs Binding</a> &#8211; a demonstration of basic usage of ONNX Runtime Node.js binding.</li>
<li><a href="https://github.com/microsoft/onnxruntime-inference-examples/tree/main/js/quick-start_onnxruntime-web-script-tag">Quick Start &#8211; Web (using script tag)</a> &#8211; a demonstration of basic usage of ONNX Runtime Web using script tag.</li>
<li><a href="https://github.com/microsoft/onnxruntime-inference-examples/tree/main/js/quick-start_onnxruntime-web-bundler">Quick Start &#8211; Web (using bundler)</a> &#8211; a demonstration of basic usage of ONNX Runtime Web using a bundler.</li>
</ul>
<p>既存の速いランタイムを使う場合と比べてNode.jsで動かすのはあまりメリットが無いので(JSで書けるくらい)、今回はWebブラウザで動くものを試します。HTMLのscript tagに全ての実装を埋め込むチュートリアルはわかりやすいですが、昨今のフロントエンド開発においては実践向きではないので必然的にbundler版を試すことになります。とは言ってもbundler版もwebpackを使っているだけでそんなに変わらないようです。いずれにせよチュートリアル通りに動かせば特に躓くところは無さそうなので今回はもう少し現実的ににVue.jsと併せて使ってみました。まぁ2,3年後もVueが活発に使われているか分かりませんが。</p>
<h4 class="term">環境</h4>
<p>Vue.js: 3.2.11<br />
ONNX Runtime for Web (onnxruntime-web): 1.8.0<br />
Webブラウザ: Chrome 93.0, Firefox 92.0<br />
実装: JavaScript (async/awaitを使うためES2017以降)</p>
<h4 class="term">Vue.jsでONNX Runtime for Webを使う</h4>
<p>今のVue.jsはvue-cliがあるので、webpackを直接使う機会は減っているかとは思います。ここでもvue-cliの<code>vue-cli-service</code>を使って動作確認やデプロイをします。</p><pre class="crayon-plain-tag"># Vue.jsプロジェクトの生成 (説明を簡単にするためVue 3用のプリセットを利用)
$ vue create ort-sample
Vue CLI v4.5.13
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
❯ Default (Vue 3) ([Vue 3] babel, eslint)
  Manually select features

# ONNX Runtime for Web (onnxruntime-web)のインストール
$ cd ort-sample
$ yarn add onnxruntime-web</pre><p></p>
<p>bundler版チュートリアルのJavaScript実装をVue.js用に移植します。vue-cliでプロジェクトを作ると最初からあるsrc/components/HelloWorld.vueをHelloONNX.vueにリネーム、補足情報を少し追加して移植します。</p><pre class="crayon-plain-tag">&lt;template&gt;
  &lt;div class=&quot;hello&quot;&gt;
    &lt;h1&gt;{{ msg }}&lt;/h1&gt;
    &lt;div&gt;
      &lt;button @click=&quot;run&quot; type=&quot;button&quot;&gt;run&lt;/button&gt;
    &lt;/div&gt;
    &lt;div class=&quot;info&quot;&gt;
      &lt;p&gt;&lt;label&gt;input a: &lt;/label&gt;{{ inputA }}&lt;/p&gt;
      &lt;p&gt;&lt;label&gt;input b: &lt;/label&gt;{{ inputB }}&lt;/p&gt;
      &lt;p&gt;&lt;label&gt;output c: &lt;/label&gt;{{ outputC }}&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import { InferenceSession, Tensor } from 'onnxruntime-web';

export default {
  name: 'HelloONNX',  // HelloWorldからリネームしてるだけ
  props: {
    msg: String
  },
  data() {
    return {
      inputA: &quot;&quot;, // inputA, inputB, outputCは画面表示用
      inputB: &quot;&quot;,
      outputC: &quot;&quot;,
      session: null, // InferenceSession
      tensorA: null, // Tensor
      tensorB: null  // Tensor
    };
  },
  async mounted() {
    this.session = await InferenceSession.create('model.onnx');  // モデルファイル名を指定してONNX Runtimeのセッション生成
    console.log(this.session.inputNames, this.session.outputNames);  // モデルの入出力層の名前を確認

    // prepare inputs. a tensor need its corresponding TypedArray as data
    const dataA = Float32Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
    const dataB = Float32Array.from([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]);
    this.tensorA = new Tensor('float32', dataA, [3, 4]);  // float32(単精度浮動小数点数)、3x4の行列データ
    this.tensorB = new Tensor('float32', dataB, [4, 3]);  // float32(単精度浮動小数点数)、4x3の行列データ

    this.inputA = dataA.toString();
    this.inputB = dataB.toString();
  },
  methods: {
    async run() {
      // prepare feeds. use model input names as keys.
      const feeds = { a: this.tensorA, b: this.tensorB };

      // feed inputs and run
      const results = await this.session.run(feeds);

      // read from results
      const dataC = results.c.data;
      this.outputC = dataC.toString();
    }
  }
};
&lt;/script&gt;
# CSS部分は省略</pre><p>onnxruntime-webのモジュールとして、<code>InferenceSession</code> と <code>Tensor</code> が提供されています。今のところ2つだけなので覚えることは少ないですが、Tensorにはリッチな機能は備わっていないので大半の処理は自前で実装する必要があります。公式チュートリアルでは以下の簡単な行列計算(3&#215;4と4&#215;3の行列積)のモデルを通すだけなので簡単です。モデルファイル本体(model.onnx)はチュートリアルに付属しています。また、<code>InferenceSession</code> のAPIはPromiseを返すので <code>async/await</code> を付けて出力を受け取ります。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/model.png?x40497" alt="" width="130" height="244" class="alignnone size-full wp-image-3652" />  (Netronでmodel.onnxを可視化)</p>
<p>上記モデル構造については実装上で少し意識する必要があります。具体的にはモデルの入力と出力層の名前を知る必要がありますが、それは <code>InferenceSession.inputNames/outputNames</code> プロパティで取得できます。ここでは入力層が a,b で出力層が c となっています。</p>
<p>そろそろ動作確認したいところですが、実行前に事前準備をしておきます。モデルファイルとwasmファイルの配置です。チュートリアルではCopyPluginを使っているのですが、たいしたことをしていないので適切な場所にファイルを配置して、あとはvue-cli-serviceに任せます。具体的にはpublicディレクトリ以下に該当ファイルを置くだけでビルド時にコピーしてくれるようです。</p><pre class="crayon-plain-tag">$ tree -L 2 public
public
├── index.html
├── js
│&nbsp;&nbsp; ├── ort-wasm-threaded.wasm
│&nbsp;&nbsp; └── ort-wasm.wasm
└── model.onnx</pre><p>wasmファイルはnode_modules/onnxruntime-web/dist/以下にありますのでコピーしてpublic/js/に、モデルファイル(model.onnx)をpublic/直下に配置したら動作確認してみます。</p><pre class="crayon-plain-tag">$ yarn serve
 DONE  Compiled successfully in 10864ms                                                                         14:53:59

  App running at:
  - Local:   http://localhost:8080
  - Network: http://{おまえのIPアドレス}:8080

  Note that the development build is not optimized.
  To create a production build, run yarn build.</pre><p>あとはlocalhostでブラウザから確認します。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/2021-09-21_14h56_00.png?x40497" alt="" width="479" height="495" class="alignnone size-full wp-image-3653" srcset="https://rest-term.com/wp-content/uploads/2021/09/2021-09-21_14h56_00.png 479w, https://rest-term.com/wp-content/uploads/2021/09/2021-09-21_14h56_00-290x300.png 290w" sizes="(max-width: 479px) 100vw, 479px" /></p>
<p>ページの見出しタイトルとかモデル構造画像はおまけで表示していますが、肝心の計算の方も動作確認できました。</p>
<p>ここからはチュートリアルには載っていない追加情報となります。</p>
<h4 class="term">ONNX Runtime for WebをWebGLで使う</h4>
<p>ONNX Runtime for WebはデフォルトだとCPU(WebAssembly)が使われるようですがGPU(WebGL)を使うことも出来ます。WebGLを有効にするにはセッション作成時に <code>executionProviders</code> オプションを指定するだけです。</p><pre class="crayon-plain-tag">const option =  {executionProviders: ['webgl']};
const session = await InferenceSession.create('model.onnx', option);</pre><p>そこそこの規模の計算になる場合は基本的にCPU(WebAssembly)よりGPU(WebGL)の方が高速に動作しますので是非覚えておきたいです。対応ブラウザについてですが、現在の主要なWebブラウザはWebGL対応しているのでそこは心配しなくても良さそうです。そもそもWebGL対応していないブラウザだとTyped Arrayも恐らく使えないためWebAssembly版も動作しないと考えられます。</p>
<h4 class="term">モデルファイル読み込みの注意点</h4>
<p>チュートリアルに付属しているモデルファイル(model.onnx)は120バイトですが、実用上のモデルファイルは数十MB、数百MBの大きさになるのでファイル読み込み時に注意が必要です。<code>InferenceSession</code>にはそのための機能も備わっているので是非使いましょう。</p><pre class="crayon-plain-tag">const modelFilePath = './model.onnx';
if (typeof fetch !== 'undefined') {
  const response = await fetch(filePath);
  const buffer = await response.arrayBuffer();
  const option =  {executionProviders: ['webgl']};
  const session = await InferenceSession.create(buffer, option); // データのArrayBufferとオプションを指定
}</pre><p>ファイル読み込みとセッション生成処理を分けて実行しています。実際のVueアプリケーションにおいては画面初期化処理の裏でファイル読み込みを事前に行っておくのが良いかと思います。<a href="https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch">fetch API</a>は主要なブラウザならほぼ実装されているかと思いますが、ファイル取得部分は他の方法を使っても問題ありません。</p>
<h3 class="term">自前のモデルファイルを使う</h3>
<p>ちょうど前回の記事でPyTorchの新しいパッケージング機能を紹介するときにモデルファイルを作ったので流用します。こちらはCNNのAlexNetをCIFAR-10で学習したモデルになります。</p>
<ul>
<li><a href="https://rest-term.com/archives/3645/">PyTorchモデルの新しいパッケージング機能について</a></li>
<p>　</p>
<li><a href="https://github.com/wellflat/pytorch-samples/blob/master/v1.9/alexnet_cifar10.ipynb" class="broken_link">pytorch-samples/alexnet_cifar10.ipynb at master · wellflat/pytorch-samples</a></li>
</ul>
<p>ONNXファイルへの変換はPyTorchの機能を使えば簡単です。以下にサンプルコードを載せますが、<code>torch.onnx</code>パッケージにある<code>export</code>を使うだけです。細かいオプションは他にもありますがここでは省略しています。</p><pre class="crayon-plain-tag">from alexnet import AlexNet
import torch
from torch import nn, onnx

def convert_onnx(model: nn.Module, output_path: str):
    model.eval()
    dummy_input = torch.randn(1, 3, 32, 32, requires_grad=True)
    onnx.export(
        model,
        dummy_input,
        output_path,
        export_params=True,
        input_names=[&quot;input&quot;],
        output_names=[&quot;output&quot;]
    )

if __name__ == '__main__':
    model_path = './cifar10_net.pth'
    device = torch.device(&quot;cuda:0&quot; if torch.cuda.is_available() else &quot;cpu&quot;)
    net = AlexNet()
    net.load_state_dict(torch.load(model_path, map_location=device))
    output_path = './cifar10_net.onnx'
    convert_onnx(net, output_path)</pre><p>これで cifar10_net.onnx ファイルが生成されていれば成功ですが、念のためにORT Webで読み込んでみると確実だと思います。AlexNetの小さいモデルですが90MB弱ありましたので、前述のテクニックで事前読み込みをしておくのが良さそうです。ちなみにORT Webに限らずモデルファイルを読み込んだ後に数回処理を通してウォーミングアップする手順(定常運用時の処理を速くする目的)がありますがそれは別の機会に紹介します。</p>
<h3 class="term">おわりに</h3>
<p>とりあえず今回はORT Webの触りの部分と補足情報を紹介しましたが。チュートリアルの行列計算では面白くないので、もう少し分かりやすいアプリケーションを作ってみたいと思います。</p>
<p>ORT Webを軽く触ってみて個人的に使うのが難しいと思った点について書くと、ORT Webの推定ユーザー層がデータサイエンス系のエンジニアやリサーチャーの方とは異なるというところかなと思います。チュートリアル程度の内容であればとっつきやすいですが、まともなアプリケーションを作ろうと思ったときに現在のORT Webがフロントエンド層のライブラリとしてユーザーフレンドリーとは言い辛いです。ORT WebのTensorがPythonのNumPyと比較すると機能が貧弱だったり、各種前処理・後処理をサポートするユーティリティが付属していない点については、今後のアップデートで改善されるかもしれませんが、今のところ使い勝手の点では従来のonnx.jsと同等水準のインタフェースなので、Webアプリ作る層のエンジニアにとっては多分使いにくいだろうなという印象です。</p>
<p>逆に言うと、データサイエンスがわかるフロントエンドエンジニアにとっては、ORT Web用の便利ライブラリを作れば周りに貢献できるかもしれません。まずは気軽に触ってみるところから始めてみるのが良いのではないでしょうか。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3649/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PyTorchモデルの新しいパッケージング機能について</title>
		<link>https://rest-term.com/archives/3645/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=pytorch%25e3%2583%25a2%25e3%2583%2587%25e3%2583%25ab%25e3%2581%25ae%25e3%2583%2591%25e3%2583%2583%25e3%2582%25b1%25e3%2583%25bc%25e3%2582%25b8%25e3%2583%25b3%25e3%2582%25b0%25e3%2581%25ab%25e3%2581%25a4%25e3%2581%2584%25e3%2581%25a6</link>
					<comments>https://rest-term.com/archives/3645/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Mon, 06 Sep 2021 12:00:00 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3645</guid>

					<description><![CDATA[前回に引き続きPyTorch 1.9のリリースノートを眺めていたら、PyTorchのモデルとコードをパッケージングするための新機能が入っているようだったので調べてみました。 PyTorch 1.9 Release, including torch.linalg and Mobile Interpreter &#124; PyTorch New APIs to optimize performance and packaging for model inference deployment 前半の opti&#8230;]]></description>
										<content:encoded><![CDATA[<p><a href="https://rest-term.com/archives/3622/">前回</a>に引き続きPyTorch 1.9のリリースノートを眺めていたら、PyTorchのモデルとコードをパッケージングするための新機能が入っているようだったので調べてみました。</p>
<ul>
<li><a href="https://pytorch.org/blog/pytorch-1.9-released/">PyTorch 1.9 Release, including torch.linalg and Mobile Interpreter | PyTorch</a></li>
</ul>
<blockquote><p>New APIs to optimize performance and packaging for model inference deployment</p></blockquote>
<p>前半の optimize performance の部分については、<strong>Inference Mode</strong> の紹介を前回したのでそちらを参照してください。</p>
<ul>
<li><a href="https://rest-term.com/archives/3622/">PyTorchの新しい推論モードについて</a></li>
</ul>
<p>今回は packaging for model inference deployment の部分を調べてみたところ、<a href="https://pytorch.org/docs/1.9.0/package.html"><code>torch.package</code></a> というモジュールがβとして追加されたようです。</p>
<blockquote><p>torch.package is a new way to package PyTorch models in a self-contained, stable format. A package will include both the model’s data (e.g. parameters, buffers) and its code (model architecture). Packaging a model with its full set of Python dependencies, combined with a description of a conda environment with pinned versions, can be used to easily reproduce training. Representing a model in a self-contained artifact will also allow it to be published and transferred throughout a production ML pipeline while retaining the flexibility of a pure-Python representation. </p></blockquote>
<h3 class="term">お試し</h3>
<p>公式のチュートリアルでは、DCGANのモデルをPyTorch Hub(torch.hub)から取得してモデルをエクスポート/インポートしていますが、生成されるパッケージの内部構造等についてわかりにくかったので、手元でシンプルなモデルを作ってパッケージングします。</p>
<h4 class="term">前準備</h4>
<p>パッケージングのテストなので、モデルを作る部分は簡単に済ませます。ここではAlexNetをPyTorch Hubやtorchvision経由で取得せずに自前で実装し、CIFAR-10データセットを学習したモデルを準備しておきます。</p>
<ul>
<li>データセット: CIFAR-10</li>
<li>モデルアーキテクチャ: AlexNet</li>
</ul>
<p>学習処理部分のコードを全て載せると長くなるので、GitHubにGoogle Colab用のノートブックを上げておきます。ちなみにノートブック内では前回紹介した <code>torch.inference_mode</code> を使ってバリデーションとテストしてますが特に不具合などは無さそうでした。v1.9以降は <code>torch.no_grad</code> の代わりに使っても良さそうです。細かい学習処理部分のパラメータなどはupしたノートブックの中を参照してください。</p>
<ul>
<li><a href="https://github.com/wellflat/pytorch-samples/blob/master/v1.9/alexnet_cifar10.ipynb" class="broken_link">alexnet_cifar10.ipynb</a></li>
</ul>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/2021-09-04_15h23_48.png?x40497" alt="" width="735" height="115" class="alignnone size-full wp-image-3646" srcset="https://rest-term.com/wp-content/uploads/2021/09/2021-09-04_15h23_48.png 735w, https://rest-term.com/wp-content/uploads/2021/09/2021-09-04_15h23_48-300x47.png 300w" sizes="(max-width: 735px) 100vw, 735px" /></p>
<p>AlexNetはCIFAR-10用に畳み込み層のカーネルサイズとか微修正したものになります。</p><pre class="crayon-plain-tag"># alexnet.py
import torch
from torch import nn


class AlexNet(nn.Module):
    features: nn.Sequential
    classifier: nn.Sequential

    def __init__(self, num_classes: int=10):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(64, 192, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2),
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 2 * 2, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )
    
    def forward(self, x: torch.Tensor) -&gt; torch.Tensor:
        x = self.features(x)
        x = x.view(x.size(0), 256 * 2 * 2)
        x = self.classifier(x)
        return x</pre><p>モデルを定義したファイルは後のパッケージング処理で必要となるのでファイル名はわかりやすいように付けておくと良いです。</p>
<h3 class="term">モデルのパッケージング</h3>
<p>やっと本題ですが、パッケージング作業自体は簡単です。以下のように作ったモデルとコードを <code>torch.package</code> を使ってパッケージングします。</p><pre class="crayon-plain-tag">import torch
from torch import package
from alexnet import AlexNet  ## 前述のAlexNet (alexnet.py)

model_path = &quot;./cifar10_net.pth&quot;  ## 前述のAlexNetでCIFAR-10を学習したモデルファイルへのパス
pt_path = &quot;./alexnet_cifar10.pt&quot;  ## 任意の出力パッケージファイルのパス
package_name = &quot;alexnet_cifar10&quot;  ## 任意のパッケージ名
resource_name = &quot;model.pkl&quot;  ## パッケージ内に含める任意のモデルファイル名 (pickleファイル)

device = torch.device(&quot;cuda:0&quot; if torch.cuda.is_available() else &quot;cpu&quot;)
model = AlexNet()
model.load_state_dict(torch.load(model_path, map_location=device))
print(model)

## torch.package.PackageExporter でパッケージング処理
with package.PackageExporter(pt_path, verbose=False) as exporter:
    exporter.intern(&quot;alexnet&quot;)  ## 前述のalexnetモジュール(alexnet.py)をパッケージに含める
    exporter.save_pickle(package_name, resource_name, model)  ## パッケージファイル出力</pre><p>これで alexnet_cifar10.pt という名前のパッケージファイルが出力されます。PyTorch公式には拡張子は.ptを使っているようで、ファイルの実体はzipファイルとなっています。</p><pre class="crayon-plain-tag">$ file alexnet_cifar10.pt
alexnet_cifar10.pt: Zip archive data</pre><p>次はこのzipファイルの内部構造を見てみます。</p><pre class="crayon-plain-tag">$ unzip alexnet_cifar10.pt
$ tree -a alexnet_cifar10
alexnet_cifar10
├── .data
│&nbsp;&nbsp; ├── 94579536640256.storage
│&nbsp;&nbsp; ├── 94579536682800.storage
│&nbsp;&nbsp; ├── 94579537130400.storage
│&nbsp;&nbsp; ├── 94579537131952.storage
│&nbsp;&nbsp; ├── 94579537132464.storage
│&nbsp;&nbsp; ├── 94579537134640.storage
│&nbsp;&nbsp; ├── 94579537135152.storage
│&nbsp;&nbsp; ├── 94579537136816.storage
│&nbsp;&nbsp; ├── 94579537137328.storage
│&nbsp;&nbsp; ├── 94579537138944.storage
│&nbsp;&nbsp; ├── 94579537141824.storage
│&nbsp;&nbsp; ├── 94579537158848.storage
│&nbsp;&nbsp; ├── 94579537159360.storage
│&nbsp;&nbsp; ├── 94579537176384.storage
│&nbsp;&nbsp; ├── 94579537340864.storage
│&nbsp;&nbsp; ├── 94579537341536.storage
│&nbsp;&nbsp; ├── extern_modules
│&nbsp;&nbsp; └── version
├── alexnet.py
└── alexnet_cifar10
    └── model.pkl</pre><p>今回はシンプルなモデルなので、上記のパッケージング処理のコードを見れば内部構造との対応がだいたいわかるかと思います。指定したパッケージ名(alexnet_cifar10)のディレクトリが作られ、その中に model.pkl ファイルが出力されています。また、AlexNetを定義したファイル(alexnet.py)も指定通りに含まれています。補足として、.data ディレクトリ内にある .storage ファイル群はシリアライズされたTensorデータの実体となるバイナリファイル、extern_modules は依存モジュールが書かれたテキストファイルです。依存モジュールは良い感じに自動検知してくれます(内部的にはpickletoolsを使用して依存関係チェックしている)。</p><pre class="crayon-plain-tag">$ cat alexnet_cifar10/.data/extern_modules
torch
torch.nn
collections
builtins
torch.nn.modules.container
torch.nn.modules.conv
torch._utils
torch.nn.modules.activation
torch.nn.modules.pooling
torch.nn.modules.dropout
torch.nn.modules.linea</pre><p></p>
<p><code>torch.package</code> は内部的には <a href="https://docs.python.org/ja/3/library/pickle.html"><code>pickle</code></a> モジュールに依存しており、モデルのファイル形式もpickleフォーマットになります。</p>
<h3 class="term">モデルの読み込み</h3>
<p>読み込みも簡単です。<code>torch.package.PackageImporter</code> を使います。</p><pre class="crayon-plain-tag">pt_path = &quot;./alexnet_cifar10.pt&quot;
package_name = &quot;alexnet_cifar10&quot;
resource_name = &quot;model.pkl&quot;

## torch.package.PackageImporter でパッケージ読み込み
importer = package.PackageImporter(pt_path)
loaded_model = importer.load_pickle(package_name, resource_name)
print(loaded_model)

## 出力結果
AlexNet(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (3): Conv2d(64, 192, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (4): ReLU(inplace=True)
    (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (6): Conv2d(192, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (7): ReLU(inplace=True)
    (8): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU(inplace=True)
    (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (classifier): Sequential(
    (0): Dropout(p=0.5, inplace=False)
    (1): Linear(in_features=1024, out_features=4096, bias=True)
    (2): ReLU(inplace=True)
    (3): Dropout(p=0.5, inplace=False)
    (4): Linear(in_features=4096, out_features=4096, bias=True)
    (5): ReLU(inplace=True)
    (6): Linear(in_features=4096, out_features=10, bias=True)
  )
)</pre><p>正しく読み込みができているようです。</p>
<h3 class="term">機能説明補足</h3>
<p>torch.package モジュールについて機能面を整理してみます。ここでは簡単なモデルをパッケージングしたのでモデルファイル以外の添付物はalexnet.pyのみでしたが、テキストファイルなど任意のファイルを含めることができます。<code>torch.package.PackageExporter</code> には <code>save_pickle</code> 以外にも <code>save_text</code> (テキストファイルを添付)や <code>save_binary</code> (Pythonオブジェクトをシリアライズしたバイナリファイルを添付)メソッドが提供されています。<code>torch.package.PackageImporter</code>による読み込みも同様です。</p><pre class="crayon-plain-tag">exporter.save_pickle(&quot;my_resources&quot;, &quot;tensor.pkl&quot;, torch.randn(4))
exporter.save_text(&quot;config_stuff&quot;, &quot;words.txt&quot;, &quot;a sample string&quot;)
exporter.save_binary(&quot;raw_data&quot;, &quot;binary&quot;, my_bytes)</pre><p></p>
<p>また、パッケージ内に含める/含めないは intern / extern メソッドを使います。globパターンでの指定もOKです。<strong>注意点として、NumPyなどのC拡張モジュールはパッケージには含められないので明示的に extern で外しておく必要があります。</strong>また、モデル作成の再現ができるように学習部分の実装コードも含めておくと良いでしょう。</p><pre class="crayon-plain-tag">## torchvision.* モジュールを全て含める
exporter.intern(&quot;torchvision.**&quot;)
## numpy モジュールを含めない
exporter.extern(&quot;numpy&quot;)</pre><p></p>
<p>その他細かい機能はいろいろあるのですが、しばらく使ってみて知っておいた方が良い機能を見つけたらまた紹介します。</p>
<h3 class="term">不明、懸念点など</h3>
<p>上記サンプルコードを見てもわかるように、<code>torch.package.PackageImporter</code> でパッケージを読み込む場合、モジュール名やモデルファイル名を認識していないと読み込めないことになります。パッケージファイル単体で管理することは想定されていないということでしょうか。</p>
<p>ちなみにパッケージ内部構造はzipファイルを展開しなくても、以下のようにAPI経由で一応確認は出来ます。treeコマンドのような出力になるようです。ただ、内部構造がわかったところで、model.pklファイルがモデルファイルかどうかはわからないのですが。パッケージ内はモジュール毎にディレクトリが分かれているので、一つのモジュールにモデルファイルが一つだけという規約を作ればモデルファイル名は固定(例えばmodel.pkl)してもいいのかなとは思います。</p><pre class="crayon-plain-tag">importer = package.PackageImporter(pt_path)
print(importer.file_structure())

## 出力結果
─── ./alexnet_cifar10.pt
    ├── .data
    │   ├── 93997045004704.storage
    │   ├── 93997045047360.storage
    │   ├── 93997045494960.storage
    │   ├── 93997045496512.storage
    │   ├── 93997045497024.storage
    │   ├── 93997045499200.storage
    │   ├── 93997045499712.storage
    │   ├── 93997045501376.storage
    │   ├── 93997045501888.storage
    │   ├── 93997045503552.storage
    │   ├── 93997045506432.storage
    │   ├── 93997045523456.storage
    │   ├── 93997045523968.storage
    │   ├── 93997045540880.storage
    │   ├── 93997045540992.storage
    │   ├── 93997045706048.storage
    │   ├── extern_modules
    │   └── version
    ├── alexnet_cifar10
    │   └── model.pkl
    └── alexnet.py</pre><p></p>
<p>懸念点としては、この機能は1.9から追加されたということで、1.8以前では当然動かないということになります。小さな会社内ならともかく一般に普及させるには労力が必要そうですね。</p>
<h3 class="term">おわりに</h3>
<p>PyTorch1.9からモデルをパッケージングするための機能 <code>torch.package</code> モジュールが追加されました。データサイエンティストとか研究者の方々にとっては他者による実験の再現を行うのに有用ですし、プロダクションに適用するエンジニアにとっては公式フォーマットでパッケージングされていることでCI/CDの仕組みに載せやすいというメリットがあります。得体の知れないzipファイルだとセキュリティチェックに引っかかることもありますので。</p>
<p>一方で、自由度が高い故に多少冗長なところもあります。依存ファイルの多いモデルのパッケージングは少し面倒かもしれません。ある程度制約を設けることでパッケージングの際の労力が減らせるといいかなと思いました。とはいえ、本機能はPythonモジュールをsdistとかbdistで作ったことがあるエンジニアなら簡単に扱えるでしょう。また、MLOps系ツールは世の中にたくさんありますが、PyTorch公式のフォーマットであれば正式対応してくれる可能性も期待できそうです。パッケージング処理のコードをわざわざ書かなくても、MLOpsツール側で自動でパッケージングしてくれると楽ですね。</p>
<p>本機能はまだβ版ということもあるので荒削りな部分はありますが、今後もユーザーの意見を取り入れながらブラッシュアップされていくといいですね。興味があれば是非使ってみましょう。</p>
<ul>
<li>今回使った学習部分のソースコード(ipynb) <a href="https://github.com/wellflat/pytorch-samples/blob/master/v1.9/alexnet_cifar10.ipynb" class="broken_link">alexnet_cifar10.ipynb</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3645/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PCパーツ新調メモ 2021/08</title>
		<link>https://rest-term.com/archives/3625/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=pc%25e3%2583%2591%25e3%2583%25bc%25e3%2583%2584%25e6%2596%25b0%25e8%25aa%25bf%25e3%2583%25a1%25e3%2583%25a2-2021-08</link>
					<comments>https://rest-term.com/archives/3625/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Wed, 01 Sep 2021 01:00:29 +0000</pubDate>
				<category><![CDATA[diary/work]]></category>
		<category><![CDATA[hardware]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3625</guid>

					<description><![CDATA[PC環境を少し変えてしばらく経ったので、前回のエントリーからの差分をメモ(記載価格は購入当時の価格)。グラボ以外はだいたいパーツは揃ったかな。 CPU AMD Ryzen 5 5600X \38,888 Mother Board ASUS ROG STRIX B550-A GAMING \18,869 SSD CFD M.2 2280 NVMe PCI-E Gen.4 x 4(NVMe 1.3) PG3VNFシリーズ 1TB CSSD-M2B1TPG3VNF \16,980 RAM Corsai&#8230;]]></description>
										<content:encoded><![CDATA[<p>PC環境を少し変えてしばらく経ったので、<a href="https://rest-term.com/archives/3519/">前回のエントリー</a>からの差分をメモ(記載価格は購入当時の価格)。グラボ以外はだいたいパーツは揃ったかな。</p>
<table>
<tbody>
<tr>
<td>CPU</td>
<td><a href="https://www.amd.com/ja/products/cpu/amd-ryzen-5-5600x">AMD Ryzen 5 5600X</a></td>
<td>\38,888</td>
</tr>
<tr>
<td>Mother Board</td>
<td><a href="https://rog.asus.com/jp/motherboards/rog-strix/rog-strix-b550-a-gaming-model/">ASUS ROG STRIX B550-A GAMING</a></td>
<td>\18,869</td>
</tr>
<tr>
<td>SSD</td>
<td><a href="https://www.cfd.co.jp/product/ssd/cssd-m2b1tpg3vnf/">CFD M.2 2280 NVMe PCI-E Gen.4 x 4(NVMe 1.3) PG3VNFシリーズ 1TB CSSD-M2B1TPG3VNF</a></td>
<td>\16,980</td>
</tr>
<tr>
<td>RAM</td>
<td><a href="https://www.corsair.com/ja/ja/%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%83%BC/%E8%A3%BD%E5%93%81/CORSAIR-%E3%83%9B%E3%83%AF%E3%82%A4%E3%83%88-PC-%E3%83%93%E3%83%AB%E3%83%89/Vengeance-RGB-PRO-SL-%E3%83%9B%E3%83%AF%E3%82%A4%E3%83%88/p/CMH32GX4M4E3200C16W">Corsair DDR4-3200MHz VENGANCE RGB PRO 32GB</a></td>
<td>\27,800</td>
</tr>
<tr>
<td>Power Supply</td>
<td><a href="https://www.super-flower.com.tw/product-data.php?productID=96&amp;lang=en" class="broken_link">SUPERFLOWER LEADEXIII GOLD ARGB 850W</a></td>
<td>\16,254</td>
</tr>
<tr>
<td>Case</td>
<td><a href="https://www.fractal-design.com/ja/products/cases/define/define-7-compact/white/">Fractal Design Define 7 Compact White</a></td>
<td>\19,360</td>
</tr>
<tr>
<td>CPU Cooler</td>
<td><a href="https://www.coolermaster.com/jp/ja-jp/catalog/coolers/cpu-air-coolers/hyper-212-led-white-edition/" class="broken_link">Cooler Master Hyper 212 LED Turbo White Edition</a></td>
<td>\4,464</td>
</tr>
<tr>
<td>Case Fan</td>
<td><a href="https://www.in-win.com/ja/cooling/asp120">INWIN 120mm ARGB対応 PCケースファン Sirius Pure</a></td>
<td>\3,861</td>
</tr>
</tbody>
</table>
<p>今回は全体的に構成を変えて、ホワイト+シルバー基調にしてみた。前住んでた家はフローリングが濃いめの茶色だったんだけど、今は白系の床材で家具もだいたい白いので、パソコンも白基調の方が馴染むかなと思って。</p>
<p>ケースはFractal Designが好きなのでDefine 7 Compactにしてみた。こんな名前だけどそんなにコンパクトではない。写真だと見えないけど、左サイドパネルは強化ガラスでPC内部が見えるようになっている。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/08/DSC06373_crop.jpg?x40497" alt="" width="400" height="687" class="alignnone size-full wp-image-3633" srcset="https://rest-term.com/wp-content/uploads/2021/08/DSC06373_crop.jpg 400w, https://rest-term.com/wp-content/uploads/2021/08/DSC06373_crop-175x300.jpg 175w" sizes="(max-width: 400px) 100vw, 400px" /></p>
<p>パーツもだいたい白で揃えてみたけど、黒系のパーツに比べると選択肢が少ないのと少し割高な品が多かった。</p>
<p>B550の無難なマザーボード、一応白系統。ちなみに下にヨガマットを敷いて作業したけどけっこう楽でオススメ。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/08/DSC06388.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3632" srcset="https://rest-term.com/wp-content/uploads/2021/08/DSC06388.jpg 800w, https://rest-term.com/wp-content/uploads/2021/08/DSC06388-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/08/DSC06388-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p>CPUはRyzen 5800Xとだいぶ悩んだけど、TDP 65Wが魅力的だったので今回は5600Xにした。CPUが電力あまり使わないのでクーラーは簡易水冷ではなく空冷、Hyper 212はデュアルファンの製品だけど二つ付けてもたいして冷えるわけではなかったので一つにした。いや、本音を言うとデュアルにしたい、でもメモリに少しだけ干渉してたし仕方ない。。</p>
<p>電源はSuper Flowerの850Wでこちらも白い、ついでにARGB対応なので光る。ケースでほぼ隠れるんだけど。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/08/DSC06380.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3634" srcset="https://rest-term.com/wp-content/uploads/2021/08/DSC06380.jpg 800w, https://rest-term.com/wp-content/uploads/2021/08/DSC06380-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/08/DSC06380-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p>メモリも白、これもギラギラ光る。前のPCもVENGANCEだったので安心して使える。メモリは価格変動が激しくて買い時を見計うのが大変だった。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/08/DSC06394.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3635" srcset="https://rest-term.com/wp-content/uploads/2021/08/DSC06394.jpg 800w, https://rest-term.com/wp-content/uploads/2021/08/DSC06394-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/08/DSC06394-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p>SSDはPCI-E4.0対応のコスパ良い話題のやつ。マジで安いし、ほぼスペック通りで速い。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/08/DSC06372.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3627" srcset="https://rest-term.com/wp-content/uploads/2021/08/DSC06372.jpg 800w, https://rest-term.com/wp-content/uploads/2021/08/DSC06372-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/08/DSC06372-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/08/CrystalDiskMark_20210815014014_PCI4.0.jpg?x40497" alt="" width="782" height="436" class="alignnone size-full wp-image-3628" srcset="https://rest-term.com/wp-content/uploads/2021/08/CrystalDiskMark_20210815014014_PCI4.0.jpg 782w, https://rest-term.com/wp-content/uploads/2021/08/CrystalDiskMark_20210815014014_PCI4.0-300x167.jpg 300w, https://rest-term.com/wp-content/uploads/2021/08/CrystalDiskMark_20210815014014_PCI4.0-768x428.jpg 768w" sizes="(max-width: 782px) 100vw, 782px" /></p>
<p>あとはグラボを白系のRTX 3000系に変えれば一旦落ち着くかなと。白の3060Tiか3070Tiが欲しいけど、品薄でかなり割高なので厳しい。。もうしばらく待ち。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/DSC06426.jpg?x40497" alt="" width="800" height="553" class="alignnone size-full wp-image-3640" srcset="https://rest-term.com/wp-content/uploads/2021/09/DSC06426.jpg 800w, https://rest-term.com/wp-content/uploads/2021/09/DSC06426-300x207.jpg 300w, https://rest-term.com/wp-content/uploads/2021/09/DSC06426-768x531.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p>うーん、やはりグラボも白にしないと調和が取れない。あと、CPUクーラーのファンはリア向きの排気にしてるので、PC全体で吸気と排気で2枚ずつ。トップファンも付けたい気もするけど、穴だらけのトップパネルが気持ち悪くて付けたくない。そういえばサイドパネルのファンって最近ほとんど見かけなくなった気がする、時代かな。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/DSC06431.jpg?x40497" alt="" width="800" height="659" class="alignnone size-full wp-image-3642" srcset="https://rest-term.com/wp-content/uploads/2021/09/DSC06431.jpg 800w, https://rest-term.com/wp-content/uploads/2021/09/DSC06431-300x247.jpg 300w, https://rest-term.com/wp-content/uploads/2021/09/DSC06431-768x633.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/DSC06424.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3641" srcset="https://rest-term.com/wp-content/uploads/2021/09/DSC06424.jpg 800w, https://rest-term.com/wp-content/uploads/2021/09/DSC06424-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/09/DSC06424-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p>ちなみに普段はPC内の照明はOFFにしてる、光っても意味ないので。</p>
<p>これまでずっとMicro-ATXかMini-ITXだったけど、今回は10年ぶりくらいにATXに帰ってきた。ATXは広くて組みやすいのはいいけど、中がすっかすか。やっぱり狭いスペースを工夫してやりくりする方が楽しいかも。ケースをDefine 7 Compactにしたのは良かった。しばらくはこれでいこう。</p>
<p>スペック的にはCPUはCore i5-9600KからRyzen 5600Xに変えたことでそこそこ上がってるはず。プログラミングなどの開発系作業の効率はそれほど変わってないけど、LightroomやPhotoshopの作業は体感レベルで速くなった(特にLightroomの現像処理)。一方でグラボをまだ変えてないのでゲーム性能はそんなに変わってなさそうだった。早く3070Tiとか3080あたりに買い換えたい。</p>
<p>いろいろ書いたけど、2年半ぶりにPCを組めて楽しかった。コスパ悪い趣味だとは思うけど楽しいから仕方ない。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/7M303547.jpg?x40497" alt="" width="533" height="800" class="alignnone size-full wp-image-3639" srcset="https://rest-term.com/wp-content/uploads/2021/09/7M303547.jpg 533w, https://rest-term.com/wp-content/uploads/2021/09/7M303547-200x300.jpg 200w" sizes="(max-width: 533px) 100vw, 533px" /></p>
<hr>
<p><strong>10/22 追記</strong><br />
本記事を書いた直後くらいにやっとグラボも白いやつに変えた。後々Micro-ATXのサブマシンに流用する都合上、トリプルファンの大型ボードではなくデュアルファンの白を探したけどあまりいいものがなかった。</p>
<p>GALAKUROのGeForce RTX 3060 Ti「<strong>GK-RTX3060Ti-E8GB/WHITE</strong>」という製品、白いグラボの中ではけっこう有名だと思う。デュアルファンだとRTX3070のものもあるみたいだけどコスパが微妙なので3060Tiで妥協。</p>
<p>白いので今回組んだPCだと調和は取れているとは思う。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/DSC06521.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3668" srcset="https://rest-term.com/wp-content/uploads/2021/09/DSC06521.jpg 800w, https://rest-term.com/wp-content/uploads/2021/09/DSC06521-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/09/DSC06521-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/DSC06522.jpg?x40497" alt="" width="800" height="533" class="alignnone size-full wp-image-3669" srcset="https://rest-term.com/wp-content/uploads/2021/09/DSC06522.jpg 800w, https://rest-term.com/wp-content/uploads/2021/09/DSC06522-300x200.jpg 300w, https://rest-term.com/wp-content/uploads/2021/09/DSC06522-768x512.jpg 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
<p>これで一応パーツは揃ったかな。今回の構成だと3DMarkのTime Spy(DirectX 12対応グラフィックスベンチマーク)は以下の結果になった。BFVでWQHD 100FPS以上出るらしいし、RTX 2080相当だと考えると予想以上に良い結果。一方でPort Royal(レイトレーシングのベンチマーク)だと30FPS程度だったので3060Tiの実力相当という感じだろうか。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2021/09/3dmark_timespy_rtx3060ti.png?x40497" alt="" width="800" height="268" class="alignnone size-full wp-image-3670" srcset="https://rest-term.com/wp-content/uploads/2021/09/3dmark_timespy_rtx3060ti.png 800w, https://rest-term.com/wp-content/uploads/2021/09/3dmark_timespy_rtx3060ti-300x101.png 300w, https://rest-term.com/wp-content/uploads/2021/09/3dmark_timespy_rtx3060ti-768x257.png 768w" sizes="(max-width: 800px) 100vw, 800px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3625/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>PyTorchの新しい推論モードについて</title>
		<link>https://rest-term.com/archives/3622/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=pytorch%25e3%2581%25ae%25e6%2596%25b0%25e3%2581%2597%25e3%2581%2584%25e6%258e%25a8%25e8%25ab%2596%25e3%2583%25a2%25e3%2583%25bc%25e3%2583%2589%25e3%2581%25ab%25e3%2581%25a4%25e3%2581%2584%25e3%2581%25a6</link>
					<comments>https://rest-term.com/archives/3622/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Thu, 05 Aug 2021 13:47:03 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[c/c++]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3622</guid>

					<description><![CDATA[ひさしぶりのエントリー。今年度になってから家庭環境がガラッと変わってなかなかプライベートの一人の時間が取れず時間が空いてしまいました。 今回はPyTorch関連のメモ的な記事になります。最近仕事で古いPyTorchで作られたプロダクトを最新のPyTorchにアップグレードする対応をしていたのですが、バージョン1.4からいきなり最新バージョンと1.9に上げたのでちょっと大変でした。ここでは1.9から入った新機能の一つを紹介したいと思います。地味ではありますが、意外と気になる人が多そうな機能を調べて&#8230;]]></description>
										<content:encoded><![CDATA[<p>ひさしぶりのエントリー。今年度になってから家庭環境がガラッと変わってなかなかプライベートの一人の時間が取れず時間が空いてしまいました。</p>
<p>今回はPyTorch関連のメモ的な記事になります。最近仕事で古いPyTorchで作られたプロダクトを最新のPyTorchにアップグレードする対応をしていたのですが、バージョン1.4からいきなり最新バージョンと1.9に上げたのでちょっと大変でした。ここでは1.9から入った新機能の一つを紹介したいと思います。地味ではありますが、意外と気になる人が多そうな機能を調べてみました。</p>
<p>* <a href="https://pytorch.org/blog/pytorch-1.9-released/">PyTorch 1.9 Release, including torch.linalg and Mobile Interpreter | PyTorch</a></p>
<h3 class="term">Inference Mode</h3>
<blockquote><p>Inference Mode API allows significant speed-up for inference workloads while remaining safe and ensuring no incorrect gradients can ever be computed. It offers the best possible performance when no autograd is required. </p></blockquote>
<p>Performance Optimization and Toolingの項目に、<strong>Inference Mode API</strong>なるものがBeta版として追加されています。文字通り、推論モード用のAPIということなんですけど、PyTorchを以前から使っていた人は一見困惑するのではないかと思います。</p>
<p>まず整理としてこれまで推論時にどのような対応をしていたかというと、</p><pre class="crayon-plain-tag">model.eval()  ## torch.nn.Module.eval
with torch.no_grad():  ## disable autograd
    model(data)  # forward</pre><p>意味としては、評価モード(Dropouts Layers、BatchNorm Layersをスキップ)に切り替えて、自動微分を無効(勾配計算用パラメータを保存しないNoGrad Mode)にしてから実行することで不要な処理、無駄なメモリ消費を抑えて推論を実行することができます。<code>torch.no_grad()</code> は以下のようにデコレータとしても指定できます。</p><pre class="crayon-plain-tag">@torch.no_grad()
def doubler(x):
    return x * 2
z = doubler(x)
print(z.requires_grad)  ## False</pre><p></p>
<p>1.9で追加された新しい推論モード <code>torch.inference_mode()</code> も従来の <code>torch.no_grad()</code> と同様にコンテキストマネージャーとして提供されておりPythonレベルでの実装はこれまでとそう変わらないようにデザインされています。</p><pre class="crayon-plain-tag">x = torch.ones(1, 2, 3, requires_grad=True)
with torch.inference_mode():
    y = x * x
print(y.requires_grad)  ## False

@torch.inference_mode()
def func(x):
    return x * x
out = func(x)
print(out.requires_grad)  ## False</pre><p><code>inference_mode</code> を使った場合でも <code>requires_grad=False</code> になります。重要なのは <code>torch.no_grad()</code> と何が違うかということなので実装を見てみます。</p>
<p><code>torch.inference_mode</code> は前述の通りコンテキストマネージャーなのでPythonのclassとして実装されています。<br />
* <a href="https://github.com/pytorch/pytorch/blob/master/torch/autograd/grad_mode.py">pytorch/grad_mode.py at master · pytorch/pytorch · GitHub</a></p><pre class="crayon-plain-tag">class inference_mode(_DecoratorContextManager):
... 省略
    def __init__(self, mode=True):
        if not torch._jit_internal.is_scripting():
            super().__init__()
        # Holds a python binding to a RAII guard that can enable or disable
        # inference mode
        self._inference_mode_raii_guard = None
        self.mode = mode</pre><p><a href="https://ja.wikipedia.org/wiki/RAII">RAII</a>なる単語が出てきていますが、Resource Acquisition Is Initializationの略で，リソースの確保(Acquisition)と解放を変数の初期化(Initialization)と破棄に紐付けるというテクニックです。C++を書くプログラマには馴染みは深いかと思いますし、僕も学生時代にEffective C++を読んで学びました。最近流行りのRustだとRAIIが強制されるのでメモリ安全性を備えていると言われてますね。</p>
<p>Python実装内では単にモード切替フラグの管理だけで実装の本体は低レイヤー(C++)の方にあります。<br />
* <a href="https://github.com/pytorch/pytorch/blob/master/c10/core/TensorImpl.h">pytorch/TensorImpl.h at master · pytorch/pytorch · GitHub</a></p>
<p>ちなみにC10というのは少し前からPyTorchのバックエンド実装に用いられているコアライブラリを指します。ざっくり言うとCaffe2内部でも使っているATen(テンソル演算用C++モジュール)を統合したものです。C10という名前はCore TENsor Libraryからきているみたいな話もありますけど公式の由来なのかはわかりません。あまり興味もないですが。</p>
<p>細かい実装はATenの詳細に入るので省略しますが、ざっくり言うとInference Modeの場合は、そのブロック内でのみ使われる推論専用のTensorが用意され(RAII)、推論計算時にはInplace(破壊的)に値が更新されます。</p>
<p>また、この推論用に用意されるTensorには一部制限があり、<strong>Inference Modeの外側ではTensor内の値は変更不可、変更しようとするとRuntimeError</strong>となります。プロダクションでの推論処理用途なら特に問題は無いかと思います。また、Inference Modeで作られたTensorに対してView操作をしても追跡されません。</p><pre class="crayon-plain-tag">x = torch.ones(1, 2, 3, requires_grad=True)
with torch.inference_mode():
    y = x * x
y[0][0][1] = 2
RuntimeError: Inplace update to inference tensor outside InferenceMode is not allowed.You can make a clone to get a normal tensor before doing inplace update.See https://github.com/pytorch/rfcs/pull/17 for more details.</pre><p></p>
<p>また、Inference ModeのTensorは以下のような内部動作となります。</p>
<ul>
<li><code>torch.no_grad()</code> と同様に自動微分での勾配計算は無効になり、<code>grad_fn</code> を始め計算グラフの情報は常に記録されない</li>
<li>Version Counter(<code>torch.Tensor._version</code>)は保存されず、全てのオペレーションが追跡されない</li>
</ul>
<p>Version Counterというのは自動微分するために内部で必要となるデータバージョニング用のカウンター変数ですが推論用には不要なので保存されません。詳細については自動微分の実装に関わってくるのでここでは省略しますが、気になる方はATenの <code>ADInplaceOrView</code> あたりから読んでいくといいかと思います。ちなみにカウンターの値自体は以下のように簡単に確認できます。</p><pre class="crayon-plain-tag">v = values.add_(1).add_(1).sub_(1).sub_(1)
print(v._version)  ## =&gt; 4 (オペレーション毎にカウントアップされるデータバージョニング用変数)

## 推論モード
with torch.inference_mode():
    y = x * x
print(y._version)
## Inference Modeではversion counterは保存されない
Traceback (most recent call last):
  File &quot;./test.py&quot;, line 14, in &lt;module&gt;
    print(y._version)
RuntimeError: Inference tensors do not track version counter.</pre><p></p>
<p><code>torch.no_grad()</code> ではVersion Counterは保存していますし、Tensor内の値も後で変更可能です。Inference Modeは低レイヤー実装内でメモリ使用の無駄を省く処理がいろいろ施されているようでした。公式にも the extreme version of no-grad mode. としているので内部実装ではそこそこ差はあるようです。</p>
<h3 class="term">おわりに</h3>
<p>PyTorch 1.9から新しい推論モード <code>torch.inference_mode()</code> が追加されました。Pythonレベルだと、<code>torch.no_grad()</code> と同様にコンテキストマネージャーまたはデコレータとして利用することができるため書き換えは容易です。実体としては <code>torch.no_grad()</code> の単なるエイリアスというわけではなく、メモリ効率が改善された新たな推論処理特化の仕組みとなっています。実用上の不都合についてはまだプロダクションでは使っていないので性能面での定量評価もできていませんが、環境が整ったらより詳細を調査してみようと思います。</p>
<p>公式でも It is recommended that you try out inference mode in the parts of your code that do not require autograd tracking って言ってるのでぜひ試してみてください。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3622/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cloudflare Workersでサーバレス開発 part3 Workers KVを無料で使う</title>
		<link>https://rest-term.com/archives/3619/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=cloudflare-workers%25e3%2581%25a7%25e3%2582%25b5%25e3%2583%25bc%25e3%2583%2590%25e3%2583%25ac%25e3%2582%25b9%25e9%2596%258b%25e7%2599%25ba-part3-workers-kv%25e3%2582%2592%25e7%2584%25a1%25e6%2596%2599%25e3%2581%25a7%25e4%25bd%25bf%25e3%2581%2586</link>
					<comments>https://rest-term.com/archives/3619/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Mon, 23 Nov 2020 13:00:40 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[service]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3619</guid>

					<description><![CDATA[引き続きCloudflare Workersの話。今月、ありがたいことにCloudflare Workers KVのFree Tierが拡大されたようです。 Workers KV &#8211; free to try, with increased limits! 今までは動作確認程度しか使えませんでしたが、無料枠が拡大されたことによりコンテンツキャッシュとして使えそうかなという感じです。AWSでいうところのLambda + DynamoDBの組み合わせをCloudflareの無料枠でも実現で&#8230;]]></description>
										<content:encoded><![CDATA[<p>引き続きCloudflare Workersの話。今月、ありがたいことに<a href="https://www.cloudflare.com/ja-jp/products/workers-kv/">Cloudflare Workers KV</a>のFree Tierが拡大されたようです。</p>
<ul>
<li><a href="https://blog.cloudflare.com/workers-kv-free-tier/">Workers KV &#8211; free to try, with increased limits!</a></li>
</ul>
<p>今までは動作確認程度しか使えませんでしたが、無料枠が拡大されたことによりコンテンツキャッシュとして使えそうかなという感じです。AWSでいうところのLambda + DynamoDBの組み合わせをCloudflareの無料枠でも実現できるようになります。もちろんまだAWSと比べると細かい設定はできないどころか容量も桁違いに少ないですが、用途の限定すれば使い途はあるのではないでしょうか。</p>
<p>Workers KVの無料枠制限の内容だけ抜粋すると以下のようになります。</p>
<ul>
<li>1日10万回の読み取り操作</li>
<li>1日1万回の書き込み、削除、リスト操作</li>
<li>最大バリューサイズ25MB</li>
</ul>
<p>Cloudflareのブログ内では書き込みが1日1000回となっていましたが、実際の制限は10000回かと思われます(ダッシュボード上では10000回になっている、要確認)。また、最大バリューサイズ(1つのキーに保存できるデータ容量)が10MBから25MBに引き上げられたことで巨大なアセットでも保存できるようになりました。25MBもあれば十分ですね。</p>
<h3 class="term">環境構築</h3>
<p>過去のエントリーを参考にしていただければ。今回もJavaScriptでWorkerを作っていきます。</p>
<ul>
<li><a href="https://rest-term.com/archives/3603/">Cloudflare Workersでサーバレス開発</a></li>
</ul>
<p>* <strong>wrangler</strong><br />
wrangler 1.1x系だと絵文字たくさんですね。意味分かりませんが。</p><pre class="crayon-plain-tag"><img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f477.png" alt="👷" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" />  wrangler 1.12.2
The Wrangler Team &lt;wrangler@cloudflare.com&gt;

USAGE:
    wrangler [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    kv:namespace    <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f5c2.png" alt="🗂" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Interact with your Workers KV Namespaces
    kv:key          <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f511.png" alt="🔑" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Individually manage Workers KV key-value pairs
    kv:bulk         <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4aa.png" alt="💪" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Interact with multiple Workers KV key-value pairs at once
    route           <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" />  List or delete worker routes.
    secret          <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f92b.png" alt="🤫" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Generate a secret that can be referenced in the worker script
    generate        <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f46f.png" alt="👯" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Generate a new worker project
    init            <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f4e5.png" alt="📥" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Create a wrangler.toml for an existing project
    build           <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f980.png" alt="🦀" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Build your worker
    preview         <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f52c.png" alt="🔬" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Preview your code temporarily on cloudflareworkers.com
    dev             <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f442.png" alt="👂" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Start a local server for developing your worker
    publish         <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f199.png" alt="🆙" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Publish your worker to the orange cloud
    config          <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f575.png" alt="🕵" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Authenticate Wrangler with a Cloudflare API Token or Global API Key
    subdomain       <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f477.png" alt="👷" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Configure your workers.dev subdomain
    whoami          <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f575.png" alt="🕵" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Retrieve your user info and test your auth config
    tail            <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f99a.png" alt="🦚" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Aggregate logs from production worker
    login           <img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f513.png" alt="🔓" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Authenticate Wrangler with your Cloudflare username and password
    help            Prints this message or the help of the given subcommand(s)</pre><p></p>
<h3 class="term">Cloudflare Workers KV</h3>
<p>まずはwranglerでネームスペースを作ります。</p><pre class="crayon-plain-tag">wrangler-kv:namespace 
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f5c2.png" alt="🗂" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Interact with your Workers KV Namespaces

USAGE:
    wrangler kv:namespace &lt;SUBCOMMAND&gt;

FLAGS:
    -h, --help    Prints help information

SUBCOMMANDS:
    create    Create a new namespace
    delete    Delete namespace
    help      Prints this message or the help of the given subcommand(s)
    list      List all namespaces on your Cloudflare account

## 作業ディレクトリ(wrangler.tomlの置いてあるディレクトリ)で実行
## &quot;test&quot;ネームスペースを作成
$ wrangler kv:namespace create test
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f300.png" alt="🌀" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Creating namespace with title &quot;hello-test&quot;
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Success!
Add the following to your configuration file:
kv_namespaces = [ 
         { binding = &quot;test&quot;, id = &quot;4c8cd836b9bc4eb4ab41a64366801e91&quot; }
]</pre><p></p>
<h4 class="term">wrangler.tomlへの反映</h4>
<p>ネームスペースを作成したら忘れないように wrangler.toml にも記載しておきます。</p><pre class="crayon-plain-tag">name = &quot;hello&quot;
type = &quot;javascript&quot;
account_id = {your account ID}
workers_dev = true
## 以下を追加
kv_namespaces = [
  {binding = &quot;test&quot;, id = &quot;4c8cd836b9bc4eb4ab41a64366801e91&quot;}
]

[env.production]
route = &quot;rest-term.com/worker/hello&quot;
zone_id = {Zone ID}</pre><p></p>
<p>Workers KVには任意の静的ファイルやJSONデータを入れることができます。ここでは適当なテキストファイルを保存してみます。</p><pre class="crayon-plain-tag">wrangler-kv:key 
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/1f511.png" alt="🔑" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Individually manage Workers KV key-value pairs

USAGE:
    wrangler kv:key &lt;SUBCOMMAND&gt;

FLAGS:
    -h, --help    Prints help information

SUBCOMMANDS:
    delete    Delete a key and its value from a namespace
    get       Get a key's value from a namespace
    help      Prints this message or the help of the given subcommand(s)
    list      List all keys in a namespace. Produces JSON output
    put       Put a key-value pair into a namespace

## 適当なファイル hello_kv.txt を作成
$ echo &quot;hello, cloudflare workers kv&quot; &gt; hello_kv.txt
## test_key というキーでhello_kv.txtを保存
$ wrangler kv:key put test_key ./hello_kv.txt --path --binding test
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Success
## 正常に保存されたか確認
$ wrangler kv:key get test_key --binding test
hello, cloudflare workers kv</pre><p>ファイルの中身を保存したい場合はpathオプションが必要です。bindingオプションにはネームスペース名を指定します。</p>
<h4 class="term">JavaScriptからWorkers KVへのアクセス</h4>
<p>Workers KVのネームスペースがそのままJavaScriptオブジェクトになっていて、get/put/delete メソッドが提供されています。</p><pre class="crayon-plain-tag">// Workers KVの動作確認のための簡易コード
addEventListener('fetch', event =&gt; {
  event.respondWith(handleRequest(event.request))
})
/**
 * Respond with hello worker text
 * @param {Request} request
 */
async function handleRequest(request) {
  // namespace: test, key: test_key でKVからデータ取得
  const content = await test.get(&quot;test_key&quot;);
  return new Response(content, {
    headers: { 'content-type': 'text/plain' },
  })
}</pre><p>ここではデータ登録処理は省略しますが、<code>await test.put("test_key", "test_value")</code> のようにputメソッドを呼ぶだけです。オブジェクトそのものは保存できないので、<code>JSON.stringify()</code>などで文字列にシリアライズしてから保存します。注意点として、ネームスペース名は他の変数とコンフリクトしないように注意して付けないといけませんね。チュートリアルにあるように大文字にした方がわかりやすいかも。</p>
<h4 class="term">workers.devに反映</h4>
<p>開発環境(workers.dev)にデプロイします。前回はGitHub Action経由でデプロイしたんですが今回はコマンドから直接デプロイします。</p><pre class="crayon-plain-tag">$ wrangler publish
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" />  JavaScript project found. Skipping unnecessary build!
<img src="https://s.w.org/images/core/emoji/14.0.0/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" />  Successfully published your script to
 https://hello.wellflat.workers.dev</pre><p>表示されたURLにブラウザからアクセスして、&#8221;hello, cloudflare workers kv&#8221; が表示されたらOKです。簡単ですね。</p>
<h3 class="term">おわりに</h3>
<p>今回はCloudflare Workers KVの使い方を紹介しました。機能的にはシンプルなKVSなので目新しさはありませんが、Worker環境に無料枠のストレージ層が新たに拡大されてサーバレス環境で開発する動機付けがより強くなるのではないでしょうか。</p>
<ul>
<li><a href="https://github.com/wellflat/cloudflare-worker-samples">wellflat/cloudflare-worker-samples: Cloudflare Workers sample codes</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3619/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cloudflare Workersでサーバレス開発 part2 GitHub ActionsによるCI/CD</title>
		<link>https://rest-term.com/archives/3611/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=cloudflare-workers%25e3%2581%25a7%25e3%2582%25b5%25e3%2583%25bc%25e3%2583%2590%25e3%2583%25ac%25e3%2582%25b9%25e9%2596%258b%25e7%2599%25ba-part2-github-actions%25e3%2581%25ab%25e3%2582%2588%25e3%2582%258bci-cd</link>
					<comments>https://rest-term.com/archives/3611/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 22 Sep 2020 13:00:24 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[ops]]></category>
		<category><![CDATA[service]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3611</guid>

					<description><![CDATA[前回のエントリー Cloudflare Workersでサーバレス開発 ではCloudflare Workersの入門的な使い方について紹介しました。今回はGitHub ActionsとWranglerによるCI/CD対応についてデプロイ周りの補足説明をします。 Wrangler GitHub Action WranglerによるGitHub Action用のDockerコンテナアクションがCloudflareより提供されていますので是非活用しましょう。 cloudflare/wrangler-&#8230;]]></description>
										<content:encoded><![CDATA[<p>前回のエントリー <a href="https://rest-term.com/archives/3603/">Cloudflare Workersでサーバレス開発</a> ではCloudflare Workersの入門的な使い方について紹介しました。今回はGitHub ActionsとWranglerによるCI/CD対応についてデプロイ周りの補足説明をします。</p>
<h3 class="term">Wrangler GitHub Action</h3>
<p><a href="https://github.com/cloudflare/wrangler">Wrangler</a>によるGitHub Action用のDockerコンテナアクションがCloudflareより提供されていますので是非活用しましょう。</p>
<ul>
<li><a href="https://github.com/cloudflare/wrangler-action">cloudflare/wrangler-action</a></li>
</ul>
<p>GitHub Actionsの設定ファイルはこんな感じで。Publish Worker以下が wrangler-action によるデプロイの設定になります(ここではGitHub Actions自体の説明は省略します)。<br />
* .github/workflows/deploy.yml</p><pre class="crayon-plain-tag">name: Deploy to Cloudflare Workers
on:
  push:
    branches:
        - master
  pull_request:
  repository_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 60
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Publish Worker
        uses: cloudflare/wrangler-action@1.2.0
        with:
          workingDirectory: hello
          apiToken: ${{ secrets.CF_API_TOKEN }}
        env:
          CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}</pre><p>Workerを1レポジトリ内に複数作ってそれぞれビルド/デプロイするパイプラインを作るケースもあると思うので、その場合はwith句の workingDirectory で wrangler-action を実行するディレクトリを指定することができます。また、デプロイに必要な環境変数(クレデンシャル)として <strong>CF_API_TOKEN</strong> と <strong>CF_ACCOUNT_ID</strong> を指定します。GitHub上の [Settings] > [Secrets]からこの2つの値を設定しておきます。APIトークンとアカウントIDの確認方法は<a href="https://rest-term.com/archives/3603/">前回のエントリー</a>を参考にしてください。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/09/cf_secrets.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/09/cf_secrets-300x97.png?x40497" alt="" width="300" height="97" class="alignnone size-medium wp-image-3613" srcset="https://rest-term.com/wp-content/uploads/2020/09/cf_secrets-300x97.png 300w, https://rest-term.com/wp-content/uploads/2020/09/cf_secrets-1024x329.png 1024w, https://rest-term.com/wp-content/uploads/2020/09/cf_secrets-768x247.png 768w, https://rest-term.com/wp-content/uploads/2020/09/cf_secrets.png 1116w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<h4 class="term">その他のCloudflare Workers設定</h4>
<p>直接wranglerによる手動デプロイをする際に wrangler.toml ファイルを別途用意していましたが、GitHub Actionsでデプロイする際にも必要です。wrangler-action では現在対応していない設定に関しては wrangler.toml ファイルで設定しておきます。</p>
<p>* wrangler.toml (JavaScriptのWorkerをdevドメインにデプロイする例)</p><pre class="crayon-plain-tag">name = &quot;hello&quot;
type = &quot;javascript&quot;
account_id = &quot;&quot;
workers_dev = true
route = &quot;&quot;
zone_id = &quot;&quot;</pre><p>アカウントIDはGitHub Actionsのクレデンシャルで設定するので中身は空にしてレポジトリにアップロードしてOKです。上記例だとdevドメインにデプロイする際には wrangler.toml で指定する必要がありました。いずれは wrangler-action 上でルーティングやゾーン等の設定もできるといいなと思います。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/09/actions-success.png?x40497" alt="" width="401" height="380" class="alignnone size-full wp-image-3614" srcset="https://rest-term.com/wp-content/uploads/2020/09/actions-success.png 401w, https://rest-term.com/wp-content/uploads/2020/09/actions-success-300x284.png 300w" sizes="(max-width: 401px) 100vw, 401px" /></p>
<h3 class="term">Deploy Button</h3>
<p>Cloudflareは以下のようなCloudflare Workersのデプロイボタンも用意してくれています。</p>
<ul>
<li><a href="https://developers.cloudflare.com/workers/platform/deploy-button">Deploy button · Cloudflare Workers docs</a></li>
</ul>
<p>以下のようにGitHubレポジトリ上に設置することができて、押すと予め設定した内容でCloudflare Workersがデプロイされます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/09/cf-deploy-button.png?x40497" alt="" width="640" height="338" class="alignnone size-full wp-image-3615" srcset="https://rest-term.com/wp-content/uploads/2020/09/cf-deploy-button.png 640w, https://rest-term.com/wp-content/uploads/2020/09/cf-deploy-button-300x158.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>GitHub Actionsのワークフロー設定では前述のように repository_dispatch を指定しておきましょう。あとは以下のスニペットを任意の場所に置くだけでOKです。</p><pre class="crayon-plain-tag">[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/{ユーザー名}/{レポジトリ名})</pre><p></p>
<p>ボタン押下後、リクエストに成功すると以下のような画面に変わります。お疲れ様でした。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/09/cf_deploying.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/09/cf_deploying-300x298.png?x40497" alt="" width="300" height="298" class="alignnone size-medium wp-image-3612" srcset="https://rest-term.com/wp-content/uploads/2020/09/cf_deploying-300x298.png 300w, https://rest-term.com/wp-content/uploads/2020/09/cf_deploying-768x764.png 768w, https://rest-term.com/wp-content/uploads/2020/09/cf_deploying-144x144.png 144w, https://rest-term.com/wp-content/uploads/2020/09/cf_deploying.png 867w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<h3 class="term">おわりに</h3>
<p><a href="https://rest-term.com/archives/3603/">前回のエントリー</a>を書いた時点ではCloudflare Workersの不備というか機能不足の点を指摘しました。その後、今回紹介した wrangler-action のように周辺環境の機能拡張が行われ、他のFaaSプロダクトの品質に少しずつですが近づいてきた印象があります。CloudflareのCDNに乗っかっているユーザーはせっかくなのでWorkersの活用も検討しておいて良い時期になってきたのではないでしょうか。リージョンに縛られないグローバルな環境は魅力的なので今後の成長を期待したいプロダクトですね。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3611/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cloudflare Workersでサーバレス開発</title>
		<link>https://rest-term.com/archives/3603/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=cloudflare-workers%25e3%2581%25a7%25e3%2582%25b5%25e3%2583%25bc%25e3%2583%2590%25e3%2583%25ac%25e3%2582%25b9%25e9%2596%258b%25e7%2599%25ba</link>
					<comments>https://rest-term.com/archives/3603/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Sun, 12 Jul 2020 10:00:54 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[service]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3603</guid>

					<description><![CDATA[今回は日頃からいくつものサイト配信で大変お世話になっているCloudflare社が提供するCloudflare Workersを試してみました。 普段はあまりここでサービス紹介系の記事は書かないのですが、いくつかのドメインを運用している都合上、コンテンツ生成に必要なアプリケーションサーバの用意が最近少しずつ大変になってきたので、今回紹介するCloudflare Workersの活用で開発リソース削減を検討するための備忘録として記事にしています。 Cloudflare Workers Cloudf&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回は日頃からいくつものサイト配信で大変お世話になっているCloudflare社が提供する<a href="https://workers.cloudflare.com/">Cloudflare Workers</a>を試してみました。</p>
<p>普段はあまりここでサービス紹介系の記事は書かないのですが、いくつかのドメインを運用している都合上、コンテンツ生成に必要なアプリケーションサーバの用意が最近少しずつ大変になってきたので、今回紹介するCloudflare Workersの活用で開発リソース削減を検討するための備忘録として記事にしています。</p>
<h3 class="term">Cloudflare Workers</h3>
<p>Cloudflare WorkersはCloudflareが提供するサーバレスコンピューティング環境です。</p>
<ul>
<li><a href="https://workers.cloudflare.com/">Cloudflare Workers®</a></li>
</ul>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/07/cloudflare_workers_logo.png?x40497" alt="" width="500" height="159" class="alignnone size-full wp-image-3604" srcset="https://rest-term.com/wp-content/uploads/2020/07/cloudflare_workers_logo.png 500w, https://rest-term.com/wp-content/uploads/2020/07/cloudflare_workers_logo-300x95.png 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p>AWS LambdaやGoogle Cloud Functionと同様のサービスでFaaS(Function as a Service)と呼ばれることもあります。Cloudflare公式の説明はこちら。</p>
<ul>
<li><a href="https://www.cloudflare.com/learning/serverless/glossary/function-as-a-service-faas/">What Is Function as a Service (FaaS)? | Cloudflare</a></li>
</ul>
<p>Cloudflareは大規模なCDN事業をやってますから世界中にエッジサーバを持っています(2020/07現在は約200とのこと)。WorkersアプリケーションはCloudflareのグローバルネットワーク上に展開されるので、世界中のどこからアクセスしても低いレイテンシーで実行することができます。</p>
<h4 class="term">想定利用者</h4>
<p>後述しますが、FaaSプロダクトとしては機能面において他社プロダクトに劣っている部分はあるので、高機能で多機能なFaaS環境が必要であれば他社プロダクトを使うはずです。Cloudflare Workersの想定利用者としては既にCloudflareのCDNを使ってコンテンツ配信しているサイト制作者が該当するのではないでしょうか。つまり自身のドメインを持っていてなんらかのサーバ運用もしている技術者にとっては使いやすいサービスなのだと思います。既にCloudflareのCDNでコンテンツ配信やHTTPS対応している人ならば、Cloudflare Workers導入のハードルは低くなっています。</p>
<p>あるいはCloudflareのグローバルネットワーク上で動作するFaaS環境を本気で必要としているサービス提供者でしょうか。AWSとかのパブリッククラウドだと結局はリージョン毎に区切られてリソースが割り当てられているので、どこでも同じパフォーマンスでサービス提供するのは困難です。Cloudflareのエッジにコードを載せることができれば、いつでもどこでも高いパフォーマンスでのサービス提供を実現できます。</p>
<h4 class="term">無料利用枠</h4>
<p>無料利用枠では以下のようなリソースが使えるようです。開発用のサブドメインが無料で使えるのはうれしいですね。<br />
<a href="https://www.cloudflare.com/ja-jp/products/cloudflare-workers/">Cloudflare Workers | サーバーレスコンピューティング | Cloudflare</a></p>
<ul>
<li>1日あたりリクエスト：10万（UTC+0）</li>
<li>最大30個のスクリプトをリリース</li>
<li>すべての200データセンターで実行</li>
<li>無料のworkers.devサブドメイン</li>
<li>リクエストあたりのCPU処理時間：10ミリ秒未満</li>
<li>最初のリクエスト後に最小レイテンシー</li>
</ul>
<h3 class="term">環境構築</h3>
<p>前述の通り、Cloudflare Workersを使ってみようと考える人はCloudflareのCDNサービスを利用済みという前提があるので、アカウント作成などの手順は省略してWorkersアプリケーション構築に必要な情報のみ整理してみます。2020/07現在、利用できるランタイムはJavaScript(Node.JS)/Rust/C/C++の4つです。今回は手軽なJavaScriptでWorkerを作ってみます。</p>
<h4 class="term">Wranglerのインストール</h4>
<p>まずはCloudflare Workersのプロジェクト管理用CLIツール(Cloudflare公式) <a href="https://github.com/cloudflare/wrangler">Wrangler</a> をインストールします。パッケージマネージャーは好きなものを使えば良いです。</p><pre class="crayon-plain-tag">## wranglerのインストール
$ yarn global add @cloudflare/wrangler
$ wrangler --help
 wrangler 1.10.3
The Wrangler Team &lt;wrangler@cloudflare.com&gt;

USAGE:
    wrangler [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    kv:namespace     Interact with your Workers KV Namespaces
    kv:key           Individually manage Workers KV key-value pairs
    kv:bulk          Interact with multiple Workers KV key-value pairs at once
    route            List or delete worker routes.
    secret           Generate a secret that can be referenced in the worker script
    generate         Generate a new worker project
    init             Create a wrangler.toml for an existing project
    build            Build your worker
    preview          Preview your code temporarily on cloudflareworkers.com
    dev              Start a local server for developing your worker
    publish          Publish your worker to the orange cloud
    config           Set up wrangler with your Cloudflare account
    subdomain        Configure your workers.dev subdomain
    whoami           Retrieve your user info and test your auth config
    tail             Aggregate logs from production worker
    help            Prints this message or the help of the given subcommand(s)</pre><p>wrangler という名前のCloudflareと関係ないモジュールも存在しているので @cloudflare/wrangler と指定します。ちなみにwrangler自体はRustで書かれているようです。</p>
<h4 class="term">API Tokenの取得</h4>
<p></p><pre class="crayon-plain-tag">To find your API Token, go to https://dash.cloudflare.com/profile/api-tokens
and create it using the &quot;Edit Cloudflare Workers&quot; template.</pre><p>Global API KeyではなくAPI Tokenを使いましょうとwranglerに注意されるので指示通りにAPI Tokenを作ります。以下のページ上部の Create Token ボタンからトークン発行できます。</p>
<ul>
<li><a href="https://dash.cloudflare.com/profile/api-tokens">My Profile | Cloudflare</a></li>
</ul>
<p>指示に従ってトークンを作り、wranglerに記憶させておきます。</p><pre class="crayon-plain-tag">$ wrangler config
... 省略
Enter API Token:
## ここに発行したAPI Tokenを入力
 Validating credentials...
 Successfully configured. You can find your configuration file at: /home/ryo/.wrangler/config/default.toml</pre><p></p>
<h4 class="term">プロジェクト作成</h4>
<p>引き続きwranglerを使ってWorkersプロジェクトを作ります。手動で必要なファイル群を揃えることはもちろんできますが、generateサブコマンドを使えばプロジェクト一式を生成できるので活用しましょう。</p><pre class="crayon-plain-tag">$ wrangler generate hello
  Installing cargo-generate v0.5.0...
 Creating project called `hello`...
 Done! New project created /home/ryo/workspace/dev/cloudflare-worker/hello
 You will need to update the following fields in the created wrangler.toml file before continuing:
 You can find your account_id in the right sidebar of your account's Workers page, and zone_id in the right sidebar of a zone's overview tab at https://dash.cloudflare.com
- account_id

$ ls
CODE_OF_CONDUCT.md  LICENSE_APACHE  LICENSE_MIT  README.md  index.js  package.json  wrangler.toml</pre><p>まずアカウントIDを設定ファイル wrangler.toml に書いておく必要があるとのことなので、アカウントIDを確認します。これはCloudflareのダッシュボードページに行かなくても wrangler コマンドから取得できます。</p><pre class="crayon-plain-tag">$ wrangler whoami

+------------------------------+----------------------------------+
| Account Name                 | Account ID                       |
+------------------------------+----------------------------------+
| {登録メールアドレス}m's Account | {アカウントID}                |
+------------------------------+----------------------------------+</pre><p>whoamiサブコマンドで表示されるAccount IDを wrangler.toml 内に書いておきます。</p><pre class="crayon-plain-tag">$ cd hello
$ cat wrangler.toml
name = &quot;hello&quot;
type = &quot;javascript&quot;
account_id = &quot;{アカウントIDをここに書く}&quot;
workers_dev = true
route = &quot;&quot;
zone_id = &quot;&quot;</pre><p>zone_idは開発環境では指定必須ではありません。ここでは後回しにします。</p>
<h3 class="term">実装</h3>
<p>generateサブコマンドではテンプレートを指定することができますが、何も指定しない場合は以下のようなhello worldのプログラムが出力されます。</p><pre class="crayon-plain-tag">$ cat index.js
addEventListener('fetch', event =&gt; {
  event.respondWith(handleRequest(event.request))
})
/**
 * Respond with hello worker text
 * @param {Request} request
 */
async function handleRequest(request) {
  return new Response('Hello worker!', {
    headers: { 'content-type': 'text/plain' },
  })
}</pre><p>Cloudflare Workersは独自のランタイム/API仕様ではなく<a href="https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API">Service Workers</a>仕様で動作するので、Service WorkersのAPI仕様に則ってアプリケーションを実装することができます。</p>
<p>また、workerテンプレートのギャラリーサイトがあるので眺めてみると良いです。もちろんJavaScriptだけでなくRustなど他言語のテンプレートも揃っています。</p>
<ul>
<li><a href="https://developers.cloudflare.com/workers/templates/">Template Gallery | Cloudflare Workers</a></li>
</ul>
<h4 class="term">動作確認</h4>
<p>devサブコマンドでローカルサーバを起動できます。今回は静的なレスポンスを返すだけなので確認も簡単です。</p><pre class="crayon-plain-tag">$ wrangler dev
 JavaScript project found. Skipping unnecessary build!
 watching &quot;./&quot;
 Listening on http://127.0.0.1:8787</pre><p>127.0.0.1:8787で起動するようです、curlで確認しましょう。</p><pre class="crayon-plain-tag">$ curl -v http://127.0.0.1:8787
* About to connect() to 127.0.0.1 port 8787 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8787 (#0)
&gt; GET / HTTP/1.1
&gt; User-Agent: curl/7.29.0
&gt; Host: 127.0.0.1:8787
&gt; Accept: */*
&gt; 
&lt; HTTP/1.1 200 OK
&lt; content-length: 13
&lt; content-type: text/plain
&lt; date: Fri, 10 Jul 2020 08:40:58 GMT
&lt; 
* Connection #0 to host 127.0.0.1 left intact
Hello worker!</pre><p>Hello worker! と出力されたので問題なさそうです。</p>
<h3 class="term">デプロイ</h3>
<h4 class="term">workers.dev</h4>
<p>まずはこのhello worldプログラムを開発環境 workers.dev にデプロイしてみます。既にアプリケーションIDは前述の手順で設定済みなのであとは公開するだけです。publishサブコマンドを使います。</p><pre class="crayon-plain-tag">$ wrangler publish
 JavaScript project found. Skipping unnecessary build!
 Successfully published your script to https://hello.wellflat.workers.dev</pre><p>{プロジェクト名}.{アカウント名}.workers.dev のドメインで開発環境でのアプリケーションが公開されるようです。ブラウザでアクセスすると、Hello worker! と表示されるはずです。</p>
<p>また、Cloudflareのダッシュボードページにも登録・公開したworkerがリストされます。(僕のブラウザではダークモード表示してるので黒いですが実際は白いサイトです)</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/07/worker_dashboard.png?x40497" alt="" width="692" height="368" class="alignnone size-full wp-image-3605" srcset="https://rest-term.com/wp-content/uploads/2020/07/worker_dashboard.png 692w, https://rest-term.com/wp-content/uploads/2020/07/worker_dashboard-300x160.png 300w" sizes="(max-width: 692px) 100vw, 692px" /></p>
<h4 class="term">production 本番環境</h4>
<p>次はproduction環境にデプロイしてみましょう。自身のドメインで公開されますので、既にCloudflareにドメイン設定している前提となります。production環境へのデプロイには別途Zone IDが必要ですが、こちらはwranglerコマンドでは取得できない(?ようなので、仕方なくダッシュボードページでZone IDを調べます。Overviewタブのページ右下のAPIのセクションにZone IDとAccount IDが表示されているので控えておいて wrangler.toml に書いておきます。以下の例のようにproduction環境用のセクションを追加してそこにZone IDとルーティングのパターンを書くだけです。routeにはもちろん自身のドメイン下のパスを指定します。</p><pre class="crayon-plain-tag">name = &quot;hello&quot;
type = &quot;javascript&quot;
account_id = &quot;{Account ID}&quot;
workers_dev = true


[env.production]
route = &quot;rest-term.com/worker/hello&quot;
zone_id = &quot;{Zone ID}&quot;</pre><p>公開するにはpublishサブコマンドのオプションでproduction指定するだけです。</p><pre class="crayon-plain-tag">$ wrangler publish --env production
 JavaScript project found. Skipping unnecessary build!
 Deployed to the following routes:
rest-term.com/worker/hello =&gt; created</pre><p>指定したrouteにアクセスして Hello worker! が表示されればOKです。</p>
<h3 class="term">おわりに</h3>
<p>workers.dev環境で動かしているとイマイチ面白くないのですが、やっぱり自分のドメイン上で動くのを見るとサーバレスの良さを実感しますね。オリジンにコードもランタイムも置かなくていいのは助かります。</p>
<p>一方で冒頭でも少し述べたように、単なるFaaS環境としてみると機能面では他社プロダクトより劣っている部分はあります。設定できる項目は2020/07現在だと環境変数とWorkers KVの名前空間だけですし(<a href="https://www.cloudflare.com/ja-jp/products/workers-kv/">Workers KV</a>は今回の記事では紹介対象外)、収集されるメトリクス情報もリクエスト数やエラー数、CPU時間くらいです。</p>
<p>また、今回試したプロジェクト一式はGitHubにも一応上げてありますので興味があれば。今後もCloudflare Worker関連のソースコードは適宜こちらのレポジトリに上げていく予定です。</p>
<ul>
<li><a href="https://github.com/wellflat/cloudflare-worker-samples">wellflat/cloudflare-worker-samples: Cloudflare Workers sample codes</a></li>
</ul>
<p>今回はJavaScriptでworkerを作ってみましたが、次はRust(Rust-generated WebAssembly)でも作ってみたいですね。Wrangler本体もRust製ですしCloudflare社内でも積極採用されてるのでしょうか。公式ブログでもRustカテゴリの記事がいろいろあります。</p>
<ul>
<li><a href="https://blog.cloudflare.com/tag/rust/">Rust &#8211; The Cloudflare Blog</a></li>
</ul>
<p>Cloudflareのリリースアナウンスのブログにもあるように、リージョンなどに縛られること無く、コードがグローバルネットワーク上のどこでも動くことこそがクラウドコンピューティングの夢であると。前述の通り、現時点では機能面で不足があるかもしれませんが、Cloudflareの巨大なグローバルネットワーク上でサーバレスアプリケーションを公開できることが最大のメリットなのだと思います。今後の発展に期待ですね。</p>
<blockquote><p>We believe the true dream of cloud computing is that your code lives in the network itself. Your code doesn&#8217;t run in &#8220;us-west-4&#8221; or &#8220;South Central Asia (Mumbai)&#8221;, it runs everywhere. </p></blockquote>
<p>* <a href="https://blog.cloudflare.com/cloudflare-workers-unleashed/">Everyone can now run JavaScript on Cloudflare with Workers</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3603/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>JavaScriptで機械学習の実装 6 t-SNEによる次元削減</title>
		<link>https://rest-term.com/archives/3590/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=javascript%25e3%2581%25a7%25e6%25a9%259f%25e6%25a2%25b0%25e5%25ad%25a6%25e7%25bf%2592%25e3%2581%25ae%25e5%25ae%259f%25e8%25a3%2585-6-t-sne%25e3%2581%25ab%25e3%2582%2588%25e3%2582%258b%25e6%25ac%25a1%25e5%2585%2583%25e5%2589%258a%25e6%25b8%259b</link>
					<comments>https://rest-term.com/archives/3590/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Fri, 08 May 2020 08:37:35 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[algorithm]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[machinelearning]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3590</guid>

					<description><![CDATA[ここ数ヶ月はネットワーク周りのネタ書いてたせいかあんまり頭を使ってなかった気がするので、このGW期間中は久しぶりに理論寄りのコードをけっこう書きました。今回はその一例を挙げます。 今回はt-SNE(t-Distributed Stochastic Neighbor Embedding: t分布型確率的近傍埋め込み法)による高次元データの次元削減および可視化を試してみました。t-SNEはちょっと前に流行って、今では幅広い分野で実用されている次元削減(次元圧縮)手法の一つです。古くは主成分分析(PC&#8230;]]></description>
										<content:encoded><![CDATA[<p>ここ数ヶ月はネットワーク周りのネタ書いてたせいかあんまり頭を使ってなかった気がするので、このGW期間中は久しぶりに理論寄りのコードをけっこう書きました。今回はその一例を挙げます。</p>
<p>今回は<strong>t-SNE(t-Distributed Stochastic Neighbor Embedding: <a href="https://ja.wikipedia.org/wiki/T%E5%88%86%E5%B8%83%E5%9E%8B%E7%A2%BA%E7%8E%87%E7%9A%84%E8%BF%91%E5%82%8D%E5%9F%8B%E3%82%81%E8%BE%BC%E3%81%BF%E6%B3%95">t分布型確率的近傍埋め込み法</a>)</strong>による高次元データの次元削減および可視化を試してみました。t-SNEはちょっと前に流行って、今では幅広い分野で実用されている次元削減(次元圧縮)手法の一つです。古くは主成分分析(PCA)やIsomapなど多くの手法がありましたが、それらと比較して優れた可視化結果が得られます。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/05/tsne_sample_result.png?x40497" alt="" width="500" height="505" class="alignnone size-full wp-image-3596" /></p>
<h3 class="term">t-SNE: t-Distributed Stochastic Neighbor Embedding</h3>
<p>元論文と、発案者の一人 Laurens van der Maaten 氏によるt-SNEの解説動画はこちら。元論文は図が多いとはいえ文字分量も多くて読むのがしんどいので、解説動画を見た方がずっとわかりやすいかと思います。数式も分解しながら図と併せて説明してくれてます。</p>
<ul>
<li><a href="http://www.cs.toronto.edu/~hinton/absps/tsne.pdf">Visualizing Data using t-SNE</a>(元論文 PDF)</li>
<li><a href="https://www.youtube.com/watch?v=RJVL80Gg3lA&#038;feature=emb_title">Visualizing Data Using t-SNE &#8211; YouTube</a></li>
</ul>
<p>日本語での説明は以下のスライドの説明が要点がまとまっていて個人的にわかりやすかったです。ありがとうございます。</p>
<ul>
<li><a href="https://www.slideshare.net/t_koshikawa/visualizing-data-using-tsne-56773191">Visualizing Data Using t-SNE</a>(slideshare)</li>
</ul>
<p>(※ 上記Webページのタイトルは全部同じですが別サイトです)</p>
<p>元論文にある基本的なアルゴリズムは以下のようになっています。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/05/t-sne-algo.png?x40497" alt="" width="632" height="328" class="alignnone size-full wp-image-3591" /></p>
<p>SNEは高次元空間での各データ点間のユークリッド距離を、類似度に相当する条件付き確率に変換して低次元空間にマッピングする手法ですが、いくつか欠点があり、それらを改善したのがt-SNEです。SNEでは損失関数の最小化が難しいという問題に対して、t-SNEでは分布を対称化(symmetric SNE)する工夫をしています。低次元分布と高次元分布を平均化している <code>set <em>pij</em></code> の箇所が該当します。対称性があると助かるというのは、例えば点Aと点Bがあったとして、Aから見たBまでの距離とBから見たAまでの距離が異なると近さをどう表現するか困るからですね。</p>
<p>また、低次元空間におけるデータ点間の類似度に自由度1の<a href="https://ja.wikipedia.org/wiki/T%E5%88%86%E5%B8%83">t分布</a>(Student&#8217;s t-distribution)を使って分布を表現しており、これがt-SNEの名前の由来となっています。ガウス分布より裾が広くなっているので、高次元空間において距離の遠いデータ点を低次元空間でも同様に遠くに配置することができ、距離が近いデータ点群と混ざりにくくなります。分布間での距離の関係性を保ちやすくなるということですね。ここで perplexity という重要なパラメータが出てくるのですが、これについてはデモの紹介と併せて後述します。</p>
<p>あとはSNEと同じ様に分布間の差異を<a href="https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%83%BB%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%BC%E6%83%85%E5%A0%B1%E9%87%8F">カルバック・ライブラー情報量</a>(KLD: Kullback-Leibler Divergence)の総和を損失関数(コスト関数)に設定し、勾配法でコツコツと最小化していきます。KLDの計算自体は簡潔です。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/05/kld.png?x40497" alt="" width="352" height="76" class="alignnone size-full wp-image-3594" /></p>
<p>勾配計算は、最終的にはこちらもシンプルな形になってます。(導出はここで数式をたくさん貼るのは大変なので、知りたい場合は元論文の Appendix A. Derivation of the t-SNE gradient を参照してください)</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/05/tsne_gradient.png?x40497" alt="" width="454" height="76" class="alignnone size-full wp-image-3595" /></p>
<h3 class="term">実装・検証</h3>
<p>いつもの縛りで、t-SNEモジュール本体は外部ライブラリ無しで素朴にJavaScriptで実装しました。前述の元論文にあるアルゴリズム(Algorithm 1)通りに書いているつもりです。つまりナイーブな実装になってるので効率化はほとんど行っていない理論学習用ですが、可視化の動作確認くらいはできるようになっているかと。論文内での文言もコード内のJSDocコメント等に適宜差し込んであるので、論文とコードを交互に追いながら理論を理解することもできるかなとは思います。GitHubに置いておくので興味あれば。</p>
<ul>
<li><a href="https://github.com/wellflat/imageprocessing-labs/tree/master/ml/t-sne">imageprocessing-labs/ml/t-sne at master &#8211; wellflat</a></li>
</ul>
<p>今回利用するデータは<a href="https://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits">digits</a>データセットを使います。MNISTの軽量版みたいなやつですね。64次元(8&#215;8)なので数字の判別は人の目でも難しいかも。後述しますが、局所性を保ってるか視覚的な確認もけっこう難しかったです。。</p><pre class="crayon-plain-tag">Classes: 10
Samples per class: ~180
Samples total: 1797
Dimensionality: 64
Features: integers 0-16</pre><p><a href="https://rest-term.com/wp-content/uploads/2020/05/digits_data_part.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/05/digits_data_part.png?x40497" alt="" width="426" height="211" class="alignnone size-full wp-image-3592" /></a></p>
<h4 class="term">開発環境</h4>
<p>* Node.JS 13.9.0<br />
* Babel 7.8.4<br />
* Vue.js 2.6.11</p>
<p>今回の実装では、指定回数まで一気に勾配法を進めるPromise(非同期)板と、勾配計算を1ステップ毎に一時停止するジェネレータ版の二つのインタフェースを用意しました。なのでES2017以降の環境が必要です。パラメータは perplexity/η(学習率)/α(モーメンタム) を指定できるようにしています。</p><pre class="crayon-plain-tag">import tSNE from './tsne';
import { digits } from './digits_data';  // digitsデータセット本体もモジュール化

const params = { perplexity: 30, eta: 100, alpha: 0.5 };  // パラメータ
const tsne = new tSNE(digits.data, params);
const maxIter = 1000;  // 反復回数

// Promise 非同期版
tsne.compute(maxIter).then(result =&gt; {
    console.log(result);  // result: Float64Array[], 二次元座標の配列
});
// async/await
const result = await tsne.compute(maxIter);

// ジェネレータ版
const it = tsne.iterator(maxIter);
let step = it.next();  // 1ステップずつ結果を返す
while(!step.done) {
    console.log(step.value);
    step = it.next();
}</pre><p></p>
<p>t-SNE自体は外部ライブラリ無しで書いていますが、デモツール作成用にはVue.js + <a href="https://www.chartjs.org/">Chart.js</a>を使いました。ここ数ヶ月は副業でVue.jsを触っているのでツール作るのも少し慣れてきました。まぁ今回は書き捨てツールなのでまともにコンポーネント設計はしてませんが、。</p>
<ul>
<li><a href="https://rest-term.com/labs/html5/tsne/">t-SNE demo tool</a></li>
</ul>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/05/tsne_projection_visualizeer-1024x604.png?x40497" alt="" width="695" height="410" class="alignnone size-large wp-image-3602" srcset="https://rest-term.com/wp-content/uploads/2020/05/tsne_projection_visualizeer-1024x604.png 1024w, https://rest-term.com/wp-content/uploads/2020/05/tsne_projection_visualizeer-300x177.png 300w, https://rest-term.com/wp-content/uploads/2020/05/tsne_projection_visualizeer-768x453.png 768w, https://rest-term.com/wp-content/uploads/2020/05/tsne_projection_visualizeer.png 1403w" sizes="(max-width: 695px) 100vw, 695px" /></p>
<p>最初は高次元分布の構築処理が走るため、少し時間と負荷がかかりますので注意してください。端末のスペックが低いとブラウザによって処理が停止させられるかも(?しれません。</p>
<p>t-SNEの重要なパラメータであるperplexityの扱いは難しく、実際に何度もテストして変化を確かめてました。perplexityはデータの局所的な特性と大域的な特性のどちらをより考慮するかのバランスを決めるパラメータですが、値によって最終的なクラスタの形が大きく変わってきます。元論文では5~50の間が適切とされているようです。今回作ったデモツールでもperplexityを変更できるようにしてあるので興味あればいろいろ試してみてください。</p>
<p>似ているように見える数字、例えば7と9のクラスタはperplexityをいい感じに設定すると期待通りに近くに配置されやすいですが、digitsデータセットは低解像度なので、そもそも似ているかどうか人間でも判別しにくかったりするので解釈が難しいです。分類されたクラスタは「局所性を保っているような&#8221;気がする&#8221;」という印象でした。やはりMNISTくらいの解像度は必要なのでしょう。</p>
<p>また、t-SNEの効率化方法として、Early exaggeration や Early compression と呼ばれる方法があります。Early exaggeration の方は実装が簡単だったので一応試してみましたが、digitsデータセット程度の規模ではいまいちよくわかりませんでした。GitHubに上げているコード内での該当箇所はコメントアウトして残してあるので、別のデータセットで試すときにもうちょっと効果を調べてみたいと思います。</p>
<h3 class="term">おわりに</h3>
<p>次元削減とデータ可視化に関連する話は久しぶりに書いたような気がします。t-SNEは前述の通りパラメータと結果の解釈が難しいですが、従来の手法よりも優れた可視化結果が得られます。局所性については今回試したデータだと上手く説明できませんでしたが、少なくとも綺麗にクラスタが分かれてくれることは確認できました。別のデータセットでも試してみる必要がありそうです。</p>
<p>また、2018年にUMAP(Uniform Manifold Approximation and Projection)という手法が提案されており、t-SNEより処理効率が良いとされているようです。SciPy2018でのUMAPの解説動画はこちら。UMAPも是非JavaScriptで実装してt-SNEと比較してみたいですね。</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=nq6iPZVUxZU">UMAP Uniform Manifold Approximation and Projection for Dimension Reduction | SciPy 2018</a></li>
</ul>
<p>今回作成したコード(t-SNEモジュール、<a href="https://vue-chartjs.org/">vue-chartjs</a>による可視化サンプルコード、デモツール)</p>
<ul>
<li><a href="https://github.com/wellflat/imageprocessing-labs/tree/master/ml/t-sne">imageprocessing-labs/ml/t-sne at master &#8211; wellflat</a></li>
<li><a href="https://github.com/wellflat/vue-chart-sample">vue-chart-sample at master -wellflat</a></li>
<li><a href="https://rest-term.com/labs/html5/tsne/">t-SNE demo tool</a></li>
</ul>
<h3 class="term">参考</h3>
<ul>
<li><a href="http://www.cs.toronto.edu/~hinton/absps/tsne.pdf">Visualizing Data using t-SNE</a> (元論文 PDF)</li>
<li><a href="https://www.youtube.com/watch?v=RJVL80Gg3lA&#038;feature=emb_title">Visualizing Data Using t-SNE &#8211; YouTube</a> (Laurens van der Maaten氏によるt-SNEの解説動画)</li>
<li><a href="https://www.slideshare.net/t_koshikawa/visualizing-data-using-tsne-56773191">Visualizing Data Using t-SNE</a> (slideshare)</li>
<li><a href="https://distill.pub/2016/misread-tsne/">How to Use t-SNE Effectively</a></li>
<li><a href="http://scienceandtechnology.jp/archives/19324">scRNAseq論文の図のtSNEて何？ &#8211; PUBLISH AND PERISH 日本の科学と技術PUBLISH AND PERISH 日本の科学と技術</a></li>
<li><a href="https://jetbead.hatenablog.com/entry/20160615/1465925347">t-SNEで遊ぶ &#8211; Negative/Positive Thinking</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3590/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Yamaha Network Organizer (YNO)を試す</title>
		<link>https://rest-term.com/archives/3567/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=yamaha-network-organizer-yno%25e3%2582%2592%25e8%25a9%25a6%25e3%2581%2599</link>
					<comments>https://rest-term.com/archives/3567/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Tue, 03 Mar 2020 14:48:07 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[ops]]></category>
		<category><![CDATA[service]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3567</guid>

					<description><![CDATA[ヤマハルーター関連記事がまさかの3連続。実機を使ってIPv6関連技術(IPoEやMAP-Eなど)の勉強がそれなりに出来たので、次はWeb寄りの話をまとめようかと思います。 今回は以前から試してみたかったYamaha Network Organizer (YNO)を試してみました。試してみましたが、個人用途かつ機器1台のみの利用なのであんまり有用な検証できてません。。一応、YNOの機能紹介程度にちょっと整理してみます。 環境 機器: YAMAHA RTX830 (Rev.15.02.09以降) 回&#8230;]]></description>
										<content:encoded><![CDATA[<p>ヤマハルーター関連記事がまさかの3連続。実機を使ってIPv6関連技術(IPoEやMAP-Eなど)の勉強がそれなりに出来たので、次はWeb寄りの話をまとめようかと思います。</p>
<p>今回は以前から試してみたかった<a href="https://network.yamaha.com/products/software_service/ysl-yno/index">Yamaha Network Organizer (YNO)</a>を試してみました。試してみましたが、個人用途かつ機器1台のみの利用なのであんまり有用な検証できてません。。一応、YNOの機能紹介程度にちょっと整理してみます。</p>
<h4 class="term">環境</h4>
<p>機器: YAMAHA RTX830 (Rev.15.02.09以降)<br />
回線: ドコモ光タイプA(GMO とくとくBB) ひかり電話契約無し(RA)<br />
接続方式: IPv6 IPoE + IPv4 over IPv6 (v6プラス)<br />
ONU -> RTX830 -> RT-AC86U</p>
<p>YNOには3カ月無償で利用できる試用ライセンスがあります。申し込みは以下のページから、メールでやり取りしてアカウント情報をもらったら、YNOサービスにアクセスしてログイン確認します。</p>
<ul>
<li><a href="https://www.scsk.jp/product/common/yamaha/yno_campaign.html">ヤマハネットワーク製品： 【Yamaha Network Organizer（YNO）無償試用ライセンス】のご案内</a></li>
</ul>
<h3 class="term">Yamaha Network Organizer (YNO)とは</h3>
<p>YNOはヤマハネットワーク機器をインターネット経由で遠隔監視・管理するためのクラウドサービスです。2020/03現在の対応製品は以下の通りです。ファームウェアは最新にしておきましょう。</p>
<blockquote><p>対応製品：NVR700W, NVR510, RTX5000, RTX3500, RTX1210, RTX830, RTX810, FWX120</p></blockquote>
<p>用語としては、管理対象となるヤマハネットワーク機器を<strong>YNOエージェント</strong>、クラウド上の管理サーバ及びWebインタフェースを<strong>YNOマネージャー</strong>と呼ぶそうです。</p>
<p>YNOの基本機能は公式にありますが以下の通りです。</p>
<blockquote><p>クラウド上でのネットワーク機器情報管理<br />
異常の一元把握<br />
複数機器設定の自動化<br />
『YNO』からのLAN環境把握<br />
ユーザー管理機能</p></blockquote>
<p>今回は手持ちのRTX830を1台のみで利用しているためメリットの多くを享受できていないかもしれませんが、ひととおり基本機能を試してみます。</p>
<h3 class="term">使い方</h3>
<p>操作方法は公式ドキュメントを見た方がわかりやすいのでリンクを貼ります。スクリーンショット付きで特に戸惑うことも無いかと思います。</p>
<ul>
<li><a href="http://www.rtpro.yamaha.co.jp/RT/docs/yno/manual/index.html">Yamaha Network Organizer(YNO) 操作マニュアル</a></li>
</ul>
<p>まずはYNOエージェント側(クライアント機器側)のYNOエージェント機能を有効にして、YNOのアクセスコードを設定します。アクセスコードというのは管理対象機器の識別・認証に利用するパスワードのようなもので、YNOマネージャーから登録しておきます。</p>
<ul>
<li><a href="http://www.rtpro.yamaha.co.jp/RT/docs/yno/manual/start_using_yno.html">YNOの使用を開始する</a></li>
</ul>
<p>登録したアクセスコードをYNOエージェント側にも設定します。</p><pre class="crayon-plain-tag"># yno use on
この機能を有効にするには「ライセンスキーの購入」と「購入時の規約の同意」が必要です
http://www.rtpro.yamaha.co.jp/RT/docs/yno/license.pdf
ライセンスキーの購入と規約の同意は済んでいますか? (Y/N)Y
# yno access code {オペレーターID} {アクセスコード}</pre><p>オペレーターIDというのは管理者アカウント名のようなものです。YNOにはユーザー管理機能が備わっており、全体の管理者(オペレーター)と一般ユーザー(個々のシステム担当者)を登録できます。今回は利用者は僕だけなのでオペレーターとして操作していきます。</p>
<p>アクセスコードを登録して、しばらく待つとYNOマネージャーとYNOエージェントの疎通が取れ、YNOマネージャーの機器一覧画面から確認することができます。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/2020-03-04_23h23_09-1.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/2020-03-04_23h23_09-1.png?x40497" alt="" width="1458" height="193" class="alignnone size-full wp-image-3574" /></a></p>
<p>プロトコルはCWMP/XMPP/GFWの3つでそれぞれ接続され、疎通状態は以下のコマンドで確認できます。</p><pre class="crayon-plain-tag"># show status yno
CWMP              :  正常 (2020/03/04 23:11:20)
XMPP              :  正常
GFW               :  正常
オペレーターID    :  {オペレーターID}</pre><p>CWMPとXMPPはよく知られたプロトコルですが、GFWというのはGUI Forwarderの略だそうです。中国のグレートファイアウォールかなとか最初は連想しましたけど違います。GUI ForwarderについてはYNOの目玉機能の一つなので後述します。</p>
<p>また、syslogにも以下例のようなYNO接続のログが出ているのが確認できるはずです。</p><pre class="crayon-plain-tag">2020/03/04 23:11:20: [YNO_AGENT] YNO session was established</pre><p></p>
<h4 class="term">機器情報の管理</h4>
<p>YNOに登録された機器情報を一元管理できます。リモートから各種情報がわかりやすく可視化されるのは嬉しいですね。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/ynoagent_info-1.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/ynoagent_info-1.png?x40497" alt="" width="1580" height="859" class="alignnone size-full wp-image-3573" /></a></p>
<p>機器詳細画面の各タブから様々な情報を確認できます。リモートでコマンド実行されているようです。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/2020-03-06_00h58_35.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/2020-03-06_00h58_35.png?x40497" alt="" width="607" height="624" class="alignnone size-full wp-image-3575" /></a></p>
<p>RTX830の機器管理画面の各タブから確認出来る情報(内部的にリモート実行されるコマンド)を以下に羅列しておきます。機器状態確認の際によく利用されるコマンドとなっています。</p><pre class="crayon-plain-tag">show status boot
show environment
show techinfo
show status {LAN番号}
show status pp {ピア番号}
show status tunnel {トンネル番号}
show nat descriptor address detail
show ip route detail
show status external-memory
show file list {外部ストレージインタフェース名}</pre><p></p>
<p>コマンド実行タブから任意のコマンドを実行することもできます。十分注意して利用しましょう。以下はLuaの環境情報を取得する例です。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_command_test.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_command_test.png?x40497" alt="" width="1253" height="698" class="alignnone size-full wp-image-3576" /></a></p>
<p>今回は一台のみなのであまりメリットは示せませんが、各機器にはラベルを設定することができるので、複数機器を管理する際はラベルでグルーピングして検索することもできます。管理対象機器をユーザー毎に割り当てる機能もあるので、自身が担当する機器のみを表示・操作させることもできるようです。</p>
<h4 class="term">ファームウェア更新</h4>
<p>YNOから最新版のファームウェアを自動取得してリモートで機器に適用することができます。時間指定実行や結果をメール通知設定も可能です。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_fw_update.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_fw_update.png?x40497" alt="" width="837" height="465" class="alignnone size-full wp-image-3585" /></a></p>
<h4 class="term">CONFIGの管理</h4>
<p>CONFIG関連の各種操作が可能です。機器詳細画面のCONFIG管理タブから操作できます。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_config_manage.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_config_manage.png?x40497" alt="" width="1000" height="601" class="alignnone size-full wp-image-3577" /></a></p>
<p>機器毎に複数のCONFIGファイルをYNOマネージャー上に保存しておくことができるようです。保存済みCONFIGの種別は以下の3つ。</p>
<ul>
<li><strong>Startup config</strong>: 起動時に使用するCONFIG</li>
<li><strong>Previous config</strong>: 現在のStartup configが保存される前にStartup configとして保存されていたCONFIG</li>
<li><strong>Backed-up config</strong>: CONFIGのバックアップ機能を利用して保存されたCONFIG</li>
</ul>
<p>もちろんYNO上でCONFIGの中身も確認できます。また、一時保存CONFIGというのもあり、これはYNO上で編集して一時的に保存されているCONFIGを指します。機器の設定済みのCONFIGをYNO上で編集・保存・配信することができます。</p>
<p>CONFIGのバックアップは機器一覧画面から。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_config_backup_1.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_config_backup_1.png?x40497" alt="" width="1000" height="285" class="alignnone size-full wp-image-3578" /></a></p>
<p>任意の名前を付けて保存します。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_config_backup_2.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_config_backup_2.png?x40497" alt="" width="943" height="273" class="alignnone size-full wp-image-3579" /></a></p>
<p>ここでバックアップしたCONFIGがBacked-up configとして登録されます。バックアップしたCONFIGはいつでもリモートから機器に配信・反映(復元)することができます。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_config_backup_3.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_config_backup_3.png?x40497" alt="" width="1000" height="227" class="alignnone size-full wp-image-3580" /></a></p>
<p>また、2つのCONFIG間の差分をYNO上で確認することもできます。差分箇所は以下例のように赤く表示されます。CONFIG適用前に変更箇所を確認することができるので便利ですね。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_config_diff.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_config_diff.png?x40497" alt="" width="548" height="322" class="alignnone size-full wp-image-3584" /></a></p>
<h5 class="term">ゼロコンフィグ</h5>
<p>ゼロコンフィグはYNOマネージャーに接続してきた機器に、あらかじめ登録しておいたCONFIGを自動で送信する機能です。この機能については公式で動画が出ているのでそちらを見た方がわかりやすいかと思います。</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=hFhTPKPkh8M">YNO ゼロコンフィグ機能 &#8211; YouTube</a></li>
</ul>
<p>以下はCONFIGの作成画面です。プレースIDというのは機器とCONFIGを紐付ける識別子のようなもので、YNOマネージャーから自動でプレースIDに一致するCONFIGが対象機器に送信されます。また、有効期間というのは機器にCONFIGを送信できる期間です。有効期間を設定すると、この期間外に接続してきた機器にはCONFIGが送信されません。 </p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_zeroconf_new.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_zeroconf_new.png?x40497" alt="" width="1000" height="765" class="alignnone size-full wp-image-3582" /></a></p>
<p>CONFIGで利用できるプレースホルダを以下に載せます。サーバの環境変数のようなもので適用時に展開されます。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/yno_config_placeholder.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/yno_config_placeholder.png?x40497" alt="" width="868" height="212" class="alignnone size-full wp-image-3581" /></a></p>
<p>注意点として、ゼロコンフィグ機能の対応機種は2020/03現在ではまだ限定的のようです。RTX830だとRev.15.02.08以降で対応しているためファームウェアのバージョンにも注意しましょう。対応機種およびファームウェアは以下のページで確認できます。</p>
<ul>
<li><a href="http://www.rtpro.yamaha.co.jp/RT/docs/yno/agent/index.html">YNOエージェント機能</a></li>
</ul>
<h4 class="term">GUI Forwarder (GFW)</h4>
<p>YNOマネージャーからYNOエージェント側のダッシュボード(WebUI)にアクセスできます。X Windowみたいな感じ。起動方法は機器一覧画面のアクション列にある以下のボタンを押すだけです。Webベースで立ち上がりも思ってたより速かったです。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/GFW_open.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/GFW_open.png?x40497" alt="" width="605" height="188" class="alignnone size-full wp-image-3571" /></a></p>
<p>GFWでアクセスすると以下のような通知が表示されます。この時のログインユーザーはGFW.Adminになるようです。</p>
<p><a href="https://rest-term.com/wp-content/uploads/2020/03/2020-03-05_23h40_56.png?x40497"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2020/03/2020-03-05_23h40_56.png?x40497" alt="" width="1235" height="694" class="alignnone size-full wp-image-3570" /></a></p>
<p>環境に依るのだとは思いますが、ほとんどラグを感じずに操作できました。LANマップもGFWから確認することができます。</p>
<h3 class="term">その他</h3>
<h4 class="term">MFA</h4>
<p>YNOは多要素認証(MFA)にも対応しています。セキュリティ意識を持って是非設定しておきましょう。[アカウント管理]>[マイアカウント]>[アカウントの編集]からMFA (多要素認証)のチェックを入れて、任意のMFAアプリケーションから多要素認証設定をします。僕はGoogle認証システムアプリを使ってますが、各々好きなアプリを使ったら良いです。</p>
<ul>
<li><a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&#038;hl=ja">Google 認証システム &#8211; Google Play のアプリ</a></li>
</ul>
<h4 class="term">ライセンス</h4>
<p>今回は3ヶ月間無償の試用ライセンスで使っていますが、基本ライセンスはネットワーク機器の台数分必要になります。ネットワーク機器台数が1/5/10/30/50/100台単位でそれぞれ1 &#8211; 5年契約でライセンス購入できるようです。例えば5台で3年契約とか、ユーザーの利用状況に合わせて購入することができますね。</p>
<p>また、拡張ライセンスというものもあり、基本ライセンスの契約期間中にYNOへ接続できるネットワーク機器台数を拡張するライセンスとのことです。公式サイトのリンクも貼っておくのでライセンス体系の最新情報はここから。</p>
<ul>
<li><a href="https://network.yamaha.com/products/software_service/ysl-yno/price#tab">YSL-YNO 価格</a></li>
</ul>
<h3 class="term">おわりに</h3>
<p>今回はYamaha Network Organizer (YNO)の基本的な機能紹介をしてみました。試用ライセンスの1台利用なのでメリットを活かしきれたわけではありませんが、YNOの便利さはある程度理解できたかと思います。Web界隈でいうところのCI/CD対応がネットワーク機器に対しても一般的になる時代がやってきたという感じでしょうか。個人家庭用途ではさすがにオーバースペックですが、プロのネットワークエンジニアの方々にはきっと重宝される製品になるのでしょう。僕も試用期間が切れるまでいろいろ使い倒したいと思います。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3567/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>YAMAHA RTX830のLuaスクリプト機能</title>
		<link>https://rest-term.com/archives/3559/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=yamaha-rtx830%25e3%2581%25aelua%25e3%2582%25b9%25e3%2582%25af%25e3%2583%25aa%25e3%2583%2597%25e3%2583%2588%25e6%25a9%259f%25e8%2583%25bd</link>
					<comments>https://rest-term.com/archives/3559/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Sat, 28 Dec 2019 07:05:09 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[hardware]]></category>
		<category><![CDATA[lua]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[ops]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3559</guid>

					<description><![CDATA[年の瀬に上げるような内容の記事ではないですけど、ネットワーク設定が一通り終わって安定運用に入ると、ルーターに触る機会も減ってしまうので定期的に勉強しておこうかなと。 前回の記事はヤマハ製ルーターRTX830におけるIPv6環境のネットワーク設定についての内容でした。 YAMAHA RTX830でIPv6 IPoE + IPv4 over IPv6(MAP-E)接続 ヤマハルーターのLuaスクリプト機能について RTX830など多くのヤマハ製ルーターにはLuaスクリプトの実行環境が備わっています。&#8230;]]></description>
										<content:encoded><![CDATA[<p>年の瀬に上げるような内容の記事ではないですけど、ネットワーク設定が一通り終わって安定運用に入ると、ルーターに触る機会も減ってしまうので定期的に勉強しておこうかなと。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/12/yamaha_lua.png?x40497" alt="" width="472" height="233" class="alignnone size-full wp-image-3564" /></p>
<p>前回の記事はヤマハ製ルーター<a href="https://network.yamaha.com/products/routers/rtx830/index">RTX830</a>におけるIPv6環境のネットワーク設定についての内容でした。</p>
<ul>
<li><a href="https://rest-term.com/archives/3551/">YAMAHA RTX830でIPv6 IPoE + IPv4 over IPv6(MAP-E)接続</a></li>
</ul>
<h3 class="term">ヤマハルーターのLuaスクリプト機能について</h3>
<p>RTX830など多くのヤマハ製ルーターにはLuaスクリプトの実行環境が備わっています。導入方法については公式サイトの通りに進めていけば問題ないかと思います。</p>
<ul>
<li><a href="https://network.yamaha.com/setting/router_firewall/monitor/lua_script/manual">Luaスクリプト導入手順マニュアル</a></li>
</ul>
<h4 class="term">環境</h4>
<p>機器: YAMAHA RTX830 (Rev.15.02.10)<br />
Luaライブラリバージョン: Lua 5.1.5<br />
Luaスクリプト機能バージョン: 1.08</p>
<p>* Lua有効化<br />
デフォルトでは有効化されていますが念の為に設定コマンドを載せておきます。</p><pre class="crayon-plain-tag"># lua use ?
    入力形式： lua use スイッチ
               スイッチ = 'on' or 'off'
　    　説明： Luaスクリプト機能を使用するか否かを設定します
デフォルト値： on</pre><p></p>
<p></p><pre class="crayon-plain-tag"># lua -v
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio

# show status lua
Luaライブラリバージョン:        Lua 5.1.5
Luaスクリプト機能バージョン:    1.08
... 省略</pre><p>Luaのバージョンはファームウェア配信でアップデートされているようです。2019/12現在は5.1.5なのでけっこう古め。また、Luaスクリプト機能バージョンというのは、ファームウェアによって独自に追加された機能等を管理するリビジョン番号のようなものです。</p>
<p>また、ヤマハルーターにはRTFSというルーターの内蔵フラッシュROMに構築されるファイルシステムがあり、Luaスクリプトなどのファイル類はここに置くことができます。ここではルート以下にluaディレクトリを作り、その下にLuaスクリプトファイルを置いています。</p><pre class="crayon-plain-tag"># show file list /
2019/07/26 17:21:38 &lt;DIR&gt;           dashboard
2019/08/10 14:34:41 &lt;DIR&gt;           lanmap
2019/09/01 23:14:16 &lt;DIR&gt;           lua</pre><p></p>
<p>Luaのサンプルスクリプトは公式サイトや個人ブログ上にていろいろ公開されてます。ルーターコマンドやログの内容を監視して、何らかのイベントを補足したらメール等の方法で通知するというようなユースケースが多いようです。また、開発環境としてはVisual Studio Codeを使いました。適当なLua lint系のextension入れておくと使いやすくなります。ヤマハルーター専用APIだけチェックできませんが、必要なら拡張してやれば良いかなと思います。</p>
<h3 class="term">ヤマハルーター専用 API</h3>
<p>ヤマハルーターのLua処理系で利用できる専用の機能(rtモジュール)があります。</p>
<ul>
<li><a href="http://www.rtpro.yamaha.co.jp/RT/docs/lua/rt_api.html">ヤマハルーター専用 API</a></li>
</ul>
<p><code>rt.command/rt.mail/rt.syslog</code> あたりはサンプルスクリプトでも良く利用されているのを見かけます。特に <code>rt.command</code> は任意のルーターコマンドを実行する汎用関数なので活用の機会は自然と多くなりそうですが、セキュリティ的に外部からコマンドを受け取れるような作りにはしない等のコマンドインジェクション対策を忘れずにしておくと良いかと思います。今回はせっかくなので専用APIを使ってサンプルを作ってみます。</p>
<p><strong>サンプル: CPU利用率が閾値以上だったら、syslogにログ出力してブザー鳴らしてステータスLEDを10秒間点灯させる</strong></p><pre class="crayon-plain-tag">-- test.lua
local function alert_cpu_usage(threshold)
    local cpu, cpuerr = rt.hw.open('cpu1')
    if cpu then
        local load = cpu:read()
        cpu:close()
        local msg = 'warning cpu usage: ' .. load.load_1m
        if load.load_1m &gt; threshold then
            print(msg)
            rt.syslog('info', msg)
            local led, lederr = rt.hw.open('status-led1')
            if led then
                local bz, bzerr = rt.hw.open('buzzer1')
                if bz then
                    bz:tone('B2')
                    rt.sleep(1)
                    bz:tone('E3')
                    rt.sleep(1)
                    bz:tone('B3')
                    rt.sleep(1)
                    bz:tone('B4')
                    rt.sleep(1)
                    bz:off()
                    bz:close()
                else
                    print(bzerr)
                end
                led:on()
                rt.sleep(10)
                led:off()
                led:close()
            else
                print(lederr)
            end
        end
    else
        print(cpuerr)
    end
end

-- main
alert_cpu_usage(50)</pre><p>とても汚い書き殴りをしてみましたが、ハードウェア系APIをいろいろ使ってます。</p>
<p>* 動作確認<br />
クライアント側でLuaスクリプトファイルをルーターに転送</p><pre class="crayon-plain-tag">$ tftp 192.168.100.1
tftp&gt; put test.lua /lua/test.lua/{ログインパスワード}
転送を正常に完了しました: 1 秒間に 1262 バイト、1262 バイト/秒</pre><p>ルーター側でLuaスクリプト実行 (administrator権限が必要)</p><pre class="crayon-plain-tag"># lua /lua/test.lua
warning cpu usage: 61</pre><p></p>
<p>ステータスLEDが点灯するとダッシュボードに警告として通知されました。これは地味に嬉しいですね。ちなみにステータスLEDはオレンジ色でした。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/12/rtx_led_alert.png?x40497" alt="" width="564" height="101" class="alignnone size-full wp-image-3562" /></p>
<p>RTX830だとハードウェア制御系APIがあまり使えないのが寂しい感じではあります。RTX1200とかだと温度計とか各種LEDとか制御できるようです。</p>
<h4 class="term">Luaスクリプトのコンパイル</h4>
<p>Luaスクリプトはluacコマンドでバイトコードにコンパイルできるので、大きめのスクリプトはコンパイルしてから実行するのが良さそうです。</p><pre class="crayon-plain-tag"># luac -o /lua/test.out /lua/test.lua
luacコマンドの実行が終了しました
# lua /lua/test.out</pre><p></p>
<h4 class="term">その他の実行方法</h4>
<p>ルーターにログインしてluaコマンドを都度叩いて実行するのは非現実的です。ヤマハルーターにはcronのようなスケジューリング機能やDOWNLOADボタンでLuaスクリプトを実行する機能があります。</p>
<p>* DOWNLOADボタンで起動するLuaスクリプトを登録</p><pre class="crayon-plain-tag"># operation button function download execute lua /lua/test.lua</pre><p>上記コマンドで登録したら、筐体前面のDOWNLOADボタンを3秒以上押すことで登録したLuaスクリプトが実行されます。RTX830だと実行開始と実行終了時にアラームが鳴りました。他のモデルでも多分だいたい同じだと思います。</p>
<p>* スケジュール機能で実行<br />
<code>schedule</code> コマンドの使い方は<a href="http://www.rtpro.yamaha.co.jp/RT/manual/rt-common/schedule/schedule_at.html">40.1 スケジュールの設定</a>を参考にしてください。以下は例です。</p><pre class="crayon-plain-tag"># schedule at 1 1/1 0:0 * lua /lua/test.lua</pre><p></p>
<p>補足として、Luaスクリプトの強制終了方法も載せておきます。不用意に負荷の高いスクリプトを流してしまってリソースが逼迫することを回避できます。<code>show status lua</code> コマンドでタスク番号を確認して、<code>terminate</code> コマンドで強制終了します。</p><pre class="crayon-plain-tag"># show status lua
... 省略

[running]
LuaタスクID (状態):  1  (RUN)
走行トリガー:        'lua' コマンド
コマンドライン:      lua sd1:/test.lua
スクリプトファイル:  sd1:/test.lua
開始日時:            2019/11/30 12:45:08
経過時間:            6秒

# terminate lua 1
Luaスクリプトを停止しました</pre><p></p>
<h3 class="term">注意点など</h3>
<h4 class="term">Luaのバージョンと機能</h4>
<p>2019/12現在、Luaのバージョンは5.1系となっていて古めです。モジュールシステムが5.2以降から変わっているので、モジュールを作る/使う時は5.1での書き方を確認しておきましょう。</p>
<p>また、<a href="http://www.rtpro.yamaha.co.jp/RT/docs/lua/tutorial/library.html">Lua言語のライブラリ関数</a>にヤマハルーターのLuaスクリプトで利用できるライブラリ関数が掲載されています。ルーターに実装されている処理系だと一部機能制限されており、Lua標準関数であっても利用できないものがあるので注意が必要です。例えば、標準入出力(stdin/stdout)が使えなかったり、<code>require()</code>関数でCのモジュールをロードすることはできなかったり等のいろいろな制限があります。Luaのバージョンだけでなく、<a href="http://www.rtpro.yamaha.co.jp/RT/docs/lua/index.html#support">Luaスクリプト機能バージョンのchangelog</a>も気にしつつ開発/運用するのが良いかと思います。</p>
<p>他の制限としてはLuaタスクの同時実行数は上限があり、luaコマンド用に8個、DOWNLOADボタンからの実行用に1個、Luaコンパイラ用に1個の計10個が用意されています。</p><pre class="crayon-plain-tag"># lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
# lua sd1:/test.lua
エラー: Luaスクリプトを実行するための空きタスクがありません</pre><p></p>
<h4 class="term">RTFSの注意点</h4>
<p><a href="http://www.rtpro.yamaha.co.jp/RT/docs/rtfs/index.html">RTFS</a>(公式サイト)にRTFSについての説明と利用の際の注意点が載っています。要約すると、RTFSは頻繁に読み書きする使い方は避けてくださいということのようです。今回は紹介も兼ねてRTFSを使いましたが、LuaスクリプトファイルはRTFSよりも外部microSDカードに置いた方が良いかもしれません。</p>
<h3 class="term">終わりに</h3>
<p>今回はヤマハルーターRTX830でのLuaスクリプト環境を試してみました。組み込み系ということもあってWeb系実行環境と比べると機能制限は厳しめで、Luaバージョンも2019/12現在は5.1系と古めなのですが、C言語ではなくLuaスクリプトで開発できるのは手軽で嬉しいですね。これまでLuaは<a href="http://torch.ch/">Torch</a>の勉強の際に少し書いてた程度、実践で使ったことはほとんど無いので引き続き勉強していきます。</p>
<p>また、GitHubにはサンプルスクリプトを、wikiの方にヤマハルーター関連のメモを書いてますので参考までに。</p>
<ul>
<li><a href="https://github.com/wellflat/yamaha-lua-samples">wellflat/yamaha-lua-samples:Lua script samples for YAMAHA router</a></li>
<li><a href="https://rest-term.com/technote/index.php/YAMAHA%20Router">Tech Note > YAMAHA Router</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3559/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>YAMAHA RTX830でIPv6 IPoE + IPv4 over IPv6(MAP-E)接続</title>
		<link>https://rest-term.com/archives/3551/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=yamaha-rtx830%25e3%2581%25a7map-e</link>
					<comments>https://rest-term.com/archives/3551/#comments</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Wed, 31 Jul 2019 13:59:17 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[hardware]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[ops]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3551</guid>

					<description><![CDATA[最近引っ越ししまして、ネット回線もいろいろと変わりました。IPv6対応もだいたい終わったので備忘録も兼ねて。今回はヤマハ製ルーターでv6プラス(IPv6 IPoE + IPv4 over IPv6)接続についてメモしてます。 v6プラスについて v6プラス(IPv6/IPv4インターネットサービス) &#124; 日本ネットワークイネイブラー株式会社 「v6プラス」は、NTT東西の次世代ネットワーク（NGN）を利用しインターネット接続を提供するISP事業者が、IPv6及びIPv4の設備を持たずに、インター&#8230;]]></description>
										<content:encoded><![CDATA[<figure id="attachment_3552" aria-describedby="caption-attachment-3552" style="width: 600px" class="wp-caption alignnone"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/07/yamaha_rtx830.jpg?x40497" alt="" width="600" height="400" class="size-full wp-image-3552" /><figcaption id="caption-attachment-3552" class="wp-caption-text">YAMAHA RTX830</figcaption></figure>
<p>最近引っ越ししまして、ネット回線もいろいろと変わりました。IPv6対応もだいたい終わったので備忘録も兼ねて。今回はヤマハ製ルーターでv6プラス(IPv6 IPoE + IPv4 over IPv6)接続についてメモしてます。</p>
<h3 class="term">v6プラスについて</h3>
<ul>
<li><a href="https://www.jpne.co.jp/service/v6plus/">v6プラス(IPv6/IPv4インターネットサービス) | 日本ネットワークイネイブラー株式会社</a></li>
</ul>
<blockquote><p>
「v6プラス」は、NTT東西の次世代ネットワーク（NGN）を利用しインターネット接続を提供するISP事業者が、IPv6及びIPv4の設備を持たずに、インターネット接続をお客さま(エンドユーザ)にご提供いただくためのサービスです。<br />
本サービスは、「IPoE方式」によるIPv6インターネット接続とIPv6ネットワーク上で実現するIPv4インターネット接続のデュアルスタックのローミングサービスです。
</p></blockquote>
<p>v6プラスはJPNE(日本ネットワークイネイブラー株式会社)をVNE事業者とするIPv4/IPv6インターネット接続サービスの総称で、<a href="https://ja.wikipedia.org/wiki/IPv6%E7%A7%BB%E8%A1%8C%E6%8A%80%E8%A1%93">IPv4/IPv6共存技術あるいはIPv6移行技術</a>と呼ばれているそうです。技術的には<strong>IPv6 IPoE + IPv4 over IPv6</strong>接続方式となっており、IPv4 over IPv6にはトンネリング技術の一つである<strong>MAP-E(Mapping of Address and Port with Encapsulation)</strong>方式が採用されています。IPv4パケットをIPv6にカプセル化しトンネリングすることで、ネットワーク基幹部分をIPv6だけで構築することができます。</p>
<ul>
<li><a href="https://tools.ietf.org/html/rfc7597">RFC 7597 &#8211; Mapping of Address and Port with Encapsulation (MAP-E)</a></li>
</ul>
<p>NGNはインターネットとは切り離されている閉域網であり、IPv6の扱いが特殊なので、この部分は日本固有の問題として学ぶ必要がありました。</p>
<ul>
<li><a href="https://ja.wikipedia.org/wiki/%E3%83%95%E3%83%AC%E3%83%83%E3%83%84%E7%B6%B2%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8BIPv6#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%8D%E3%83%83%E3%83%88_(IPv6_IPoE)_%E6%8E%A5%E7%B6%9A">フレッツ網におけるIPv6 &#8211; Wikipedia</a></li>
</ul>
<p>ということなので、海外製ルーターだとv6プラスに対応してないこともまぁまぁあります。以前使っていたUbiquiti Networks製のEdgeRouterやASUS製Wi-FiルーターなどはMAP-E対応していませんでした(今現在の最新製品ならできるかもしれません、未調査)。</p>
<p>ではなぜ多少なりとも手間をかけてv6プラス対応するのかというと、ネットワーク回線速度の向上が期待できるからです。トラフィック経路がISP(相互接続点の網終端装置)ではなくVNE経由になり、NGNと直接ルーティングすることで通信の混雑を回避して通信速度向上を図ります。</p>
<h3 class="term">ヤマハルーターでMAP-E接続</h3>
<p>現在自宅で使っている<a href="https://network.yamaha.com/products/routers/rtx830/index">YAMAHA RTX830</a>ではRev.15.02.03以降のファームウェアからMAP-E方式に正式対応しており、コンフィグ情報も公式サイトの通りで問題なくMAP-E接続できました。なのでわざわざブログ記事にする必要はないんですが、実際に回線速度がかなり上がったので嬉しくて記事にしました(^^</p>
<p>* 環境<br />
機器: YAMAHA RTX830 (Rev.15.02.09以降)<br />
回線: ドコモ光タイプA(GMO とくとくBB) ひかり電話契約無し(RA)<br />
接続方式: IPv6 IPoE + Ipv4 over IPv6 (v6プラス)<br />
ONU -> RTX830 -> RT-AC86U </p>
<ul>
<li><a href="https://network.yamaha.com/setting/router_firewall/ipv6/v6plus">v6プラスでインターネット接続 : コマンド設定</a></li>
</ul>
<p>設定したコンフィグを一応以下に残しておきます。見やすいようにフィルタ設定等は省いてIPv6 IPoE + Ipv4 over IPv6接続対応部分だけ抜粋します。</p><pre class="crayon-plain-tag"># RTX830 Rev.15.02.09 (Fri Nov  2 14:23:51 2018)
# (filter定義部分は長くなるため省略しています)
ip route default gateway tunnel 1
ipv6 prefix 1 ra-prefix@lan2::/64
ip lan1 address 192.168.100.1/24
ipv6 lan1 address ra-prefix@lan2::1/64
ipv6 lan1 rtadv send 1 o_flag=on
ipv6 lan1 dhcp service server
description lan2 &quot;IPv6 IPoE(MAP-E)&quot;
ipv6 lan2 dhcp service client ir=on
tunnel select 1
 tunnel encapsulation map-e
 ip tunnel mtu 1460
 ip tunnel nat descriptor 1000
 tunnel enable 1
nat descriptor type 1000 masquerade
nat descriptor address outer 1000 map-e
dhcp service server
dhcp server rfc2131 compliant except remain-silent
dhcp scope 1 192.168.100.2-192.168.100.191/24
dns server dhcp lan2</pre><p></p>
<p>* 接続確認</p><pre class="crayon-plain-tag"># show status tunnel 1
TUNNEL[1]:
説明:
  インタフェースの種類: MAP-E
  IPv6: xxxx:xx:xxxx:xxxx:xx:xxxx:xxx:xxxx
  トンネルインタフェースは接続されています                 
  開始: 2019/07/07 07:07:07
  通信時間: 14時間8分21秒
  受信: (IPv4) 3969155 パケット [4890313899 オクテット]
        (IPv6) 0 パケット [0 オクテット]
  送信: (IPv4) 1194736 パケット [162735369 オクテット]
        (IPv6) 0 パケット [0 オクテット]

# show status lan2
LAN2
説明:                           IPv6 IPoE(MAP-E)
IPアドレス:                     
イーサネットアドレス:           ac:44:f2:63:da:e1
動作モード設定:                 Auto Negotiation (1000BASE-T Full Duplex)
最大パケット長(MTU):            1500 オクテット
プロミスキャスモード:           OFF
送信パケット:                   31083764 パケット(10377392756 オクテット)
  IPv4(全体/ファストパス):      0 パケット / 0 パケット
  IPv6(全体/ファストパス):      31083764 パケット / 29789642 パケット
受信パケット:                   70086225 パケット(99076765285 オクテット)
  IPv4:                         0 パケット
  IPv6:                         70086225 パケット</pre><p>DNS周りで少し躓いたのですが、最終的にはコンフィグを一旦全部消して公式サイト通りに設定したら動きました。。原因はGUIとCUIの設定を混在させて作業していたせいだと思います。意図していない設定が差し込まれ、コマンド評価順の都合等で意図通りに動作しないケースはけっこうありそうです。また、2019/07現在、MAP-E対応はコマンドでしかできません。</p>
<p>* 通信速度 (speedtest.netで測定)<br />
IPv4 PPPoEの頃はだいたい100 &#8211; 120Mbpsくらいだったのですが予想以上に速くなりました。下の結果は平日夜間(金曜の20時くらい)に測ったものですが、時間帯に依らず通信速度が安定したのも嬉しいです。パケットフィルタは前述のヤマハ公式サイトの設定+α程度で適用していますが、ノーガードならさらに速くなるのでしょうかね。<br />
<img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/07/Speedtest_by_Ookla_-_The_Global_Broadband_Speed_Test-2.png?x40497" alt="" width="578" height="273" class="alignnone size-full wp-image-3555" /></p>
<h4 class="term">おまけ: MAP-E アドレス/ポートレンジ計算</h4>
<p>RTX830では正式にMAP-E対応されており、トンネルの設定等で<code>map-e</code>という文字列も使えるようになっています。でも実は正式対応前のファームウェアでもMAP-Eのアドレス/ポートレンジ計算を手動でやって設定すればMAP-E接続できたという報告がちらほら上がっていたようです。IPv6技術の勉強にもなるかなと思って僕も手計算してみました。</p>
<ul>
<li><a href="http://taiyaki.seesaa.net/article/450972796.html">v6プラスのIPアドレス＆ポートの計算方法: ネトゲー回想録</a></li>
<li><a href="https://tools.ietf.org/html/rfc7597#appendix-A">Appendix A.  Examples &#8211; RFC7597</a></li>
</ul>
<p>BRのIPv6アドレスとマップルールを知っていれば設定できるようです。計算手順例については上記のRFC7597 Appendix Aを見るのがわかりやすいかと。ヤマハルーターの設定の自由度とネットワークの知識があればMAP-E対応前の既存コマンドの組み合わせで繋がるんですね。これはもしかしてEdgeRouter Xでも手動計算すれば接続できるのかな。もう処分しちゃったから試せませんが、。</p>
<p>MAP-E接続完了後なら以下のコマンドで各情報を見て検算できます。</p><pre class="crayon-plain-tag"># show ipv6 address
LAN1 scope-id 1 [up]
 Received:    352937 packets 56673991 octets
 Transmitted: 306356 packets 44687875 octets

 グローバル     240b:xxxx:qqrr:ss00::1/64 (lifetime: 604728/2591928) ## IPv6プレフィックス
 グローバル     240b:xxxx:qqrr:ss00:aa:bbbb:cccc:dddd/64  ## CE IPv6アドレス
... 省略

# show nat descriptor address
NAT/IPマスカレード 動作タイプ : 2
参照NATディスクリプタ : 1000, 適用インタフェース : TUNNEL[1](1)
Masqueradeテーブル
   外側アドレス: map-e/xxx.xxx.xxx.xxx ## MAP-E IPv4アドレス, ポート
   ポート範囲: 4880-4895, 8976-8991, 13072-13087, 17168-17183, 21264-21279, 25
360-25375, 29456-29471, 33552-33567, 37648-37663, 41744-41759, 45840-45855, 499
36-49951, 54032-54047, 58128-58143, 62224-62239
... 省略</pre><p>ヤマハルーターではLuaが動きますが、Luaの勉強ついでにこの計算用スクリプトを書いてみるのも良いかもしれません。</p>
<h3 class="term">おわりに</h3>
<p>IPv6 IPoE + IPv4 over IPv6対応している内にネットワーク技術を多少学ぶことができて良かったです。ヤマハ製ルーター使ってると学ぶモチベーションも割と上がったような気がします。EdgeRouterを使ってた時は、良くも悪くもLinux的な感じなのでWeb屋にとっては新鮮さを感じなかったせいというのはあります。コスパは最高でしたが、いずれにせよMAP-Eに対応してなかったので買い換える必要はありました。</p>
<p>ヤマハ製ルーターは写真だとわかりにくいですけど、金属筐体の質が良いんですよ、触り心地というか。RTXだとACアダプタも無くてすっきりしてます。<br />
<figure id="attachment_3552" aria-describedby="caption-attachment-3552" style="width: 600px" class="wp-caption alignnone"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/07/yamaha_rtx830.jpg?x40497" alt="" width="600" height="400" class="size-full wp-image-3552" /><figcaption id="caption-attachment-3552" class="wp-caption-text">YAMAHA RTX830</figcaption></figure></p>
<p>ネットワーク関連の技術メモはブログよりWikiの方が合ってるかなと思ったので<a href="https://rest-term.com/technote/index.php/YAMAHA%20Router">Tech Note > YAMAHA Router</a>の方に都度メモ書きしていこうと思います。</p>
<p>次はVPN環境を整備していく予定です。</p>
<h3 class="term">参考</h3>
<ul>
<li><a href="https://www.jpne.co.jp/service/v6plus/">v6プラス(IPv6/IPv4インターネットサービス) | 日本ネットワークイネイブラー株式会社</a></li>
<li><a href="http://www.rtpro.yamaha.co.jp/RT/docs/ipip/index.html">IPIPトンネリング</a></li>
<li><a href="https://ja.wikipedia.org/wiki/%E3%83%95%E3%83%AC%E3%83%83%E3%83%84%E7%B6%B2%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8BIPv6#%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%8D%E3%83%83%E3%83%88_(IPv6_IPoE)_%E6%8E%A5%E7%B6%9A">フレッツ網におけるIPv6 &#8211; Wikipedia</a></li>
<li><a href="http://dotsukareta.blogspot.com/2018/09/nvr510v6.html">疲労コンパイル: NVR510でv6プラスに接続してみた</a></li>
<li><a href="https://villas-windmill.blog.so-net.ne.jp/2018-04-27" class="broken_link">Yamaha RTX830 を使ってｖ6プラスの接続試験をしてみました。：（仮）タイトルいつ決めるのさ</a></li>
<li><a href="https://tools.ietf.org/html/rfc7597">RFC 7597 &#8211; Mapping of Address and Port with Encapsulation (MAP-E)</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3551/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>BRISQUE &#8211; リファレンス画像無しで画質を評価する</title>
		<link>https://rest-term.com/archives/3525/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=opencv%25e3%2581%25a7%25e7%2594%25bb%25e5%2583%258f%25e3%2581%25ae%25e5%2593%2581%25e8%25b3%25aa%25e3%2582%2592%25e6%25b8%25ac%25e5%25ae%259a%25e3%2581%2599%25e3%2582%258b</link>
					<comments>https://rest-term.com/archives/3525/#respond</comments>
		
		<dc:creator><![CDATA[wellflat]]></dc:creator>
		<pubDate>Sat, 20 Apr 2019 12:20:24 +0000</pubDate>
				<category><![CDATA[tech/study]]></category>
		<category><![CDATA[c/c++]]></category>
		<category><![CDATA[computervision]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://rest-term.com/?p=3525</guid>

					<description><![CDATA[今回はリファレンス画像無しで画質を評価する手法を紹介しようと思います。 先日、OpenCV4.1がリリースされました。 OpenCV 4.1 has been released changelogを見ると、quality という新しいモジュールが opencv_contrib レポジトリに追加されたようです。 New Image Quality Analysis module (quality) has been added to the opencv_contrib, referenceles&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回はリファレンス画像無しで画質を評価する手法を紹介しようと思います。</p>
<p>先日、OpenCV4.1がリリースされました。</p>
<ul>
<li><a href="https://opencv.org/opencv-4-1/">OpenCV 4.1 has been released</a></li>
</ul>
<p>changelogを見ると、quality という新しいモジュールが opencv_contrib レポジトリに追加されたようです。</p>
<blockquote><p>New Image Quality Analysis module (quality) has been added to the opencv_contrib, referenceless BRISQUE algorithm has been implemented as well as PSNR, SSIM and others</p></blockquote>
<p>qualityモジュールでは画像の品質を測るための機能が提供されており、PSNRやSSIM等の古典的なアルゴリズムに加えて、リファレンス画像が不要なBRISQUEアルゴリズムも含まれています。BRISQUEは名前だけは聞いたことはありましたけど実際に使ったことはなかったです。</p>
<h3 class="term">環境</h3>
<p>* CentOS 7.5 (x86_64/Xeon 8core CPU/16GB RAM)<br />
* GCC 4.8.5<br />
* CMake 3.13.1<br />
* Python 3.6.5 (Anaconda custom)</p>
<p>いつものようにLinuxサーバ環境上に構築しました。qualityモジュールの動作にはCUDA環境は不要です。構築方法は以前のエントリー(<a href="https://rest-term.com/archives/3491/">OpenCV 4.0のQRコード検出、G-APIを試す</a>)を参考にしていただければ。<a href="https://github.com/opencv/opencv_contrib">opencv_contrib</a> のモジュールもリンクするのでCMakeオプション(OPENCV_EXTRA_MODULES_PATH)を一つ追加する必要があります。</p>
<h3 class="term">BRISQUEの概要</h3>
<p>ここではBRISQUEの概要を簡単に紹介します。詳細は元論文を参照ください。</p>
<ul>
<li>1.A. Mittal, A. K. Moorthy and A. C. Bovik, “ <a href="http://live.ece.utexas.edu/publications/2012/TIP%20BRISQUE.pdf">No-Reference Image Quality Assessment in the Spatial Domain</a> ”, IEEE Transactions on ImageProcessing, 2012 (to appear). </li>
</ul>
<p>BRISQUEはImage Quality Assessment(IQA)のアルゴリズムの一つで、リファレンス画像が不要であるNR IQA(No-Reference/Blind IQA)に分類されます。ちなみに、PSNRやSSIMなどはリファレンス画像が必要となるのでFR IQA(Full-Reference IQA)と呼ばれています。SSIMは輝度値の平均や分散などの基本統計量を使っていますがBRISQUEもそこは同様です。BRISQUEではNSS(Natural Scene Statistics)ベースのMSCN係数というものを定義しており、これはMean Substracted Contrast Normalizationの略で、単純に輝度値を平均0 分散1に正規化するわけではなく、ちょっと手を加えた以下の式で定義されています。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/mscn.png?x40497" alt="" width="254" height="59" class="alignnone size-full wp-image-3533" /></p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/localmean.png?x40497" alt="" width="321" height="72" class="alignnone size-full wp-image-3534" /></p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/localdeviation.png?x40497" alt="" width="464" height="90" class="alignnone size-full wp-image-3535" /></p>
<p>Iは画像の行列(i,j)成分における輝度値、wは局所領域(KxL)におけるガウシアンカーネルの重みで、つまりガウシアンフィルタをかけた画像の輝度値を平均として使っています。Cは0除算を避けるための定数項なのであまり気にしなくてよいです。</p>
<p>そして近隣画素間でMSCN係数の積(実装上は行列全体をシフトして要素積)を取ります。以下の horizontal H(右), vertical V(下), main-diagonal D1(右下), secondary-diagonal D2(左下) の4方向で定義されています。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/neighboring_mscn.png?x40497" alt="" width="300" height="112" class="alignnone size-full wp-image-3536" /></p>
<p>これで1つの入力画像に対してMSCN係数の積が詰まった行列が4つ得られます。この行列データのヒストグラムが正規分布に従うという観測結果に基づき、確率分布の当てはめ(フィッティング;Gaussian Fitting)を行います。この辺りはBRISQUE固有のロジックというよりは、確率モデルに基づく画像処理では割とよくある考え方かと思います。元論文ではAGGD(Asymmetric Generalized Gaussian Distribution)を仮定してモーメントマッチングベースの手法でパラメータ推定を行ったようです。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/histogram_mscn_resized.png?x40497" alt="" width="500" height="394" class="alignnone size-full wp-image-3538" /></p>
<p>BRISQUEではこのMSCN係数の確率分布から36次元の特徴量(特徴ベクトル)を作って学習させます。内訳は最初の2要素が一般化正規分布(GCD:Generalized Gaussian Distribution)のパラメータ2つ(形状、分散)、次の4要素が非対称一般化正規分布(AGCD:Asymmetric Generalized Gaussian Distribution)のパラメータ4つ(形状、平均、左方向分散、右方向分散)、4方向の要素積を各々計算しているので 2 + 4*4 で18、半分の解像度にリサイズしたデータに対して再度同じ計算をしてx2、合計で36次元となっています。</p>
<p><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/brisque_features.png?x40497" alt="" width="791" height="267" class="alignnone size-full wp-image-3539" /></p>
<p>教師信号には<a href="https://en.wikipedia.org/wiki/Mean_opinion_score">MOS/DMOS</a>という主観的スコアを用いて回帰により学習させるので、画質に対する人間の知覚と相関します。学習アルゴリズムは元論文も今回紹介するOpenCV実装もSVM(SVR:サポートベクター回帰)を使っています。ツールとしては元論文では<a href="https://www.csie.ntu.edu.tw/~cjlin/libsvm/">LIBSVM</a>、OpenCVでは<code>cv::ml</code>のSVM実装を使っているようです。</p>
<h3 class="term">動作確認</h3>
<p>サンプル画像は以下の2枚を使います。左側がオリジナル画像(lenna.jpg)で、右側がPhotoshopでノイズ付与等の加工した画像(lenna_noised.jpg)になります。<br />
<img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2018/12/lenna.jpg?x40497" alt="" width="400" height="400" class="alignnone size-full wp-image-3498" /><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/lenna_noised.jpg?x40497" alt="" width="400" height="400" class="alignnone size-full wp-image-3527" /></p>
<p>* quality_sample.cpp</p><pre class="crayon-plain-tag">#include &lt;iostream&gt;
#include &quot;opencv2/highgui.hpp&quot;
#include &quot;opencv2/quality.hpp&quot;

int main(int argc, char **argv) {
    using namespace std;

    if(argc != 2) {
        cerr &lt;&lt; &quot;usage: ./quality_sample &lt;image path&gt;&quot; &lt;&lt; endl;
        exit(-1);
    }

    // SVMのモデルを2つ指定(SVMモデルと特徴ベクトルの正規化[-1,1]用データ)
    const string model_path = &quot;./brisque_model_live.yml&quot;;
    const string range_path = &quot;./brisque_range_live.yml&quot;;
    const cv::Mat input_image = cv::imread(argv[1]);
    // QualityBRISQUEのインスタンス生成
    shared_ptr&lt;cv::quality::QualityBase&gt; quality(cv::quality::QualityBRISQUE::create(model_path, range_path));
    // computeメソッドで画質評価
    cv::Scalar score = quality-&gt;compute(input_image);
    cout &lt;&lt; score[0] &lt;&lt; endl;
    return 0;
}</pre><p>学習済みのSVMモデル(実際は回帰モデルなのでSVR)は modules/quality/samples ディレクトリ以下にあります。学習には<a href="http://live.ece.utexas.edu/research/Quality/subjective.htm">LIVE DB R2</a>と呼ばれるデータセットを使っているようです。これは、オリジナル画像に対してJPEG圧縮やブラー加工、ホワイトノイズ付与等の加工を施した画像のセットとなっており、教師データはDMOSスコアとなっています。</p>
<p>APIのインタフェースはまだ洗練されていないようには見えます。インスタンス生成用の <code>cv::quality::QualityBase::create</code> メソッドは常に <code>cv::Ptr</code> で返ってくるようですが、こういったファクトリメソッドは <code>std::unique_ptr</code> で返して欲しいなと。もし共有したければ <code>std::shared_ptr</code> に付け替えれば良いだけなので。C++11対応のOpenCV4.xにおいては <code>cv::Ptr</code> や <code>cv::String</code> を保守する動機付けは弱いでしょうから後々deprecated扱いになるのかもしれません。あと、スコアはfloat等のプリミティブ型ではなく <code>cv::Scalar</code> で返されていますが、2019/05現在では1要素目しか値が入ってなかったです。doxygenのコメントにはチャンネル毎にスコア計算すると書いてありましたのでそのように改修されるのかもしれません。</p>
<p></p><pre class="crayon-plain-tag">// cvstd_wrapper.hpp
// cv::Ptr は std::shared_ptr のエイリアステンプレート
// 互換性のためインタフェース変換する程度の薄いラッパーメソッドがいくつか用意されている
template &lt;typename _Tp&gt; using Ptr = std::shared_ptr&lt;_Tp&gt;;
... 省略
template&lt;typename _Tp, typename ... A1&gt; static inline
Ptr&lt;_Tp&gt; makePtr(const A1&amp;... a1) { return std::make_shared&lt;_Tp&gt;(a1...); }

// cvstd.hpp
// cv::String は std::string のエイリアス
typedef std::string String;</pre><p></p>
<p>* 動作確認</p><pre class="crayon-plain-tag">$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)
set(project quality_sample)
project(${project} CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS &quot;-Wall -g&quot;)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(${project} ${project}.cpp)
target_link_libraries(${project} ${OpenCV_LIBRARIES})
$ cmake .
$ make
$ ./quality_sample lenna.jpg
13.9547
$ ./quality_sample lenna_noised.jpg
53.7845</pre><p>BRISQUEではスコアの値が大きいほど低画質の画像だということがわかります。OpenCVのBRISQUE実装ではスコアの範囲は0 &#8211; 100となっていました。SVRのハイパーパラメータ等は以下の通りです。</p><pre class="crayon-plain-tag">$ head -20 brisque_model_live.yml
%YAML:1.0                                                                                                                                                                                                               
---
opencv_ml_svm:
   format: 3
   svmType: EPS_SVR
   kernel:
      type: RBF
      gamma: 5.0000000000000003e-02
   C: 1024.
   p: 1.0000000000000001e-01
   term_criteria: { epsilon:1.0000000000000000e-03 }
   var_count: 36
   sv_total: 774
   support_vectors:
... 省略</pre><p></p>
<p>せっかくなのでPythonインタフェースも試します。使い方は簡単です。</p><pre class="crayon-plain-tag">#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import sys

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('usage: ./quality_sample.py &lt;image path&gt;')
        sys.exit(-1)

    model_path = &quot;./brisque_model_live.yml&quot;
    range_path = &quot;./brisque_range_live.yml&quot;
    input_image = cv2.imread(sys.argv[1])
    quality = cv2.quality.QualityBRISQUE_create(model_path, range_path)
    score = quality.compute([input_image])
    print(score[0])</pre><p>出力結果はC++版と同様なので省略します。いつものようにサンプルコードはGitHubに上げておきます。</p>
<ul>
<li><a href="https://github.com/wellflat/opencv-samples/tree/master/quality">opencv-samples/quality at master · wellflat/opencv-samples</a></li>
</ul>
<p>次は以下の各画像でPSNRなど他の手法と比較してみました。<code>cv::quality</code>モジュールにはPSNRやSSIMなどの実装も含まれているのでそれらを使います。ちなみにSSIMの実装を見てみましたが、以下の公式サンプルコードがベースになっていました。ガウシアンブラーをかける部分や各パラメータの値も同じでした。SSIMには任意パラメータがあるので、実装によって値がまちまちになって使いにくい面がありますが、BRISQUEでも同様に適用するモデルによって出力値は変わるので注意しておきます。</p>
<ul>
<li><a href="https://docs.opencv.org/2.4/doc/tutorials/highgui/video-input-psnr-ssim/video-input-psnr-ssim.html">Video Input with OpenCV and similarity measurement</a></li>
</ul>
<figure id="attachment_3529" aria-describedby="caption-attachment-3529" style="width: 256px" class="wp-caption alignnone"><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/einstein.png?x40497" alt="" width="256" height="256" class="size-full wp-image-3529" /><figcaption id="caption-attachment-3529" class="wp-caption-text">オリジナル画像</figcaption></figure>
<table>
<tr>
<td><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/contrast.png?x40497" alt="contrast" width="256" height="256" class="size-full wp-image-3530" /></td>
<td><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/blur.png?x40497" alt="blur" width="256" height="256" class="size-full wp-image-3531" /></td>
<td><img decoding="async" loading="lazy" src="https://rest-term.com/wp-content/uploads/2019/04/jpg.png?x40497" alt="" width="256" height="256" class="size-full wp-image-3532" /></td>
</tr>
<tr>
<td>MSE: 144.2<br />PSNR: 26.5<br />SSIM: 0.90<br />BRISQUE: 18.6</td>
<td>MSE: 143.9<br />PSNR: 26.5<br />SSIM: 0.72<br />BRISQUE: 59.4</td>
<td>MSE: 142.0<br />PSNR: 26.6<br />SSIM: 0.70<br />BRISQUE: 76.4</td>
</tr>
</table>
<p><a href="https://ece.uwaterloo.ca/~z70wang/research/ssim/#test">The SSIM Index for Image Quality Assessment</a>より画像引用</p>
<p>比較用ソースコード:  <a href="https://github.com/wellflat/opencv-samples/blob/master/quality/quality_comparison.py">opencv-samples/quality/quality_comparison.py</a></p>
<p>念のため再度注意書きしておきますが、BRISQUEはNR IQAなのでオリジナル画像は不要です。上記のようなサンプルだとMSE/PSNRのスコアはどれもほぼ同じですが、見た目は全然異なっていますね。一方でSSIMとBRISQUEは視覚的な差異が定量的に判断できるので良い指標と言えそうです。SSIMは計算式の任意パラメータを開示すれば共通利用できますが、BRISQUEはモデル自体あるいは学習データを配布する必要があるので面倒かもしれません。</p>
<p>上記の例だと精度面での比較は不十分ですが、機械学習ベースのBRISQUEはデータ精査やハイパーパラメータの調整等でモデル改善していけば精度向上が期待できます。僕は <code>cv::ml</code> モジュールを使って本格的に何かを学習させたことは無いんですが、<code>cv::ml::SVM::trainAuto</code> を使うとグリッドサーチとかクロスバリデーションとか自動でやってくれるので便利に利用できそうです。</p>
<h3 class="term">おわりに</h3>
<p>元論文内ではBRISQUEの活用例が紹介されていますが、こういったNR IQAの手法は画質の劣化具合の測定に重用するというよりは、ノイズ除去などの画素補完や生成の為に活用すると良いようです。この分野だと学習データの鮮度は比較的重要ではなく、モデルの更新も頻繁には必要ないはずなので利用しやすいと思います。</p>
<p>深層学習ベースの手法が数多く提案・実用化された今では、2012年に提案されたBRISQUEの仕組みは特徴量設計の職人芸的な部分辺りに懐かしさを感じる面はありますね。現在だと既にCNNベースのNR IQAアルゴリズムがいくつか提案されているようですし、OpenCVへの最新手法の実装も期待できそうです。</p>
<p>今回作成したサンプルコード (C++/Python)</p>
<ul>
<li><a href="https://github.com/wellflat/opencv-samples/tree/master/quality">opencv-samples/quality at master · wellflat/opencv-samples</a></li>
</ul>
<h3 class="term">参考</h3>
<ul>
<li>1.A. Mittal, A. K. Moorthy and A. C. Bovik, “ No-Reference Image Quality Assessment in the Spatial Domain ”, IEEE Transactions on ImageProcessing, 2012 (to appear).</li>
<li><a href="https://hal.archives-ouvertes.fr/hal-01286903/document">Comparison of No-Reference Image Quality Assessment Machine Learning-based Algorithms on Compressed Images</a> (PDF)</li>
<li><a href="http://Automatic Image Quality Assessment in Python" class="broken_link">Automatic Image Quality Assessment in Python &#8211; Towards Data Science</a></li>
<li><a href="http://live.ece.utexas.edu/research/Quality/nrqa.htm">No Reference Image and Video Quality Assessment</a></li>
<li><a href="https://ece.uwaterloo.ca/~z70wang/research/ssim/#test">The SSIM Index for Image Quality Assessment</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://rest-term.com/archives/3525/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Object Caching 117/574 objects using apc
Page Caching using apc (SSL caching disabled) 
Database Caching 1/221 queries in 0.086 seconds using apc (Request-wide modification query)

Served from: rest-term.com @ 2023-03-05 00:39:42 by W3 Total Cache
-->