2016-12-12 2 views
5

В ответ на this question я побежал следующий VBA эксперимент:Как понять автокорреляции, вызванные чрезмерным посевом ГСЧ?

Sub Test() 
    Dim i As Long, A As Variant 
    Dim count1 As Long, count2 As Long 
    ReDim A(1 To 10000) 

    For i = 1 To 10000 
     Randomize 
     A(i) = IIf(Rnd() < 0.5, 0, 1) 
    Next i 

    'count how often A(i) = A(i+1) 
    For i = 1 To 9999 
     If A(i) = A(i + 1) Then count1 = count1 + 1 
    Next i 

    For i = 1 To 10000 
     A(i) = IIf(Rnd() < 0.5, 0, 1) 
    Next i 

    'count how often A(i) = A(i+1) 
    For i = 1 To 9999 
     If A(i) = A(i + 1) Then count2 = count2 + 1 
    Next i 

    Debug.Print "First Loop: " & count1 
    Debug.Print "Second Loop: " & count2 & vbCrLf 

End Sub 

Когда я увидел выход так:

First Loop: 5550 
Second Loop: 4976 

Я был уверен, что я знал, что происходит: VBA было преобразование системы (возможно, микросекунды), что, как следствие, приведет к Randomize, иногда производя идентичные семена в двух или более проходах через петлю. В моем первоначальном ответе я даже уверенно утверждал это. Но тогда я побежал код еще немного, и заметил, что выход был иногда так:

First Loop: 4449 
Second Loop: 5042 

подсев до сих пор вызывает заметную автокорреляцию - но в противоположном (и неожиданном) направлении. Последовательные прохождения через петлю с одним и тем же семенем должны давать одинаковые выходы, поэтому мы должны видеть, что последовательные значения согласуются чаще, чем вероятность, предсказанная, а не расходящаяся чаще, чем вероятность.

Любопытный теперь я изменил код для:

Sub Test2() 
    Dim i As Long, A As Variant 
    Dim count1 As Long, count2 As Long 
    ReDim A(1 To 10000) 

    For i = 1 To 10000 
     Randomize 
     A(i) = Rnd() 
    Next i 

    'count how often A(i) = A(i+1) 
    For i = 1 To 9999 
     If A(i) = A(i + 1) Then count1 = count1 + 1 
    Next i 

    For i = 1 To 10000 
     A(i) = Rnd() 
    Next i 

    'count how often A(i) = A(i+1) 
    For i = 1 To 9999 
     If A(i) = A(i + 1) Then count2 = count2 + 1 
    Next i 

    Debug.Print "First Loop: " & count1 
    Debug.Print "Second Loop: " & count2 & vbCrLf 

End Sub 

Который всегда дает следующий результат:

First Loop: 0 
Second Loop: 0 

Кажется, что не так, что последовательные вызовы Randomize иногда возвращает одно и то же семя (по крайней мере, не так часто, чтобы иметь значение).

Но если это не источник автокорреляции - что такое? И - почему это иногда проявляется как негативная, а не положительная автокорреляция?

+0

В справке MSDN указано, что «Использование Randomize с тем же значением для числа не повторяет предыдущую последовательность». , поэтому, если таймер возвращает дважды то же значение, Rnd не должен сбрасываться в одно и то же значение. –

+0

, если вы добавите «Rnd -1» перед рандомизацией, вы увидите корреляцию, растущую как ракета. –

+0

@VincentG Хорошие наблюдения, хотя в документации явно не указано, как «Randomize» ведет себя в этом отношении, если вы не передадите ему число, просто «значение, возвращаемое системным таймером, используется в качестве нового начального значения. " Кажется, что государство не является функцией только семени, а семени плюс что-то еще. То, что не ясно, и почему у вас все еще есть корреляции, если возвращаемые числа не совпадают. Тем не менее, ваше наблюдение за добавлением 'Rnd -1' достаточно, чтобы показать, что я неверно истолковал результат моего второго эксперимента. Возможно, вы можете опубликовать его как (частичный) ответ. –

ответ

2

Частичный ответ, не имеет права редактировать и дополнять.


Ну, есть явная корреляция, когда вы злоупотребляете функцией Randomize.

