2017年6月20日火曜日

第13回 Cognitive Servicesの活用(続)

第13回 Cognitive Servicesの活用(続)
~機械学習(Azure Machine Learning)の入門からビジネス活用へ~

 
ClassFileIo.csを開いて、以下のコードを記述します。



using System;

using System.Configuration;

using System.IO;

using System.Text;

 

// 参照の追加でSystem.Configurationを有効化する必要があります。

 

namespace CogFacePhoto

{

    class ClassFileIo

    {

        /// <summary>

        /// 画像ファイルの保存先フォルダパスを指定します。

        /// </summary>

        public static string IMG_DIR = @"C:\work\";

 

        /// <summary>

        /// 画像ファイルの保存先ファイル名称を指定します。

        /// </summary>

        private static string IMG_NAME = @"'frame'yyyyMMddHHmmssfff'.png'";

 

        /// <summary>

        /// ファイルが存在するか検査します。

        /// </summary>

        /// <param name="filePath">検査対象のファイルパス</param>

        /// <returns>ファイルが存在する場合はTrueを、それ以外の場合はFalseを返します</returns>

        public static bool FileCheck(string filePath)

        {

            try

            {

                return File.Exists(filePath);

            }

            catch (Exception)

            {

                return false;

            }

        }

 

        /// <summary>

        /// 保存先フォルダが存在するか検査し、存在しない場合は作成します。

        /// </summary>

        /// <returns>フォルダが存在するか作成できた場合はTrueを、それ以外の場合はFalseを返します</returns>

        public static bool FolderCheck()

        {

            try

            {

                if (!Directory.Exists(Path.GetDirectoryName(IMG_DIR)))

                    Directory.CreateDirectory(Path.GetDirectoryName(IMG_DIR));

 

                return Directory.Exists(Path.GetDirectoryName(IMG_DIR));

            }

            catch (Exception)

            {

                return false;

            }

        }

 

        /// <summary>

        /// 前面カメラまたは背面カメラを選択するために、configファイルから設定値を読み取ります。

        /// </summary>

        /// <returns></returns>

        public static int getCameraId()

        {

            var cameraIdString = ConfigurationManager.AppSettings["cameraId"];

 

            // 構成ファイルから読み込んだ設定値「cameraId」が数値形式ではない場合は0と見なします。

            int cameraId = 0;

            if (int.TryParse(cameraIdString, out cameraId))

            {

                return cameraId;

            }

            else

            {

                return 0;

            }

        }

 

        /// <summary>

        /// 現在の日付を基に、画像の保存先ファイルパスを生成します。

        /// </summary>

        /// <returns>ファイルパス文字列</returns>

        public static string GetImagePath()

        {

            DateTime dt = DateTime.Now;

 

            // アセンブリ名を含めます。

            var myName = Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location);

 

            return IMG_DIR + myName + dt.ToString(IMG_NAME);

        }

 

        /// <summary>

        /// テキストファイルに文字列を書き出します。

        /// </summary>

        /// <param name="filePath">出力先ファイルパス</param>

        /// <param name="contents">書き出す文字列</param>

        public static void WriteLine(string filePath, string contents)

        {

            Console.WriteLine(contents);

 

            using (StreamWriter streamWriter = new StreamWriter(filePath, true, Encoding.UTF8)) // 既存ファイルに追記します。

            {

                streamWriter.WriteLine(contents);

            }

        }

    }

}


ClassOpencv.csを開いて、以下のコードを記述します。



using OpenCvSharp;

 

namespace CogFacePhoto

{

    class ClassOpencv

    {

        /// <summary>

        /// Webカメラから画像を取得します。

        /// </summary>

        /// <returns>画像ファイルの保存先パス</returns>

        public static string GetBitmapFromCamera(string imagePath, int cameraId)

        {

            // CreateCameraCaptureの引数はカメラのインデックス番号です。

            using (var capture = Cv.CreateCameraCapture(cameraId))

            {

                IplImage frame = new IplImage();

 

                // 640px, 高さ480pxのウィンドウ「Capture」を作ります。

                double w = 640, h = 480;

                Cv.SetCaptureProperty(capture, CaptureProperty.FrameWidth, w);

                Cv.SetCaptureProperty(capture, CaptureProperty.FrameHeight, h);

 

                // 何かキーを押すまでは、Webカメラの画像を表示し続けます。

                while (Cv.WaitKey(1) == -1)

                {

                    // カメラからフレームを取得します。

                    frame = Cv.QueryFrame(capture);

 

                    // ウィンドウ「Capture」に、Webカメラのプレビューを表示します。

                    Cv.ShowImage("Capture", frame);

                }

 

                // 一度PNGファイルへ保存します。

                frame.SaveImage(imagePath);

 

                //  使い終わったWindowCapture」を破棄します。

                Cv.DestroyWindow("Capture");

 

                // 画像ファイルを保存したファイルパスを返します。

                return imagePath;

            }

        }

    }

}




