トピックス

特にコンセプトなくいろいろ書きます

いつか法のない世界が来ればいいなと思っている

 法というものが一体何のためにあるのかを考える。

 答えは、罰がないと犯罪を抑止できないからだと、自分は思う。

 何にせよ、法は社会的に生じる何らかの害を排除するために、必要だから存在している。

 確かに法は有効に働く。

 現行の社会は法があるバージョンなので、ないバージョンと比較できないが、多分、それがあることでないよりは平和になっている。

 ただ、だからそれでいいと言うこともできない。

 法には有用な面もあるけれど、それ自体が自由を害するという負の側面ももっている。

 罰を与えることで脅して平和にすることが、いいことだとは思えない。

 ただ、今のところそういう脅しなしには社会は成り立たない。

 法は必要悪だ。

 それはわかっているが、それでも、なくなればいいのになあと思う。

 法と自由はそういうトレードオフの関係にあって、そのバランスをとるのはとても難しい。

 いきなりなくせば、かなりの混乱を引き起こすだろう。

 人類という未熟な存在には法という補助輪が必要なのだ、たぶん。

 それでも希望として、法がなくなっても犯罪のない、自由で平和な世界がいつか来るといい。

 束の間訪れた平和で自由なゴールデンウィーク初日、そんなことを考える。

 来週はまた仕事が始まり、世界平和についてなんて考えることもなくなる。

 哲学は暇で余裕のあるやつのすることだと改めて思う。

継続は力だと信じる

今回特にテーマというものはない

 テーマなく何か書いてみたくなったので書いてみる。

 こんな暴挙に及ぶのは書くモティベーションが著しく下がってきたからだ。

 書いてもどうせ誰も見ないしアクセス解析は日々「0」か「1」のどちらかだ。

 バイナリで表現できる。

 しかもアクセスがあったかと思うとgoogleかhatenaで、一般の人は見てくれない。

 このまま静かにフェードアウトしていってしまいそうなので、食い止める。

 食い止めるだけの、自己目的的な記事。

ワーキングメモリはいかにして意識的なふるまいを生じさせるのか?8

 問題解決には2つの相がある。

 ひとつは自分が観察している対象が何であるかを正しく判断する「理解」の相、もうひとつは問題を打開するための解法を考えて行動する「回答」の相だ。

 つまり、何かを解決するためには、われわれは「分類問題」と「生成問題」の2つのタスクに取り組む必要がある。

 もちろん、分類と生成のいずれかだけで解決できる問題もある。

 たとえば分類すること自体が問題の解決であればそれ以上に答えを出す必要はないし、事態が分類を要さないほど自明であればあとはアクションするだけ、という場合もあるだろう。

 ただ、大概の問題は事態がどうなっているかを理解した上で解決を考えるて行動を決定するという手順を必要とする。

 ここまでの話は専ら「分類」に関するものだった。

 これ以降、「生成」に関して考えていきたい。

 

 これまでに、ワーキングメモリ(ある領域への情報の収束)は現実と虚構を区別するための前提条件になっている、ということを見てきた。

 次は、現実と虚構を区別できると何がうれしいのか、要するに、それができるとどんないいことが起きるのか、ということについて考える。

 何の意味もないことをするほどわれわれは暇ではないはずなので、そこには何かメリットがあるはずだ。

 

 先に答えを言ってしまうと、これができるようになると比喩表現(メタファー)が使えるようになる。

 メタファーというのはたとえば皮肉(イロニー)や笑い(ユーモア)のことで、要するに、この機能(現実と虚構の弁別能)があれば冗談を言えるようになる、ということだ。

 本気か冗談かというのはコンテクストで使い分けているもの(あるモード)だが、本気モードで話しているか冗談で話しているのかの判断はその話が現実に属するか虚構のものかというプレ判断(何モードかの察知)が基準になっている。

 「分類」フェイズにおけるワーキングメモリはその事前判断の余地を提供してくれる。

 

 ここまでで現実/虚構の判断員ついては見てきたが、「冗談を言う」という部分、もっと拡大的に言うとメタファーを使うという面については触れてこなかった。

 以降、ワーキングメモリがどのようにして比喩表現を可能にしているのかという点について見ていきたい。

