AzureMachineLearningを使ってSalesforceの商談をクラスタリング

Azure Machine Learningで商談を分類する学習モデルを作っておき、それをWeb APIとして公開してSalesforceから商談を投げると、お前の分類は○○だ！と返ってくるのを作る。

#AzureML側
##データセット作成
学習させるために商談データを準備。
本当はどこかの組織の本番データをぶっこぬいた方がリアルなんだけど、手が後ろに回りそうなので止めておく。
Opportunityの適当な項目をピックアップし、100件のデータを作った。
今回対象にしたのは「金額」「種別」「リードソース」「所有者.名前」の項目。
**Shift-JISだと化けるのでUTF-8**にしてcsv保存。
作ったcsvをAzure Machine Learning StudioのDETASETSにファイルアップロード。

##学習モデルを作成
EXPERIMENTSから新規に学習モデルを作成。
こんな感じになった。
![experiments-商談分類.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/44ad93e1-1665-93d4-c4dd-543ad40b6b45.jpeg)
各モジュールのアウトプット部分を右クリックするとVisualizeが選択できる。学習用と評価用に使ったデータセットの中身はこんな感じ。

![商談データセット.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/5a18c4e0-b69d-2920-8052-c27d68a39976.jpeg)


「K-Means Clustering」がクラスタリングの学習モジュールで、それを「Train Clustering Model」に繋ぎ、右上からくるサンプルデータと合わせて学習させている。
学習モジュールのプロパティにあるNumber of Centroidsで分類数を選べる。今回は3にする。

「Assign Data to Clusters」に学習モデルと評価用データを食わせ、その下の「Select Clumn in Dataset」の出力を見るとこうなっていた。
![output.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/3bc40dbf-daf4-efca-0477-c9c4b2a50269.jpeg)

**Assignments**
このデータが何に分類されたか。どの分類がどの数値に割り当てられるかは計算によって変わるらしい。
**DistancesToClusterCenter No.x**
このデータがどの分類に近かったか。数値が小さいほど近く、大きいほど遠い。

キモとなる学習モデルのモジュールは「Train Clustering Model」で、このモジュールを右クリックからTrained Modelとして登録しておく。

##WebAPIを作る
作っておいた学習モデルの画面下の方にある「SET UP WEB SERVICE」を選択する。
画面がうにょーんと動いて、勝手に無駄なモジュールを削除したり、WebServiceのinputとoutputのモジュールを作ってくれる。
![webService.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/d37f8d7a-7d3a-a6fb-c2f6-596f808c7b79.jpeg)
inputとoutputのプロパティで名前を変更することも可能。今回はデフォルトの"input1"、"output1"にしておく。
Runで実行してエラーが無いことを確認し、下のメニューの「DEPLOY WEB SERVICE」を選択すると簡単にAPIが作成される。
同時にAPI Documentが作成され、API Keyやインタフェースなどが丸わかり。おまけにコードサンプルまで生成される。ただしC#,Python,Rのみ。
惜しくもApexはなかったので対応が待たれる。
![codeSample.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/4d58e6c2-1b6d-948b-8dba-fd15fb8418e8.jpeg)

これでAzureML側のAPIは作成できたので、次はSalesforce側のセットアップに入る。

#Salesforce側
商談の標準画面にVFを埋め込んで、ボタンを押すと表示中の商談レコードの情報をAzureMLに投げてレスポンスを受け取ることにする。

##リモートサイト設定
SF側から呼び出す先としてAuzreMLのURL"https://asiasoutheast.services.azureml.net"
を追加。

##Apex
商談画面に埋め込むためOpportunityのStandardControllerを使うので、extention用として以下のApexClassを作った。

