tjtjtjのメモ

自分のためのメモです

日本語文章のセンチメント分析

先日の上原浩治投手引退会見ニュースをセンチメント分析にかけてみた。前回はanalyze-entity-sentiment指定したが、entity がよくなかった。今回はanalyze-sentimentでやってみた。

{
  "document":{
    "type":"PLAIN_TEXT",
    "content":"巨人・上原浩治投手が20日、都内ホテルで引退会見を行った。シーズン序盤、突然の引退発表。会見場には報道陣200人超が詰めかけた。21年間に及んだ現役生活。会見場に上がった上原は冒頭から涙を流し、「本日をもちまして21年間の現役生活を終えたいなと思います」と挨拶すると、「えー…」と声を詰まらせ、目をぬぐった。「これまで自分に関わってもらった人々、方々、みんなに感謝したいと思います、ありがとうございました」と語った。"
  },
  "encodingType": "UTF8"
}

scoreが -0.7 から 0.9と、感情的な文章ってことがわかる。

curl "https://language.googleapis.com/v1/documents:analyzeSentiment?key=${API_KEY}" \
  -s -X POST -H "Content-Type: application/json" --data-binary @request.json
{
  "documentSentiment": {
    "magnitude": 3.1,
    "score": 0
  },
  "language": "ja",
  "sentences": [
    {
      "text": {
        "content": "巨人・上原浩治投手が20日、都内ホテルで引退会見を行った。",
        "beginOffset": 0
      },
      "sentiment": {
        "magnitude": 0.7,
        "score": -0.7
      }
    },
    {
      "text": {
        "content": "シーズン序盤、突然の引退発表。",
        "beginOffset": 87
      },
      "sentiment": {
        "magnitude": 0.4,
        "score": -0.4
      }
    },
    {
      "text": {
        "content": "会見場には報道陣200人超が詰めかけた。21年間に及んだ現役生活。",
        "beginOffset": 132
      },
      "sentiment": {
        "magnitude": 0.2,
        "score": 0.2
      }
    },
    {
      "text": {
        "content": "会見場に上がった上原は冒頭から涙を流し、「本日をもちまして21年間の現役生活を終えたいなと思います」と挨拶すると、「えー…」",
        "beginOffset": 231
      },
      "sentiment": {
        "magnitude": 0.5,
        "score": 0.5
      }
    },
    {
      "text": {
        "content": "と声を詰まらせ、目をぬぐった。「",
        "beginOffset": 417
      },
      "sentiment": {
        "magnitude": 0.2,
        "score": -0.2
      }
    },
    {
      "text": {
        "content": "これまで自分に関わってもらった人々、方々、みんなに感謝したいと思います、ありがとうございました」と語った。",
        "beginOffset": 465
      },
      "sentiment": {
        "magnitude": 0.9,
      }
        "score": 0.9
    }
  ]
}

Entity and Sentiment Analysis with the Natural Language API(英語)

今回は5部構成。

Call the Natural Language API

最初に使用する自然言語APIメソッドはanalyzeEntitiesです。 このメソッドを使用すると、APIはテキストからエンティティ(人、場所、イベントなど)を抽出できます。

request.json

{
  "document":{
    "type":"PLAIN_TEXT",
    "content":"Joanne Rowling, who writes under the pen names J. K. Rowling and Robert Galbraith, is a British novelist and screenwriter who wrote the Harry Potter fantasy series."
  },
  "encodingType":"UTF8"
}
curl "https://language.googleapis.com/v1/documents:analyzeEntities?key=${API_KEY}" \
  -s -X POST -H "Content-Type: application/json" --data-binary @request.json
{
  "entities": [
    {
      "name": "Joanne Rowling",
      "type": "PERSON",
      "metadata": {
        "wikipedia_url": "https://en.wikipedia.org/wiki/J._K._Rowling",
        "mid": "/m/042xh"
      },
      "salience": 0.79828626,
      "mentions": [
        {
          "text": {
            "content": "Joanne Rowling",
            "beginOffset": 0
          },
          "type": "PROPER"
        },
        {
          "text": {
            "content": "Rowling",
            "beginOffset": 53
          },
          "type": "PROPER"
        },
        {
          "text": {
            "content": "novelist",
            "beginOffset": 96
          },
          "type": "COMMON"
        },
        {
          "text": {
            "content": "Robert Galbraith",
            "beginOffset": 65
          },
          "type": "PROPER"
        }
      ]
    },
    :
  ],
  "language": "en"
}

応答内の各エンティティについて、エンティティタイプ、関連するウィキペディアのURL(存在する場合)、特徴、およびこのエンティティがテキスト内のどこに出現したかのインデックスを取得します。 顕著性とは、テキスト全体に対するエンティティの中心性を指す[0,1]の範囲の数値です。 自然言語APIは、同じエンティティをさまざまな方法で認識することもできます。 レスポンスのメンションリストを見てください。APIは、 "Joanne Rowling"、 "Rowling"、 "novelist"、および "Robert Galbriath"のすべてが同じことを示していることがわかります。

