しんさんのブログ

科学や技術のこと読書のことなど

NVIDIA GPUの歴史ざっくりまとめ

GPUアーキテクチャの変遷について自分用のメモです

2006年 CUDA発表
G80アーキテクチャ発表 初めてのUnfied shader architecture
GeForce8800

2007年 Tesla HPC用
TSUBAME1.2 Tesla S1070 2008年Top500で20位

2009年 Fermi世代
double precisionの大幅な性能向上
TSUBAME2.0 Tesla M2050 TOP500で4位

2012年 Keplar世代 28nm
電力性能比の向上
多くのスパコンに採用される
TSUBAME KFC Tesla K20x Green500で1位

2014年 Maxwell世代 28nm
single precisionがKeplarの1.4倍に
doubleはKeplarより劣る -> スパコンには採用されなかった 
ディープラーニングなどのワークロードに最適化

2016年 Pascal世代 16nm
HBM2の採用
NVLink 160GB/s GPU間インターコネクト
Unified Memory CPU-GPU memory
Tesla P100
FP32: 10.6TFlops
FP64: 5.3TFlops 倍精度の性能向上
FP16: 21.2TFlops
HPCも機械学習にも対応
TSUBAME 3.0 Green500で1位
GPU仮想化に対応

2017年 Volta世代
Tensor coreの追加: 4x4 half float matrix mul -> half or single float / one clock
120TFlops
Tesla V100
AWS TeslaV100x8のP3インスタンスの提供

機械学習基礎の基礎: 過学習を防ぐ正則化

前回のブログでは2次元平面上の8つの点を10次関数でfittingしました。
すると、fittingする関数の自由度が高いために、8つの点すべてを通るようにうまくパラメータを調節できてしまい、学習データに対しては100%近い正解率がでるのに、新たなデータに対する予言の精度が下がってしまうという現象をみました。
これが過学習というもので、別の言い方をすると汎化性が低いとも言えます。
この過学習を防ぐためには学習データを増やすか、fittingする関数の自由度を下げればいいということがわかります。
ここでは自由度を下げるために、L2正則化と呼ばれる手法を実装してみます。
実装はとても簡単で、最適化する目的関数に、(1/2) theta^2 を追加するだけです。
目的関数を最小化するようなthetaの組を求めるときに、この項の効果でthetaをあまり大きな値にできないという拘束が生まれます。
これにより、自由度が下がって過学習を防ぐことができるという仕組みです。
実際のコードは以下のようになります。

###############
# overfittingと正則化の実装
###############
import numpy as np
import matplotlib.pyplot as plt

# 3次関数を定義
def f(x):
	return 0.2*(x**3 + x **2 + x)

# ノイズを付与する
trainDataX = np.linspace(-1, 1, 8)
trainDataY = f(trainDataX) + 0.05 * np.random.randn(trainDataX.size)

# dataを平均ゼロ、標準偏差1に標準化する
mu = trainDataX.mean()
sigma = trainDataX.std()

def standardization(x):
	return (x - mu)/sigma

trainDataXSTD = standardization(trainDataX)

# 10次の多項式でfittingする
# data が8個しかないのに、自由度が10個ある関数でfitするのでoverfitする

def ToMatrix(x):
	return np.vstack([
		np.ones(x.size),
		x, x**2, x**3, x**4, x**5, x**6, x**7, x**8, x**9, x**10,
		]).T

X = ToMatrix(trainDataXSTD)

# パラメータの初期値設定
theta = np.random.randn(X.shape[1])

# fitting関数
def fitfunc(x):
	return np.dot(x, theta)

# loss 関数(最小2乗誤差)
def E(x, y):
	return 0.5 * np.sum((y - fitfunc(x))**2)

eta = 1e-4 #学習率
l = 5.0 #正則化係数

# loss 関数の変化量
diff = 1
### 学習 ###
error = E(X, trainDataY)
while diff > 1e-8:
	# 正則化項
	r = l * np.hstack([0, theta[1:]]) #バイアスには正則化を足さない
	theta = theta - eta * (np.dot(fitfunc(X) - trainDataY, X ) + r);
	current_error = E(X, trainDataY)
	diff = error - current_error
	error = current_error
