【リアイム】


タップ(クリック)で消す



よみこみ中…








閉じる
もくじ

リアイム

アウトプット&知識の共有

人工知能の中身を見てみる ニューラルネットワークの実装 イラスト 図でわかりやすく

f:id:a1t2s2u2:20211128155640j:plain


この記事ではわかりやすく人工知能の作り方の雰囲気を図を用いて実際にコードを書きながら用語も説明していきます。ですが、専門書のように難しくならないようにイラストや図でイメージをつかめるようにしています。また、プログラムはコピーしてご自身の環境で実行することができます。
実行結果を掲載しているので実行しなくても大丈夫です。


概要を知りたい場合はこちらの記事をお読みください。
riaimu.hateblo.jp
riaimu.hateblo.jp



もともとは有料記事を作ってみたいと思ってnoteに書き始めたのですが、多くの人に見てもらいたくなり、はてなブログに投稿しました。
そのせいで本文は1万文字にもなりましたが、その分深くリアルな中身を知れると思うのでゆっくり読み進めてみましょう。




人工知能を学びたい方へ




対象者

f:id:a1t2s2u2:20211128154315j:plain

  • 人工知能に興味がある
  • 中身を見てみたい
  • 実際のコードで学びたい

プログラムを理解せずとも、簡単に仕組みを知ることで正しく人工知能と接することができます。そのため、全ての人に知ってもらいたいです。

プログラムの言語はPythonです。








基本的な仕組み

人工知能の基本的な仕組みからみていきましょう。
実はAIは人間の脳の構造をまねして作られています。脳の神経細胞である「ニューロン」は0.1mm〜0.005mmの大きさで、ヒトの脳全体では約860億個にのぼります。


たくさんのニューロンは繋がっていて、電気信号を送り合います。

f:id:a1t2s2u2:20211128152328p:plain

これを簡単な図にしてみます。

f:id:a1t2s2u2:20211128152339p:plain

この図ではAとBの2つのニューロンがαとβに接続しています。
wという値は重みを表しています。

Aにフォーカスして値の動きを見てみましょう。
αの値にAの値とw1(重み)の積とBの値とw3の積を送信します。

つまり、数式で表すとこうなります。

f:id:a1t2s2u2:20211128152351p:plain

※本当は活性化関数や閾値などがあります。今回は簡略化のため省略します。

実際に値を代入して計算してみましょう。

f:id:a1t2s2u2:20211129213418p:plain

f:id:a1t2s2u2:20211129213453p:plain

計算結果が出てきましたね。
この結果もまたAやBのような値となり、次のニューロンへ送信されます。

この計算をプログラムで表現してみましょう。​

NeuronA = 2.0
NeuronB = 1.0

w1 = 1.0
w2 = 3.0
w3 = 2.0
w4 = 2.0

α = NeuronA * w1 + NeuronB * w3
β = NeuronA * w2 + NeuronB * w4

print("α : "+str(α))
print("β : "+str(β))

- - - - - - - - - - - 

『 出力結果 』
α : 4.0
β : 8.0


しっかり計算されていますね。



ニューラルネットワーク


ニューロンは膨大な量でネットワークを構築しています。このことを「ニューラルネットワーク」といい、縦列のことを層と呼びます。また、全体を大きく入力層・中間層・出力層の3つに分けることができます。

f:id:a1t2s2u2:20211128152431p:plain

※図ではみやすくするため一部接続を省いています。

ニューロンは必ずしも次の層の全てのニューロンに接続しなければならないわけではありません。中間層の層数はいくつでも構わず、層のニューロン数も指定はありません。一つ一つの処理は全て同じで、先程の計算の繰り返しとなります。

補足ですが、脳の仕組みは完全に解析されたわけではありません。そのためめ仕組みを予想しての実装となります。

ニューロンをプログラムで表現

では、このネットワークをプログラムで表現してみましょう。
最初のプログラムではニューロンが4つだったため簡単でしたが、今回はたくさんあるため、ニューロンを配列として保存します。その中に値・重み・バイアス…などを設定します。(今回は値と重みのみ)

少し複雑なので図でイメージを掴みましょう。

f:id:a1t2s2u2:20211128152546p:plain

ニューロン一つをプログラムで表現する(辞書型)

NeuronA = {
    "v" : 2.0 ,
    "w" :  [2.0 , 3.0],
}


 v:value(値)
 w:weight(重み)

の頭文字をとっています。
重みは接続の数だけ必要なので、配列を用いて表現します。


ニューラルネットワークの実装

では、このニューロンがたくさんあるニューラルネットワークを表現してみましょう。

※ここからプログラムが多めです。
プログラムをコピーして実際に御自身の環境で実行してみてください。

まずはネットワークの形状を設定します。
それぞれの深さ(列にあるニューロン数)の配列で表現します。この配列の要素数だけネットワークの層があるということになります。
今回は各層にニューロン

 入力層:3
 中間層:4,4,4
 出力層:3
