DSL3C The Number of Windows

はじめに

カテゴリーAtCoder版蟻本中級編では、AtCoder 版!蟻本 (中級編)でまとめられている問題をPythonで解いています。

問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DSL_3_C&lang=ja

方針

  • しゃくとり法を用います

コード

Pythonの場合、若干時間が厳しく地道な計算量削減が必要になってしまいます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
N, Q = map(int, input().split())
A = list(map(int, input().split()))
X = list(map(int, input().split()))

def two_pointers(x):
left = 0
sm = 0
ans = 0
for right in range(N):
sm += A[right]
while(sm > x):
sm -= A[left]
left += 1
ans += (right-left+1) # leftに対する条件を満たすパターン数
return ans

for x in X:
print(two_pointers(x))

記事情報

  • 投稿日:2020年5月10日
  • 最終更新日:2020年5月10日

AtCoder版!蟻本 (中級編) Python解答例

このページは何?

このブログでは、AtCoder 版!蟻本 (中級編)でまとめられている問題をPythonで解いています。

このページは、各解説記事へのリンクをまとめています。

リンク

3. ここで差がつく! — 中級編

3-2. 厳選!頻出テクニック (1)

3-2-1. Subsequence (POJ No.3061)

キーワード

記事情報

  • 投稿日:2020年5月10日
  • 最終更新日:2020年5月10日

JOI2008予選D 星座探し

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/joi2008yo/tasks/joi2008yo_d

この問題は全探索を用いて解くことができます。

方針

全探索をします。ただし、平行移動の方法が無数にあるので、候補を限定します。
星座のうちある星に対して、写真中のN個の星のいずれかがマッチする必要があるので、試すべき平行移動はN通りに絞ることができます。よって計算量はO(MN^2)で、十分に間に合います。

さらに、写真中のN個の星をハッシュテーブルで保持しておくことで、O(MN)に削減することが可能です。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SM = []
SN = []
M = int(input()) # 探したい星座
for m in range(M):
x, y = map(int,input().split())
SM.append((x,y))
N = int(input()) # 星空の写真
for n in range(N):
x, y = map(int,input().split())
SN.append((x,y))
st = set(SN) # inが高速
base = SM[0] # 探したい星座のうち基準となる星を設定
for sn in SN:
dx, dy = sn[0]-base[0], sn[1]-base[1] # 平行移動の方法
ok = True
for sm in SM:
if not (sm[0]+dx, sm[1]+dy) in st: # さらに高速化 O(MN)
ok = False
break
if (ok):
print (dx,dy)
exit()
print (ans)

記事情報

  • 投稿日:2020年5月10日
  • 最終更新日:2020年5月10日

ABC122B ATCoder

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc122/tasks/abc122_b

この問題は全探索を用いて解くことができます。

方針

Sの長さが十分に小さいので、全ての部分文字列を全探索することができます。

とはいえ、線形探索であっても実装がシンプルになるため、線形探索で解きます。

コード

リストの末尾に特殊な文字(番兵と呼びます)を追加することで、処理をシンプルにしています。番兵を用いない場合、ループ終了後に改めて

1
ans = max(ans, sm)

とする必要があります。

1
2
3
4
5
6
7
8
9
10
11
S = input()
S+='*'
sm = 0
ans = 0
for s in S:
if s in ('A','C','G','T'):
sm += 1
else:
ans = max(ans, sm)
sm = 0
print (ans)

記事情報

  • 投稿日:2020年5月10日
  • 最終更新日:2020年5月10日

ALDS1 13A 8クイーン問題

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_13_A&lang=ja

この問題は全探索を用いて解くことができます。

方針

工夫して全探索

単純に全探索すると候補は64C8となります。各候補に対する判定でさらにループが回ることから、間に合わないでしょう。

縦横にクイーンが重なってはならないという制約を利用して候補を絞ります。

具体的には、二次元配列の各列各行に重複があってはならないということなので、1~8までの重複を許さない順列として表現できます。例えば以下のような表現は

1
(6, 0, 2, 7, 5, 3, 1, 4)
1
2
3
0行目には、6列目にクイーンが存在する。
1行目には、0列目にクイーンが存在する。
2行目には、2列目にクイーンが存在する。

というような意味となります。

これにより計算量はO(8!)となります。

判定

先ほどの絞り込みにより縦横を見る必要がないので、あとは斜めに二つ以上のクイーンがあるかどうかを判定すれば良いですが、これが重めです。

判定する処理を作り、盤面を反転させて使い回すように実装すると楽でしょう。

コード

Nが十分に小さいので全探索しても間に合います。その場合、計算量はO(N!N)です。

しかし、さらに計算量を減らす方法を考えます。各経路の距離の期待値について考えます。すると

各経路の距離の期待値 = 各町の間の距離の期待値 * (N-1)

