52

私は以下の例で、1000程度のエントリの配列を持っています:

wickedweather
liquidweather
driveourtrucks
gocompact
slimprojector

次のように、これらをそれぞれの単語に分割できるようにしたいと思います。

wicked weather
liquid weather
drive our trucks
go compact
slim projector

私は正規表現がうまくいくことを望んでいました。しかし、立ち止まる境界がなく、キーとなる可能性のある大文字化も存在しないため、何らかの辞書への参照が必要になるのではないかと考えています。

手作業でできると思いますが、コードでできるのになぜでしょうか! =) しかし、これは私を困惑させました。何か案は?

4

15 に答える 15

82

ビタビ アルゴリズムははるかに高速です。上記の Dmitry の回答の再帰的検索と同じスコアを計算しますが、O(n) 時間で計算されます。(Dmitry の検索には指数関数的な時間がかかります。Viterbi は動的計画法によってそれを行います。)

import re
from collections import Counter

def viterbi_segment(text):
    probs, lasts = [1.0], [0]
    for i in range(1, len(text) + 1):
        prob_k, k = max((probs[j] * word_prob(text[j:i]), j)
                        for j in range(max(0, i - max_word_length), i))
        probs.append(prob_k)
        lasts.append(k)
    words = []
    i = len(text)
    while 0 < i:
        words.append(text[lasts[i]:i])
        i = lasts[i]
    words.reverse()
    return words, probs[-1]

def word_prob(word): return dictionary[word] / total
def words(text): return re.findall('[a-z]+', text.lower()) 
dictionary = Counter(words(open('big.txt').read()))
max_word_length = max(map(len, dictionary))
total = float(sum(dictionary.values()))

テスト:

>>> viterbi_segment('wickedweather')
(['wicked', 'weather'], 5.1518198982768158e-10)
>>> ' '.join(viterbi_segment('itseasyformetosplitlongruntogetherblocks')[0])
'its easy for me to split long run together blocks'

実用的にするには、いくつかの改良が必要になる可能性があります。

  • 確率のログを追加します。確率を乗算しないでください。これにより、浮動小数点のアンダーフローが回避されます。
  • 一般に、入力はコーパスにない単語を使用します。これらの部分文字列には、単語としてゼロ以外の確率を割り当てる必要があります。そうしないと、解がないか、悪い解になってしまいます。(これは、上記の指数検索アルゴリズムにも当てはまります。) この確率は、コーパスの単語の確率から吸い上げられ、他のすべての単語候補にもっともらしく分配される必要があります。一般的なトピックは、統計言語モデルでは平滑化として知られています。(ただし、かなり大まかなハックで回避できます。) これは、O(n) ビタビ アルゴリズムが検索アルゴリズムを吹き飛ばす場所です。
于 2009-01-26T23:10:31.063 に答える
34

人間はそれを行うことができますか?

ファーサイドバッグ
ファーサイドバッグ
ファーサイドバッグ
ファーサイドバッグ

辞書を使用する必要があるだけでなく、統計的アプローチを使用して最も可能性の高いものを特定する必要がある場合があります (または、神が禁じているように、選択した人間の言語の実際の HMM を使用することもできます...)。

役に立つかもしれない統計を行う方法については、Peter Norvig 博士を参照してください。彼は、21 行のコードでスペル チェックに関する別の、しかし関連する問題に取り組んでいます: http://norvig.com/spell-correct.html

(彼は、すべての for ループを 1 行に折りたたむことで少しごまかしていますが、それでもなお)。

更新これが頭に詰まったので、今日出産しなければなりませんでした。このコードは、Robert Gamble によって説明されたものと同様の分割を行いますが、提供された辞書ファイル内の単語の頻度に基づいて結果を並べ替えます (これは、ドメインまたは一般的な英語を表すテキストであると予想されます。上のリンクにある Norvig の .txt に辞書を追加して、欠落している単語をカバーしています)。

頻度の差が非常に大きい場合を除き、ほとんどの場合、2 つの単語の組み合わせは 3 つの単語の組み合わせよりも優れています。


このコードにいくつかの小さな変更を加えてブログに投稿しました

http://squarecog.wordpress.com/2008/10/19/splitting-words-joined-into-a-single-string/ また、このコードのアンダーフロー バグについて少し書きました..それを修正しますが、これは以前にログのトリックを見たことがない一部の人々を助けるかもしれないと考えました :