2.2.3.    結果と考察



肖像権等の制約があるため、実際にWebカメラで撮影した画像や検出結果を載せることは控えたいと思います。ご自身で実装して試してみてください。


画像認識に対する精度評価としては、正答率の算出が最も容易に評価できる方法だと考えられますが、個人識別や認証など、顔検出(顔認識)を使用するアプリケーションによっては、単純な正答率よりも誤認識率(偽陽性率)や認識漏れ率(偽陰性率)を重視しなければならない状況も考えられます。こうした場合、誤認識率と認識漏れ率はトレードオフの関係にあるため、ただ値が大きければ(小さければ)良いという評価ではなく双方を許容できる閾値を探さなければならず、評価が難しくなります。また、Webカメラと相対している場合にはあまり大きな問題にはならないのですが、カメラに対する顔の角度や顔領域の隠れ具合(メガネやマスク)、照明条件など、顔検出の精度を低下させる要因はいくつもあるため、それらを網羅して精度評価を行うことは大変困難です。

検出精度を向上させるための手段として、今回のアプリケーションでは実装していませんが、Cognitive Servicesの各APIに対してリクエストを送信する前に、画像ファイルに対して事前加工処理を行うことが考えられます。Cognitive Servicesを使用している場合には学習モデルや訓練データが明確ではないため困難かもしれませんが、画像の解像度や明度・彩度を増減させたり、余計な背景領域をトリミングしたり、画像を回転させたりする手法が考えられます。

3.     Language API

 
Language APIを使うことによって、大量のトレーニングデータを自前で用意することなく、自然言語の分析や音声入力の解析、感情や重要な語句の分析といった処理を行うことができます。

3.1.     Text Analytics

3.1.1.    方針

Text Analytics APIの応用例として、Webページの文章から重要語句を抽出してみます。今回分析対象とするデータは、著作物に当てはまらない文章として法律の条文を用いることにしますが、報道記事や論文記事といった文書から重要語句を抽出する場合でも同様に適用できます。

3.1.2.    手順

 
Cognitive ServicesWebサイトを開き、サブスクリプションキーを取得しておきます。

Visual Studioを起動し、Visual C#Windowsフォームアプリケーションのプロジェクトを作成します。今回は「CogText」という名称のプロジェクトを作成しました。

ソリューションエクスプローラーのプロジェクト名直下の参照アイコンを右クリックして参照の追加メニューをクリックし、System.Runtime.Serialization, System.Webを有効化します。


以下のクラスを新規作成します。
クラス名
内容
ClassTextAnalyticsApi
Text Analytics APIへデータを渡し、言語解析を行った結果を取得する関数をまとめたクラス
ReturnedJson
Text Analytics APIから返されるJSON形式のデータを解析するために、データの中身を定義するクラス

Form1のデザイン画面を開き、以下のコントロールを配置します。デザインは適宜調整してください。
種類
Name
Buttonコントロール
button1
TextBoxコントロール
textBox1
WebBrowserコントロール
webBrowser1
 

ClassTextAnalyticsApi.csを開いて、以下のコードを記述します。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using System.Web;
 
namespace CogText
{
    /// <summary>
    /// Text Analytics APIへデータを渡し、言語解析を行った結果を取得します。
    /// </summary>
    class ClassTextAnalyticsApi
    {
        /// <summary>
        /// APIのベースURLを指定します。
        /// </summary>
        private const string BaseUrl = "https://westus.api.cognitive.microsoft.com/";
 
        /// <summary>
        /// Cognitive Services( https://www.microsoft.com/cognitive-services/en-us/subscriptions )で取得した
        /// サブスクリプション・キーを指定します。
        /// </summary>
        private const string AccountKey = "********************************";
 
        /// <summary>
        /// 言語判定の際に、判定結果として取得したい言語の最大数を指定します。既定値は1です。
        /// </summary>
        private const int NumLanguages = 1;
 