となることが分かります。つまり、各町の間の距離を計算すればよく、これは計算量O(N^2)です。

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
from itertools import permutations
K = int(input())
N = 8
RC = []
for k in range(K):
r, c = map(int,input().split())
RC.append((r,c))

def diag(board): # 斜めの判定
for i in range(2*N-1):
sm = 0
for j in range(i+1):
if (i-j>=8 or j>=8):
continue
sm += board[i-j][j]
if sm > 1:
return False
return True

def judge(ls):
board = [[0]*N for _ in range(N)]
for r in range(N):
c = ls[r]
board[r][c] = 1
for r, c in RC: # 指定された箇所にクイーンを置いているか判定
if board[r][c] == 0:
return False

if not diag(board):
return False

if not diag(board[::-1]): # 反転
return False
return True

for ls in permutations(range(N)):
if judge(ls):
for c in ls:
s = ['.'] * N
s[c] = 'Q'
print (''.join(s))
exit()

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年5月10日

ABC145C Average Length

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc145/tasks/abc145_c

この問題は全探索を用いて解くことができます。

コード

Nが十分に小さいので全探索しても間に合います。その場合、計算量はO(N!N)です。

しかし、さらに計算量を減らす方法を考えます。各経路の距離の期待値について考えます。すると

各経路の距離の期待値 = 各町の間の距離の期待値 * (N-1)

となることが分かります。つまり、各町の間の距離を計算すればよく、これは計算量O(N^2)です。

1
2
3
4
5
6
7
8
9
10
11
from itertools import combinations
N = int(input())
town = []
for n in range(N):
x, y = map(int,input().split())
town.append((x,y))

ans = 0
for i,j in combinations(town,2):
ans += ((i[0]-j[0])**2 + (i[1]-j[1])**2)**0.5
print (2*ans/N)

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年5月9日

ABC106B 105

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/abc106/tasks/abc106_b

この問題は全探索を用いて解くことができます。

コード

特に注意点はないです。for文の範囲に気をつけるくらいです。

1
2
3
4
5
6
7
N = int(input())
ans = 0
for n in range(1,N+1,2):
sm = sum(n%i==0 for i in range(1,n+1))
if sm==8:
ans += 1
print (ans)

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年5月9日

言語処理100本ノック2020 第8章 ニューラルネット

はじめに

言語処理100本ノック2020

言語処理100本ノックは東北大学が公開している自然言語処理の問題集です。

とても良質なコンテンツで企業の研修や勉強会で使われています。

そんな言語処理100本ノックが2020年に改定されてました。昨今の状況を鑑みて、深層ニューラルネットワークに関する問題が追加されました。(その他にも細かい変更があります)

この記事では、言語処理100本ノック2020にPythonで取り組んでいきます。

他にも色々な解法があると思うので、一つの解答例としてご活用ください!

全100問の解説に戻る

第6章で取り組んだニュース記事のカテゴリ分類を題材として,ニューラルネットワークでカテゴリ分類モデルを実装する.なお,この章ではPyTorch, TensorFlow, Chainerなどの機械学習プラットフォームを活用せよ.

70. 単語ベクトルの和による特徴量

問題50で構築した学習データ,検証データ,評価データを行列・ベクトルに変換したい.例えば,学習データについて,すべての事例xiの特徴ベクトルxiを並べた行列Xと,正解ラベルを並べた行列(ベクトル)Yを作成したい.(以下略)

コード

第7章と同様にword2vecにはgensimを用います。

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
import pandas as pd
import gensim
import numpy as np
train = pd.read_csv('train.txt',sep='\t',header=None)
valid = pd.read_csv('valid.txt',sep='\t',header=None)
test = pd.read_csv('test.txt',sep='\t',header=None)
model = gensim.models.KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)

d = {'b':0, 't':1, 'e':2, 'm':3}
y_train = train.iloc[:,0].replace(d)
y_train.to_csv('y_train.txt',header=False, index=False)
y_valid = valid.iloc[:,0].replace(d)
y_valid.to_csv('y_valid.txt',header=False, index=False)
y_test = test.iloc[:,0].replace(d)
y_test.to_csv('y_test.txt',header=False, index=False)

def write_X(file_name, df):
with open(file_name,'w') as f:
for text in df.iloc[:,1]:
vectors = []
for word in text.split():
if word in model.vocab:
vectors.append(model[word])
if (len(vectors)==0):
vector = np.zeros(300)
else:
vectors = np.array(vectors)
vector = vectors.mean(axis=0)
vector = vector.astype(np.str).tolist()
output = ' '.join(vector)+'\n'
f.write(output)
write_X('X_train.txt', train)
write_X('X_valid.txt', valid)
write_X('X_test.txt', test)

71. 単層ニューラルネットワークによる予測

問題70で保存した行列を読み込み,学習データについて以下の計算を実行せよ.(以下略)

