WatsonのPython SDKを使ってAPIを叩いてみた 13連発 (絶賛途中)

  • 13
    いいね
  • 0
    コメント

WatsonのPython SDKを使ってAPIを叩いてみた 13連発 (絶賛途中)
Teratailのアドベントカレンダー 22日目の記事です!
青汁欲しいのでいいね下さい。

これはなに

WatsonのPython SDKが提供されているので、使ってみようというもの。

#94 にも書いてあるように、Pythonのサンプルが少ないし、日本語の記事もあんまりないので、まとめてみようというのが動機です。

pipが入っていれば、次のコマンドでSDKがインストールできます。

pip install --upgrade watson-developer-cloud

用意するもの

この記事中のコードは Python 3.5.2 で検証しています。

IBMが純粋に提供しているWatsonのAPI

サードパーティを除くと13個あります。

  1. AlchemyAPI
  2. Conversation
  3. Discovery
  4. Document Conversion
  5. Language Translator
  6. Natural Language Classifier
  7. Personality Insights
  8. Retrieve and Rank
  9. Speech to Text
  10. Text to Speech
  11. Tone Analyzer
  12. Tradeoff Analytics
  13. Visual Recognition

1. AlchemyAPI

Data News と Language の2つがある。前者はある期間内、指定した条件でスクレイピングしてくれるイメージ。
後者はテキストやURLから記事の基本情報やコンセプト、感情分析などさまざまな分析をしてくれるタイプ。
Languageの方はiOS SDK も提供されている。

AlchemyData News

サンプルコード

import json
from watson_developer_cloud import AlchemyDataNewsV1

alchemy_data_news = AlchemyDataNewsV1(api_key='')

results = alchemy_data_news.get_news_documents(
    start='1453334400',
    end='1454022000',
    return_fields=[
        'enriched.url.title',
        'enriched.url.url',
        'enriched.url.author',
        'enriched.url.publicationDate'
    ],
    query_fields={
        # クエリフィールド
        'q.enriched.url.enrichedTitle.entities.entity': '|text=IBM, type=company|'
    }
)

print(json.dumps(results, indent=2))

実行結果

1日の使用上限を超えてしまったのでまだ載せられない。

メソッド

AlchemyDataNewsV1 は1つだけメソッドを提供している。

def get_news_documents(self, start, end, max_results=10, query_fields=None,
                       return_fields=None, time_slice=None,
                       next_page=None, dedup=None, dedup_threshold=None,
                       rank=None):

start は必須で、探索の開始時間を指定することができる。UNIXタイムでも可能である。
他には、 現時刻だと now 、現時刻から1週間前だと now-7d と言った記述も可能。

return_fields には返ってくる値を指定することができる。

  • enriched.url.title
  • enriched.url.url
  • enriched.url.author
  • enriched.url.publicationDate

query_fields には探索したいパラメータを指定することが可能。

  • q.enriched.url.enrichedTitle.relations.relation
  • q.enriched.url.enrichedTitle.entities.entity
  • q.enriched.url.enrichedTitle.taxonomy.taxonomy
  • q.enriched.url.enrichedTitle.docSentiment.type
  • q.enriched.url.concepts.concept.text
  • q.enriched.url.enrichedTitle.keywords.keyword.text

entitiesとtaxonomyはリストがあるので、リファレインスページのリンクからダウンロードして適宜参照するとよい。

その他

使ってるとすぐに下の様に一日の使用量を超過してしまうので注意。

WatsonException: Error: daily-transaction-limit-exceeded

AlchemyLanguage

サンプルコード

import json
from os.path import join, dirname
from watson_developer_cloud import AlchemyLanguageV1

alchemy_language = AlchemyLanguageV1(api_key='')

url = ''

print(json.dumps(
    alchemy_language.targeted_sentiment(text='I love cats! Dogs are smelly.',
                                        targets=['cats', 'dogs'],
                                        language='english'), indent=2))

targeted_sentimenttargets に入力したワードについて感情分析するメソッド。
ソースコードを見た感じ、html, text, url などが指定可能。

def targeted_sentiment(self, targets, html=None, text=None, url=None,
                       language=None, constraint_query=None,
                       xpath_query=None, show_source_text=False,
                       source_text_type=None):

