エイバースの中の人

株、不動産、機械学習など。

コロナショックで市場が荒れているので、将来の為に現在のポートフォリオと考え方をまとめておこうと思います。

2020年3月のポートフォリオです。現在はフルポジションに近いです。中部日本放送がPER4.85、PBR0.22、配当利回り4.36%、ネットネット倍率2.925まで売り込まれたため、買い進んでみました。あとは、JRや物流、タバコ、電力など、ディフェンシブ系の銘柄と、TOB期待でハピネットやペパボ、アルプス物流、スリーエフあたりを購入しています。あとは高配当と優待目的で、不動産、銀行、商社あたりを買っています。

graph2


2019年3月から2020年3月までのポジションの推移です。マネーフォワードから出力しているため、若干、誤差があります。中国でコロナが流行った割に日経平均が下がらなかったので、リスクオフを予想して2月にキャッシュポジションを増やしたまではよかったのですが、その後の下落での買い戻しが少し早すぎたなというのが反省点です。本来、-30%だったのを、-15%に抑え込めたものの、-15%はダメージを受けた感じです。現在はほぼフルポジションなので、入金投資法として給与などを振り向けていく予定です。

graph1


また、都心でいい物件が見つかったので、賃貸不動産投資を開始しています。初の家賃が振り込まれるのは今月からなのですが、それも株式に再投資していこうと考えています。築年数が40年を超えているので、10年くらい、家賃を獲得するとともに、出口としては周辺の一帯の再開発により地権者住戸を取得することを目指しています。不動産は登記コストや契約の手間などが大きいので、基本的には一度買ったら、ずっと持っておく方がいいかなと考えていて、そうすると家賃があまりに低いと修繕費でペイしなくなるので、多少高くても都心の一定平米数以上の方が良いかなと考えています。1年ぐらい運用してみて、税計算などを学んで、いけそうなら、もう少し物件を増やしてもいいかなと考えていたのですが、REITが下がりすぎているので今ならREITですね。

REITは過剰に売り込まれていると考えていて、REITで5%以上の利回りが取れるようになっているので、都心のレジ系(アコモデーション、コンフォリア)やスポンサーの強いビル系(森ヒルズ、ヒューリック)あたりは買いかなと考えています。この利回りだと、あまり現物不動産投資する意味がなくなってきますね。少しだけJHRも買ってみましたが、これはただのギャンブルです。

総論として、ディフェンシブ系で高配当利回りの銘柄が増えてきているので、配当金生活に向けたポートフォリオは作りやすくなっているのかなと感じます。この機会に、理想的なポートフォリオを目指してみるのが良いのではと考えています。

ポジション一覧

