ニューラルネットワークは素数を認識できますか?


26

バックグラウンド

素数性の認識は、(人工)ニューラルネットワークにはあまり適していないと思われます。ただし、普遍近似定理では、ニューラルネットワークは任意の連続関数を近似できるため、特に、希望する有限サポート関数を表すことができるはずです。それで、最初の100万の数の中のすべての素数を認識してみましょう。

より正確には、これはプログラミングWebサイトであるため、2 ^ 20 = 1,048,576まで進みましょう。このしきい値を下回る素数の数は82,025または約8%です。

チャレンジ

すべての20ビット整数を素数または素数として正しく分類できるニューラルネットワークはどれくらい小さいでしょうか?

この課題のために、ニューラルネットワークのサイズは、それを表現するために必要な重みとバイアスの総数です。

詳細

目標は、単一の明示的なニューラルネットワークのサイズを最小化することです。

ネットワークへの入力は、整数の個々のビットを含む長さ20のベクトルになり、0と1で表されるか、-1と+1で表されます。これらの順序は、最上位ビットが先か、最下位ビットが先です。

ネットワークの出力は単一の数値である必要があります。これにより、カットオフ値を超えると入力が素数として認識され、同じカットオフ値を下回ると入力が素数でないと認識されます。たとえば、正は素数を意味し(負ではなく負)、あるいは0.5より大きい場合は素数を意味します(そして0.5未満は素数ではありません)。

ネットワークは、すべての2 ^ 20 = 1,048,576の入力で100%正確でなければなりません。上記のように、この範囲には82,025個の素数があることに注意してください。(常に「素数ではない」を出力すると、92%正確になります。)

標準的なニューラルネットワークの用語では、これはおそらくオーバーフィッティングと呼ばれます。言い換えれば、あなたの目標は素数を完全に過剰適合させることです。他の言葉としては、「トレーニングセット」と「テストセット」が同じであることです。

この課題では、「トレーニング可能な」または「学習可能な」パラメータの数は考慮されていません。実際、ネットワークにはハードコーディングされた重みが含まれている可能性が高く、以下の例は完全にハードコーディングされています。代わりに、すべての重みとバイアスがパラメーターと見なされ、カウントされます。

ニューラルネットワークをトレーニングまたは生成するために必要なコードの長さはスコアに関係ありませんが、関連するコードを投稿することは確かにありがたいです。

ベースライン

ベースラインとして、82,025個のすべての素数を1,804,551個の合計重みとバイアスで「記憶」することができます。

以下のこのコードには、実例、テストコード、既知のニューラルネットワークライブラリを使用したニューラルネットワークの動作定義、「ハードコード」(または少なくとも「トレーニングされていない」)ニューラルネットワーク、スコアの実際の測定。

import numpy as np

bits = 20

from keras.models import Sequential
from keras.layers import Dense

from sympy import isprime

# Hardcode some weights
weights = []
biases  = []
for n in xrange(1<<bits):
    if not isprime(n):
        continue
    bit_list = [(n / (1 << i))%2 for i in xrange(bits)]
    weight = [2*bit - 1 for bit in bit_list]
    bias   = - (sum(bit_list) - 1)
    weights.append(weight)
    biases .append(bias)
nprimes = len(biases)
weights1 = np.transpose(np.array(weights))
biases1  = np.array(biases )
weights2 = np.full( (nprimes,1), 1 )
biases2  = np.array( [0] )

model = Sequential()
model.add(Dense(units=nprimes, activation='relu', input_dim=bits, weights=[weights1, biases1]))
model.add(Dense(units=1, activation='relu', weights=[weights2, biases2]))
print "Total weights and biases: {}".format( np.size(weights1) + np.size(weights2) + np.size(biases1) + np.size(biases2) )

# Evaluate performance
x = []
y = []
for n in xrange(1<<bits):
    row = [(n / (1 << i))%2 for i in xrange(bits)]
    x.append( row )
    col = 0
    if isprime(n):
        col = 1
    y.append( col )
x = np.array(x)
y = np.array(y)

