GCPの無料トライアル開始

AWSだけでなくGCPのMLサービスも自宅環境で試したくなったので、アカウント作成することにしました。

1. GCPのページにアクセス。
2. 「無料で開始」ボタンをクリック。
3. 国を選び、利用規約を読んで「続行」をクリック。 (Google アカウントはログイン済みで作業しました。)
4. お支払いプロファイルの選択。
AdSene用のプロファイルがあったのでそれを選びました。ない場合はおそらく新規に作るのだと思います。
5. アカウントの種類は個人。名前と住所は入力されていたので確認のみ。
6. 支払い方法でクレジットカード番号を入力

これで無料トライアル開始になったようです。

メッセージには次の様に出てきました。

ご登録いただきありがとうございます。無料トライアルには、12 か月間有効の $300 分のクレジットが含まれています。クレジットを使い切ってもご心配はいりません。自動請求を有効にするまで課金されることはありません。

(個人利用はAWSをメインに使ってるので、)試す分には$300を使い切ることはなさそうです。
ただ、12ヶ月経ったら有効にしないと有料のAPIは使えなくなるのかもしれませんね。
しっかり覚えておく必要がありそうです。

GCPでは利用の際に最初にプロジェクトを作ると本で読んだのですが、
「My First Project」 というプロジェクトが自動的に作成されていました。
特にプロジェクト名にこだわりはないのでしばらくこれを使っていこうと思います。

ついでに 「Cloud Natural Language API」 を有効にしたのでこれの使い方は今後の記事で書いていきます。

認証情報を取得しないとAPIを使えないので次にそれを行います。

1. 「APIの概要」に移動。
2. 左ペインの「認証情報」をクリック。
3. 「認証情報を作成」を選択。
4. APIキーを作成。

おそらくこれで使えるはず。

Amazon Comprehend でエンティティ認識

Amazon Comprehend シリーズの3記事目です。今回はエンティテイ認識をやってみます。
キーフレーズ抽出とかなりかぶるのですが、抽出した要素に対して人物なのか場所なのか時間なのかのフラグがつく点がメリットと言えるでしょう。

使うboto3のメソッドは、対象の文章が1つなら、detect_entities() で、
複数(ただし25個まで)なら batch_detect_entities()です。

比較のために前回の記事と全く同じテキストに対して実行してみました。


# サンプルテキスト。
# これで括弧内のテキストが連結されて1つの文字列になる。
text= (
    "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    "メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"
    "けれども邪悪に対しては、人一倍に敏感であった。"
    "きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。"
)

import boto3
comprehend = boto3.client("comprehend")
result = comprehend.detect_key_phrases(Text=text, LanguageCode="ja")

import boto3
comprehend = boto3.client("comprehend")

entities = comprehend.detect_entities(Text=text, LanguageCode="ja")
for entity in entities["Entities"]:
    print(entity)

"""
{'Score': 0.9998800754547119, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 0, 'EndOffset': 3}
{'Score': 0.9998433589935303, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 36, 'EndOffset': 39}
{'Score': 0.9998514652252197, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 49, 'EndOffset': 52}
{'Score': 0.7808283567428589, 'Type': 'QUANTITY', 'Text': '人一倍', 'BeginOffset': 90, 'EndOffset': 93}
{'Score': 0.6928854584693909, 'Type': 'DATE', 'Text': '未明', 'BeginOffset': 104, 'EndOffset': 106}
{'Score': 0.9995416402816772, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 106, 'EndOffset': 109}
{'Score': 0.506858766078949, 'Type': 'LOCATION', 'Text': '十里', 'BeginOffset': 124, 'EndOffset': 126}
{'Score': 0.9970796704292297, 'Type': 'LOCATION', 'Text': 'シラクス', 'BeginOffset': 132, 'EndOffset': 136}
{'Score': 0.6032651662826538, 'Type': 'LOCATION', 'Text': '市', 'BeginOffset': 137, 'EndOffset': 138}
"""

キーフレーズ抽出に比べて抽出された単語は少ないですが、 PERSON とか、 LOCATION といったTypeが付与されています。
ドキュメントによると、Typeは次の値を取りうる様です。

