Kaggle - 災害ツイートについての自然言語処理(4) - データクレンジング

Natural Language Processing with Disaster Tweetsrに関する4回目の記事です。

Natural Language Processing with Disaster Tweets

今回はデータクレンジングを行います。

学習データと検証データの結合

まずは一括でデータクレンジングするために、学習データと検証データを結合します。

[ソース]

1
2
df = pd.concat([tweet,test])
df.shape

URLの排除

URLの排除を行います。正規表現を使ってURLパターンに合致したものを排除します。

[ソース]

1
2
3
4
5
6
7
8
example = "New competition launched :https://www.kaggle.com/c/nlp-getting-started"

def remove_URL(text):
url = re.compile(r'https?://\S+|www\.\S+')
return url.sub(r'', text)

remove_URL(example)
df['text'] = df['text'].apply(lambda x : remove_URL(x))

[結果]

HTMLタグの排除

HTMLタグも正規表現を使って排除します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
example = """<div>
<h1>Real or Fake</h1>
<p>Kaggle </p>
<a href="https://www.kaggle.com/c/nlp-getting-started">getting started</a>
</div>"""

def remove_html(text):
html = re.compile(r'<.*?>')
return html.sub(r'',text)
print(remove_html(example))

df['text'] = df['text'].apply(lambda x : remove_html(x))

[結果]

絵文字の排除

絵文字も正規表現を使って排除します。

複数パターンある場合は、リスト型を使ってまとめて指定することができます。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Reference : https://gist.github.com/slowkow/7a7f61f495e3dbb7e3d767f97bd7304b
def remove_emoji(text):
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002702-\U000027B0"
u"\U000024C2-\U0001F251"
"]+", flags=re.UNICODE)
return emoji_pattern.sub(r'', text)

remove_emoji("Omg another Earthquake 😔😔")

df['text'] = df['text'].apply(lambda x: remove_emoji(x))

[結果]

句読点の排除

句読点を排除します。下記の方法で文字の変換を行っています。

  1. str.maketrans()でstr.translate()に使える変換テーブルを作成する。
  2. str.translate()で文字列内の文字を変換する。

[ソース]

1
2
3
4
5
6
7
8
def remove_punct(text):
table = str.maketrans('','', string.punctuation)
return text.translate(table)

example = "I am a #king"
print(remove_punct(example))

df['text'] = df['text'].apply(lambda x : remove_punct(x))

[結果]

スペル修正

最後にスペルの修正を行います。

pyspellcheckerというライブラリを使うので、まずこれをインストールしておきます。

[コマンド]

1
2
!pip install pyspellchecker

[結果]

 

スペルチャックは一旦単語ごとに分解し、スペルミスがあれば正しいスペルに修正し、最後に修正した単語を含めて結合し直しています。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from spellchecker import SpellChecker

spell = SpellChecker()
def correct_spellings(text):
corrected_text = []
misspelled_words = spell.unknown(text.split())
for word in text.split():
if word in misspelled_words:
corrected_text.append(spell.correction(word))
else:
corrected_text.append(word)
return " ".join(corrected_text)

text = "corect me plese"
correct_spellings(text)

[結果]


今回は、英語文字列のクレンジングを行いました。

次回は、単語ベクター化モデルの一つであるGloVeを試してみます。

Kaggle - 災害ツイートについての自然言語処理(3)

Natural Language Processing with Disaster Tweetsrに関する3回目の記事です。

Natural Language Processing with Disaster Tweets

今回は単語ごとの解析を行います。

単語解析

ツイートを単語ごとに分割する関数を定義します。

引数のtargetには災害関連(=1)か災害に関係ない(=0)かを渡します。

[ソース]

1
2
3
4
5
6
7
def create_corpus(target):
corpus=[]

for x in tweet[tweet['target']==target]['text'].str.split():
for i in x:
corpus.append(i)
return corpus

災害に関係のないツイート(target=0)を単語ごとに分割し、出現頻度が多い順にグラフ化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
corpus = create_corpus(0)

dic=defaultdict(int)
for word in corpus:
if word in stop:
dic[word] += 1

top = sorted(dic.items(), key=lambda x:x[1], reverse=True)[:10]

x,y=zip(*top)
plt.bar(x,y)

[結果]

the、a、toという冠詞、前置詞の出現が多いようです。


次に災害に関係のあるツイート(target=1)を単語ごとに分割し、出現頻度が多い順にグラフ化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
corpus=create_corpus(1)