        public static async Task<string> Main(string textToAnalyze)
        {
            // 重要語句抽出に使用すべき言語を判定します。
            var lang = await MakeRequestsOfLanguages(textToAnalyze);
 
            // 抽出された重要語句のリストを取得します。
            var keyPhrasesList = await MakeRequestsOfKeyPhrases(textToAnalyze, lang);
 
            // WebBrowserコントロールに表示するためのHTML文字列を生成します。
            var htmlsource = evaluateKeyPhrase(textToAnalyze, keyPhrasesList);
            return htmlsource;
        }
 
        /// <summary>
        /// 抽出された語句と元の文章を比較し、HTML文字列を返します。
        /// </summary>
        /// <param name="message">元の文章</param>
        /// <param name="keyPhrasesLists">抽出された語句の一覧</param>
        /// <returns>結果を示すHTML文字列</returns>
        private static string evaluateKeyPhrase(string message, List<string> keyPhrasesList)
        {
            foreach (var keyPhrase in keyPhrasesList)
            {
                // 重要語句を強調するために太字にし、文字色を変更します。
                message = message.Replace(
                    keyPhrase,
                    "<b><font color=\"green\">" + keyPhrase + "</font></b>"
                    );
            }
            return "<html><body>" + message + "</body></html>";
        }
 
        /// <summary>
        /// URI形式で与えられたエンドポイントにデータを渡し、結果を戻します。
        /// </summary>
        /// <param name="client">HttpClient</param>
        /// <param name="uri">エンドポイントURL</param>
        /// <param name="byteData">パラメータ文字列のバイト配列</param>
        /// <returns></returns>
        static async Task<string> CallEndpoint(HttpClient client, string uri, byte[] byteData)
        {
            string result = string.Empty;
            using (var content = new ByteArrayContent(byteData))
            {
                content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var response = await client.PostAsync(uri, content);
                result = await response.Content.ReadAsStringAsync();
            }
 
            return result;
        }
 
        /// <summary>
        /// パラメータを組み立てます。
        /// </summary>
        /// <param name="messages">分析対象の文章</param>
        /// <param name="lang">分析対象の文章の言語</param>
        /// <returns>パラメータ文字列のバイト配列</returns>
        private static byte[] getParameters(string message, string lang)
        {
            StringBuilder messageBuilder = new StringBuilder();
            messageBuilder.Append("{\"documents\":[");
 
            #region // この部分をfor文でのループ処理に変更すると、1リクエストで複数の文章を同時に入力することができます。
            // 1つの文章は10KB、最大1000個の文章または合計1MBまでを1リクエストで送信することができます。
            messageBuilder.Append("{\"id\":\"");
            messageBuilder.Append("1"); // 文章を区別するためのIDを指定します。ループ処理の場合は"(i++).toString()"のような値に変更します。
           
            if (!string.IsNullOrEmpty(lang))
            {
                // langsなしで関数が呼ばれた場合は、languageを出力しません。
                messageBuilder.Append("\",\"language\":\"");
                messageBuilder.Append(lang);
            }
            messageBuilder.Append("\",\"text\":\"");
            messageBuilder.Append(message);
            messageBuilder.Append("\"},");
            #endregion // この部分をfor文でのループ処理に変更すると、1リクエストで複数の文章を同時に入力することができます。
 
            messageBuilder.Append("]}");
            byte[] byteData = Encoding.UTF8.GetBytes(messageBuilder.ToString());
            return byteData;
        }
 
        /// <summary>
        /// 言語の判定を非同期的に行います。
        /// </summary>
        /// <param name="messages">分析対象の文章</param>
        static async Task<string> MakeRequestsOfLanguages(string messages)
        {
            using (var client = new HttpClient())
            {
                // パラメータを組み立てます。
                byte[] byteData = getParameters(messages, null);
 
                // APIのベースURLを指定します。
                client.BaseAddress = new Uri(BaseUrl);
 
                // リクエストヘッダに認証情報を指定します。
                client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", AccountKey);
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 
                // 非同期的に言語を判定します。
                var queryString = HttpUtility.ParseQueryString(string.Empty);
                queryString["numberOfLanguagesToDetect"] = NumLanguages.ToString(CultureInfo.InvariantCulture);
                var uri = "text/analytics/v2.0/languages?" + queryString;
                string response = await CallEndpoint(client, uri, byteData);
                System.Text.RegularExpressions.MatchCollection mc =
                    System.Text.RegularExpressions.Regex.Matches(
                        response,
                        "\"iso6391Name\":\"(?<lang>[1-z][1-z])\"",
                        System.Text.RegularExpressions.RegexOptions.IgnoreCase
                        | System.Text.RegularExpressions.RegexOptions.Singleline);
 
                int i = 0;
                string[] langs = new string[mc.Count];
                foreach (System.Text.RegularExpressions.Match m in mc)
                {
                    langs[i++] = m.Groups["lang"].Value;
                }
 
                // 最も確度の高い結果を返します。
                return langs[0];
            }
        }
 