– ‘PERSON’
– ‘LOCATION’
– ‘ORGANIZATION’
– ‘COMMERCIAL_ITEM’
– ‘EVENT’
– ‘DATE’
– ‘QUANTITY’
– ‘TITLE’
– ‘OTHER’,

Amazon Comprehend でキーフレーズ抽出

前回の記事に続いて Amazon Comprehend の話です。
今度はキーフレーズ抽出をやってみます。

ドキュメントは同じところを参照します。
Comprehend — Boto3 Docs 1.14.32 documentation

Amazon Comprehend の特徴ページによると、
キーフレーズ抽出 API は、キーフレーズまたは会話のポイント、およびそれがキーフレーズであることを裏付ける信頼性スコアを返します。
とのことです。

早速やってみましょう。使うboto3のメソッドは、
detect_key_phrases() か、batch_detect_key_phrases()です。
それぞれ、単一テキストを対象とするか、テキストのリストを対象とするかの違いです。
ほとんど同じなので、今回は1テキストだけやってみることにしました。
サンプルにはいつのもメロスの最初の方の文章を使います。(全テキストで実行したら5000byteの制限によりエラーになりました。)


# サンプルテキスト。
# これで括弧内のテキストが連結されて1つの文字列になる。
text= (
    "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    "メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"
    "けれども邪悪に対しては、人一倍に敏感であった。"
    "きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。"
)

import boto3
comprehend = boto3.client("comprehend")
result = comprehend.detect_key_phrases(Text=text, LanguageCode="ja")

for key_phrase in result["KeyPhrases"]:
    print(key_phrase)

"""
{'Score': 0.9999992847442627, 'Text': 'メロス', 'BeginOffset': 0, 'EndOffset': 3}
{'Score': 0.5104176998138428, 'Text': 'かの', 'BeginOffset': 12, 'EndOffset': 14}
{'Score': 0.9680508971214294, 'Text': '邪智暴虐の王', 'BeginOffset': 14, 'EndOffset': 20}
{'Score': 0.9999995231628418, 'Text': 'メロス', 'BeginOffset': 36, 'EndOffset': 39}
{'Score': 0.9999402761459351, 'Text': '政治', 'BeginOffset': 41, 'EndOffset': 43}
{'Score': 0.9999988079071045, 'Text': 'メロス', 'BeginOffset': 49, 'EndOffset': 52}
{'Score': 0.9999657869338989, 'Text': '村の牧人', 'BeginOffset': 54, 'EndOffset': 58}
{'Score': 0.9999977350234985, 'Text': '笛', 'BeginOffset': 62, 'EndOffset': 63}
{'Score': 0.9999823570251465, 'Text': '羊', 'BeginOffset': 67, 'EndOffset': 68}
{'Score': 0.9975282549858093, 'Text': '人一倍', 'BeginOffset': 90, 'EndOffset': 93}
{'Score': 0.8310525417327881, 'Text': 'きょう未明', 'BeginOffset': 101, 'EndOffset': 106}
{'Score': 0.9997578263282776, 'Text': 'メロス', 'BeginOffset': 106, 'EndOffset': 109}
{'Score': 0.9997960925102234, 'Text': '村', 'BeginOffset': 110, 'EndOffset': 111}
{'Score': 0.9999336004257202, 'Text': '野', 'BeginOffset': 116, 'EndOffset': 117}
{'Score': 0.9991182088851929, 'Text': '十里', 'BeginOffset': 124, 'EndOffset': 126}
{'Score': 0.9819957613945007, 'Text': '此のシラクスの市', 'BeginOffset': 130, 'EndOffset': 138}
"""

ものすごく簡単に使えましたね。
一応重要な名詞は拾えてる様な気がしますし、王様は「邪智暴虐の王」として抽出できています。
ただ抽出したテキストだけなど意味がわからないので使い道に悩むところです。
BeginOffset と、 EndOffset は、その該当テキストを切り出すスライスに使える様です。

こんな感じです。


print(text[14: 20])
# 邪智暴虐の王

print(text[54: 58])
# 村の牧人

Amazon Comprehend でテキストのセンチメント分析

いつのまにか Amazon Comprehend の感情分析が日本語に対応しているのを見つけたので試してみました。

