汎用ロボットのアーキテクチャについて 続き2

前回の記事はこちらです。
汎用ロボットのアーキテクチャについて 続き1 - neuralnetな日記

次に、アルファのブレインとボディの接続について検討します。データの送り方は、データを流し続けるストリーミングタイプと、一度だけデータを送付するワンショットタイプがありそうです。

ボディからブレインに入力可能な情報は以下の通りです。

  • カメラ情報:映像ストリーミング
  • マイク情報:音声ストリーミング
  • 温度/湿度情報:テキスト情報のストリーミング(JSON等)

ストリーミングデータを受け取り、メッセージキューで処理することになりそうなので、クラウドのIoTソリューションが向いていそうですが、一旦は単体サーバーで進めてみます。

余談になりますが、クラウドのIoTソリューションですと、PubSubができるようです。カーシェアリングの車の貸し借りのような感覚で、ロボットのボディを貸し借りする世界観も想像に難くありません。
こちらは、GoogleのIoTソリューションです。
cloud.google.com

次にボディの出力は以下の通りです。

  • スピーカー(答えるため、また音楽を流すため)
  • 赤外線出力(リモコン操作)
  • ディスプレイ(表情を表現するため)
  • 移動(キャタピラ移動)

出力はすこし複雑で、ワンショットとストリーミングで流すものがありそうです。また、ボディで完結するものもあるかもしれません。
基本的には、自律的な発言には意思決定が必要なので、ブレインからキックする必要があります。

スピーカーでの回答、実行単位はワンショットもしくは文脈の長さによってはストリーミングです。
スピーカーでの音楽は、ストリーミングです。
赤外線出力は、リモートから送信パターンを送付するものとし、データをワンショットで送付することとします。
ディスプレイは、表情パターン(にっこり、笑う、悲しい、眠る、しゃべる、うれしそうにしゃべる、忙しくしゃべる、普通等)のみを送付し、表示はボディが行うことします。転回する際にデータを送付するワンショットとします。
移動は、左右に対して、-1,0,1の3パターンを送付するものとします。(右,左)=(1,1)は前進、(右,左)=(1,-1)は左に回転等です。また、電圧のレベルを1から10で決定します。指令が出ているときのみ動くと考えれば、ストリーミングのほうが適切です。

まとめると以下の通りです。

カメラ情報 入力:ボディからブレイン 映像 ストリーミング
マイク情報 出力:ボディからブレイン 音声 ストリーミング
温度/湿度情報 入力:ボディからブレイン テキスト ストリーミング
スピーカー(回答) 出力:ブレインからボディ 音声 ストリーミングもしくはワンショット
スピーカー(音楽) 出力:ブレインからボディ 音声 ストリーミング
赤外線出力 出力:ブレインからボディ テキスト ワンショット
ディスプレイ 出力:ブレインからボディ テキスト ワンショット
キャタピラ移動 出力:ブレインからボディ テキスト ストリーミング

仮に、移動をワンショットで行う場合は、緊急停止等のボディのみで終始する仕組みがあったほうがよいかなと思いましたが、アルファでは、ボディ側に周辺を把握するセンサー等がないので実装が困難であったのと、ストリーミングが切れたら停止するという対応ができるので、その方向で検討を進めます。

逆に、ボディがアルファよりリッチな場合や、将来的に、安全性を考慮しなければいけない場合は、ボディがある程度自律的に安全性を判断して緊急停止するなどの動きを取り入れる必要がありそうです。

思い付きで、音楽を再生できる機能を搭載する設計にしましたが、同じ流れでいうと、ディスプレイに映像を表示できる機能があってもよくなります。どちらも、このロボットが完全なサービス提供者という立ち位置であれば自然なのですが、ロボットがパートナーであると考えると、逆に不自然さを感じます。ドラえもんは歌が下手であり、音楽を流すロボットではなく、音楽をともに聴くロボットだからです。当たり前な話ですが、汎用ロボットであっても目的に応じて搭載する機能が変わってくるため、ベータの設計では、それを意識したほうがよさそうです。

本題に戻ります。これだけ複数のセッションがある場合、全体的なセッション管理が必要です。LTEの場合、IPアドレスも同的に変わる可能性があるため、どのレイヤーでセッション維持するかを検討する必要もありそうです。