16.19% 9402 中日放送
6.23% 9022 JR東海
4.76% 9036 東部ネット
4.03% 2914 JT
2.92% 9020 JR東
2.66% 9502 中部電力
1.80% 7950 デコラックス
1.79% 7552 ハピネット
1.77% 1345 上場Jリート
1.69% 8801 三井不
1.34% 6178 日本郵政
1.33% 9404 日テレHD
1.30% 4188 三菱ケミHD
1.26% 8369 京都銀
1.23% 9509 北海電力
1.21% 9505 北陸電力
1.14% 5938 LIXIL G
1.14% 9436 沖縄セルラー
1.03% 9433 KDDI
0.92% 9401 TBSHD
0.90% 8316 三井住友
0.90% 8566 リコーリース
0.86% 8964 フロンティア
0.85% 9432 NTT
0.85% 9432 NTT
0.84% 3633 GMOペパボ
0.83% 9531 東瓦斯
0.81% 7751 キヤノン
0.81% 9313 丸八倉
0.81% 8058 三菱商事
0.80% 8830 住友不
0.73% 4517 ビオフェルミン
0.72% 6623 愛知電
0.72% 1376 カネコ種苗
0.71% 7242 KYB
0.70% 9513 Jパワー
0.69% 4676 フジHD
0.66% 3712 情報企画
0.66% 9511 沖縄電力
0.64% 1930 北陸電工
0.64% 8386 百十四銀
0.63% 9532 大瓦斯
0.56% 9324 安田倉
0.53% 6301 コマツ
0.53% 9507 四国電力
0.53% 8031 三井物産
0.52% 9534 北海瓦斯
0.52% 8365 富山銀行
0.52% 9409 テレビ朝日HD
0.52% 9504 中国電力
0.51% 3863 日本紙
0.51% 7911 凸版印
0.49% 4541 日医工
0.48% 8354 ふくおか
0.47% 8905 イオンモール
0.47% 8802 三菱地所
0.46% 9055 アルプス物
0.45% 9302 三井倉HD
0.44% 3407 旭化成
0.42% 3791 IGポート
0.42% 8053 住友商
0.41% 8818 京阪神ビ
0.40% 8591 オリックス
0.40% 7561 ハークスレイ
0.40% 7272 ヤマハ発
0.39% 8253 クレセゾン
0.38% 3436 SUMCO
0.38% 9303 住友倉
0.38% 8570 イオンFS
0.37% 9468 KADOKAWA
0.35% 8961 森トラスト
0.35% 9503 関西電力
0.35% 9099 C&FロジHD
0.35% 3003 ヒューリック
0.34% 9506 東北電力
0.33% 3222 U.S.M.H
0.33% 7731 ニコン
0.33% 9305 ヤマタネ
0.33% 8308 りそなHD
0.33% 3086 Jフロント
0.31% 2329 東北新社
0.31% 9414 日本BS放送
0.30% 3234 森ヒルズ
0.30% 5301 東海カーボ
0.30% 9508 九州電力
0.30% 3476 投資法人みらい
0.29% 3048 ビックカメラ
0.29% 3892 岡山製紙
0.29% 9278 ブックオフGHD
0.29% 9278 ブックオフGHD
0.28% 9322 川西倉庫
0.28% 7544 スリーエフ
0.27% 3001 片倉
0.26% 7867 タカラトミー
0.25% 8181 東天紅
0.25% 1898 世紀東急
0.25% 6349 小森
0.25% 8806 ダイビル
0.25% 2674 ハードオフ
0.25% 3099 ミツコシイセタン
0.25% 8214 AOKI HD
0.24% 6486 イーグル工
0.24% 9059 カンダ
0.24% 8958 グロバル
0.23% 9681 東京ドーム
0.23% 9405 朝日放送グループHD
0.23% 3478 森トラストホテル
0.22% 3504 丸八ホールディングス
0.22% 3895 ハビックス
0.21% 6797 名古屋電
0.21% 3284 フージャース
0.19% 8167 リテールパートナーズ
0.18% 8002 丸紅
0.18% 8418 山口FG
0.18% 9831 ヤマダ電機
0.18% 8869 明和地所
0.17% 8985 JHR
0.17% 7844 マーベラス
0.17% 2768 双日
0.16% 8385 伊予銀
0.15% 9428 クロップス
0.15% 3289 東急不HD
0.14% 7201 日産自
0.14% 8306 三菱UFJ
0.13% 9307 杉村倉庫
0.13% 8864 空港施設
0.12% 5973 トーアミ
0.12% 1448 スペースバリューHD
0.12% 9501 東電力HD
0.11% 5020 JXTG
0.09% 9119 飯野海
0.08% 7971 東リ
0.07% 8963 INV
0.05% 9827 リリカラ

tf2onnxでONNXに書き出すと、大量のTransposeが生成されます。これは、TensorflowがNHWCなのに対して、ONNXがNCHWなため、並べ替えが発生するためです。この問題は、TransposeOptimizerを使用することで回避することができます。

通常のエクスポートコード
    graph1 = tf.Graph()
    with graph1.as_default():
        tf.import_graph_def(frozen_graph_def)
        onnx_graph = tf2onnx.tfonnx.process_tf_graph(graph1, input_names=input_names, output_names=output_names, opset=10)

        model_proto = onnx_graph.make_model("onnx")
        with open("output.onnx", "wb") as f:
            f.write(model_proto.SerializeToString())

before

TransposeOptimizerを使用
    graph1 = tf.Graph()
    with graph1.as_default():
        tf.import_graph_def(frozen_graph_def)
        onnx_graph = tf2onnx.tfonnx.process_tf_graph(graph1, input_names=input_names, output_names=output_names, opset=10)

        from tf2onnx.optimizer.transpose_optimizer import TransposeOptimizer
        optimizer = TransposeOptimizer()
        opt_model_proto = optimizer.optimize(onnx_graph)

        model_proto = onnx_graph.make_model("onnx")
        with open("output.onnx", "wb") as f:
            f.write(model_proto.SerializeToString())

after

オプティマイズすることで、推論速度は1.5倍程度高速化されます。

tf-onnx introducing many transpose operations
add transpose optimizer, and integrate it after tf graph conversion #108

2017年〜2018年は割安株が少なくなっていたため、小型のバリュー株に投資しつつ、あまり大きなポジションは持っていませんでした。しかし、2019年に入って、株価の二極化が進み、割安な株が増えてきたためポジションを増やしています。

具体的には、銀行、電力、不動産、化学の分野で配当利回り4〜5%でPER10倍以下、PBR1倍以下の大型株を中心に分散したポートフォリオを作っています。従来は小型株への集中投資派だったのですが、ここまで大型株の株価が下がると、あえて流動性の低い小型株に投資する妙味が減っているように思います。

