今回は前回に引き続いて、ニューラルネットワークについての定義と少々実際のコーディングを交えてお伝えしていこうと考えています。専門的な話が多くなるので戸惑われる方もいるかとも思いますが、自己補填しつつ、最後までついてきていただけると幸いです。再三にわたっての話ですが、自己補填することがエンジニアとして活躍する一番の近道です。

線形回帰モデル

回帰は、従属変数と2つ以上の独立変数の関係を推定する必要がある場合に使用されます。線形回帰は、変数間の関係を直線的に近似する場合に適用される手法です。この方法は19世紀にさかのぼり、最も一般的な回帰法です。

注:線形関係とは、独立変数と従属変数の間に直接的な関係があることです。

変数間の関係を線形としてモデル化することで、従属変数を独立変数の加重和として表すことができます。つまり、各独立変数には重みというベクトルが掛けられます。重みと独立変数の他に、バイアスという別のベクトルも加えます。これは、他のすべての独立変数がゼロに等しいときの結果を設定します。

線形回帰モデルの構築方法の実例として、地域と家の古さに基づいて家の価格を予測するモデルを学習したいとします。あなたは、このモデルをあなたは、線形回帰を使ってこの関係をモデル化することにしました。次のコードブロックは、記載された問題に対する線形回帰モデルをどのように書くかを擬似的に示しています。

price = (weight_area * area) + (weights_age * age) + bias

上記の例では、weights_areaとweights_ageの2つの重みがあります。学習プロセスでは、モデルが正しい価格を予測できるように、重みとバイアスを調整します。そのためには、予測誤差を計算し、それに応じて重みを更新する必要があります。

以上が、ニューラルネットワークの仕組みの基本です。ここからは、Pythonを使ってこれらの概念を応用する方法を見ていきましょう。

Python AI:初めてのニューラルネットワーク構築の開始

ニューラルネットワークを構築する最初のステップは、入力データから出力を生成することです。そのためには、変数の加重和を作成します。まず必要なのは、PythonとNumPyで入力を表現することです。

NumPyでニューラルネットワークの入力をラップする

ネットワークの入力ベクトルを配列として表現するために、NumPyを使います。しかし、NumPyを使う前に、何が起こっているのかをよりよく理解するために、純粋なPythonでベクトルを使ってみるのは良いアイデアです。

この最初の例では、1つの入力ベクトルと他の2つの重みベクトルがあります。目的は、方向と大きさを考慮して、どちらの重みがより入力に近いかを見つけることです。ベクトルをプロットすると、このようになります。

weights_2は、同じ方向を向いていて、大きさも似ているので、より入力ベクトルに近いですね。では,Pythonを使ってどのベクトルが似ているかを調べるにはどうすればよいでしょうか?

まず、3つのベクトルを定義します。1つは入力用、もう2つは重み用です。そして、input_vector と weights_1 がどれだけ似ているかを計算します。そのためには、ドット積を適用します。すべてのベクトルは 2 次元のベクトルなので、以下がその手順です。

・input_vector の 1 番目のインデックスと weights_1 の 1 番目のインデックスを掛け合わせます。

・input_vector の 2 番目のインデックスに weights_2 の 2 番目のインデックスを乗算します。

・両方の乗算の結果を合計します。

IPythonのコンソールやJupyter Notebookを使って一緒に見ることができます。venvはPythonのバージョン3.3以降に同梱されており、仮想環境を作るのに便利です。

$ python -m venv ~/.my-env

$ python -m venv ~/.my-env, $ source ~/.my-env/bin/activate

上記のコマンドを使うと、まず仮想環境を作成し、次にそれをアクティベートします。次はpipを使ってIPythonコンソールをインストールする番です。NumPyとMatplotlibも必要なので、これらもインストールしておくと良いでしょう。

(my-env) $ python -m pip install ipython numpy matplotlib

(my-env) $ ipython

これで、コーディングを始める準備ができました。以下は、input_vectorとweights_1のドット積を計算するコードです。

In [1]: input_vector = [1.72, 1.23].

In [2]: weights_1 = [1.26, 0].

In [3]: weights_2 = [2.17, 0.32]

In [5]: first_indexes_mult = input_vector[0] * weights_1[0].

In [6]: second_indexes_mult = input_vector[1] * weights_1[1].

In [7]: dot_product_1 = first_indexes_mult + second_indexes_mult

 In [8]: print(f “The dot product is: {dot_product_1}”)

点積の結果は2.1672です。点積の計算方法がわかったところで、今度はNumPyのnp.dot()を使ってみましょう。np.dot()を使ってdot_product_1を計算する方法を紹介します。

In [9]: import numpy as np

In [10]: dot_product_1 = np.dot(input_vector, weights_1)

In [11]: print(f “The dot product is: {dot_product_1}”)

np.dot()は、先ほどと同じことを行いますが、今度は2つの配列を引数として指定するだけです。それでは,input_vectorとweights_2のドット積を計算してみましょう.

In [10]: dot_product_2 = np.dot(input_vector, weights_2)

In [11]: print(f “The dot product is: {dot_product_2}”)