model.compile(loss='binary_crossentropy', optimizer='sgd', metrics=['accuracy'])

loss, accuracy = model.evaluate(x, y, batch_size=256)
if accuracy == 1.0:
    print "Perfect fit."
else:
    print "Made at least one mistake."

ニューラルネットワークとは何ですか?

この課題のために、(人工)ニューラルネットワークの狭いが正確な定義を書き留めることができます。一部の外部リーディングについては、ウィキペディアの人工ニューラルネットワークフィードフォワードニューラルネットワーク多層パーセプトロン、および活性化機能をお勧めします

フィードフォワードニューラルネットワークはの集まりであるニューロンの。層ごとのニューロンの数は異なります。入力層に20個のニューロン、1つ以上の隠れ層にいくつかのニューロン、出力層に1個のニューロンがあります。(素数と素数はビットパターンに従って線形に分離できないため、少なくとも1つの隠れ層が必要です。)上記のベースラインの例では、層のサイズは[20、82025、1]です。

入力ニューロンの値は、入力によって決まります。上記のように、これは0と2 ^ 20の間の数のビットに対応する0と1、または同様に-1と+1のいずれかです。

出力層を含む次の各層のニューロンの値は、層から事前に決定されます。最初に、線形関数が完全に接続され形式または高密度形式で適用されます。このような関数を表す1つの方法は、重み行列を使用することです。たとえば、ベースラインの最初の2つのレイヤー間の遷移は、82025 x 20マトリックスで表すことができます。重みの数は、このマトリックス内のエントリの数、たとえば1640500です。各エントリには、(別個の)バイアス項が追加されます。これは、ベクトル(例では82025 x 1行列)で表すことができます。バイアスの数は、エントリの数です(例:82025)(重みとバイアスはともにアフィン線形関数を表すことに注意してください)。

重みまたはバイアスは、ゼロであってもカウントされます。この狭い定義の目的のために、バイアスはすべてがゼロであっても重みとしてカウントされます。ベースラインの例では、2つの異なる重み(+1と-1)のみが使用されていることに注意してください(わずかに異なるバイアスのみ)。それでも、サイズが100万を超えるのは、繰り返しがスコアに役立たないためです。

最後に、活性化関数と呼ばれる非線形関数が、このアフィン線形関数の結果にエントリ単位で適用されます。この狭い定義のために、許可されるアクティベーション関数はReLUtanh、およびsigmoidです。レイヤー全体で同じアクティベーション機能を使用する必要があります。

ベースラインの例では、重みの数は20 * 82025 + 82025 * 1 = 1722525であり、バイアスの数は82025 + 1 = 82026であり、合計スコアは1722525 + 82026 = 1804551です。象徴的な例として、もう1つのレイヤーとレイヤーサイズが代わりに[20、a、b、1]であった場合、重みの数は20 * a + a * b + b * 1になり、バイアスの数はa + b + 1になります。

ニューラルネットワークのこの定義は、Kerasscikit-learn、およびTensorflowを含む多くのフレームワークによって十分にサポートされています。Kerasは上記のベースラインの例で使用されており、コードは基本的に次のとおりです。

from keras.models import Sequential
model = Sequential()
from keras.layers import Dense
model.add(Dense(units=82025, activation='relu', input_dim=20, weights=[weights1, biases1]))
model.add(Dense(units=1, activation='relu', weights=[weights2, biases2]))
score = numpy.size(weights1) + numpy.size(biases1) + numpy.size(weights2) + numpy.size(biases2)

重み行列とバイアス行列がnumpy配列の場合、numpy.sizeはエントリの数を直接通知します。

他の種類のニューラルネットワークはありますか?

この課題のためにニューラルネットワークとスコアの単一の正確な定義が必要な場合は、前のセクションの定義を使用してください。正しい方法で見た「任意の関数」がパラメーターのないニューラルネットワークであると思われる場合は、前のセクションの定義を使用してください。