dic=defaultdict(int)
for word in corpus:
if word in stop:
dic[word]+=1

top=sorted(dic.items(), key=lambda x:x[1],reverse=True)[:10]

x,y=zip(*top)
plt.bar(x,y)

[結果]

こちらもthe、in、ofという冠詞、前置詞の出現が多いようです。

句読点

今度は句読点について調べていきます。

string.punctuation(6行目)は、英数字以外のアスキー文字(句読点含む)を表します。

災害に関係のあるツイートの句読点出現回数をグラフで表示します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
plt.figure(figsize=(10,5))
corpus = create_corpus(1)

dic = defaultdict(int)
import string
special = string.punctuation
for i in (corpus):
if i in special:
dic[i] += 1

x,y = zip(*dic.items())
plt.bar(x,y)

[結果]


災害に関係のないツイートの句読点出現回数をグラフで表示します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
plt.figure(figsize=(10,5))
corpus = create_corpus(0)

dic = defaultdict(int)
import string
special = string.punctuation
for i in (corpus):
if i in special:
dic[i] += 1

x, y = zip(*dic.items())
plt.bar(x,y,color='green')

[結果]

どちらも1番目が-(ハイフン)、2番目が|(パイプ)と同じですが、3番目は:(コロン)と?(クエスチョン)と少し違いがあるようです。

共通する単語(ストップワード以外)

最後にストップワードを含まない共通する単語を調べます。

ストップワードとは、自然言語を処理するにあたって処理対象外とする単語のことです。

「at」「of」などの前置詞や、「a」「an」「the」などの冠詞、「I」「He」「She」などの代名詞がストップワードとされます。

ストップワードに含まれない単語を抽出し、グラフ化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
counter = Counter(corpus)
most = counter.most_common()
x=[]
y=[]
for word,count in most[:40]:
if (word not in stop) :
x.append(word)
y.append(count)

sns.barplot(x=y, y=x)

[結果]

まだハイフンやアンパサンドなどがあるので、さらにデータクレンジングをする必要がありそうです。

N-gram解析

N-gram解析とは、対象となるテキストの中で、連続するN個の表記単位(gram)の出現頻度を求める手法です。

そうすることによって、テキスト中の任意の長さの表現の出現頻度パターンなどを知ることができるようになります。

N=2(bigram)として、N-gram解析を行います。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
def get_top_tweet_bigrams(corpus, n=None):
vec = CountVectorizer(ngram_range=(2, 2)).fit(corpus)
bag_of_words = vec.transform(corpus)
sum_words = bag_of_words.sum(axis=0)
words_freq = [(word, sum_words[0, idx]) for word, idx in vec.vocabulary_.items()]
words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True)
return words_freq[:n]

plt.figure(figsize=(10,5))
top_tweet_bigrams=get_top_tweet_bigrams(tweet['text'])[:10]
x,y=map(list,zip(*top_tweet_bigrams))
sns.barplot(x=y,y=x)

[結果]

これに関しても、URLに関するものが多かったり、前置詞・冠詞の組み合わせが多かったりと、まだまだデータクレンジングが必要そうです。

というわけで、次回はデータクレンジングを行っていきます。

Kaggle - 災害ツイートについての自然言語処理(2)

Natural Language Processing with Disaster Tweetsrに関する2回目の記事です。

Natural Language Processing with Disaster Tweets

今回はツイート(text)に関するEDA(探索的データ解析)を行います。

ツイートの探索的データ解析

基本的なテキスト分析として次の3点を調べます。

  • 文字数
  • 単語数
  • 平均単語レングス

まずは文字数をカウントしグラフ化します。

災害関連かどうかでグラフを分けています。

[ソース]

1
2
3
4
5
6
7
8
9
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
tweet_len = tweet[tweet['target']==1]['text'].str.len()
ax1.hist(tweet_len,color='red')
ax1.set_title('disaster tweets')
tweet_len=tweet[tweet['target']==0]['text'].str.len()
ax2.hist(tweet_len,color='green')
ax2.set_title('Not disaster tweets')
fig.suptitle('Characters in tweets')
plt.show()

[結果]

両グラフともほとんど同じ分布になっています。

120語から140語付近がもっとも度数が多いようです。


次に、単語数をカウントしグラフ化します。

[ソース]