他にも単体でメソッドが用意されているので、必要に応じて使い分けておくいい。

2. Conversation

Conversation - API | IBM Watson Developer Cloud

APIを取得後、Workspaceを一度作る必要がある。

書きかけです

3. Discovery

指定子た条件下で最新の情報を拾ってきてくれるやつ。検索の手間を省くというイメージ。

DEMO

Discovery - API | IBM Watson Developer Cloud

ドキュメントを見る限り、
size = 0 とすれば、無料枠で叩けるはずなのだけれど、叩けなかった。
一応、sizeは1,2,3のどれかしか取れない。(一応書き換えてやってもアウトだった)

def create_environment(self, name="", description="", size=1):
        """

        :param name: name of the environment (max 255 chars) can be empty
        :param description: description of the environment (max 255 chars)
        can be empty
        :param size: size of the environment (1,2, or 3)
        :return:
        """
        self._valid_name_and_description(name=name, description=description)
        if size not in range(1, 4):
            raise ValueError("Size can be 1, 2, or 3")

4. Document Conversion

Document Conversion - API | IBM Watson Developer Cloud

PDFやMS WordなどをHTMLやらPlain Textに変換してくれるAPI。

config にどこの階層まで見出しをつけるかなどを指定できる。

絶賛UnicodeDecodeErrorでコケているので、記事に書けない・・・。できたら追記します。

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf0 in position 14: invalid continuation byte

5. Language Translator

Language Translator - API | IBM Watson Developer Cloud

入力した言語を指定した言語に翻訳してくれるAPI。

サンプルコード

import json
from watson_developer_cloud import LanguageTranslatorV2

language_translator = LanguageTranslatorV2(
    username='', # 発行された username を入れて下さい
    password=''  # 発行された password を入れて下さい
)

translation = language_translator.translate(
    text='Hello World!',
    source='en', # 翻訳前の言語を指定
    target='ja'  # 翻訳後の言語を指定
)

# 結果を出力します
# json.dumpsの文字化け回避に ensure_ascii=False を指定
print(json.dumps(translation, indent=2, ensure_ascii=False))

出力結果

6. Natural Language Classifier

質問を投げたら、それに対する回答をしてくれるAPI。

DEMO

Natural Language Classifier - API | IBM Watson Developer Cloud

サンプルコード

あらかじめ、会話を学習させておく。例えば天気予報に対する応答など。
サンプルコードにあるCSVファイルを用いてみる。

import json
from watson_developer_cloud import NaturalLanguageClassifierV1

natural_language_classifier = NaturalLanguageClassifierV1(
  username='',
  password=''
)

with open('wheater_data_train.csv', 'rb') as training_data:
    d = natural_language_classifier.create(training_data = training_data, name='weather')
    c_id = d["classifier_id"]

status = natural_language_classifier.status(c_id)
print(status) # Training が表示されている間は、応答しない。

if status['status'] == 'Available':
    classes = natural_language_classifier.classify(c_id, 'How hot will it be tomorrow?')
    print(json.dumps(classes, indent=2))

トレーニングしたclassifierごとにclassifier_idが発行される。
これをに対して質問をしたりする。
また、statusTrainingの間は、トレーニング中なのでこの間は応答しない。

出力結果

{
  "url": "https://gateway.watsonplatform.net/natural-language-classifier/api/v1/classifiers/xxxxxxxxxxxxx",
  "classes": [
    {
      "confidence": 0.9934488976826474,
      "class_name": "temperature"
    },
    {
      "confidence": 0.006551102317352602,
      "class_name": "conditions"
    }
  ],
  "text": "How hot will it be tomorrow?",
  "classifier_id": "xxxxxxxxxxxxx",
  "top_class": "temperature"
}

How hot will it be tomorrow? に対する回答が返ってきている事がわかる。

7. Personality Insights

テキストから性格診断してくれるサービス。(デモが日本語)

DEMO

Personality Insights - API | IBM Watson Developer Cloud

サンプルコード

サンプルではjsonファイルを読み込む。

personality-v3.json

import json
from os.path import join, dirname
from watson_developer_cloud import PersonalityInsightsV3

