説明
ニューラルネットワークを理解するため、Numpy、keras、Pytorchを使ってMNISTデータセットの手書き数字を識別してみました。
- 使用しているデータ
- Kaggle Digit Recognizer
- MNISTデータセットは、機械学習でとても有名なデータセットであり、0から9までの手書き数字のグレースケール画像が入っています。各画像は、縦28X横28合計784ピクセルとなっており、各ピクセルには一つ0から255のピクセル値をついています。そのピクセル値が大きいければ大きいほど暗いことを意味しています。
- Trainデータに関しては、先頭のlabel列はユーザーが描いた数字を表しており、残りの列は該当画像のピクセル値が格納されています。
- testデータに関してはTrainデータと同様に、画像のピクセル値が格納していますが、label列がついていないので、それを識別するのは今回のタスクです。
- training data,validate dataを用意します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19data = np.array(train)
m,n = data.shape
np.random.shuffle(data)
data_val=data[0:1000].T
Y_val = data_val[0]
X_val = data_val[1:n]
X_val = X_val/255.
print(data_val.shape,Y_val.shape,X_val.shape)
data_train = data[1000:m].T
Y_train = data_train[0]
X_train = data_train[1:n]
X_train = X_train/255.
_,m_train = X_train.shape
print(data_train.shape,Y_train.shape,X_train.shape)
#(785, 1000) (1000,) (784, 1000)
(785, 41000) (41000,) (784, 41000)1.EDA
まずkaggleからダウンロードしたtrain.csvとtest.csvを読み込んで、データの中身をいろいろ確認してみました。
- モジュールをインポートします。
1
2
3
4
5
6
7
8import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from collections import Counter
from pyg2plot import Plot
from pyecharts.charts import Bar,Line
from pyecharts import options as opts - データ件数
train:42000件訓練データ、785列、testデータと違って、label列がついています。
test:28000件テストデータ、784列1
2
3
4
5
6
7train.shape,test.shape
#((42000, 785), (28000, 784))
for i in train.columns:
if i not in test.columns:
print(i)
#label - 実際にどういう数字なのか、画像で見てみます。
1
2
3
4
5
6num=train.iloc[10]
pic=np.array(num)
pic=pic[1:785]
pic=pic.reshape(28,28)
plt.imshow(pic,cmap="magma")
plt.show() - 全体のvalue値の幅を見てみます。
- value値は0から255の間に落ちているので、後ほどの前処理ではvalue値を標準化処理にします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20scatter = Plot("Scatter")
df_scatter = pd.DataFrame(pic.reshape(392,2))
df_scatter_dict = df_scatter.to_dict(orient = 'dict')
scatter.set_options(
{
'appendPadding': 30,
'data': df_scatter.to_dict(orient='records'),
'xField': 0,
'yField': 1,
'size': [4, 30],
'shape': 'circle',
'pointStyle':{'fillOpacity': 0.8,'stroke': '#bbb'},
'xAxis':{'line':{'style':{'stroke': '#aaa'}},},
'yAxis':{'line':{'style':{'stroke': '#aaa'}},},
'quadrant':{
'xBaseline': 0,
'yBaseline': 0,
},
})
scatter.render()
- ラベルごとデータの件数を見てみます。
- 各ラベルのデータ件数が大きいな差がなく、均等的であることがわかりました。
1
2
3
4
5
6
7
8
9bar = (
Bar(init_opts=opts.InitOpts(width="620px", height="420px"))
.add_xaxis(list(train_count.keys()))
.add_yaxis('count', list(train_count.values()))
.set_global_opts(title_opts=opts.TitleOpts(title="count", subtitle=None))
.set_global_opts(title_opts={"text": "count", "subtext": None})
)
bar.render_notebook()
2.Numpyでニューラルネットワークを実装してみました
- 今回2層ニューラルネットワークを実装しようと思います。
- 活性化関数:sigmoid関数(h1)とsoftmax関数(h2)
- 損失関数:交差エントロピー誤差
- 事前に活性化関数、損失関数、labelのone-hot処理の関数を用意します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#h1 sigmoid関数
def sigmoid(x):
return 1/(1+np.exp(-x))
#back propagation用sigmoid関数の微分関数
def sigmoid_(x):
return sigmoid(x)*(1-sigmoid(x))
#h2 softmax関数
def softmax(z):
A = np.exp(z)/sum(np.exp(z))
return A
#one-hot 処理関数
def one_hot(Y):
one_hot_Y = np.zeros((Y.size, Y.max() + 1))
one_hot_Y[np.arange(Y.size), Y] = 1
one_hot_Y = one_hot_Y.T
return one_hot_Y
#損失関数
def cross_entropy(a, y):
return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a))) - w1,b1,w2,b2の初期化
1
2
3
4
5
6
7
8input_, hidden, output = 784, 100, 10
w1 = np.random.randn(hidden,input_)
b1 = np.random.randn(hidden,1)
w2 = np.random.randn(output,hidden)
b2 = np.random.randn(output,1)
print(w1.shape,b1.shape,w2.shape,b2.shape)
#(100, 784) (100, 1) (10, 100) (10, 1) - forwardの実現
1
2
3
4
5
6
7
8
9
10def forward(x,w1,w2,b1,b2):
a1 = np.dot(w1,x)+b1
z1 = sigmoid(a1)
a2 = np.dot(w2,z1)+b2
z2 = softmax(a2)
y_pred = z2
return a1,a2,z1,y_pred
a1,a2,z1,y_pred = forward(X_train,w1,w2,b1,b2)
print(a1.shape,a2.shape,z1.shape,y_pred.shape)
#(100, 41000) (10, 41000) (100, 41000) (10, 41000) - back propagatationの実現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def backward(z1,a1,y_pred,a2,x,y,w1,w2):
dz2 = y_pred - one_hot(y)
dw2 = (np.dot(dz2,z1.T))/m
db2 = (1/m)*np.sum(dz2,axis=1,keepdims=True)
da1 = np.dot(w2.T,dz2)
dz1 = da1*sigmoid_(a1)
dw1 =(1/m)*np.dot(dz1,x.T)
db1 = (1/m)*np.sum(dz1,axis=1,keepdims=True)
return dw1,db1,dw2,db2
def update(w1,b1,w2,b2,dw1,db1,dw2,db2,lr):
w2 -= lr*dw2
b2 -= lr*db2
w1 -= lr*dw1
b1 -= lr*db1
return w1,w2,b1,b2 - パラメータ最適化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# accuracy値の関数
def get_accuracy(predictions,y):
return np.sum(predictions == y)/y.size
def gradient_descent(x,y,w1,w2,b1,b2,iterations,lr):
accuracy_ = list()
for i in range(iterations):
a1,a2,z1,y_pred = forward(x,w1,w2,b1,b2)
dw1,db1,dw2,db2 = backward(z1,a1,y_pred,a2,x,y,w1,w2)
w1,w2,b1,b2=update(w1,b1,w2,b2,dw1,db1,dw2,db2,lr)
if i % 10 == 0:
print(f'第{(i/10)}回',i)
predictions = np.argmax(a2,0)
accuracy = round(get_accuracy(predictions,y),3)
accuracy_.append(accuracy)
print(accuracy)
return w2,b2,w1,b1,accuracy,accuracy_
w2,b2,w1,b1,accuracy,accuracy_ = gradient_descent(X_train,Y_train,w1,w2,b1,b2,5000,0.01)
#第0.0回 0
0.09409756097560976
第1.0回 10
0.1038780487804878
第2.0回 20
0.11382926829268293
第3.0回 30
0.1218780487804878
.... - accuracyの推移値
1
2
3
4
5
6
7
8
9
10x_axis = [i for i in range(len(accuracy_))]
y_axis = accuracy_
line1=(
Line()
.add_xaxis(x_axis)
.add_yaxis('accuracy',y_axis)
.set_global_opts(title_opts=opts.TitleOpts(title='accuracy'),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=20)))
)
line1.render_notebook()
3.Kerasでニューラルネットワークを構築てみました
- 考え方は基本Numpyでの構築と同様ですが、Sequentialモデルをもとに、.add()でレイヤーを積み重ねて、.compile()で訓練プロセスを設定します。
1
2
3
4
5
6
7
8model=Sequential() #
model.add(Dense(100,activation = 'relu'))
model.add(Dense(10,activation = 'softmax'))
model.compile(optimizer='adam',
loss ='sparse_categorical_crossentropy',
metrics ='acc')
model.fit(X_train.T,Y_train,epochs=60,batch_size=512) - testデータの予測し、Kaggleに提出します。
1
2
3result = model.predict(np.array(test))
result_=pd.DataFrame(results,index=list(range(1,len(results)+1))).reset_index().rename(columns = {'index':'ImageId',0:'Label'})
result_.to_csv('Digit Recognizer_version3(keras).csv',index=False)
4.Pytorchでニューラルネットワークを構築してみました
- データ集をPytorch専用のTensorに変換する
1
2
3
4
5
6
7
8
9
10
11import torch
from torch.utils.data import DataLoader
from torch import nn,optim
import torch.nn.functional as F
import torch.utils.data
X_train_torch = torch.from_numpy(X_train).float()
Y_train_torch = torch.from_numpy(Y_train).long()
X_val_torch = torch.from_numpy(X_val).float()
Y_val_torch = torch.from_numpy(Y_val).long()
X_train_torch.T.size(0),Y_train_torch.T.size(0)
#(41000, 41000) - Batchごとを行うため、TensorDatasetに格納します。
1
2
3
4
5train_dataset=torch.utils.data.TensorDataset(X_train_torch.T, Y_train_torch)
val_dataset=torch.utils.data.TensorDataset(X_val_torch.T, Y_val_torch)
train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=1000,shuffle=True)
val_loader = torch.utils.data.DataLoader(val_dataset,batch_size=1000,shuffle=True) - Pytorchのフォーマットに沿って、ニューラルネットワークの構築を行います。過学習を防ぐため、Dropoutを利用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class NN(nn.Module):
def __init__(self):
super().__init__()
self.layer1 = nn.Linear(784,100)
self.layer2 = nn.Linear(100,10)
self.dropout = nn.Dropout(p=0.2)
def forward(self,x):
x = x.view(x.shape[0],-1)
x = self.dropout(F.relu(self.layer1(x)))
x = F.softmax(self.layer2(x),dim=1)
return x - 損失関数と最適化手法を設定し、訓練プロセスを設定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47model = NN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(),lr=0.001)
epochs = 100
train_losses = []
test_losses = []
accuracy_list = []
print('start')
model = model.float()
for e in range(epochs):
running_loss = 0
for images,labels in train_loader:
optimizer.zero_grad()
log_ps = model(images)
loss = criterion(log_ps,labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
else:
test_loss = 0
accuracy = 0
with torch.no_grad():
model.eval()
for images,labels in val_loader:
log_ps =model(images)
test_loss += criterion(log_ps,labels)
ps = torch.exp(log_ps)
top_p,top_class = ps.max(dim=1, keepdim=True)
equals = top_class == labels.view(*top_class.shape)
accuracy += torch.mean(equals.type(torch.FloatTensor))
model.train()
train_losses.append(running_loss/len(train_loader))
test_losses.append(test_loss/len(val_loader))
accuracy_list.append(accuracy)
print("epoch: {}/{}.. ".format(e+1, epochs),
"train_loss: {:.3f}.. ".format(running_loss/len(train_loader)),
"test_loss: {:.3f}.. ".format(test_loss/len(val_loader)),
"accuracy: {:.3f}".format(accuracy/len(val_loader))) - 可視化によって、損失と精度の推移を確認します。
1
2
3
4plt.plot(train_losses,label='Training loss')
plt.plot(test_losses,label='Validation loss')
plt.plot(accuracy_list,label='Accuracy')
plt.legend()
5.Next
- Pytorchでテキストの分類を行ってみる