Я пробовал следующий код с условным форматированием (черная заливка для значений> 0,5), и явно появляются шаблоны (попробуйте прокомментировать Randomize, чтобы увидеть более «случайный» шаблон. (Лучше всего видно с 20 pt колонны и 10% увеличение)

Function Rndmap() 
    Dim i As Long, j As Long 
    Dim bmp(1 To 512, 1 To 512) As Long 
    For i = 1 To 512 
     For j = 1 To 512 
      ' Rnd -1 ' uncomment this line to get a big white and black lines pattern. 
      Randomize 'comment this line to have a random pattern 
      bmp(i, j) = IIf(Rnd() < 0.5, 0, 1) 
     Next j 
    Next i 
    Range(Cells(1, 1), Cells(512, 512)) = bmp 
End Function 

Так в то время как MSDN говорится, что «Использование Randomize с тем же значением числа не повторяет предыдущую последовательность.», это означает, что если таймер возвращается в два раза и то же значение, то Rnd должны сохраняться в одной и той же случайной последовательности без перепродажи, есть еще некоторые ссылки за сценой.

Некоторые скриншоты:

RND() только: Rnd

Использование Randomize: randomize

Использование Rnd -1 и Randomize: Rnd -1

+0

Очень приятный ответ (+1), хотя я пока не соглашусь на его принятие, чтобы узнать, входят ли другие ответы. Графические представления довольно приятные. –

+0

Ну, я не придумал это, я адаптировал его из http://boallen.com/random-numbers.html сообщения, которое я нашел на сайте random.org. Надеюсь, что у нас появятся люди с большим пониманием. –

1

Метод Randomize инициализирует Rnd функция с текущим системным временем, поскольку это семя, вы также можете указать число с Randomize, которое будет использоваться в качестве семени.

Я решил проверить, как долго последовательность продолжается до того повторяется:

Sub randomRepeatTest() 
    For i = 1 To 100000 
     Randomize 
     randomThread = randomThread & Int(9 * Rnd + 1) 
     If i Mod 2 = 0 Then 
      If Left(randomThread, i/2) = Right(randomThread, i/2) Then 
       Debug.Print i/2 
       Exit Sub 
      End If 
     End If 
    Next i 
End Sub 

Эта подпрограмма генерирует случайную последовательность цифр 0 - 9, и, как последовательность становится даже длину опробовано, чтобы увидеть если первая половина последовательности соответствует второй половине, и если да, то она выводит длину, полученную до повторения. После запуска его несколько раз и дисконтирования, когда цифра повторяется дважды в начале, результат выводится на 256 (nice).

Обеспечение любое значение для Randomize будет возвращать результат 256.


Мы рандомизации Rnd каждый цикл, так что здесь происходит?

Ну, как я сказал в начале, если значение не дано Randomize, оно будет использовать системное время в качестве семени. Разрешение этого времени - это то, что я, похоже, не могу найти, но я считаю, что оно низкое.

Я проверил значение timer, которое возвращает время суток в секундах до 2 знаков после запятой (например, 60287.81). Я также попробовал GetTickCount, который возвращает системное активное время (начинает отсчет при загрузке) в миллисекундах. Оба из них по-прежнему приводят к 256 предел последовательности.

Итак, почему, когда мы рандомизируем каждую петлю, последовательность повторяется? Ну, на самом деле, код выполняется в течение миллисекунды. По сути, мы предоставляем одинаковый номер для рандомизации каждого цикла, и поэтому мы фактически не перетасовываем семя.


Итак, Rnd более случайна без Randomize?

Я снова использовал вышеуказанный субподряд без Randomize; ничего не вернулось. Я увеличил количество циклов до 2 000 000; еще ничего.

Я сумел source the algorithm used по формуле Rand Учебного пособия, которое я считаю, таким же, как Rnd без каких-либо сгенерированных семян:

С IX, IY, IZ должен быть установлен в целочисленных значениях между 1 и 30000 ДО первого въезда

IX = MOD (171 * IX, 30269)

И.Ю. = MOD (172 * И.Ю., 30307)

ИЗ = MOD (170 * я Z, 30323)

СЛУЧАЙНЫХ = AMOD (ПОПЛАВКОВЫЕ (IX),/30269,0 + ПОПЛАВКОВЫЕ (IY)/30307,0 + ПОПЛАВКОВЫЕ (ИЗ)/30323,0, 1,0)

Это итеративный функция, которая использует результат предыдущего вызова для генерации нового номера. Указанная процедура Вихмана-Хилла гарантирует, что более чем 10^13 чисел будут сгенерированы до того, как последовательность повторится.


Проблема с Rnd

Для алгоритма работы, он сначала должен быть инициализирован со значениями для IX, IY & IZ. Проблема, которую мы имеем здесь, состоит в том, что мы не можем инициализировать алгоритм со случайными величинами, так как именно этот алгоритм нам нужен, чтобы получить случайные значения, поэтому единственный вариант - предоставить некоторые статические значения, чтобы получить его.

Я проверил это и, похоже, это так. Открытие нового экземпляра Excel, ? Rnd() возвращает 0.70554. Выполнение этого же результата возвращает тот же самый номер.

Таким образом, проблема у нас есть Rnd без использования Randomize дает нам намного более длинную последовательность случайных чисел, однако последовательность будет начинаться в одном и том же месте при каждом открытии Excel. Если функции зависят от случайной генерации, например генерации пароля, этого недостаточно, так как мы получим одинаковые повторяющиеся результаты каждый раз, когда мы открываем Excel.


Раствор

Вот функция, я придумал, и это, кажется, работает хорошо:

Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal Milliseconds As LongPtr) 
Public Declare Function GetTickCount Lib "kernel32"() As Long 
Public randomCount As Long 
Function getRandom() 
    If randomCount Mod 255 = 0 Then 
     Sleep 1 
    End If 
    Randomize GetTickCount 
    getRandom = Rnd() 
    randomCount = randomCount + 1 
End Function 

Это делает использование функции GetTickCount как Randomize семян. Каждый вызов добавляет 1 к переменной randomCount, и после каждых 255 запусков макрос вынужден спать в течение 1 миллисекунды (хотя на самом деле это работает около 15 в моей системе), так что семя GetTickCount будет изменено, и поэтому новый последовательность чисел будет возвращена Rnd

Это, конечно же, вернет ту же последовательность, если она будет использоваться в одно и то же системное время, однако в большинстве случаев это будет достаточный способ генерации более случайных чисел. Если нет, то потребуется какая-то причудливая работа, используя что-то вроде Random.Org API.