1
2
3
4
5
6
7
8
9
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
tweet_len=tweet[tweet['target']==1]['text'].str.split().map(lambda x: len(x))
ax1.hist(tweet_len,color='red')
ax1.set_title('disaster tweets')
tweet_len=tweet[tweet['target']==0]['text'].str.split().map(lambda x: len(x))
ax2.hist(tweet_len,color='green')
ax2.set_title('Not disaster tweets')
fig.suptitle('Words in a tweet')
plt.show()

[結果]

こちらも似た分布にはなっていますが、単語数15のところの災害関連ツイートが妙に少なくなっていることが分かります。


最後に、平均単語レングスをカウントしグラフ化します。

[ソース]

1
2
3
4
5
6
7
8
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
word=tweet[tweet['target']==1]['text'].str.split().apply(lambda x : [len(i) for i in x])
sns.distplot(word.map(lambda x: np.mean(x)),ax=ax1,color='red')
ax1.set_title('disaster')
word=tweet[tweet['target']==0]['text'].str.split().apply(lambda x : [len(i) for i in x])
sns.distplot(word.map(lambda x: np.mean(x)),ax=ax2,color='green')
ax2.set_title('Not disaster')
fig.suptitle('Average word length in each tweet')

[結果]

こちらもほぼ同じ分布になっているように見えます。

文字数、単語数、平均レングスでは災害関連かどうかを判断するのは難しいのかもしれません。

次回は語尾の単語や句読点、よく使われる単語などを調べていきます。

Kaggle - 災害ツイートについての自然言語処理(1)

今回からはNatural Language Processing with Disaster Tweetsrコンペに参加していきたいと思います。

Natural Language Processing with Disaster Tweets

簡単に説明すると、ツイートが実際の災害に関するものか否かを判定するコンペとのことです。

ちなみに自然言語処理とは、「人間が日常的に使っている自然言語をコンピュータに処理させる一連の技術であり、人工知能と言語学の一分野」ということです。

探索的データ解析 (Exploratory data analysis)

探索的データ分析を行います。つまりどんなデータかを確認していきたいと思います。

まずは必要なライブラリをインポートします。

(Kaggleノートブックで動作確認しています。)

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import os
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from nltk.corpus import stopwords
from nltk.util import ngrams
from sklearn.feature_extraction.text import CountVectorizer
from collections import defaultdict
from collections import Counter
plt.style.use('ggplot')
stop=set(stopwords.words('english'))
import re
from nltk.tokenize import word_tokenize
import gensim
import string
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from tqdm import tqdm
from keras.models import Sequential
from keras.layers import Embedding,LSTM,Dense,SpatialDropout1D
from keras.initializers import Constant
from sklearn.model_selection import train_test_split
from keras.optimizers import Adam

訓練データと検証データを読み込みます。

訓練データの一部を表示してデータを確認します。

[ソース]

1
2
3
tweet= pd.read_csv('../input/nlp-getting-started/train.csv')
test=pd.read_csv('../input/nlp-getting-started/test.csv')
tweet.head(10)

[結果]

keywordとlocationが欠損値ばかりです。

targetが予測すべき項目(1が災害関連。0が災害に無関係)なので、ほとんど残りのtext(ツイート)から予測することになりそうです。

検証データの一部も表示してみます、

[ソース]

1
test.head(10)

[結果]

keyword,location,text項目はありますが、target項目がありませんので、このデータを予測する必要があることが確認できました。

分布確認

targetごとのデータ数を分布図で確認してみます。

[ソース]

1
2
3
x=tweet.target.value_counts()
sns.barplot(x.index,x)
plt.gca().set_ylabel('samples')

[結果]

災害関連ツイート(=1)よりも災害に関連しないツイート(=0)の方が多いことが分かります。

次回からはツイート(text)に関するEDA(探索的データ解析)を行っていきます。

Kaggle - Digit Recognizer(5) - 手書きの数字認識

手書きの数字認識問題Digit Recognizerの5回目(最終)の記事になります。

今回は、予測結果をKaggleに提出します。

前回記事のTorchvisionで用意されているResNet-18(畳み込みニューラル ネットワーク)モデルを使った成績が一番よかったので、このソースに提出ファイルの作成処理を追加します。

提出ファイルの作成

まずKaggle環境からtest.csvファイルをダウンロードしておきます。