当初想定していた仕様
汎用ロボットのアーキテクチャについて - neuralnetな日記
では、セッションをつなげに行くのはボディからという想定です。これは、ボディの通信手段が常に変わる事を想定しており、ネットワークアドレスが普遍なものがブレインしか存在しないためです。
暗に、従来のwebのホスト名およびIPアドレスを使用すると宣言しています。

なお、この段階で基本的にはブレインとボディはサーバークライアント方式を採用しており、その制約を受けることになります。具体的に言うと、あるボディに別のロボットのブレインが接続しに行くということが困難な構成になります。SIP(もしくはそれに類似した機能)などを導入し、双方向の通信を可能にすることで、ネットワークの構成がより柔軟になり、ロボットの相互接続性が広がると思います。このあたりは、どちらの設計思想が将来求められてくるかに依存するのだと思います。
Session Initiation Protocol - Wikipedia

今回はシンプルに、ブレインサイドにWebサーバーを立てて、そことGET/POSTを通じてセッション管理を行う前提で話を進めていきます。

汎用ロボットのアーキテクチャについて 続き1

次に、ボディに必要な部品をリストアップします。
まず大まかに候補をリストアップした後で、詳細の見直しを行います。

難易度とコストと期間を把握することが目的です。

 

※ 金額は送料別です。

 1) 本体

hiramineさんのHW一覧から単三充電器と電池ケースを除いたもの: 9,163円

www.hiramine.com

 

2) カメラ 

同じくWebカム:2,031円

www.hiramine.com

Raspberry PiにはCSIインターフェイスがあるので他の候補も考えます。

カメラ候補その2 RaspberryPiカメラモジュール 3,080円

akizukidenshi.com

正規品です。

他、割安のものだと

ラズパイ用 高画質 カメラモジュール - Camera Module for Raspberry Pi 920円

 赤外線付きのもので

for raspberry pi カメラモジュールIR Camera高速冷却赤外線LED ラズベリーパイ Raspberry Pi 3 b+ / Pi Zero W使用OV5647 5MP 2,859円

というものもありました。

 

CANDY Pi Lite ラズパイ用LTE通信モジュール【CANDY-PI-LITE-LTE】:15,980円

www.marutsu.co.jp

 

ワイヤレス充電モジュール:1,050円

www.sengoku.co.jp

 バッテリーはこちらの記事を参考にAnker PowerCore 10000 が良いかと思います。

http://densikousaku.com/archives/45

item.rakuten.co.jp

出力はUSBなので、USBからmicroUSDへの接続ケーブルが別途必要です。 100円ショップで買えるものですので、+108円と考えます。

 

なお、通常のRaspi3は5000mAhで6時間動くようです。

https://ichibariki.hatenablog.com/entry/2017/10/29/160454

 

温度湿度センサー 気圧付き 1,080円

akizukidenshi.com

こちらのサイトを参考にさせていただきました。

deviceplus.jp

 

 赤外線発信機 583円
指向性が心配です。

www.switch-science.com

https://qiita.com/_kazuya/items/62a9a13a4ac140374ce8 

 

スピーカー 770円

karaage.hatenadiary.jp

 USBマイク 179円

qiita.com

こちらのようです。

「BU-Bauty いつでもどこへも携帯可能!世界最小USBマイク PC Mac用USBマイク 超小型 超ミニ 22mmx18mmx5mm」

 

 

 ディスプレイ 3,320円

nomolk.hatenablog.com


RasPiは再起動後に時間がリセットされてしまいます。RTCモジュールも将来的に必要になりそうですので前提とします。 990円

www.sgv417.jp

 

このまま機材を購入し、実装に進みたい気持ちでいっぱいですが、目的が違うのと、残念ながら時間がないので、このぐらいで止めておきます。

パーツ コスト 構築
RasPi本体一式 ¥9,163 3日
カメラ ¥3,080 1日
LTEモジュール ¥15,980 1日
ワイヤレス充電モジュール ¥1,050 1日
バッテリー ¥2,990 1日
バッテリーケーブル(USB) ¥108 1日
温度湿度センサー ¥1,080 1日
赤外線発信機 ¥583 1日
スピーカー ¥770 1日
USBマイク ¥179 1日
ディスプレイ ¥3,320 1日
RTCモジュール ¥990 1日
合計 ¥39,293 14日

 LTEモジュールのコストが嵩むところですが、概ね4万円+半月もあれば構築が可能そうです。

