社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

ユーザー類似度の計算(協調フィルタリングの前段階) python

今回は Mario さん、Peach さん、Koopa さんに5つのゲームを遊んでもらい、それぞれに5段階点数で評価をつけてもらいました。
※登場人物はフィクションです
その結果が以下です。
data.py

review={
 'Mario': {
 'OCTOPATH TRAVELER': 3.5, 'MARIO TENNIS ACE': 3.0, 'Splatoon2': 2.5, 'Legend of Zelda: Breath of the Wild': 3.5, 'Xenoblade 2': 2.5
  },
 'Peach': {
 'OCTOPATH TRAVELER': 1.5, 'MARIO TENNIS ACE': 3.5, 'Splatoon2': 5.0, 'Legend of Zelda: Breath of the Wild': 5.0, 'Xenoblade 2': 3.0
  },
 'Koopa': {
 'OCTOPATH TRAVELER': 3.0, 'MARIO TENNIS ACE': 3.5, 'Splatoon2': 3.5, 'Legend of Zelda: Breath of the Wild': 4.0, 'Xenoblade 2': 3.5
  }
}

この結果から、これらの3人が”どの位似ている”のかを求めていきます。
いわゆる 協調フィルタリングと呼ばれるものに使われたりするものです。
何らかの評価データから、ユーザー同士の類似度を計算し、似ているユーザーが好むものは、そのユーザーにも好まれやすいという仮説の元、レコメンデーションに使われたりします。

ここでは、ユーザー同士の類似度を以下で計算しました。
user-similarity.py

import sys
from math import sqrt
from data import review

def calc_similarity(member1, member2):
  games = set(review[member1].keys())
  distance_list = []

  for game in games:
    distance = pow(review[member1][game] - review[member2][game], 2)   <- (1)
    distance_list.append(distance)

  print "similarity of " + member1 + " and " + member2
  print 1/(1 + sqrt(sum(distance_list)))  <- (2)

if __name__ == '__main__':
  args = sys.argv
  calc_similarity(args[1], args[2])

1. それぞれの評価(点数)の差を2乗したものを”距離”とし、それぞれの評価が以下に離れているかを数字で表します。
2乗にしているのは、あくまでその距離を絶対値として評価するためですね。

2. それぞれのゲームに対する”距離”の和を求めてから、その平行根を求めています。
※sqrt(sum(distance_list))の部分
この値が大きいほど、”距離が離れている”= "類似していない" ということになります。
そのため、”類似している” 度合いを取るために、その逆数を取る(1から割る)ようにしています。

ちなみに 1 + sqrt(sum(distance_list)) のようにに +1 していますが、もし距離がゼロの場合、sqrt(sum(distance_list)) が 0 になります。
よって、1 / 1 + sqrt(sum(distance_list)) = 1/1 という計算式になり、類似度が"1" という結果になります。
これが完全に一致している状態です。

では、それぞれの類似度をみていきます。

$ python user-similarity.py Mario Peach
similarity of Mario and Peach
0.217129272955

$ python user-similarity.py Mario Koopa
similarity of Mario and Koopa
0.37617851153

$ python user-similarity.py Peach Koopa
similarity of Peach and Koopa
0.294298055086

ということで、Mario さんと Koopa さんが似た趣向を持っていて、Mario さんと Peach さんがあまり似ていないという結果になりました。
確かに、Mario さんと Koopa さんでは、最大で 1.0 しか評価の差はありませんが、Mario さんと Peach さんでは 2.5 の差がある評価もありました。

もし Koopa さんに何かレコメンドしたいのであれば、Mario さんが好んでいるものをレコメンドすれば、刺さりやすいだろうということになります。

何らかのデータを元に仮説を立てるという作業は面白いと思います。
あとは、それを実際に検証が出来ると良いのですが。

5つのプログラミング テクニック(参照元あり)

参照元の記事は以下です。自分なりに読み解いてみたいと思います。
https://nobugsproject.com/2017/04/07/5-great-programming-techniques-every-developer-should-know/

この手の英語の記事はたくさんあって、それぞれ違う観点で書いてあったりするから面白いです。

1. if 条件は必要とするメソッドの中に入れてしまおう

Encapsulate って直訳すると、カプセル化になると思うのですが、Java 用語のカプセル化とは違う意味で混乱しそうだったので、単純に”入れる”という表現で解釈しました。
一応、同じ例を参照元と同じですが書いてみると、
悪い例

if (value > 500) {
  applyDiscountForGreaterThan500();
} else if (value > 1000) {
  applyDiscountForGreaterThan1000();
}

良い例

applyDiscountIfValueIsGreaterThan500();
applyDiscountIfValueIsGreaterThan1000();

これ以上の解説は特に書いてありませんが、とりあえず、各メソッドは呼び出すけれど、実際に適用するかどうかは、各メソッドの中で判断させるべきという意味かと思います。
value が 1000 より大きい時に、両方のメソッドが適用されそうだけど良いのだろうか。とか変なところが気になります。