        /// <summary>
        /// 重要な語句の抽出を非同期的に行います。
        /// </summary>
        /// <param name="message">分析対象の文章</param>
        /// <param name="lang">分析対象の文章の言語</param>
        static async Task<List<string>> MakeRequestsOfKeyPhrases(string message, string lang)
        {
            List<string> keyPhrasesList = new List<string>();
 
            using (var client = new HttpClient())
            {
                // パラメータを組み立てます。
                byte[] byteData = getParameters(message, lang);
 
                // APIのベースURLを指定します。
                client.BaseAddress = new Uri(BaseUrl);
 
                // リクエストヘッダに認証情報を指定します。
                client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", AccountKey);
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
 
                // 非同期的に重要な語句の抽出を行います。
                var uri = "text/analytics/v2.0/keyPhrases";
                var response = await CallEndpoint(client, uri, byteData);
 
                // 返り値のJSON文字列をパースして、リストに格納します。
                var serializer = new DataContractJsonSerializer(typeof(ReturnedJson));
                using (var memstream = new MemoryStream(Encoding.UTF8.GetBytes(response)))
                {
                    var jsonData = (ReturnedJson)serializer.ReadObject(memstream);
                    var documents = jsonData.documents;
                    foreach (var d in documents)
                    {
                        // 文章ごとに行を追加します。
                        var keyPhrases = d.keyPhrases;
                        foreach (var kp in keyPhrases)
                        {
                            // 文章内の語句ごとにリストに格納します。
                            keyPhrasesList.Add(kp);
                        }
                    }
                }
            }
 
            // 重要語句のみを格納したリストを返します。
            return keyPhrasesList;
        }
    }
}

ReturnedJson.csを開いて、以下のコードを記述します。

using System.Collections.Generic;
using System.Runtime.Serialization;
 
/// <summary>
/// Text Analytics APIから返されるJSON文字列に対応するクラスです。
/// https://westus.dev.cognitive.microsoft.com/docs/services/TextAnalytics.V2.0/operations/56f30ceeeda5650db055a3c6 でレスポンスのJSON文字列を確認して、それに合わせて作成します。
/// </summary>
namespace CogText
{
    [DataContract]
    class ReturnedJson
    {
        [DataContract]
        public class Document
        {
            [DataMember]
            public List<string> keyPhrases { get; set; }
 
            [DataMember]
            public string id { get; set; }
        }
 
        [DataMember]
        public List<Document> documents { get; set; }
 
        [DataMember]
        public List<object> errors { get; set; }
    }
}

フォームをダブルクリックして、ロードイベントのイベントハンドラ(private void Form1_Load(object sender, EventArgs e))を作成します。メソッドの内容を以下のように記述します。

        /// <summary>
        /// フォーム読み込み時に呼び出されるメソッドです。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            // 例文をセットします。
            textBox1.Text = @"日本国民は、正当に選挙された国会における代表者を通じて行動し、われらとわれらの子孫のために、諸国民との協和による成果と、わが国全土にわたつて自由のもたらす恵沢を確保し、政府の行為によつて再び戦争の惨禍が起ることのないやうにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は、国民の厳粛な信託によるものであつて、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍の原理であり、この憲法は、かかる原理に基くものである。われらは、これに反する一切の憲法、法令及び詔勅を排除する。
 日本国民は、恒久の平和を念願し、人間相互の関係を支配する崇高な理想を深く自覚するのであつて、平和を愛する諸国民の公正と信義に信頼して、われらの安全と生存を保持しようと決意した。われらは、平和を維持し、専制と隷従、圧迫と偏狭を地上から永遠に除去しようと努めてゐる国際社会において、名誉ある地位を占めたいと思ふ。われらは、全世界の国民が、ひとしく恐怖と欠乏から免かれ、平和のうちに生存する権利を有することを確認する。
 われらは、いづれの国家も、自国のことのみに専念して他国を無視してはならないのであつて、政治道徳の法則は、普遍的なものであり、この法則に従ふことは、自国の主権を維持し、他国と対等関係に立たうとする各国の責務であると信ずる。
 日本国民は、国家の名誉にかけ、全力をあげてこの崇高な理想と目的を達成することを誓ふ。";
        }