Amazon Comprehend というのは AWSが提供している自然言語処理の機械学習サービスです。
すでに学習済みのモデルが用意されており、利用者はデータを渡すだけで、感情分析(ポジネガ)や、キーフレーズの抽出ができます。

少し前までは日本語は言語判別のみ対応していて、センチメント分析するには一度対応してる言語に翻訳する必要があったはずなのですが、今は日本語も使えます。

ということで、Pythonからこれを動かしてみましょう。

・準備
AWSのPython SDKである boto3が動くように環境をつくっておきます。
主な作業は、 Comprehend の権限を持ったIAMの作成と、そのAccess Key と Secret Access Keyの設定でしょうか。
(僕は環境変数に入れました。)

これで動くはずなのでやってみます。
ドキュメントはこちらです。
Comprehend — Boto3 Docs 1.14.32 documentation

テキストは青空文庫の走れメロスを拝借しました。
まず、1文のポジネガを判定します。使うメソッドは、 detect_sentimentです。
本当に簡単に使えます。


import boto3

# 試すテキストを準備する
text1 = "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
text2 = "メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"

comprehend = boto3.client("comprehend")
for t in [text1, text2]:
    comprehend_result = comprehend.detect_sentiment(Text=t, LanguageCode="ja")
    print(t)
    print(comprehend_result["Sentiment"])
    for k, v in comprehend_result["SentimentScore"].items():
        print(f"    {k}: {v}")
    print()

# 以下出力結果
"""
メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。
NEGATIVE
    Positive: 0.00021381396800279617
    Negative: 0.8228716850280762
    Neutral: 0.17690636217594147
    Mixed: 8.087589776550885e-06

メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。
NEUTRAL
    Positive: 0.0011265947250649333
    Negative: 6.331767508527264e-05
    Neutral: 0.9988085031509399
    Mixed: 1.5915205722194514e-06
"""

みて分かる通り、 Positive(肯定的)/ Negative(否定的)/ Neutral(中立的)/ Mixed(混在) の4つの感情の割合を返してくれます。

複数のテキストをまとめて判定する関数も用意されています。
それが、batch_detect_sentimentです。

A list containing the text of the input documents. The list can contain a maximum of 25 documents. Each document must contain fewer that 5,000 bytes of UTF-8 encoded characters.

とある通り、 25個までのテキスト(それぞれ5000バイト未満)を順番に判定してくれます。

走れメロスの中に登場する会話文を順番に判定かけてみましょう。

準備として、以下のコードでメロスの会話文の一覧を取得します。


import requests
from bs4 import BeautifulSoup
import re

# 青空文庫 走れメロスのURL
url = "https://www.aozora.gr.jp/cards/000035/files/1567_14913.html"
response = requests.get(url)
# 文字化け対応
response.encoding = response.apparent_encoding
html = response.text

soup = BeautifulSoup(html)

# ルビを取り除く
for tag in soup.findAll(["rt", "rp"]):
    # タグとその内容の削除
    tag.decompose()

# ルビを取り除いたテキストを取得
text = soup.find(class_="main_text").get_text()

# 改行を消す。
text = text.replace("\r\n", "")
text = text.replace("\n", "")
# 全角スペースを消す
text = text.replace("\u3000", "")

# カッコの内側の文字列を抽出する正規表現パターン
speech_pattern = re.compile("「([^「」]+)」")

# カッコの内側の文字列取得
speech_texts = speech_pattern.findall(text)

全部で62文あるので3回に分けて取得します。
結果は扱いやすい様にPandasのDataFrameに入れておきましょう。


import pandas as pd

sentiment_list = []
positive_list = []
negative_list = []
neutral_list = []
mixed_list = []

for i in range(3):
    target_texts = speech_texts[25*i: 25*(i+1)]
    comprehend_result = comprehend.batch_detect_sentiment(
        TextList=target_texts,
        LanguageCode="ja"
    )
    result_list = comprehend_result["ResultList"]

    for r in result_list:
        sentiment_list.append(r["Sentiment"])

        positive_list.append(r["SentimentScore"]["Positive"])
        negative_list.append(r["SentimentScore"]["Negative"])
        neutral_list.append(r["SentimentScore"]["Neutral"])
        mixed_list.append(r["SentimentScore"]["Mixed"])