前回のソースに下記のソースを追記し、実行します。(処理内容はコメントをご参照ください。)

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test_data = pd.read_csv('./test.csv', dtype=np.float32)
test_data = test_data.to_numpy() / 255.0
test_data = test_data.reshape(-1, 28, 28, 1) # reshapeの一つのサイズが決まっているとき、もう一方を-1とすると、-1には元の形状から推測された値が入る。

test_tensor = torch.from_numpy(test_data).permute(0, 3, 1, 2) # numpy ndarrayからPytorch tensorに変換し、次元の入れ替え(permute)
test_tensor = test_tensor.repeat(1, 3, 1, 1) # データセットの繰り返し

images = test_tensor.to(device)
outputs = model(images)
_, predictions = torch.max(outputs, 1) # 配列の最大値の要素を返す

predictions = predictions.cpu()
submission = pd.DataFrame({'ImageId': np.arange(1, (predictions.size(0) + 1)), 'Label': predictions})
submission.to_csv("submission.csv", index = False)
print("# 終了 #")

submission.csvが出力されますので、このファイルをKaggleに提出します。

[提出結果]

提出結果は98.21%となりました。

十分な正解率になったかと思います。

Kaggle - Digit Recognizer(4) - 手書きの数字認識

手書きの数字認識問題Digit Recognizerの4回目の記事になります。

今回はTorchvisionで用意されているResNet-18(畳み込みニューラル ネットワーク)モデルを使って数字認識を行います。

(Torchvisionはpytorchが用意してくれている画像周りのDataLoaderです。)

Torchvisionで数字認識

最初はKaggleノートブックで実行していたのですが、Torchvisionのデータセットをロードするところでエラーになってしまい解消方法がみつかりませんでした。

そこで今回はローカルで実行しております。

必要ライブラリをpipインストールする必要がありましたが、基本的に’No Module’と表示されるものをインストールすれば問題ありませんでした。

またKaggle環境からtrain.csvをダウンロードしておく必要もあります。

処理に関してはソース上のコメント参照して頂ければと思います。

[全体ソース]

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# ライブラリをインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
from tqdm import tqdm
import seaborn as sns
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings("ignore")

import torch
import torch.nn as nn
import torchvision
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms

# CSVファイルを読み込み、正解ラベルのデータを分ける
data = pd.read_csv('./train.csv', dtype = np.float32)
labels = data.pop('label').astype('int64')

# 0~255のピクセルデータを0~1に変換
data = data.to_numpy() / 255.0 # converting to numpy and normalizing between 0 and 1
labels = labels.to_numpy()

data = data.reshape(-1, 28, 28, 1)
labels = labels.reshape(-1,1)
print(labels.shape)

# 訓練データと評価データに分ける
x_train, x_val, y_train, y_val = train_test_split(data, labels, test_size=0.2)
print(f'x_train.shape: {x_train.shape}, x_val.shape: {x_val.shape}')

# カスタムデータセットの定義
class MNISTDataset(Dataset):

def __init__(self, images, labels, transform = None):
"""Method to initilaize variables."""
self.images = images
self.labels = labels
self.transform = transform

def __getitem__(self, index):
label = self.labels[index]
image = self.images[index]

if self.transform is not None:
image = self.transform(image)
image = image.repeat(3, 1, 1)
return image, label

def __len__(self):
return len(self.images)

# データを0~1のテンソルに変換
train_set = MNISTDataset(x_train, y_train, transform=transforms.Compose([transforms.ToTensor()]))
val_set = MNISTDataset(x_val, y_val, transform=transforms.Compose([transforms.ToTensor()]))
all_data = MNISTDataset(data, labels, transform=transforms.Compose([transforms.ToTensor()]))

train_loader = DataLoader(train_set, batch_size=32)
val_loader = DataLoader(val_set, batch_size=32)
all_data_loader = DataLoader(all_data, batch_size=32)

# GPUが使えればGPUを使用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

learning_rate = 0.001
num_classes = 10
num_epochs = 10
# TorchvisionからResNet-18(畳み込みニューラル ネットワーク)モデルをダウンロード
model = torchvision.models.resnet18(pretrained=True) # Kaggleノートブックではここでダウンロードエラー
num_ftrs = model.fc.in_features
# 全結合を行う3層構造のニューラルネットワークを生成
model.fc = nn.Linear(num_ftrs, num_classes)

model.to(device)

criterion = nn.CrossEntropyLoss()

# 全てのパラメータが最適化されることを観察
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)