# 結果を表示
x = np.linspace(-1, 1, 100)
xSTD = standardization(x)
plt.plot(trainDataXSTD, trainDataY, 'o') #3次関数に誤差を乗せた学習データ
plt.plot(xSTD, fitfunc(ToMatrix(xSTD))) # fittingした10次関数の曲線
#plt.show()
# データをplotする#plt.plot(trainDataX, trainDataY, 'o')
plt.plot(xSTD, f(x))
plt.show()

このコードを実行すると、次のようなグラフが描画されます。
正則化係数lを変更すると正則化項の強さが変わって、過学習の度合いが変化するのがわかります。
f:id:wshinya:20180401231849p:plain
グラフは、緑の線が学習データの基になった3次関数で、赤が正則化後のfittingカーブです。
前回の正則化なしの結果に比べて、データの基になった関数をより近似していることがわかります。

機械学習基礎の基礎: 過学習

今回は過学習についてみて見ます。
まずは訓練データを作成します。
訓練データは3次関数にノイズを加えた8つの点(x, y)構成することとします。
プログラムの初めに8点生成して、それらの点を10次関数でfittingすることにします。
データの個数がfitting関数の自由度よりも小さいのでoverfittingすることが予想できます。
最適化の目的関数は最小2乗誤差です。
損失関数の変化が小さくなるまで学習を繰り返し、fittingする10次関数の係数thetaを決めます。
コードにまとめると,

###############
# overfittingと正則化の実装
###############
import numpy as np
import matplotlib.pyplot as plt

# 3次関数を定義
def f(x):
	return 0.2*(x**3 + x **2 + x)

# ノイズを付与する
trainDataX = np.linspace(-1, 1, 8)
trainDataY = f(trainDataX) + 0.05 * np.random.randn(trainDataX.size)

# dataを平均ゼロ、標準偏差1に標準化する
mu = trainDataX.mean()
sigma = trainDataX.std()

def standardization(x):
	return (x - mu)/sigma

trainDataXSTD = standardization(trainDataX)

# 10次の多項式でfittingする
# data が8個しかないのに、自由度が10個ある関数でfitするのでoverfitする

def ToMatrix(x):
	return np.vstack([
		np.ones(x.size),
		x, x**2, x**3, x**4, x**5, x**6, x**7, x**8, x**9, x**10,
		]).T

X = ToMatrix(trainDataXSTD)

# パラメータの初期値設定
theta = np.random.randn(X.shape[1])

# fitting関数
def fitfunc(x):
	return np.dot(x, theta)

# loss 関数(最小2乗誤差)
def E(x, y):
	return 0.5 * np.sum((y - fitfunc(x))**2)

eta = 1e-4 #学習率

# loss 関数の変化量
diff = 1

### 学習 ###
error = E(X, trainDataY)
while diff > 1e-8:
	theta = theta - eta * np.dot(fitfunc(X) - trainDataY, X)
	current_error = E(X, trainDataY)
	diff = error - current_error
	error = current_error

# 結果を表示
x = np.linspace(-1, 1, 100)
xSTD = standardization(x)
plt.plot(trainDataXSTD, trainDataY, 'o') #3次関数に誤差を乗せた学習データ
plt.plot(xSTD, fitfunc(ToMatrix(xSTD))) # fittingした10次関数の曲線
#plt.show()
# データをplotする#plt.plot(trainDataX, trainDataY, 'o')
plt.plot(xSTD, f(x))
plt.show()

f:id:wshinya:20180327000706p:plain
緑の線は学習データを作成するために使った3次関数のグラフ。
点は3次関数にノイズを加えて作成した学習用データ。
さらにオレンジの線は10次元関数でfittingした結果のグラフ。
つまり学習結果です。
オレンジのグラフは元の3次関数のグラフとはかなりずれた結果になっていますので、この結果を使用して新しい入力xに対して出力yを求めると期待しているものとは異なった結果となりそうです。
与えられた8つの学習データに対してはそれなりにいい近似になっているように見えますが、未知の入力に対しての予想精度が悪くなっている、これが過学習です。

機械学習基礎の基礎: パーセプトロンで線形分離を実装してみる