1
2
3
4
5
6
7
8
import torch
import numpy as np
X_train = np.loadtxt(base+'X_train.txt', delimiter=' ')
X_train = torch.tensor(X_train, dtype=torch.float32)
W = torch.randn(300, 4)
softmax = torch.nn.Softmax(dim=1)
print (softmax(torch.matmul(X_train[:1], W)))
print (softmax(torch.matmul(X_train[:4], W)))

72. 損失と勾配の計算

学習データの事例x1と事例集合x1,x2,x3,x4に対して,クロスエントロピー損失と,行列Wに対する勾配を計算せよ.なお,ある事例xiに対して損失は次式で計算される.

li=−log[事例xiがyiに分類される確率]

ただし,事例集合に対するクロスエントロピー損失は,その集合に含まれる各事例の損失の平均とする.

1
2
3
4
5
6
7
8
9
10
y_train = np.loadtxt(base+'y_train.txt')
y_train = torch.tensor(y_train, dtype=torch.int64)
loss = torch.nn.CrossEntropyLoss()
print (loss(torch.matmul(X_train[:1], W),y_train[:1]))
print (loss(torch.matmul(X_train[:4], W),y_train[:4]))

ans = [] # 以下、確認
for s,i in zip(softmax(torch.matmul(X_train[:4], W)),y_train[:4]):
ans.append(-np.log(s[i]))
print (np.mean(ans))

クロスエントロピー損失の一部を実装し、確認をした。

73. 確率的勾配降下法による学習

確率的勾配降下法(SGD: Stochastic Gradient Descent)を用いて,行列Wを学習せよ.なお,学習は適当な基準で終了させればよい(例えば「100エポックで終了」など)

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
from torch.utils.data import TensorDataset, DataLoader
class LogisticRegression(torch.nn.Module):
def __init__(self):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(300, 4),
)
def forward(self, X):
return self.net(X)

model = LogisticRegression()
ds = TensorDataset(X_train, y_train)
# DataLoaderを作成
loader = DataLoader(ds, batch_size=1, shuffle=True)

loss_fn = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.net.parameters(), lr=1e-1)

for epoch in range(10):
for xx, yy in loader:
y_pred = model(xx)
loss = loss_fn(y_pred, yy)
optimizer.zero_grad()
loss.backward()
optimizer.step()

74. 正解率の計測

問題73で求めた行列を用いて学習データおよび評価データの事例を分類したとき,その正解率をそれぞれ求めよ.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def accuracy(pred, label):
pred = np.argmax(pred.data.numpy(), axis=1)
label = label.data.numpy()
return (pred == label).mean()


X_valid = np.loadtxt(base+'X_valid.txt', delimiter=' ')
X_valid = torch.tensor(X_valid, dtype=torch.float32)
y_valid = np.loadtxt(base+'y_valid.txt')
y_valid = torch.tensor(y_valid, dtype=torch.int64)

pred = model(X_train)
print (accuracy(pred, y_train))
pred = model(X_valid)
print (accuracy(pred, y_valid))

75. 損失と正解率のプロット

問題73のコードを改変し,各エポックのパラメータ更新が完了するたびに,訓練データでの損失,正解率,検証データでの損失,正解率をグラフにプロットし,学習の進捗状況を確認できるようにせよ.

コード

tensorboardを利用しました。

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
%load_ext tensorboard
!rm -rf ./runs
%tensorboard --logdir ./runs
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
from torch.utils.data import TensorDataset, DataLoader
class LogisticRegression(torch.nn.Module):
def __init__(self):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(300, 4),
)
def forward(self, X):
return self.net(X)

model = LogisticRegression()
ds = TensorDataset(X_train, y_train)
# DataLoaderを作成
loader = DataLoader(ds, batch_size=1, shuffle=True)

loss_fn = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.net.parameters(), lr=1e-1)

for epoch in range(10):
for xx, yy in loader:
y_pred = model(xx)
loss = loss_fn(y_pred, yy)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
y_pred = model(X_train)
loss = loss_fn(y_pred, y_train)
writer.add_scalar('Loss/train', loss, epoch)
writer.add_scalar('Accuracy/train', accuracy(y_pred,y_train), epoch)

y_pred = model(X_valid)
loss = loss_fn(y_pred, y_valid)
writer.add_scalar('Loss/valid', loss, epoch)
writer.add_scalar('Accuracy/valid', accuracy(y_pred,y_valid), epoch)

76. チェックポイント

問題75のコードを改変し,各エポックのパラメータ更新が完了するたびに,チェックポイント(学習途中のパラメータ(重み行列など)の値や最適化アルゴリズムの内部状態)をファイルに書き出せ.

学習途中のパラメータ、最適化アルゴリズムの内部状態はそれぞれstate_dict()で取得可能で、torch.saveでシリアライズして保存可能です。

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
from torch.utils.data import TensorDataset, DataLoader
class LogisticRegression(torch.nn.Module):
def __init__(self):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(300, 4),
)
def forward(self, X):
return self.net(X)

