LoginSignup
1
1

【Drools】NLUを用いて非構造化データをルールエンジンで扱えるようにする

Last updated at Posted at 2023-10-01

ルールエンジン×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を呼び出し、レビューコメントの感情分析をさせてみます。

AiApiClient.java
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)を保持しています。

PriorityRuleUnit.java
public class PriorityRuleUnit implements RuleUnitData {
    
    private DataStream<Customer> customer;

    private DataStream<AnalyzedReviewComment> analyzedReviewComment;

    private SingletonStore<Priority> priority;
...

ルールの実態はDRLファイルに記載します。
一部抜粋したものを紹介しておきます。

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()メソッドを呼び出し、結果を判定に利用しています。

AnalyzedReviewComment.java
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のクエリという機能を使って、判定結果のデータを取得しています。

DecisionService.java
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が少しわかりづらいです。」という批判的なレビューをコメントをしたとします。

Main.java
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公式ドキュメント

紹介したコードの全量はこちら

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1