今度は、パーセプトロンを使って線形分離をしてみます。
適当に線形分離できそうなデータを以下のように作ってみました。
データは2次元平面上の点の座標(x, y)とそれぞれの正解ラベル1 or -1をつけています。
10, 20, 1
11, 30, 1
15, 30, 1
17, 35, 1
5, 25, 1
25, 35, 1
50, 40, 1
44, 38, 1
33, 30, 1
35, 35, 1
15, 8, -1
20, 2, -1
23, 11, -1
31, 22, -1
40, 35, -1
42, 29, -1
45, 39, -1
47, 37, -1
50, 39, -1
35, 20, -1
これを、テキストファイルとして読み込んで教師データとしています。
r = (x, y), 正解 t = 1or -1として、2入力、1出力のパーセプトロンをつくります。
重みw = (w0, w1)に対して、
出力層への入力は u = dot(w, r)となります。
識別関数fは
f = 1 ( if u >= 0)
f = -1 ( if u< 0)
とします。
教師データと出力が一致しない場合は
w := w + r* t
として重みw を更新します。
コードは以下のようになりました。
学習は120エポックとしました。

###############
# パーセプトロンの実装
###############
import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('linerSeparateData.txt', delimiter=',', skiprows=0)
trainDataX = trainData[:, 0:2] # (x, y)
trainDataY = trainData[:, 2] # 正解ラベル

# 重みwの初期化
w = np.random.rand(2)

# activation function
def f(x):
	if np.dot(w, x) >= 0:
		return 1
	else:
		return -1

num_epoch = 120 #エポック数
countNum = 0 #カウンター

### 学習 ###
for _ in range(num_epoch):
	for x, y in zip(trainDataX, trainDataY):
		if f(x) != y: # 予想が間違えたとき
			w = w + y*x # 重みを更新
		# print result
	countNum+=1
	print('{}: w = {}' .format(countNum, w))

# 結果の表示
x0 = np.arange(0, 50)

plt.plot(trainDataX[trainDataY == 1, 0], trainDataX[trainDataY == 1, 1], 'o')
plt.plot(trainDataX[trainDataY == -1, 0], trainDataX[trainDataY == -1, 1], 'x')
plt.plot(x0, -w[0]/w[1] * x0)
plt.show()

見た感じはうまく分離できているように見えます。
f:id:wshinya:20180325221236p:plain

機械学習基礎の基礎: 確率的勾配降下法の実装

前回のデータとコードを改造してStochastic gradient descent method(SGD)を実装してみました。
と言っても、大した改造ではなくパラメータの更新を1つの訓練サンプルごとに行うようにしただけです。
np.random.permutation(X.shape[0])
で、いったんランダムにデータを並べなおして、そこから一つずつデータを取り出してデータの数の回数だけパラメータを更新します。
最後に、平均誤差をプロットして学習が進むごとに誤差が小さくなっていることを確認してみました。

###############
# SGDの実装
###############
import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('kodomoShinchouTaijyu.txt', delimiter=',', skiprows=0)
trainDataX = trainData[:, 0]
trainDataY = trainData[:, 1]

### (2) データの標準化
mu = trainDataX.mean() # 平均
sigma = trainDataX.std() # 標準偏差

def standaradize(x):
	return (x - mu)/sigma

trainDataXSTD = standaradize(trainDataX)

# fitting 関数を2次関数にする
# y = theta0 + theta1 * x + theta2 * x^2

# 学習データをマトリクスとしてまとめる
# 2次関数でfittingするので2次まで
def toMatrix(x):
	return np.vstack([np.ones(x.shape[0]), x, x**2]).T

X = toMatrix(trainDataXSTD)

# パラメータの初期値
theta = np.random.rand(3)

### 線形fitting ###
# fitting関数 (線形回帰)
def f(x):
	return np.dot(x, theta)

# MSE関数(最小2乗の損失関数)
def E(x, y):
	return 0.5 * np.sum( (y - f(x)) ** 2)

# 平均2乗誤差
def MSE(x, y):
	return (1.0/x.shape[0] * np.sum((y - f(x))**2 ))

### 学習 ###
eta = 1e-2 #学習率
diff = 1 # 誤差