model = LogisticRegression()
ds = TensorDataset(X_train, y_train)
# DataLoaderを作成
loader = DataLoader(ds, batch_size=1, shuffle=True)

loss_fn = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.net.parameters(), lr=1e-1)

for epoch in range(10):
for xx, yy in loader:
y_pred = model(xx)
loss = loss_fn(y_pred, yy)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
y_pred = model(X_train)
loss = loss_fn(y_pred, y_train)
writer.add_scalar('Loss/train', loss, epoch)
writer.add_scalar('Accuracy/train', accuracy(y_pred,y_train), epoch)

y_pred = model(X_valid)
loss = loss_fn(y_pred, y_valid)
writer.add_scalar('Loss/valid', loss, epoch)
writer.add_scalar('Accuracy/valid', accuracy(y_pred,y_valid), epoch)

torch.save(model.state_dict(), base+'output/'+str(epoch)+'.model')
torch.save(optimizer.state_dict(), base+'output/'+str(epoch)+'.param')

77. ミニバッチ化

問題76のコードを改変し,B事例ごとに損失・勾配を計算し,行列Wの値を更新せよ(ミニバッチ化).Bの値を1,2,4,8,…と変化させながら,1エポックの学習に要する時間を比較せよ.

すでに、batch_sizeを指定できるように実装しているので、その値を変えるだけです。

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
import time
from torch.utils.data import TensorDataset, DataLoader
class LogisticRegression(torch.nn.Module):
def __init__(self):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(300, 4),
)
def forward(self, X):
return self.net(X)

model = LogisticRegression()
ds = TensorDataset(X_train, y_train)
loss_fn = torch.nn.CrossEntropyLoss()


ls_bs = [2**i for i in range(15)]
ls_time = []
for bs in ls_bs:
loader = DataLoader(ds, batch_size=bs, shuffle=True)
optimizer = torch.optim.SGD(model.net.parameters(), lr=1e-1)
for epoch in range(1):
start = time.time()
for xx, yy in loader:
y_pred = model(xx)
loss = loss_fn(y_pred, yy)
optimizer.zero_grad()
loss.backward()
optimizer.step()
ls_time.append(time.time()-start)
print (ls_time)
1
[5.749649286270142, 2.9937779903411865, 1.630518913269043, 0.833240270614624,...]

batch_sizeが大きくなるほど高速になります。

78. GPU上での学習

問題77のコードを改変し,GPU上で学習を実行せよ.

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
import time
from torch.utils.data import TensorDataset, DataLoader
class LogisticRegression(torch.nn.Module):
def __init__(self):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(300, 4),
)
def forward(self, X):
return self.net(X)

model = LogisticRegression()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

ds = TensorDataset(X_train.to(device), y_train.to(device))
loss_fn = torch.nn.CrossEntropyLoss()

ls_bs = [2**i for i in range(15)]
ls_time = []
for bs in ls_bs:
loader = DataLoader(ds, batch_size=bs, shuffle=True)
optimizer = torch.optim.SGD(model.net.parameters(), lr=1e-1)
for epoch in range(1):
start = time.time()
for xx, yy in loader:
y_pred = model(xx)
loss = loss_fn(y_pred, yy)
optimizer.zero_grad()
loss.backward()
optimizer.step()
ls_time.append(time.time()-start)
print (ls_time)

[9.553595066070557, 5.196623802185059, 2.7027416229248047, 1.3737561702728271,…]

バッチサイズが小さいうちは、CPUの方が高速です。バッチサイズが大きくなるとその差は縮まりますが逆転することはありませんでした。ネットワークの構造を複雑にすると、GPUの長所が生きてくるはずです。

79. 多層ニューラルネットワーク

問題78のコードを改変し,バイアス項の導入や多層化など,ニューラルネットワークの形状を変更しながら,高性能なカテゴリ分類器を構築せよ.

バイアス項はtorch.nn.Linearではbiasで指定できるがデフォルトでTrueである。多層化をすることで正解率が向上した。

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
import time
from torch.utils.data import TensorDataset, DataLoader
class MLP(torch.nn.Module):
def __init__(self):
super().__init__()
self.net = torch.nn.Sequential(
torch.nn.Linear(300, 32),
torch.nn.ReLU(),
torch.nn.Linear(32, 4),
)
def forward(self, X):
return self.net(X)

model = MLP()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)

ds = TensorDataset(X_train.to(device), y_train.to(device))
loss_fn = torch.nn.CrossEntropyLoss()

loader = DataLoader(ds, batch_size=1024, shuffle=True)
optimizer = torch.optim.SGD(model.net.parameters(), lr=1e-1)
for epoch in range(100):
start = time.time()
for xx, yy in loader:
y_pred = model(xx)
loss = loss_fn(y_pred, yy)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
y_pred = model(X_train.to(device))
loss = loss_fn(y_pred, y_train.to(device))
writer.add_scalar('Loss/train', loss, epoch)
train_acc = accuracy(y_pred.cpu(),y_train.cpu())
writer.add_scalar('Accuracy/train', acc, epoch)