配置したButtonコントロールをダブルクリックして、クリックイベントのイベントハンドラ(private async void button1_Click(object sender, EventArgs e))を作成します。メソッドの内容を以下のように記述します。

        /// <summary>
        /// Analyzeボタンがクリックされたときに発生するイベントです。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void button1_Click(object sender, EventArgs e)
        {
            // 解析対象の文章を取得します。
            string textToAnalyze = textBox1.Text;
 
            // 重要語句抽出を行います。
            var htmlsource = await ClassTextAnalyticsApi.Main(textToAnalyze);
 
            // 結果をWebBrowserコントロールに表示します。
            webBrowser1.DocumentText = htmlsource;
        }

ツールバーにある開始ボタンをクリックしてアプリケーションを実行します。フォーム上部のテキストボックスに、初期値の文章が表示されていることを確認します。

 
フォーム中段右側の「Analyze」ボタンをクリックして、処理を開始させます。処理が完了すると、下のWebBrowserコントロールに結果が表示されます。入力された文章のうち重要語句だと判定されたものが、緑色太文字で強調されます。

 

3.1.3.    結果


語句抽出を行った結果を以下に示します。

日本国民は、正当に選挙された国会における代表者通じ行動し、われらとわれらの子孫ために、諸国民との協和による成果と、わが国全土にわたつて自由のもたらす恵沢確保し、政府行為よつて再び戦争惨禍が起ることのないやうにすることを決意し、ここに主権が国民に存することを宣言し、この憲法を確定する。そもそも国政は、国民の厳粛な信託によるものあつて、その権威は国民に由来し、その権力は国民の代表者がこれを行使し、その福利は国民がこれを享受する。これは人類普遍原理であり、この憲法は、かかる原理に基くものである。われらは、これに反する一切憲法法令及び詔勅排除する。rn 日本国民は、恒久平和念願し、人間相互関係支配する崇高な理想を深く自覚するのであつて、平和を愛する諸国民の公正と信義信頼して、われらの安全生存保持しよう決意した。われらは、平和維持し、専制隷従圧迫偏狭地上から永遠に除去しようと努めゐる国際社会おいて、名誉ある地位を占めたい思ふ。われらは、全世界の国民が、ひとしく恐怖欠乏から免かれ、平和うち生存する権利を有することを確認する。rn われらは、いづれの国家も、自国ことのみ専念して他国無視してはならないのであつて、政治道徳法則は、普遍的なものであり、この法則従ふことは、自国主権維持し、他国と対等関係に立たうとする各国の責務であると信ずる。rn 日本国民は、国家名誉にかけ、全力あげてこの崇高な理想目的達成することを誓ふ

3.1.4.    評価


抽出結果に対する評価方法として、直感的には、入力した文章を要約していったときに残る語句が出力結果として得られていて、それらだけを眺めたときに元の文章の意味がぼんやりとわかれば精度よく抽出されていると判断できそうです。実際のところ、語句抽出自体に対する定量的な評価手法は確立されていないので、最終的には人が読んでみて判断する必要があります。

また、日本語を処理対象とする場合には、諸外国語とは異なり形態素解析が必要となるため、形態素解析自体の評価も必要となることに注意が必要です。形態素解析を行う際には、助詞や助動詞など、文書の特徴を見る上で不要な「不要語(stop word)」は除去する必要があります。語句抽出や文書検索では一般に、「名詞」「動詞」「形容詞」のみを対象とします。

まず、文書内にある単語の重要さを評価するための指標として、以下のものがあります。

元の文書に含まれるそれぞれの単語に対して値を求めて、値の高い単語が出力結果に含まれていれば、精度よく抽出されていることになります。

評価指標
内容
TF
ある単語が、その文書内にいくつ出現するかを表す値(出現頻度)
IDF
ある単語が、他のいくつの文書にも出現するかを表す値の
逆数をとったもの(希少性)
TF-IDF
TF値とIDF値の両方を考慮した値
BM25
TF-IDFに加えて文書の長さを示す値(NDL)を加味したもので、
短い文書の中で繰返し出現する単語が重要な単語

 
今回は入力となる文書を1つしか用意していないため、TF値を指標として使用してみます。

