random 자바 - Java에서 setSeed 이후의 첫 번째 난수는 항상 비슷합니다.




범위지정 랜덤 (4)

setSeed 를 루프 밖으로 이동하십시오. Java의 PRNG는 선형 합동 생성기이므로 순차 값을 시드에 배치하면 반복의 반복을 통해 상관 관계가있는 결과가 보장됩니다.

추가

나는 회의에 출입하기 전에 그 일을 망 쳤고, 이제 나는 위에서 말한 것을 설명 할 시간을 갖습니다.

Schrage의 휴대용 프라임 모듈러스 곱셈 선형 합자 생성기를 구현하는 루비 스크립트를 작성했습니다. 두 개의 LCG 복사본을 인스턴스화합니다 (둘 다 값 1로 시드 됨). 그러나 출력 루프의 각 반복에서 루프 인덱스를 기반으로 두 번째 복사본을 다시 시드했습니다. 코드는 다음과 같습니다.

# Implementation of a Linear Congruential Generator (LCG)
class LCG
  attr_reader :state
  M = (1 << 31) - 1    # Modulus = 2**31 - 1, which is prime

  # constructor requires setting a seed value to use as initial state
  def initialize(seed)
    reseed(seed)
  end

  # users can explicitly reset the seed.
  def reseed(seed)
    @state = seed.to_i
  end

  # Schrage's portable prime modulus multiplicative LCG
  def value
    @state = 16807 * @state % M
    # return the generated integer value AND its U(0,1) mapping as an array
    [@state, @state.to_f / M]
  end
end

if __FILE__ == $0
  # create two instances of LCG, both initially seeded with 1
  mylcg1 = LCG.new(1)
  mylcg2 = LCG.new(1)
  puts "   default progression     manual reseeding"
  10.times do |n|
    mylcg2.reseed(1 + n)  # explicitly reseed 2nd LCG based on loop index
    printf "%d %11d %f %11d %f\n", n, *mylcg1.value, *mylcg2.value
  end
end

여기에 출력 결과가 나와 있습니다.

   default progression     manual reseeding
0       16807 0.000008       16807 0.000008
1   282475249 0.131538       33614 0.000016
2  1622650073 0.755605       50421 0.000023
3   984943658 0.458650       67228 0.000031
4  1144108930 0.532767       84035 0.000039
5   470211272 0.218959      100842 0.000047
6   101027544 0.047045      117649 0.000055
7  1457850878 0.678865      134456 0.000063
8  1458777923 0.679296      151263 0.000070
9  2007237709 0.934693      168070 0.000078

열은 LCG에 의해 생성 된 기본 정수가 뒤 따르는 반복 번호이며 범위 (0,1)로 스케일 된 결과입니다. 왼쪽 열은 자체적으로 진행할 수있는 경우 LCG의 자연스러운 진행을 보여주고 오른쪽 세트는 각 반복을 다시 시드 할 때 발생하는 상황을 보여줍니다.

컨텍스트를 제공하기 위해 필자는 Java에서 Perlin 노이즈 구현을 주로 작성했으며 시딩 구현시에는 설명 할 수없는 버그가 발생했습니다.

동일한 시드에 대해 매번 동일한 임의의 가중치 벡터를 생성하기 위해 어떤 좌표 세트의 노이즈 레벨을 쿼리하고 어떤 순서로 정렬할지에 관계없이 원본 시드와 해당 시드의 조합을 기반으로 새 시드 ( newSeed )를 생성했습니다. 가중치 벡터의 좌표를 계산하고이를 실행하여 가중치 벡터의 임의 화에 대한 시드로 사용합니다.

rnd.setSeed(newSeed);
weight = new NVector(2);
weight.setElement(0, rnd.nextDouble() * 2 - 1);
weight.setElement(1, rnd.nextDouble() * 2 - 1);
weight.normalize()

여기서 NVector 는 벡터 수학을위한 자체 제작 클래스입니다.

그러나 실행하면 프로그램에서 매우 나쁜 잡음을 생성합니다.