汎用ロボットのアーキテクチャについて

クラウド上にLinuxサーバーを起き、その上で自律可能なラジコン制御プログラムを展開します。

ボディの実装として、タミヤのモーターキットに制御用OS+LTEモジュールを設置し、更にマイク、スピーカー、カメラ、赤外線出力を実装したものを用意します。

これにより、簡単なクラウド型のロボットが作れます。つまり、現在の技術群を持って、ある程度人に役立つロボットが実装できるはずです。

仮にこのロボットをアルファと呼びます。

f:id:neuralnet:20190502114531j:plain

アルファに対して、詳細と拡張を考えてみたいと思います。拡張したものをベータと呼びます。この記事では、アルファとベータを通じて今の段階で分かる現実的な汎用ロボットのアーキテクチャを思考実験してみたいと思います。

アルファの目的は、簡単なコミュニケーション、音楽、そして赤外線による家電の操作です。ワンルーム程度のスペースを動き回り、必要に応じて自ら充電するぐらいのものを想定します。それなりに役に立ちつつ人から好まれる存在の原型です。

ベータの目的は、汎用ロボットユニットです。拡張可能で、表面的でもある程度、自律的なものであると良いと思います。役に立ちつつ好まれる存在という位置づけは保持します。また、ロボット三原則の実装まで取り入れれば良いかなと思っています。

想定する結論

あらかじめ、この記事を通じて言いたかった事を書きますと、将来のロボットのアーキテクチャは、おそらく以前から描かれていたようなものとは異なりそうという事です。

まず、汎用ロボットでは、いわゆる脳と体は分離されそうです。脳であるコンピューティング部分をクラウドに置くことで、常に機能をアップグレード可能です。ボディはただの外部との接触点に過ぎず、単体としてロボットが成立する必要性がなさそうです。

次に、それ故ですが、ロボットは単体で機能するというより、群体のような存在になりそうです。1つのクラウドコンピュータ上に複数のパーソナリティが存在し、単一のパーソナリティが複数のボディを扱うことができるような位置づけになると思います。

最後に、個体の概念が目的志向なものになりそうです。ロボットにおける個体という概念は特定の情報範囲を保護する為のファイヤウォール機能という位置付けに過ぎず、人に対するユーザーインターフェイスとしてのパーソナリティは、教育、パートナーシップの構築、警備などの特別な目的がない限り、誰もに好まれるような汎用的な人格になると思います。

というのが、当初予測なのですが、果たしてどのようになるか興味深いところです。(対極的な結果として、従来通りのロボット像が浮かび上がる可能性もあるとも思っています)

では、はじめてみます。

思考実験その1:アルファの実現可能

その1では、調査を通じてアルファを構築可能な手順を作ります。成果物はいわゆる設計なので、実装の影響を受けます。(つまり、今回は設計まで行いますが作ってみたら違いましたは全然起こりえることですのでお知りおきください)

構築する対象は
1) ハードウェア(タミヤのタンクキットとRasberryPiもしくはAndroid、各種センサー)
2)クラウド上のシステム(Google Cloud上のLinux)
の2つのコンポーネントです。

1)がボディ、2)がブレインといったところでしょうか。

ボディ部分

まず、ボディですが様々なリファレンスがあります。私のお気に入りはこちらの記事で、RasberryPiとwifiによる実装です。

www.hiramine.com

こちらの拡張版まで行くと、カメラの搭載まで一気に進める事ができます。

ハードウェア側の制御OSはAndroidにしたかったのですが(ディスプレイや電源などの理由により)、ここまで実装が進んでいるものがあるので、こちらをベースにさらなる拡張をすることで、アルファのボディを目指します。

差分は以下です。

  • 入出力系の追加(マイクや赤外線等)
  • 電源を乾電池から充電式へ
  • 通信をWifiからLTE

センサーはカメラが既に搭載されていますから、追加でマイク、スピーカー、赤外線出力をつけれれば目的が達成できます。ポートの空きがあるかと、求めるパーツが世の中に販売されているかが大事になってくるかと。

