目次


Bluemix、Watson Discovery、Cloudant を利用して、他のアプリを分析するモバイル・アプリを作成する

Comments

データ・アナリティクスの詳細を知りたいと思った私は、最近リリースされた Watson Discovery サービスを利用してみることにしました。このサービスを利用して、iTunes App Store から無料で入手できるアプリのうち、最も人気の高い上位 10 個のアプリに寄せられたレビューから、何かしら価値のある情報を迅速に引き出せるかどうかを調べたかったのです。アプリのレビューを抽出してクロールする手段としては、単純さと速さを理由に Python を選びました。また、検出したデータのすべてを表示するモバイル・アプリを作成する必要があったため、情報を表示する手段として Swift を使用しました。この App Insights というモバイル・アプリは、IBM Bluemix をベースに稼働し、レビューを分析するためにWatson Discovery サービスを、アプリの詳細を保管するために Cloudant を利用します。

App Insights アプリの概要を示すスクリーンショット
App Insights アプリの概要を示すスクリーンショット

このアプリには、以下の機能が備わっています。

  • 一定の期間にわたるセンチメント: Watson Discovery が各レビューの際に検出する、特定のターゲット・フレーズに基づくセンチメント。
  • キーワード: 各レビューから抽出する重要なトピック。
  • 機会: Discovery サービスに対するクエリーから返されるレビュー。それぞれのレビューが、アプリに実装すべき機能や、変更または削除すべき機能を知る機会になります。

前提条件

このアプリを作成するには、以下の前提条件が揃っている必要があります。

バックエンドの構成をセットアップする

App Store の RSS フィードから取得したレビューを抽出、解析、アップロードするには、Python を使用します。Python を使用して、データから不要なものを取り除いてから、アプリの詳細は Cloudant に、アプリのレビューは Discovery サービスにアップロードします。

  1. 使用する Python スクリプトに必要となる以下のサード・パーティー依存関係をインストールします。
    pip install --upgrade watson-developer-cloud
    pip install cloudant
    pip install -U python-dotenv
    pip install beautifulsoup4
    pip install lxml
    pip install cssselect
  2. Watson Discovery と Cloudant の資格情報を Scripts/.env ファイルに挿入します。
    DISCOVERY_USERNAME="YOUR-DISCOVERY-USERNAME-HERE"
    DISCOVERY_PASSWORD="YOUR-DISCOVERY-PASSWORD-HERE"
    DISCOVERY_VERSION="2016-12-15"
    COLLECTION_NAME="EXAMPLE-COLLECTION-NAME"
    CLOUDANT_USERNAME="YOUR-CLOUDANT-USERNAME-HERE"
    CLOUDANT_PASSWORD="YOUR-CLOUDANT-PASSWORD-HERE"
    DATABASE_NAME="EXAMPLE-DB-NAME"

    Watson Discovery コレクションと Cloudant データベースに任意の名前を付けます。Discovery コレクションまたは Cloudant データベース・インスタンスが存在しなければ、Python スクリプトによって該当するコレクションまたはデータ・インスタンスが自動的に作成されます (各サービスの資格情報が正しく入力されていることが前提となります)。

  3. 以下の 2 つのスクリプトを記載順に実行して、Cloudant および Discovery に情報をロードします。
    python Scripts/ingest_reviews.py
    python Scripts/extract_upload_app_details.py

    ingest_reviews.py スクリプトは上位 10 個の無料アプリのレビューをクロールするために、App Store の RSS フィードからレビューを抽出します。その後、レビューを Discovery サービスに取り込んでデータ・エンリッチメントを適用します。extract_upload_app_details.py スクリプトは、アプリの一般的な詳細情報を抽出します。例えば、アプリの名前、説明、URL、レビューの数、評価などの情報です。アプリから呼び出す Cloudant 内には、これらの情報を保管します。

