多腕バンディット問題の実装

こんばんは。
今日のことなのですが、アルバイト先である案件を解決するために問題を多腕バンディット問題に帰着させる必要性にかられました。
今までバンディット問題などは証明が難しい印象があり食わず嫌いだったのですが、調べてみたら思っていたよりも簡単(あくまでアルゴリズムは。bound証明などはもちろん僕の手にはおえません(笑))だったので、家でも状況を多腕バンディット問題に戻してサラッと実装してみました。

多腕バンディット問題とは

多腕バンディット問題とは、機械学習の分野でもよく取り沙汰される問題です。
僕たちがゲーセンのスロットマシーンで遊ぶことを考えます。
ただし簡単のため、スロットマシーンで1回遊ぶことで得られる報酬は当たりなら1円、はずれなら0円です。
ゲーセンにはスロットマシーンが複数台あり、店員さんが小細工しているので各台ごとに1円がもらえる確率は違います。
僕たちは出来るだけたくさんお金をゲットしたいので、複数あるスロットマシーンの中で一番当たりが出るもので最初から最後までずっと遊び続ければ、利益は最大化されますよね?

でもここで問題があります。
僕たちはどのスロットマシーンが、どれくらいの確率で当たりになるか知らないのです。
なので遊んでいる回数が少ないうちは、どのマシンが利益が大きくなるのかを調べるために、多少の犠牲を払ってでもあらゆるマシンで遊ぶ必要があります。これを一般に探索といいます。
一方で、探索ばかりしていては当たる確率の低いマシンでも遊ぶため、利益を最大化するためにはどのマシンが当たる確率が高くなるかわかったら、そのマシンに集中するべきです。これを一般に活用といいます。

おわかりだと思いますが、探索と活用はトレードオフの関係にあります。
探索ばかりしていては利益を大きくできませんし、活用ばかりしていたらひょっとしたらもっと良いマシンがあるかもしれないのにもったいないって状況になるかもしれません。

探索と活用のトレードオフをいい塩梅にするアルゴリズムがいくつか提案されています。
以下では代表的な3つ、「トンプソンサンプリング」「UCB」「ε-greedy」を実装しています。
詳細は省きますが、多分コードを見てすぐわかるくらい単純な数式で導かれます。
slot_machine関数の中身や、スロットで遊ぶ回数を変えながら遊んでみると地味に楽しいですよ。
1分くらいで飽きますけど

# -*- coding: utf-8 -*-
import numpy as np
import scipy.stats

def slot_machine(n):
	# 人工スロットマシーン。
	# nは引くスロットのラベル。
	# 引くスロットの当たる確率をもとに、当たりならば1を、外れならば0を出力
	prob_list = [0.3, 0.5, 0.7, 0.5, 0.3]
	prob = prob_list[n]
	return scipy.stats.bernoulli.rvs(prob, size=1)

class BanditProblem:
	def __init__(self, n_trial=np.zeros(5), n_hit=np.zeros(5)):
		self.n_trial = n_trial  # 各スロットマシーンの試行回数
		self.n_hit = n_hit  # 各スロットマシーン試行回数のうち当たった回数

	def add_new_information(self, new_arm, hit_or_miss):
		# 新しく試行をしたときに、結果を追加する
		# new_armは新しくスロットを引いたマシーンのラベル、hit_or_missは当たりかはずれか。
		# 当たりならば1、はずれならば0を入れる
		self.n_trial[new_arm] += 1
		self.n_hit[new_arm] += hit_or_miss

	def decide_next_slot(self, method = 'ThompsonSampling', epsilon=0.2):
		# 次にどのスロットを引いたらいいか、スロットのラベルを出力する。
		#  アルゴリズムは「トンプソンサンプリング」「UCB方策」「ε-greedy探索」から選べる。
		if np.count_nonzero(self.n_trial) !=5:
			# もしまだ引いたことがないスロットがあるのなら、それを引く。
			indexes=np.where(self.n_trial == 0)[0]
			return np.random.choice(indexes)

		else:
			if method == 'ThompsonSampling':
				return self.thompson_sampling()

			elif method == 'UCB':
				return self.UCB()

			else:
				return self.epsilon_greedy(epsilon)

	def thompson_sampling(self):
		alpha = self.n_hit + 1
		beta = self.n_trial - self.n_hit +1
		prob = np.random.beta(alpha, beta)
		return np.argmax(prob)

	def UCB(self):
		mu = self.n_hit / self.n_trial
		UCB=mu + np.sqrt(2*np.log(self.n_trial.sum()) / self.n_trial)
		return np.argmax(UCB)

	def epsilon_greedy(self, epsilon):
		if np.random.rand() < epsilon:
			return np.argmin(self.n_trial)

		else:
			return np.argmax(self.n_hit / self.n_trial)

if __name__ == '__main__':
	n_search=30  # スロットを引く回数
	bp=BanditProblem()
	for i in range(n_search):
		next_arm = bp.decide_next_slot()  # 次のスロットマシーンを選ぶ
		result = slot_machine(next_arm)  # スロットマシーンを引く
		bp.add_new_information(next_arm, result)  # 得られた情報の追加
	print(bp.n_hit)
	print(bp.n_trial)