# 平均誤差を記録する
errors = []

# 学習する
errors.append(MSE(X, trainDataY))

while diff > 1e-3: # 損失関数の変化が小さくなるまでループする
	# 学習データをランダムにシャッフルする
	n = np.random.permutation(X.shape[0])
	# ランダムにシャッフルした学習データの中から一つ抜き出してパラメータを更新していく(確率的勾配降下法)
	for x, y in zip(X[n, :], trainDataY[n]):
		theta = theta - eta * (f(x) - y) * x
	# 平均誤差を配列に追加
	errors.append(MSE(X, trainDataY))
		# 1つ前のステップの誤差との差分
	diff = errors[-2] - errors[-1]

### 結果をグラフにする
x = np.linspace(-2, 2, 100)
plt.plot(trainDataXSTD, trainDataY, 'o')
plt.plot(x, f(toMatrix(x)))
plt.show()

# 平均誤差をグラフにする
x = np.arange(len(errors))
plt.plot(x, errors)
plt.show()

平均誤差の推移は以下のようになりました。20回ほどのイテレーションでほぼ収束していることがわかります。
f:id:wshinya:20180325183703p:plain

機械学習基礎の基礎: 非線形fitting

前回の線形fittingでは、身長が低いところでfittingのずれが大きくなっていましたので、今回は2次関数でfittingすることにします。
同時に、入力データやパラメータをmatrixで扱うことでコードをシンプルにしています。
前回との差分は、fitting関数を
y = theta0 + theta1 * x + theta2 * x^2
に変更したことです。

具体的なコードは以下のようになります。
matrixを使ったことでコードがシンプルになりました。

###############
# 多項式回帰の実装
###############

import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('kodomoShinchouTaijyu.txt', delimiter=',', skiprows=0)

trainDataX = trainData[:, 0]
trainDataY = trainData[:, 1]

#plt.plot(trainDataX, trainDataY, 'o')
#plt.show()

### (2) データの標準化
mu = trainDataX.mean() # 平均
sigma = trainDataX.std() # 標準偏差

def standaradize(x):
	return (x - mu)/sigma

trainDataXSTD = standaradize(trainDataX)

# fitting 関数を2次関数にする
# y = theta0 + theta1 * x + theta2 * x^2

# 学習データをマトリクスとしてまとめる
# 2次関数でfittingするので2次まで
def toMatrix(x):
	return np.vstack([np.ones(x.shape[0]), x, x**2]).T

X = toMatrix(trainDataXSTD)
#print(X)
# パラメータの初期値
theta = np.random.rand(3)

### 線形fitting ###
# fitting関数 (線形回帰)
def f(x):
	return np.dot(x, theta)

# MSE関数(最小2乗の損失関数)
def E(x, y):
	return 0.5 * np.sum( (y - f(x)) ** 2)

### 学習 ###
ncount = 0 # 更新回数
eta = 1e-2 #学習率
diff = 1 # 誤差

# 学習する
error = E(X, trainDataY)
while diff > 1e-3: # 損失関数の変化が小さくなるまでループする
	# 勾配降下法の実行
	theta = theta - eta * np.dot(f(X) - trainDataY, X)[f:id:wshinya:20180325151001p:plain]
	# 1つ前のステップの誤差との差分
	currentError = E(X, trainDataY)
	diff = error - currentError
	error = currentError
	# 途中経過を出力
	ncount += 1
	#log = '{}: t0 = {:.3f} t1 = {:.3f}, diff={:.4f}'
	#print(log.format(ncount, t0, t1, diff) )


### 結果をグラフにする
x = np.linspace(-2, 2, 100)
plt.plot(trainDataXSTD, trainDataY, 'o')
plt.plot(x, f(toMatrix(x)))
plt.show()

実行結果は以下のようなグラフになりました。

f:id:wshinya:20180325151001p:plain
2次関数でfittingした結果
直線で近似した時よりもxが小さいところでよくfitしていることがわかります。

機械学習基礎の基礎: データ読み込みから線形fittingまで

機械学習の基本である線形回帰のコードを書いてみます。