あなたがより自由な精神であれば、私はあなたがさらに探検することをお勧めします。おそらくあなたの答えは狭い挑戦にカウントされないでしょうが、多分あなたはもっと楽しくなるでしょう。他のアイデアとしては、よりエキゾチックな活性化関数、リカレントニューラルネットワーク(一度に1ビットずつ読み取る)、畳み込みニューラルネットワーク、よりエキゾチックなアーキテクチャ、ソフトマックス、LSTM(!)などがあります。標準のアクティベーション機能と標準のアーキテクチャを使用できます。「標準の」ニューラルネットワーク機能の自由な定義には、この質問を投稿する前にarxivに投稿されたものが含まれます。


これらの重みはどのような種類ですか?通常、人々は浮動小数点数を使用しますが、他の数値型を使用できますか?たとえば、精度が低い、高い、または無制限のタイプ。
ウィートウィザード

@ SriotchilismO'Zaic:狭い定義のために、すべての重みと中間値について、floatとdouble(IEEE単精度および倍精度浮動小数点実数)に制限することは理にかなっていると思います。(一部の実装では、評価中に他の精度(たとえば80ビット)を使用する場合があることに注意してください。)
A. Rex

私はこの質問が大好きですが、十分なトレーニング時間で見つけることができるほど小さなニューラルネットワークがないことに失望しています。
アヌーシュ

回答:


13

試行分割:スコア59407、レイヤー6243、ニューロン合計16478

ネットを生成および検証するPythonプログラムとして指定されます。trial_division仕組みの説明については、コメントを参照してください。検証は非常に遅くなります(たとえば、実行時間は時間単位で測定されます)。PyPyまたはCythonの使用をお勧めします。

αmax(0,α)

しきい値は1です。それより上は素数、下はコンポジットまたはゼロであり、1を出力する唯一の入力は1そのものです。

#!/usr/bin/python3

import math


def primes_to(n):
    ps = []
    for i in range(2, n):
        is_composite = False
        for p in ps:
            if i % p == 0:
                is_composite = True
                break
            if p * p > i:
                break
        if not is_composite:
            ps.append(i)
    return ps


def eval_net(net, inputs):
    for layer in net:
        inputs.append(1)
        n = len(inputs)
        inputs = [max(0, sum(inputs[i] * neuron[i] for i in range(n))) for neuron in layer]
    return inputs


def cost(net):
    return sum(len(layer) * len(layer[0]) for layer in net)