# 7エポックごとにLRを0.1ずつ減らす
# LR range test:初期学習率を決める手段で、ある幅で学習率を徐々に増加させながらAccuracyないしLossを観察し決定する手法
exp_lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)

# 損失関数と最適化アルゴリズムを生成
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

# 学習率を更新する
def update_lr(optimizer, lr):
for param_group in optimizer.param_groups:
param_group['lr'] = lr

# 学習する
total_step = len(train_loader)
curr_lr = learning_rate
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)

# Forward pass(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(images)
loss = criterion(outputs, labels.flatten())

# Backward and optimize(逆伝播と最適化を行う)
optimizer.zero_grad()
loss.backward()
optimizer.step()

if (i + 1) % 300 == 0:
print(f'Epoch: {epoch + 1}/{num_epochs}, Loss: {loss.item()}')

# 評価する
model.eval()
with torch.no_grad():
correct = 0
total = 0
for images, labels in val_loader:
images = images.to(device)
labels = labels.to(device)

outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels.flatten()).sum()

print(f'Test acc: {100 * correct / total}')

[結果]

最終正解率は98.38%となりました。

前回記事の正解率は95%前後だったのでそれよりも精度を上げることができています。

数字認識の成果としては十分なものになっていると思います。

Kaggle - Digit Recognizer(3) - 手書きの数字認識

手書きの数字認識問題Digit Recognizerの3回目の記事になります。

今回は畳み込みニューラルネットワーク(Convolutional Neural Networks)の処理を実装していきます。

CNNは画像全体にわたって、特徴の一致件数を計算することによって、フィルタをかける手法です。この時に実行する演算が「畳み込み」で、畳み込みニューラルネットワークの名称はここから取られています。

画像や動画認識に広く使われているモデルです。

ライブラリのインポート

必要なライブラリをインポートします。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
from tqdm import tqdm
from sklearn.model_selection import train_test_split
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")

import torch
import torch.nn as nn
from torch.autograd import Variable
import torchvision

CSVファイル読み込み・正解ラベルの準備

CSVファイルを読み込み、正解ラベルのデータを分けます。

正解ラベルはNumpyのint32型に変換します。

[ソース]

1
2
3
4
data = pd.read_csv('../input/digit-recognizer/train.csv', dtype = np.float32)
labels = data.pop('label').astype('int32')

data.head() # let's see first five rows

[結果]


変換した正解ラベルを表示します。

[ソース]

1
labels.head() # after converting labels to `int32`

[結果]


次に0~255のピクセルデータを0~1に変換します。(正規化)

正解ラベルと0~1に変換したデータはNumpyの配列型にしておきます。

また、訓練データと評価データに分けます。(4:1の割合)

[ソース]

1
2
3
4
5
data = data.to_numpy() / 255.0 # converting to numpy and normalizing between 0 and 1
labels = labels.to_numpy()

x_train, x_val, y_train, y_val = train_test_split(data, labels, test_size=0.2)
print(f'x_train.shape: {x_train.shape}, x_val.shape: {x_val.shape}')

[結果]

ピクセルデータをイメージ化

手書き数字のピクセルデータをイメージ化して表示します。

[ソース]

1
2
3
4
5
6
7
8
plt.figure(figsize=(12, 10))
for i in range(16):
plt.subplot(4, 4,i+1)
plt.xticks([])
plt.yticks([])
plt.imshow(x_train[i].reshape(28,28), cmap=plt.cm.binary)
plt.xlabel(y_train[i])
plt.show()

[結果]

PyTorchデータセットの準備

NumpyのデータをPyTorchのデータセットに変換します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
x_train_tensor = torch.from_numpy(x_train)
y_train_tensor = torch.from_numpy(y_train).type(torch.LongTensor)

x_val_tensor = torch.from_numpy(x_val)
y_val_tensor = torch.from_numpy(y_val).type(torch.LongTensor)

print(f'x_train_tensor.dtype: {x_train_tensor.dtype}, y_train_tensor.dtype: {y_train_tensor.dtype}')

train_data = torch.utils.data.TensorDataset(x_train_tensor, y_train_tensor)
val_data = torch.utils.data.TensorDataset(x_val_tensor, y_val_tensor)

train_loader = torch.utils.data.DataLoader(dataset=train_data, batch_size=32, shuffle=True)
val_loader = torch.utils.data.DataLoader(dataset=val_data, batch_size=1, shuffle=False)