最近はコーポレートガバナンス・コードの圧力もあり、戦略的な投資を行う強い大企業が出てきている印象があります。小型のバリュー株におけるカタリストという観点だと、製販一体化のためのM&Aや、成熟市場の寡占化のためのM&A、親子上場解消のためのM&Aなどを狙うことになると思います。ただ、小型のバリュー株は流動性の問題が大きいので、こちらもできるだけ分散して持っておくことが重要かなと思います。2018年までの高値は、流動性が低い中での株式投資ブームとSNSの活用に伴うイナゴ化による上昇の影響が大きい気がしていて、現状の株式市場の新規参入が少ない中では、分散しておいて安定配当をもらいながら、カタリストの到来を待つ、という方がよいかなと思います。

不動産の領域では、国内のマネタリーベースが順調に拡大しており、日銀のバランスシートの肥大化が止まらないので、日本銀行券の価値は落ちる方向と考えていて、直近のREITの高騰はその先取りかなと考えています。それに比べると、国内の不動産株は含み資産が多く、REITを買うよりも国内の不動産株の方が魅力的かなと思います。



銀行株は、単純に2016年水準まで落ちてきており、地銀だとPER3、PBR0.5とか、全く評価されていない水準になっているので買っています。米中貿易戦争に伴い、もう一回、東京がアジアの金融センターになるかもという期待もあります。

電力株は、現在は無配ですが、将来的にはどれも50円配当まで戻るのではと考えて、先行して買っています。電力も自由化で合併が進むような気がしていて、北陸電力は中部電力からのM&A期待です。

気持ち的には、資産が国内に偏っているので、海外株などにも手を出したいとは思っているのですが、米国株よりも国内株の方が今後のパフォーマンスが良い気がしていて、まずは国内株中心に投資しようと思います。



ちなみに、現在のポジション一覧は下記になっています。これだけ大量に分散したのは初めてです。

8354 ふくおか
9513 Jパワー
8801 三井不
7911 凸版印
3106 クラボウ
7272 ヤマハ発 
1515 日鉄鉱
3892 岡山製紙
9505 北陸電力
3407 旭化成
7242 KYB
9511 沖縄電力
5938 LIXIL G
4676 フジHD
9401 TBSHD
4401 ADEKA
5411 JFE
1376 カネコ種苗
9507 四国電力
5979 カネソウ
9302 三井倉HD
8418 山口FG
8316 三井住友
6486 イーグル工
6178 日本郵政
9324 安田倉
9501 東電力HD
9509 北海電力
3048 ビックカメラ
9502 中部電力
8306 三菱UFJ
9831 ヤマダ電機
3222 U.S.M.H
8385 伊予銀
4517 ビオフェルミン
7201 日産自
4188 三菱ケミHD
9409 テレビ朝日HD
8864 空港施設
3895 ハビックス
6301 コマツ
7945 コマニー
2193 COOKPAD 
2329 東北新社
2674 ハードオフ
9405 朝日放送グループHD
7902 ソノコム
7552 ハピネット
3284 フージャース
7544 スリーエフ 
9055 アルプス物
7399 ナンシン
1514 住石HD
4364 マナック
9193 東京船
9414 日本BS放送
5973 トーアミ
9428 クロップス
6797 名古屋電
7891 日本ユピカ
9404 日テレHD
3954 PAXXS
7950 デコラックス
9402 中日放送 
9036 東部ネット
2914 JT 
3436 SUMCO
8818 京阪神ビ
8251 パルコ
3791 IGポート
9278 ブックオフGHD
8591 オリックス
7561 ハークスレイ
8308 りそなHD
9468 KADOKAWA
2329 東北新社

サンプル:examples/deeplab
モデル:chainercv/links/model/deeplab

学習に使用したデータセットによって入力解像度が異なる。入力解像度未満の画像は入力解像度まで引き上げられる。

cityscapes : 1025x2049
ada20k : 513x513
voc : 513x513

入力画像のレンジは-1.0〜1.0。

Deeplabv3のFeatureExtractorは下記の解像度を出力する。

voc : 21x129x129

21はカテゴリ数である。各画素はカテゴリ数分のスケールがかかっているため、/21を行う。

セグメンテーションを可視化する際にはバイリニアで元解像度まで拡大した後、しきい値処理する。

vis_semantic_segmentation.py

1. RuntimeError: Only tuples, lists and Variables supported as JIT inputs, but got collections.OrderedDictが発生する

deeplabv3などでは以下のようにOrderedDictに値を代入して返していますが、ONNXエクスポータが扱うことができません。

result = OrderedDict()
x = features["out"]
x = self.classifier(x)
x = F.interpolate(x, size=input_shape, mode='bilinear')
result["out"] = x
return result

そのようなケースでは、下記のように書き換えます。

x = features["out"]
x = self.classifier(x)
x = F.interpolate(x, size=input_shape, mode='bilinear')
return x

RuntimeError: Only tuples, lists and Variables supported as JIT inputs, but got dict #13040

2. KeyError: 'upsample_bilinear2d'が発生する