일부 파고 nextDouble() 나는 각 벡터의 첫 번째 요소가 매우 유사하다는 것을 알았고 (즉, 각 setSeed() 호출 후에 첫 번째 nextDouble() 호출이 발생하여 벡터 그리드에서 모든 벡터의 첫 번째 요소가 유사 함).

다음을 실행하여이를 증명할 수 있습니다.

long seed = Long.valueOf(args[0]);
int loops = Integer.valueOf(args[1]);
double avgFirst = 0.0, avgSecond = 0.0, avgThird = 0.0;
double lastfirst = 0.0, lastSecond = 0.0, lastThird = 0.0;
for(int i = 0; i<loops; i++)
{
    ran.setSeed(seed + i);
    double first = ran.nextDouble();
    double second = ran.nextDouble();
    double third = ran.nextDouble();
    avgFirst += Math.abs(first - lastfirst);
    avgSecond += Math.abs(second - lastSecond);
    avgThird += Math.abs(third - lastThird);
    lastfirst = first;
    lastSecond = second;
    lastThird = third;
}
System.out.println("Average first difference.: " + avgFirst/loops);
System.out.println("Average second Difference: " + avgSecond/loops);
System.out.println("Average third Difference.: " + avgSecond/loops);

setSeed() 메서드가 프로그램의 인수로 지정된 시드 범위에서 호출 된 후 생성 된 첫 번째, 두 번째 및 세 번째 임의 숫자 사이의 평균 차이를 찾습니다. 나는이 결과를 돌려 주었다.

C:\java Test 462454356345 10000
Average first difference.: 7.44638117976783E-4
Average second Difference: 0.34131692827329957
Average third Difference.: 0.34131692827329957

C:\java Test 46245445 10000
Average first difference.: 0.0017196011123287126
Average second Difference: 0.3416750057190849
Average third Difference.: 0.3416750057190849

C:\java Test 1 10000
Average first difference.: 0.0021601598225344998
Average second Difference: 0.3409914232342002
Average third Difference.: 0.3409914232342002

여기에서 첫 번째 평균 차이는 나머지보다 현저히 작으며 씨앗이 많을수록 감소한 것으로 나타났습니다.

이와 같이 weight 벡터를 설정하기 전에 nextDouble() 간단한 더미 호출을 추가하여 Perlin 노이즈 구현을 수정할 수있었습니다.

rnd.setSeed(newSeed);
rnd.nextDouble();
weight.setElement(0, rnd.nextDouble() * 2 - 1);
weight.setElement(1, rnd.nextDouble() * 2 - 1);

를 야기하는:

