ルールエンジン×AIは結構使い古されているネタな気もしますが、Droolsでの例がなかったので書いてみようかと。
ルールエンジンの扱うデータ
NLUを用いたデータの構造化
サンプルコード
終わりに
参考
ルールエンジンの扱うデータ
ルールエンジンは、「〇〇という条件をみたすデータがあれば、✕✕という処理をする」というルールを蓄積し、入力データに対してルールに基づいた一連の処理を行うことのできる技術です。
業務ルールに関する処理を一元管理できるなどのメリットがあります。
ただ、ルールエンジンも内部で動いているのはプログラムのため、In/Outのデータは構造化されている必要があります。
ルールエンジンの保持するルールは、以下のような形式となります。
「人」データの項目「年齢」を指定の値と比較する | 「飲み物」データを生成し、項目「名前」を指定の値にする | |
---|---|---|
20歳以上ならビールが飲める | 人.年齢 >= 20 | 飲み物.名前 = "Beer" |
20歳未満ならジュースにする | 人.年齢 < 20 | 飲み物.名前 = "Orange Juice" |
これはルールエンジンが、
- 「人」データには項目「年齢」が存在すること
- 「人」データの項目「年齢」は、数値と比較できること
- 「飲み物」データには項目「名前」が存在すること
- 「飲み物」データの項目「名前」には文字列が設定できること
ということを前提に動いていることを指しています。
人間が思うままに書いたフリーフォーマットのデータを扱うことは、ルールエンジンの苦手とするところです。
NLUを用いたデータの構造化
ここでNLU(Natural Language Understanding)の出番です。
NLUは自然言語(普段我々が使用している言葉)の意味を分析するAI技術です。
人が書いたテキストデータをうけて、いくつかのカテゴリに分類分けしたり、文章を書いた人間の感情を分析したりできます。
今回の記事では、テキストデータをNLUを用いて構造化し、ルールエンジンに投入することを考えてみます。
サンプルコード
題材
あなたがSaaSを提供する会社の顧客サポート担当になったとします。
利用ユーザからポストされるレビューコメントを一つ一つ確認して、今後の開発やマーケティングに役立てることを考えています。
レビューの分析は以下のような流れで行います。
- レビューに込められた感情が、「ネガティブな感情」「ポジティブな感情」「ニュートラル(中立)」のいずれに当たるのか判断する
- レビューから導き出した感情と顧客情報をもとに、下記表のルールに基づいて優先順位を決定する
- 優先度の高いレビューから順に内容を精査し、必要に応じて関係部署(開発チーム等)への連携を行う
レビューの感情 | ユーザの契約期間 | 優先順位 | |
---|---|---|---|
長期顧客_ネガティブ | ネガティブ | 2年以上 | 5 |
長期顧客_ニュートラル | ニュートラル | 2年以上 | 2 |
長期顧客_ポジティブ | ポジティブ | 2年以上 | 3 |
短期顧客_ネガティブ | ネガティブ | 2年未満 | 3 |
短期顧客_ニュートラル | ニュートラル | 2年未満 | 1 |
短期顧客_ポジティブ | ポジティブ | 2年未満 | 2 |
サンプルなので適当なルールになっていますが、実業務ではユーザの課金状況やアプリの使用頻度などもルールの判断条件に使えるかと思います。
ここで、ユーザレビューの感情を分析するのはNLUの仕事です。
レビューはフリーフォーマットのデータなので、まずNLUによってルールエンジンが扱いやすいデータに変換します。
続く優先順位付けの手続きでは、NLUが判定した感情と顧客情報をもとに、ルールエンジンで優先順位を決定します。
アプリ構成
題材で感情分析を行う部分については、Azure AI サービスを使用します。
(かつてCognitive Servicesと呼ばれていたもののようです。2023/9/30現在公式ドキュメントでは「Azure AI サービス」となっています)
レビューの感情と顧客情報から優先順位を判断する部分は、DroolsのRule Unitという仕組みを用いて実現します。
構成としては以下の図のようになります。
Azure AI サービスを用いた感情分析
Azure AI サービスは、自然言語処理・画像検出・音声分析などのAI技術をAPIとして呼び出すことのできるAzureのサービスです。
今回は言語に関するAPIを呼び出し、レビューコメントの感情分析をさせてみます。
public class AiApiClient {
public AnalyzedReviewComment analyzeComment(String comment) {
// get token from env
String key = System.getenv("AZURE_CREDENTIAL_KEY");
String endpoint = System.getenv("AZURE_ENDPOINT");
// set up api client
AzureKeyCredential credential = new AzureKeyCredential(key);
TextAnalyticsClient textAnalyticsClient = new TextAnalyticsClientBuilder()
.credential(credential)
.endpoint(endpoint)
.buildClient();
// analyze comment
DocumentSentiment documentSentiment = textAnalyticsClient.analyzeSentiment(comment);
System.out.println("======Analyze comment======");
System.out.println("negative:" + documentSentiment.getConfidenceScores().getNegative());
System.out.println("neutral:" + documentSentiment.getConfidenceScores().getNeutral());
System.out.println("positive:" + documentSentiment.getConfidenceScores().getPositive());
System.out.println("===========================");
return new AnalyzedReviewComment(
comment
, documentSentiment.getConfidenceScores().getNegative()
, documentSentiment.getConfidenceScores().getNeutral()
, documentSentiment.getConfidenceScores().getPositive()
);
}
}
上記コードでは、Azure SDKを用いてAzure AIサービスのAPIを呼び出しています。
AzureのAPIからは、送信したテキストに含まれる「ネガティブ」「中立」「ポジティブ」の感情がどの程度かを数値で受け取ることができます。
この数値をもとに、ルールエンジンのInputとなるオブジェクトを生成しています。
SDKを用いてAzure AIサービスを利用するには、
- Azure アカウントの作成(無料アカウントで大丈夫です)
- Azure AIサービスの作成
- Azure AIサービスのクレデンシャルキーを取得し、アプリでロードできるようにする(今回は環境変数に設定しています)
が必要です。
詳細はAzureの公式ドキュメントをご参照ください。
当たり前ですが、クレデンシャルキーなどの機密情報は、コードにべた書きしないようにご注意ください!!
ルールエンジンで優先順位を判定
ここからは、RuleUnitを実装していきます。
RuleUnitについてはDroolsの公式ドキュメント、もしくは過去記事もご参照ください。
以下がRuleUnitのクラス(一部)です。
判定に必要な顧客情報(customer
)、感情分析されたレビューコメント(analyzedReviewComment
)、ルールの判定結果である優先順位(priority
)を保持しています。
public class PriorityRuleUnit implements RuleUnitData {
private DataStream<Customer> customer;
private DataStream<AnalyzedReviewComment> analyzedReviewComment;
private SingletonStore<Priority> priority;
...
ルールの実態はDRLファイルに記載します。
一部抜粋したものを紹介しておきます。
package org.example.azure.negaposirule.ruleunit;
unit PriorityRuleUnit;
rule "長期顧客_ネガティブ"
when
/customer[ contractPeriod >= 2 ];
/analyzedReviewComment[ sentiment().equals("negative") ];
then
priority.set(new Priority(5));
end
rule "長期顧客_ニュートラル"
when
/customer[ contractPeriod >= 2 ];
/analyzedReviewComment[ sentiment().equals("neutral") ];
then
priority.set(new Priority(2));
end
rule "長期顧客_ポジティブ"
when
/customer[ contractPeriod >= 2 ];
/analyzedReviewComment[ sentiment().equals("positive") ];
then
priority.set(new Priority(3));
end
...
/analyzedReviewComment[ sentiment().equals("negative") ];
の部分では、AnalyzedReviewCommentインスタンスのsentiment()メソッドを呼び出し、結果を判定に利用しています。
public record AnalyzedReviewComment(
String comment
, double negative
, double neutral
, double positive) {
public String sentiment() {
// Azure AIサービスで算出した感情分析の数値で最も点数が高いものを算出し、
// negative, neutral, positiveのいずれかの結果を返す
var sentimentMap = new HashMap<String, Double>();
sentimentMap.put("negative", this.negative);
sentimentMap.put("neutral", this.neutral);
sentimentMap.put("positive", this.positive);
String maxKey = sentimentMap.entrySet().stream()
.max((entry1, entry2) -> entry1.getValue().compareTo(entry2.getValue()))
.get()
.getKey();
return maxKey;
}
}
ルール呼び出し部分のコードは以下です。
PriorityRuleUnitインスタンスを生成し、判定に必要なデータを投入しています。
RuleUnitInstanceのfire()メソッドで、ルールを実行させています。
最後にRuleUnitのクエリという機能を使って、判定結果のデータを取得しています。
public class DecisionService {
public Priority decidePriority(Customer customer, AnalyzedReviewComment comment) {
PriorityRuleUnit priorityRuleUnit = new PriorityRuleUnit();
RuleUnitInstance<PriorityRuleUnit> instance = RuleUnitProvider.get().createRuleUnitInstance(priorityRuleUnit);
priorityRuleUnit.getCustomer().append(customer);
priorityRuleUnit.getAnalyzedReviewComment().append(comment);
// execute rule
instance.fire();
// get result by query
Priority priority = null;
var queryResult = instance.executeQuery("FindPriority").toList();
if (queryResult.size() == 1) {
priority = (Priority) queryResult.get(0).get("$p");
}
instance.close();
return priority;
}
}
メインロジック
Mainクラスは以下です。
契約期間が2年(長期顧客)の太郎さんが「UIが少しわかりづらいです。」という批判的なレビューをコメントをしたとします。
public class Main {
public static void main(String[] args) {
var customer = new Customer("Taro", 2);
// call API to analyze comment
String reviewComment = "UIが少しわかりづらいです。" ;
AiApiClient ac = new AiApiClient();
AnalyzedReviewComment analyzedReviewComment = ac.analyzeComment(reviewComment);
// decide the priority
DecisionService ds = new DecisionService();
var priority = ds.decidePriority(customer, analyzedReviewComment);
System.out.println(priority);
}
}
実行結果は以下のようになります。
======Analyze comment======
negative:0.95
neutral:0.05
positive:0.0
===========================
Priority[value=5]
negative:0.95とあるように、当該レビューの感情はネガティブなものと判定されています。
優先度は、長期顧客のネガティブなコメントなので、5
と判定されています。
終わりに
今回はNLUとルールエンジンとを組み合わせた題材を紹介しました。
自然言語処理以外にも、他のAI技術とも組み合わせることでルールエンジンがさらに便利になっていきそうと感じました。
本当は今話題のOpenAI API(GPT)での題材にしようと思っていたのですが、無料クレジットの有効期限がきれてしまい。。
ボツになったプロジェクト
参考
Azure AI Language公式ドキュメント
Drools公式ドキュメント
紹介したコードの全量はこちら