電源の換装はそれほど難しくないと思いますが、無線式充電器の有無がポイントです。また、充電の状態を制御用OSに取り込むようにしたいと思います。

通信ですが、ワンルーム程度であれば、WiFiでも機能すると思います。が、ベータでは、5Gも視野に入れたいので、是非ともLTEモジュールへ換装したいところです。

ブレイン部分

クラウドサービス上にLinuxを用意します。おそらくどこでも良いのですが、個人的な知識から、Google CloudとUbuntuで進めます。

ブレインとボディどちらをSSLのサーバーとして設定するかはセキュリティとボディの所有権制御の観点から非常に興味深いテーマですが、一旦は、ボディがクライアントであり、起動後にブレイン側にSSL接続する方針で進めます。

ブレイン側のサーバーに展開するバックグラウンドプロセスは、以下の様なものになるはずです。

  • カメラ情報を受け続けるプロセス
  • マイク情報を受け続けるプロセス
  • センサーと時間の情報から、何らかの意思決定をし、音声もしくは赤外線出力をアウトプットするプロセス

ここまで書いて、ロボットにディスプレイが無い事に気づきました。Android端末の流用であれば、ディスプレイがビルトインされていますが、RasberryPiですと、ハード、ソフト共に追加が必要です。(ディスプレイを追加する事で、表情を表現したいと思いました)

ついでに温度/湿度センサーも追加したいと思います。不快な温度で自律的にエアコンつける仕様です。

まとめると、

ボディ部分

  • ベースはhiramineさんの実装(タミヤのタンクにRasberryPi、カメラ実装済)
  • 電源を充電式のものとし無線充電を導入(自律的に充電可能にするため)
  • 通信をLTEにする(将来的な宅外での通信を可能にするため)
  • 入力 カメラ(周囲の画像を得るため)
  • 入力 マイク(周囲の音を拾うため)
  • 入力 温度/湿度センサー(暑さ、不快指数を把握するため)
  • 出力 スピーカー(答えるため、また音楽を流すため)
  • 出力 赤外線出力(リモコン操作)
  • 出力 ディスプレイ(表情を表現するため)
  • 出力 移動(キャタピラ移動)


ブレイン部分

  • Google Cloud上のubuntu
  • ボディからブレインにSSL接続
  • カメラ情報を受け続けるプロセス
  • マイク情報を受け続けるプロセス
  • 温度/湿度情報を受け続けるプロセス
  • 入力系プロセスと時間から、何らかの意思決定をし出力系を通じて出力するプロセス(移動を含む)

という項目を深掘りしていきます。

続きはこちらです。
汎用ロボットのアーキテクチャについて 続き1 - neuralnetな日記

損失関数の出力サンプル / Output sample of Loss function

SMEと交差エントロピーの具体的な出力を見て見ます。交差エントロピーに非対称性があること、複数次元の出力の出力の仕方がわかると思います。

import numpy as np

# 2乗和誤差
def mean_squared_error(y, t):
  # ニューラルネットワークの出力と教師データの各要素の差の2乗、の総和
  return 0.5 * np.sum((y-t)**2)

# 交差エントロピー誤差
def cross_entropy_error(y, t):
  delta = 1e-7 # マイナス無限大を発生させないように微小な値を追加する
  return -np.sum(t * np.log(y + delta))

以下を参照させていただきました: Python vs Ruby 『ゼロから作るDeep Learning』 4章 損失関数 (loss function) の実装 - Qiita

そもそもの違いを見ます。数字の差にあまり意味はありません。

t = 0.5 # 正解データ
y = 0.4 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))
MSE: 0.0049999999999999975
CEE: 0.45814524093709313

MSEでは、位置による距離は変わりません。交差エントロピーは位置により出力が変化します。

t = 0.8 # 正解データ
y = 0.7 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))

t = 0.6 # 正解データ
y = 0.5 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))

t = 0.2 # 正解データ
y = 0.1 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))
MSE: 0.005000000000000009
CEE: 0.2853398408652799
MSE: 0.0049999999999999975
CEE: 0.41588818833597924
MSE: 0.005000000000000001
CEE: 0.4605168185989092
t = 0.95 # 正解データ
y = 0.85 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))