フロントエンドの構成をセットアップする

  1. Carthage を使用して、サード・パーティー依存関係をインストールします。以下のコマンドを初めて実行するときは、実行が完了するまでに最大 20 分かかる場合があることに注意してください。
    cd app-insights-iOS
    cd carthage update --platform iOS

    上記のコマンドによって、Carthage からすべての依存関係が取り込まれます。具体的には、Graphs、Watson Developer Cloud Swift SDK、および SwiftyJSON ライブラリーを取り込んで使用します。

  2. Discovery および Cloudant のユーザー名とパスワードを app-insights-iOS/app-insights/Configuration.swift ファイルに挿入します。この Configuration ファイルには、Python スクリプトを実行したときに作成したのと同じ Discovery コレクション名と Cloudant データベース名を挿入してください。
    import Foundation
    public struct Credentials {
    static let DiscoveryUsername = "your-discovery-username-here"
    static let DiscoveryPassword = "your-discovery-password-here"
    static let EnvironmentName = "your-discovery-environment-name"
    static let CollectionName = "your-collection-name"
    static let DiscoveryVersion = "2017-02-14"
      static let CloudantUsername = "your-cloudant-username-here"
      static let CloudantPassword = "your-cloudant-password-here"
      static let AppsDBName = "your-cloudant-database-name"
    }

実行中のアプリを表示するには、Xcode の iPhone シミュレーター内で「Build and run (ビルドして実行)」をクリックします。

アーキテクチャーの説明

Python スクリプトによって、App Store から抽出された情報の静的コピーをセットアップします。iTunes のトップ・チャート・ページに掲載される上位アプリそれぞれの RSS フィードにアクセスして、各アプリのレビューを取得します。scriptextract_reviews.py スクリプトは、取得した各レビューから不要なデータを削除した後、Discovery サービス内に作成されたコレクションに、すべてのデータを 1 つのドキュメントとしてアップロードします。このフォーマットから複数の JSON ドキュメント (以下を参照) に XML データを変換するために必要となる関連情報を抽出するには、Python の ElementTree ライブラリーを使用します。

{
"review": "It's a fun park management game. Just at the moment when you found yourself enjoying the game, poof, all your progress was gone and Atari reassigned you a new player ID.",
"review_id": 1553629663,
"version": "1.07",
"updated": "2017-02-27T01:41:00-07:00",
"rating": 2,
"app_name": "RollerCoaster Tycoon® Touch™v2",
"title": "Fun game till all progress is lost"
}
レビューを抽出する仕組みの概要を示す図
レビューを抽出する仕組みの概要を示す図

extract_reviews.py スクリプトの実行が完了した後、Discovery サービスがどのアプリを取り込んだかを記録する ingested_apps.txt テキスト・ファイルを作成します。すべてのアプリに RSS フィードがあるわけではないので、このテキスト・ファイルは重要です。アプリに RSS フィードがなければ、そのアプリに関して抽出するレビューもありません。したがって、このテキスト・ファイルを基に、RSS フィードのないアプリをスキップして、次に人気の高いアプリのレビューを取得するというわけです。extract_upload_app_details.py ファイルはこのテキスト・ファイルを取り、各アプリの詳細を取得するために、App Store に対してさらに URL リクエストを送信します。そして Cloudant Python SDK を使用して、以下の JSON をドキュメントとして Cloudant にアップロードします。

{
"name": "Bitmoji - Your Personal Emoji",
"description": "Bitmoji is your own personal emoji. Create an expressive cartoon avatar. Choose from a huge library of stickers all featuring YOU. Use Bitmoji in Snapchat, iMessage and wherever else you chat. Using Bitmoji in Snapchat unlocks friendmoji 2-person bitmojis featuring you and your friends!",
"imageURL": "http://is4.mzstatic.com/image/thumb/Purple122/v4/2e/53/b3/2e53b39c-5101-94df-5cdf-88e331bc594e/source/1200x630bb.jpg",
"category": "Utilities",
rating: 4.5,
"numberOfReviews": 36907,
"topKeyword": "keyboard",
"numberOfTurnarounds": 16,
"appSentimentValue": -0.079542891986754932
}
アプリの概要を示す画像
アプリの概要を示す画像

コードを調べると、topKeywordnumberOfTurnarounds、および appSentimentValue フィールドは App Store 自体から取得されるのではなく、Discovery サービスから取得されることがわかります。このようにしているわけは、作成するアプリのホーム・ページ上では、これらのデータを、抽出された各アプリのサマリーとして使用するためです。アプリのロード時に URL リクエストを Discovery サービスと Cloudant サービスの両方に送信してリクエストの量を 2 倍にするのではなく、サマリーを Cloudant 内に保管して、ユーザーが各セルをクリックするとそのサマリーを確認できるようにします。