AlexNetをchainerで実装(scikit-learn風味)

お久しぶりです。
はてなブログって90日以上更新がないと広告が出るんですね…

scikit-learnっていいよね

突然ですが、scikit-learnが僕は大好きです。
scikit-learnと結婚したいくらい好きです。
だってメジャーで定番な手法はディープラーニング以外はだいたい揃っているし、「この手法で予測モデル作りたいなあ」って思ったら「scikit-learn ランダムフォレスト」とかってググって見つけたらあとは定義してfitしてpredictするだけです。ちょーかんたん。
でもディープラーニングを使いたいとき、scikit-learnって多層パーセプトロンはかろうじてありますが、もうちょっと凝ったニューラルネットを作りたい場合無理です。
多分ディープラーニング自体設計の自由度が高すぎるので、汎用的なメソッドとして確立するのは難しいからなのかなあと思ったり。

じゃあ自分で作っちゃおう

僕はchainerも大好きです。
愛人にしたいくらい好きです。
あくまで僕の好みなんですけど、Kerasはめちゃめちゃ簡単なかわりにあまり凝った構造を作れなくて、theanoやcaffeは難しすぎる感じで、tensorflowとchainerが良い感じな印象を持っています。
ただいかんせん、やっぱ自分でネットワークを作らなきゃなぶん、「scikit-learnほど手軽に」ってわけにはいきません。
ディープラーニングが使いたくなるたびにググってつくるのはめんどくさい
今後のコーディングを効率化するために、テンプレを作っておきました。

今回つくったものは、AlexNetと多層パーセプトロンとCNNです。
下記コードの80行目あたりのmodel=AlexNet()をmodel=MLP()とかって書き換えれば選択可能です。
バッチサイズなどのハイパーパラメータの調整は13~16行目を書き換えて、ネットワークの構造は…まあ中身をいじってください。

使い方はメイン関数の中を見ていただければわかると思いますが、結婚したいくらい好きなscikit-learnのやり方に準拠しています。
このメイン関数の例はMNISTの分類で、AlexNetと多層パーセプトロンとCNNのどれを使っても精度98%以上は出ます。
おすすめは、計算時間が有り余っているならばAlexNet、時間がないなら多層パーセプトロンって感じですかね。
なんてったってAlexNetはチャンピオンモデルの一つですしね~。
また、トレーニング中は進捗をバーで教えてくれます。
ちょっと見ない間にchainerがスーパーパワーアップしていて驚きました(笑)。
カラー画像の分類などはためしていないので、バグを含んでいるかもしれません。
適宜確認次第修正、メソッドの追加などしていくつもりです。

# -*- coding: utf-8 -*-
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_mldata
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import optimizers, Chain, Variable, initializers
from chainer.training import extensions
from chainer.datasets import tuple_dataset

# モデル設定
batch_size = 100  # バッチサイズ
n_epoch = 20  # エポック数
n_channel = 1  # channel数(画像の奥行的な。カラー画像ならRGBなので3、モノクロなら1)
n_label = 10  # 正解ラベルの種類数

class MLP(Chain):
	# 多層パーセプトロンによる分類
	def __init__(self):
		super(MLP, self).__init__()
		with self.init_scope():
			self.fc1 = L.Linear(None, 100)
			self.fc2 = L.Linear(100, n_label)
			self.bn1 = L.BatchNormalization(100)

	def __call__(self, x):
		h = F.sigmoid(self.fc1(x))
		h = self.bn1(h)
		return self.fc2(h)

class LeNet(Chain):
	# CNNを用いた分類
	def __init__(self):
		super(LeNet, self).__init__()
		with self.init_scope():
			self.conv1 = L.Convolution2D(n_channel,6,5,1)
			self.conv2 = L.Convolution2D(6,16,5,1)
			self.conv3 = L.Convolution2D(16,120,4,1)
			self.fc4 = L.Linear(None, 84)
			self.fc5 = L.Linear(84,n_label)

	def __call__(self, x):
		h = F.sigmoid(self.conv1(x))
		h = F.max_pooling_2d(h, 2, 2)
		h = F.sigmoid(self.conv2(h))
		h = F.max_pooling_2d(h,2,2)
		h = F.sigmoid(self.conv3(h))
		h = F.sigmoid(self.fc4(h))
		return self.fc5(h)

class Alex(Chain):
	# AlexNet
	def __init__(self):
	    super(Alex, self).__init__(
	        conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
	        conv2 = L.Convolution2D(96, 256, 5, pad=2),
	        conv3 = L.Convolution2D(256, 384, 3, pad=1),
	        conv4 = L.Convolution2D(384, 384, 3, pad=1),
	        conv5 = L.Convolution2D(384, 256, 3, pad=1),
	        fc6 = L.Linear(None, 4096),
	        fc7 = L.Linear(4096, 4096),
	        fc8 = L.Linear(4096, n_label),
	    )

	def __call__(self, x):
		h = F.max_pooling_2d(F.local_response_normalization(
		    F.relu(self.conv1(x))), 3, stride=2)
		h = F.max_pooling_2d(F.local_response_normalization(
		    F.relu(self.conv2(h))), 3, stride=2)
		h = F.relu(self.conv3(h))
		h = F.relu(self.conv4(h))
		h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)
		h = F.dropout(F.relu(self.fc6(h)))
		h = F.dropout(F.relu(self.fc7(h)))
		return self.fc8(h)