personality_insights = PersonalityInsightsV3(
    version='2016-10-20',
    username='',
    password='')

with open(join(dirname(__file__), 'personality-v3.json')) as profile_json:
    profile = personality_insights.profile(
        profile_json.read(), content_type='application/json',
        raw_scores=True, consumption_preferences=True)
    print(json.dumps(profile, indent=2))

出力結果は長いので割愛。また、jsonではなくただのテキストを判定したい場合は

content_type を指定しなければいい(デフォルトでtext/plainになっている)

personality_insights.profile("テキスト")

ソースコードを確認すると、メソッドは次の1個しか用意されていない。

def profile(self, text, content_type='text/plain', content_language=None,
            accept='application/json',
            accept_language=None, raw_scores=False,
            consumption_preferences=False, csv_headers=False):

8. Retrieve and Rank

Apache Solr + 機械学習の検索サービスを作れるやつ。

DEMO

Retrieve and Rank - API | IBM Watson Developer Cloud

  1. RerieveAndRankのインスタンスを作成
retrieve_and_rank = RetrieveAndRankV1(
    username='',
    password='')
  1. Apache Solrクラスタを作成する
  • Apache Solrクラスタを作成、
  • Apache Solrクラスタのidが発行される
created_cluster = retrieve_and_rank.create_solr_cluster(cluster_name='Test Cluster', cluster_size='1')
solr_cluster_id = ""
  • 設定をアップロード
with open('solr_config.zip', 'rb') as config:
    config_status = retrieve_and_rank.create_config(solr_cluster_id, 'test-config', config)
    print(json.dumps(config_status, indent=2))
  1. Solrコレクションを作成する。
collection = retrieve_and_rank.create_collection(solr_cluster_id, 'test-collection', 'test-config')
  1. 検索のランカー用のインスタンスを作成する。
  • Rankerのインスタンスを作成。
with open('source/ranker_training_data.csv', 'rb') as training_data:
    retrieve_and_rank.create_ranker(training_data=training_data, name='Ranker Test')
  1. クエリを投げる
with open('ranker_answer_data.csv', 'rb') as answer_data:
    ranker_results = retrieve_and_rank.rank('766366x22-rank-1804', answer_data)
    print(json.dumps(ranker_results, indent=2))

9. Speech to Text

  1. 検索クエリを投げる

Speech to Text - API | IBM Watson Developer Cloud

入力した音源を文字起こししてくれる、すごく便利なAPI。

サンプルコード

from watson_developer_cloud import SpeechToTextV1

speech_to_text = SpeechToTextV1(
    username='', # 発行された username を入れて下さい
    password='', # 発行された password を入れて下さい
    x_watson_learning_opt_out=False
)

with open('source', 'rb') as audio_file: # 音源を取得
    r = speech_to_text.recognize(
        audio_file,                    # 読み込んだした音源
        model="ja-JP_NarrowbandModel", # 言語とサンプリングレートの指定
        content_type='audio/flac',     # コンテントタイプを指定
        timestamps=True,               # タイムスタンプ
        word_confidence=True
)

print(r)

対応言語とサンプリングレート

model引数には、言語とサンプリングレート(Broadband, Narrowband)が指定できる。
ドキュメントによれば、以下のとおり。

  • ar-AR_BroadbandModel
  • en-UK_BroadbandModel
  • en-UK_NarrowbandModel
  • en-US_BroadbandModel (the default)
  • en-US_NarrowbandModel
  • es-ES_BroadbandModel
  • es-ES_NarrowbandModel
  • fr-FR_BroadbandModel
  • ja-JP_BroadbandModel
  • ja-JP_NarrowbandModel
  • pt-BR_BroadbandModel
  • pt-BR_NarrowbandModel
  • zh-CN_BroadbandModel
  • zh-CN_NarrowbandModel

SpeechToTextV1ソースコードの30行目あたりを見れば、
結構な引数が指定できるので、ドキュメントを読みながら適宜していきたい。