流れの説明:
1) データファイルの読み込み:
データファイルはtextデータで
x0, y0
x1, y1
 ...

のように格納しています。

今回以下のような ダミーの(身長, 体重)データを使用しました.
85, 16.8
90.5, 17.2
100.0, 17.5
110.3, 18.9
116.5, 21.4
122.5, 27.2
128.2, 30.5
133.5, 34.2
139.0, 38.2
145.0, 44.0
152.8, 49.0
160.0, 53.9
165.3, 58.9
168.2, 60.6
169.9, 62.6

2) データの標準化
読み込んだデータをパラメータの収束を早めるために標準化します。
ここでは、データの平均をゼロ、標準偏差を1にするように標準化します。

3) 線形fittingの実行
x = 身長
y = 体重
として、
線形関数
y = t0 + t1 * x
でデータをfittingします。
具体的には、パラメータt0, t1をすこしずつ変化させながら、最小2乗誤差
E = 0.5 ( y_予想 - y_正解)^2
が最小になる場所を求めます。
Eが最小になる場所ではEのt0, t1の微分がゼロになることから、パラメータを変化させたときのEの変化が小さくなったらEのlocal minimumになったと判定して、パラメータ更新のループを終了させます。
パラメータの更新の方法は勾配降下法を採用します。これは、誤差関数をパラメータで微分してその点での傾きに応じて傾きの方向に徐々に勾配を下っていくという方法です。
パラメータと誤差の変化の途中経過は毎ループprintするようにします。

4) 結果をグラフに書く
最後に、元のデータとfitting結果をグラフに描画します

以上の内容を具体的にPythonのコードで書いたものが以下になります.

###############
# 線形回帰の実装
###############

import numpy as np
import matplotlib.pyplot as plt

### (1) データ読み込み ###
trainData = np.loadtxt('kodomoShinchouTaijyu.txt', delimiter=',', skiprows=0)

trainDataX = trainData[:, 0]
trainDataY = trainData[:, 1]

#plt.plot(trainDataX, trainDataY, 'o')
#plt.show()

### (2) データの標準化
mu = trainDataX.mean() # 平均
sigma = trainDataX.std() # 標準偏差

def standaradize(x):
	return (x - mu)/sigma

trainDataXSTD = standaradize(trainDataX)

#plt.plot(trainDataXSTD, trainDataY, 'o')
#plt.show()

### 線形fitting ###
# fitting関数 (線形回帰)
def f(x):
	return t0 + t1 * x

# MSE関数(最小2乗の損失関数)
def E(x, y):
	return 0.5 * np.sum( (y - f(x)) ** 2)

### 学習 ###
ncount = 0 # 更新回数
eta = 1e-2 #学習率
diff = 1 # 誤差

# パラメータの初期値
t0 = np.random.rand()
t1 = np.random.rand()

# 学習する
error = E(trainDataXSTD, trainDataY)
while diff > 1e-3: # 損失関数の変化が小さくなるまでループする
	# 勾配降下法の実行
	tmp0 = t0 - eta * np.sum( (f(trainDataXSTD) - trainDataY))
	tmp1 = t1 - eta * np.sum( (f(trainDataXSTD) - trainDataY) * trainDataXSTD)
	t0 = tmp0
        t1 = tmp1
       # 1つ前のステップの誤差との差分
	currentError = E(trainDataXSTD, trainDataY)
	diff = error - currentError
	error = currentError[f:id:wshinya:20180325141117p:plain]
	# 途中経過を出力
	ncount += 1
	log = '{}: t0 = {:.3f} t1 = {:.3f}, diff={:.4f}'
	print(log.format(ncount, t0, t1, diff) )


### 結果をグラフにする
x = np.linspace(-2, 2, 100)
plt.plot(trainDataXSTD, trainDataY, 'o')
plt.plot(x, f(x))
plt.show()

上記コードを実行すると、私の環境ですと48回のイテレーションで結果が収束し、以下のようなグラフが表示されました。

f:id:wshinya:20180325141117p:plain
線形回帰の結果

身長が小さい時にあえて線形からずれるようにデータを作りましたのでxが小さい時にだいぶずれていますね。
それから、横軸は標準化したままのデータを描画しています。