y_pred = model(X_valid.to(device))
loss = loss_fn(y_pred, y_valid.to(device))
writer.add_scalar('Loss/valid', loss, epoch)
valid_acc = accuracy(y_pred.cpu(),y_valid.cpu())
writer.add_scalar('Accuracy/valid', acc, epoch)
print (train_acc, valid_acc)

最後に

全100問の解説に戻る

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年5月13日

言語処理100本ノック2020 第7章 単語ベクトル

はじめに

言語処理100本ノック2020

言語処理100本ノックは東北大学が公開している自然言語処理の問題集です。

とても良質なコンテンツで企業の研修や勉強会で使われています。

そんな言語処理100本ノックが2020年に改定されてました。昨今の状況を鑑みて、深層ニューラルネットワークに関する問題が追加されました。(その他にも細かい変更があります)

この記事では、言語処理100本ノック2020にPythonで取り組んでいきます。

他にも色々な解法があると思うので、一つの解答例としてご活用ください!

全100問の解説に戻る

単語の意味を実ベクトルで表現する単語ベクトル(単語埋め込み)に関して,以下の処理を行うプログラムを作成せよ.

60. 単語ベクトルの読み込みと表示

Google Newsデータセット(約1,000億単語)での学習済み単語ベクトル(300万単語・フレーズ,300次元)をダウンロードし,”United States”の単語ベクトルを表示せよ.ただし,”United States”は内部的には”United_States”と表現されていることに注意せよ.

コード

gensimPythonのライブラリでトピックモデルやNLPの用途で利用されます。この記事ではgensimを使って学習済み単語ベクトルを扱おうと思います。

1
2
3
import gensim
model = gensim.models.KeyedVectors.load_word2vec_format('GoogleNews-vectors-negative300.bin', binary=True)
model['United_States']

61. 単語の類似度

“United States”と”U.S.”のコサイン類似度を計算せよ.

コード

similarityを利用します。

1
model.similarity('United_States','U.S.')

62. 類似度の高い単語10件

“United States”とコサイン類似度が高い10語と,その類似度を出力せよ.

コード

most_similarを利用します。デフォルトで上位10件を返却します。

1
model.most_similar('United_States',topn=10)

63. 加法構成性によるアナロジー

“Spain”の単語ベクトルから”Madrid”のベクトルを引き,”Athens”のベクトルを足したベクトルを計算し,そのベクトルと類似度の高い10語とその類似度を出力せよ.
positive,negativeを指定することで加算や減算が可能です。

コード

1
model.most_similar(positive=['Spain','Athens'], negative=['Madrid'],topn=10)

64. アナロジーデータでの実験

単語アナロジーの評価データをダウンロードし,vec(2列目の単語) - vec(1列目の単語) + vec(3列目の単語)を計算し,そのベクトルと類似度が最も高い単語と,その類似度を求めよ.求めた単語と類似度は,各事例の末尾に追記せよ.

コード

私の環境では処理に一時間程度かかりました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
with open('questions-words.txt') as f:
questions = f.readlines()
with open('64.txt','w') as f:
for i,question in enumerate(questions):
words = question.split()
if len(words)==4:
ans = model.most_similar(positive=[words[1],words[2]], negative=[words[0]],topn=1)[0]
words += [ans[0], str(ans[1])]
output = ' '.join(words)+'\n'
else:
output = question
f.write(output)
if (i%100==0):
print (i)

65. アナロジータスクでの正解率

64の実行結果を用い,意味的アナロジー(semantic analogy)と文法的アナロジー(syntactic analogy)の正解率を測定せよ.
先ほど出力したファイルを読み込み、適当なカラム同士を比較します。

コード

1
2
3
4
5
6
7
8
9
10
11
cnt = 0
ok = 0
with open('64.txt','r') as f:
questions = f.readlines()
for question in questions:
words = question.split()
if len(words)==6:
cnt += 1
if (words[3]==words[4]):
ok +=1
print (ok/cnt)

66. WordSimilarity-353での評価

The WordSimilarity-353 Test Collectionの評価データをダウンロードし,単語ベクトルにより計算される類似度のランキングと,人間の類似度判定のランキングの間のスピアマン相関係数を計算せよ.

コード

set1set2を結合したcombined.csvを利用すれば良いです。スピアマンの順位相関係数はpandasで計算できます。

1
2
3
4
5
6
7
8
import pandas as pd
df = pd.read_csv('wordsim353/combined.csv')
sim = []
for i in range(len(df)):
line = df.iloc[i]
sim.append(model.similarity(line['Word 1'],line['Word 2']))
df['w2v'] = sim
df[['Human (mean)', 'w2v']].corr(method='spearman')