このアプリを作成する上で最も難しかった部分は、抽出されたデータに興味深い傾向やコンテンツが含まれているかどうかを調べることでした。ありがたいことに、Discovery サービスに用意されている素敵なツールでは、作成されたコレクションを表示するだけでなく、構成、クエリー集約をいろいろとカスタマイズすることもできます。

iOS クライアント・サイドのコード

バックエンドはセットアップできたので、次は、このアプリを作成するために使用したクエリーと集約について詳しく説明します。Carthage 内に Watson Developer Cloud 依存関係を含めることで、Watson 向けに Swift SDK を利用して、この SDK の Discovery サービスを使用できるようにしました。

app-insights-iOS/app-insights/DiscoveryManager.swift ファイルの内容を見ていきましょう。これは、ユーザーに表示したい主要なデータをアプリで取得できるようにするためのファイルです。Configuration/Credentials.swift ファイルに入力した資格情報にアクセスするための定数を定義してから、このクラスに対して 1 つのインスタンスしか存在できないよう、Discovery シングルトンを作成し、後はシングルトン・デザイン・パターンに従います。シングルトン・パターンの背後にある考えは、インスタンス化と DiscoveryManager へのアクセスの両方を制御することです。なぜこの手法を採用したのかというと、アプリに必要な DiscoveryManager のインスタンスは 1 つだけであり、使用するビュー・コントローラーのすべてで、その唯一のインスタンスのクエリー・メソッドにアクセスして Discovery サービスから受信したデータを操作するからです。

余談として、シングルトンを使用する背景には、さまざまな考え方があります。Apple ではごく当たり前のようにシングルトンを使用しています。けれども、シングルトンはグローバルな状態に作用する可能性があり、オブジェクト指向プログラミングの入門書では、グローバル変数は使用すべきでないとしています (その理由を思い出すには、このリンク先のページを読んでください)。グローバル変数によって、オブジェクトのインターフェースには明示的に取り込まれていない共通の知識に依存して、オブジェクトの定義とオブジェクトの使用が結び付けられるという、暗黙的な結合が作り出されることがあります。別の言葉に置き換えると、隠れた依存関係が存在する可能性があるということです。さらに、シングルトンは独自のライフサイクルを管理することから、スコーピングの問題も発生する可能性もあります。ただし、現在のアプリは DiscoveryManager と CloudantManager にアクセスする必要があります。この両方が提供するデータに、アプリ全体にわたってアクセスしなければならないためです。

Swift SDK の Discovery サービスをどのように使用するかと言うと、まず、DiscoveryManager 内で DiscoveryV1 をインポートして、SDK の Discovery メソッドにアクセスできるようにします。次に、setupDiscovery メソッド内で Discoveryobject をインスタンス化します。このメソッドは、アプリのホーム・ページを表示するときにシングルトン・インスタンスをセットアップするために呼び出されて、クラスを遅延してインスタンス化します。

func setupDiscovery(onSuccess success: @escaping () ->Void, onFailure failure: @escaping (DiscoveryErrors) ->Void) {
// Instantiate a Discovery instance using our Credentials.
discovery = Discovery(
username: Credentials.DiscoveryUsername,
password: Credentials.DiscoveryPassword,
version: kDiscoveryVersion)

Discovery インスタンスをインスタンス化した後は、データのコレクションを保存するための環境を取得する必要があります。そこで、作成した環境の名前を使用して getEnvironments メソッドを呼び出し、メソッドから返される、環境名に対応する environmentIDfileprivate var として保管します。これで、DiscoveryManager 全体にわたってこの変数にアクセスできるようになります。なぜこうする必要があるかというと、すべてのメソッドはこの環境 ID に依存するからです。この変数にアクセスできない場合は、エラーを返すようにします。

// Fetch environment
discovery.getEnvironments(withName: kEnvironmentName,
failure: { error in
print("Error - getEnvironments: (error)")
failure(.other(error.localizedDescription))},
success: { environments in
print("Environments: (environments)")
if let environmentID = environments.first?.environmentID {
self.environmentID = environmentID
// Fetch collection
self.getCollectionID(onSuccess: success, onFailure: failure)
} else {
failure(DiscoveryErrors.noEnvironments)
}
})
}