```ruby:qiita.apex
public with sharing class ClusterOpportunityCtrl {

    private final String apiKey = '[APIkeyを入れてね]';
	private Opportunity record;
	public String screenMsg {get;set;}

	public ClusterOpportunityCtrl(ApexPages.StandardController stdCtrl) {
		this.record = (Opportunity)stdCtrl.getRecord();
	}

	public PageReference cluster() {
		Opportunity record = [SELECT Amount,
		                             Type,
		                             LeadSource,
		                             Owner.Name
		                        FROM Opportunity 
		                       WHERE Id =: record.Id];
		Map<String, Object> fieldsToValue = record.getPopulatedFieldsAsMap();
		List<String> columnNames  = new List<String>();
		List<String> values = new List<String>();
		
		for(String fieldName : fieldsToValue.keySet()){
			if(fieldName == 'Id' || fieldName == 'OwnerId') {
				// requestのIFに無いので除外
				continue;
			}
			System.debug('@@@'+fieldName);
			if(fieldName == 'Owner.Name') {
				// AzureML側の項目名をOwnerにしているので置換
				fieldName = 'Owner';
			}
			columnNames.add(fieldName);
			values.add(String.valueOf(fieldsToValue.get(fieldName)));
		}
		
		String requestBody = createRequestJson(columnNames, values);
		HttpRequest req = new HttpRequest();
		req.setEndpoint('[AzureMLのAPIdocに記載されたendPointを入れてね]');
		req.setMethod('POST');

		req.setHeader('Authorization', 'Bearer ' + apiKey);
		req.setHeader('Content-Length', String.valueOf(requestBody.length()));
		req.setHeader('Content-Type', 'application/json');
		req.setBody(requestBody);

		Http h = new Http();
		HttpResponse res = h.send(req);
		System.debug(res.getBody());

		if(res.getStatusCode() == 200){
			screenMsg = res.getBody();
		} else {
			screenMsg = 'エラー' + res.getBody();
		}
		return null;
	}
	
	private String createRequestJson(List<String> columnName, List<String> oneRecVals){
		List<List<String>> values = new List<List<String>>();
		values.add(oneRecVals);

		JSONGenerator gen = JSON.createGenerator(false);
		gen.writeStartObject();
		gen.writeFieldName('Inputs');
		gen.writeStartObject();
		gen.writeFieldName('input1');
		gen.writeStartObject();
		gen.writeObjectField('ColumnNames', columnName);
		gen.writeObjectField('Values', values);
		gen.writeEndObject();
		gen.writeEndObject();
		gen.writeFieldName('GlobalParameters');
		gen.writeStartObject();
		gen.writeEndObject();
		gen.writeEndObject();

		String pretty = gen.getAsString();
		return pretty;
	}
}
```
それほど小難しいことはしていない。
AzureMLが作ってくれたAPIdocのインタフェースに従ってrequest headerをセットし、またrequest BodyのJSONを作った。

##Visualforceとページレイアウト
先ほどのApexClassを使ったVFを作り、ボタンを押すとcluster()を呼ぶようにする。
VFを作ったら商談のページレイアウトに配置したらこんな感じ。
![opportunity-before.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/17f7dac4-bdf6-74b7-f1ce-884ef7aedac4.jpeg)
##動かしてみる
Submitボタンを押すと表示している商談の情報をAzureMLが受け取り、学習モデルに従ったクラスタリングの情報を返してくれる。
![opportunity-after.jpg](https://qiita-image-store.s3.amazonaws.com/0/42504/f26b55f1-34fb-a9c4-12e6-2f3cfbe5f98f.jpeg)
このレコードではAssignmentsが"0"で返ってきているのが分かる。
表示の所はサボってresponseをそのまま表示しているが、実際はもうちょっと分かりやすく表示してあげたり、あるいはカスタム項目に「お前の区分はこれだ！」とセットしてあげることもできるだろう。

#最後に駄文
機械学習というと統計学とか難しい数式が必要になるイメージだが、今回のくらいであればよく分からんでも作れた。
ただ、本格的に分析するのであればやはり数学の知識は必要になる。
例えばクラスタリングだけでも非階層型クラスタリング、階層型クラスタリング、ノード間の距離を測るにしてもユークリッド距離やマハラノビス距離、評価尺度基準だとクラスタ内距離二乗和、Pseudo F、CCC、Pseudo T-squareなどなど（適当にググっただけで）専門的な用語がバンバン出てくる。
ようはどういう分類や予測をするのであれば、どういったアルゴリズムで学習して、また評価をするのかといった知識だ。
AzureMLで用意されたモジュールを使えばそれなりにそれっぽい数値は出るけど、どこまで妥当性があるのかよく分からない。
その辺本格的にやるのであれば学ぶ必要はあるだろう。

ちなみに今回やってみたのは商談の分類で、イメージ的には類似商談から成功アクションの提示とか、もっと考えるとレコメンドみたいなことができないかなと思ったわけだが、分類だけだとピンとこなかった。
他にもTwitterによる言語分析みたいなサンプルもあるから、Chatterをデータソースにしてユーザーを分類したりできるかもしれない。メンタルヘルス対策とか。
あるいはプロジェクト管理で見積の価格予測を出すとか。

salesforceも人工知能関連の企業を何社か買収しているけど、あくまでSalesforceIQのようなCRMとかSFAとかマーケティングとかサービスに組み込んだ形で提供されるような気がするので、Platformとして汎用性のある機械学習の機能を提供する感じはなさそう。
ってことで、その辺をカスタマイズしながら使いたいなら今回使ったAzureMLだったり、Amazon Machine Learningだったり、IBMのWatsonだったり、Google Machine Learning辺りが有効な選択肢になりそうだ。

VFも含めた全体のコードは[こちら](https://github.com/isanuki/OpportunityClusterForce)。