ワーキングメモリはいかにして意識的なふるまいを生じさせるのか?7

 ワーキングメモリがしていることは、単純化して言えば「情報の統合」という、ただそれだけのことだ。

 複数の情報を一箇所に集め、統合すること。ただそれだけ。

 けれどこの単純な操作は重大な結果を生じさせる。

 何と言ってもワーキングメモリは意識発生に一役買っているのだ。

 問題はこの単純な仕掛けがなぜそれほどの効果を持つのかという点だ。

 今回確認できたのは、ワーキングメモリによって現実と虚構の区別をつけることができるようになる、ということだ。

 これは逆に、ワーキングメモリがなければ現実と虚構の区別をつけることができない、と言った方が表現としては適切かも知れない。

 現実と虚構の区別は一般的に考えた場合物事がどうあるかという「常識的な見解」と、「現に今起こっている出来事」が食い違っているかどうかという異常検知をもとにしたものだからだ。

 異常を検知するためには「普通」ならどうあるかという情報と、現に今起こっている「実際」の事態を同時にモニターする必要がある。

 ワーキングメモリーはその、異常を検知する(現実と虚構を区別する)ために必要な「『常識』と『実際』の同時のモニタリング」を可能にしている。

 そして、意識の機能のひとつとして数えることのできる判断留保の起源が現実と虚構の区別にあり、現実と虚構の区別の起源が「『常識』と『実際』の同時のモニタリング」にあるために、それを可能にしているワーキングメモリは実質、意識の根拠になっている。

 これが、ここまでの話を統合して言えることだ。

 

 実は、ワーキングメモリが意識発生に寄与する理由はもうひとつある。

 これを説明するためにはもうひとつ追加実験をした方がわかりやすい。

 これについては次回。

ワーキングメモリはいかにして意識的なふるまいを生じさせるのか?6

***

 あるものが表面的に見せる姿だけからそれが何であるかを言い当てることは難しい。  厳密に言えば、難しい、と言うよりも、できなくはないけれど、それをやると高確率で事実を見誤ることになる。  見た目Aに見えるものがAではないことは日常的だ。  だからこそ見た目から即断することを回避する判断を留保する機構が必要になる。  もし意識が生まれたことに合理的な理由があるなら、それがひとつの理由になるだろう。

 事実を正しく言い当てる確率を上げるために、観察者は「見た目」だけではなく「匂い」や「音」、あるいは時刻や場所などの「状況」を加味して事物を判断する必要がある。  あるものを見た目で判断した場合Aだったとしても、それについての他のすべての要素が(たとえばそれが発する匂いや音や置かれている状況が)Bであることを告げていたとすれば、それは多分Bだろう。  たとえばタツノオトシゴは見た目は完全に馬かドラゴンだけれど、それを馬やドラゴンに分類するのは間違っている。  この観点から言うと、ワーキングメモリがないモデルがしていることは、見た目から即座に結論を出そうとしているのと同じことになる。  ワーキングメモリをもたないということは、物事を複数の要素から判断しない(複数の情報を総合するエリアがない)ことなので、その判断はどこまでも「ある側面から見て」という性質をもつことになる。  前回の実験結果はそれを如実に物語るものになっている。

ワーキングメモリはいかにして意識的なふるまいを生じさせるのか?5