class DeepLearningClassifier:
	def __init__(self):
		model = Alex()
		self.model = L.Classifier(model)
		self.opt = optimizers.Adam()
		self.opt.setup(self.model)

	def fit(self,X_train, y_train):
		train_data = tuple_dataset.TupleDataset(X_train, y_train)
		train_iter = chainer.iterators.SerialIterator(train_data, batch_size)
		updater = chainer.training.StandardUpdater(train_iter, self.opt)
		self.trainer = chainer.training.Trainer(updater, (n_epoch, 'epoch'), out='result')
		self.trainer.extend(extensions.LogReport())
		self.trainer.extend(extensions.PrintReport(['epoch', 'main/accuracy']))
		self.trainer.extend(extensions.ProgressBar())
		self.trainer.run()

	def fit_and_score(self, X_train, y_train, X_test, y_test):
		train_data = tuple_dataset.TupleDataset(X_train, y_train)
		test_data = tuple_dataset.TupleDataset(X_test, y_test)
		train_iter = chainer.iterators.SerialIterator(train_data, batch_size)
		test_iter = chainer.iterators.SerialIterator(test_data, batch_size, repeat=False, shuffle=False)
		updater=chainer.training.StandardUpdater(train_iter, self.opt)
		self.trainer = chainer.training.Trainer(updater, (n_epoch, 'epoch'), out='result')
		self.trainer.extend(extensions.Evaluator(test_iter, self.model))
		self.trainer.extend(extensions.LogReport())
		self.trainer.extend(extensions.PrintReport(['epoch', 'main/accuracy', 'validation/main/accuracy']))
		self.trainer.extend(extensions.ProgressBar())
		self.trainer.run()

	def predict(self, X_test):
		x=Variable(X_test)
		y=self.model.predictor(x)
		answer=y.data
		answer=np.argmax(answer, axis=1)
		return answer

	def score(self, X_test, y_test):
		y=self.predict(X_test)
		N=y_test.size
		return 1.0-np.count_nonzero(y-y_test)/N

	def predict_proba(self, X_test):
		x=Variable(X_test)
		y=self.model.predictor(x)
		y=np.exp(y.data)
		H=y.sum(1).reshape(-1,1)
		return np.exp(y)/H

if __name__=='__main__':
	# mnist 使用例
	# 前処理
	mnist = fetch_mldata('MNIST original', data_home=".")
	X = mnist.data
	y = mnist.target
	X =X/X.max()
	X = X.astype(np.float32)
	y = y.astype(np.int32)
	X =X .reshape(70000,1,28,28)  # 必ず(データの総数, channel数, 縦, 横)の形にしておく
	X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

	# 定義
	clf = DeepLearningClassifier()
	# トレーニング
	clf.fit(X_train, y_train)
	# 予測
	prediction = clf.predict(X_test)
	# 精度測定
	acc = clf.score(X_test, y_test)

	"""
	トレーニングと予測精度の測定を一度にやってしまいたい場合は
	clf.fit_and_score(X_train, y_train, X_test, y_test)
	各ラベルの確率を計算したいなら
	clf.predict_proba(X_test)
	"""

最後に

前回のchainerの記事は、chainerのversion1に準拠したものでしたが、chainerがversion2に変わってメソッドなどがいろいろ変わりました。
以前のコードはもう今は確実に動かないです。
ちなみにchainerを作っているPFNの社員さんが、2017年9月末にchainerの新バージョンをリリースすると言っていました。
めっちゃ更新されていて少し勉強を怠ると全く別の生き物になっていそうなところが、僕が感じるchainer唯一の欠点です。

chainerのディープラーニング(多層パーセプトロン?)をscikit-learn風味に

おひさです

お久しぶりです。
忙しすぎて「ブログに勉強したことまとめっぞ!」と思っていたのに三日坊主になりかけてました(笑)。
最近研究が軌道に乗ってきたようなそうでもないような…
リア充ではないけどラボ充です( ̄▽ ̄)

はやりのディープラーニングをscikit-learn風にしちゃいました

はい。しちゃいました。

僕の研究内容はハイパーパラメータの推定手法で、ディープラーニングは専門ではないのですが、ご存知ディープラーニングはハイパーパラメータがアホみたいにたくさんあるので、なにかとハイパーパラメータ推定の実験材料になったり、今しているアルバイトでディープラーニングによる分類モデルを作成したりとなにかと作る機会があって、「これいちいち作るのめんどいなあ」と思っていました。

それならばもうハイパーパラメータを調整する部分をある程度一か所にまとめておいて、scikit-learnっぽく使えるようにしておこうと思い下のプログラムを作成しました。(ほんとはなんか別のクラスを作成することでそんなことができるらしいのですが、まあモデル作成の手間を省きたかったので)
使用例はメイン関数内に書いてあるのでわかると思います。
ほんとうはモデル調整部分を一か所にまとめておきたかったのですが…思うことあって__init__とforwardとfitのしょっぱなのself.modelの部分に分散してあります。
ここの部分を書き換えれば、基本的なニューラルネットならどんな形でも対応可能なんじゃないかなあ。
もっとうまい書き方ありそうですが悪しからず!