nextDouble() (다른 유형의 임의성을 확인하지 못함 nextDouble() 에 대한 첫 번째 호출에서 이러한 나쁜 변형이 발생하고 / 또는이 문제점에 대해 사람들에게 경고하는 이유를 알고 싶습니다.

물론, 그것은 저를 위해서만 구현 오류가 될 수 있습니다, 나는 그것이 나에게 지적 되었다면 나는 위대 할 것입니다.


Random 클래스는 의사 난수의 낮은 오버 헤드 소스로 설계되었습니다. 그러나 "낮은 오버 헤드"구현의 결과는 숫자 스트림이 통계적 관점에서 완벽하게 벗어난 속성을 가지고 있다는 것입니다. 당신은 불완전 함 중 하나를 만났습니다. Random 는 Linear Congruential Generator로 문서화되며 이러한 생성기의 속성은 잘 알려져 있습니다.

이것을 처리하는 다양한 방법이 있습니다. 예를 들어 조심스럽게 "가장 가난한"특성을 숨길 수 있습니다. (그러나 두 번째 이미지에 추가 된 잡음에는 비 임의성을 볼 수 없지만 여전히 통계 검사를 수행하는 것이 좋습니다.)

좋은 통계 특성을 보장하는 의사 난수를 원한다면 Random 대신 SecureRandom 을 사용해야합니다. 상당한 오버 헤드가 있지만 많은 "현명한 사람들"이 알고리즘의 설계, 테스트 및 분석에 많은 시간을 할애 할 것이라는 확신을 얻을 수 있습니다.

마지막으로, 숫자를 생성하는 대체 알고리즘을 사용하는 Random 의 하위 클래스를 만드는 것은 비교적 간단합니다. link 참조 link . 문제는 적절한 알고리즘을 선택하거나 설계하고 구현해야한다는 것입니다.

이것을 " 이슈 "라고 부르는 것은 논쟁의 여지가 있습니다. 그것은 LCGs의 잘 알려지고 이해되는 재산이고, LCGs의 사용은 공정한 기술 설계 선택이었다. 사람들은 낮은 오버 헤드 PRNGs를 원하지만, 낮은 오버 헤드 PRNGs는 불량한 속성을 가지고 있습니다. 탄 스타 랩.

오라클이 Random 변경을 고려하고있는 것은 아닙니다. 사실, 변경하지 않은 이유는 javadoc 에서 Random 클래스에 명확하게 명시되어 있습니다.

"이 속성을 보증하기 위해 특정 알고리즘이 Random 클래스에 대해 지정됩니다. Java 구현은 Java 코드의 절대 이식성을 위해 Random 클래스에 대해 여기에 표시된 모든 알고리즘을 사용해야합니다."


이는 알려진 문제입니다. 비슷한 씨앗도 비슷한 값의 첫 번째 값을 생성합니다. 랜덤은 실제로 이렇게 사용하도록 설계되지 않았습니다. 좋은 씨앗을 가진 인스턴스를 만든 다음 적당한 크기의 "임의의"숫자 시퀀스를 생성해야합니다.

현재의 솔루션은 괜찮아 보입니다. 또한 문제를 해결하기 위해 설계된 해싱 / 믹싱 기능을 사용할 수도 있습니다 (그리고 옵션으로 출력을 시드로 사용). 예를 들어보십시오 : 2D 잡음 생성을위한 파라 메트릭 무작위 함수


new Random() 을 할 때마다 시계를 사용하여 초기화됩니다. 이것은 단단한 루프에서 동일한 값을 많이 얻음을 의미합니다. 하나의 Random 인스턴스를 유지하고 동일한 인스턴스에서 Next 을 계속 사용해야합니다.

//Function to get a random number 
private static readonly Random random = new Random(); 
private static readonly object syncLock = new object(); 
public static int RandomNumber(int min, int max)
{
    lock(syncLock) { // synchronize
        return random.Next(min, max);
    }
}

편집 (의견보기) : 왜 여기에 lock 가 있어야합니까?

기본적으로, NextRandom 인스턴스의 내부 상태를 변경합니다. 여러 스레드에서 동시에 수행 할 경우 "우리는 방금 결과를 더 무작위로 만들었습니다"라고 주장 할 있지만 실제로 수행하는 작업은 잠재적으로 내부 구현을 손상시키는 것입니다. 또한 동일한 숫자를 얻을 수도 있습니다 문제 수도 있고 그렇지 않을 수도 있습니다. 내부적으로 일어나는 일에 대한 보장은 더 큰 문제입니다. Random 은 스레드 안전성을 보장하지 않기 때문입니다. 따라서 두 가지 유효한 접근 방법이 있습니다.

  • 서로 다른 스레드에서 동시에 액세스하지 못하도록 동기화
  • 스레드마다 다른 Random 인스턴스 사용

어느 쪽이든 괜찮을 수 있습니다. 동시에 여러 호출자로부터 단일 인스턴스를 상호 배제하는 것은 문제를 묻는 것입니다.

lock 는 이러한 접근 방식 중 첫 번째 (그리고 더 간단하게)를 달성합니다. 그러나 다른 접근법은 다음과 같습니다.

private static readonly ThreadLocal<Random> appRandom
     = new ThreadLocal<Random>(() => new Random());

이것은 스레드 당이므로 동기화 할 필요가 없습니다.





java random noise