環境に関連付けられたデータ・コレクションを検出するには、success コールバック内で ongetCollectionID 呼び出します。このメソッド呼び出しは有効な環境 ID を設定することが前提となるため、environmentID 変数をサービスから返された値に設定してから、getCollectionID メソッドを呼び出します。アプリをロードした後、コレクションのセルをクリックすると、AppDetailsview が表示され、そこから GraphViewController にスムーズに移行できるようになります。このビュー・コントローラーはロードされる時点で DiscoveryManager のシングルトン・インスタンスを使って queryForSentiment メソッドを呼び出すことで、Discovery サービスに対してセンチメント・データのクエリーを実行します。

func queryForSentiment(appName: String, onSuccess success: @escaping ([GraphSentiment]) ->Void, onFailure failure: @escaping (DiscoveryErrors) ->Void) {
discovery.queryDocumentsInCollection(
withEnvironmentID: environmentID,
withCollectionID: collectionID,
withAggregation: "filter(app_name:(appName)).timeslice(updated,1day).term(review_enriched.docSentiment.type)",
return: "aggregations",
failure: { error in
failure(.other(error.localizedDescription))},
success: { response in
if let responseData = try? JSONSerialization.data(withJSONObject: response.json, options: []) {
var graphSentiments =GraphSentiment
let json = JSON(data: responseData)
                // Unwrap first aggregation response returned by Discovery service. Unwrapping over response index and corresponding value.
                guard let (_, firstAggregation) = json["aggregations"].first else {
                    failure(DiscoveryErrors.unexpectedJSON)
                    return
                }
                // Safely unwrap second part of aggregation json response returned by Discovery service.
                guard let (_, secondAggregation) = firstAggregation["aggregations"].first else {
                    failure(DiscoveryErrors.unexpectedJSON)
                    return
                }

                let timeSlice = secondAggregation["results"]
                for (_, timeSliceInterval) in timeSlice {
                    let time = timeSliceInterval["key_as_string"].stringValue
                    var positiveSentiment = Sentiment(type: "positive", matchingResults: 0)
                    var negativeSentiment = Sentiment(type: "negative", matchingResults: 0)

                    // Iterating over array's index and its corresponding value
                    guard let (_, timeSliceIntervalResults) = timeSliceInterval["aggregations"].first else {
                        failure(DiscoveryErrors.unexpectedJSON)
                        return
                    }
                    for (_, sentiment) in timeSliceIntervalResults["results"] {
                        guard let matchingResults = Int(sentiment["matching_results"].stringValue) else {
                            failure(DiscoveryErrors.stringToIntFailed)
                            break
                        }
                        if sentiment["key"] == "positive" {
                            positiveSentiment.matchingResults = matchingResults
                        }
                        if sentiment["key"] == "negative" {
                            negativeSentiment.matchingResults = matchingResults
                        }
                    }
                    let graphSentiment = GraphSentiment(date: time, positiveSentiment: positiveSentiment, negativeSentiment: negativeSentiment)
                    graphSentiments.append(graphSentiment)
                    success(graphSentiments)
                }

            }
    })
}

この queryForSentiment メソッドの内容を細かく見ていきましょう。

func queryForSentiment(appName: String, onSuccess success: @escaping ([GraphSentiment]) ->Void, onFailure failure: @escaping (DiscoveryErrors) ->Void)

上記の関数定義は、クエリー対象のアプリの名前と、エスケープされた成功クロージャーと失敗クロージャーを取ります。これは、クエリーが成功した場合と失敗した場合の両方をアプリで処理できるようにするための対処です。@escaping クロージャーを使用すると、クロージャーをメソッドのスコープからクラスのスコープにエスケープできます。クエリーが成功した場合は、この queryForSentiment メソッドを呼び出した対象のクラス内で使用できる GraphSentiment の配列に強参照を返します。成功しなかった場合は、DiscoveryErrors オブジェクトに強参照を返します。Swift 3.0 では、オブジェクトが誤ってクロージャーからエスケープしてサイクルが保持されるという問題を防ぐために、デフォルトでクロージャーをエスケープさせないようになっています。