TF値を求める手順を以下に示します。

①最初に正答データを作成します。入力したのと同じ文書を用意し、形態素ごとに改行で区切っていきます。一般に形態素解析を行うためには「MeCab」や「JUMAN」のようなツールをインストールする必要がありますが、機密情報でなければWebサービスとして公開されているツールを使用すると、容易に実施できます。例として、国立国語研究所のツールで形態素解析を実施した例を以下に示します。



②語句(書字形)の列だけを取り出して昇順ソートし、同じ語句が近い行にまとまるようにします。



③それぞれの語句の出現個数を数え上げた後、重複する語句を各々1つずつ残して削除します。


CSVファイルのA列に語句が、B列に出現個数が表示されていることを確認し、データリボンにある並べ替えアイコンをクリックして、降順ソートを行います。

 
Excelに表示されている表の上位の項目を以下に示します。品詞でフィルタリングをしないと、どの文書にも頻繁に出てくるような活用語尾や形式名詞が上位に入ってしまうことが確認できると思います。そこで、先ほど説明した通り「名詞」、「動詞」、「形容詞」のみを残して残りの語句は除去します。また、形式名詞(「こと」、「もの」など)についても重要性は低いと考えられるので除去します。もちろんどの品詞を残すかは自由ですし、そもそもある単語が文脈の中でどの品詞として解釈されるかが一通りに定まらないことも多いので、必ずしも表と同一の値にする必要はありません。

語句
出現回数
13
国民
11
する
10
われら
7
こと
6
平和
4
これ
4
憲法
3
もの
3
つて
3


TF値を求めます。TF値は、文書内に含まれる単語数を、その文書に含まれるすべての単語数で割ったものです。今回の例では、抽出対象の総語句数が146となったため、各語句のTF値は以下の表のようになりました。

語句
出現回数
TF
国民
11
0.075
われら
7
0.048
平和
4
0.027
日本
3
0.021
憲法
3
0.021
理想
2
0.014
名誉
2
0.014
法則
2
0.014
普遍
2
0.014
代表
2
0.014
他国
2
0.014
生存
2
0.014
崇高
2
0.014
主権
2
0.014
2
0.014
自国
2
0.014
国家
2
0.014
原理
2
0.014
決意
2
0.014
関係
2
0.014
維持
2
0.014

ここまでの手順で、正答データから求めたTF値が上位にある形態素のランキングを作成することができました。これを利用した評価方法としては、例えばTF値がある一定以上の値を示す形態素が重要語句として抽出されたリストにどれ程の割合で存在しているかを算出したり、逆に存在していないかを算出したりすることが考えられます。

ここまでは、個々の語句の重要度に注目して精度を評価する方法を紹介してきましたが、抽出された語句の集合と元の文書の文意がどの程度合致しているかで評価を行うことも考えられます。文書同士の類似度を評価するための指標としては、共起尺度と呼ばれる以下のようなものがあります。いずれも、複数の文書間に共通して出現する単語が多いほど、大きな値をとります。

l  共起頻度

l  コサイン類似度

l  ジャッカード係数

l  ダイス係数

l  シンプソン係数


4.     Bing Search API


Bing Search APIとは、ユーザーが検索サイト(Bing.com/Search)を使用したときに得られるのと同様の検索結果を取得するためのAPIです。結果に含めるコンテンツの種類に応じて、Web検索API(Web Search API)だけでなく、画像API(Image Search API)、ニュースAPI(News Search API)、動画API(Video Search API)といったサブセットを呼び出して使用することもできます。

4.1.1.    方針

Pythonから検索ワード(クエリ)を指定してWeb Search APIを呼び出してみます。

4.1.2.    手順

はじめに、サブスクリプションキーを取得します。



続いて、PythonからCognitive Servicesを呼び出して使用するためのラッパーライブラリである「py-ms-cognitive」をインストールします。スタートメニューから「Anaconda Prompt」を開き、以下のコマンドを入力します。

pip3 install py-ms-cognitive

 


続いて、Pythonスクリプトを作成するために、スタートメニューから「Spyder」を開き、左側のエディタ欄に以下のスクリプトを入力します。

# -*- coding: utf-8 -*-
 