個あります。

# [入力層, 中間層, 出力層]
network_shape = [34,4,43]

ニューロンを生成する関数を作成します。
初期のv(値)は0で、重みは乱数で生成します。(今回は0から2までの有効数字2桁)

# 引数としてlayer_num(層の番号)を設定し、wの配列を作る際に使用する
def make_neuron(layer_num):
    new_neuron = {
        "v" : 0 ,
        "w" : make_w(layer_num) ,
    }
    return new_neuron

少し難しいのがweightの配列で、要素数は次の層の深さ(ニューロン数)です。
では、weightの配列を作成する関数を作りましょう。
注意点として乱数を使用するのでimport randomを書いてください。

import random

def make_w( layer_num ):
    new_w = []
    # 出力層には重み配列を作らない
    if layer_num == len( network_shape ) - 1:
        return new_w
    else:
        # 次の層の深さ分だけ重みを追加
        for a in range( network_shape[layer_num+1] ):
            # wに要素を追加
            new_w.append(
                round( random.uniform( 0 , 2 ) , 2 )
            )
    return new_w

ニューロンの作成は無事に実装できましたね。

次に層を作る関数を実装してみましょう。

# この関数にも引数を層の番号にする
def make_layer(layer_num):
    new_layer = []
    # 深さの分だけニューロンを追加
    for a in range( network_shape[layer_num] ):
        new_layer.append( make_neuron(layer_num) )
    return new_layer

層の作成をたくさん実行することでニューラルネットワークを表現します。

def make_network():
    new_network = []
    for layer_num in rangelen(network_shape) ):
        new_network.append( make_layer(layer_num) )
    return new_network


お疲れ様でした。
これでニューラルネットワークを生成する準備は整いました。
これらの関数を実行可能な順番に整理してみます。

import random

# ネットワークの形状
network_shape = [34,4,43]


# 重みの生成
def make_w( layer_num ):
    new_w = []
    # 出力層には重み配列を作らない
    if layer_num == len( network_shape ) - 1:
        return new_w
    else:
        # 次の層の深さ分だけ重みを追加
        for a in range( network_shape[layer_num+1] ):
            # wに要素を追加
            new_w.append(
                round( random.uniform( 0 , 2 ) , 2 )
            )
    return new_w

# ニューロンの生成
def make_neuron(layer_num):
    new_neuron = {
        "v" : 0 ,
        "w" : make_w(layer_num) ,
    }
    return new_neuron

# 層の生成
def make_layer(layer_num):
    new_layer = []
    # 深さの分だけニューロンを追加
    for a in range( network_shape[layer_num] ):
        new_layer.append( make_neuron(layer_num) )
    return new_layer


# ネットワークの生成
def make_network():
    new_network = []
    for layer_num in rangelen(network_shape) ):
        new_network.append( make_layer(layer_num) )
    return new_network


# 実行する
network = make_network()
# 作ったネットワークの表示
print(network)

Visual Studioでの実際のプログラム(拡張機能あり)

f:id:a1t2s2u2:20211128152905p:plain

実行の結果はこうなりました。(実際のターミナルで例)

f:id:a1t2s2u2:20211128152933p:plain

ですが、見づらいですよね。
では作成したネットワークを見やすく表示する関数を作成しましょう。
もしかしたら、別に表示しなくてもいいかなって思うかもしれませんが、ここではネットワークの要素を扱う方法を学べますので、練習として勉強してみましょう。


目標は

1層目
{中身}
{中身}
{中身}

2層目…

とします。

ニューロンを取得するときの例を提示します。


1層目の最初のニューロンを取得してみます。
注意:配列は0から数えます。

# network[何層の][何番目]
first_neuron = network[0][0]

# ちなみに、vやwを取り出す方法は
first_neuron_v = network[0][0]["v"]
first_neuron_w = network[0][0]["w"]

では、for文を用いて表示をしてみましょう。

def show_network():
    # 層数だけ実行
    for a in rangelen(network) ):
        print(str(a)+"層目")
        # 深さ分だけ実行
        for b in rangelen(network[a]) ):
            print(network[a][b]["v"])

# これで実行
show_network()

実行結果(最初の画像とは別に実行したため値は違う)

f:id:a1t2s2u2:20211128153115p:plain

少し複雑ですね。実は次のように書くこともできます。

# 層番号を表示するならこちら
def show_network2():
    layer_num = 0
    for layer in network:
        print(str(layer_num)+"層目")
        for neuron in layer:
           print(neuron)
        ++layer_num
        # 改行
        print("\n")

"""
# 層番号を表示しないならこちら
def show_network2():
    for layer in network:
        for neuron in layer:
            print(neuron)
         print("\n")
"""

# これで実行
show_network2()

実行結果は同じです。
Visual Studioでの実際のプログラム(拡張機能あり)

f:id:a1t2s2u2:20211128153201p:plain

本当にお疲れ様でした。
ニューロンの扱いを学べたと思います。では、計算処理を行う関数を実装してみましょう。