67. k-meansクラスタリング

国名に関する単語ベクトルを抽出し,k-meansクラスタリングをクラスタ数k=5として実行せよ.

コード

国名のリストはこちらを利用しました。テキストファイルにコピペして、一部改行の修正をしました。

学習済みの語彙とは表記が一致していない国も多く、対応できるように置換を行いましたが、全ての国に対応している訳ではありません。

k-meansクラスタリングにはsklearnを利用しました。

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
from sklearn.cluster import KMeans
with open('country.txt','r') as f:
lines = f.readlines()
countries = []
for line in lines:
country = line.split(' ')[-1].replace('\n','')
countries.append(country)
dic = {'United States of America':'United_States', 'Russian Federation':'Russia'}
ng = 0
vec = []
target_countries = []
for c in countries:
for k,v in dic.items():
c = c.replace(k,v)
c = c.replace(' ','_').replace('-','_').replace('_and_','_')
try:

vec.append(model[c])
target_countries.append(c)
except:
ng += 1
kmeans = KMeans(n_clusters=5, random_state=0)
kmeans.fit(vec)
for c,l in zip(target_countries, kmeans.labels_):
print (c,l)

68. Ward法によるクラスタリング

国名に関する単語ベクトルに対し,Ward法による階層型クラスタリングを実行せよ.さらに,クラスタリング結果をデンドログラムとして可視化せよ.

コード

デンドログラムを作成する際は、scipyを使うと良いでしょう。国名が多いので、文字が潰れないように工夫が必要です。

1
2
3
4
5
6
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import dendrogram, linkage
plt.figure(figsize=(32.0, 24.0))
link = linkage(vec, method='ward')
dendrogram(link, labels=target_countries,leaf_rotation=90,leaf_font_size=10)
plt.show()

69. t-SNEによる可視化

国名に関する単語ベクトルのベクトル空間をt-SNEで可視化せよ.

コード

t-snesklearnを使うと良いです。

1
2
3
4
5
6
7
from sklearn.manifold import TSNE
vec_embedded = TSNE(n_components=2).fit_transform(vec)
vec_embedded_t = list(zip(*vec_embedded)) # 転置
fig, ax = plt.subplots(figsize=(16, 12))
plt.scatter(*vec_embedded_t)
for i, c in enumerate(target_countries):
ax.annotate(c, (vec_embedded[i][0],vec_embedded[i][1]))

続きはまた明日!

最後に

全100問の解説に戻る

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年5月9日

言語処理100本ノック2020 第6章 機械学習

はじめに

言語処理100本ノック2020

言語処理100本ノックは東北大学が公開している自然言語処理の問題集です。

とても良質なコンテンツで企業の研修や勉強会で使われています。

そんな言語処理100本ノックが2020年に改定されてました。昨今の状況を鑑みて、深層ニューラルネットワークに関する問題が追加されました。(その他にも細かい変更があります)

この記事では、言語処理100本ノック2020にPythonで取り組んでいきます。

他にも色々な解法があると思うので、一つの解答例としてご活用ください!

全100問の解説に戻る

本章では,Fabio Gasparetti氏が公開しているNews Aggregator Data Setを用い,ニュース記事の見出しを「ビジネス」「科学技術」「エンターテイメント」「健康」のカテゴリに分類するタスク(カテゴリ分類)に取り組む.

50. データの入手・整形

News Aggregator Data Setをダウンロードし、以下の要領で学習データ(train.txt),検証データ(valid.txt),評価データ(test.txt)を作成せよ.

  1. ダウンロードしたzipファイルを解凍し,readme.txtの説明を読む.
  2. 情報源(publisher)が”Reuters”, “Huffington Post”, “Businessweek”, “Contactmusic.com”, “Daily Mail”の事例(記事)のみを抽出する.
  3. 抽出された事例をランダムに並び替える.
  4. 抽出された事例の80%を学習データ,残りの10%ずつを検証データと評価データに分割し,それぞれtrain.txt,valid.txt,test.txtというファイル名で保存する.ファイルには,1行に1事例を書き出すこととし,カテゴリ名と記事見出しのタブ区切り形式とせよ(このファイルは後に問題70で再利用する).

学習データと評価データを作成したら,各カテゴリの事例数を確認せよ.

コード