t = 0.15 # 正解データ
y = 0.05 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("CEE:" , cross_entropy_error(y,t))
MSE: 0.0049999999999999975
CEE: 0.15439287125818693
MSE: 0.004999999999999999
CEE: 0.4493595410333986

MSEは入れ替えても出力は変わりません。交差エントロピーは入れ替えると出力が変化します。

t = 0.5 # 正解データ
y = 0.4 # 出力データ
print("MSE:" , mean_squared_error(y,t))
print("MSE:" , mean_squared_error(t,y))

print("CEE:" , cross_entropy_error(y,t))
print("CEE:" , cross_entropy_error(t,y))
MSE: 0.0049999999999999975
MSE: 0.0049999999999999975
CEE: 0.45814524093709313
CEE: 0.27725879222398614

3次元における出力を見てみます。

t = [0.1, 0.3, 0.7] # 正解データ
y = [0.0, 0.2, 0.8] # トライデータ
print("MSE:" , mean_squared_error(np.array(y),np.array(t)))
print("CEE:" , cross_entropy_error(np.array(y),np.array(t)))
MSE: 0.015000000000000006
CEE: 2.250841187246052

損失関数の種類 / Types of Loss Function

機械学習における損失関数(Loss Function)の位置付けは、作成した学習ロジックに対する評価指標です。

 

正解データ z (zは実数の集合) に対し、入力データ x (xは実数の集合) を学習ロジックf に投入して得られたデータ y (yは実数の集合でzと同じ配列数) があった時、yがzに近ければ近いほど、 学習ロジック f は優秀であるといえます。

 

「近い」という尺度の概念は、1つの数直線上の評価です。つまり、ただの1次元の数字になるので、y = f(x) と z に対する損失関数 L (y, z) は 実数への写像になります。

 

2点間を測る指標です。片方(z)の方がより「正しく」て、検査対象(y)が正しいデータにどれだけ近いかを測るのが損失関数だと思えば、大まかな把握ができると思います。

 

ただし、いわゆる距離ではありません。zから見たyの損失とyから見たzの損失は必ずしも一致しないため、対象律が成立していません。距離函数 - Wikipedia

 

損失関数としては「クロスエントロピー (交差エントロピーとも言います、cross entoropy)」と「平均二乗誤差/MSE (Mean Squared Error)」がよく使用されています。

 

より直感的なのは、平均二乗誤差です。2つの実数 a ,b があった場合、比較に二乗平方根
 

{ \displaystyle \sqrt{(a-b)^2} }

がよく使われます。発想としては、これを複数の変数に対応して、平方根を外したものになります。平方根はとってもとらなくても順序は特に変わらないので、無駄な計算を省くために外しているものと考えると良いと思います。

こちらのサイトによくまとまっています。

www5e.biglobe.ne.jp

クロスエントロピーは、もう少し複雑で、2つデータを、それぞれのデータがもつ複雑さを表す指標(自己情報量と言います)で比較する手法です。

 

平均二乗誤差(MSE)は、回帰分析で使用されます。データ分類ではクロスエントロピーを使用します。

 

それぞれの具体的な出力をこちらの記事に書きました。

損失関数の出力サンプル / Output sample of Loss function - neuralnetな日記


 

ニューラル常微分方程式 / Neural Ordinary Differential Equations

昨年末にとても面白い論文を見つけましたので、合間の時間を見つけて読み進めています。

arxiv.org

この論文は、機械学習の分野でもっとも権威のある国際学会、NeurIPSにて2018年度に最優秀論文として評価されている論文です。
ミーハー心と興味本位で読み進めていましたが、読めば読むほど面白く読み応えのある論文です。

従来までのニューラルネットワークは、脳をモデルとして作られています。一見アナログ的ですが、数学的にはとてもデジタルな存在で、自然数個のノードを繋げてネットワークを構成しています。
近年のネットワークの層は年々ディープになり、2016年にMicrosoftから発表されたResNet(残差ネットワーク)は実に152層のノードから構成されています。

