태그 보관물: code-challenge

code-challenge

대략 Moby Dick 작성 받기 전에 항상 1 바이트의

다음은 Herman Melville의 Moby-Dick 의 텍스트를 포함 하는 1.2Mb ASCII 텍스트 파일 입니다 . 또는, 고래 . 당신의 임무는 한 번에 한 문자 씩이 파일을 제공 할 프로그램이나 함수 (또는 클래스 등-아래 참조)를 작성하는 것이며 각 단계에서 다음 문자를 추측해야합니다.

이것은 입니다. 당신의 점수는

2*L + E

어디는 L바이트 제출의 크기이며, E그것은 잘못 추측 문자 수입니다. 가장 낮은 점수가 이깁니다.

추가 세부 사항

제출물은 여러 번 호출되거나 호출되거나 전송되는 프로그램 또는 기능 등입니다. (1215235 번 정확해야합니다.) n 번째 시간 동안 호출되면 n 번째 문자 whale.txt또는 whale2.txt( n + 1 ) 번째 문자에 대한 추측 값을 출력해야합니다 . E점수 의 구성 요소는 잘못 추측 한 총 문자 수입니다.

대부분의 제출은 호출간에 몇 가지 상태를 저장해야하므로 호출 된 횟수와 이전 입력이 무엇인지 추적 할 수 있습니다. 외부 파일에 static쓰거나 전역 변수 를 사용 하거나 함수가 아닌 클래스를 제출하거나 상태 모나드를 사용하거나 언어에 적합한 다른 것을 사용하여이를 수행 할 수 있습니다. 제출에는 첫 번째 호출 전에 상태를 초기화하는 데 필요한 모든 코드가 포함되어야합니다.

프로그램은 결정 론적으로 실행되어야하므로 항상 동일한 입력이 주어지면 항상 같은 추측을하게됩니다 (따라서 항상 같은 점수를 얻습니다).

답변에는 제출물뿐만 아니라 E점수 의 일부 를 계산하는 데 사용한 코드도 포함해야합니다 . 제출 한 언어와 동일한 언어로 작성 될 필요는 없으며 바이트 수에 포함되지 않습니다. 읽을 수 있도록하는 것이 좋습니다.

제출과이 점수 계산 프로그램 간의 인터페이스와 관련하여 프로그램이 다음 바이트 입력을 받기 전에 항상 1 바이트의 출력을 제공하는 한 아무 문제가 없습니다. (예를 들어, 모든 입력을 포함하는 문자열을 전달하고 모든 출력을 포함하는 문자열을 다시 가져올 수는 없습니다.)

출품작을 제출하기 전에 시험 프로그램을 실제로 실행하고 점수를 계산 / 확인해야합니다. 제출물이 점수를 확인하기에 너무 느리게 진행되는 경우 원칙적으로 점수가 무엇인지 알더라도 경쟁 할 수 없습니다.

L점수 의 구성 요소는 코드 골프 챌린지에 대한 일반적인 규칙에 따라 계산됩니다. 제출물에 여러 파일이 포함 된 경우 해당 경우 점수디렉토리 구조 규칙을 참고하십시오 . 코드에서 사용하는 모든 데이터가 L점수에 포함되어야합니다 .

기존 라이브러리를 가져올 수 있지만 다른 외부 파일을로드 할 수 없으며 코드가 whale.txt또는whale2.txt위에서 설명한 방법 이외의 방법으로 파일을 저장하십시오. 사전 훈련 된 신경망 또는 기타 통계 데이터 소스를로드 할 수 없습니다. 신경망을 사용하는 것이 좋지만 제출에 가중치 데이터를 포함시키고 바이트 수로 계산해야합니다. 어떤 이유로 언어 또는 라이브러리에 Moby Dick의 일부 또는 모든 텍스트를 제공하는 기능이 포함 된 경우 해당 기능을 사용하지 못할 수 있습니다. 그 외에도 언어 또는 표준 라이브러리의 일부인 한 텍스트 처리, 예측 또는 압축과 관련된 기능을 포함하여 원하는 다른 내장 또는 라이브러리 기능을 사용할 수 있습니다. 통계 데이터 소스를 포함하는보다 이국적인 특수 루틴의 경우이를 직접 구현하고 바이트 수에 포함시켜야합니다.

일부 제출에는 코드에 의해 자체적으로 생성 된 구성 요소가 포함될 수 있습니다. 이 경우 해당 코드를 생성하는 데 사용 된 코드를 답변에 포함시키고 작동 방식을 설명하십시오 . 제출을 실행하는 데이 코드가 필요하지 않으면 바이트 수에 포함되지 않습니다.

기록적인 이유로 파일에는 두 가지 버전이 있으며 둘 중 하나를 답변에 사용할 수 있습니다. 에서 whale2.txt줄 바꿈 문단의 끝에 만 나타나도록 (위 링크) 텍스트는, 포장되지 않습니다. 원본 whale.txt에서 텍스트는 74 자 너비로 줄 바꿈되므로 각 줄의 끝을 예측하고 텍스트를 예측해야합니다. 이로 인해 도전이 더 어려워 지므로 whale2.txt새로운 답변에 권장됩니다. 두 파일의 크기는 1215236 바이트입니다.