# 結果をDataFrameに変換
df = pd.DataFrame(
    {
        "text": speech_texts,
        "sentiment": sentiment_list,
        "positive": positive_list,
        "negative": negative_list,
        "neutral": neutral_list,
        "mixed": mixed_list,
    }
)

こうして、走れメロスの全台詞に対してポジネガのフラグをつけることができました。
ちなみに最もポジティブだったセリフはこちらです。

text ありがとう、友よ。
sentiment POSITIVE
positive 0.998629
negative 1.88228e-05
neutral 0.00135026
mixed 1.96162e-06

そして、最もネガティブだったのがこれ。

text ‘もう、駄目でございます。むだでございます。走るのは、やめて下さい。もう、あの方をお助けになることは出来ません。
sentiment NEGATIVE
positive 0.000276001
negative 0.996889
neutral 0.00283329
mixed 1.40147e-06
Name: 46, dtype: object

全テキストの結果を見ていくと流石に「ん?」と思う様なものもあるのですが、
概ねしっかりと感情分析ができてる様に思います。

Jupyter Notebookでインタラクティブに関数を実行する

Juputer Notebookで関数を実行するとき、結果を見ながら何度も引数を変更して再実行したくなることはないでしょうか。
毎回コード中の引数を書き換えて実行しても良いのですが、それをGUIで実行できると便利です。

その便利な機能が、ウィジェットとして用意されているのでそれを紹介します。
使うのは ipywidgets です。
ドキュメントはこちら: Jupyter Widgets
実は細かい設定でGUIをいろいろ作れるのですが、今回はとりあえず最低限度の機能でGUIで操作しながらグラフを描写する関数を実行します。

実行するサンプル関数としてリサージュ曲線を描く関数を用意しました。


import numpy as np
import matplotlib.pyplot as plt


def lissajous_curve(a, b, delta, color="b", title="リサージュ曲線"):

    t = np.linspace(0, 2, 601) * np.pi
    x = np.cos(a * t)
    y = np.sin(b * t + delta)

    fig = plt.figure(figsize=(8, 8))
    ax = fig.add_subplot(111, aspect="equal", title=title)
    ax.plot(x, y, color=color, )
    plt.show()

一応説明しておくと、これは変数$t$を媒介変数とし、
$x=A\cos(at), y=B\sin(bt+\delta)$で表される点をプロットした曲線です。
($A,B$はただ図形の縮尺が変わるだけなので今回のコードは$1$で固定しました。)

さて、通常は$a, b, \delta$や色とタイトルに値を指定して実行することで目当てのグラフを描写しますが、
これらの引数をGUIで指定できる様にします。

方法は簡単で、ipywidgets.interact に先ほどの関数と、指定できる引数の範囲を渡すだけです。
引数の範囲は次の様に指定できます。
– (開始, 終了, ステップ) のタプル(ステップを省略したら1)
– 指定できる値のリスト
– テキスト
– 表示用文字列: 渡す値の辞書


from ipywidgets import interact


interact(
    lissajous_curve,
    a=(1, 10, 1),
    b=(1, 10, 1),
    delta=(0, 2*np.pi, 0.01),
    title="リサージュ曲線",
    # ただの配列で指定することも可能。
    # color=["b", "k", "r", "g", "y", "m", "c"]
    # 表示用文字列: 渡す値の辞書も使える
    color={
        "Blue": "b",
        "Green": "g",
        "Red": "r",
        "Black": "k",
    }
)

実行したのがこちらです。
Blogなので画像キャプチャになってしまっていますが、実際はスライドバーやドロップダウンで操作し、グラフを書き直すことができます。

scikit-learnの学習済み決定木モデルから学習結果を抽出する

scikit-learnで学習した決定木の学習結果を確認するにはライブラリを使うのが便利ですが、
自分でも直接取得してみたかったので方法を調べてみました。

参考:
dtreevizで決定木の可視化
graphvizで決定木を可視化