今度こそやってみよう

 やるやると言っていながら全然やってこなかった確認作業に入りたいと思う。

 ワーキングメモリをもつモデルともたないモデルで架空/現実の識別タスクの実行結果に差が出るか、出るとすればどの程度出るのかを確認する。

 一般的に、架空と現実がどのように弁別されるかを考えてみると、われわれは普通、判断の対象になっているものがどの程度「ありえない」かでそれを区別している。

 対象が完全に「ありえない」ものであればそれは架空のものだと言えるし、よくあることならそれはきっと現実だ(ありえなくはないものが架空のものだったということもありうる[いたずらで食品サンプルを食卓に並べられる可能性は否定できない]けれど、とりあえずそれについては考えないでおく)。

 架空/現実の識別をするためにはその「ありえなさ」を測定する必要がある。

 ありえなさをどう測るかという点について、やり方は色々考えられるが、今回は一般的に考えて最もありうる事象と観察された事象が食い違っている場合を「ありえない」状況として定義しておこうと思う。

 今の説明だとどういうことなのかわからないはずなので、例を挙げたい。

 たとえば、明らかにリンゴにしか見えないものを見せられて、「これはうんこです」と言われたとしても、信じることはきっとできないだろう。

 それは嘘だ(うんこをリンゴそっくりに整形したかも知れないが、確率的に多分、それはない)。

 そのリンゴはうんこではない。

 それは架空の話のはずだ。

 今回の実験ではこの決定方針に従おうと思う。

 つまり、どう考えてもAなはずのものがBだった場合を「ありえない」ものとして、虚構(架空)と判断する。

$$ {X_i} = \begin{cases} 現実(よくある) & if(s_1=リンゴ, s_2=リンゴ) \\ 虚構(ありえない) & if(s_1=リンゴ, s_2=うんこ) \end{cases}\hspace{50pt}(1) $$

 上の式では見た感じ(${s_1}$)がリンゴで実際(${s_2}$)もリンゴだった場合現実と判断(${x} = 現実$)しているけれど、もちろん、見た感じ(${s_1}$)がうんこで実際(${s_2}$)もうんこだった場合も現実という判断(${x} = 現実$)になる。

 ${s_1}$と${s_2}$は実際にコード化する際はダミー変数(それぞれ、$\boldsymbol{s}(リンゴ)=[1,0]$と$\boldsymbol{s}(うんこ)=[0,1]$)とし、ベクトルで扱う。

$$ \boldsymbol {x_i} = (\boldsymbol{s_1^j},\boldsymbol{s_2^j})\hspace{10pt} j:for\ all\ elements $$

 この設定で実験してみる。

 モデルにはニューラルネットワークを使う。

 ワーキングメモリ(情報の収束領域)をもつネットワークともたないネットワークを作ってその挙動を比べてみる。

 イメージ的には以下のように作る。

f:id:N0Z:20210411084837p:plain

 このネットワーク構造をもつものをコードに落とす。  ニューラルネットワークの説明と実装の仕方は他にわかりやすい説明が山ほどあるのでここではしない。  むしろ、それについてはこちらがお世話になっている。  ここでのモデル作りにはたとえば

  を参考にさせてもらっている。  下で作るモデルは「ワーキングメモリがあるパターン」になる。

import numpy as np
import matplotlib.pyplot as plt

# HiddenLayerクラスを作る
class HiddenLayer:
    
    def __init__(self,n_in,n_out):
        self.w = np.random.randn(n_in,n_out)
        self.b = np.random.randn(n_out)
        
    def forward(self,x):
        self.x = x
        u = np.dot(x,self.w) + self.b
        self.y = 1 / (1 + np.exp(-u))
        return self.y
    
    def backward(self,grad_out,eta):
        grad_u = grad_out * (1 - self.y) * self.y
        self.grad_w = np.dot(self.x.T,grad_u)
        self.grad_b = np.sum(grad_u,axis=0)
        self.grad_x = np.dot(grad_u,self.w.T)
        self.w -= eta * self.grad_w
        self.b -= eta * self.grad_b
        return self.grad_x

# OutputLayerクラスを作る
class OutputLayer:
    
    def __init__(self,n_in,n_out):
        self.w = np.random.randn(n_in,n_out)
        self.b = np.random.randn(n_out)
        
    def forward(self,x):
        self.x = x
        u = np.dot(x,self.w) + self.b
        self.y = 1 / (1 + np.exp(-u))
        return self.y
    
    def backward(self,t,eta):
        grad_u = self.y - t
        self.grad_w = np.dot(self.x.T,grad_u)
        self.grad_b = np.sum(grad_u,axis=0)
        self.grad_x = np.dot(grad_u,self.w.T)
        self.w -= eta * self.grad_w
        self.b -= eta * self.grad_b
        return self.grad_x