pandasを使うと良いです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
from sklearn.model_selection import train_test_split
from functools import reduce
# 2. 事例の抽出
news_corpora = pd.read_csv('NewsAggregatorDataset/newsCorpora.csv',sep='\t',header=None)
news_corpora.columns = ['ID','TITLE','URL','PUBLISHER','CATEGORY','STORY','HOSTNAME','TIMESTAMP']
publisher = ['Reuters', 'Huffington Post', 'Businessweek', 'Contactmusic.com', 'Daily Mail']
ls_is_specified = [news_corpora.PUBLISHER == p for p in publisher]
is_specified =reduce(lambda a, b: a | b, ls_is_specified)
df = news_corpora[is_specified]
# 3. 並び替え
df = df.sample(frac=1) # 全てをサンプリングするので、並び替えと等価
# 4.保存
train_df, valid_test_df = train_test_split(df, test_size=0.2) # 8:2
valid_df, test_df = train_test_split(valid_test_df, test_size=0.5) # 8:1:1
train_df.to_csv('train.txt', columns = ['CATEGORY','TITLE'], sep='\t',header=False, index=False)
valid_df.to_csv('valid.txt', columns = ['CATEGORY','TITLE'], sep='\t',header=False, index=False)
test_df.to_csv('test.txt', columns = ['CATEGORY','TITLE'], sep='\t',header=False, index=False)
# 事例数の確認
df['CATEGORY'].value_counts()

51. 特徴量抽出

学習データ,検証データ,評価データから特徴量を抽出し,それぞれtrain.feature.txt,valid.feature.txt,test.feature.txtというファイル名で保存せよ. なお,カテゴリ分類に有用そうな特徴量は各自で自由に設計せよ.記事の見出しを単語列に変換したものが最低限のベースラインとなるであろう.

コード

scikit-learnCountVectorizerを使いました。単語を数え上げて特徴量を生成します。ベースラインとして適当だと思います。

1
2
3
4
5
6
7
8
9
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(train_df['TITLE'])
X_valid = vectorizer.transform(valid_df['TITLE'])
X_test = vectorizer.transform(test_df['TITLE'])
np.savetxt('train.feature.txt', X_train.toarray(), fmt='%d') # スパース行列から密行列に変換
np.savetxt('valid.feature.txt', X_valid.toarray(), fmt='%d')
np.savetxt('test.feature.txt', X_test.toarray(), fmt='%d')

52. 学習

51で構築した学習データを用いて,ロジスティック回帰モデルを学習せよ.

コード

scikit-learnに実装されているので、それを利用すると簡単です。

1
2
3
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(X_train, train_df['CATEGORY'])

53. 予測

52で学習したロジスティック回帰モデルを用い,与えられた記事見出しからカテゴリとその予測確率を計算するプログラムを実装せよ.

コード

predict_probaで各カテゴリの確率を取得することができます。

1
2
3
4
5
6
7
8
9
10
11
dic = {'b':'business', 't':'science and technology', 'e' : 'entertainment', 'm' : 'health'}
def predict(text):
text = [text]
X = vectorizer.transform(text)
ls_proba = clf.predict_proba(X)
for proba in ls_proba:
for c, p in zip(clf.classes_, proba):
print (dic[c]+':',p)
s = train_df.iloc[0]['TITLE']
print(s)
predict(s)

54. 正解率の計測

52で学習したロジスティック回帰モデルの正解率を,学習データおよび評価データ上で計測せよ.

コード

正解率を求める際はaccuracy_scoreを使います。

1
2
3
4
5
6
7
from sklearn.metrics import accuracy_score
y_train_pred = clf.predict(X_train)
y_test_pred = clf.predict(X_test)
y_train = train_df['CATEGORY']
y_test = test_df['CATEGORY']
print (accuracy_score(y_train, y_train_pred))
print (accuracy_score(y_test, y_test_pred))

55. 混同行列の作成

52で学習したロジスティック回帰モデルの混同行列(confusion matrix)を,学習データおよび評価データ上で作成せよ.

コード

confusion_matrixを用います。明示的にラベルの順序を指定することができます。

1
2
3
from sklearn.metrics import confusion_matrix
print (confusion_matrix(y_train, y_train_pred, labels=['b','t','e','m']))
print (confusion_matrix(y_test, y_test_pred, labels=['b','t','e','m']))

56. 適合率,再現率,F1スコアの計測

52で学習したロジスティック回帰モデルの適合率,再現率,F1スコアを,評価データ上で計測せよ.カテゴリごとに適合率,再現率,F1スコアを求め,カテゴリごとの性能をマイクロ平均(micro-average)とマクロ平均(macro-average)で統合せよ.

コード

precision_score,recall_score,f1_scoreを用います。引数averageとしてNoneを指定した場合はカテゴリ毎にリストで返却、micro,macroを指定した場合はそれぞれマイクロ平均とマクロ平均が返却されます。

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
print (precision_score(y_test, y_test_pred, average=None, labels=['b','t','e','m']))
print (recall_score(y_test, y_test_pred, average=None, labels=['b','t','e','m']))
print (f1_score(y_test, y_test_pred, average=None, labels=['b','t','e','m']))
print (precision_score(y_test, y_test_pred, average='micro', labels=['b','t','e','m']))
print (recall_score(y_test, y_test_pred, average='micro', labels=['b','t','e','m']))
print (f1_score(y_test, y_test_pred, average='micro', labels=['b','t','e','m']))
print (precision_score(y_test, y_test_pred, average='macro', labels=['b','t','e','m']))
print (recall_score(y_test, y_test_pred, average='macro', labels=['b','t','e','m']))
print (f1_score(y_test, y_test_pred, average='macro', labels=['b','t','e','m']))