def trial_division(num_bits):
    # Overview: we convert the bits to a single number x and perform trial division.
    # x is also our "is prime" flag: whenever we prove that x is composite, we clear it to 0
    # At the end x will be non-zero only if it's a unit or a prime, and greater than 1 only if it's a prime.
    # We calculate x % p as
    #     rem = x - (x >= (p << a) ? 1 : 0) * (p << a)
    #     rem -= (rem >= (p << (a-1)) ? 1) : 0) * (p << (a-1))
    #     ...
    #     rem -= (rem >= p ? 1 : 0) * p
    #
    # If x % p == 0 and x > p then x is a composite multiple of p and we want to set it to 0

    N = 1 << num_bits
    primes = primes_to(1 + int(2.0 ** (num_bits / 2)))

    # As a micro-optimisation we exploit 2 == -1 (mod 3) to skip a number of shifts for p=3.
    # We need to bias by a multiple of 3 which is at least num_bits // 2 so that we don't get a negative intermediate value.
    bias3 = num_bits // 2
    bias3 += (3 - (bias3 % 3)) % 3

    # inputs: [bit0, ..., bit19]
    yield [[1 << i for i in range(num_bits)] + [0],
           [-1] + [0] * (num_bits - 1) + [1],
           [0] * 2 + [-1] * (num_bits - 2) + [1],
           [(-1) ** i for i in range(num_bits)] + [bias3]]

    for p in primes[1:]:
        # As a keyhole optimisation we overlap the cases slightly.
        if p == 3:
            # [x, x_is_even, x_lt_4, x_reduced_mod_3]
            max_shift = int(math.log((bias3 + (num_bits + 1) // 2) // p, 2))
            yield [[1, 0, 0, 0, 0], [0, 1, -1, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, -1, p << max_shift]]
            yield [[1, -N, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, -1, 1]]
            yield [[1, 0, 0, 0], [0, 1, -p << max_shift, 0]]
        else:
            # [x, x % old_p]
            max_shift = int(num_bits - math.log(p, 2))
            yield [[1, 0, 0], [1, -N, -p_old], [-1, 0, p << max_shift]]
            yield [[1, -N, 0, 0], [0, 0, -1, 1]]
            yield [[1, 0, 0], [1, -p << max_shift, 0]]

        for shift in range(max_shift - 1, -1, -1):
            # [x, rem]
            yield [[1, 0, 0], [0, 1, 0], [0, -1, p << shift]]
            yield [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 1]]
            yield [[1, 0, 0, 0], [0, 1, -p << shift, 0]]
        # [x, x % p]
        p_old = p

    yield [[1, 0, 0], [1, -N, -p]]
    yield [[1, -N, 0]]


def validate_primality_tester(primality_tester, threshold):
    num_bits = len(primality_tester[0][0]) - 1
    primes = set(primes_to(1 << num_bits))
    errors = 0
    for i in range(1 << num_bits):
        expected = i in primes
        observed = eval_net(primality_tester, [(i >> shift) & 1 for shift in range(num_bits)])[-1] > threshold
        if expected != observed:
            errors += 1
            print("Failed test case", i)
        if (i & 0xff) == 0:
            print("Progress", i)

    if errors > 0:
        raise Exception("Failed " + str(errors) + " test case(s)")


if __name__ == "__main__":
    n = 20

    trial_div = list(trial_division(n))
    print("Cost", cost(trial_div))
    validate_primality_tester(trial_div, 1)

余談ですが、

普遍近似定理は、ニューラルネットワークは任意の連続関数を近似できると述べています

max(0,1ai)max(0,1+(ai1)ただし、入力が0または1であることが保証されている場合にのみ正しく機能し、より大きな整数を出力する場合があります。他のさまざまなゲートを1つのレイヤーで使用できますが、NOR自体はチューリング完全であるため、詳細に説明する必要はありません。


余談ですが、トライアル除算を試みる前にオイラーテストの作業を開始しました。これはより効率的であると考えたためです。ただし、(x-(x mod 2) )は38の乗算とそれに続くリダクションmod xを必要とし、20ビットの数値を乗算するために私が見つけた最良のネットワークは1135のコストがかかるため、競争力はありません。
ピーターテイラー

7

合計984314、82027層、合計246076のニューロン

アクティベーション関数ReLUを使用すると、すべてを整数で保持でき、分析が簡単になります。

xx=a

  1. gea=(xa)+lea=(x+a)+
  2. eqa=(gealea+1)+eqa1x=a0

x

ge2=(x2)+le2=(x+2)+

accum2=(ge2le2+1)+ge3=(ge2(32))+le3=(ge2+(32))+

accum3=(221accum2ge3le3+1)+ge5=(ge3(53))+le5=(ge3+(53))+

accum5=(221accum3ge5le5+1)+ge7=(ge5(75))+le7=(ge5+(75))+

...

accum1048571=(221accum1048559ge1048571le1048571+1)+ge1048573=(ge1048571(10485731048571))+le1048573=(ge1048571+(10485731048571))+

accum1048573=(221accum1048571ge1048573le1048573+1)+

しきい値は0です。doubleを使用する場合、オーバーフローは + かなり可能ですが、それはルールに完全に従っているようです。

スコアは(82026-3)* 12 + 21 + 4 + 9 + 4。


クール。私が理解するように、これは素数を「記憶」しますが、「並列」ではなく「順次」で平等をテストします。(または、ベースラインの転置のようなものです。)最初のステップは、ビットパターンからすぐに離れて、実際の整数自体を操作することです。その結果、等価性チェックで20倍のペナルティはありません。ご提出いただきありがとうございます
A. Rex

上付き文字プラスとは何ですか?
feersum

1
@feersum、それは質問にリンクされたReLUのWikipediaページからの表記です。 バツ+=最大0バツ
ピーターテイラー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.