なお、本プログラムの作成にあたりこのブログを全面的に参考にさせていただきました。
「あんまりディープラーニング知らない…」みたいな人もこれ見ればすぐわかるんじゃないかなあってくらい親切すぎる解説でとても参考になりました。
qiita.com


試していただけるとわかりますが精度が40%程度とクズです(笑)
調整がめんどいので←
使用する場合はハイパーパラメータの調整は適宜問題にあわせてやってください。
間違いや「こうした方がいいぞ!」などございましたら教えていただけると幸いです。
(*^-^*)

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from chainer import Variable,FunctionSet,optimizers
import chainer.functions as F
import chainer.links as L
from sklearn.datasets import fetch_mldata
from sklearn.cross_validation import train_test_split

class DeepLearning:
	def __init__(self):
		# バッチサイズ
		self.batchsize = 1000
		# エポック数
		self.n_epoch = 10
		# 最適化手法
		self.optimizer = optimizers.Adam()

	def forward(self, x_data, y_data=None, train=True):
		x = Variable(x_data)
		b1 = self.model.bn1(x)
		h1 = F.dropout(F.relu(self.model.l1(b1)), ratio=0.2, train=train)
		b2 = self.model.bn2(h1)
		h2 = F.dropout(F.relu(self.model.l2(b2)), ratio=0.2, train=train)
		b3 = self.model.bn3(h2)
		y = F.dropout(F.relu(self.model.l3(b3)), ratio=0.2, train=train)

		if train:
			t = Variable(y_data)
			return F.softmax_cross_entropy(y,t), F.accuracy(y,t), y.data

		else:
			return y.data
		
	def fit(self, x_train, y_train):

		self.number_of_label=np.unique(y_train).size
		self.model = FunctionSet(
			bn1 = L.BatchNormalization(x_train.shape[1]),
			l1 = F.Linear(x_train.shape[1], 1000),
			bn2 = L.BatchNormalization(1000),
			l2 = F.Linear(1000, 1000),
			bn3 = L.BatchNormalization(1000),
			l3 = F.Linear(1000, self.number_of_label)
			)

		# トレーニングデータの数
		N = y_train.size
		# optimizer セットアップ
		self.optimizer.setup(self.model.collect_parameters())

		# 念のためトレーニングデータの型合わせ
		x_train = x_train.astype(np.float32)
		y_train = y_train.astype(np.int32)

		# Learning loop
		for epoch in range(1,self.n_epoch+1):
			# エポック数表示
			print 'epoch{}'.format(epoch)

			# training
			# N個の順番をランダムに並び替える
			perm = np.random.permutation(N)

			# ロスと正答率初期化
			sum_accuracy = 0
			sum_loss = 0
			# 0〜Nまでのデータをバッチサイズごとに使って学習
			for i in range(0, N, self.batchsize):
				if i + self.batchsize < N:
					x_batch = x_train[perm[i:i+self.batchsize]]
					y_batch = y_train[perm[i:i+self.batchsize]]

				else:
					x_batch = x_train[perm[i:]]
					y_batch = y_train[perm[i:]]

				# 勾配を初期化
				self.optimizer.zero_grads()
				# 順伝播させて誤差と精度を算出
				loss, acc, answer = self.forward(x_batch, y_batch)
				# 誤差逆伝播で勾配を計算
				loss.backward()
				self.optimizer.update()

				sum_loss     += float(loss.data) * x_batch.shape[0]
				sum_accuracy += float(acc.data) * x_batch.shape[0]

			print 'train mean loss={}, accuracy={}'.format(sum_loss / N, sum_accuracy / N)

	def predict(self, x_test):
		N_test=x_test.shape[0]
		# ロスと正答率初期化
		sum_accuracy = 0
		sum_loss = 0
		answers = np.array([])
		x_test = x_test.astype(np.float32)
		for i in range(0, N_test, self.batchsize):
			if i + self.batchsize < N_test:
				x_batch = x_test[i:i+self.batchsize]

			else:
				x_batch = x_test[i:]
			
			# 順伝播させて出力
			sub_answer = self.forward(x_batch, train=False)
			# 出力最大となるラベルを予測値とする
			sub_answer = np.argmax(sub_answer, axis=1)
			answers = np.append(answers, sub_answer)

		answers=answers.reshape(-1,1)

		return answers

	def predict_proba(self, x_test):
		N_test=x_test.shape[0]
		# ロスと正答率初期化
		sum_accuracy = 0
		sum_loss = 0
		answers = np.zeros((x_test.shape[0], self.number_of_label))
		x_test = x_test.astype(np.float32)
		for i in range(0, N_test, self.batchsize):
			if i + self.batchsize < N_test:
				x_batch = x_test[i:i+self.batchsize]
				answers[i:i+self.batchsize] = self.forward(x_batch, train=False)

			else:
				x_batch = x_test[i:]
				answers[i:] = self.forward(x_batch, train=False)
		
		probability=np.exp(answers)/np.sum(np.exp(answers), axis=1).reshape(-1,1)
		return probability

	def score(self, x_test, y_test):
		predict_y = self.predict(x_test).reshape(-1,)
		wrong_size = np.count_nonzero(predict_y - y_test)
		score=float(y_test.size - wrong_size)/y_test.size
		return score