ONNXのエクスポータにバイリニアが定義されていないため、エラーになります。

x = F.interpolate(x, size=input_shape, mode='bilinear')

nearestに置き換えるか、/usr/local/lib/python3.7/site-packages/torch/onnx/symbolic_opset10.pyに下記を追加します。

upsample_bilinear1d = _interpolate('upsample_bilinear1d', 3, "linear")
upsample_bilinear2d = _interpolate('upsample_bilinear2d', 4, "linear")
upsample_bilinear3d = _interpolate('upsample_bilinear3d', 5, "linear")

upsample_bilinear2d issue when exporting to onnx #22906

PytorchからONNXへの変換時に、Pytorch0.3系で学習したモデルの場合、BatchNorm2dもしくはInstanceNorm2dにおいて
 object has no attribute ‘track_running_stats’
というエラーが発生します。

この問題を解決するには、BatchNorm2dもしくはInstanceNorm2dを探索し、track_running_statsに値を設定します。

def recursion_change_bn(module):
    if isinstance(module, torch.nn.BatchNorm2d) or isinstance(module, torch.nn.InstanceNorm2d):
        module.track_running_stats = 1
    else:
        for i, (name, module1) in enumerate(module._modules.items()):
            module1 = recursion_change_bn(module1)
    return module

for i, (name, module) in enumerate(model._modules.items()):
    module = recursion_change_bn(model)
model.eval()

上記対処をすると、ONNXへの書き出しに成功します。

x = Variable(torch.randn(1, 3, 64, 64))
torch.onnx.export(model, x, 'output.onnx', verbose=True)

参考:‘BatchNorm2d’ object has no attribute ‘track_running_stats’

2019年のIGポート株主総会レポートです。

IMG_0848

場所:三鷹産業プラザ
参加者:90名程度

最初に京アニへのお悔やみの言葉があった後、今期の報告がありました。営業制作事業がコスト増による赤字との説明。その後、来期の計画の説明。グローバルな事業展開が進む中、子会社の役員は子会社の専念する体制を作るとともに、新たにCEOとCOOを置く経営体制になる。また、NTTぷららの坂東さんが社外役員に就任されるとのことです。以下、質疑応答です。

魔法使いの嫁は100%権利を持っているが、無料公開して販促に活用するようなことはできるか


魔法使いの嫁は原作権を持っているが、60%の権利になっている。現在、有料配信しており、ビジネス上の判断から無料公開は行わない。

コスト削減施策は一時的なものと永続的なものがあるが、来期の映像制作のコスト削減施策はどれくらいの比率か


来季は粗利率の改善を見込む。戦略作品は原価が上がる傾向がある。総合的に、粗利が上がるような施策を行っていく。

サイコパスの舞台には出資しているか


次の舞台は製作委員会を組成中。

対処すべき課題の映像制作の高度化に関して取り組んでいることは何か


シグナルMD含めて、紙からのデジタル化を推進している。タブレットで描く。また、サーバやインフラの整備を行っており、Nスタジオで技術開発を行っている。現在はトライアルで短い作品をやっている。とつくにの少女はフルデジタル。サイコパスはアナログとデジタルの融合。IGの強みを活かしていく。

XEBECの売却の理由は何か


利益剰余金が減る一方で、この先、さらに現象が進むと判断して、最悪のパターンになる前に手を打とうとしたが、うまく改善できなかったので、最終的に売却した。苦渋の選択ではある。サンライズさんは通常はアニメーターを正社員として雇うことはないが、交渉の中で正社員として受けていただいた。映像の権利はIGが保有している。ファフナーは今後も作っていく。

取締役の人数が減っている。利益なき繁忙ではないか。子会社の何がうまくいっていないか。


IGグループ一丸となって良い作品を作ってきたが、自分たちの身の丈にあった制作環境になっていなかった。毎月、仕掛かりについて周知徹底する会議を開く。スタジオとして強くしていく。また、第四の柱としてディジタルメディア事業を考えている。坂東さんの招聘はそのため。NTTぷららで増収増益を行ってきた手腕を発揮していただきたい。

クラウドファンディングは検討しているか。


クラウドファンディングに限らずフィンテックの活用を検討していく。

ドル円による影響は。


110円を想定している。海外はIG USAが窓口。アビアラッドが会長をしており、海外展開を推進していく。

ネットフリックスとの提携による影響は。


現在、ビデオグラムのみではコストが合わなくなってきている。ネットフリックスとの提携はプラス。

アクティビストファンドが購入しているが接触は。


機関投資家との1on1ミーティングをやっている。そこで評価されたのではないか。現状は株主提案はない。

ナデシコの権利は?


IGにある。

その後、決議を行い、坂東さんの挨拶。23年前、NTTぷららの社長になった。2008年からは光TVを一生懸命やった。この10年で日本を代表するサービスになった。このノウハウ、IP化がお役に立てると考えている。全力で経営に当たりたい。

