こんにちは、エンジニアの建三です。今までのブログでは主にノマドとノマドクラウドについてお話してきました。最近イタンジのもう一つの主要プロダクトぶっかくんのデータ解析をしたのでそのお話をします。
仲介会社と管理会社
不動産会社は仲介会社と管理会社に大きく分かれます。管理会社は大家さんの所有する物件を管理するのが主な役割で、仲介会社はお客様の希望に合わせて物件を紹介するのが主な役割です。
仲介会社の業務の一つに物件確認(物確)というものがあります。これはお客様が問い合わせてきた物件が空室かどうかを管理会社に確認する作業です。基本的には物確は電話で行われます。
ぶっかくんとは?
管理会社には沢山の物確電話が掛かってくるので、規模が拡大すればその分対応工数も拡大します。その課題を解決したのがぶっかくんです。ぶっかくんは自動音声で物確の対応をしてくれるシステムです。ぶっかくんのデータベースにはどの仲介会社がどの管理会社に何回電話をしたかが記録されています。
データの使い道
今回はこのデータを使って、それぞれの管理会社と相性の良い仲介会社を見つけ出すことにしました。それが何の役に立つの?と思うかもしれませんね。管理会社は自社の物件を仲介会社に売り込みに行っています。仲介会社は宅建事業者なのでどの管理会社の物件もATBBやレインズ、独自HPといったデータベースからアクセス出来るんですが、やはり馴染みのある管理会社の物件をお客様に紹介する傾向があります。
しかし仲介会社は沢山存在するので、全ての会社へローラー営業するのは効率的ではありません。効率的に営業するには自社と相性の良さそうな仲介会社さんを優先的に営業した方がいいですよね。
Standardization
アルゴリズムを作る前に、どの仲介会社と相性が良いか自分達の感覚で考えてみましょう。以下がシンプルな例です。この表では管理会社1に対して、仲介会社1は40回物確をして、仲介会社2は20回物確をしたということになります。
受電数 | 仲介会社1 | 仲介会社2 |
---|---|---|
管理会社1 | 40 | 20 |
管理会社1にとってどちらの仲介会社が相性が良いでしょうか? パッと思いつくのが、仲介会社1の方が物確を沢山してるので相性が良いということです。
では他の管理会社のデータも加えてみましょう。
受電数 | 仲介会社1 | 仲介会社2 |
---|---|---|
管理会社1 | 40 | 20 |
管理会社2 | 60 | 10 |
まだ仲介会社1の方が相性が良いと言えますか?仲介会社1は管理会社2の方に沢山物確してますよね。一方で仲介会社2は管理会社1の方に沢山物確をしてます。よって感覚的に仲介会社2の方が管理会社1と相性が良いことが分かります。つまり重要なのは受電回数の絶対値ではなく、他の管理会社との相対値になります。
恋愛に例えてみましょう。例えば春美ちゃんは毎日学校で自分にあいさつしてくれて、夏美ちゃんは週に一回くらいしかあいさつしてくれません。これだけだと春美ちゃんが自分のことを好きなのか?と思い込んでしまいそうですが、実は春美ちゃんは自分だけじゃなくて皆にフレンドリーだったんですねー。それに比べ夏美ちゃんは、自分以外の男子には全くあいさつしないんです。そしたら春美ちゃんより夏美ちゃんの方が自分のことが好きである可能性が高いと言えますよね!
とまぁ僕なりに一生懸命説明したのですが理解が深まりましたか?この感覚を数式にするにはどうすればいいでしょうか?実はStandardizationという手法を使えば出来ます。scikit learnに入ってるんですが、numpyで簡単に書けます。
import numpy as np
def scale(X):
new = X - np.mean(X, axis=0)
return new / np.std(new, axis=0)
a = np.array([[40,20],[60,10]])
print(scale(a))
#Output
[[-1. 1.]
[ 1. -1.]]
スコア | 仲介会社1 | 仲介会社2 |
---|---|---|
管理会社1 | −1 | 1 |
管理会社2 | 1 | −1 |
数値が高いほど相性が良いということになります。ちゃんと仲介会社2の方が管理会社1にとってスコアが高くなってますね!
Normalization
相対値が重要なのは仲介会社だけではありません。以下の例を見てみましょう。管理会社2の受電数が前回の10倍になっています。
受電数 | 仲介会社1 | 仲介会社2 |
---|---|---|
管理会社1 | 40 | 20 |
管理会社2 | 600 | 100 |
これを先ほどの数式に入れるとこうなります。
スコア | 仲介会社1 | 仲介会社2 |
---|---|---|
管理会社1 | −1 | −1 |
管理会社2 | 1 | 1 |
でも本当はまだ仲介会社2のスコアの方が高くなるべきですよね?管理会社の規模が大きいだけでスコアが高くなるのはおかしいので、これを直す必要があります。これはNormalizationという手法で解決出来ます。これもscikit learnにありますが、numpyで書きました。
def normalize(X, norm='l2', axis=1):
"""scales individual samples to have unit norm."""
if axis == 0:
X = X.T
if norm == 'l1':
norms = np.abs(X).sum(axis=1)
else:
norms = np.sqrt((X * X).sum(axis=1))
new = X / norms[:, np.newaxis]
if axis == 0:
new = new.T
return new
print(normalize(np.array([[40,20],[60,10]])))
print(normalize(np.array([[40,20],[600,100]])))
#Output
[[ 0.89442719 0.4472136 ]
[ 0.98639392 0.16439899]]
[[ 0.89442719 0.4472136 ]
[ 0.98639392 0.16439899]]
ご覧の通り、ノーマライズした後のarrayはどちらも同じ数値になりました。
ではこれらを使って仲介会社のスコアを出してみましょう。今回はもうちょっと複雑な例を使います。
受電数 | 仲介会社1 | 仲介会社2 | 仲介会社3 |
---|---|---|---|
管理会社1 | 60 | 40 | 5 |
管理会社2 | 6 | 5 | 0 |
管理会社3 | 40 | 30 | 0 |
まずノーマライズした後にスケールします。
a = np.array([[60,40,5],[6,5,0],[40,30,0]])
print(scale(normalize(a)))
#Output
[[ 1.21322356 -1.25389778 1.41421356]
[-1.23594988 1.19334563 -0.70710678]
[ 0.02272631 0.06055214 -0.70710678]]
スコア | 仲介会社1 | 仲介会社2 | 仲介会社3 |
---|---|---|---|
管理会社1 | 1.21322356 | -1.25389778 | 1.41421356 |
管理会社2 | -1.23594988 | 1.19334563 | -0.70710678 |
管理会社3 | 0.02272631 | 0.06055214 | -0.70710678 |
これをスコア毎に並べると以下のようになります。
ランキング | 1位 | 2位 | 3位 |
---|---|---|---|
管理会社1 | 仲介会社3 | 仲介会社1 | 仲介会社2 |
管理会社2 | 仲介会社2 | 仲介会社3 | 仲介会社1 |
管理会社3 | 仲介会社2 | 仲介会社1 | 仲介会社3 |
管理会社1の1位が仲介会社3というのは驚きかもしれません。これは仲介会社3が管理会社1にしか物確してないためです。同様に、管理会社2は仲介会社3から1回も受電していないにも関わらず、仲介会社3の方が仲介会社1よりもスコアが高くなってます。ある程度受電数も考慮すべきなのでそこは課題です。今は総物確数が5未満の仲介会社を除いて対応していますが、もうちょっと良い方法があれば試したいです。
スコアを分かり易くする
これで管理会社はどの仲介会社に営業に行けばいいのか分かるようになりました。ただ問題が一つあります。スコアは仲介会社との相性を測る役割は果たしていますが、数字をパッと見ても何を意味してるのか分かりません。相性が良ければスコアはプラスで悪ければマイナスというのは分かり易いのですが、100点中何点のように表せれば尚良いですよね。
これの解決方法は色々試したのですが、シンプルな方法に落ち着きました。全体のスコアの最大値で全スコアを割るだけです。100点にしたいので割る前に100をかけてます。10段階にしたければ10をかけるだけです。
a = np.array([[60,40,5],[6,5,0],[40,30,0]])
b = scale(normalize(a)))
print(b * 100 / np.max(b)
#Output
[[ 85.78786086 -88.66396207 100. ]
[-87.39485398 84.38227907 -50. ]
[ 1.60699311 4.28168301 -50. ]]
これで一番高いスコア(管理会社, 仲介会社3)が100となり、他のスコアも同じようにスケールされてます。
まとめ
今までのタスクは既存の機械学習のフレームワークに沿って考えていたので、関係のありそうな論文を何枚か参照しながら進めていました。それに比べ今回のタスクはそのフレームワークに当てはまらなかったためリサーチのしようがなく、ひたすら自分の感覚を数学に変換していく作業となりました。
StandardizationもNormalizationも本来は機械学習を使う上での前処理に使われますが、アイディア次第ではそれだけで面白いことが出来ることに気づき、良い学びとなりました。昔scikit learnのライブラリをひたすらリバースエンジニアリングしてた時期があり、その時StandardizationとNormalizationを勉強したのが今回役立ちました。
一見役に立たなそうな数学のコンセプトも、勉強しておくと"Think outside the box"が出来るのかなと思います。今はディープラーニングが流行ってますが、やはり機械学習の基礎となる数学は疎かに出来ないなと改めて実感しました。
ということで、イタンジでは"Think outside the box"が好きなエンジニアを募集してます!