if __name__=='__main__':
	# 使用例

	# 前処理
	mnist=fetch_mldata('MNIST original',data_home=".")
	data=mnist.data
	data/=data.max()
	target=mnist.target
	# データを訓練用とテスト用に分割
	x_train, x_test, y_train, y_test=train_test_split(data, target, test_size=0.2)

	# 分類器定義
	DL = DeepLearning()
	# トレーニング
	DL.fit(x_train, y_train)

	# 予測
	predict_answer = DL.predict(x_test)
	print predict_answer
	# 各ラベルをとる確率の計算
	probability = DL.predict_proba(x_test)
	print probability
	# テストデータでの正答率
	score = DL.score(x_test, y_test)
	print score

影の薄いカーネル密度推定にもっと注目してあげてほしい!

おはようございます。

ここ最近研究発表と論文読み会と課題に追われて、研究のアイデアはたまってきたのに実験に移る時間がないりとささんです。

いやもうさあ、再来週も発表あるし今週も発表だったし論文読み会のために自分の研究分野とは無関係の論文読まなきゃだし研究させる気があるのかいって感じです(笑)

 

最近ハマっていて、そんな研究のイライラを癒してくれるものがファミマのピザまんです。←

あれ本当に食べた瞬間チーズがびよーんってなるくらいトロトロに溶けていて本当においしいんですね(*´ω`)

なんか今100円セールやってるみたいなので食べたことない方はぜひ食べてみてください。

 

 

さてさて、機械学習には機械学習の大支柱とも言うべき強力なアルゴリズムがいくつかあります。

SVMしかり、CNNしかり、k-meansしかり、MCMCしかり...

でもそんなド派手で表立って活躍するアルゴリズムの陰に隠れて、ちゃっかり有用で、データを運用するうえで、つまり人間がデータを解釈しそれをどのように実社会の抱える問題に適用させていくかを決めるうえで、非常に重要なアルゴリズムも存在します。

 

今回はそんなアルゴリズムの一つ(だと僕が勝手に思っている)でもっともっと日の目を見てもいいんじゃあないか?と思っている、カーネル密度推定についてメモ書きしていこうと思います。

 

カーネル密度推定ってなんですのん?

そういえば最近8.6秒バズーカ見ない...(笑)

それはさておき、カーネル密度推定とは一言で言ってしまえばヒストグラムの連続バージョンです。

ヒストグラムとは、ある変数が各区間内であるサンプルの数を数え、その数を棒グラフにしたやつでしたね。

カーネル密度推定では、そのサンプルの分布を連続な関数として表現することができます。

カーネル密度推定により得られる分布曲線は次のように表されます。

http://www.fukudat.com/texcrop/render.php/texcrop.png?tex=%5Cbegin%7Balign*%7D%0Ap%28x%29%3D%5Cfrac%7B1%7D%7Bnh%5E%7Bd%7D%7D%5Csum_%7Bi%3D1%7D%5E%7Bn%7DK%28%5Cfrac%7Bx-x_%7Bi%7D%7D%7Bh%7D%29%0A%5Cend%7Balign*%7D&resolution=150&font=times&jfont=hiragino&opaque=off&bold=off

ただし、nはサンプル数、x_iは各サンプルの座標、dは入力xやx_iの次元数、hはバンド幅、Kはカーネル関数と呼ばれるものになっています。

Kは全入力空間で積分した結果が1であるという正規化条件を満たさなければなりません。

一般的にはKにはガウスカーネルを用いられることが多く、それを考えるとバンド幅というものは、標準偏差のようなものだとわかります。

ようは一点が周囲の領域の関数に及ぼす範囲の大きさを示しています。

 

非常にシンプルな形で書けるカーネル密度推定!
実際にこれを使ってみましょう。

この程度なら自分で実装するのも簡単そうですが、scipy大先生の力を借りると一撃なので、素直にscipy大先生に頼ります。
まずは必要なライブラリをインポートします。

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import gaussian_kde

次に、推定する分布をサンプルしてみましょう!
普通のガウス分布でもいいのですがそれだけだとつまらないので、2つばかりガウス分布を混ぜてみます。
よく使われるフツ―のヒストグラムではこのような分布になります。

random_seed=np.random.randint(2,size=10000)
samples=random_seed*np.random.normal(loc=40.0,scale=20.0,size=10000)+(1-random_seed)*np.random.normal(loc=120.0,scale=30.0,size=10000)
plt.hist(samples,bins=30)
plt.show()

f:id:phyquar:20170115072406p:plain

うーん...
これを見るとなんかヒストグラムで全然いい感じがしますね!←

でもでも、ヒストグラムだとビン幅(棒グラフ1個の横幅)の調整をいい感じに調整してあげないといけません(カーネル密度推定もバンド幅の調整が必要だということには目をつむります笑)。
要するにこの棒グラフが細かすぎるとちょいちょい棒の間に一つもサンプルが入らない謎な分布ができたり、一つの区間内に入るサンプルが少なくなるためその区間内のサンプル数に意味が見出しづらくなりますし、逆にビン幅が太すぎると、真の分布がなかなかみえてこなかったりします。こんな感じで↓

f:id:phyquar:20170115073251p:plain

f:id:phyquar:20170115073414p:plain

取りうる変数の値が連続で、かつサンプル数が非常に多い場合、その分布の全容を知るには、ヒストグラムを使うよりもむしろカーネル密度推定を使うことをおすすめします。
視覚的にも見やすいですし、多分これで確率分布を求めておけばMCMCとかを使って、この分布にしたがったサンプルを自分で生成できるんじゃないかなあと思います。
早速使ってみましょう(*^-^*)

search_range=np.linspace(np.min(samples),np.max(samples),num=10000)
kde_model=gaussian_kde(samples)
plt.plot(search_range,kde_model(search_range))
plt.show()

f:id:phyquar:20170115074622p:plain

うん。連続な分布が描写できました!(笑)
ちなみにさっきMCMCを使えばリサンプリングできるんじゃなかろうかといいましたが、このscipy.stats.kde_gaussian()には.resampleメソッドがあり、推定した確率密度関数に従うサンプルを生成してくれるそうです。
ヒストグラムを使っているのではこんなことはできません。
これらの作業がたったの数行で終わってしまうなんてやっぱりカーネル密度推定ってすごい!!(ライブラリがすごいだけかもですけど笑)
研究でサンプルがいくつも取れて、その値が連続値なときはヒストグラムもいいですけどぜひぜひカーネル密度推定君をつかってあげてください。
僕はこのカーネル密度推定、知っている方は知っていますけど、もっともっとメジャーな手法になってもいいんじゃないかなあと思ってます。
ヒストグラムの単純かつ明瞭さの陰に隠れてしまっていますが(笑)
プレゼンの資料とかにも使いやすいと思います。


今日は靴底に穴が空いたのでこれから靴を買いに行きます!
ではまた!

人工知能ってなんぞや???

こんばんは。

ただでさえ友達が少ないのに友達みんな帰省してしまい、しかもお金がないというダブルパンチで、大晦日だというのにやることがなくて困っているりとささんです。

 

思えば今年はひじょおおおおおに濃密な一年間でした。

濃密すぎてちょっと疲れました(笑)あんなにおいしいダシがとれるのだからその素はもっとおいしいのだろうとおもって、味の素をペロッと舐めてオエッってなる感じです(伝わりますかね...笑)

 

僕の今の研究分野の機械学習を本格的に勉強し始めたのも、ちょうど1年ほど前からだったような気がします。

それまでは別のことを勉強していました。(物理モデルを用いた"最適化"による画像修復なので、一概に無関係とは言えないかもしれませんが)

勉強したことをここではちょくちょく記録して振り返ろうと思うのですが、その前に機械学習が何たるか、勉強してわかったことから俯瞰的な立ち位置で、今年の締めくくりとして大雑把にかつわかりやすく頑張って説明してみたいと思います。

 

最近人工知能がアツいですよね

アツくないですか?(笑)

ちょうど人工知能ブームが来る直前にこの世界に入った感じなので、我ながら先見の明があったのだなあと。(すみません本当に超たまたまなんです)

 

あらゆるメディアでもてはやされている人工知能ですが、無理もありません。

最近ではGoogleが開発したAlphaGoという人工知能囲碁ロボットが、囲碁のトップ棋士に4勝1敗で勝ち越しました。

1990年代ごろだったかな...すでにチェスではヨーロッパのどこかの国のチェスチャンピオンが機械に負けているのですが(それにまつわるある心温まるお話も聞いたことがあるのですがそれはまた別の機会に笑)、チェスと囲碁とを比較すると、取りうる盤面の数が桁違いで囲碁の方が多いので、長年機械は人間には囲碁では勝てないと思われていました。

 

また、今医療診断に人工知能を導入しようとする動きもあり、実際にアメリカ軍の医療機関では医師でも発見が難しいガン細胞を画像から見つける人工知能が導入されています。

 

国内でももちろん研究がどんどんされていて、東京大学合格を目指す人工知能「東ロボくん」の開発も記憶に新しいです。

実はもう今は東大合格は諦めているようなのですが(人間が持ち合わせている「常識」が絡んでくる問題や、言語処理系の問題がやはりまだ苦手なようです)、それでも数学の全国模試で偏差値68を出したりしています。

 

このように「機械ができること」がどんどん増えていっています。

しかし一方でそれに対して恐怖心を覚える人もいて、ひょっとしたら人工知能が人間の知性を超える「技術的特異点(シンギュラリティ)」の到来も絵空事ではないのではないかと思う人もたくさんいるようです。(僕自身はこのシンギュラリティの到来には懐疑的な立場なのですが、その理由はこのブログの最後に述べます)

 

人工知能って中身どーなってんの?

しかしいくら「知能」といっても、所詮は機械のなかで行われる計算が導き出した「人口」のものなので、機械の中ではめまぐるしく計算が行われ、その結果を反映させているにすぎません。

この裏で動かしているアルゴリズムのことを、総称して機械学習アルゴリズムとよんでいます。

機械学習アルゴリズムには本当にたくさん種類があり、とても挙げきれるものではないのですが、超大まかにわけると、「教師あり学習(supervised machine learning)」と「教師なし学習(unsupervised machine learning)」にわけられます。(本当は強化学習や、半教師あり学習などの亜種もあるのですが、脇においときます笑)

人工知能というと、小難しいことが内部で起こっているんだろうと思われがちですが、細かいアルゴリズムはさておいて、やっていること自体は至極単純です。

 

  • 教師あり学習

教師あり学習とは、ある解きたい問題があったときに、それに対する「問題」と「答え」を与えて機械に学習させていき、そのモデルで今度は「答え」がない問題に対して予測、対処させるという方法です。

 

f:id:phyquar:20161231140709p:plain

 

 やっていることは、僕たちが学校でテスト期間中に経験した、「テスト前日に教科書の問題を見る→何らかの答えを出す→答え合わせ→間違っているところは勉強する→テスト当日→とりあえずわかってもわからなくても解答欄埋める」のプロセスと全く変わりません。

代表的な手法としては、ニューラルネットサポートベクターマシンSVM)が挙げられます。

 

教師あり学習の目標は、「未知の状況に対して、正解を予測する」ということに尽きると思います。

乱数を発生させてサンプルを作ってみました!下の図をご覧ください。

f:id:phyquar:20161231144430p:plain

 上の図は地図で、それぞれのプロットは観測した天気(赤が晴れ、青が雨)だと思ってください。

ここで、教師あり学習の一手法であるサポートベクターマシンというありがた~い手法を使ってこの二つの境界線を引くと次のようになります。

f:id:phyquar:20161231150908p:plain

 

なんか若干パラメータチューニングがうまくいっていない気がしますが(笑)

とにかく境界線が引けました。

この例では、「各サンプルの位置において、天気は晴れか雨か?」という問題に対して、「各地点におけるお天気データ(実際に晴れだったか雨だったか)」といった答えをもとに、学習を行っています。

 

「これができて何が嬉しいんだよ!」と思う方もいると思います。

例えば観測ができなかった次の緑の位置の天気を知りたいときはどうでしょう。

f:id:phyquar:20161231152230p:plain

 

このとき、もし境界線を作っておけば、たとえ本当の天気を知らなくても「この境界線より下側にあるから、この緑の点は晴れなんじゃないかな」と機械が予測することができます。

今のはすごく簡単な例でしたが、この「機械が結果を予測する」ということが実は昨今人工知能と呼ばれるものの礎になっていたりします。

 

  • 教師なし学習

教師なし学習とは、教師あり学習では「答え」が存在していましたが、その答えがないバージョンです。

f:id:phyquar:20161231155451p:plain

先程とは異なり、サンプルごとに「正解」は与えられていません。

ですが僕たち人間がこの図をパッと見たときに、「なんとなく右上と左下にかたまりがあるな~」ということが直感的にわかると思います。

ですがこのことは、機械にとって必ずしも自明ではありません。

が、教師なし学習のk-means法と呼ばれる手法などなどを使うと、いい感じに分離してくれます。

この、なんとなーくなかたまりを見つけるアルゴリズムクラスタリングと言ったりします。

このように、教師あり学習は「結果を予測する」という目的のアルゴリズムだったのに対し、教師なし学習では「データの背後に隠れた規則性を見つけ出す」という目的のアルゴリズムであるといえます。

それゆえ、教師なし学習での実験、とくにクラスタリングの実験では、正解か不正解かを測る尺度がないので、定量的な評価が難しかったりします。

僕の研究室の博士は、「クラスタリングの実験は評価のしようがないから、見た目の美しさで決まるんやで。あんなのアートやん」って言ってました。(笑)

 

シンギュラリティって来ますかね?来ませんかね?

どう思いますか?

実はえらい学者さんの間でも意見が分かれているようで。

このままの状態で技術が進歩していくと、2045年に人工知能の知性が人間をこえるのではないかという2045年問題というのも話題です。

 

matome.naver.jp

 

ちなみに僕は来ないんじゃないの?派です。(笑)研究しといてあれですけど。(笑)

 

まず、人工知能が人間の知性を超えるためには、自ら考え、創造するという、ある意味0から1を生み出すような発想の機構が必要になってきます。(でないと、その人工知能はそれを作った作成者の知性を超えることができません)

ここで、今まで述べたように人工知能の背後で動くアルゴリズムを考えたときに教師あり学習か教師なし学習かの大まかに二つに分類されるのでした。

しかしながら、教師あり学習はあくまで今までのトレーニングデータ(問題と正解)の積み重ねから、未知の状況に対して結果を予測しているにすぎません。

つまり、トレーニングデータからは全く想像もつかないような結果を出力するということはできません。

教師なし学習に至ってはその目的がデータの隠れた規則性を見つけることなので論外です。

唯一可能性があるとしたら、今まで説明しなかった強化学習かなとも思います。

強化学習とは、機械にある探索範囲内で自分でtryアンドerrorで「学習」をさせ、その結果をトレーニングに使うという、自学自習版教師あり学習です。

ですが、これも探索範囲や探索のルールを作成者が決めておく必要があるので、この範囲外の行動は機械はできません。

 

もう少し踏み込んで、今現在機械が人間を超えているものについて考えます。

だいたいこの3つなんじゃないかなあと思います。

  1. パワー
  2. 情報処理、伝達速度
  3. 情報記憶力

パワーについて、僕たちがショベルカーに勝てないのは言うまでもありませんが、そのパワーの源は、電気だったりガソリンだったりといった、エネルギー資源を力という形に変換しているおかげだといえます。

情報処理、伝達速度について、これらはICチップの中を電気がめっちゃ速く動いていたり、光ファイバーが光速(屈折率とかありますけど)で移動しているおかげです。

情報記憶力に関しては、2の情報処理速度が人間も速ければ、同じ土俵に立てます。

だって高速でメモを取ればいいんだもの(笑)

 

これらのことを考えると、1と2に関しては自然界の資源やその物質の特性の恩恵を受けてようやく人間を超えることができているといえます。

人間の知性を超えるということは、作成者が人間であるうちは、「知性の源泉」的なサムシングを見つけない限り不可能なのではないかなあと思います。

 

まとめ

 

と、ここまで言っておいてあれなのですが、人間に害のない形でならシンギュラリティ来てほしいです。(笑)

だって研究とか仕事とか機械にやらせればいいんだもの←

なにより夢がありますよね!

僕は幽霊は見たことないから信じてないんですけど、でも人間死んだらそれまでってのはなんか悲しいからいてほしい!みたいな感じに似てます。

 

大晦日。暇を持て余してたっぷり語ってしまいました。

なんだか最近忙しくて、何もやらなくていい(てか、お金がなくて何もできないんですけどね(´;ω;`))日は久しぶりでした。