def recognize(self, audio, content_type, continuous=False, model=None,
                  inactivity_timeout=None,
                  keywords=None, keywords_threshold=None,
                  max_alternatives=None,
                  word_alternatives_threshold=None,
                  word_confidence=None, timestamps=None, interim_results=None,
                  profanity_filter=None,
                  smart_formatting=None,
                  speaker_labels=None):

10. Text to Speech

Text to Speech - API | IBM Watson Developer Cloud

入力した文字を音声に起こしてくれるAPI。

サンプルコード

from os.path import join, dirname
from watson_developer_cloud import TextToSpeechV1

text_to_speech = TextToSpeechV1(
    username='',
    password='',
    x_watson_learning_opt_out=True)  # Optional flag

with open(join(dirname(__file__), './output/output.wav'), 'wb') as audio_file:
    audio_file.write(text_to_speech.synthesize('Hello world!',     # 第一引数に入力テキスト
                                               accept='audio/wav', # 保存形式
                                               voice="en-US_AllisonVoice")) # 言語と音声を指定

対応言語とサンプリングレート

  • de-DE_BirgitVoice
  • de-DE_DieterVoice
  • en-GB_KateVoice
  • en-US_AllisonVoice
  • en-US_LisaVoice
  • en-US_MichaelVoice (the default)
  • es-ES_EnriqueVoice
  • es-ES_LauraVoice
  • es-LA_SofiaVoice
  • es-US_SofiaVoice
  • fr-FR_ReneeVoice
  • it-IT_FrancescaVoice
  • ja-JP_EmiVoice
  • pt-BR_IsabelaVoice

合成音声を作成するメソッド synthesizeソースコードで確認して見ると、以下の通り。

def synthesize(self, text, voice=None, accept=None, customization_id=None):

また、カスタムモデルは少しだけ触った感じ、新しい音を登録する、と言うものではないことだけははっきりした。
サンプルを見る限り、例えば「iPhone」を「I Phone」と読むように分解しておくとか、表記と発音が違うものを登録しておくような感じ。
具体的には、「IEEE」は「I triple e」(アイ トリプル イー)とか。

具体的なサンプルコードをおいておく。

from os.path import join, dirname
from watson_developer_cloud import TextToSpeechV1

text_to_speech = TextToSpeechV1(
    username='', # 発行された username を入れて下さい
    password='', # 発行された password を入れて下さい
    x_watson_learning_opt_out=True) 

c_id = text_to_speech.create_customization('short-word', language="en-US")["customization_id"]
text_to_speech.add_customization_words(c_id, [{'word': 'IEEE', 'translation': 'I triple E'}])

with open(join(dirname(__file__), './ieee.wav'), 'wb') as audio_file:
    audio_file.write(text_to_speech.synthesize('This is IEEE.',
                                               accept='audio/wav',
                                               customization_id=c_id))

出力された音声ファイルを聞いてみると「I triple E」と発言していることが聞き取れる。

11. Tone Analyzer

Tone Analyzer - API | IBM Watson Developer Cloud

入力された文字から感情分析を行うAPI。

サンプルコード

import json
from watson_developer_cloud import ToneAnalyzerV3

tone_analyzer = ToneAnalyzerV3(
    username='',
    password='',
    version='2016-02-11')

print(json.dumps(tone_analyzer.tone(text='I am very happy'), indent=2, ensure_ascii=False))

出力結果

jsonが次のように返ってくる。