11:00開始で12:00に終了しました。

昔は映像制作が黒字、出版が赤字だったのですが、最近は映像制作が赤字、出版が黒字と逆転しており、ポートフォリオを持った経営が重要だと思いました。今期から役員体制が刷新され、特にNTTぷららを退任された坂東さんが社外役員に入るということは驚きでした。ビデオグラム中心から映像配信中心へのシフトという中、IPを軸にどのような事業展開を行っていくのかに注目しています。

TensorflowからONNXにエクスポートするには、tf2onnxを使用します。

インストール
pip3 install tf2onnx

まず、tf.graph_util.convert_variables_to_constantsを実行することで、Tensorflowのgraphのvariableをconstantに変換しておきます。これをfrozenと呼びます。

ただし、convert_variables_to_constantsにはBatchNormalizationでmoving_varianceのvariableをfrozenできない問題があるため、事前に問題の起こるノードを修正しておきます。(Unable to import frozen graph with batchnorm

# fix batch norm nodes
gd = sess.graph.as_graph_def()
for node in gd.node:
    if node.op == 'RefSwitch':
        node.op = 'Switch'
        for index in range(len(node.input)):
            if 'moving_' in node.input[index]:
                node.input[index] = node.input[index] + '/read'
   elif node.op == 'AssignSub':
        node.op = 'Sub'
        if 'use_locking' in node.attr: del node.attr['use_locking']

# Freeze the graph
output_node_names=["upscale/mul","hourglass/hg_2/after/hmap/conv/BiasAdd","radius/out/fc/BiasAdd"]
frozen_graph_def = tf.graph_util.convert_variables_to_constants(
    sess,
    gd,
    output_node_names
)

次に、tf2onnx.tfonnx.process_tf_graphを実行することで、ONNXに変換します。引数には入力と出力に対応するノードを指定します。

# Convert to onnx
input_names=["import/eye:0"]
output_names=["import/upscale/mul:0","import/hourglass/hg_2/after/hmap/conv/BiasAdd:0","import/radius/out/fc/BiasAdd:0"]
graph1 = tf.Graph()
with graph1.as_default():
    tf.import_graph_def(frozen_graph_def)
    onnx_graph = tf2onnx.tfonnx.process_tf_graph(graph1, input_names=input_names, output_names=output_names)
    model_proto = onnx_graph.make_model("sample")
    with open("sample.onnx", "wb") as f:
        f.write(model_proto.SerializeToString())

出力されたONNXが正しいかどうかを、ONNXRuntimeを使用して検証します。

# Inference
import numpy
import onnxruntime as rt
onnx_sess = rt.InferenceSession("sample.onnx")
for node in onnx_sess.get_inputs():
    print(node.name)
    print(node.shape)
    print(node.type)
X = numpy.random.random((2, 36, 60, 1)).astype(numpy.float32)
pred_onnx = onnx_sess.run(None, {"import/eye:0":X})
print(pred_onnx)

なお、Placeholderがtf.contrib.layers.batch_normのis_trainingに接続されているなどすると、Ifを含むONNXが出力されるため、必要に応じてFalseなどの直値を設定しておきます。

Macのadbでadb devicesをしてもunauthorizedになる問題の対処方法です。この状態だと、adb logcatが使えません。

Macのadbでは、~/.android/にあるキーファイルと、Androidのdata/misc/adb/adb_key/にあるキーファイルで認証を行なっています。~/.android/にキーファイルが存在する場合、AndroidのUSBデバッグの設定で、”USBデバッグ認証を無効にする”を実行することで、接続できるようになるそうです。

しかし、何らかの原因で~/.android/にあるキーファイルが失われると、unauthorizedのまま復帰できなくなります。

このような場合、adb接続できる別のMacを用意し、~/.android/フォルダのadbkeyとadbkey.pubを、問題の起きているMacにコピーします。その後、adb devicesを実行することで接続ができるようになります。

How to solve ADB device unauthorized in Android ADB host device?

Androidのパフォーマンスプロファイラーの一つに、Qualcommの提供するSnapdragonProfilerがあります。

SnapdragonProfilerをMacから使おうとした際、adbではデバイスを認識していますが、SnapdragonProfilerからはデバイスが見つからない問題が発生しました。これは、SnapdragonProfilerがadbを認識できていないのが問題のようです。

Macでは一般に、.bash_profileにAndroidSDKのplatform-toolsのパスを通しますが、そのパスをSnapdragonProfilerが認識できないのが原因なため、Terminalからopen /Applications/SnapdragonProfiler.appとしてSnapdragonProfilerを起動すると認識できるようになります。

Forums - Snapdragon Profiler can not find device in Mac

なお、Profilerにかけるには、apkのdebugging attributeをONにする必要があるため、UnityではDevelopmentBuildに指定する必要があります。

TensorFlow Graphicsは3Dメッシュに対してCNNを適用できるようにするフレームワークです。従来、TensorFlowで3Dメッシュを扱う場合は、ボクセルに変換して扱っていました。TensorFlow Graphicsでは、新たに、微分可能レンダラと、メッシュに対するGraph Convolutionを実装することで、3Dメッシュをそのまま扱えるようにしています。mesh_segmentationによる3Dメッシュの各ポリゴンへのラベリングなど、今までできなかった処理が可能になります。

従来のコンボリューションは2D画像に対して適用していました。

cat_image_convolutions

グラフコンボリューションでは、3Dメッシュの頂点に連結している頂点に対して重みをかけて畳み込みを行います。畳み込みを行うと、チャンネル方向に3Dメッシュが増殖していくイメージになります。

cat_mesh_convolutions

グラフコンボリューションによって、3Dメッシュの部位ラベルの付与が可能になります。

mesh_segmentation

頂点と頂点の接続関係はマトリックスで定義します。頂点iと頂点jが接続されている場合、A[i,j]に値が入ります。性質上、対角には必ず値が入ります。

A[i, j] = w[i,j] if vertex i and vertex j share an edge,
A[i, i] = w[i,i] for each vertex i,
A[i, j] = 0 otherwise.
where, w[i, j] = 1/(degree(vertex i)), and sum(j)(w[i,j]) = 1

モデルデータは、頂点列とポリゴンを構成する頂点ID、頂点の接続情報から構成され、tfrecordsに格納されます。fbxなどからの変換ツールはまだリリースされていません。(3D format to .tfrecords

それでは、MacにTensorFlow Graphicsをインストールしてみます。

TensorFlow Graphicsをインストールします。

pip3 install tensorflow-graphics

Jupyter Notebookをインストールします。

pip3 install jupyter

Colabtoolsをインストールします。

git clone https://github.com/googlecolab/colabtools.git
cd colabtools
python setup.py install

Colabtoolsをインストールしていない場合は、threejsを使ったモデルビューワで下記のエラーが発生します。

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
 in 
     19     'vertex_colors': mesh_viewer.SEGMENTATION_COLORMAP[test_labels[0, ...]],
     20 }
---> 21 input_viewer = mesh_viewer.Viewer(input_mesh_data)

/usr/local/lib/python3.7/site-packages/tensorflow_graphics/notebooks/mesh_viewer.py in __init__(self, source_mesh_data)
     56 
     57   def __init__(self, source_mesh_data):
---> 58     context = threejs_visualization.build_context()
     59     self.context = context
     60     light1 = context.THREE.PointLight.new_object(0x808080)

/usr/local/lib/python3.7/site-packages/tensorflow_graphics/notebooks/threejs_visualization.py in build_context()
     72   """Builds a javascript context."""
     73   threejs_url = 'https://www.gstatic.com/external_hosted/threejs-r98/'
---> 74   _publish.javascript(url=threejs_url + 'three.min.js')
     75   _publish.javascript(url=threejs_url + 'examples/js/controls/OrbitControls.js')
     76   return _js_builder.Js(mode=_js_builder.PERSISTENT)

NameError: name '_publish' is not defined

サンプルをcloneします。

git clone https://github.com/tensorflow/graphics.git

サンプルを実行します。

cd tensorflow_graphics
cd notebooks
jupyer notebook

notebooks/mesh_segmentation_demo.ipynbがmesh_segmentationの推論サンプルです。まだ学習側のコードは公開されていませんが、ディープラーニングの応用例が3Dにまで広がっていく未来が楽しみです。

notebooks/spherical harmonics optimization.ipynbが球面調和ライティングの環境マップを計算するサンプルです。TensorFlow Graphicsに含まれる微分可能レンダラーを使用することで、与えられた画像から、環境マップテクスチャを推定します。

学習の推移です。

spherical_harmonics2

学習結果のテクスチャです。

spherical_harmonics3

TensorFlow Graphicsの概要動画です。

TensorFlow+Kerasでヒートマップ系のモデルを使用する場合、レイヤーの途中で画像サイズを拡大するためにlambdaを使用したBilinearフィルタが含まれるケースがあります。lambdaを扱えないハードウェアで推論する場合、この処理を規定のレイヤーに置き換える必要があります。

up2 = Lambda(
     lambda x: tf.image.resize_bilinear(
        x[0],
         x[1].shape.as_list()[1:3], 
         align_corners=True))([low3, up1])

単純に、元画像の1/2になっている場合は、UpSampling2Dに置き換えることができます。しかし、縮小時のテンソルが奇数の場合に、丸めが行われ、UpSamping2Dではテンソルサイズが合わない場合があります。たとえば、(5,5) -> (3,3)に縮小されたテンソルだと、(3,3) -> (6,6)に拡大されてしまいます。

この場合、4倍まで出力を拡大したあと、Croppingして、縮小することでサイズをあわせることができます。

from_shape=low3.shape.as_list()[1:3]
to_shape=up1.shape.as_list()[1:3]
crop_h=from_shape[0]*2-to_shape[0]
crop_w=from_shape[1]*2-to_shape[1]
common_upsample = UpSampling2D(size=(4,4), name=prefix_name+"_up_sampling2d")(low3)
common_multiple = Cropping2D((crop_h,crop_w), name=prefix_name+"_crop")(common_upsample)
up2 = AveragePooling2D(pool_size=(2,2), strides=(2,2), padding="same", name=prefix_name+"_downsampling")(common_multiple)

keras2caffeを使う場合は、UpSampling2Dの名前はup_sampling2dをつけておきます。

これで、多様なハードウェアで動作するようなネットワークを構築することができます。なお、出力はBilinearとは異なり、NearestNeighborとなるため、完全に一致はしません。

このように変更したあとに、規定の学習済みモデルを読み込むには、load_weightsでby_name=Trueを指定します。

net.load_weights("keras.h5",by_name=True)

numpy 1.16ではcaffeが動作しないため、numpy 1.15を使いたい場合に対応するtensorflowのバージョンは1.12のようです。

TensorFlow 1.12
scikit-learn 0.20.0
XGBoost 0.81
numpy 1.15.4

ランタイム バージョン リスト

バージョンを指定したインストールは下記のコマンドで行います。

pip install numpy==1.15
pip install tensorflow==1.12

Tensorflow、numpy、cudnn、cuda、caffeのバージョンを揃えないといけないのがとっても大変ですね。

netronはcaffemodelやtfliteファイルのネットワーク構造を可視化できるツールです。ブラウザで動作します。

例えば、darkflowで変換したyolo_tinyを可視化すると下記のようになります。leaky reluがmulとmaxにマッピングされていることが分かります。

yolo_tiny


mobilenetだと下記のようになります。tfliteによってモデルが最適化され、レイヤーが統合されていることが分かります。

mobilenet

WindowsのPythonにdlibをインストールする場合、Links for dlibからPythonのバージョンに対応したビルド済みバイナリのURLを取得します。

Python 3.5 + 64bitの場合、以下のコマンドでインストールすることができます。cp35がPythonのバージョン、amd64が64bitバイナリの指定です。

pip install https://files.pythonhosted.org/packages/38/18/92fc25855307bcf582a30034ae657fda205de4f29773323bb388e592f17c/dlib-19.4.0-cp35-cp35m-win_amd64.whl#sha256=67e7d86eedaef650788b058d9e086198ead583f5bb3041fd9a431ae50658e9f4

MojaveでXcodeをインストールしようとすると、40GBの空き容量があるのに、空き容量が足りないと言われる問題が発生しました。

Mojaveでは1時間に1つ、TimeMachineのローカルバックアップを作成します。「このMacについて」で表示される空き容量にはこのローカルバックアップはカウントされないため、空き容量があるものの、AppStoreアプリから見たときに空き容量が不足して見えるようです。

"Not enough free space" when trying to install XCode even though there's ~7x more free space than required

このバックアップは下記コマンドで削除できます。

for d in `tmutil listlocalsnapshots / | awk -F'.' '{print $4}'`; do sudo tmutil deletelocalsnapshots $d; done


ローカルバックアップを削除すると、無事、インストールできました。

UnityのWebCamTextureはデバイスによって回転された画像が取得されるため、画像認識を行う際、適切に回転させる必要があります。デバイスによって取得できる値をまとめました。使用した環境はUnity5.5です。

パラメータ
パラメータ名内容
videoRotationAngle右回りの角度 (度) を返します。多角形を回転させるためにその値を使用し、カメラのコンテンツを正しい向きで表示できます。
videoVerticallyMirroredテクスチャ画像が垂直に向きをかえているかどうか。

実機での取得値
デバイスvideoRotationAnglevideoVerticallyMirrored
MacPro20130False
iPadPro 2018 Landscape Incam180True
iPadPro 2018 Landscape Outcam0True
iPadPro 2018 Portrait Incam90True
iPadPro 2018 Portrait Outcam90True
GeminiPDA Landscape Incam180False
GeminiPDA Landscape Outcam--
GalaxyS8 Portrait Incam90False
GalaxyS8 Portrait Outcam270False

コードとしては、angle=90もしくはangle=270の場合は出力のxy軸を反転、angle=90もしくはangle=180の場合は出力の垂直反転でいけるようです。

小さな画像が大量にある場合、ImageDataGeneratorを使用するとディスクのランダムアクセスネックでGPU使用率が伸びず、学習速度が低下します。学習画像を事前にHDF5に書き込んでおくことで、ランダムアクセスを抑制し、高速化することができます。

まず、flow_from_directoryから取得できる学習用データからh5pyを使用してHDF5を作成します。以下の例ではtrain.h5を作成します。同様にvalidation.h5も作成します。

train_generator = train_datagen.flow_from_directory(
   INPUT_PATH+'/annotations/'+ANNOTATIONS+'/train',
   target_size=(IMAGE_SIZE, IMAGE_SIZE),
   batch_size=BATCH_SIZE,
   class_mode='categorical',
   shuffle=True
)

training_data_n = len(train_generator.filenames)
training_class_n=len(train_generator.class_indices)

import h5py
f = h5py.File("train.h5", 'w')
train_x = f.create_dataset('training_x', (training_data_n,IMAGE_SIZE,IMAGE_SIZE,3), dtype='f')
train_y = f.create_dataset('training_y', (training_data_n,training_class_n), dtype='f')

cnt=0
for x_batch, y_batch in train_generator:
  for i in range(BATCH_SIZE):
    train_x[cnt] = x_batch[i]
    train_y[cnt] = y_batch[i]
    cnt = cnt+1
    if cnt>=training_data_n:
        break
  if cnt>=training_data_n:
    break

f.close()

学習時はHDF5Matrixを使用します。

  from keras.utils.io_utils import HDF5Matrix
  x_train = HDF5Matrix("train.h5", 'training_x')
  y_train = HDF5Matrix("train.h5", 'training_y')
  x_validation = HDF5Matrix("validation.h5", 'validation_x')
  y_validation = HDF5Matrix("validation.h5", 'validation_y')
  fit = model.fit(
    epochs=EPOCS,
    x=x_train, 
    y=y_train,
    validation_data=(x_validation,y_validation),
    batch_size=BATCH_SIZE,
    shuffle='batch'
  )

VGGFace2を使用した性別推定モデル(3146003画像)では160GBのHDF5を生成し、学習時間を1EPOCで3時間から30分に削減でき、6倍高速化できました。評価環境はGeforceRTX2080、32GBメモリ、1TB SSDです。

floatではなく8bit unsigned intでhdf5ファイルに書き込むこともできるようですが、以下のコードで試してみたところ、学習がほとんど進まなくなってしまいました。

train_x = f.create_dataset('training_x', (training_data_n,IMAGE_SIZE,IMAGE_SIZE,3), dtype='uint8')
train_x[cnt] = x_batch[i].astype(np.uint8)

学習時のノーマライズは下記のように行いましたが、どこかに問題があるようです。

ds_norm = lambda x: x / 255.0
x_train = HDF5Matrix(HDF5_PATH, 'training_x', normalizer=ds_norm)

kerasではデフォルトでGPUメモリを100%確保します。ディスクネックでGPU使用率が低く、複数の学習を同時に走らせたい場合は、モデル作成前に以下のようにすることで、GPUのメモリ使用率を抑制することができます。

import tensorflow as tf
import keras.backend as backend

config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.5
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)
backend.set_session(sess)