# パッケージ読込み ############################################################
import json, math
from py_ms_cognitive import PyMsCognitiveWebSearch
import http.client
#################################################################
 
# 定数値 #########################################################
api_key_search = "********************************"
api_key_textanalytics = "********************************"
#################################################################
 
 
 
# ここからBing Web Search API ######################################
 
# Bing Web Search APIについてはパッケージを使用して呼び出します。
## import json, math
## from py_ms_cognitive import PyMsCognitiveWebSearch
 
max_result_count = 50 # 最大検索件数を50の倍数で指定します。
search_term = "Python" # 検索対象文字列を指定します。
 
search_service = PyMsCognitiveWebSearch(api_key_search, search_term)
 
descriptions = '{"documents":['
 
count = 0
for i in range(0,math.ceil(max_result_count/50)):
    result = search_service.search(limit=50, format="json")
    #   search_service.search()は、
    #   ループ1回目は001件目から050件目を、
    #   ループ2回目は051件目から100件目を取得します。
   
    for j in result:
        # print (j.id)
        # print (j.title)
        # print (j.description)
        jsonitem = json.dumps({
            "id":j.id,
            "text":j.description
            },
            sort_keys=True, ensure_ascii=False, indent=2
            )
        descriptions = descriptions + jsonitem + u","
 
descriptions = descriptions + "]}"
 
# ここまでBing Web Search API ######################################
 
 
 
# ここからText Analytics API #######################################
# Text Analytics APIについてはモジュールが存在しなかったため
# http.clientモジュールを使用してREST APIを直接叩きます。
## import http.client
 
headers = {
    # Request headers
    "Content-Type": "application/json",
    "Ocp-Apim-Subscription-Key": api_key_textanalytics,
}
 
try:
    conn = http.client.HTTPSConnection("westus.api.cognitive.microsoft.com")
    conn.request("POST", "/text/analytics/v2.0/keyPhrases", descriptions.encode("utf-8"), headers)
    response = conn.getresponse()
    data = response.read()
    conn.close()
   
    js = json.loads(data)
    for k in js["documents"]:
        for l in k["keyPhrases"]:
            print (l)
        print ("-----")
 
except Exception as e:
    print("[Errno {0}] {1}".format(e.errno, e.strerror))
 
# ここまでText Analytics API #######################################

スクリプトを一度保存した後、Spyderのツールバーにある「ファイルを実行」をクリックします。



4.1.3.    実行結果


スクリプトの実行が終了した時点で、右下のコンソールに結果が出力されるので、確認します。検索クエリに「QES」を指定して実行した結果の例を以下に示します。

 
QUICKのシステムの開発
証券
運用
保守を担うとともに
プロ集団として
-----
万円 代表者 代表取締役社長 武村 大輔 役員 専務取締役 CIO兼システム担当兼
推進室担当 松本 邦裕 専務取締役 管理本部長兼経営企画室
資本金
商号 株式会社QES 設立
-----
品質工学会が発行する学会誌
品質工学会の活動を紹介するとともに
-----
(以下略)

 
また、同一の検索クエリを用いてBing.com/Searchで検索した例を以下に示します。



各検索結果の要約部分を見ると分かる通り、API経由でのアクセスとBing Webサイトへのアクセスで同一の結果を取得できています。


4.1.4.    結果と考察


Bing Web Search APIは、直接データ分析や予測に使用するためのサービスというわけではありませんが、データセットを作成する上で重要な役割を果たすサービスだと言えます。例えば、Cognitive Servicesの他のAPIに入力するためのテキストや画像のデータセットを作成したり、Bot Frameworkを使用してユーザーに提示する情報を収集したりするような用途が考えられます。

今回の記事では、Microsoft Cognitive Servicesの一部について、サンプルを交えながらご紹介してきました。Azure Machine Learningとは異なりアルゴリズムの部分を変更することはできませんが、データセットを用意してREST APIを呼び出すだけで、高度な分析を実施できることがお分かりいただけたかと思います。

今回ご紹介できなかったサービスの中には、Language Understanding Intelligent ServicesContent Moderatorといった、実用性が高いと考えられるものの、自分たちの手で一から実装するとコストが掛かるような分析サービスもありますので、機会があればご紹介していきたいと思います。

ブログアーカイブ

前回までのブログと今後の予定
 
機械学習(Azure Machine Learning)の入門からビジネス活用へ
 
 
 
その他以下ブログを発信中です。