"tone_categories": [
      {
        "category_id": "emotion_tone",
        "category_name": "Emotion Tone",
        "tones": [
          {
            "score": 0.006169,
            "tone_id": "anger",
            "tone_name": "Anger"
          },
          {
            "score": 0.008786,
            "tone_id": "disgust",
            "tone_name": "Disgust"
          },
          {
            "score": 0.007084,
            "tone_id": "fear",
            "tone_name": "Fear"
          },
          {
            "score": 0.973498,
            "tone_id": "joy",
            "tone_name": "Joy"
          },
          {
            "score": 0.0179,
            "tone_id": "sadness",
            "tone_name": "Sadness"
          }
        ]
      },
      // 以下省略

評価内容

Scoreはつぎの3つのカテゴリーに分類され、さらにトーンごとに採点される。

  • Emotion Tone
    • Anger : 怒り
    • Disgust : 嫌悪感
    • Fear : 恐怖
    • Joy : 喜び
    • Sadness : 悲しみ
  • Writing Tone
    • Analytical : 分析的
    • Confident : 自信
    • Tentative : 自信なさげな
  • Social Tone
    • Openness : 開放性
    • Conscientiousness : 誠実さ
    • Extraversion : 外交性
    • Agreeableness : 妥当性
    • Emotional Range : 感情的な範囲

12. Tradeoff Analytics

Tradeoff Analytics - API | IBM Watson Developer Cloud

対立案があったときに、どの案がいいのか提案してくれるというもの。(エヴァのマギシステムてきなやつ

実行するPythonの方は下記の通り。

サンプルコード

import json
import os
from os.path import join, dirname
from watson_developer_cloud import TradeoffAnalyticsV1

tradeoff_analytics = TradeoffAnalyticsV1(
    username='',
    password='')

with open(os.path.join(os.path.dirname(__file__), 'problem.json')) as problem_json:
    dilemma = tradeoff_analytics.dilemmas(json.load(problem_json),
                                          generate_visualization=False)

print(json.dumps(dilemma, indent=2, ensure_ascii=False))

出力結果

例えば、次の "problem.json" を使ってみる。

{
  "subject": "使徒討伐か、エヴァ残存か",
  "columns": [
    {
      "key": "number_of_shito",
      "type": "numeric",
      "goal": "min",
      "full_name": "使徒の数",
      "is_objective": true,
      "range": {
        "low": 0,
        "high": 13
      },
      "format": "number:0"
    },
    {
      "key": "number_of_nerv_base",
      "type": "numeric",
      "goal": "max",
      "full_name": "ネルフの本部の数",
      "range": {
        "low": 0,
        "high": 1
      },
      "format": "number:0"
    },
    {
      "key": "number_of_eva",
      "type": "numeric",
      "goal": "max",
      "full_name": "エヴァの数",
      "is_objective": true,
      "range": {
        "low": 0,
        "high": 3
      },
      "format": "number:0"
    }
  ],
  "options": [
    {
      "key": "1",
      "name": "ネルフ本部を放棄して使徒を減らす方が良い",
      "values": {
        "number_of_shito": 12,
        "number_of_nerv_base": 0,
        "number_of_eva": 3
      }
    },
    {
      "key": "2",
      "name": "ネルフ本部とエヴァを残して使徒を見逃す",
      "values": {
        "number_of_shito": 13,
        "number_of_nerv_base": 1,
        "number_of_eva": 3
      }
    },
    {
      "key": "3",
      "name": "ネルフ本部を破棄、エヴァを撃破されるが、使徒を2体撃破",
      "values": {
        "number_of_shito": 11,
        "number_of_nerv_base": 0,
        "number_of_eva": 2
      }
    }
  ]
}

出力結果

{
  "resolution": {
    "solutions": [
      {
        "status": "FRONT",
        "solution_ref": "1"
      },
      {
        "status": "EXCLUDED",
        "solution_ref": "2",
        "excluded_by": [
          {
            "objectives": [
              {
                "key": "number_of_shito",
                "difference": 1.0
              }
            ],
            "solution_ref": "1"
          }
        ]
      },
      {
        "status": "FRONT",
        "solution_ref": "3"
      }
    ]
  },
  "problem": {
    "columns": [
      {
        "key": "number_of_shito",
        "format": "number:0",
        "type": "numeric",
        "range": {
          "low": 0.0,
          "high": 13.0
        },
        "goal": "min",
        "is_objective": true,
        "full_name": "使徒の数"
      },
      {
        "key": "number_of_nerv_base",
        "format": "number:0",
        "type": "numeric",
        "range": {
          "low": 0.0,
          "high": 1.0
        },
        "goal": "max",
        "is_objective": false,
        "full_name": "ネルフの本部の数"
      },
      {
        "key": "number_of_eva",
        "format": "number:0",
        "type": "numeric",
        "range": {
          "low": 0.0,
          "high": 3.0
        },
        "goal": "max",
        "is_objective": true,
        "full_name": "エヴァの数"
      }
    ],
    "subject": "使徒討伐か、エヴァ残存か",
    "options": [
      {
        "name": "ネルフ本部を放棄して使徒を減らす方が良い",
        "values": {
          "number_of_eva": 3,
          "number_of_shito": 12,
          "number_of_nerv_base": 0
        },
        "key": "1"
      },
      {
        "name": "ネルフ本部とエヴァを残して使徒を見逃す",
        "values": {
          "number_of_eva": 3,
          "number_of_shito": 13,
          "number_of_nerv_base": 1
        },
        "key": "2"
      },
      {
        "name": "ネルフ本部を破棄、エヴァを撃破されるが、使徒を2体撃破",
        "values": {
          "number_of_eva": 2,
          "number_of_shito": 11,
          "number_of_nerv_base": 0
        },
        "key": "3"
      }
    ]
  }
}

結果は次の通り

FRONT(最良)

  • ネルフ本部を放棄して使徒を減らす方が良い
  • ネルフ本部を破棄、エヴァを撃破されるが、使徒を2体撃破

EXCLUDED(まぁまぁ)

  • ネルフ本部とエヴァを残して使徒を見逃す

らしいので、使徒と戦いましょう。

13. Visual Recognition

「顔」「文字」「その他?」の種類で画像認識をするAPI。
このAPIだけ、他のAPIと違って api_key が発行される。

サンプルコード

import json
from os.path import join, dirname
from watson_developer_cloud import VisualRecognitionV3

# API Keyを入力する
visual_recognition = VisualRecognitionV3('2016-05-20', api_key='')

# ローカルにあるファイルを使う場合

# 何かわからないものを認識させたい時
with open(join(dirname(__file__), 'nanika.png'), 'rb') as image_file:
    print(json.dumps(visual_recognition.classify(images_file=image_file), indent=2))

# 顔認識
with open('face.jpg', 'rb') as image_file:
    print(json.dumps(visual_recognition.detect_faces(images_file=image_file), indent=2))

# 文字認識
with open('text.png', 'rb') as image_file:
    print(json.dumps(visual_recognition.recognize_text(images_file=image_file), indent=2))

# URLで画像認識させる場合
print(json.dumps(visual_recognition.detect_faces(images_url=""), indent=2))
print(json.dumps(visual_recognition.recognize_text(images_url=""), indent=2))

出力結果

下の画像を classify メソッドを使って認識させてみる。

miku.jpg

{
  "images": [
    {
      "image": "miku.jpg",
      "classifiers": [
        {
          "classifier_id": "default",
          "classes": [
            {
              "score": 0.838,
              "class": "oxygen mask",
              "type_hierarchy": "/device/oxygen mask"
            },
            {
              "score": 0.838,
              "class": "device"
            },
            {
              "score": 0.584,
              "class": "plush (fabric for toys)"
            },
            {
              "score": 0.584,
              "class": "fabric"
            },
            {
              "score": 0.554,
              "class": "mask"
            },
            {
              "score": 0.942,
              "class": "emerald color"
            }
          ],
          "name": "default"
        }
      ]
    }
  ],
  "images_processed": 1,
  "custom_classes": 0
}

エメラルドカラーに94%とかは分かるのですが、「酸素マスク」が項目に入るのはちょっと驚いた。

使用したメソッドをソースコード
で見てみると下のようになっている。

def classify(self, images_file=None, images_url=None, classifier_ids=None,
                 owners=None, threshold=None):
def recognize_text(self, images_file=None, images_url=None):
def detect_faces(self, images_file=None, images_url=None):

お気づきだろうが、ドキュメントには image_url なんて無いという。
きっとわざとだろう。そう信じてる。

終わりに

PythonからAPIを一通り呼び出してみた感じ、日本語が交じるとUnicodeErrorあたりでいちいち躓くなぁ、という印象。
個人的にサクッと使えたなーというのが以下のもの。

  1. AlchemyAPI
  2. Natural Language Classifier
  3. Personality Insights
  4. Speech to Text
  5. Text to Speech
  6. Tone Analyzer
  7. Tradeoff Analytics
  8. Visual Recognition

それぞれのサービスで完結している感じがあるので、APiで呼び出すより、WEBでやったほうが楽だろうなぁ、というのが今回の感想。