Sentiment analysis with the Natural Language API

エンティティの抽出に加えて、Natural Language APIでは、テキストブロックに対して感情分析を実行することもできます。

{
  "document":{
    "type":"PLAIN_TEXT",
    "content":"Harry Potter is the best book. I think everyone should read it."
  },
  "encodingType": "UTF8"
}
curl "https://language.googleapis.com/v1/documents:analyzeSentiment?key=${API_KEY}" \
  -s -X POST -H "Content-Type: application/json" --data-binary @request.json
{
  "documentSentiment": {
    "magnitude": 0.8,
    "score": 0.4
  },
  "language": "en",
  "sentences": [
    {
      "text": {
        "content": "Harry Potter is the best book.",
        "beginOffset": 0
      },
      "sentiment": {
        "magnitude": 0.7,
        "score": 0.7
      }
    },
    {
      "text": {
        "content": "I think everyone should read it.",
        "beginOffset": 31
      },
      "sentiment": {
        "magnitude": 0.1,
        "score": 0.1
      }
    }
  ]
}

2つのタイプの感情値があることに注意してください。文書全体の感情と、文ごとに分類された感情です。 感情メソッドは2つの値を返します。

score  - ステートメントが正または負であることを示す-1.0〜1.0の数値です。
magnitude - 正または負に関係なく、ステートメントで表される感情の重みを表す0から無限の範囲の数値です。

Analyzing entity sentiment

テキストドキュメント全体の感情の詳細を提供することに加えて、Natural Language APIはテキスト内のエンティティによる感情を細分化することもできます。 例としてこの文章を使用してください。

「私は寿司が好きでしたが、サービスはひどかった」

{
  "document":{
    "type":"PLAIN_TEXT",
    "content":"I liked the sushi but the service was terrible."
  },
  "encodingType": "UTF8"
}

「寿司」のスコアが0.7、「サービス」のスコアは-0.9。

curl "https://language.googleapis.com/v1/documents:analyzeEntitySentiment?key=${API_KEY}" \
  -s -X POST -H "Content-Type: application/json" --data-binary @request.json
{
  "entities": [
    {
      "name": "sushi",
      "type": "CONSUMER_GOOD",
      "metadata": {},
      "salience": 0.51064336,
      "mentions": [
        {
          "text": {
            "content": "sushi",
            "beginOffset": 12
          },
          "type": "COMMON",
          "sentiment": {
            "magnitude": 0.7,
            "score": 0.7
          }
        }
      ],
      "sentiment": {
        "magnitude": 0.7,
        "score": 0.7
      }
    },
    {
      "name": "service",
      "type": "OTHER",
      "metadata": {},
      "salience": 0.48935664,
      "mentions": [
        {
          "text": {
            "content": "service",
            "beginOffset": 26
          },
          "type": "COMMON",
          "sentiment": {
            "magnitude": 0.9,
            "score": -0.9
          }
        }
      ],
      "sentiment": {
        "magnitude": 0.9,
        "score": -0.9
      }
    }
  ],
  "language": "en"
}

Analyzing syntax and parts of speech

Natural Language APIの3番目の方法であるテキスト注釈を見てみると、テキストの言語に関する詳細をさらに深く理解することができます。 annotateTextは、テキストのセマンティック要素および構文要素に関する詳細をすべて網羅した高度なメソッドです。 テキスト内の各単語について、APIはその単語の品詞(名詞、動詞、形容詞など)と、それが文中の他の単語とどのように関連しているかを教えてくれます(ルート動詞ですか?修飾語?)。

{
  "document":{
    "type":"PLAIN_TEXT",
    "content": "Joanne Rowling is a British novelist, screenwriter and film producer."
  },
  "encodingType": "UTF8"
}

Joanne は名詞(NOUN) のように単語毎に品詞が分析されている。