計算処理の実装

最初の方にこのような計算の例がありました。

NeuronA = 2.0
NeuronB = 1.0

w1 = 1.0
w2 = 3.0
w3 = 2.0
w4 = 2.0

α = NeuronA * w1 + NeuronB * w3
β = NeuronA * w2 + NeuronB * w4

print("α : "+str(α))
print("β : "+str(β))

- - - - - - - - - 

『 出力結果 』
α : 4.0
β : 8.0

この計算処理は4つのニューロン間であるため全てを書いても良いですが、ニューラルネットワークには多くのニューロンがあります。
それらの計算処理を担当する関数を実装しましょう。

人工知能プログラムではnumpyを用いた行列計算で計算処理を行うことがあります。今回は仕組みを理解しやすくするためしっかり式で表します。

入力層に値を入力しないと0のままになるので、好きな数字を入力してください。今回は1.0 , 2.0 , 3.0としています。

# 計算処理を担当する
def calculation():
    # 入力層に値の入力
    network[0][0]["v"] = 1.0
    network[0][0]["v"] = 2.0
    network[0][0]["v"] = 3.0

    for layer_num in rangelen(network) ):
        for neuron in network[layer_num]:
            # ニューロンにある重みの分だけ実行
            for target_num in rangelen( neuron["w"] ) ):
                # 次のニューロンに値の送信
                # 送信先のニューロン = network[layer_num+1][target_num]
                network[layer_num+1][target_num]["v"] += neuron["v"] * neuron["w"][target_num]
                # 四捨五入
                network[layer_num+1][target_num]["v"] = round(network[layer_num+1][target_num]["v"] ,2)

    # ネットワークの表示
    show_network()

# 計算の実行
calculation()

今回は値を小数点2桁で四捨五入しています。(見やすいように)
先程作成したshow_networkで結果を表示してみましょう。

f:id:a1t2s2u2:20211128153249p:plain

このようになりました。
この出力層の値に応じて答えを決定します。

例えばですが、この場合だと「りんご」と判定する感じです。

出力層のw(重み)は必要ないのでニューロン作成時に作らないようにしてもいいと思います。そこはみなさんへの課題とします。







人工知能を学びたい方へ



もっと深く

実際の出力層には出力値の「名前」があります。計算をした後、出力値をsoft max関数に入れて出力結果を決定します。

このソフトマックス関数を数式で表すと次のようになります。

f:id:a1t2s2u2:20211128153629p:plain

yは確率を表し、数値は0〜1.0です。

xは対象の要素(出力層のニューロン)の値で、分母のΣではe(ネイピア数)を出力層の値乗したものを全て足し合わせています。

簡単な例を提示します。

f:id:a1t2s2u2:20211128153702p:plain

細かく言うと値は0.01321...です。
別にeを使わなくても大体の数値は得られると考えられますよね。


人工知能に用いる活性化関数には次のようなものがあります。

f:id:a1t2s2u2:20211128153723p:plain
f:id:a1t2s2u2:20211128153733p:plain

ピンク色はシグモイド関数と呼ばれ、0〜1の値を出力します。
青色はtanh関数(双曲線正接関数)と呼ばれ、-1〜1の値を出力します。シグモイド関数の値の範囲を拡張したような関数です。

これらの関数にもe(ネイピア数)が使われています。
どうしてこんなに人気なのかというと、微分するときに簡単だからです。


ネイピア数について

ネイピア数を習っていない方向けに簡単にご説明します。
e = 2.71828 18284...という定数で、eのx乗を微分積分)してもeのx乗となります。


ネイピア数の定義です。実際に値を代入して計算してみるとあの数値に近づくと思います。(他にも表し方もある)


f:id:a1t2s2u2:20211128153814p:plain


数3を勉強すればシグモイド関数や、tanh関数は自分で微分することができます。是非チャレンジしてみてください。


お疲れ様でした

どうでしたか?細かいプログラムの説明は省略していますが、人工知能の作り方の雰囲気を味わえたらいいなと思っています。


このコードは僕のオリジナルであるため、実際の現場で使用されているものとは異なります。
多くの場合ゼロから作らず、ライブラリを用いてAIを実装していて、中身を詳しく勉強しようという人は少ないかもしれません。
ですが、ブラックボックスとも呼ばれる人工知能の思考を実装することはとても楽しいですよ。結局のところ、数学的に誤差などを減らす方法であるためブラックボックスなのかと思ってしまいますがね。

また最近僕が実装している方法とは異なります。それは新しいアルゴリズムを応用しているためですが、この記事の方法は一般的なものです。




おすすめ記事

riaimu.hateblo.jp
riaimu.hateblo.jp
riaimu.hateblo.jp
riaimu.hateblo.jp
riaimu.hateblo.jp
riaimu.hateblo.jp




記事の内容に間違いがあった場合は、Twitterもしくはお問い合わせフォームからお伝えください。





人工知能を学びたい方へ