ああすがすがしい。

てかコーヒーがうめえ。

 

全く人工知能機械学習を知らない人に、少しでも興味を持ってもらえれば嬉しいなあと思って書きました。

だって流行りものの人工知能の裏では、こんなに面白い世界が広がっているから。

 

次回からは勉強したことの備忘録、整理ノートとして使わせてもらいます。

最後まで読んでくださってありがとうございました。m(_ _)m

そして新年もどうぞよろしくお願いいたします。

ブログデビュー

はい。こんばんは。

人生初ブログです。

 

簡単に自己紹介を。

僕は現在機械学習人工知能について勉強している大学院の修士1年生です。

基本的には研究室に入りびたっていますが、ちょこちょこIT企業でプログラムを書いてデータ分析するバイトに顔をだし、ちょこちょこコンビニのバイトにも顔をだすバイト戦士でもあります。

大学学部時代は応用物理学を専攻していましたが、当時所属していた某武道系サークルがアホみたいに忙しかったことを言い訳にして勉強から逃げてきたのであまり身になっていません(笑)

ちょっと思うことがあって大学院からは別の院に入学し、研究内容も専攻もガラッと変えて現在はコンピュータサイエンス系Masterです(どうでもいいですけど修士のことをMasterって言うのかっこいいですよね。あと一年ちょっとしか使えないのでどんどん使っていきます)

 

 