[結果]


GPUが使用できる場合は、GPU使用するように設定します。GPUがない場合はCPUを利用します。

[ソース]

1
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

CNNモデルの定義

CNNモデルを定義します。

損失関数(交差エントロピー)と最適化アルゴリズム(SGD Optimizer)もあわせて定義します。

[ソース]

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
learning_rate = 0.001
num_epochs = 10
num_classes = 10

# Convolutional neural network (two convolutional layers)
class ConvNet(nn.Module):
def __init__(self, num_classes=10):
super(ConvNet, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))

self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2))

self.fc = nn.Linear(7 * 7 * 32, num_classes)

def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.reshape(out.size(0), -1)
out = self.fc(out)
return out

model = ConvNet(num_classes).to(device) # CNNモデルの生成

criterion = nn.CrossEntropyLoss() # 交差エントロピーの生成
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # SGD Optimizer(確率的勾配降下法)の生成

学習と評価

CNNモデルを使って学習と評価を行います。

Kaggleのノートブック環境で実行しましたが30分以上かかりましたので気長にお待ちください。

[ソース]

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
47
48
49
50
51
iter_num = 0
loss_list = []
iteration_list = []
accuracy_list = []

# Train the model()
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader): # (訓練データごとにループ)
images = images.view(32, 1, 28, 28).to(device)
labels = labels.to(device)

# Forward pass(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(images)
loss = criterion(outputs, labels)

# Backward and optimize(最適化、確率的勾配降下法)
optimizer.zero_grad()
loss.backward()
optimizer.step()

iter_num += 1

if iter_num % 50 == 0:
# Calculate Accuracy(正解率の算出)
correct = 0
total = 0
# Iterate through val dataset(評価データごとにループ)
for images, labels in val_loader:
test = images.view(1, 1, 28, 28).to(device)
labels = labels.to(device)
# Forward propagation(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(test)

# Get predictions from the maximum value(予測値の取得)
predicted = torch.max(outputs.data, 1)[1]

# Total number of labels(正解ラベルの総数)
total += len(labels)

correct += (predicted == labels).sum()

accuracy = 100 * correct / float(total)

# store loss and iteration(損失・正解率を保持)
loss_list.append(loss.data)
iteration_list.append(iter_num)
accuracy_list.append(accuracy)
if iter_num % 500 == 0:
# Print Loss(損失と正解率を表示)
print('Iteration: {}, Loss: {:.4f}, Accuracy: {:.4f} %'.format(iter_num, loss.data, accuracy))

[結果]

最終的な正解率は94.98%になりました。


学習済みモデルを使って最終評価を行います。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Evaluation(評価)
correct = 0
total = 0
# Iterate through val dataset(データごとにループ)
for images, labels in val_loader:
test = images.view(1, 1, 28, 28).to(device)
labels = labels.to(device)
# Forward propagation(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(test)

# Get predictions from the maximum value(予測値の取得)
predicted = torch.max(outputs.data, 1)[1]

# Total number of labels(正解ラベルの総数)
total += len(labels)

correct += (predicted == labels).sum()

accuracy = 100 * correct / float(total)
print(accuracy)

[結果]

同じく正解率は94.98%です。

グラフ化

損失と正解率の推移をグラフ化します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# Visualize loss
plt.plot(iteration_list,loss_list)
plt.xlabel("Num. of Iters.")
plt.ylabel("Loss")
plt.title("Loss vs Num. of Iters.")
plt.show()

# Visualize Accuracy
plt.plot(iteration_list,accuracy_list)
plt.xlabel("Num. of Iters.")
plt.ylabel("Accuracy")
plt.title("Accuracyvs Num. of Iters.")
plt.show()

[結果]

前回記事(ロジスティック回帰モデル)と比べると損失はより早く収束しているように見えます。

また正解率に関しては収束のペースはあまり変わってないようですが、最終正解率は85%前後から95%前後と10%ほど精度が上がっています。

Kaggle - Digit Recognizer(2) - 手書きの数字認識

手書きの数字認識問題Digit Recognizerの2回目の記事になります。

PyTorchデータセットの読み込み

前回記事で読み込んだデータを訓練データと評価データに分けます。(4:1の割合)

分けたデータはPyTorch用のデータセットに変換します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x_train, x_val, y_train, y_val = train_test_split(data, labels, test_size=0.2)
print('Train shape:', x_train.shape, 'Val shape:', x_val.shape)