Caffe 1.0.274でkeras2caffeを使った際、KeyErrorが発生します。

Traceback (most recent call last):
  File "convert_to_caffemodel.py", line 76, in 
    keras2caffe.convert(keras_model, PROTOTXT, WEIGHTS)
  File "keras2caffe\convert.py", line 76, in convert
    if layer_type=='InputLayer' or not hasattr(caffe_net, 'data'):
  File "Anaconda3\envs\py35\lib\site-packages\caffe\net_spec.py", line 180, in __getattr__
    return self.tops[name]
KeyError: 'data'

これは、hasattrがgetattrで実装されていますが、caffeがKeyErrorをAttributeErrorに置き換えていないために発生します。

How to make a class which has __getattr__ properly pickable?

class NetSpec(object):
    """A NetSpec contains a set of Tops (assigned directly as attributes).
    Calling NetSpec.to_proto generates a NetParameter containing all of the
    layers needed to produce all of the assigned Tops, using the assigned
    names."""

    def __init__(self):
        super(NetSpec, self).__setattr__('tops', OrderedDict())

    def __setattr__(self, name, value):
        self.tops[name] = value

    def __getattr__(self, name):
        return self.tops[name]

そこで、NetSpecでKeyErrorをAttributeErrorに変換します。

class NetSpec(object):
    """A NetSpec contains a set of Tops (assigned directly as attributes).
    Calling NetSpec.to_proto generates a NetParameter containing all of the
    layers needed to produce all of the assigned Tops, using the assigned
    names."""

    def __init__(self):
        super(NetSpec, self).__setattr__('tops', OrderedDict())

    def __setattr__(self, name, value):
        self.tops[name] = value

    def __getattr__(self, name):
        try:
            return self.tops[name]
        except KeyError:
            raise AttributeError(name)

これで正常にcaffemodelに変換することができます。

↑このページのトップヘ