なんでこのブログを作ったのかといいますと、先程も言った通り普段IT企業でバイトをしているのですが、学部時代物理専攻のくせにろくに物理を勉強してこなかった人間なのでもちろん情報理論やプログラムにはもっともっと疎いわけです。(じゃあなんでその専攻にしたんだとかいうツッコミが聞こえてきそうです...)

そんな僕がIT企業でプログラムを書いていると、「ぬぬぬぬぬんうぬぬぬぬぬぬぬうううううカタカナで書いてあるけどこれ何語だよおおおおお」みたいなコンピュータ用語がたくさん出てきたり、プログラムが僕の言うことを聞いてくれなかったりするんですね。

 

そんなとき、頼りになるのが皆様大好きGoogle大先生。

でもそんな大先生が紹介してくれるサイトでも、やっぱり英語だったり(いやまあしょうがない面もあるのですが)日本語でも「????」って感じだったりで、僕のような浅学非才の身には敷居が高いことが多いです。

そんな中、よく頼りにさせてもらっているのがQiitaです。

やはり書いている人たちが初学者の目線から書いてくれていることが多く、大変助かっています。

そんなQiitaのユーザーの記事内容を見ると、備忘録として扱っている方が多く見受けられます。

1日の95%くらいのことは次の日には忘れてしまう僕なので、「せめて1度学んだことはストックしておきたい!」ということで何らかの形で残すことにしました。

公の場に公開すると間違いにツッコミもちゃんと入りそうですし勉強になりますからね(笑)

ところが!!「プログラムのことだけだとネタがない!」ということでQiitaは断念。(Qiitaって日常のことも書いていいんですかね?なんかその辺よく知らないです)

僕も備忘録として、できるだけわかりやすい形で覚えたことを残しておこうと思い、ここに登録しました。

 

基本的に日常的なことはtwitterのこちらで書くようにしているのですが↓

twitter.com

 

こちらでは勉強した機械学習アルゴリズムやプログラム、プログラミング言語(python大好きマンだからpythonメインかも)、統計学などなどまじめなことをメインで書こうと思っています。

 

というわけで、間違いなどがありましたら、ぜひぜひご指摘いただけると嬉しいです。

もう大晦日だ!!!(笑)

というわけで今日はもう寝ます。

また後日。