x_train_tensor = torch.from_numpy(x_train)
y_train_tensor = torch.from_numpy(y_train).type(torch.LongTensor)

x_val_tensor = torch.from_numpy(x_val)
y_val_tensor = torch.from_numpy(y_val).type(torch.LongTensor)

train_dataset = TensorDataset(x_train_tensor, y_train_tensor)
val_dataset = TensorDataset(x_val_tensor, y_val_tensor)

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

[結果]

ロジスティック回帰モデルの準備

ロジスティック回帰モデルを作成します。パラメータはコメントをご参照下さい。

交差エントロピーは損失関数の一つで、予測と実際の値のズレの大きさを表す関数です。モデルの予測精度を評価するもので、損失関数の値が小さければより正確なモデルと言えます。

確率的勾配降下法は最適化アルゴリズム(勾配降下法)の一つで、重みを少しずつ更新して勾配が最小になる点を探索するアルゴリズムです。

[ソース]

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
device = torch.device('cpu' if torch.cuda.is_available() else 'cpu')

input_size = 28 * 28 # 入力層のサイズ
output_size = 10 # 出力層のサイズ
hidden_size = 100 # 隠れ層(中間層)のサイズ
learning_rate = 0.001 # 学習率
num_epochs = 10 # エポック数「一つの訓練データを何回繰り返して学習させるか」

class LogisticRegression(nn.Module):
def __init__(self, input_size, output_size):
super(LogisticRegression, self).__init__()
# Linear model
self.linear = nn.Linear(input_size, output_size)

def forward(self, x):
out = self.linear(x)
return out

model = LogisticRegression(input_size, output_size).to(device)

# Cross Entropy Loss(交差エントロピー)
criterion = nn.CrossEntropyLoss()

# SGD Optimizer(確率的勾配降下法)
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

学習と評価(CPU版)

上記で用意したデータセットと回帰モデルで学習と評価を行います。

1000回ごとに途中結果を表示しています。

[ソース]

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
loss_list = []
iteration_list = []
accuracy_list = []
iter_num = 0
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# input and label(入力データと正解ラベルを設定)
train = images.view(-1, 28*28)
labels = labels

# Forward pass(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
outputs = model(train)

# Loss calculate(損失計算)
loss = criterion(outputs, labels)

# Backward and optimize(最適化、確率的勾配降下法)
optimizer.zero_grad()
loss.backward()
optimizer.step()
iter_num += 1

if (i+1) % 50 == 0:
correct = 0
total = 0
for images, labels in val_loader:
# Forward pass(順伝搬:初期の入力を層ごとに処理して出力に向けて送ること)
images = images.view(-1, 28*28)
outputs = model(images)

# Predictions(予測)
predicted = torch.max(outputs, 1)[1]

# Total number of samples(サンプル数)
total += labels.size(0)

# Total correct predictions(正解数)
correct += (predicted == labels).sum()

accuracy = 100 * (correct/total)
loss_list.append(loss.data)
iteration_list.append(iter_num)
accuracy_list.append(accuracy)
if (iter_num+1) % 1000 == 0:
# Print Loss(損失と正解率を表示)
print('Iteration: {} Loss: {:0.4f} Val Accuracy: {:0.4f}%'.format(iter_num+1, loss.data, accuracy))

[結果]

損失(Loss)が少しずつ少なくなり、正解率が(Accuracy)少しずつ上昇し、最終的に85.53%になったことが確認できます。

学習結果をグラフ化

学習結果をグラフ表示します。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
# Visualize loss(損失のグラフ化)
plt.plot(iteration_list,loss_list)
plt.xlabel("Num. of Iters.")
plt.ylabel("Loss")
plt.title("Logistic Regression: Loss vs Num. of Iters.")
plt.show()

# Visualize accuracy(正解率のグラフ化)
plt.plot(iteration_list,accuracy_list)
plt.xlabel("Num. of Iters.")
plt.ylabel("Accuracy")
plt.title("Logistic Regression: Accuracy vs Num. of Iters.")
plt.show()

[結果]

損失(上図)が次第に下がり、正解率(下図)が徐々に上がっていることを視覚的に確認することができます。

次回は畳み込みニューラルネットワーク(Convolutional Neural Networks)の処理を実装していきます。

Kaggle - Digit Recognizer(1) - 手書きの数字認識