요약하면 모든 답변에는 다음 사항이 포함되어야합니다.

  • 제출 자체. (코드와 사용하는 모든 데이터 파일-크면 링크가 될 수 있습니다.)
  • 코드 작동 방식에 대한 설명 다음 문자를 예측하는 방법과 I / O 방법을 설명하십시오. 귀하의 알고리즘에 대한 설명은 중요하며, 좋은 설명은 저에게 바운티를 얻게됩니다.
  • 점수를 평가하는 데 사용한 코드입니다. (이것이 이전 답변과 동일하면 링크 할 수 있습니다.)
  • 제출물을 생성하는 데 사용한 코드 및 해당 코드에 대한 설명 여기에는 매개 변수를 최적화하고 데이터 파일을 생성하는 데 사용되는 코드가 포함됩니다 (바이트 수에는 포함되지 않지만 답변에 포함되어야 함).

리더 보드

바운티

때때로 나는 다른 접근법을 장려하기 위해 바운티를 제공 할 것입니다.

첫 번째 50 점은 A. Rex에 수여되었으며 당시 최고 점수를 받았습니다.

두 번째 100 점도 A. Rex에게도 주어졌으며, 그들은 기존 답변에 아주 좋은 설명을 추가했기 때문에 같은 답변을 받았습니다.

다음 현상금 인 200 점

  • 새로운 기술을 사용하는 경쟁적인 답변. (이것은 현상금에 들어가는 나의 담당자이기 때문에 나의 주관적인 판단에 근거 할 것이지만, 당신은 저를 공평하게 믿을 수 있습니다. 당신의 대답은 그것이 어떻게 작동하는지 이해하기 위해 충분한 설명을 포함해야합니다!) 최고 점수를 획득하지 않고 기존 답변과 비교하여 합리적으로 잘 수행해야합니다. 특히 반복적 인 신경망을 기반으로 한 솔루션을보고 싶어하지만 현재 최고 점수를 차지하는 Markov 모델과는 다른 것으로 바운티를 수여합니다.

또는:

  • 어떤 방법 으로든 A. Rex의 최고 점수 (현재 444444)를이기는 사람.

200 포인트 현상금이 청구되면 400 포인트 1을 제공하고 그에 따라 요구 사항을 업데이트합니다.



답변

/// , 2 * 1 + 1020874 = 1020876

 

공백을 인쇄합니다.


답변

Node.js, 2 * 224 + 524279 = 524727

점수 업데이트에 대해서는이 게시물 끝에있는 변경 로그를 참조하십시오.

바이트를 가져오고 리턴하는 함수입니다.