あなたの言葉に加えて、私自身の言葉をいくつか出力してください -- 「orcore」で何が起こるかに注目してください。

perl splitwords.pl big.txt 単語
回答: 2 つの可能性
 -子牛に答える
 - 答えてください

ウィキッドウェザー: 4 つの可能性
 - 悪天候
 - 邪悪な私たちは彼女に
 - 邪悪な天気
 - 私たちは彼女に悪口を言いました

リキッドウェザー: 6 つの可能性
 - 液体の天気
 - 液体私たちは彼女に
 - 天気予報
 -彼女に私たちを怒らせた
 - 液体の天気
 - 私たちは彼女に会いました

driveourtrucks: 1 つの可能性
 - トラックを運転する

gocompact: 1 つの可能性
 - コンパクトに

スリムプロジェクター: 2 つの可能性
 - スリムプロジェクター
 - スリムなプロジェクトまたは

orcore: 3 つの可能性
 - またはコア
 - またはコア
 - オーク鉱石

コード:

#!/usr/bin/env perl

use strict;
use warnings;

sub find_matches($);
sub find_matches_rec($\@\@);
sub find_word_seq_score(@);
sub get_word_stats($);
sub print_results($@);
sub Usage();

our(%DICT,$TOTAL);
{
  my( $dict_file, $word_file ) = @ARGV;
  ($dict_file && $word_file) or die(Usage);

  {
    my $DICT;
    ($DICT, $TOTAL) = get_word_stats($dict_file);
    %DICT = %$DICT;
  }

  {
    open( my $WORDS, '<', $word_file ) or die "unable to open $word_file\n";

    foreach my $word (<$WORDS>) {
      chomp $word;
      my $arr = find_matches($word);


      local $_;
      # Schwartzian Transform
      my @sorted_arr =
        map  { $_->[0] }
        sort { $b->[1] <=> $a->[1] }
        map  {
          [ $_, find_word_seq_score(@$_) ]
        }
        @$arr;


      print_results( $word, @sorted_arr );
    }

    close $WORDS;
  }
}


sub find_matches($){
    my( $string ) = @_;

    my @found_parses;
    my @words;
    find_matches_rec( $string, @words, @found_parses );

    return  @found_parses if wantarray;
    return \@found_parses;
}

sub find_matches_rec($\@\@){
    my( $string, $words_sofar, $found_parses ) = @_;
    my $length = length $string;

    unless( $length ){
      push @$found_parses, $words_sofar;

      return @$found_parses if wantarray;
      return  $found_parses;
    }

    foreach my $i ( 2..$length ){
      my $prefix = substr($string, 0, $i);
      my $suffix = substr($string, $i, $length-$i);

      if( exists $DICT{$prefix} ){
        my @words = ( @$words_sofar, $prefix );
        find_matches_rec( $suffix, @words, @$found_parses );
      }
    }

    return @$found_parses if wantarray;
    return  $found_parses;
}


## Just a simple joint probability
## assumes independence between words, which is obviously untrue
## that's why this is broken out -- feel free to add better brains
sub find_word_seq_score(@){
    my( @words ) = @_;
    local $_;

    my $score = 1;
    foreach ( @words ){
        $score = $score * $DICT{$_} / $TOTAL;
    }

    return $score;
}

sub get_word_stats($){
    my ($filename) = @_;

    open(my $DICT, '<', $filename) or die "unable to open $filename\n";

    local $/= undef;
    local $_;
    my %dict;
    my $total = 0;

    while ( <$DICT> ){
      foreach ( split(/\b/, $_) ) {
        $dict{$_} += 1;
        $total++;
      }
    }

    close $DICT;

    return (\%dict, $total);
}

sub print_results($@){
    #( 'word', [qw'test one'], [qw'test two'], ... )
    my ($word,  @combos) = @_;
    local $_;
    my $possible = scalar @combos;

    print "$word: $possible possibilities\n";
    foreach (@combos) {
      print ' -  ', join(' ', @$_), "\n";
    }
    print "\n";
}

sub Usage(){
    return "$0 /path/to/dictionary /path/to/your_words";
}
于 2008-10-12T02:50:14.863 に答える
11

pip install wordninja

>>> import wordninja
>>> wordninja.split('bettergood')
['better', 'good']
于 2019-09-19T11:46:00.827 に答える
8