とりあえず、 iris を学習しておきます。dtreevizの記事とパラメーターを揃えたので、
この後の結果はそちらと見比べていただくとわかりやすいです。
ただし、最初の分岐が2パターンあって乱数でどちらになるか決まるので、運が悪いと結果が変わります。


from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier

iris = load_iris()
clf = DecisionTreeClassifier(min_samples_split=5)
clf.fit(
    iris.data,
    iris.target
)

"""
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='gini',
                       max_depth=None, max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=5,
                       min_weight_fraction_leaf=0.0, presort='deprecated',
                       random_state=None, splitter='best')
"""

ロジスティック回帰などであれば、係数が coef_に入っているだけなので簡単なのですが、
決定木の場合読み解くのに少し手間がかかります。

その辺りのことは、ドキュメントにも
Understanding the decision tree structureとしてまとめてあるのでこちらも参照しながら読み解いてみました。

必要な情報は clf.tree_の属性としてまとまっているので順番に取り出してみます。


# ノードの数
n_nodes = clf.tree_.node_count
print(n_nodes)
# 13

# 各ノードに振り分けられた学習データの数。
node_values = clf.tree_.value

# 各ノードの左の子ノード。 葉の場合は -1
children_left = clf.tree_.children_left
print(children_left)
# [ 1 -1  3  4  5 -1 -1  8 -1 -1 11 -1 -1]

# 各ノードの右の子ノード。 葉の場合は -1
children_right = clf.tree_.children_right
print(children_right)
# [ 2 -1 10  7  6 -1 -1  9 -1 -1 12 -1 -1]

# 分割に使う特徴量。 葉の場合は-2
feature = clf.tree_.feature
print(feature)
# [ 3 -2  3  2  3 -2 -2  3 -2 -2  2 -2 -2]

# 分割に使う閾値。 葉の場合は-2
threshold = clf.tree_.threshold
print(threshold)
"""
[ 0.80000001 -2.          1.75        4.95000005  1.65000004 -2.
 -2.          1.55000001 -2.         -2.          4.85000014 -2.
 -2.        ]
"""

要するに、各ノードが配列の要素に対応しており、
それぞれ配列に、左の子ノード、右の子ノード、分割に使う特徴量、分割に使う閾値が順番に入っています。

これらの情報を日本語に変化して表示すると次の様になるでしょうか。


for i in range(n_nodes):
    print("\nノード番号:", i)
    if children_left[i] == -1:
        print("    このノードは葉です。")
        print("        予測結果: ")
        for v, t in zip(node_values[i][0], iris.target_names):
            print("            "+t+": ", round(v/sum(node_values[i][0]), 3))
    else:
        print(
            "    "+iris.feature_names[feature[i]],
            "が",
            round(threshold[i], 3),
            "未満の場合、ノード:",
            children_left[i],
            "に進み、それ以外の場合は、",
            children_right[i],
            "に進む。"
        )

出力結果のテキストはこちらです。


ノード番号: 0
    petal width (cm) が 0.8 未満の場合、ノード: 1 に進み、それ以外の場合は、 2 に進む。

ノード番号: 1
    このノードは葉です。
        予測結果: 
            setosa:  1.0
            versicolor:  0.0
            virginica:  0.0

ノード番号: 2
    petal width (cm) が 1.75 未満の場合、ノード: 3 に進み、それ以外の場合は、 10 に進む。

ノード番号: 3
    petal length (cm) が 4.95 未満の場合、ノード: 4 に進み、それ以外の場合は、 7 に進む。

ノード番号: 4
    petal width (cm) が 1.65 未満の場合、ノード: 5 に進み、それ以外の場合は、 6 に進む。

ノード番号: 5
    このノードは葉です。
        予測結果: 
            setosa:  0.0
            versicolor:  1.0
            virginica:  0.0

ノード番号: 6
    このノードは葉です。
        予測結果: 
            setosa:  0.0
            versicolor:  0.0
            virginica:  1.0

ノード番号: 7
    petal width (cm) が 1.55 未満の場合、ノード: 8 に進み、それ以外の場合は、 9 に進む。

ノード番号: 8
    このノードは葉です。
        予測結果: 
            setosa:  0.0
            versicolor:  0.0
            virginica:  1.0

