MNISTのデーターセットを使ってニューラルネットワークを実装しよう
この記事は以下の記事の続きになり、実際にPythonのKerasというライブラリーを用いてニューラルネットワーク(深層学習より層が少ない)を実装していきたいと思います。
Kerasとは
Kerasは、ディープラーニングモデルを作成するための高レベルのプログラミングインターフェースを提供するPythonライブラリです。複雑なディープラーニングモデルを比較的簡単に構築できます。
層、目的関数、最適化手法などを追加するのが簡単で、どう簡単なのかは実際にコードを見ながら理解して頂ければと思います。
MNISTとは
MNIST(Mixed National Institute of Standards and Technology database)は、手書き数字の大規模なデータセットで、データセットの簡単さとクリーンさにより、初心者が基本的なディープラーニングを学び、経験を積むのに役立ちます。
60,000の訓練画像と10,000のテスト画像から成り、手書きの数字(0から9)がグレースケール画像として表現されています。各画像は28x28ピクセルで、各ピクセルは0から255までの値を取り、画像内の特定の位置の強度を表します。
データセットの構造
データセットは上述の通り60,000の訓練画像と10,000のテスト画像が入っています。
ですが6万個.pngや.jpegの画像が入っているわけではなく、各ピクセルの白黒の強さが0から255の数字で表されていた6万個の28×28の配列が入っています。
まとめると、データセットは以下の構図になっています。
data_set = ( (A,B) , (C,D) )
A ... 訓練用データセット、6万個、白黒の強さが0から255の数字が入った28×28の配列
B ... 訓練用データセットのラベル、6万個、手書きの数字(0から9)の訓練用データセットの正解
C ... 検証用データセット、1万個、白黒の強さが0から255の数字が入った28×28の配列
B ... 検証用データセットのラベル、1万個、手書きの数字(0から9)検証用データセットの正解
実際にモデルを作成
ライブラリーのインストール
import keras
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import RMSprop
必要なライブラリーをインポートしていきます。大前提のKeras、MNISTのデーターセット(Kerasからダウンロードできます)、ニューラルネットワークを作る上で必要な機能をimportしてきます。
データセットの用意
#1 データセットを代入
(x_train, y_train), (x_test, y_test) = mnist.load_data()
#2 データを調整
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
#1 データセットを代入
上のデータセットの構造のまま以下のように代入されます。
A → x_train
B → y_train
C → x_test
D → y_train
#2 データを正規化
.reshapeは配列の形を変えるのに使われます。28×28の正方形のピクセル([1,2,3 ...28]が28個ある配列)を一時配列(リストの中にリストがない状態、[1,2,3,4]みたいな感じ)に直します。
理由は後で分かるのですが、今回のモデルは入力層を728個(28×28)用意したので、28個目の次が別の配列でなく、29個目につながって728まで続くように配列を変形します。
データを"正規化"というものをしていきます。
今回は0~255の数字を0~1の間に収めます。
ニューラルネットワークでは、入力データが0近辺で中心化されている(平均が0)と、または小さな範囲に収まっている(例えば-1から1、または0から1)と学習がうまく進むことが一般的に知られています。
自分の感覚的なイメージは、最後0~9のラベルに確率で出るので入力が158, 230などの大きな数字になると収束されるのが大変でモデルの精度が上がらないものと捉えています。
モデルの構造を実装
model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(10, activation='softmax'))
最初にどんなmodelにするかを決定します。
今回はadd()で追加した層を順番に実行していくSequentialを採用します。
model.add()で層(レイヤー)を追加していきます。
層は入力層、中間層を入れていきます。
層の情報はadd()の()の中の情報です。
意味はこんな感じです。
Dense ... その層に属する全てのニューロンが前の層の全てのニューロンと接続されている層
activation ... 活性化関数
input_shape ... 入力層
活性化関数についてはまた後日ブログを書ければと思います。
モデルを実行
model.compile(loss='categorical_crossentropy',
optimizer=RMSprop(),
metrics=['accuracy'])
model.fit(x_train, y_train,
batch_size=128,
epochs=10,
verbose=1,
validation_data=(x_test, y_test))
.compileは先ほどのコードをPCが実行できるようにする感じです。
.fitで実際に学習が始まります。
スコアの確認
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
このコードでモデルの結果を確認できます。
自分の実行したときはこんな数字でした。
Test loss: 0.09119302034378052
Test accuracy: 0.9825999736785889
スタック(Stack)とキュー(Queue)について
スタック(Stack)とキュー(Queue)は自分の知るがぎり簡単な部類のデータ構造ですが、どっちがどっちか間違いやすく意外と厄介です。この記事でっはその違いを簡単に紹介できればと思います。
スタック(Stack)とキュー(Queue)の違い
違いを一言で言うと、スタック(Stack)は筒で、キュー(Queue)は列です。
スタック(Stack)は筒で中に一度入れると取り出すのは筒の上にあるもの、すなわち最後に入れたものになります。
キュー(Queue)は列で最初に並んだ人が最初に呼ばれ(取り出され)ます。
スタック (Stack):
スタックは、「最後に入ったものが最初に出る」(Last In, First Out; LIFO)という原則に従ったデータ構造です。
スタックに要素を追加する操作を「プッシュ」(push)、要素を取り出す操作を「ポップ」(pop)と呼びます。
これは、物理的な積み重ね(例えば、皿の山)と考えるとわかりやすいです。最後に積み上げた皿が、最初に取り出されます。
class Stack():
def __init__(self):
self.stackList = []
def push(self, element):
self.stackList.append(element)
def remove(self):
self.stackList.pop()
def show(self):
print(self.stackList)
stack = Stack()
stack.push(1) # スタックに1を追加
stack.push(5) # スタックに5を追加
stack.push(7) # スタックに7を追加
stack.show() # スタックの状態を表示 ([1, 5, 7])
stack.remove() # スタックから最後に追加された要素(7)を削除
stack.show() # スタックの状態を表示 ([1, 5])
Stack
クラスが定義され、その中にpush
、remove
、show
というメソッドが定義されています。
push
: スタックに要素を追加します。remove
: スタックから最後に追加された要素を削除します。ただし、このメソッドは削除された要素を返さないので、pop
と名付ける方が一般的かもしれません。show
: スタックの現在の状態を表示します。
このremoveが最後に追加した要素を取り出すことから、「最後に入ったものが最初に出る」が実装されています。
キュー (Queue):
キューは、「最初に入ったものが最初に出る」(First In, First Out; FIFO)という原則に従ったデータ構造です。
キューに要素を追加する操作を「エンキュー」(enqueue)、要素を取り出す操作を「デキュー」(dequeue)と呼びます。
これは、物理的な行列(例えば、チケット売り場の列)と考えるとわかりやすいです。最初に列に並んだ人が、最初にサービスを受けるでしょう。
class Queue():
def __init__(self):
self.stackList = []
def enqueue(self, element):
self.stackList.append(element)
def dequeue(self):
self.stackList.pop(0)
def show(self):
print(self.stackList)
queue = Queue()
queue.enqueue(1) # キューに1を追加
queue.enqueue(5) # キューに5を追加
queue.enqueue(7) # キューに7を追加
queue.show() # キューの状態を表示 ([1, 5, 7])
queue.dequeue() # キューから最初に追加された要素(1)を削除
queue.show() # キューの状態を表示 ([5, 7])
Queue
クラスが定義され、その中にenqueue
、dequeue
、およびshow
というメソッドが定義されています。
enqueue
: キューの末尾に要素を追加します。dequeue
: キューの先頭から要素を削除します。ただし、このメソッドは削除された要素を返さないので、一般的なキューの実装では削除された要素を返します。show
: キューの現在の状態を表示します。
dequeuueでリストの一番最初に追加した要素を取り出すことで、「最初に入ったものが最初に出る」が実装されています。
機械学習、初学者必見!深層学習のアルゴリズムのイメージを書いてみた
機械学習、初学者の方へ
最初から複雑なモデルを作れるだけのプログラミングと数式の理解はハードルが高く難易度ががります。ですが簡単に何かおーっとなるようなAIモデルを作りたい。
そんな人にGoogleのTeachable Machineはおすすめです。
Teachable MachineとはChatGPTに聞くと以下のような回答を頂きました。
Teachable Machineは、Googleが提供するオンラインツールです。このツールは、機械学習を使用してユーザーがカスタムモデルを作成し、それを教えることができるように設計されています。ユーザーは、ビデオ、音声、または画像を使用して、機械学習モデルをトレーニングすることができます。
Teachable Machineは、機械学習の基本的な概念を理解していないユーザーにもアクセスしやすくするために開発されました。コーディングやデータサイエンスの知識がなくても、ビジュアルなインターフェースを通じて簡単にモデルを作成し、トレーニングすることができます。
要は知識のない人でもWEBでビジュアルで簡単に画像や音声の機械学習モデルを作れるというものです。
詳しい作り方はこの記事などを参考にされてはいかがでしょうか?
https://www.itmedia.co.jp/news/articles/2209/16/news038.html
この記事ではそこで使われる知識を紹介できればと思います。
機械学習とは
そもそもですが、機械学習とはめっちゃ簡単に言うと1000組のデータと予測したい物の答え(家賃を予測するAIの場合、データ:物件の広さ、答え:家賃 とか)を与えて、決められた数式の形を前提に最適な数式を探し出す物です。
例えば
y= ax+bを数式の形として与えた場合、データをxに入れてyの値を出した時、そのすべてのyとすべてのデータが最も近くなるaとbのを何100回、何1000回も計算して探すことです。
この数式の形の一つがニューラルネットワークで、ニューラルネットワークの中である条件を満たすものを深層学習(ディープラーニング)と言います。
アルゴリズムについて
おそらくTeachable Machineには深層学習(ディープラーニング)が使われていると思われます。
その深層学習について厳密性は省き、めちゃくちゃ簡単に説明できればと思います。
深層学習のイメージは以下になります。
入力層にデータが入り、→の方向に数が渡されていき出力層の分類する数分あるノード(丸の箱)にそれぞれの確率が入ります。
ポイント1
→はただ前のノードから次のノードに数字を渡すだけでなく、重り(通常wと書かれる)をかけた値を渡します。このw(矢印分存在)がy=ax+bのaとb、すなわり学習して適切な値を探すものになります。
ポイント2
グレーの⚪️の中には活性化関数と呼ばれるものが入っており、すべての矢印から送られてきた数の和をそのまま次のノードに送るわけではなく、すべての矢印から送られてきた数の和に活性化関数に通した数を次のノードに渡します。
活性化関数にはReLU関数、Sigmoid関数などがあります。
なぜ活性化関数をかける理由は今後の記事で書きたいと思いますが、一言で言うと、数式(数理モデル)が重りの掛け算とノードでの足し算でできた単調なものにしないことで柔軟なモデルを作るためです。
イメージのための簡単な例
例として飲み物の特徴から飲み物の種類を判定する深層学習があったとします。(画像の分類は深層学習のイメージを掴んでもらった後に書いてます。)
種類(出力)は[オレンジジュース, コーヒー, 水]として、特徴(入力)は[にがさ、水っぽさ、甘さ]とします。
深層学習では左の入力と呼ばれるところから[にがさ、水っぽさ、甘さ]のそれぞれの数字を受け取り矢印の先になるノードに数字が送られます。ただ矢印は送る時に重りと呼ばれる数をかけてノードに送ります。
この場合、にがさ:73、水っぽさ:16、甘さ:11の数字が入ったことになります。(答えの分類はコーヒーが正解です。)
なのでAのノード(箱)には
それぞれの入力層の数字にそれぞれの重り(矢印の横に書いた数字)がかけられてAに入ります
なのでA = 73×0.1+16×0.5+11×0.3 = 18.6がが入ります。
こんな感じで下のノードにも数字が入ります。
ノード内には活性化関数と呼ばれる関数が入っておりノードの値を関数に入れてノードの値を新しくします。
そして、ノード(Aとか)が活性化関数で出力した新しい値を次の右のノード達に重りをかけて渡します。
これを出力層にたどり着くまで繰り返します。
出力層に以下のような結果が出たとします。
[水:0.79,コーヒー:0.11,オレンジジュース:0.10]
分類結果は水になっています。これは正解のコーヒーとは異なります。
機械学習はこの違いをもとに重りを修正して新しくモデルを作ります。
これを繰り返しできた正答率の高いモデルを採用します。
画像処理の場合
画像処理の場合入力層に画像を入れるだけです。
実は画像はめちゃくちゃ小さな単色の正方形の集合でできており、入力層はそれぞれの一つの小さな正方形を入れます。ただ注意点はコンピューターは数字しか処理できないので(red, green, blue)を数字で表した数で正方形を表現して入れます。(今後もそちらもブログで書ければと思います。)
ここが深層学習の難しいところであり、面白いところなのですが、入力層に何を入れてもよく出力層に何を出してもよくて、ただ入力層から受けたデータを正しい答えを出す確率が高い数式を出すのが深層学習になります。
プロのエンジニアは何が違う?保守性です。
アメリカの大学を卒業して1年弱経ち、新卒でIT企業に入社しエンジニアとしてシステムの開発に約10ヶ月ほど従事しました。
そこで気づいたプロのエンジニアの書くコードと自分の大学時代に書いていたコードの書き方で明確な違いが何点か見つかったのでここで共有できればと思います。前提として複数人で実際にサービスとして提供されるシステムを作る際の実務での気づきになります。
実務未経験のエンジニアの方にはとても役にたつのではないかと思います。
保守性を高める
保守性とはバグや誤りの見つけやすさと仕様変更、機能追加の行いやすさです。
1、変数、定数、関数クラスの名前の付け方
これは実務経験がなくても大学などで教授に指摘されたり、自分でも気づいていたりするかもしれませんが、変数などの名前の付け方を適当にすると面倒なことが起こります。
個人の開発でも半年前のコードを読んだ時に解読できないという経験をしたことは多いのでは中と思います。
書いた本人でさえ時間が経てばわからなくなるものを、書いたことすらない他のチームメイトが解読することはとても骨の折れる行為で時間がとてもかかります。この時間は自分が少し名前を意識するのにかかる時間と比べ物になりません。(またチームメイトは自分ともう一人とも限りません。)
そのため見ただけで変数や関数などの意味がわかりやすい名前の付け方は意識することを要求されます。
例)
× num
○ totalUserNumber
バグがあった際も、仕様変更、機能追加の際も基本的に既存のコードを見るので、保守性の全てに通ずるところがあります。
名前の付け方のルールは具体的な単語を入れるだけでなく、単語の繋げ合わせ方でも以下のようなルールがあります。キャメルケース(camelCase)やパスカルケース(PascalCase)、スネークケース(snake_case)、ケバブケース(kebab-case)
この()内の英語の名前が表記の仕方になっています。
例えば、キャメルケース(camelCase)の場合は複数の単語のうち真ん中の先頭を大文字にして、スネークケース(snake_case)の場合は複数の単語を_で繋ぎます。
キャメルケース(camelCase)だとuserItemや、スネークケース(snake_case)の場合はcurrent_userのように。
ちなみに、自分のおすすめの方法は以下のように種類でケースを分けることです。
変数、定数、関数 ... キャメルケース(camelCase) 例)findAllUsers()
クラス ... パスカルケース(PascalCase) 例)LinkedList
DB関連の名前 ... スネークケース(snake_case) 例)user_password
HTMLのidなど ... ケバブケース(kebab-case) 例)title-container
普段HTMLは触らないのでこんな名前があるかはわからないのですが苦笑
2、Unitテストを入れる
Unitテストとは関数やクラスの一つ一つが正常にテストを動くかをチェックするものです。
機能でも複数の関数が連続として動くことで作用するものは多くあると思います。その際にそれぞれの関数自体は正常で動いていることが分かれば、接続さえ見れば良いことになるのでバグを解決しやすくなります。
また、ただそれぞれのユニット(関数、クラス)の正常さの保証だけでなく、仕様変更の際などでバグを残してしまっていた際にユニットテストが回っていれば気づ気安くなります。
3、設計
設計という言葉は粒度がありますが、今回はクラス設計や関数の切り分け方などです。
クラスなどでは単一責務という考え方があり、一つのクラスには一つの責任を持たせて、同時に異なり目的のことをさせないというものです。
これがなぜ保守性を高めるかですが、単一責務でない場合自分が想定していなかったエラーが起こりうる可能性が増えるからです。
これは自分もまだ勉強中で人に説明できるレベルではないので、また後日書き足させてください。
JavaScriptの基本文法をpythonと比較してマスターしよう!③
関数
定義の仕方
pythonの場合
def add(a,b):
return a+b
JavaScriptの場合
const add = function(a,b){
return a+b;
};
呼び出し方は両方とも同じです。
add(1,2)
add(1,2)
アロー関数
*新しいJavaScriptではアロー関数といって以下の書き方もできます
const add = (a,b)=>{
処理;
};
アローとは英語で矢印という意味があります。
クラス
クラスはReactやVue.jsを学ぶのに必須の知識になります。
pythonのクラスの例
class Person {
def __init__(self, name, age)
self.name = name
self.age = age
def info(self)
print('名前は'+self.name+'です')
print(self..age+'歳です')
animal = Person("Astrostory", 30);
animal.info()
JavaScriptのクラスの例
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
info() {
console.log(`名前は${this.name}です`);
console.log(`${this.age}歳です`);
}
}
const animal = new Person("Astrostory", 30);
animal.info();
pythonとJavaScriptのクラスの違い
- pythonのinit()がJavaScriptではconstructorみたいだね。
- pythonのselfがjavaScriptだとthisに変わる
- クラスや中のメソッドには ; をつけない
JavaScriptの基本文法をpythonと比較してマスターしよう!②
繰り返し処理
while文
num = 10
while num >0:
print(num)
num -= 1
while (num >0){
console.log();
num -= 1;
}
for文
for i in range(num):
print(i)
for (let num = 10; num<10; num -=1){
console.log(num);
}
この場合、num -=1をnum --と書くことも可能です。
もしnum+=1の場合はnum ++になります。
配列(リスト)
num = [1,2,3]
const num =[1,2,3];
要素の取得は同じ
num[i] *iにはインデックスの番号が入ります
要素の更新も同じ
num[i] = (変更したい値) *iにはインデックスの番号が入ります
配列の要素の数を取得
len(num)
num.length
*配列と繰り返し
pythonだとfor分の中に直接リストを入れることができましたが、JavaScriptではダメなようです
ただし、foreach文というものがあり以下のようなことはできます。
num.foreach((i) => { console.log(i); });
これはpythonでいうところの
for i in num:
print(i)
と同じ意味です。
オブジェクト(辞書、ディクショナリー)
customer = {"id":3, "name":"中山"}
const customer = {id:3, name="中山"};
pythonの場合はキーを""で囲わないとNameErrorが起こる
JavaScriptではキーではなく、プロパティーと呼ぶらしい
要素の取得
customer["id"]
customer.id
要素の更新
customer["id"] = 2
customer.id = 2;
続きはこちらから
JavaScriptの基本文法をpythonと比較してマスターしよう!①
上がpythonの書き方で、下がJavaScriptの書き方です。
出力
print()
console.log();
コメントアウト
#
//
計算
+ - * / %で、pythonとJavaScriprで変更なし
変数
変数の定義の仕方
blog = "アメリカの大学で奮闘中"
let blog = ""アメリカの大学で奮闘中";
変数の更新
blog = "IT企業で奮闘中"
blog = "IT企業で奮闘中"
JavaScriptでは変数を更新する際はletをつけない
定数
ん!定数って何?ってなりますよね。pythonでは基本的に定数を定義することは
できませんでした。ですがJavaScriptはできます。
const author = "Astrostory";
定数は定まった数と書くだけあって、author = "Other"のように更新することができないようになっています。
文字列の結合
"Okay "+name+" , I will do that"
`Okay ${name} , I will do that`
if文①
if num > 10:
print("10より大きい")
if (num > 10){
console.log("10より大きい");
}
JavaScriptはインデントエラーは起こらない
if文② ~else~
if num > 10:
print("10より大きい")
else:
print("10より大きくない")
if (num > 10){
console.log("10より大きい");
} else {
console.log("10より大きくない");
}
if文② ~else if~
if num > 10:
print("10より大きい")
elif num > 5:
print("5より大きい")
else:
print("5より大きくない")
if (num > 10){
console.log("10より大きい");
} else if (num > 5){
console.log("5より大きい");
} else {
console.log("5より大きくない");
}
真偽値
True False
true false
JavaScriptではそれぞれ小文字になっている
厳密等価演算子
=== 厳密に等しい
!== 厳密に異なる
かつ と または
and or
&& ||
Switch文
最近pythonでもできるようになりましたが、馴染みがない人も多いと思うので割愛
switch(変数名){
case 値1:
処理
case 値2:
処理
default:
処理
}
defaultはif文でいうところのelseに似たようなものになります。
pythonを使っていた人がミスしがちな点
文末の ;
続きはこちらから