1

次のマルコフ スクリプトを変更して、大文字と小文字の単語を同じものとして扱うにはどうすればよいですか?

全体的なアイデアは、私のマルコフ テキスト ジェネレーターの出力の品質を向上させることです。

現状では、99 個の小文字の文と 1 つの大文字の文を差し込むと、ほとんどの場合、大文字の文のマルコフ化されていないバージョンが出力に表示されます。

# Copyright (C) 1999 Lucent Technologies
# Excerpted from 'The Practice of Programming'
# by Brian W. Kernighan and Rob Pike

# markov.pl: markov chain algorithm for 2-word prefixes

$MAXGEN = 10000;
$NONWORD = "\n";
$w1 = $w2 = $NONWORD;                    # initial state
while (<>)
{                                        # read each line of input
    foreach (split)
    {
      push(@{$statetab{$w1}{$w2}}, $_);
      ($w1, $w2) = ($w2, $_);        # multiple assignment
    }
}

push(@{$statetab{$w1}{$w2}}, $NONWORD);  # add tail
$w1 = $w2 = $NONWORD;

for ($i = 0; $i < $MAXGEN; $i++) 
{
    $suf = $statetab{$w1}{$w2};      # array reference
    $r = int(rand @$suf);            # @$suf is number of elems
    exit if (($t = $suf->[$r]) eq $NONWORD);
    print "$t\n";
    ($w1, $w2) = ($w2, $t);          # advance chain
}
4

3 に答える 3

6

Nathan Fellman と mobrule はどちらも、一般的な方法である正規化を提案しています。

プログラムまたはサブルーチンの主な目的である実際の計算を行う前に、内容と構造の期待される規範に準拠するようにデータを処理する方が簡単な場合がよくあります。

マルコフ連鎖プログラムが面白かったので、それで遊んでみました。

これは、マルコフ連鎖のレイヤー数を制御できるバージョンです。変更$DEPTHすることで、シミュレーションの順序を調整できます。

コードを再利用可能なサブルーチンに分割しました。正規化ルーチンを変更することで、正規化ルールを変更できます。定義された一連の値に基づいてチェーンを生成することもできます。

多層状態テーブルを生成するコードは、最も興味深いビットでした。Data::Diver を使用することもできましたが、自分で解決したかったのです。

単語の正規化コードは、ノーマライザーが単一の単語ではなく、処理する単語のリストを返すことを実際に許可する必要があります-しかし、私は今それを修正すると単語のリストを返すことができるとは思いません..処理されたものをシリアル化するなどの他のことコマンド ライン スイッチに Getopt::Long を使用することはまだ残っています。私は楽しい部分だけをしました。

オブジェクトを使用せずにこれを書くのは、私にとって少し挑戦でした。これは、マルコフ生成オブジェクトを作成するのに適した場所のように感じました。私はオブジェクトが好きです。しかし、元の精神を保持するために、コードを手続き型のままにすることにしました。

楽しむ。

#!/usr/bin/perl
use strict;
use warnings;

use IO::Handle;

use constant NONWORD => "-";
my $MAXGEN = 10000;
my $DEPTH  = 2;

my %state_table;

process_corpus( \*ARGV, $DEPTH, \%state_table );
generate_markov_chain( \%state_table, $MAXGEN );


sub process_corpus {
    my $fh    = shift;
    my $depth = shift;
    my $state_table = shift || {};;

    my @history = (NONWORD) x $depth;


    while( my $raw_line = $fh->getline ) {

        my $line = normalize_line($raw_line);
        next unless defined $line;

        my @words = map normalize_word($_), split /\s+/, $line;
        for my $word ( @words ) {

            next unless defined $word; 

            add_word_to_table( $state_table, \@history, $word );
            push  @history, $word;
            shift @history;
        }

    }

    add_word_to_table( $state_table, \@history, NONWORD );

    return $state_table;
}

# This was the trickiest to write.
# $node has to be a reference to the slot so that 
# autovivified items will be retained in the $table.
sub add_word_to_table {
    my $table   = shift;
    my $history = shift;
    my $word    = shift;

    my $node = \$table;

    for( @$history ) {
        $node = \${$node}->{$_};
    }

    push @$$node, $word;

    return 1;
}

# Replace this with anything.
# Return undef to skip a word
sub normalize_word {
    my $word = shift;
    $word =~ s/[^A-Z]//g;
    return length $word ? $word : ();
}

# Replace this with anything.
# Return undef to skip a line
sub normalize_line {
    return uc shift;
}


sub generate_markov_chain {
    my $table   = shift;
    my $length  = shift;
    my $history = shift || [];

    my $node = $table;

    unless( @$history ) {

        while( 
            ref $node eq ref {}
                and
            exists $node->{NONWORD()} 
        ) {
            $node = $node->{NONWORD()};
            push @$history, NONWORD;
        }

    }

    for (my $i = 0; $i < $MAXGEN; $i++) {

        my $word = get_word( $table, $history );

        last if $word eq NONWORD;
        print "$word\n";

        push @$history, $word;
        shift @$history;
    }

    return $history;
}


sub get_word {
    my $table   = shift;
    my $history = shift;

    for my $step ( @$history ) {
        $table = $table->{$step};
    }

    my $word = $table->[ int rand @$table ];
    return $word;
}

更新:normalize_word()上記のコードを修正して、ルーチン から返される複数の単語を処理できるようにしました。

大文字と小文字を区別せずに句読点を単語として扱うには、 and を次のように置き換えnormalize_line()ますnormalize_word()

sub normalize_line {
    return shift;
}

sub normalize_word {
    my $word = shift;

    # Sanitize words to only include letters and ?,.! marks 
    $word =~ s/[^A-Z?.,!]//gi;

    # Break the word into multiple words as needed.
    my @words = split /([.?,!])/, $word;

    # return all non-zero length words. 
    return grep length, @words;
}

もう 1 つの大きな-落とし穴は、NONWORD キャラクターとして使用したことです。ハイフンを句読点記号として含めたい場合は、8 行目の NONWORD 定数定義を変更する必要があります。決して単語にならないものを選択してください。

于 2010-03-03T02:51:46.387 に答える
5

すべての入力を処理する前に小文字に変換しますか?

関数を参照してlcください。

于 2010-03-02T21:15:07.480 に答える
4

最善の策は、単語が入力されたらすぐに小文字 (または大文字) にすることだと思います。

while (<>)
{                                        # read each line of input
    lc; # convert $_ to lowercase
    # etc.
}
于 2010-03-02T21:15:57.197 に答える