ここでの作業に最適なツールは、正規表現ではなく再帰です。基本的な考え方は、文字列の最初から単語を探し、次に文字列の残りの部分を取り出して別の単語を探すというように、文字列の最後に到達するまで続きます。文字列の特定の残りの部分を単語のセットに分割できない場合にバックトラックを実行する必要があるため、再帰的な解決策は自然です。以下の解決策は、辞書を使用して単語とは何かを判断し、解決策を見つけたときにそれを出力します(たとえば、wickedweatherは「wickedwe at her」として解析できるなど、いくつかの文字列を複数の可能な単語のセットに分割できます)。単語のセットが1つだけ必要な場合は、最適なセットを選択するためのルールを決定する必要があります。

#!/usr/bin/perl

use strict;

my $WORD_FILE = '/usr/share/dict/words'; #Change as needed
my %words; # Hash of words in dictionary

# Open dictionary, load words into hash
open(WORDS, $WORD_FILE) or die "Failed to open dictionary: $!\n";
while (<WORDS>) {
  chomp;
  $words{lc($_)} = 1;
}
close(WORDS);

# Read one line at a time from stdin, break into words
while (<>) {
  chomp;
  my @words;
  find_words(lc($_));
}

sub find_words {
  # Print every way $string can be parsed into whole words
  my $string = shift;
  my @words = @_;
  my $length = length $string;

  foreach my $i ( 1 .. $length ) {
    my $word = substr $string, 0, $i;
    my $remainder = substr $string, $i, $length - $i;
    # Some dictionaries contain each letter as a word
    next if ($i == 1 && ($word ne "a" && $word ne "i"));

    if (defined($words{$word})) {
      push @words, $word;
      if ($remainder eq "") {
        print join(' ', @words), "\n";
        return;
      } else {
        find_words($remainder, @words);
      }
      pop @words;
    }
  }

  return;
}
于 2008-10-12T04:12:28.130 に答える
4

正規表現の仕事ではないと考えているのは正しいと思います。辞書のアイデアを使用してこれにアプローチします。辞書内の単語である最長の接頭辞を探します。それが見つかったら、それを切り取り、残りの文字列で同じことを行います.

上記の方法はあいまいな場合があります。たとえば、"drivereallyfast" は最初に "driver" を見つけ、次に "eallyfast" で問題が発生します。したがって、この状況に遭遇した場合は、バックトラックを行う必要もあります。または、分割する文字列がそれほど多くないため、自動分割に失敗したものだけを手動で行います。

于 2008-10-12T02:40:14.743 に答える
1

まあ、問題自体は正規表現だけでは解決できません。解決策 (おそらく最善ではない) は、辞書を取得し、辞書内の各作業とリスト内の各単語を正規表現で一致させ、成功した場合は常にスペースを追加することです。確かに、これはそれほど速くはありませんが、プログラムするのは簡単で、手動で行うよりも高速です。

于 2008-10-12T02:41:20.013 に答える
1

辞書ベースのソリューションが必要になります。発生する可能性のある単語の辞書が限られている場合、これは多少単純化される可能性があります。そうでない場合、他の単語の接頭辞を形成する単語が問題になります。

于 2008-10-12T02:41:31.137 に答える
1
output :-
['better', 'good'] ['coffee', 'shop']
['coffee', 'shop']

    pip install wordninja
import wordninja
n=wordninja.split('bettergood')
m=wordninja.split("coffeeshop")
print(n,m)

list=['hello','coffee','shop','better','good']
mat='coffeeshop'
expected=[]
for i in list:
    if i in mat:
        expected.append(i)
print(expected)
于 2021-06-23T07:48:33.540 に答える
1

形態素解析に使用できる mlmorph と呼ばれる Santhosh thottingal という python パッケージがリリースされています。

https://pypi.org/project/mlmorph/

例:

from mlmorph import Analyser
analyser = Analyser()
analyser.analyse("കേരളത്തിന്റെ")

与える

[('കേരളം&lt;np><genitive>', 179)]

彼はまた、トピックに関するブログを書きましたhttps://thottingal.in/blog/2017/11/26/towards-a-malayalam-morphology-analyser/

于 2019-01-16T05:34:37.890 に答える
0

気分を害するかもしれませんが、秘書にやってもらいましょう。

手動で処理するよりも、ディクショナリ ソリューションにより多くの時間を費やすことになります。さらに、ソリューションに 100% の信頼性があるとは限らないため、手動で対処する必要があります。

于 2008-10-12T02:49:03.213 に答える