curl "https://language.googleapis.com/v1/documents:analyzeSyntax?key=${API_KEY}" \
  -s -X POST -H "Content-Type: application/json" --data-binary @request.json
{
  "sentences": [
    {
      "text": {
        "content": "Joanne Rowling is a British novelist, screenwriter and film producer.",
        "beginOffset": 0
      }
    }
  ],
  "tokens": [
    {
      "text": {
        "content": "Joanne",
        "beginOffset": 0
      },
      "partOfSpeech": {
        "tag": "NOUN",
        "aspect": "ASPECT_UNKNOWN",
        "case": "CASE_UNKNOWN",
        "form": "FORM_UNKNOWN",
        "gender": "GENDER_UNKNOWN",
        "mood": "MOOD_UNKNOWN",
        "number": "SINGULAR",
        "person": "PERSON_UNKNOWN",
        "proper": "PROPER",
        "reciprocity": "RECIPROCITY_UNKNOWN",
        "tense": "TENSE_UNKNOWN",
        "voice": "VOICE_UNKNOWN"
      },
      "dependencyEdge": {
        "headTokenIndex": 1,
        "label": "NN"
      },
      "lemma": "Joanne"
    },
    {
      "text": {
        "content": "Rowling",
        "beginOffset": 7
      },
      "partOfSpeech": {
        "tag": "NOUN",
        "aspect": "ASPECT_UNKNOWN",
        "case": "CASE_UNKNOWN",
        "form": "FORM_UNKNOWN",
        "gender": "GENDER_UNKNOWN",
        "mood": "MOOD_UNKNOWN",
        "number": "SINGULAR",
        "person": "PERSON_UNKNOWN",
        "proper": "PROPER",
        "reciprocity": "RECIPROCITY_UNKNOWN",
        "tense": "TENSE_UNKNOWN",
        "voice": "VOICE_UNKNOWN"
      },
      "dependencyEdge": {
        "headTokenIndex": 2,
        "label": "NSUBJ"
      },
      "lemma": "Rowling"
    },
  :
  ],
  "language": "en"
}

Multilingual natural language processing

Natural Language APIは英語以外の言語もサポートしています。

{
  "document":{
    "type":"PLAIN_TEXT",
    "content":"日本のグーグルのオフィスは、東京の六本木ヒルズにあります"
  }
}
curl "https://language.googleapis.com/v1/documents:analyzeEntities?key=${API_KEY}" \
  -s -X POST -H "Content-Type: application/json" --data-binary @request.json
{
  "entities": [
    :
    {
      "name": "グーグル",
      "type": "ORGANIZATION",
      "metadata": {
        "mid": "/m/045c7b",
        "wikipedia_url": "https://en.wikipedia.org/wiki/Google"
      },
      "salience": 0.21214141,
      "mentions": [
        {
          "text": {
            "content": "グーグル",
            "beginOffset": -1
          },
          "type": "PROPER"
        }
      ]
    },
    {
      "name": "六本木ヒルズ",
      "type": "PERSON",
      "metadata": {
        "mid": "/m/01r2_k",
        "wikipedia_url": "https://en.wikipedia.org/wiki/Roppongi_Hills"
      },
      "salience": 0.19418614,
      "mentions": [
        {
          "text": {
            "content": "六本木ヒルズ",
            "beginOffset": -1
          },
          "type": "PROPER"
        }
      ]
    },
    :
  ],
  "language": "ja"
}

Cloud Natural Language API: Qwik Start(日本語)

Google Cloud Natural Language API を使用すると、ドキュメントやニュース記事、ブログ投稿に含まれる人、場所、イベントなどに関する情報を抽出できます。ソーシャル メディア上のコメントから商品に対するセンチメント(感情)を把握したり、コールセンターやメッセージ アプリに寄せられた消費者の意見から顧客満足度を分析したりすることができます。また、分析を行うためにドキュメントをアップロードすることもできます。

例題

gcloud ml language analyze-entities --content="Michelangelo Caravaggio, Italian painter, is known for 'The Calling of Saint Matthew'."

ミケランジェロ・カラヴァッジョ、イタリアの画家、聖マタイの召命で知られている。ですかね。

{
  "entities": [
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 0,
            "content": "Michelangelo Caravaggio"
          },
          "type": "PROPER"
        },
        {
          "text": {
            "beginOffset": 33,
            "content": "painter"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {
        "mid": "/m/020bg",
        "wikipedia_url": "https://en.wikipedia.org/wiki/Caravaggio"
      },
      "name": "Michelangelo Caravaggio",
      "salience": 0.82904786,
      "type": "PERSON"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 25,
            "content": "Italian"
          },
          "type": "PROPER"
        }
      ],
      "metadata": {
        "mid": "/m/03rjj",
        "wikipedia_url": "https://en.wikipedia.org/wiki/Italy"
      },
      "name": "Italian",
      "salience": 0.13981608,
      "type": "LOCATION"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 56,
            "content": "The Calling of Saint Matthew"
          },
          "type": "PROPER"
        }
      ],
      "metadata": {
        "mid": "/m/085_p7",
        "wikipedia_url": "https://en.wikipedia.org/wiki/The_Calling_of_St_Matthew_(Caravaggio)"
      },
      "name": "The Calling of Saint Matthew",
      "salience": 0.031136045,
      "type": "EVENT"
    }
  ],
  "language": "en"
}

テキトーに選んだニュースをエンティティ分析

上原浩治投手引退会見ニュースをエンティティ分析