ノード番号: 9
    このノードは葉です。
        予測結果: 
            setosa:  0.0
            versicolor:  0.667
            virginica:  0.333

ノード番号: 10
    petal length (cm) が 4.85 未満の場合、ノード: 11 に進み、それ以外の場合は、 12 に進む。

ノード番号: 11
    このノードは葉です。
        予測結果: 
            setosa:  0.0
            versicolor:  0.333
            virginica:  0.667

ノード番号: 12
    このノードは葉です。
        予測結果: 
            setosa:  0.0
            versicolor:  0.0
            virginica:  1.0

先日可視化した結果とバッチリ対応していますね。

NumPyのブロードキャストで変換できる型

NumPyを普段使いしてると便利な機能に、ブロードキャストがあります。
これは配列のサイズが違うもの通しを演算するときに、いい感じに小さい方を拡張して演算してくれるものです。

例えば、配列とスカラーの和や、行列とベクトルの和を次の様に計算してくれます。


a = np.array(range(4))
print(a)
# [0 1 2 3]

# 7 を [7, 7, 7, 7] として扱って足してくれる
b = a + 7
print(b)
# [ 7  8  9 10]

c = np.array(range(6)).reshape(2, 3)
print(c)
"""
[[0 1 2]
 [3 4 5]]
"""

d = np.array([5, 5, 5])

# [5, 5, 5] を [[5, 5, 5], [5, 5, 5]] として扱って足してくれる
e = c+d
print(e)
"""
[[ 5  6  7]
 [ 8  9 10]]
 """

本当にいい感じにやってくれるのであまり意識せずに使っていましたが、仕様を正確に把握しておきたかったので改めてドキュメントを読みました。
と言うのも、いつでも動くわけではありませんし、正方行列とベクトルの和の様にどちらの軸にブロードキャストされるか迷うことなどあるからです。


# 動かない例
a = np.array(range(6)).reshape(3, 2)
b = np.array(range(3))
a + b
# ValueError: operands could not be broadcast together with shapes (3,2) (3,)

# 行か列か引き伸ばされる方向がわかりにくい例
c = np.zeros(shape=(3, 3))
d = np.array(range(3))
print(c+d)
"""
[[0. 1. 2.]
 [0. 1. 2.]
 [0. 1. 2.]]
"""

ドキュメントはこちらです。
参考: Broadcasting

長いのですが、基本的なルールは General Broadcasting Rules に書かれてる次の法則だけです。
配列の次元数は後ろから順番に前に向かって比較されます。(長さが違う場合は、短い方に1が追加されて揃えられます。)
そして、それらの値が等しいか、もしくは一方が1であればブロードキャストされます。

ブロードキャストされるときは、値が1だった(もしくは無かった)次元の向きにデータがコピーされて拡張されます。

先ほどのエラーが起きた例で言えば、 (3, 2)次元と (3) 次元の 2と3が比較されてこれが等しくないからエラーになったわけですね。
その次の例についてはまず、
[0, 1, 2] (shapeは(3,))に、次元が一個追加されて、
[[0, 1, 2]] (shapeは(1, 3)) に変換され、それが各行にコピーされたので上の例の様な結果になっています。

先述のシンプルなルールさえ満たしていれば、次の例の様な少々無茶でイメージしにくい配列同士でもブロードキャストされます。


a = np.array(range(21)).reshape(1,1,3,1,7)
b = np.array(range(10)).reshape(2,1,5,1)

print(a.shape)
# (1, 1, 3, 1, 7)
print(b.shape)
# (2, 1, 5, 1)

c = a+b
print(c.shape)
# (1, 2, 3, 5, 7)

(1, 1, 3, 1, 7) と (2, 1, 5, 1) では長さが違うので、後者の方の先頭に1が挿入され
以下の二つになるわけですね。
(1, 1, 3, 1, 7)
(1, 2, 1, 5, 1)
これを順番に見ていくと、ブロードキャストのルールをみたいしているので足りない向きについてはデータがコピーされ、
和がとれているわけです。

外部リンクへのクリックをイベントとして計測する