discovery.queryDocumentsInCollection(
withEnvironmentID: environmentID,
withCollectionID: collectionID,
withAggregation: "filter(app_name:(appName)).timeslice(updated,1day).term(review_enriched.docSentiment.type)",
return: "aggregations",
failure: { error in
failure(.other(error.localizedDescription))},
success: { response in

Discovery サービスのメソッドを呼び出してドキュメントのクエリーを実行するために、Discovery インスタンスをセットアップする際に定義した environmentIDcollectionID を使用した上で、データに対して実行する集約を指定します。

withAggregation: "filter(app_name:(appName)).timeslice(updated,1day).term(review_enriched.docSentiment.type)",
return: "aggregations",

Discovery サービスには固有のクエリー・リファレンス言語があるので、それに従う必要があります。クエリー対象のコレクションが含まれるアプリを指定するために、filter(app_name:\(appName) を指定してアプリ名をフィルタリングします。これにより、Discovery サービスは関数呼び出しの appName 内で渡されたストリングと一致する値を見つけるために、keyapp_name を基準にフィルタリングすることになります。このフィルターから返された値に他の集約を「.」で連結して構成します。返されたアプリに応じて、timeslice を作成します。この timeslice によって返される JSON から、1 日のうちの一定の期間にわたるセンチメント・グラフを作成するための情報を取得します。その情報を term キーワードと一緒に集約し、これによって最も頻度の高いドキュメント・センチメント・タイプ (positive、neutral、または negative のいずれか) を抽出します。要するに、この集約の結果として、指定されたアプリに対する肯定的、中立的、否定的なセンチメントのレビュー数の 1 日あたりの合計を取得することになります。

return パラメーターを使用すれば、Discovery サービスから受け取りたい情報を指定して、返されるデータの量を削減すると同時に、情報を取り込むために必要な時間を短縮できます。私が興味を持っているのは集約結果だけなので、このサービスから集約のみが返されるように指定しています。集約できるデータについて詳しくは、Discovery サービスのクエリー・リファレンスを調べてください。

failure: { error in
failure(.other(error.localizedDescription))},

構文の誤り、ネットワーク・エラーなどの理由で Discovery サービスが集約を解析できない場合は、エラーを返します。

success: { response in
if let responseData = try? JSONSerialization.data(withJSONObject: response.json, options: []) {
var graphSentiments =GraphSentiment
let json = JSON(data: responseData)

それ以外の場合は、Discovery サービスから返されたデータを解析し、グラフを作成するために、そのデータを GraphSentiment オブジェクトとして返します。取り込まれたデータやデータのクエリー方法によって、返されるオブジェクトはさまざまに異なることから、SDK はすべての Discovery 応答を未加工の Foundation オブジェクトとして返します。そのため、Swift のような強力な型付け言語ではデータをアンパックして使用するのが難しくなりますが、不可能ではありません。Foundation の JSONSerialization クラスを使用してデータを JSON にシリアライズした後は、SwiftyJSON という救いの手があります!これによって、オブジェクト json は Dictionary のキーと値にアクセスする場合と同じように使用できる JSON オブジェクトになります。

JSON データのアンパックに関する注意事項として、データをどのようにアンパックするかは、データがどのような形で返されるかについての知識にかなり依存しています。あいにく、データがどのような形で返されるかを知るには、例えば print ("graph data = \(json)") を使用して、オブジェクトを出力して調べるしか手段はありません。オブジェクトを出力すると、以下のような体裁の悪い未加工のフォーマットが表示されます。

{
"results" : [
{
"id" : "4ef583c4-c7af-4c5c-bba4-189197606c57",
"score" : 1
},
{
"id" : "a2848c63-7f2a-40e7-bcd2-5614ee50c585",
"score" : 1
},
{
"id" : "79102dd5-2129-4e81-bcbd-79bc814c12da",
"score" : 1
},
{
"id" : "5e2b5685-1e08-4706-a470-175151c3b383",
"score" : 1
},
{
"id" : "2cf2c9ac-29dc-4e1f-b465-e7dc7fc1401e",
"score" : 1
},
{
"id" : "63488b40-56ae-4cbb-91ad-209fd40737a1",
"score" : 1
},
{
"id" : "ae8d0803-ee94-4b25-97e7-a7832fc30e15",
"score" : 1
},
{
"id" : "bbc28f66-744f-4487-ae49-f944e7a3223e",
"score" : 1
},
{
"id" : "33113ebf-20e5-498b-bf9f-5450f88b7717",
"score" : 1
},
{
"id" : "0b087b5d-542c-4f9a-bbb7-3d7e92107321",
"score" : 1
}
],
"matching_results" : 4665,
"aggregations" : [
{
"match" : "app_name:Google Maps - Navigation & Transit",
"matching_results" : 480,
"type" : "filter",
"aggregations" : [
{
"interval" : "1d",
"results" : [
{
"key" : 1488412800000,
"matching_results" : 35,
"aggregations" : [
{
"type" : "term",
"field" : "review_enriched.docSentiment.type",
"results" : [
{
"key" : "positive",
"matching_results" : 18
},
{
"key" : "negative",
"matching_results" : 15
},
{
"key" : "neutral",
"matching_results" : 2
}
]
}
],
"key_as_string" : "2017-03-02T00:00:00.000Z"
},
{
"key" : 1488499200000,
"matching_results" : 31,
"aggregations" : [
{
"type" : "term",
"field" : "review_enriched.docSentiment.type",
"results" : [
{
"key" : "positive",
"matching_results" : 18
},
{
"key" : "negative",
"matching_results" : 13
}
]
}
],
"key_as_string" : "2017-03-03T00:00:00.000Z"
},

このような形で返された情報のうち、必要な情報は、aggregations キーに含まれる、グラフを作成するために使用する値だけです。

// Unwrap first aggregation response returned by Discovery service. Unwrapping over response index and corresponding value.
guard let (_, firstAggregation) = json["aggregations"].first else {
    failure(DiscoveryErrors.unexpectedJSON)
    return
}
// Safely unwrap second part of aggregation json response returned by Discovery service.
guard let (_, secondAggregation) = firstAggregation["aggregations"].first else {
    failure(DiscoveryErrors.unexpectedJSON)
    return
}

最初の aggregations キーからこの値を取得した後は、次の aggregations キーをアンパックする必要があります。このキーの中に、必要な値のほとんどが格納されています。

let timeSlice = secondAggregation["results"]
for (_, timeSliceInterval) in timeSlice {
let time = timeSliceInterval["key_as_string"].stringValue
var positiveSentiment = Sentiment(type: "positive", matchingResults: 0)
var negativeSentiment = Sentiment(type: "negative", matchingResults: 0)
  // Iterating over array's index and its corresponding value
  guard let (_, timeSliceIntervalResults) = timeSliceInterval["aggregations"].first else {
      failure(DiscoveryErrors.unexpectedJSON)
      return
  }
  for (_, sentiment) in timeSliceIntervalResults["results"] {
      guard let matchingResults = Int(sentiment["matching_results"].stringValue) else {
          failure(DiscoveryErrors.stringToIntFailed)
          break
      }
      if sentiment["key"] == "positive" {
          positiveSentiment.matchingResults = matchingResults
      }
      if sentiment["key"] == "negative" {
          negativeSentiment.matchingResults = matchingResults
      }
  }
  let graphSentiment = GraphSentiment(date: time, positiveSentiment: positiveSentiment, negativeSentiment: negativeSentiment)
  graphSentiments.append(graphSentiment)
  success(graphSentiments)
}

上記のコード・スニペットには、グラフを作成するために必要なデータを収集する処理の大部分が含まれています。そこで、必要なデータを保管するための Sentiment モデルを作成し、GraphsViewController が同じ時点での肯定的なセンチメントと否定的なセンチメントを保管できるようにすることにしました。こうすることで、構成された GraphSentiment の配列を返します。このアプリの他の 2 つの機能も、同じような方法で Discovery サービスから受け取るデータをアンパックします。

まとめ

あらゆるタイプのデータを探索し、その中からパターンを見つけ出したいと思っている新米データ・サイエンティストが使用するには、Discovery サービスは最適なツールとなります。既存のデータと毎日作成されるデータの量を考えると、1 人でデータのすべてに目を通すのは不可能です。最も難しい部分は、すべての「ノイズ」を取り除き、洞察に直接つながる関連データに集中することです。Discovery ツールを使用すれば、データの量もタイプも問わず、詳細を調べたいデータを簡単にいろいろと試すことができます。このツール内でコレクションを作成し、ドキュメントを取り込んでクエリーを開始してください。発見した洞察を共有して提示したいクエリーと集約が決まったら、発見したデータを操作および提示するために利用できる SDK がいくつもあります。Swift SDK は、多数の Watson SDK の中の 1 つでしかありません。皆さんが Discovery サービスを利用して作成するアプリケーションを目にするのを楽しみにしています。Watson Discovery の機能を確認した今、ぜひ自分で試してみてください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Mobile development, Cloud computing
ArticleID=1050616
ArticleTitle=Bluemix、Watson Discovery、Cloudant を利用して、他のアプリを分析するモバイル・アプリを作成する
publish-date=10122017