def forward(network,x):
    for layer in network:
        x = layer.forward(x)
    return x

def backward(network,t,eta):
    for layer in reversed(network):
        t = layer.backward(t,eta)

def calc_cee(x,t):
    cee = - np.sum(t * np.log(x))
    return cee

def calc_acc(network,x,t):
    n = len(x)
    y = forward(network,x)
    classes = []
    for i in range(n):
        cls = []
        for j in range(len(y[i])):
            if y[i][j] > 0.6:
                cls.append(1)
            elif y[i][j] < 0.4:
                cls.append(0)
            else:
                cls.append('unclassified')
        classes.append(cls)
    tp_tf = 0
    uncls = 0
    for i in range(n):
        if 1 in classes[i]:
            idx_y = classes[i].index(1)
            idx_t = t[i].tolist().index(1)
            if idx_y == idx_t:
                tp_tf += 1
        if 'unclassified' in classes[i]:
            uncls += 1
    acc = tp_tf / n
    rate_uncls = uncls / n
    return acc, rate_uncls

def train(network,x,t,eta,interval):
    error_record = []
    index_random = np.arange(len(x))
    np.random.shuffle(index_random)
    epochs = len(x) // interval
    for i in range(epochs):
        index_mb = index_random[i*interval : i*interval+interval]
        x_mb = x[index_mb]
        t_mb = t[index_mb]
        y = forward(network,x_mb)
        a = backward(network,t_mb,eta)
        cee = calc_cee(y,np.array(t_mb))
        error_record.append(cee)
    return error_record

# 各層のノード数を決める
n_in = 4
n_mid = 4
n_out = 2

# 各層をインスタンス化
hidden_layer = HiddenLayer(n_in,n_mid)
output_layer = OutputLayer(n_mid,n_out)

network = [hidden_layer,output_layer]

# データセットを作る
X = [[1,0,1,0],
     [1,0,0,1],
     [0,1,1,0],
     [0,1,0,1]] * 1000
T = [[0,1] ,
     [1,0],
     [1,0] ,
     [0,1]] * 1000
X = np.array(X)
T = np.array(T)
n_data = len(X)

# 実験
interval = 10
eta = 1e-1
errors = []
accs = []
uncls = []

index_random = np.arange(n_data)
np.random.shuffle(index_random)

for i, idx in enumerate(index_random):
    x = X[idx]
    t = T[idx]
    forward(network,x.reshape(1,-1))
    backward(network,t.reshape(1,-1),eta)
    if i % interval == 0:
        y = forward(network,x)
        cee = calc_cee(y,t)
        errors.append(cee)
        acc,un = calc_acc(network,X,T)
        accs.append(acc)
        uncls.append(un)

# 結果表示
x_axis = np.arange(0,n_data// interval,1)
plt.figure()
plt.plot(x_axis,errors)
plt.xlabel('Trials/10')
plt.ylabel('CrossEntoropyLoss')
plt.figure()
plt.plot(x_axis,accs,label='Acc')
plt.plot(x_axis,uncls,label='unclassified')
plt.xlabel('Trials/10')
plt.ylabel('Rate')
plt.legend()
plt.show()

結果、どうなったか?

 上のコードを実行すると、結果はこうなる。 f:id:N0Z:20210411080918p:plain

 結果の説明はとりあえず置いておいて、「ワーキングメモリがないパターン」も試しておきたいと思う。  今度は上のコードのデータセットとノード数を次のように変更して実行してみる。

# データセット
X = [[1,0,1,0],
     [1,0,0,1],
     [0,1,1,0],
     [0,1,0,1]] * 1000
T = [[0,1] ,
     [1,0],
     [1,0] ,
     [0,1]] * 1000
X = np.array(X)
T = np.array(T)
X= X[:,:2]
n_data = len(X)

# 各層のノード数
n_in = 2
n_mid = 4
n_out = 2

 すると、結果こうなる。 f:id:N0Z:20210411092925p:plain  これで結果が出たわけだけれど、この結果からどういう結論が出るかということについては次回追記したい。