gcloud ml language analyze-entities --content="巨人・上原浩治投手が20日、都内ホテルで引退会見を行った。シーズン序盤、突然の引退発表。会見場には報道陣200人超が詰めかけた。21年間に及んだ現役生活。会見場に上がった上原は冒頭から涙を流し、「本日をもちまして21年間の現役生活を終えたいなと思います」と挨拶すると、「えー…」と声を詰まらせ、目をぬぐった。「これまで自分に関わってもらった人々、方々、みんなに感謝したいと思います、ありがとうございました」と語った。"
{
  "entities": [
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 0,
            "content": "巨人"
          },
          "type": "COMMON"
        },
        {
          "text": {
            "beginOffset": 9,
            "content": "上原浩治"
          },
          "type": "PROPER"
        }
      ],
      "metadata": {
        "mid": "/m/0c9lhg",
        "wikipedia_url": "https://en.wikipedia.org/wiki/Koji_Uehara"
      },
      "name": "上原浩治",
      "salience": 0.16061519,
      "type": "PERSON"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 60,
            "content": "引退会見"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "引退会見",
      "salience": 0.140315,
      "type": "EVENT"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 21,
            "content": "投手"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "投手",
      "salience": 0.11305461,
      "type": "PERSON"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 132,
            "content": "会見場"
          },
          "type": "COMMON"
        },
        {
          "text": {
            "beginOffset": 231,
            "content": "会見場"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "会見場",
      "salience": 0.09740271,
      "type": "EVENT"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 42,
            "content": "都"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "都",
      "salience": 0.085483804,
      "type": "LOCATION"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 216,
            "content": "現役生活"
          },
          "type": "COMMON"
        },
        {
          "text": {
            "beginOffset": 333,
            "content": "現役生活"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "現役生活",
      "salience": 0.071077295,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 48,
            "content": "ホテル"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "ホテル",
      "salience": 0.06548602,
      "type": "LOCATION"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 255,
            "content": "上原"
          },
          "type": "PROPER"
        }
      ],
      "metadata": {},
      "name": "上原",
      "salience": 0.048504733,
      "type": "PERSON"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 108,
            "content": "突然"
          },
          "type": "PROPER"
        }
      ],
      "metadata": {},
      "name": "突然",
      "salience": 0.04439941,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 117,
            "content": "引退発表"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "引退発表",
      "salience": 0.038837597,
      "type": "EVENT"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 87,
            "content": "シーズン"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "シーズン",
      "salience": 0.038837597,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 147,
            "content": "報道陣"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "報道陣",
      "salience": 0.026602311,
      "type": "EVENT"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 264,
            "content": "冒頭"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "冒頭",
      "salience": 0.015536341,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 276,
            "content": "涙"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "涙",
      "salience": 0.015536341,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 420,
            "content": "声"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "声",
      "salience": 0.013333881,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 441,
            "content": "目"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "目",
      "salience": 0.013333881,
      "type": "OTHER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 510,
            "content": "人々"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "人々",
      "salience": 0.011643294,
      "type": "PERSON"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 30,
            "content": "20"
          },
          "type": "TYPE_UNKNOWN"
        }
      ],
      "metadata": {
        "value": "20"
      },
      "name": "20",
      "salience": 0.0,
      "type": "NUMBER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 156,
            "content": "200"
          },
          "type": "TYPE_UNKNOWN"
        }
      ],
      "metadata": {
        "value": "200"
      },
      "name": "200",
      "salience": 0.0,
      "type": "NUMBER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 192,
            "content": "21"
          },
          "type": "TYPE_UNKNOWN"
        }
      ],
      "metadata": {
        "value": "21"
      },
      "name": "21",
      "salience": 0.0,
      "type": "NUMBER"
    },
    {
      "mentions": [
        {
          "text": {
            "beginOffset": 318,
            "content": "21"
          },
          "type": "TYPE_UNKNOWN"
        }
      ],
      "metadata": {
        "value": "21"
      },
      "name": "21",
      "salience": 0.0,
      "type": "NUMBER"
    }
  ],
  "language": "ja"
}

テキトーに選んだニュースをエンティティ感情分析

引数を analyze-entity-sentiment にしてみたが、面白い結果にならなかった。使い方が悪い?