今回からはDigit Recognizerコンペに参加していきたいと思います。

Digit Recognizer

有名な手書きの数字認識問題です。

全5回の記事でKaggleに提出することを目標とします。

データの確認

まずは必要なライブラリをインポートします。

[ソース]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import  numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
from tqdm import tqdm
import seaborn as sns
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings("ignore")

import torch
import torch.nn as nn
import torchvision
from torch.utils.data import TensorDataset, DataLoader, Dataset

訓練データと検証データを読み込み、訓練データの一部を表示します。

[ソース]

1
2
3
train_csv = pd.read_csv('../input/digit-recognizer/train.csv', dtype = np.float32)
test_csv = pd.read_csv('../input/digit-recognizer/test.csv', dtype = np.float32)
train_csv.head(5)

[結果]

正解ラベル(label)とピクセルごとの値が設定されています。

ピクセルごとのデータだとどんな数字かわかりませんが、のちほど画像として表示します。


ラベルとそれ以外にデータを分割し、Numpy配列に変換し配列の形を表示します。

[ソース]

1
2
3
4
5
labels = train_csv.pop('label')
labels = labels.to_numpy() # convert to numpy array

data = train_csv.to_numpy() / 255.0 # normalization
print('Data shape:', data.shape, 'Labels shape:', labels.shape)

[結果]

ラベルデータが42000個あり、ピクセルデータが42000個×784ピクセルあることが分かります。


ラベルデータごとの度数をグラフ化します。

[ソース]

1
2
sns.countplot(labels)
plt.title('Class Distribution')

[結果]

1から9までの数字がそれぞれ4000個前後存在することが分かります。


最後にピクセルデータを画像として表示します。

[ソース]

1
2
3
4
5
6
7
plt.figure(figsize=(12, 10))
for i in range(20):
plt.subplot(5, 4, i+1)
plt.grid(False)
plt.xticks([])
plt.yticks([])
plt.imshow(data[i].reshape(28,28))

[結果]

縦28ピクセル、横28ピクセルの手書きの数字であったことが分かります。

次回はPyTorch用にデータを変換したり、 ロジスティック回帰モデルを準備したりする予定です。

Kaggle(48) - タイタニック生存予測 - 答え合わせ

タイタニック生存予測のとにかく正解率の高いノートブックを探していたら100%のものが見つかりました。

タイタニック生存予測の答え

妙にソースが少ないと思ったら、生存結果がすべて含まれているCSVファイルをダウンロードして、名前で突き合わせているだけでした。。。

ズルイとは思いますが、参考までに実行・提出してみました。

タイタニック生存予測の答え

全体の処理は以下のようになります。

9~14行目のgithubからのダウンロードがエラーになったので、手動でダウンロードしKaggleノートブックにアップロードして、ファイル読み込みを行いました。(16行目)

[ソース]

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
import numpy as np
import pandas as pd

import os
import re
import warnings
print(os.listdir("../input"))

#import io
#import requests

# url="https://github.com/thisisjasonjafari/my-datascientise-handcode/raw/master/005-datavisualization/titanic.csv"
# s=requests.get(url).content
#c=pd.read_csv(io.StringIO(s.decode('utf-8')))
# 上記処理でダウンロードできなかったため手動ダウンロードしファイル読み込み
c=pd.read_csv('../input/githubtitanic/titanic.csv')

test_data_with_labels = c
test_data = pd.read_csv('../input/titanic/test.csv')

test_data_with_labels.head()

test_data.head()

# ワーニングが表示されないように
warnings.filterwarnings('ignore')

# 名前からダブルクォーテーションを除外してるだけ
for i, name in enumerate(test_data_with_labels['name']):
if '"' in name:
test_data_with_labels['name'][i] = re.sub('"', '', name)

# 名前からダブルクォーテーションを除外してるだけ
for i, name in enumerate(test_data['Name']):
if '"' in name:
test_data['Name'][i] = re.sub('"', '', name)

survived = []

for name in test_data['Name']:
# values[-1]は同姓同名の場合、最後にヒットしたほうの生存結果を取得している。
survived.append(int(test_data_with_labels.loc[test_data_with_labels['name'] == name]['survived'].values[-1]))

submission = pd.read_csv('../input/titanic/gender_submission.csv')
submission['Survived'] = survived
submission.to_csv('submission.csv', index=False)

[結果]

正解率100%となりました。当たり前ですね。