そもそも、悪い例に書いてあるコードでは、下のメソッドが呼ばれることはないので、条件式を逆にすべきなのですが、その不具合を避けるという意味も込められているのでしょうか。

2. package/class/method/variable の名前をちゃんと考えよう

これは分かり易いと思います。
package や class 名は、そのオブジェクトの役割に沿った名前にした方が良いと思いますし、method や variable についても、それが何なのか出来るだけ具体的な表現をすることで、プログラムの流れを追いやすくなります。
最初に思い浮かんだ名前を使うんじゃなくて、もっと良く考えてねって言ってますね。
これはとても同意したいです。

あいまいな名前の例

Double clientValue; // クライアントの何の値なの ?
 
void hide() // なにが隠れるの ?
 
class Historic {} // このクラスはすべての歴史なの ?
3. IDE のショートカットを使いこなそう

これは耳が少し痛いです。
IDE って便利なショートカットを備えていると思うのですが、覚えるのが面倒で、使っていないショートカットもたくさんあります。
この記事では eclipse を例に挙げていますが、使ったことがないのもあります。

もしショートカットを使わずに手動でやってしまったら、一旦その処理を元に戻して、ショートカットでやり直しなさいとのこと(覚えるために)。
確かに生産性が上がるとは思うので、なるべく意識して使うようにはしたいです。

4. 必要とする機能はメソッド化してしまおう

同じような処理を何度も書くくらいなら、メソッドに入れて使うようにすべきということと思います。

Calendar c = Calendar.getInstance();
c.setTime(new Date());
c.add(Calendar.DATE, +1);
Date userDate = c.getTime();

この例にあるような日付の計算などは、共通で何度も呼び出される可能性が高い処理だと思いますので、ユーティリティクラスにまとめてしまうのが良いだろうと思います。
もっと言うなら、ライブラリにしてしまって、アプリケーション間でも共有できるようにしても良いと思います。

5. コードレビューする文化をつくろう

自分のソースコードを他人に見られたくないとか、恥ずかしいとか思うかもしれないけど、勇気を出して他人に見てもらうべきということですね。
確かに、他人に見てもらうことで、自分が気づかなかった観点でより良いコードが書けることに気づいたり、バグが見つかったり、メリットはたくさんあるように思います。
そういったことが出来る文化や、仲間に出会えるかって、実はとても重要なことなのかもしれません。

感想

一応、すごいテクニックという見出しの記事でしたが、比較的に基本的な内容で読みやすかったです。
ただ、どれも大切な習慣であることには違いないと思います。

hbase shell non-interactive mode

hbase shell は実行したいコマンドを渡すことで、起動しなくてもコマンドの実行結果を受け取れます。

# echo "list" | hbase shell

list
TABLE
0 row(s) in 0.4830 seconds

[]

コマンドの終了コードも受け取れます。

# echo $?
-> 0

これは便利なのですが、もし実行が何らかの理由で失敗した場合も、コマンドの終了コードは 0 (成功) で返ってきます。

# echo "invalid" | hbase shell

invalid
NameError: undefined local variable or method `invalid' for #<Object:0x79b18230>
# echo $?
0

出力されたメッセージを解析しないとダメかなと思ったら、non-interactive mode と呼ばれるモードがありました。
オプション n を付けるだけです。

# echo "invalid" | hbase shell -n
ERROR NameError: undefined local variable or method `invalid' for #<Object:0x3dce6dd8>
# echo $?
1

不要なメッセージも表示されなくなったし、コマンドの終了コードは 1 (失敗) で返ってきてくれます。

やや古いドキュメントですが、このオプション付きで実行することが普通みたいですね。

ドキュメントをチェックして良かった。

hadoop namenode スタート直後は hdfs コマンドが動かない ?

Hadoop の構築スクリプトを作っています。
namenode を起動した後に、必要なディレクトhdfs 上に作成するため、以下のようにスクリプトを記述。

/usr/hdp/2.6.2.0-205/hadoop/sbin/hadoop-daemon.sh start namenode
hdfs dfs -mkdir -p /user/my_data

ところが、mkdir コマンドの実行中にエラーが発生。

mkdir: Call From hdp-rm1001.docker/172.20.0.2 to hdp-rm1001.docker:9000 failed on connection exception: java.net.ConnectException: Connection refused; For more details see:  http://wiki.apache.org/hadoop/ConnectionRefused

ConnectionRefused ? ということでしたが、同ホスト内での通信なので、ネットワークの問題ではなさそう。
ただ、この 9000 番ポートが LISTEN 状態にはなっていなかった。

少し、待ってから手動で mkdir のコマンドを試してみると、動いた!
試しに namenode 起動した後、60 秒 sleep させてみると。

/usr/hdp/2.6.2.0-205/hadoop/sbin/hadoop-daemon.sh start namenode
sleep 60
hdfs dfs -mkdir -p /user/my_data

エラー発生しない!
ということは、namenode 起動直後は hdfs とコミュニケーションを取る準備がまだ出来ていないということでしょうか。
このタイミングにおいては、9000 番ポートが LISTEN 状態になっていました。

