過去の記事でPowerCMS Xプラグインで機械学習(ベイジアンフィルタ)の実装を紹介してきましたが、弊社問い合わせフォームのメールフィルタとして導入しました。一般的なスパムフィルタと異なり独自に学習させるため、メール内容から(お客様からの)お問い合わせメールと迷惑メールを判別可能です。
学習量が増えるとフィルタ精度が上がるため、メールを読むのと同時にフィードバック(学習)できたらと考えました。今回はPowerCMS Xプラグインでフィードバック機能の実装例を紹介します。
フィードバック機能実装
前回の記事でも追加したRESTful APIのエンドポイントとして実装します。config.jsonとプラグインのphp(NaiveBayes.php)ファイルに追記します。
config.json
"api_methods": {
"v1": {
"train": {
"component": "NaiveBayes",
"method": "api_endpoint_train",
"requires_login": false,
"allowed": [
"GET",
"POST"
]
}
}
},
"callbacks": {
"naivebayes_post_save_contact": {
"contact": {
"post_save": {
"component": "NaiveBayes",
"priority": 5,
"method": "post_save_contact"
}
}
}
},
"settings": {
"naivebayes_question_id": ""
},
"cfg_template": "cfg_template.tmpl",
"cfg_system": 1,
"cfg_space": 1
メールから気軽にフィードバックできるようログイン不要にしますが、不正・重複登録を防ぐためpost_saveコールバックで(フォームの内容保存時に)tokenを発行して認証に利用します。また、プラグイン設定で学習内容(が入力される設問ID)を指定するようにします。
NaiveBayes.php
/**
* コンタクト保存時のコールバック
*
* @param object $cb コールバックオブジェクト
* @param Prototype $app アプリケーション
* @param object $obj 保存後のコンタクトオブジェクト
* @param object $original 保存前のコンタクトオブジェクト
*
* @return bool
*/
public function post_save_contact(&$cb, $app, &$obj, $original) {
$ctx = $app->ctx;
$formId = $app->param('form_id');
if (!$formId) {
return true;
}
$form = $app->db->model('form')->load((int) $formId);
$workspaceId = $form->workspace ? $form->workspace->id : 0;
// API認証用にセッション生成
$token = $app->magic();
$session = $app->db->model('session')->get_by_key([
'name' => $token,
'kind' => 'CR'
]);
$session->start($app->request_time);
$session->key('bayes_train');
$session->value($obj->id);
$session->workspace_id($workspaceId);
$sessionExpires = 86400 * 7;
$session->expires($app->request_time + $sessionExpires);
$session->save();
// メールテンプレートで利用するため変数に設定
$ctx->vars['contact_token'] = $session->name;
return true;
}
/**
* RESTful API エンドポイント train
* API経由でパラメータの値から学習
*
* @param PTRESTfulAPIv1 $app アプリケーション
*/
function api_endpoint_train($app) {
$json = [];
$category = $app->param('category');
$class = $app->param('class');
$token = $app->param('token');
$contactId = $app->param('contact_id');
$workspaceId = $app->param('workspace_id') ?? 0;
$questionId = $this->get_config_value('naivebayes_question_id', $workspaceId);
if (!$questionId) {
$json['status'] = 'error';
$json['message'] = '設問IDが指定されていません。プラグイン設定で設問IDを設定してください。';
} else if (!$token || !$contactId) {
$json['status'] = 'error';
$json['message'] = 'パラメータが指定されていません。tokenでトークン、contact_idでコンタクトIDを指定してください。';
} else if ($category && $class) {
$this->setEnablePosIds();
$this->getSmParam($app);
// セッションを取得
$session = $app->db->model('session')->load([
'name' => $token,
'key' => 'bayes_train',
'value' => $contactId
]);
$session = $session[0] ?? null;
// 学習テキストを取得
$text = '';
$questionName = 'question_' . $questionId;
$contact = $app->db->model('contact')->load([
'contact_id' => $contactId,
'workspace_id' => $workspaceId
]);
$contact = $contact[0] ?? null;
if ($contact && $contact->data) {
$contactData = json_decode($contact->data);
if ($contactData->$questionName) {
$text = trim($contactData->$questionName);
}
}
// 有効なセッションあり
if ($session && $text) {
// ベイズ文章オブジェクトを生成
$app->get_scheme_from_db('bayes_term_block');
$termBlock = $app->db->model('bayes_term_block')->new();
$termBlock->texts($text);
$termBlock->category($category);
$termBlock->class($class);
$termBlock->workspace_id($workspaceId);
$app->set_default($termBlock);
$termBlock->save();
// 学習
if ($this->train($app, $termBlock, null)) {
$json['status'] = 'success';
$json['message'] = '学習に成功しました。';
$json['text'] = $text;
$json['category'] = $category;
$json['class'] = $class;
// セッション削除(重複登録防止)
$session->remove();
} else {
$json['status'] = 'error';
$json['message'] = '学習に失敗しました。';
}
} else {
$json['status'] = 'error';
$json['message'] = '有効なセッションがありません。';
}
} else {
$json['status'] = 'error';
$json['message'] = 'パラメータが指定されていません。categoryでカテゴリ、classで分類を指定してください。';
}
$app->print_json($json);
}
APIの利用例
/api/ でAPIを設定している場合、/api/APIバージョン/ワークスペースID/train?category=フィルタカテゴリ&class=フィルタ分類&token=トークン&contact_id=コンタクトID にアクセスするとフィードバックできます。ここで先ほどpost_saveコールバックで発行したtokenを利用して認証しています。
まとめ
authenticationエンドポイントでログイン認証するのが一般的ですが、PowerCMS Xの機能を組み合わせることで1クリックで完了するフィードバック機能が実装できました。また、tokenはフィードバック完了時に削除するため、重複登録が避けられるメリットもあります。