a=[...l='14210100'],m={},s={},b={}
f=c=>a.some((t,n)=>x=s[y=l.slice(n)]>t|/^[A-Z '"(]/.test(y)&&b[y],l+=String.fromCharCode(c),a.map((_,n)=>(m[x=l.slice(n)]=-~m[x])<s[y=l.slice(n,8)]||(s[y]=m[x],b[y]=c)),l=l.slice(1))&&x||32

마지막 8자를보고 다음을 예측 하는 간단한 PPM 모델 로 구성됩니다 .

T 는 적어도 T [L] 번 발생했을 때 길이 L 의 패턴을 신뢰합니다 . 여기서 T 는 임의의 임계 값의 배열입니다 : [1,1,2,1,2,3,5,2] . 또한 우리는 항상 첫 문자가 일치하는 패턴을 신뢰합니다 .[A-Z '"(]

가장 긴 신뢰할 수있는 패턴을 선택하고 통화시이 패턴과 관련된 가장 높은 점수로 예측을 반환합니다.

노트

  • 속도에 최적화되지는 않았지만 랩톱에서 약 15 초 안에 실행됩니다.

  • 모델을 재설정하지 않고 프로세스를 연속으로 여러 번 반복 할 수 있으면 5 번 반복 한 후 오류 수가 ~ 268000으로 수렴됩니다.

  • 예측 함수의 현재 성공률은 ~ 56.8 %입니다. 의견에서 @immibis가 알 수 있듯이, 잘못된 추측과 올바른 추측이 혼합되어 있으면 결과를 거의 읽을 수 없습니다.

    예를 들어 책 끝 부분에있는이 스 니펫은 다음과 같습니다.

    Here be it said, that this pertinacious pursuit of one particular whale,[LF]
    continued through day into night, and through night into day, is a thing[LF]
    by no means unprecedented in the South sea fishery.
    

    된다 :

    "e e be it said, that thes woacangtyous sarsuet of tie oort cular thale[LF][LF]
     orsinued toeough tir on e togh   and sheough toght an o ters af t shin[LF][LF]
    be to means insrocedented tn hhe sputh Sevsaonh ry,
    

    잘못된 추측을 밑줄로 바꾸면 함수의 올바른 결과를 더 잘 알 수 있습니다.

    _e_e be it said, that th_s _____n___ous __rsu_t of __e __rt_cular _hale_[LF]
    _o__inued t__ough ___ _n__ __gh__ and _h_ough __ght _n_o ____ __ _ _hin_[LF]
    b_ _o means _n_r_cedented _n _he __uth _e_____h_ry_
    

    NB : 위 예제는 이전 버전의 코드로 작성되었으며, 입력 파일의 첫 번째 버전에서 작업합니다.

테스트 코드

/**
  The prediction function f() and its variables.
*/
a=[...l='14210100'],m={},s={},b={}
f=c=>a.some((t,n)=>x=s[y=l.slice(n)]>t|/^[A-Z '"(]/.test(y)&&b[y],l+=String.fromCharCode(c),a.map((_,n)=>(m[x=l.slice(n)]=-~m[x])<s[y=l.slice(n,8)]||(s[y]=m[x],b[y]=c)),l=l.slice(1))&&x||32

/**
  A closure containing the test code and computing E.
  It takes f as input.
  (f can't see any of the variables defined in this scope.)
*/
;
(f => {
  const fs = require('fs');

  let data = fs.readFileSync('whale2.txt'),
      len = data.length,
      err = 0;

  console.time('ElapsedTime');

  data.forEach((c, i) => {
    i % 100000 || console.log((i * 100 / len).toFixed(1) + '%');

    if(i < len - 1 && f(c) != data[i + 1]) {
      err++;
    }
  })

  console.log('E = ' + err);
  console.timeEnd('ElapsedTime');
})(f)

변경 로그

  • 524727 -whale2.txt로 전환하여 19644 포인트 절약 (챌린지 업데이트)
  • 544371- 대문자, 따옴표, 큰 따옴표 또는 여는 괄호로 시작하는 패턴을 항상 신뢰하도록하여 327 점을 절약했습니다.
  • 544698- 공백으로 시작하는 패턴을 항상 신뢰할 수 있도록하여 2119 포인트 절약
  • 546817- 임계 값을 조정하고 예측 기능을 골프화 하여 47 점을 저장했습니다.
  • 546864- 최대 패턴 길이를 8 자로 확장하여 1496 포인트 절약
  • 548360- 신뢰할 수있는 패턴의 개념을 도입하여 길이에 따른 임계 값으로 6239 포인트를 절약했습니다.
  • 554599- 줄 바꿈 예측을 개선하여 1030 점을 절약했습니다.
  • 555629- 예측 기능을 골프로하여 22 포인트 절약
  • 555651- 예측 기능을 골프로하여 40 점을 저장했습니다
  • 555691- 초기 점수

답변

펄, 2 · 70525 + 326508 = 467558

예언자

$m=($u=1<<32)-1;open B,B;@e=unpack"C*",join"",<B>;$e=2903392593;sub u{int($_[0]+($_[1]-$_[0])*pop)}sub o{$m&(pop()<<8)+pop}sub g{($h,%m,@b,$s,$E)=@_;if($d eq$h){($l,$u)=(u($l,$u,$L),u($l,$u,$U));$u=o(256,$u-1),$l=o($l),$e=o(shift@e,$e)until($l^($u-1))>>24}$M{"@c"}{$h}++-++$C{"@c"}-pop@c for@p=($h,@c=@p);@p=@p[0..19]if@p>20;@c=@p;for(@p,$L=0){$c="@c";last if" "ne pop@c and@c<2 and$E>99;$m{$_}+=$M{$c}{$_}/$C{$c}for sort keys%{$M{$c}};$E+=$C{$c}}$s>5.393*$m{$_}or($s+=$m{$_},push@b,$_)for sort{$m{$b}<=>$m{$a}}sort keys%m;$e>=u($l,$u,$U=$L+$m{$_}/$s)?$L=$U:return$d=$_ for sort@b}

이 프로그램을 실행하려면 이 파일이 필요 합니다 (여기서 이름을 지정해야 함) B. 위 문자 의 두 번째 인스턴스 에서이 파일 이름을 변경할 수 있습니다 B.이 파일을 생성하는 방법은 아래를 참조하십시오.

이 프로그램은 본질적으로 user2699의 답변 에서와 같이 Markov 모델의 조합을 사용 하지만 약간의 수정이 있습니다. 이것은 다음 캐릭터에 대한 분포 를 생성합니다 . 우리는 정보 이론을 사용하여 B인코딩 힌트 에 오류를 허용할지 또는 스토리지 비트를 소비할지 여부를 결정합니다 (있는 경우). 우리는 산술 코딩 을 사용 하여 모델의 소수 비트를 최적으로 저장합니다.

프로그램의 길이는 582 바이트 (불필요한 최종 개행 포함)이고 이진 파일의 B길이는 69942 바이트이므로 여러 파일의 점수 를 매기는 규칙에L 따라 582 + 69942 + 1 = 70525 로 점수 가 매겨집니다.

이 프로그램에는 거의 확실하게 64 비트 (little-endian?) 아키텍처가 필요합니다. m5.largeAmazon EC2 의 인스턴스에서 실행하는 데 약 2.5 분이 걸립니다 .

테스트 코드

# Golfed submission
require "submission.pl";

use strict; use warnings; use autodie;

# Scoring length of multiple files adds 1 penalty
my $length = (-s "submission.pl") + (-s "B") + 1;

# Read input
open my $IN, "<", "whale2.txt";
my $input = do { local $/; <$IN> };

# Run test harness
my $errors = 0;
for my $i ( 0 .. length($input)-2 ) {
    my $current = substr $input, $i, 1;
    my $decoded = g( $current );

    my $correct = substr $input, $i+1, 1;
    my $error_here = 0 + ($correct ne $decoded);
    $errors += $error_here;
}

# Output score
my $score = 2 * $length + $errors;
print <<EOF;
length $length
errors $errors
score  $score
EOF

테스트 하네스는 제출이 파일에 있다고 가정 submission.pl하지만 두 번째 줄에서 쉽게 변경할 수 있습니다.

텍스트 비교

"And did none of ye see it before?" cried Ahab, hailing the perched men all around him.\\"I saw him almost that same instant, sir, that Captain
"And wid note of te fee bt seaore   cried Ahab, aasling the turshed aen inl atound him. \"' daw him wsoost thot some instant, wer, that Saptain
"And _id no_e of _e _ee _t _e_ore__ cried Ahab, _a_ling the __r_hed _en __l a_ound him._\"_ _aw him ___ost th_t s_me instant, __r, that _aptain

Ahab did, and I cried out," said Tashtego.\\"Not the same instant; not the same--no, the doubloon is mine, Fate reserved the doubloon for me. I
Ahab aid  ind I woued tut,  said tashtego, \"No, the same instant, tot the same -tow nhe woubloon ws mane. alte ieserved the seubloon ior te, I
Ahab _id_ _nd I ___ed _ut,_ said _ashtego__\"No_ the same instant_ _ot the same_-_o_ _he _oubloon _s m_ne_ __te _eserved the __ubloon _or _e_ I

only; none of ye could have raised the White Whale first. There she blows!--there she blows!--there she blows! There again!--there again!" he cr
gnly  towe of ye sould have tersed the shite Whale aisst  Ihere ihe blows! -there she blows! -there she blows! Ahere arains -mhere again!  ce cr
_nly_ _o_e of ye _ould have ___sed the _hite Whale _i_st_ _here _he blows!_-there she blows!_-there she blows! _here a_ain__-_here again!_ _e cr

이 샘플 ( 다른 답변 에서 선택됨 )은 텍스트에서 다소 늦게 발생하므로이 시점에서 모델이 상당히 개발되었습니다. 이 모델은 캐릭터를 추측하는 데 도움이되는 70 킬로바이트의 “힌트”에 의해 확장됩니다. 단순히 위의 짧은 코드 스 니펫으로 구동되지는 않습니다.

힌트 생성

다음 프로그램은 위의 정확한 제출 코드 (표준 입력)를 수락하고 위의 정확한 B파일 (표준 출력)을 생성합니다 .

@S=split"",join"",<>;eval join"",@S[0..15,64..122],'open W,"whale2.txt";($n,@W)=split"",join"",<W>;for$X(0..@W){($h,$n,%m,@b,$s,$E)=($n,$W[$X]);',@S[256..338],'U=0)',@S[343..522],'for(sort@b){$U=($L=$U)+$m{$_}/$s;if($_ eq$n)',@S[160..195],'X<128||print(pack C,$l>>24),',@S[195..217,235..255],'}}'

유사한 계산을 수행하기 때문에 제출만큼 실행하는 데 거의 시간이 걸립니다.

설명

이 섹션에서는이 솔루션이 수행하는 작업을 사용자가 “집에서 시도”할 수있을만큼 충분히 자세하게 설명하려고합니다. 이 답변을 다른 답변과 구별하는 주요 기술은 “되감기”메커니즘으로 분류되는 몇 가지 섹션이지만, 거기에 도달하기 전에 기본 사항을 설정해야합니다.

모델

솔루션의 기본 성분은 언어 모델입니다. 우리의 목적을 위해 모델 은 어느 정도의 영어 텍스트 를 사용하고 다음 문자에 대한 확률 분포 를 반환하는 것 입니다. 모델을 사용할 때 영어 텍스트는 Moby Dick의 접두사입니다. 원하는 결과는 가장 가능성이 높은 문자에 대한 단일 추측이 아니라 분포 입니다.

우리의 경우, 우리는 본질적 으로 user2699에 의한이 답변 에서 모델을 사용합니다 . Anders Kaseorg 의 최고 점수 (우리 자신이 아닌)의 모형을 사용하지 않았습니다 . 단 하나의 최선의 추측보다는 분포를 추출 할 수 없었기 때문입니다. 이론적으로, 그 대답은 가중 기하 평균을 계산하지만, 문자 그대로 해석했을 때 다소 나쁜 결과를 얻었습니다. 우리의 “비밀 소스”는 모델이 아니라 전체적인 접근 방식이기 때문에 다른 답변에서 모델을 “훔친”것입니다. 누군가가 “더 나은”모델을 가지고 있다면 나머지 기술을 사용하여 더 나은 결과를 얻을 수 있어야합니다.

참고로, Lempel-Ziv와 같은 대부분의 압축 방법은 이러한 방식으로 “언어 모델”로 간주 될 수 있지만 약간 움켜 쥐어 야 할 수도 있습니다. (Brows-Wheeler 변환을 수행하는 것은 특히 까다 롭습니다!) 또한 user2699의 모델은 Markov 모델의 수정 사항입니다. 본질적으로이 도전에 대한 경쟁 또는 대체로 텍스트를 모델링하는 다른 어떤 것도 없습니다.

전반적인 아키텍처

이해를 돕기 위해 전체 아키텍처를 여러 조각으로 나누는 것이 좋습니다. 최상위 수준 관점에서 약간의 상태 관리 코드가 필요합니다. 이것은 특별히 흥미롭지는 않지만, 완전성을 위해 프로그램이 다음 추측을 요구할 때마다 Moby Dick의 올바른 접두사를 사용할 수 있다고 강조하고 싶습니다. 우리는 과거의 잘못된 추측을 어떤 식 으로든 사용하지 않습니다. 효율성을 위해 언어 모델은 첫 번째 N 문자의 상태를 재사용하여 첫 번째 (N + 1) 문자의 상태를 계산할 수 있지만 원칙적으로 호출 될 때마다 처음부터 다시 계산할 수 있습니다.

프로그램의 기본 “드라이버”를 따로 설정하고 다음 문자를 추측하는 부분을 들여다 보도록합시다. 언어 모델 (위에서 논의), “힌트”파일 및 “통역사”의 세 부분을 개념적으로 분리하는 데 도움이됩니다. 각 단계에서 통역사는 언어 모델에 다음 문자의 배포를 요청하고 힌트 파일에서 일부 정보를 읽을 수 있습니다. 그런 다음 이러한 부분을 추측으로 결합합니다. 정확히 힌트 파일에 어떤 정보가 있고 어떻게 사용되는지는 나중에 설명 할 것이지만 지금은이 부분들을 정신적으로 분리시키는 데 도움이됩니다. 구현 측면에서 힌트 파일은 문자 그대로 별도의 (이진) 파일이지만 프로그램 내부에 저장된 문자열이거나 무언가 일 수 있습니다. 근사치로

이 답변 에서처럼 bzip2 와 같은 표준 압축 방법을 사용하는 경우 “hints”파일은 압축 파일에 해당합니다. “통역사”는 압축 해제기에 해당하는 반면 “언어 모델”은 약간 암시 적입니다 (위에서 언급 한 바와 같이).

힌트 파일을 사용하는 이유는 무엇입니까?

추가 분석을위한 간단한 예를 선택해 봅시다. 텍스트가 N길고 근사한 문자 라고 가정합니다. 모든 문자는 E확률이 절반보다 약간 작은 문자 , 확률이 절반보다 약간 작은 문자 T, A1/1000 = 0.1 %의 확률을 갖는 문자 (독립적으로) 입니다. 다른 문자는 불가능하다고 가정하자. 어쨌든 A이전에 보지 못한 캐릭터가 파란색으로 보이는 경우와 매우 유사합니다.

만약 우리가 L 0 체제에서이 질문에 대한 다른 답변들 대부분이 아닌 대부분을 운영한다면, Eand 중 하나를 선택하는 것보다 통역사에게 더 나은 전략은 없습니다 T. 평균적으로 문자의 약 절반이 맞습니다. 따라서 E ≈ N / 2와 ≈ N / 2도 점수입니다. 그러나 압축 전략을 사용하면 문자 당 하나 이상의 비트로 압축 할 수 있습니다. L은 바이트 단위로 계산되므로 L ≈ N / 8을 얻으므로 이전 전략보다 두 배 좋은 strategy N / 4를 얻습니다.

이 모델에서 문자 당 하나 이상의 비트를이 속도로 달성하는 것은 사소한 것이 아니지만 한 가지 방법은 산술 코딩입니다.

산술 코딩

일반적으로 알려진 바와 같이, 인코딩 은 비트 / 바이트를 사용하여 일부 데이터를 나타내는 방법입니다. 예를 들어 ASCII는 영어 텍스트 및 관련 문자의 7 비트 / 문자 인코딩이며, 고려중인 원본 Moby Dick 파일의 인코딩입니다. 일부 문자가 다른 문자보다 일반적인 경우 ASCII와 같은 고정 너비 인코딩이 최적이 아닙니다. 이러한 상황에서 많은 사람들이 허프만 코딩에 도달합니다 . 문자 당 정수 비트 수의 고정 (프리픽스 프리) 코드를 원하는 경우에 최적입니다.

그러나 산술 코딩 이 훨씬 좋습니다. 대략적으로 말하면, “분수”비트를 사용하여 정보를 인코딩 할 수 있습니다. 온라인으로 사용할 수있는 산술 코딩에 대한 많은 안내서가 있습니다. 온라인에서 사용 가능한 다른 리소스로 인해 여기에서 세부 사항 (특히 실제 구현, 프로그래밍 측면에서 약간 까다로울 수 있음)을 건너 뛰지 만 누군가가 불평하면이 섹션을 더 자세히 설명 할 수 있습니다.

알려진 언어 모델에 의해 실제로 생성 된 텍스트가있는 경우, 산술 코딩은 해당 모델의 텍스트를 기본적으로 최적으로 인코딩합니다. 어떤 의미에서 이것은 해당 모델의 압축 문제를 “해결”합니다. (실제로 주요 문제는 모델을 알지 못하고 일부 모델은 인간의 텍스트를 모델링 할 때 다른 모델보다 낫다는 것입니다.)이 콘테스트에서 오류를 허용하지 않은 경우 이전 섹션의 언어로 이 과제에 대한 해결책을 만드는 한 가지 방법은 산술 인코더를 사용하여 언어 모델에서 “힌트”파일을 생성 한 다음 산술 디코더를 “통역사”로 사용하는 것입니다.

이 본질적으로 최적화 된 인코딩에서, 우리는 확률 p를 갖는 문자에 대해 -log_2 (p) 비트를 소비하게되고, 인코딩의 전체 비트 레이트는 Shannon 엔트로피 입니다. 이는 확률이 1/2에 가까운 문자는 인코딩하는 데 약 1 비트가 걸리고, 1/1000의 확률이있는 문자는 약 10 비트가됩니다 (2 ^ 10은 대략 1000이므로).

그러나이 과제에 대한 점수 메트릭은 압축을 최적의 전략으로 피하기 위해 잘 선택되었습니다. 더 짧은 힌트 파일을 얻는 데있어 트레이드 오프로 오류를 만드는 방법을 찾아야합니다. 예를 들어, 시도 할 수있는 전략 중 하나는 간단한 분기 전략입니다. 일반적으로 가능한 경우 산술 인코딩을 사용하려고하지만 모델의 확률 분포가 “나쁜”경우 가장 가능성이 높은 문자를 추측하여 인코딩하지 마십시오.

왜 오류가 발생합니까?

왜 우리가 “의도적으로”오류를 만들고 싶을 지 동기를 부여하기 위해 이전의 예를 분석해 봅시다. 올바른 문자를 인코딩하기 위해 산술 코딩을 사용하는 경우 E또는 의 경우 대략 1 비트를 사용 T하지만의 경우에는 약 10 비트를 사용합니다 A.

전반적으로, 이것은 세 가지 가능성이 있지만 문자 당 조금 이상을 소비하는 꽤 좋은 인코딩입니다. 기본적으로 A는 가능성이 거의 없으며 해당 10 비트를 너무 자주 소비하지 않습니다. 그러나 A? 의 경우 대신 오류를 만들 수 있다면 좋지 않을까요? 결국 문제에 대한 메트릭은 1 바이트 = 8 비트 길이의 길이가 2 개의 오류와 같다고 간주합니다. 따라서 문자에 8/2 = 4 비트 이상을 소비하는 대신 오류를 선호 해야하는 것처럼 보입니다. 하나의 오류를 저장하기 위해 1 바이트 이상을 소비하면 확실히 차선책으로 들립니다!

“되감기”메커니즘

이 섹션에서는이 솔루션의 주요 영리한 측면에 대해 설명합니다.이 방법은 비용없이 잘못된 추측을 처리 할 수있는 방법입니다.

우리가 분석 한 간단한 예에서 되감기 메커니즘은 특히 간단합니다. 인터프리터는 힌트 파일에서 1 비트를 읽습니다. 0이면 추측 E합니다. 1이면 추측 T합니다. 다음에 호출 될 때 올바른 문자가 무엇인지 확인합니다. 힌트 파일이 제대로 설정 되면 Eor 의 경우 T인터프리터가 올바르게 추측 할 수 있습니다. 그러나 어떻 A습니까? 되감기 메커니즘의 아이디어는 단순히 코드 A를 작성 하지 않는 것 입니다. 더 정확하게 말하면, 인터프리터가 나중에 올바른 문자가이라는 것을 알게되면 A은유 적으로 ” 테이프를 되감습니다 “: 이전에 읽은 비트를 반환합니다. 읽은 비트는 코딩 E하거나T그러나 지금은 아닙니다. 나중에 사용됩니다. 이 간단한 예에서 이것은 기본적으로 동일한 문자 ( 또는 )가 올바르게 될 때까지 계속 추측 한다는 것을 의미합니다 . 그런 다음 다른 비트를 읽고 계속 진행합니다.ET

이 힌트 파일의 인코딩은 매우 간단합니다. 모든 s를 완전히 무시하면서 모든 Es를 0 비트로, Ts를 1 비트로 전환 A합니다. 이전 섹션의 끝 부분에있는 분석에 따르면이 체계는 오류를 발생 시키지만 As를 인코딩하지 않으면 서 전체적으로 점수를 줄 입니다. 더 작은 효과로 실제로 힌트 파일의 길이도 절약됩니다. 비트 E와 비트가 T아닌 비트 와 비트마다 정확히 하나의 비트를 사용하기 때문 입니다.

작은 정리

언제 오류를 내야합니까? 모델이 다음 캐릭터에 대한 확률 분포 P를 제공한다고 가정합니다. 가능한 문자를 codednot coded의 두 클래스로 분리합니다 . 올바른 문자가 코딩되지 않은 경우 “되감기”메커니즘을 사용하여 길이에 상관없이 오류를 허용합니다. 올바른 문자가 코딩되면 다른 분포 Q를 사용하여 산술 코딩으로 인코딩합니다.

그러나 어떤 분포 Q를 선택해야합니까? 코딩 된 문자가 코딩되지 않은 문자보다 높은 확률 (P)을 가져야한다는 것을 너무 어렵지 않습니다. 또한 분포 Q는 코드화 된 문자 만 포함해야합니다. 결국 우리는 다른 것들을 코딩하지 않기 때문에 엔트로피를 “지출”해서는 안됩니다. 확률 분포 Q가 코딩 된 문자에서 P에 비례해야한다는 것을보기에는 조금 까다 롭습니다. 이러한 관찰 결과를 종합하면 가장 가능성이 높은 문자를 코딩해야하지만 가능성이 적은 문자는 코딩하지 않아야하며 Q는 코딩 된 문자에 대해 단순히 P 배율이 조정됩니다.

또한 코딩 문자에 대해 어떤 “컷오프”를 선택해야하는지에 대한 멋진 정리가 있음이 밝혀졌습니다. 다른 코드화 된 문자가 결합 될 가능성이 최소 1 / 5.393 인 한 문자를 코딩해야합니다. 이것은 5.393위의 프로그램의 끝 부분에있는 것처럼 보이는 임의의 상수 모양을 “설명”합니다 . 숫자 1 / 5.393 ≈ 0.18542는 방정식 -p log (16)-p log p + (1 + p) log (1 + p) = 0에 대한 해 입니다.

아마도이 절차를 코드로 작성하는 것이 합리적입니다. 이 스 니펫은 C ++입니다.

// Assume the model is computed elsewhere.
unordered_map<char, double> model;

// Transform p to q
unordered_map<char, double> code;
priority_queue<pair<double,char>> pq;
for( char c : CHARS )
    pq.push( make_pair(model[c], c) );
double s = 0, p;
while( 1 ) {
    char c = pq.top().second;
    pq.pop();
    p = model[c];
    if( s > 5.393*p )
        break;
    code[c] = p;
    s += p;
}
for( auto& kv : code ) {
    char c = kv.first;
    code[c] /= s;
}

함께 모아서

이전 섹션은 불행히도 약간 기술적 인 부분이지만 다른 부분을 모두 합하면 구조는 다음과 같습니다. 프로그램이 주어진 올바른 문자 다음에 다음 문자를 예측하도록 요청 될 때마다 :

  1. Moby Dick의 알려진 올바른 접두사에 올바른 문자를 추가하십시오.
  2. 텍스트의 (Markov) 모델을 업데이트하십시오.
  3. 비밀 소스 : 이전 추측이 정확하지 않으면, 되감기 이전 추측하기 전에 상태로 산술 디코더의 상태를!
  4. 다음 캐릭터에 대한 확률 분포 P를 예측하도록 Markov 모델에 요청하십시오.
  5. 이전 섹션의 서브 루틴을 사용하여 P를 Q로 변환합니다.
  6. 분포 Q에 따라 산술 디코더에 힌트 파일의 나머지 부분에서 문자를 디코딩하도록 요청하십시오.
  7. 결과 캐릭터를 맞춰보세요.

힌트 파일의 인코딩도 비슷하게 작동합니다. 이 경우 프로그램은 올바른 다음 문자가 무엇인지 알고 있습니다. 그것이 코딩되어야하는 문자라면, 물론 그 위에 산술 인코더를 사용해야합니다. 그러나 코드화되지 않은 문자이면 산술 인코더의 상태를 업데이트하지 않습니다.

확률 분포, 엔트로피, 압축 및 산술 코딩과 같은 정보 이론적 배경을 이해했지만이 게시물을 이해하지 못하고 시도하지 않은 경우 (이론이 참인 이유는 제외) 알려 주시면 정리할 수 있습니다. 읽어 주셔서 감사합니다!


답변

파이썬 3, 2 · 267 + 510193 = 510727

예언자

def p():
 d={};s=b''
 while 1:
  p={0:1};r=range(len(s)+1)
  for i in r:
   for c,n in d.setdefault(s[:i],{}).items():p[c]=p.get(c,1)*n**b'\1\6\f\36AcWuvY_v`\270~\333~'[i]
  c=yield max(sorted(p),key=p.get)
  for i in r:e=d[s[:i]];e[c]=e.get(c,1)+1
  s=b'%c'%c+s[:15]

이것은 0,…, 16 Markov 모델의 가중치 베이지안 조합을 사용하고 가중치는 [1, 6, 12, 30, 65, 99, 87, 117, 118, 89, 95, 118, 96, 184, 126, 219, 126].

결과는 이러한 가중치의 선택에 매우 민감하지는 않지만 각 후보 돌연변이가있는 “상원 다수결”에 대한 답변 에서 사용한 것과 동일한 후기 허용 언덕 등반 알고리즘을 사용하여 가중치를 최적화했습니다. 단일 중량에 대해 ± 1 씩 증가합니다.

테스트 코드

with open('whale2.txt', 'rb') as f:
    g = p()
    wrong = 0
    a = next(g)
    for b in f.read():
        wrong += a != b
        a = g.send(b)
    print(wrong)


답변

Python 3 , 2 * 279 + 592920 = 593478 2 * 250 + 592467 = 592967 2 * 271 + 592084 = 592626 2 * 278 + 592059 = 592615 2 * 285 + 586660 = 587230 2 * 320 + 585161 = 585801 2 * 339 + 585050 = 585728

d=m={}
s=1
w,v='',0
def f(c):
 global w,m,v,s,d
 if w not in m:m[w]={}
 u=m[w];u[c]=c in u and 1+u[c]or 1;v+=1;q=n=' ';w=w*s+c;s=c!=n
 if w in m:_,n=max((m[w][k],k)for k in m[w])
 elif s-1:n=d in'nedtfo'and't'or'a'
 elif'-'==c:n=c
 elif"'"==c:n='s'
 elif'/'<c<':':n='.'
 if v>4*(n!=q)+66:n='\n'
 if s:d=c
 if c<q:w=w[:-1]+q;v=s=0
 return n

온라인으로 사용해보십시오!

전역 변수를 사용하는 함수. 단어 수준에서 모델을 구축하면서 학습합니다. 이 단어에서 지금까지 본 것 중에서 가장 일반적인 다음 문자는 무엇입니까? 더 많은 입력이 들어 오면 텍스트에서 일반적인 단어를 꽤 잘 배우고 다음 단어 를 시작하는 가장 일반적인 문자를 배웁니다 .

예를 들면 다음과 같습니다.

  • 지금까지 본 것이 ‘Captai’인 경우 “n”을 예측합니다.
  • “캡틴”이면 공간을 예측합니다
  • 단어의 시작이고 마지막 단어가 “캡틴”인 경우 ‘A’를 예측합니다.
  • 지금까지 단어가 ‘A’이면 ‘h'(그리고 ‘a’와 ‘b’; ‘C’와 유사하게)를 예측합니다.

처음에는 잘하지 않지만 결국 실제 단어의 큰 부분이 나옵니다. 대체 옵션은 공백이며, 앞의 문자가 “nedtfo”, 숫자 또는 하이픈 또는 아포스트로피 중 하나가 아닌 한 공백은 “a”입니다. 또한 71 자 뒤의 줄 바꿈 또는 66 자 뒤의 공백이 예상되는 경우 적극적으로 줄 바꿈을 예측합니다. 둘 다 데이터에 맞게 조정되었습니다 ( “t”는 공백 후에 훨씬 흔하지 만 이미 자주 예측되었으므로 ” a “는 6 가지 특수한 경우를 제외하고 더 나은 추측입니다.)

어떤 단어 쌍이 함께되었는지 배우고 매핑을 미리 설정하는 것은 가치가없는 것으로 밝혀졌습니다.


다음과 같은 텍스트로 끝납니다.

nl tneund his    I woi tis tnlost ahet toie tn tant  wod, ihet taptain Ahab ses
 snd t
oeed Sft   aoid thshtego    Io, fhe soie tn tant  tot the soie      ahe sewbtoon
swn tagd  aoths eatmved fhe sewbtoon wor ta  I sfey  aote of totsonld nive betse
d ahe
hate Whale iorst  Ihe e ioi beaos! -there soi beaos! -there soi beaos!

이것은 입력 의이 부분에 해당합니다.

그의 주위에.

타슈 테고 대변인은“아브 선장과 거의 같은 순간을 보았습니다.

“동일한 순간이 아니었다. 동일하지 않다.-아니, doubloon은 나의 것이다. Fate는 나를 위해 doubloon을 예약했다. 나는 오직 어느 누구도 흰 고래를 먼저 올릴 수 없었다. -그녀가 불었다!

적절한 명사가 어디에서 나오는지 알 수 있지만 단어의 끝이 대부분 옳습니다. “dou”이 표시되면 “doubt”가 필요하지만 “l”이 표시되면 “doubloon”이됩니다.

동일한 모델로 두 번째로 실행하면 방금 구축 한 또 다른 92k 정확도 (51.7 %-> 59.3 %)가 발생하지만 두 번째 반복에서 항상 60 % 미만입니다.


측정 코드는 TIO 링크에 있거나 약간 더 나은 버전입니다.

total = 0
right = 0
with open('whale.txt') as fp:
    with open('guess.txt', 'w') as dest:
        for l in fp.readlines():
            for c in l:
                last = c
                if p == c: right += 1
                n = f(c)
                p = n
                total += 1
                dest.write(n)
                if total % 10000 == 0:
                    print('{} / {} E={}\r'.format(right, total, total-right), end='')
print('{} / {}: E={}'.format(right, total, total - right))

guess.txt 끝에 추측 된 출력이 있습니다.


답변

C ++, 점수 : 2 * 132 + 865821 = 866085

217 바이트를 절약 한 @Quentin에게 감사드립니다!

int f(int c){return c-10?"t \n 2  sS \n  -  08........       huaoRooe oioaoheu thpih eEA \n   neo    enueee neue hteht e"[c-32]:10;}

문자가 주어지면 입력 문자 다음에 가장 자주 나타나는 문자를 출력하는 매우 간단한 솔루션입니다.

다음을 사용하여 점수를 확인하십시오.

#include <iostream>
#include <fstream>

int f(int c);

int main()
{
    std::ifstream file;
    file.open("whale2.txt");

    if (!file.is_open())
        return 1;

    char p_ch, ch;
    file >> std::noskipws >> p_ch;
    int incorrect = 0;
    while (file >> std::noskipws >> ch)
    {
        if (f(p_ch) != ch)
            ++incorrect;
        p_ch = ch;
    }

    file.close();

    std::cout << incorrect;
}

편집 :를 사용 whale2.txt하면 점수가 높아집니다.


답변

파이썬, 2 * 516 + 521122 = 522154

연산:

또 다른 파이썬 제출,이 알고리즘은 길이 1, …, l의 시퀀스를보고 가장 가능성이 높은 다음 문자를 계산합니다. 확률의 합이 사용되며 더 나은 결과를 얻기위한 몇 가지 트릭이 있습니다.

from collections import Counter as C, defaultdict as D
R,l=range,10
s,n='',[D(C) for _ in R(l+1)]
def A(c):
 global s;s+=c;
 if len(s)<=l:return ' '
 P=D(lambda:0)
 for L in R(1,l+1):
  w=''.join(s[-L-1:-1]);n[L][w].update([c]);w=''.join(s[-L:])
  try:
   q,z=n[L][w].most_common(1)[0];x=sum(list(n[L][w].values()))
  except IndexError:continue
  p=z/x
  if x<3:p*=1/(3-x)
  P[q]+=p
 if not P:return ' '
 return max(P.items(),key=lambda i:i[1])[0]
import this, codecs as d
[A(c) for c in d.decode(this.s, 'rot-13')]

결과 :

대부분의 횡설수설이지만 “Father Mapple”과 같은 간혹 문구가 나타납니다.

errors: 521122
TRAINING:
result:  tetlsnowleof the won -opes  aIther Mapple,woneltnsinkeap hsd   lnd the  thth a shoey,aeidorsbine ao
actual: ntal knobs of the man-ropes, Father Mapple cast a look upwards, and then with a truly sailor-like bu
FINAL:
result: mnd wnd round  ahe   ind tveryaonsracting th ards the sol ens-ike aeock tolblescn the sgis of thet t
actual: und and round, then, and ever contracting towards the button-like black bubble at the axis of that s

테스트 코드 :

매우 간단합니다. 텍스트의 몇 가지 예를 다른 지점에서 출력합니다. whale2.txt를 사용하면 줄 바꿈을 계산하는 추가 논리가 필요하지 않습니다.

from minified import A

def score(predict, text):
    errors = 0
    newtext = []
    for i, (actual, current) in  enumerate(zip(text[1:], text[:-1])):
        next = predict(current)
        errors += (actual != next)
        newtext.append(next)
        if (i % (len(text) // 100) == 0):
            print ('.', end='', flush=True)
    return errors, ''.join(newtext)

t = open('whale2.txt')
text = t.read()
err2, text2 = score(A, text)
print('errors:', err2)
print("TRAINING:")
print(text2[100000:100100].replace('\n', '\\n'))
print(text1[100001:100101].replace('\n', '\\n'))
print("FINAL:")
print(text2[121400:1215500].replace('\n', '\\n'))
print(text[121401:1215501].replace('\n', '\\n'))