一応、datanode については、既に起動してあったので、恐らく namenode の起動タイミングによる問題と想定。
ちなみに sleep 時間を 10 秒に短縮すると、ダメでした。
そして namenode の起動ログを見てみると、実は1分くらい起動に要している!

2018-07-09 12:35:11,995 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: STARTUP_MSG:
2018-07-09 12:36:02,526 INFO org.apache.hadoop.hdfs.server.namenode.NameNode: NameNode RPC up at: hdp-rm1001.docker/172.20.0.2:9000

namenode start 投げてから、1分以内に hdfs コマンド投げる必要はないので、困ることはないかと。
ただ、自分は ConnectionRefused というメッセージに踊らされ、しばらく混乱してしまいました。。

反省点
start コマンド投げたからと言って、起動完了したわけではない。

python 0埋め 削除する

datetime の日付0埋めを削除する方法については、検索したら解説が見つかります。
普通の文字列、例えば "0000120" を "120" にするにはどうすれば良いのだろう?

"python 0埋め 除去(削除、解除)" など色んなキーワードで検索したけどヒットしない。
除去する桁数が決まっていたら、format とか使えば出来るのかとか難しく考えていましたが。。

num = "0000120"
print int(num)

試してみたら、これで除去してくれました。
常識なこと?であるため、検索してもヒットしなかったのかもしれません。

確かに、Java でもこんな感じでいけますしね。
正直、正攻法なのかは分かってません。

Integer.valueOf("0000120")

もし文字列 "120" にしたいのなら、str(int(num)) で。

ERROR: The node /hbase-unsecure is not in ZooKeeper.

エラー内容

Docker で HBase の Container を作っているのですが、hbase shell を起動した時に発生したエラー。
Docker とは何の関係もないエラーです。

ERROR: The node /hbase-unsecure is not in ZooKeeper. It should have been written by the master. Check the value configured in 'zookeeper.znode.parent'. There could be a mismatch with the one configured in the master.

HBase のMasterサーバー(HMaster)を起動すると、zookeeper 内に /hbase-unsecure が生成されるはずなのですが、それが作られていないよというエラー。
実際に、zkCli.sh で除いてみても、このノードは生成されていませんでした。

調査

HMaster が起動してないのかも?ということで、ログを見てみると以下のエラーが出て起動できていませんでした。

HMaster: Failed to become active master SIMPLE authentication is not enabled

このエラーは見た記憶がなかったのですが、以下に回答がありました。
https://stackoverflow.com/questions/35975094/master-hmaster-failed-to-become-active-master-simple-authentication-is-not-enab?rq=1
core-site.xml と hbase-site.xml で指定しているポート番号が一致していないだろうとのこと。

確認してみると、一致してませんでしたので、9000 で揃えました。
(HDP のドキュメント見ながら設定したはずなのに。。。)

core-site.xml

<property>
    <name>fs.defaultFS</name>
    <value>hdfs://hdp-resource.docker:9000</value>
</property>

hbase-site.xml

<property>
    <name>hbase.rootdir</name>
    <value>hdfs://hdp-resource.docker:9000/apps/hbase/data</value>
</property>
確認

改めて、HMaster を起動、RegionServer も起動してます。

/usr/hdp/current/hbase-master/bin/hbase-daemon.sh start master
/usr/hdp/current/hbase-regionserver/bin/hbase-daemon.sh start regionserver

今度は hbase shell も問題なく動作しました。

hbase(main):001:0> list
TABLE                                                                                                                                                                                             
0 row(s) in 0.5440 seconds

=> []
hbase(main):002:0> create_namespace 'ns'
0 row(s) in 0.0840 seconds

hbase(main):003:0> list_namespace
NAMESPACE                                                                                                                                                                                         
default                                                                                                                                                                                           
hbase                                                                                                                                                                                             
ns                                                                                                                                                                                                
3 row(s) in 0.1300 seconds

色んなモジュールが連携していることもあって、設定ミスなど、はまりポイントが多いです。

Unity Unable to list target platforms

Unity で作成したゲームを Android の実機にビルドしようとしたら、以下のエラーが発生。

Unable to list target platforms.

f:id:blueskyarea:20180701095839p:plain

コンソールには以下のエラーが表示されているが、意味が分からない。
f:id:blueskyarea:20180701095921p:plain

確かにしばらく Unity を触っていなかったけど、以前は問題なくビルドできていた。
メッセージが言うとおり、Android SDKのパスを確認してみたけど問題なさそう。

Unity 側

f:id:blueskyarea:20180701100417p:plain

調べてみると、どうやら Android Studio のアップデートによる影響らしい。
https://answers.unity.com/questions/1320634/unable-to-list-target-platforms-when-i-try-to-buil.html

記事のとおり、tools フォルダ毎入れ替えたらビルドが通るようになりました。
そういえば、kotlin でアプリを作る時に、Android Studio のアップデートをしたような気がします。
となると、今度は kotlin のプログラミングが上手く動作しなくなっているかも。。
開発環境に依存関係を持たせたくないので、VM などして、開発環境を分けた方がいいかもしれない。