このブログの改善のために、外部サイトへのリンクが(特に各種技術記事から公式のドキュメントへ)どれだけクリックされているのか計測しようと思います。
それが、GoogleタグマネージャーとGoogleアナリティクスの組みあわせでできるので設定していきます。

順番に設定していきます。

1. タグマネージャーのコンテナとワークスペースを選択。
2. 新しいタグを追加を選択し、タグに名前をつける。
3. タグの設定をクリック。
4. タグタイプは、 「Googleアナリティクス:ユニバーサル アナリティクス」を選択。
5. トラッキングタイプに「イベント」を選択し、以下の内容を入力。
カテゴリ: External link
アクション: {{Click URL}}
ラベル: {{Page Path}}
値: 1
非イタンラクション ヒット: 真
6. トリガーの追加を押し、トリガーの選択画面で右上の「+」を押す。
7. トリガーの設定で、クリックの下のリンクのみを選択。
8. 以下の内容設定
タグの配信を待つと妥当性をチェックは一旦無効のまま
一部のリンククリックを選択し、以下の二つを設定する
Click URL正規表現に一致 http.*
Click URL 含まない analytics-note.xyz
9. トリガーに名前をつける
10. 保存し、プレビューでテストして公開

しばらく経過を見てまた調整するかもしれませんが、一旦はこれで計測できる様になっているはずです。

Google タグマネージャー導入

タイトルの通り、このBlogでもGoogleタグマネージャーを使い始めました。
目的は主にGoogleアナリティクスでのイベント計測です。
多くの記事で参照している各種のドキュメントへのリンクや、画像へのアクセス状況を分析しより使いやすいサイトにしたいと思っています。

これまでは、GAは「Googleアナリティクス導入」の記事で書いた通り、
SEOのプラグインで設定していましたが、これがタグマネージャーによる管理に移行します。

以下、導入手順のメモです。

1. タグマネージャのサイトにアクセスし、Googleアカウントでログイン。
2. 「アカウントを作成するにはここをクリックしてください」 をクリック。
3. アカウント名(適当)、 国(日本)、コンテナ名(URLでも良いのですがサイト名にしました。)、ターゲットプラットフォーム(ウェブ)を入力。
4. 作成ボタンクリック。
5. 利用規約を読んで「はい」をクリック。
6. サイト内に貼り付ける様に指示されるコードを保存して「OK」で閉じる。 (GTM-******* と言うコードを後で使う。)

コンテナIDはタグマネージャーのホーム画面でいつでも確認できます。

次にこれをWordPressに設定します。
1. プラグインで Google Tag Manager を検索。
2. 今回はこちらをインストール。 Google Tag Manager
3. プラグインを有効化。
4. 「設定」「一般」の一番下に Google Tag Manager ID の設定ができているので、ここにコンテナIDを入力して保存。
(プラグインの設定ではないので注意。)

これでタグマネージャー自体の導入完了です。

次に、肝心のGoogleアナリティクスをタグマネージャーで設定します。

1. タグマネージャーの「ワークスペース」ページの「サマリー」を開く。
2. 「新しいタグ」 をクリック。
3. 「タグの設定」をクリックして「Google アナリティクス: ユニバーサル アナリティクス」 を選択する。
4. 「トラッキングタイプ」に「ページビュー」を選択。
5. Googleアナリティクス設定で、トラッキングコードを入力。 (UA-*********-* 形式の値)
6. 変数名を入れて保存。(とりあえず、「分析ノートUA」にしました。)
7. トリガーの設定。 「All Pages ページビュー」を選択。
8. タグに名前(分析ノートページビュー)を設定して保存。

この段階ではまだ公開されていないので、テストしてから公開します。
1. ワークスペースに戻ってプレピューをクリック。
2. そのブラウザで、このブログにアクセスすると画面下に発火したタグが確認できる。
(分析ノートページビューがFiredになっている)
3. プレビューモードを終了
4. 「公開」ボタンをクリック。
5. バージョン名と説明を求められるので入力し、もう一度「公開」をクリック。

最後に、今回に限り重要なのが、すでに直接設定してたGoogleアナリティクスの設定を消すこと。
SEOプラグインに設定してたUAの値を消しました。これをしないとpvが二重カウントになります。