$ gcloud ml language analyze-entity-sentiment --content="巨人・上原浩治投手が20日、都内ホテルで引退会見を行った。シーズン序盤、突然の引退発表。会見場には報道陣200人超が詰めかけた。21年間に及んだ現役生活。会見場に上がった上原は冒頭から涙を流し、「本日をもちまして21年間の現役生活を終えたいなと思います」と挨拶すると、「えー…」と声を詰まらせ、目をぬぐった。「これまで自分に関わってもらった人々、方々、みんなに感謝したいと思います、ありがとうございました」と語った。"
{
  "entities": [
    {
      "mentions": [
        {
          "sentiment": {
            "magnitude": 0.0,
            "score": 0.0
          },
          "text": {
            "beginOffset": 0,
            "content": "巨人"
          },
          "type": "COMMON"
        },
        {
          "sentiment": {
            "magnitude": 0.0,
            "score": 0.0
          },
          "text": {
            "beginOffset": 9,
            "content": "上原浩治"
          },
          "type": "PROPER"
        }
      ],
      "metadata": {
        "mid": "/m/0c9lhg",
        "wikipedia_url": "https://en.wikipedia.org/wiki/Koji_Uehara"
      },
      "name": "上原浩治",
      "salience": 0.16061519,
      "sentiment": {
        "magnitude": 0.0,
        "score": 0.0
      },
      "type": "PERSON"
    },
    {
      "mentions": [
        {
          "sentiment": {
            "magnitude": 0.0,
            "score": 0.0
          },
          "text": {
            "beginOffset": 60,
            "content": "引退会見"
          },
          "type": "COMMON"
        }
      ],
      "metadata": {},
      "name": "引退会見",
      "salience": 0.140315,
      "sentiment": {
        "magnitude": 0.0,
        "score": 0.0
      },
      "type": "EVENT"
    },
:

Google Cloud Speech API: Qwik Start(日本語)

{
  "config": {
      "encoding":"FLAC",
      "language_code": "en-US"
  },
  "audio": {
      "uri":"gs://cloud-samples-tests/speech/brooklyn.flac"
  }
}
  • POST
curl -s -X POST -H "Content-Type: application/json" --data-binary @request.json "https://speech.googleapis.com/v1/speech:recognize?key=${API_KEY}"`
  • レスポンス確認
{
  "results": [
    {
      "alternatives": [
        {
          "transcript": "how old is the Brooklyn Bridge",
          "confidence": 0.9835046
        }
      ]
    }
  ]
}

go modules quick start

go modules quick start したときのメモを張り付け

github.com

quick start

プロジェクトディレクトリ作成

> md tmp\scratchpad\hello
> cd tmp\scratchpad\hello

モジュール初期化。go.mod ができるので内容確認

>go mod init github.com/you/hello
go: creating new go.mod: module github.com/you/hello

>cat go.mod
module github.com/you/hello

go 1.12

hello.go 作成

package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Hello())
}

build と 実行。

> go build
go: finding rsc.io/quote v1.5.2
go: downloading rsc.io/quote v1.5.2
go: extracting rsc.io/quote v1.5.2
go: finding rsc.io/sampler v1.3.0
go: finding golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: downloading rsc.io/sampler v1.3.0
go: extracting rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go: extracting golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

> hello.exe

Hello, world.

go.mod 確認

>cat go.mod
module github.com/you/hello

go 1.12

require rsc.io/quote v1.5.2

GOHOME\pkg\mod に依存物がある。

GOHOME\pkg\mod\rsc.io\quote@v1.5.2
GOHOME\pkg\mod\rsc.io\sampler@v1.3.0

go list -m all。依存モジュールリストってところ?

>go list -m all
github.com/you/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0

-u をつけるとアップグレードできるか確認できる。sampler に v1.99.99 がある。

>go list -u -m all
go: finding rsc.io/sampler v1.99.99
go: finding golang.org/x/text v0.3.2
github.com/you/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c [v0.3.2]
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0 [v1.99.99]

バージョン指定(アップグレード)でgo get。rsc.io/quote v1.5.2 が依存しているのは sampler@v1.3.0 じゃないの?こんなことできるのか?と思いつつやったらできた。 リストされたのは 1.99.99 で、1.3.0 はなかった。 モジュール間の依存バージョンが微妙に異なるときどうなる?後のほうを使う?後方互換性大事ってこと?

>go get rsc.io/sampler@v1.99.99
go: downloading rsc.io/sampler v1.99.99
go: extracting rsc.io/sampler v1.99.99

>go list -u -m all
github.com/you/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c [v0.3.2]
rsc.io/quote v1.5.2
rsc.io/sampler v1.99.99

GOHOME\pkg\mod に sampler@v1.99.99 ができている。

GOHOME\pkg\mod\rsc.io\quote@v1.5.2
GOHOME\pkg\mod\rsc.io\sampler@v1.3.0
GOHOME\pkg\mod\rsc.io\sampler@v1.99.99

バージョン指定(ダウングレード)でgo get。これがもとの状態。

>go get rsc.io/sampler@v1.3.0

>go list -u -m all
github.com/you/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c [v0.3.2]
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0 [v1.99.99]

さらにバージョン指定(ダウングレード)でgo get。samplerを1.2.0に下げたら、quote も下がった。quote をバージョン高->低で検索しているのがわかる。面白い。 go.mod では、sampler@v1.2.0 がindirectとマークされている。

>go get rsc.io/sampler@v1.2.0
go: finding rsc.io/sampler v1.2.0
go: finding rsc.io/quote v1.5.1
go: finding rsc.io/quote v1.5.0
go: finding rsc.io/quote v1.4.0
go: finding rsc.io/sampler v1.0.0
go: downloading rsc.io/sampler v1.2.0
go: extracting rsc.io/sampler v1.2.0

>go list -u -m all
github.com/you/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c [v0.3.2]
rsc.io/quote v1.4.0 [v1.5.2]
rsc.io/sampler v1.2.0 [v1.99.99]

>cat go.mod
module github.com/you/hello

go 1.12

require (
        rsc.io/quote v1.4.0
        rsc.io/sampler v1.2.0 // indirect
)

BigQuery ML 予測モデルによるタクシー運賃の予測(日本語)

機械学習している感はないが、なんとか雰囲気は分かるようになってきた。

ながれ

  • ニューヨーク市のタクシーデータの探索
    • 質問: 2015 年のイエロー タクシーの毎月の賃走回数は?
    • 質問: 2015 年のイエロー タクシーの平均速度は?
  • モデル作成
    • (モデル・学習の)目的を特定する
    • 特徴を選択し、トレーニング データセットを作成する
    • モデルを格納する BigQuery データセットを作成する
    • BQML モデルタイプを選択し、オプションを指定する
      • モデル作成
  • 分類モデルの性能を評価する
    • 性能の基準を選択する
  • タクシー運賃を予測する
  • モデルの性能を向上させる
    • BigQuery の地理空間機能を活用して、ML モデルの精度を高める方法
  • 詳細情報
  • 探索できるその他のデータセット
    • シカゴのタクシー運賃を予測する場合

今回のデータ

ニューヨーク市が提供する一般公開データセットだそうです。

質問: 2015 年のイエロー タクシーの毎月の賃走回数は?

月別ピックアップ回数を求めている

#standardSQL
SELECT
  TIMESTAMP_TRUNC(pickup_datetime, -- ピックアップ日時 -> ピックアップ月
    MONTH) month,
  COUNT(*) trips                   -- 件数
FROM
  `bigquery-public-data.new_york.tlc_yellow_trips_2015`
GROUP BY
  1
ORDER BY
  1

質問: 2015 年のイエロー タクシーの平均速度は?

時刻別の平均速度を求めている

#standardSQL
SELECT
  EXTRACT(HOUR
  FROM
    pickup_datetime) hour,
  ROUND(AVG(trip_distance / TIMESTAMP_DIFF(dropoff_datetime,
        pickup_datetime,
        SECOND))*3600, 1) speed
FROM
  `bigquery-public-data.new_york.tlc_yellow_trips_2015`
WHERE
  trip_distance > 0
  AND fare_amount/trip_distance BETWEEN 2
  AND 10
  AND dropoff_datetime > pickup_datetime
GROUP BY
  1
ORDER BY
  1

目的を特定する

機械学習モデルを BigQuery 内に作成し、過去の賃走データセットに基づいてニューヨーク市のタクシー運賃を予測します。 乗車前に運賃を予測できれば、乗客とタクシー会社の双方が、より効率的に乗車・配車の計画を立てられるようになります。

特徴を選択し、トレーニング データセットを作成する

フィールド一覧を確認しろ、と言っているがリンク先を確認できないので雰囲気で。

ニューヨーク市のイエロー タクシーのデータセットは市が提供する一般公開データセットで、BigQuery に読み込まれ、自由に探索できるようになっています。 フィールドの全一覧をこちらで確認してから、データセットをプレビューして、機械学習モデルが過去のタクシー賃走と運賃の関係を理解するのに役立つ特徴を見つけます。

特徴

  • 通行料の金額
  • 運賃の金額
  • 時間帯
  • 乗車場所
  • 降車場所
  • 乗客人数
#standardSQL
WITH params AS (
    SELECT
    1 AS TRAIN,
    2 AS EVAL
    ),

  daynames AS
    (SELECT ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'] AS daysofweek),

  taxitrips AS (
  SELECT
    (tolls_amount + fare_amount) AS total_fare, -- 通行料の金額(通行料+運賃 チップは入れない)
    daysofweek[ORDINAL(EXTRACT(DAYOFWEEK FROM pickup_datetime))] AS dayofweek, -- 曜日
    EXTRACT(HOUR FROM pickup_datetime) AS hourofday, -- 時間帯
    pickup_longitude AS pickuplon, -- 乗車場所
    pickup_latitude AS pickuplat,
    dropoff_longitude AS dropofflon, -- 降車場所
    dropoff_latitude AS dropofflat,
    passenger_count AS passengers -- 乗客人数
  FROM
    `nyc-tlc.yellow.trips`, daynames, params
  WHERE
    trip_distance > 0 AND fare_amount > 0  -- 距離, 運賃 がゼロは除く
    AND MOD(ABS(FARM_FINGERPRINT(CAST(pickup_datetime AS STRING))),1000) = params.TRAIN -- データの 1/1000 のみを抽出(TRAINはトレーニング対象か)
  )

  SELECT *
  FROM taxitrips

チップは入れないって、チップまで記録されているのか、このデータには。チップも課税対象なんだからそらそうか。

モデル作成

前クエリの #standardSQL を置き換える。

CREATE or REPLACE MODEL taxi.taxifare_model
OPTIONS
  (model_type='linear_reg', labels=['total_fare']) AS

WITH params AS (
    SELECT
    1 AS TRAIN,
    2 AS EVAL
    ),

  daynames AS
    (SELECT ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'] AS daysofweek),

  taxitrips AS (
  SELECT
    (tolls_amount + fare_amount) AS total_fare,
    daysofweek[ORDINAL(EXTRACT(DAYOFWEEK FROM pickup_datetime))] AS dayofweek,
    EXTRACT(HOUR FROM pickup_datetime) AS hourofday,
    pickup_longitude AS pickuplon,
    pickup_latitude AS pickuplat,
    dropoff_longitude AS dropofflon,
    dropoff_latitude AS dropofflat,
    passenger_count AS passengers
  FROM
    `nyc-tlc.yellow.trips`, daynames, params
  WHERE
    trip_distance > 0 AND fare_amount > 0
    AND MOD(ABS(FARM_FINGERPRINT(CAST(pickup_datetime AS STRING))),1000) = params.TRAIN
  )

  SELECT *
  FROM taxitrips

分類モデルの性能を評価する

線形回帰モデルには、二乗平均平方根誤差(RMSE)などの損失指標を使用します。RMSE が下がるまでトレーニングを続け、モデルを改善していきます。

#standardSQL
SELECT
  SQRT(mean_squared_error) AS rmse --- 二乗平均平方根誤差
FROM
  ML.EVALUATE(MODEL taxi.taxifare_model,  -- ML.EVALUATEは 評価
  (

  WITH params AS (
    SELECT
    1 AS TRAIN,
    2 AS EVAL
    ),

  daynames AS
    (SELECT ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'] AS daysofweek),

  taxitrips AS (
  SELECT
    (tolls_amount + fare_amount) AS total_fare,
    daysofweek[ORDINAL(EXTRACT(DAYOFWEEK FROM pickup_datetime))] AS dayofweek,
    EXTRACT(HOUR FROM pickup_datetime) AS hourofday,
    pickup_longitude AS pickuplon,
    pickup_latitude AS pickuplat,
    dropoff_longitude AS dropofflon,
    dropoff_latitude AS dropofflat,
    passenger_count AS passengers
  FROM
    `nyc-tlc.yellow.trips`, daynames, params
  WHERE
    trip_distance > 0 AND fare_amount > 0
    AND MOD(ABS(FARM_FINGERPRINT(CAST(pickup_datetime AS STRING))),1000) = params.EVAL  --- 評価対象を抽出
  )

  SELECT *
  FROM taxitrips

  ))

結果は 9.4。これを下げる方法を模索するのが「モデルの改善」なのだろう。

行  rmse
1   9.47663701977026

タクシー運賃を予測する

#standardSQL
SELECT
*
FROM
  ml.PREDICT(MODEL `taxi.taxifare_model`,  -- 予測
   (

 WITH params AS (
    SELECT
    1 AS TRAIN,
    2 AS EVAL
    ),

  daynames AS
    (SELECT ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'] AS daysofweek),

  taxitrips AS (
  SELECT
    (tolls_amount + fare_amount) AS total_fare,
    daysofweek[ORDINAL(EXTRACT(DAYOFWEEK FROM pickup_datetime))] AS dayofweek,
    EXTRACT(HOUR FROM pickup_datetime) AS hourofday,
    pickup_longitude AS pickuplon,
    pickup_latitude AS pickuplat,
    dropoff_longitude AS dropofflon,
    dropoff_latitude AS dropofflat,
    passenger_count AS passengers
  FROM
    `nyc-tlc.yellow.trips`, daynames, params
  WHERE
    trip_distance > 0 AND fare_amount > 0
    AND MOD(ABS(FARM_FINGERPRINT(CAST(pickup_datetime AS STRING))),1000) = params.EVAL
  )

  SELECT *
  FROM taxitrips

));

モデルの性能を向上させる

BigQuery の地理空間機能を活用して、ML モデルの精度を高める方法

このブログを見よとのこと。

towardsdatascience.com

復習 BQML で分類モデルを使用して訪問者の購入を予測する

復習ための素晴らしいエントリがあった!感謝!感謝!

qiita.com

qiita.com

その1は、どんなデータセットなのか読み取っている。 その2は、データセット分析続きとモデル作成/評価とモデル作成/評価。

1つ目のモデル

2つの特徴(変数)から生成。値の尺度なんかを設定しなくいいものなのかな?

  • fullVisitorId
  • totals.bounces 合計直帰数
  • totals.timeOnSite セッションの合計時間
CREATE OR REPLACE MODEL `ecommerce.classification_model`
OPTIONS
(
model_type='logistic_reg',
labels = ['will_buy_on_return_visit']
)
:
  (SELECT
    fullVisitorId,
    IFNULL(totals.bounces, 0) AS bounces,
    IFNULL(totals.timeOnSite, 0) AS time_on_site
  FROM
:

モデルの評価

期間が20170501から2か月になっている。この2か月をモデルに与えて性能を評価するということなのだろう。結果は 0.72 でまあまあ。

SELECT
  roc_auc,
  CASE
    WHEN roc_auc > .9 THEN 'good'
    WHEN roc_auc > .8 THEN 'fair'
    WHEN roc_auc > .7 THEN 'decent'
    WHEN roc_auc > .6 THEN 'not great'
  ELSE 'poor' END AS model_quality
FROM
  ML.EVALUATE(MODEL ecommerce.classification_model,  (

SELECT
  * EXCEPT(fullVisitorId)
FROM

  # features
  (SELECT
    fullVisitorId,
    IFNULL(totals.bounces, 0) AS bounces,
    IFNULL(totals.timeOnSite, 0) AS time_on_site
  FROM
    `data-to-insights.ecommerce.web_analytics`
  WHERE
    totals.newVisits = 1
    AND date BETWEEN '20170501' AND '20170630') # eval on 2 months
  JOIN
  (SELECT
    fullvisitorid,
    IF(COUNTIF(totals.transactions > 0 AND totals.newVisits IS NULL) > 0, 1, 0) AS will_buy_on_return_visit
  FROM
      `data-to-insights.ecommerce.web_analytics`
  GROUP BY fullvisitorid)
  USING (fullVisitorId)

));

2つ目のモデル

totals.bounces, totals.timeOnSite にいろんな変数を加えてモデルを生成している。

CREATE OR REPLACE MODEL `ecommerce.classification_model_2`
OPTIONS
  (model_type='logistic_reg', labels = ['will_buy_on_return_visit']) AS

WITH all_visitor_stats AS (
SELECT
  fullvisitorid,
  IF(COUNTIF(totals.transactions > 0 AND totals.newVisits IS NULL) > 0, 1, 0) AS will_buy_on_return_visit
  ## totals.transactions 取引回数
  ## totals.newVisits 最初の訪問の場合は「1」、そうでない場合は「null」
  ## 帰った来て買ったら1
  FROM `data-to-insights.ecommerce.web_analytics`
  GROUP BY fullvisitorid
)

# add in new features
SELECT * EXCEPT(unique_session_id) FROM (

  SELECT
      CONCAT(fullvisitorid, CAST(visitId AS STRING)) AS unique_session_id,

      # labels
      will_buy_on_return_visit,
      ## 前段selectで生成

      MAX(CAST(h.eCommerceAction.action_type AS INT64)) AS latest_ecommerce_progress, 
      ## アクション タイプ。商品一覧のクリックスルー = 1、商品詳細表示 = 2、カートに商品を追加 = 3、
      ## カートの商品を削除 = 4、決済 = 5、購入の完了 = 6、払い戻し = 7、決算オプション = 8、不明 = 0

      # behavior on the site
      IFNULL(totals.bounces, 0) AS bounces,
      ## セッション開始後にすぐに離脱した場合、値は「1」となります。そうでない場合は「null」
      IFNULL(totals.timeOnSite, 0) AS time_on_site,
      ## セッションの合計時間(秒数)
      totals.pageviews,
      ## セッション中の合計ページビュー数。

      # where the visitor came from
      trafficSource.source,
      ## トラフィックの参照元
      trafficSource.medium,
      ## トラフィック参照元のメディア
      channelGrouping,
      ## このビューのエンドユーザーのセッションと関連付られたデフォルトのチャネル グループ

      # mobile or desktop
      device.deviceCategory,
      ## 端末の種類(モバイル、タブレット、PC)

      # geographic
      IFNULL(geoNetwork.country, "") AS country
      ## IP アドレスに基づいて特定されたセッションの起点となる国

  FROM `data-to-insights.ecommerce.web_analytics`,
     UNNEST(hits) AS h

    JOIN all_visitor_stats USING(fullvisitorid)

  WHERE 1=1
    # only predict for new visits
    AND totals.newVisits = 1
    AND date BETWEEN '20160801' AND '20170430' # train 9 months

  GROUP BY
  unique_session_id,
  will_buy_on_return_visit,
  bounces,
  time_on_site,
  totals.pageviews,
  trafficSource.source,
  trafficSource.medium,
  channelGrouping,
  device.deviceCategory,
  country
);

モデルの評価結果は 0.91 で good。