Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
23
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@piruty

TensorFlowでn次元ベクトル間の成す角を計算する

TensorFlowのいいところは、機械学習用のツールとしてもかなり優秀であるということと、数値計算系の機能がライブラリの中に隠蔽されず外で自由に実装できることにあると思う。
ということで、表題の通りn次元ベクトル間の成す角を計算してみた

計算式にはベクトルの内積を使った方法で行う。
式で表すと、「cosx = a・b / (|a||b|)」というのでcosxを求めて、最後に角度に変換する。

まずは、2次元のベクトル同士の計算の場合を考える。
入力はあとから自由に設定したいので、変数や定数ではなくplaceholderを使う

import tensorflow as tf

a = tf.placeholder(tf.types.float32, [1, 2])
b = tf.placeholder(tf.types.float32, [1, 2])

次に、内積「a・b」を計算する。
2つのベクトルの各要素がわかっているので、「a1 b1 + a2 b2」という計算式で求められる。
高次元の入力同士の掛け算にはtf.matmul()を使用する。

a_dot_b = tf.matmul(a, b, transpose_b=True)
# transpose_b=True とすると、第2引数の次元数を入れ替える(転置)してくれる。
# 今回はベクトルbが[1, 2] -> [2, 1]の形に変わる

続いて、各ベクトルの大きさ|a|, |b|を求める。
ベクトルの大きさは各要素の2乗和のルートをとることで計算できる。
tensorflowを使って書くとこんな感じ。

a_abs = tf.sqrt(tf.matmul(a, a, transpose_b=True))
b_abs = tf.sqrt(tf.matmul(b, b, transpose_b=True))

あとは、はじめの式「a・b / (|a||b|)」に当てはめてcosxを計算する。

cos = tf.div(a_dot_b, tf.mul(a_abs, b_abs))

ここまでで、「cosx = ベクトル間の成す角によって計算されるコサイン」を求める計算式が作れた。
この計算式に具体的な2次元ベクトルを入力し、コサインを計算する。

with tf.Session() as sess:
    y = sess.run([cos], feed_dict={a: [[1, 1]], b: [[1, 0]]})[0][0]
# feed_dictに渡している辞書の各valueがcosが持っている入力ベクトルa、bに対応している。
# この計算結果は行列の値として取り出されるので、含まれる定数部分だけをyに代入する。

さて、ここまででベクトル間の成す角によって計算されるコサインが求まった。
あとは逆コサイン関数arccosを使えば成す角が求まるのだが、残念ながらtensorflowには実装されていなかった。

諦めかけた、その時!numpyにarccos関数が実装されていることを見つけた!
...ということで、最後はnumpyの関数を使ってコサインの値から角度に変換する。

import numpy as np
print np.arccos(y) * 180 / np.pi
# arccosの計算結果がラジアンなので、180をかけてパイで割ることで角度を求めている。

できた。
試しにうえで入力にしたa = [[1, 1]], b = [[1, 0]]を計算してみると、[[45.]]と表示される。
これは、このa, bの成す角は高校数学でお馴染みの1:1:√2の2等辺三角形の底辺と斜辺になっているので、その間の角度は45度で正しいことがわかる。
他にも、a = [[1, 1.73205]], b = [[1, 0]] (= 1:2:√3の直角三角形に含まれる2辺)とすると、ほぼ60度という値が返ってくる。

さて、これで2次元のベクトルについて計算できることがわかった。
次に、これを2次元だけではなく好きな次元数のベクトルに対応できるように拡張する。

ただ修正するのは、一番初めのplaceholderの次元数の設定をNoneにするだけ。

a = tf.placeholder(tf.types.float32, [1, None])
b = tf.placeholder(tf.types.float32, [1, None])
# shape(placeholderの第2引数)の要素にNoneを指定すると、どのような次元数の値が来ても対応できるようになる。

これで出来た。
あとは、入力ベクトルに好きな値を入れて計算してみる。
例えば、

with tf.Session() as sess:
    y = sess.run([cos], feed_dict={a: [[1, 1, 1]], b: [[1, 1, 0]]})[0][0]
print np.arccos(y) * 180 / np.pi
# => [35.26438522]

この入力ベクトルa, bは「正立方体の底面の対角線と中心を貫く対角線との成す角」を表している。
計算してみると、確かにこれは35〜36度のあたりになる。

このようにTensorFlowを用いれば、ベクトル計算が簡単にできるようになる。
同様に、行列についても柔軟に計算ができそうなので、そちらも試してみる。

以下、今回実装したコード。(n次元ベクトル対応)

import tensorflow as tf
import numpy as np

a = tf.placeholder(tf.types.float32, [1, None])
b = tf.placeholder(tf.types.float32, [1, None])

a_dot_b = tf.matmul(a, b, transpose_b=True)
a_abs = tf.sqrt(tf.matmul(a, a, transpose_b=True))
b_abs = tf.sqrt(tf.matmul(b, b, transpose_b=True))

cos = tf.div(a_dot_b, tf.mul(a_abs, b_abs))

with tf.Session() as sess:
    y = sess.run([cos], feed_dict={a: [[1, 1, 1]], b: [[1, 1, 0]]})[0][0]
print np.arccos(y) * 180 / np.pi
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
23
Help us understand the problem. What are the problem?