今回の結果は、4.1259です。ドット積の別の考え方として、ベクトル座標の類似性をオンオフのスイッチとして扱うことができます。乗算の結果が0であれば、座標は似ていないと言うことになります。掛け算の結果が0以外であれば、「似ている」と言います。

このように、点積はベクトル間の類似性を緩やかに測定するものと考えることができます。掛け算の結果が0になるたびに、最終的な点積の結果は低くなります。例のベクトルに話を戻すと、input_vectorとweights_2のドット積は4.1259で、4.1259は2.1672より大きいので、input_vectorはweights_2とより似ているということになります。これと同じ仕組みをニューラルネットワークで使うことになります。

注:コピー&ペーストする必要がある場合は,各コードブロックの右上にあるプロンプト(>>)をクリックしてください。

このチュートリアルでは、2つの可能な結果しかない予測を行うモデルを学習します。出力結果は0か1のどちらかです。これは分類問題で、教師付き学習問題のサブセットで、入力と既知のターゲットを持つデータセットがあります。これらは、データセットの入力と出力です。

ターゲットとは、予測したい変数のことです。この例では、数字で構成されたデータセットを扱っています。これは実際の制作現場ではあまりないことです。通常、深層学習モデルの必要性がある場合、データは画像やテキストなどのファイルで提示されます。

予測をしてみよう

今回は初めてのニューラルネットワークなので、シンプルに2層だけのネットワークを構築します。これまでの説明では、ニューラルネットワークで使われる演算は、ドット積と和の2つだけでした。どちらも線形演算です。

層を増やしても線形演算しか使わないのであれば、各層は常に前の層の入力と何らかの相関があるので、層を増やしても効果はありません。このことは、複数の層を持つネットワークに対して、より少ない層で同じ結果を予測するネットワークが必ず存在することを意味します。

そこで、中間の層が入力と相関することもあれば、相関しないこともあるような操作を見つけたいと思います。

このような動作を実現するには、非線形関数を使用します。この非線形関数を活性化関数といいます。活性化関数には多くの種類があります。例えば、ReLU(rectified linear unit)は、すべての負の数をゼロに変換する関数です。つまり、ネットワークは負の数の重みを「オフ」にして、非線形性を加えることができるのです。

今回構築するネットワークでは、シグモイド活性化関数を使用します。この関数は、最後の層であるlayer_2で使用します。データセットには0と1の2つの出力しかありませんが、シグモイド関数は出力を0と1の間に限定します。 シグモイド関数を表す式は次のとおりです。

eはオイラー数と呼ばれる数学の定数で、np.exp(x)を使ってeˣを計算することができます。

確率関数は、ある事象の起こりうる結果について、その発生確率を与えます。データセットの出力は0と1の2通りしかなく、ベルヌーイ分布は同様に2通りの結果を持つ分布です。シグモイド関数は、問題がベルヌーイ分布に従っている場合に適しているので、ニューラルネットワークの最後の層で使用しているわけです。

この関数は、出力を0から1の範囲に制限するので、確率の予測に使用します。出力が0.5より大きければ「予測は1」、0.5より小さければ「予測は0」ということになります。

黄色い六角形は関数を、青い四角形は中間結果を表しています。さて、これらの知識をコードにしてみましょう。ベクトルをNumPyの配列でラップする必要があります。上の画像で紹介した関数を適用するコードは以下の通りです。

In [12]: # ベクトルをNumPyの配列で囲む

In [13]: input_vector = np.array([1.66, 1.56])

In [14]: weights_1 = np.array([1.45, -0.66])

In [15]: bias = np.array([0.0])

In [16]: def sigmoid(x):

…: return 1 / (1 + np.exp(-x))

In [17]: def make_prediction(input_vector, weights, bias):

…: layer_1 = np.dot(input_vector, weights) + bias

…: layer_2 = sigmoid(layer_1)

…: return layer_2

In [18]: prediction = make_prediction(input_vector, weights_1, bias)

In [19]: print(f “The prediction result is: {prediction}”)

 

生の予測結果は0.79で、0.5よりも高いので、出力は1になりました。次に、別の入力ベクトル、np.array([2, 1.5])で試してみましょう。他のパラメータはすべて同じなので、変数input_vectorだけを変更すればOKです。

In [20]: # input_vector の値を変更する

In [21]: input_vector = np.array([2, 1.5])

In [22]: prediction = make_prediction(input_vector, weights_1, bias)

In [23]: print(f “The prediction result is: {prediction}”)

今回、ネットワークは間違った予測をしてしまいました。この入力のターゲットは0なので、結果は0.5よりも小さくなるはずですが、生の結果は0.87でした。間違った推測をしたわけですが、その間違いはどの程度のものだったのでしょうか。次のステップは、それを評価する方法を見つけることです。

以上になります、いかがでしたでしょうか。またも中途半端な部分で結びになってしまい申し訳ございません。結果から言いますとあと2本の記事で本テーマは終わりを迎えれそうです。来週、残り2本を一挙に公開する予定なので、是非心待ちにしていただけると幸いです。