ResNetは勾配消失問題を解決し、より深い学習を可能にしましたが、一方で多くのメモリと多くの時間を要する存在になっていきました。
なにより、この152という数字には特別な意味はなく経験則的なアプローチから生み出された結果に過ぎません。
この曼荼羅のようなネットワークの状態をみて、どうにかならないものかと思った人も少なくないのではないかと思います。

ニューラル常微分方程式(Neural ODE)は、ここに対して非常に素晴らしいアプローチを提供しています。単純に数を増やすのではなく、デジタル的なネットワークの状態をアナログ化しているのです。イメージとしては今まで自然数で数えていたものを小数を含めて考えるアプローチに近いです。より正確にいうと離散化状態だったネットワークを連続的にします。

新しいネットワークの構造から比較すると、従来までのニューロンをベースに作られた概念の方が、むしろデジタルといいますか、カクカクとしたもののように見えてきます。そのぐらいネットワークの構造が滑らかに感じます。私の主観かもしれませんが、きっとそのように理解された方もいらっしゃると思います。

これによりニューラルネットワークの計算に常微分方程式(以下、ODE)ソルバーを導入することが可能になりました。最適化の手法に従来までのバックプロパゲーション(誤差逆伝搬法)ではなくアドジョイント法を用いているということも素晴らしいのですが、結果として解法ロジックを分離し、すでに研究しつくされた優秀なODEソルバーを採用すればよいという発想も素晴らしいと思います。

この構造変化の適応先はバラエティ豊かで、近年の画像処理の雄であるResNet、時系列処理の常識であるRNN(リカレントニューラルネットワーク)に加え、Glowで有名になった情報補完を可能にするNormalizing Flowへも適応できます。

メリットは以下のようなものがあります。
1) メモリが効率的
2) 最適な解法を選択可能
3) パラメータが効率的
4) スケーラブルで可逆的なNormalizing Flowを構築可能
5) 連続的な時系列モデルを実現可能

1のメモリ効率的はビジネス観点からみて有益です。ネットワークが複雑になる程、必要なメモリサイズが増えていかねばならず、高コストでした。また、現存するコンピュータの性能限界で計算できる範囲が限定されることもなくなるという意味でのメリットもあります。

2,3は最適な解法を選択可能,パラメータが効率的は機械学習システムを構築する上でのメリットです。特に2は目的に応じて最適なODEを選択できるようになるので、目的に応じて適切な機械学習手法が選択できるようになるはずです。

4,5は具体的な応用です。時系列モデルについては非常にわかりやすく、今までは自然数単位でしか解析できなかった情報が、連続化されることで任意の時間で取ることができるようになります。

以上がこの論文の概要になりますが、数学的なアプローチについても興味深いものが盛りだくさんですので、以後少しずつ書き進めていこうと思います。

TensorFlow まとめ 続き

前回の続きです。

tf.argmax

数学的なArgmaxは「その中で最大の値をとる集合」です。例えば、[1,3,2,1,3] という数列があった場合、[2番目の3,5番目の3]がArgmaxになります。tfのargmaxは少し違っていて、「最大の数字を取る値のインデックス番号」を返します。この例でいうと、2番目なので、1を返します。(インデックスなので0から始まります)

import tensorflow as tf
sess = tf.Session()

単純な数列の場合

x = tf.constant([1,2,3,2,3]) # 最大値を持つ値(ここでは3)の最初のインデックスを返すので、最初の3のインデックスである2を返す
xd = sess.run(tf.argmax(x))
print(xd)
2

行列の場合は、評価する方向を指定できます。

x = tf.constant([[1,2,3],[2,1,4]])
xd = sess.run(tf.argmax(x,axis=0)) # 行で評価する
print(xd)
xd = sess.run(tf.argmax(x,axis=1)) # 列で評価する
print(xd)
[1 0 1]
[2 2]

tf.cast

型変換をします。tf.cast(x, "float") は xをfloat型に変換します。

参考:

Tensorflow:cast

tf.reduce_mean

テンソルの次元間の要素の平均を計算します。

Tensorflow:reduce_mean

x = tf.constant([[1., 1.], [2., 2.]])
tf.reduce_mean(x)  # 1.5
tf.reduce_mean(x, 0)  # [1.5, 1.5]
tf.reduce_mean(x, 1)  # [1.,  2.]