57. 特徴量の重みの確認

52で学習したロジスティック回帰モデルの中で,重みの高い特徴量トップ10と,重みの低い特徴量トップ10を確認せよ.

コード

インスタンス名.coef_とすることで、パラメータ(重み)を取得することができます。

1
2
3
4
5
6
7
names = np.array(vectorizer.get_feature_names())
labels=['b','t','e','m']
for c, coef in zip(clf.classes_, clf.coef_): # カテゴリ毎に表示する
idx = np.argsort(coef)[::-1]
print (dic[c])
print (names[idx][:10]) # 重みの高い特徴量トップ10
print (names[idx][-10:][::-1]) # 重みの低い特徴量トップ10

58. 正則化パラメータの変更

ロジスティック回帰モデルを学習するとき,正則化パラメータを調整することで,学習時の過学習(overfitting)の度合いを制御できる.異なる正則化パラメータでロジスティック回帰モデルを学習し,学習データ,検証データ,および評価データ上の正解率を求めよ.実験の結果は,正則化パラメータを横軸,正解率を縦軸としたグラフにまとめよ.

コード

LogisticRegressionは正則化パラメータCを指定することができます。この値が小さいほど、強い正則化がかかります。

公式リファレンス

等比数列の作成には、numpy.logspaceが便利です。

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
import matplotlib.pyplot as plt
def calc_scores(c):
y_train = train_df['CATEGORY']
y_valid = valid_df['CATEGORY']
y_test = test_df['CATEGORY']

clf = LogisticRegression(C=c)
clf.fit(X_train, y_train)

y_train_pred = clf.predict(X_train)
y_valid_pred = clf.predict(X_valid)
y_test_pred = clf.predict(X_test)

scores = []
scores.append(accuracy_score(y_train, y_train_pred))
scores.append(accuracy_score(y_valid, y_valid_pred))
scores.append(accuracy_score(y_test, y_test_pred))
return scores

C = np.logspace(-5, 4, 10, base=10)
scores = []
for c in C:
scores.append(calc_scores(c))
scores = np.array(scores).T
labels = ['train', 'valid', 'test']

for score, label in zip(scores,labels):
plt.plot(C, score, label=label)
plt.ylim(0, 1.1)
plt.xscale('log')
plt.xlabel('C', fontsize = 14)
plt.ylabel('Accuracy', fontsize = 14)
plt.tick_params(labelsize=14)
plt.grid(True)
plt.legend()

59. ハイパーパラメータの探索

学習アルゴリズムや学習パラメータを変えながら,カテゴリ分類モデルを学習せよ.検証データ上の正解率が最も高くなる学習アルゴリズム・パラメータを求めよ.また,その学習アルゴリズム・パラメータを用いたときの評価データ上の正解率を求めよ.

コード

正則化パラメータC以外のハイパーパラメータとして、solver(学習アルゴリズム)やclass_weight(クラス毎の重みづけ)を指定できます。

グリッドサーチを行いたいですが、GridSearchCVはクロスバリデーションを前提としているため利用できません。そこでitertools.productで各ハイパーパラメータの組み合わせを作成しました。

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
import itertools
def calc_scores(C,solver,class_weight):
y_train = train_df['CATEGORY']
y_valid = valid_df['CATEGORY']
y_test = test_df['CATEGORY']

clf = LogisticRegression(C=C, solver=solver, class_weight=class_weight)
clf.fit(X_train, y_train)

y_train_pred = clf.predict(X_train)
y_valid_pred = clf.predict(X_valid)
y_test_pred = clf.predict(X_test)

scores = []
scores.append(accuracy_score(y_train, y_train_pred))
scores.append(accuracy_score(y_valid, y_valid_pred))
scores.append(accuracy_score(y_test, y_test_pred))
return scores

C = np.logspace(-5, 4, 10, base=10)
solver = ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga']
class_weight = [None, 'balanced']
best_parameter = None
best_scores = None
max_valid_score = 0
for c, s, w in itertools.product(C, solver, class_weight):
print(c, s, w)
scores = calc_scores(c, s, w)
#print (scores)
if scores[1] > max_valid_score:
max_valid_score = scores[1]
best_parameter = [c, s, w]
best_scores = scores
print ('best patameter: ', best_parameter)
print ('best scores: ', best_scores)
print ('test accuracy: ', best_scores[2])

最後に

全100問の解説に戻る

関連書籍

本章で出てきたscikit-learnに関する内容は以下の書籍で勉強すると良いかと思います。
コードとその解説が丁寧でぜひ手元に置いておきたい書籍です。

記事情報

  • 投稿日:2020年5月9日
  • 最終更新日:2020年7月31日