Phát hiện âm tiết trong một từ


138

Tôi cần tìm một cách khá hiệu quả để phát hiện các âm tiết trong một từ. Ví dụ,

Vô hình -> in-vi-sib-le

Có một số quy tắc âm tiết có thể được sử dụng:

V CV VC CVC CCV CCCV CVCC

* trong đó V là nguyên âm và C là phụ âm. Ví dụ,

Phát âm (5 Pro-nun-ci-a-tion; CV-CVC-CV-V-CVC)

Tôi đã thử một vài phương pháp, trong đó sử dụng regex (chỉ giúp nếu bạn muốn đếm các âm tiết) hoặc định nghĩa quy tắc mã hóa cứng (một cách tiếp cận mạnh mẽ chứng tỏ là rất không hiệu quả) và cuối cùng sử dụng một automata trạng thái hữu hạn (đã làm không có kết quả với bất cứ điều gì hữu ích).

Mục đích của ứng dụng của tôi là tạo ra một từ điển của tất cả các âm tiết trong một ngôn ngữ nhất định. Từ điển này sau đó sẽ được sử dụng cho các ứng dụng kiểm tra chính tả (sử dụng phân loại Bayes) và văn bản để tổng hợp giọng nói.

Tôi sẽ đánh giá cao nếu ai đó có thể cho tôi lời khuyên về một cách khác để giải quyết vấn đề này bên cạnh các phương pháp trước đây của tôi.

Tôi làm việc trong Java, nhưng bất kỳ mẹo nào trong C / C ++, C #, Python, Perl ... đều phù hợp với tôi.


Bạn có thực sự muốn các điểm phân chia thực tế hoặc chỉ số lượng âm tiết trong một từ? Nếu sau này, hãy cân nhắc tìm kiếm các từ trong từ điển chuyển văn bản thành giọng nói và đếm các âm vị mã hóa các nguyên âm.
Adrian McCarthy

Cách hiệu quả nhất (tính toán khôn ngoan; không lưu trữ khôn ngoan), tôi đoán sẽ chỉ là có một từ điển Python với các từ là khóa và số lượng âm tiết làm giá trị. Tuy nhiên, bạn vẫn cần một dự phòng cho những từ không có trong từ điển. Hãy cho tôi biết nếu bạn từng tìm thấy một từ điển như vậy!
Brōtsyorfuzthrāx

Câu trả lời:


120

Đọc về cách tiếp cận TeX cho vấn đề này với mục đích gạch nối. Đặc biệt là xem luận văn luận án của Frank Liang Từ Hy-phen-a-tion của Com-put-er . Thuật toán của anh ta rất chính xác, và sau đó bao gồm một từ điển ngoại lệ nhỏ cho các trường hợp thuật toán không hoạt động.


52
Tôi thích rằng bạn đã trích dẫn một luận văn luận án về chủ đề này, đó là một gợi ý nhỏ cho người đăng ban đầu rằng đây có thể không phải là một câu hỏi dễ.
Karl

Vâng, tôi biết rằng đây không phải là một câu hỏi đơn giản, mặc dù tôi đã không làm việc nhiều với nó. Tôi đã đánh giá thấp vấn đề mặc dù, tôi nghĩ rằng tôi sẽ làm việc trên các phần khác của ứng dụng của mình và sau đó quay lại vấn đề 'đơn giản' này. Tôi thật ngốc :)
user50705

Tôi đọc bài luận văn, và thấy nó rất hữu ích. Vấn đề với cách tiếp cận là tôi không có bất kỳ mẫu nào cho ngôn ngữ Albania, mặc dù tôi đã tìm thấy một số công cụ có thể tạo ra các mẫu đó. Dù sao, với mục đích của mình, tôi đã viết một ứng dụng dựa trên quy tắc, giải quyết vấn đề ...
user50705

10
Lưu ý rằng thuật toán TeX là để tìm các điểm gạch nối hợp pháp, không chính xác giống như các phân chia âm tiết. Đúng là các điểm gạch nối rơi vào các phân chia âm tiết, nhưng không phải tất cả các phân chia âm tiết đều là các điểm gạch nối hợp lệ. Ví dụ, dấu gạch nối không (thường) được sử dụng trong một hoặc hai chữ cái cuối của một từ. Tôi cũng tin rằng các mẫu TeX đã được điều chỉnh để đánh đổi các âm tính giả cho các dương tính giả (không bao giờ đặt dấu gạch nối ở nơi không thuộc về nó, ngay cả khi điều đó có nghĩa là bỏ lỡ một số cơ hội gạch nối hợp pháp).
Adrian McCarthy

1
Tôi cũng không tin rằng gạch nối là câu trả lời.
Ezequiel

46

Tôi tình cờ tìm thấy trang này để tìm kiếm điều tương tự, và tìm thấy một vài triển khai của bài báo Liang tại đây: https://github.com/mnater/hyphenator hoặc người kế vị: https://github.com/mnater/Hyphenopoly

Đó là trừ khi bạn là người thích đọc một luận án 60 trang thay vì điều chỉnh mã có sẵn miễn phí cho vấn đề không phải là duy nhất. :)


đã đồng ý - thuận tiện hơn nhiều khi chỉ sử dụng một cấy ghép hiện có
hoju

41

Đây là một giải pháp sử dụng NLTK :

from nltk.corpus import cmudict
d = cmudict.dict()
def nsyl(word):
  return [len(list(y for y in x if y[-1].isdigit())) for x in d[word.lower()]] 

Xin cảm ơn lỗi bé nhỏ trong hàm nên là hàm def nsyl (word): return [len (list (y for y in x if y [-1] .itorigit ())) cho x in d [word.lower ()] ]
Gourneau

6
Bạn có đề xuất gì như một dự phòng cho những từ không có trong kho văn bản đó?
Dan Gayle

4
@Pureferret cmudict là một từ điển phát âm cho các từ tiếng Anh Bắc Mỹ. nó chia các từ thành các âm vị, ngắn hơn các âm tiết (ví dụ: từ 'cat' được chia thành ba âm vị: K - AE - T). nhưng nguyên âm cũng có "điểm nhấn trọng âm": 0, 1 hoặc 2, tùy thuộc vào cách phát âm của từ (vì vậy, AE trong 'cat' trở thành AE1). mã trong câu trả lời sẽ đếm các dấu trọng âm và do đó số nguyên âm - sẽ cho số lượng âm tiết một cách hiệu quả (chú ý cách các ví dụ của OP mỗi âm tiết có chính xác một nguyên âm).
billy_ch chương

1
Điều này trả về số lượng âm tiết, không phải âm tiết.
Adam Michael Wood

19

Tôi đang cố gắng giải quyết vấn đề này cho một chương trình sẽ tính toán điểm đọc flesch-kincaid và flesch của một khối văn bản. Thuật toán của tôi sử dụng những gì tôi tìm thấy trên trang web này: http://www.howmanysyllables.com/howtocountsyllables.html và nó được đóng lại một cách hợp lý. Nó vẫn gặp rắc rối với những từ phức tạp như vô hình và gạch nối, nhưng tôi đã tìm thấy nó trong sân bóng cho mục đích của tôi.

Nó có mặt trái là dễ thực hiện. Tôi tìm thấy "es" có thể là âm tiết hoặc không. Đó là một canh bạc, nhưng tôi đã quyết định loại bỏ es trong thuật toán của mình.

private int CountSyllables(string word)
    {
        char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'y' };
        string currentWord = word;
        int numVowels = 0;
        bool lastWasVowel = false;
        foreach (char wc in currentWord)
        {
            bool foundVowel = false;
            foreach (char v in vowels)
            {
                //don't count diphthongs
                if (v == wc && lastWasVowel)
                {
                    foundVowel = true;
                    lastWasVowel = true;
                    break;
                }
                else if (v == wc && !lastWasVowel)
                {
                    numVowels++;
                    foundVowel = true;
                    lastWasVowel = true;
                    break;
                }
            }

            //if full cycle and no vowel found, set lastWasVowel to false;
            if (!foundVowel)
                lastWasVowel = false;
        }
        //remove es, it's _usually? silent
        if (currentWord.Length > 2 && 
            currentWord.Substring(currentWord.Length - 2) == "es")
            numVowels--;
        // remove silent e
        else if (currentWord.Length > 1 &&
            currentWord.Substring(currentWord.Length - 1) == "e")
            numVowels--;

        return numVowels;
    }

Đối với kịch bản đơn giản của tôi là tìm các âm tiết trong tên riêng, điều này dường như ban đầu hoạt động đủ tốt. Cảm ơn vì đã đưa nó ra đây.
Norman H


5

Tại sao phải tính toán? Mỗi từ điển trực tuyến có thông tin này. http://dipedia.reference.com/browse/invisible trong · vis · i · ble


3
Có lẽ nó phải hoạt động cho những từ không xuất hiện trong từ điển, chẳng hạn như tên?
Wouter Lievens

4
@WouterLievens: Tôi không nghĩ rằng tên ở bất cứ đâu gần đủ hoạt động tốt để phân tích cú pháp âm tiết tự động. Một trình phân tích cú pháp âm tiết cho tên tiếng Anh sẽ thất bại thảm hại với tên của người gốc Wales hoặc Scotland, chứ đừng nói đến tên của nguồn gốc Ấn Độ và Nigeria, nhưng bạn có thể tìm thấy tất cả những điều này trong một phòng duy nhất ở đâu đó, ví dụ như London.
Jean-François Corbett

Người ta phải nhớ rằng thật không hợp lý khi mong đợi hiệu suất tốt hơn con người có thể cung cấp vì đây là một cách tiếp cận thuần túy đối với một miền sơ sài.
Darren Ringer

5

Cảm ơn Joe Basirico, vì đã chia sẻ cách triển khai nhanh và bẩn của bạn trong C #. Tôi đã sử dụng các thư viện lớn và chúng hoạt động, nhưng chúng thường hơi chậm và đối với các dự án nhanh, phương pháp của bạn hoạt động tốt.

Đây là mã của bạn trong Java, cùng với các trường hợp thử nghiệm:

public static int countSyllables(String word)
{
    char[] vowels = { 'a', 'e', 'i', 'o', 'u', 'y' };
    char[] currentWord = word.toCharArray();
    int numVowels = 0;
    boolean lastWasVowel = false;
    for (char wc : currentWord) {
        boolean foundVowel = false;
        for (char v : vowels)
        {
            //don't count diphthongs
            if ((v == wc) && lastWasVowel)
            {
                foundVowel = true;
                lastWasVowel = true;
                break;
            }
            else if (v == wc && !lastWasVowel)
            {
                numVowels++;
                foundVowel = true;
                lastWasVowel = true;
                break;
            }
        }
        // If full cycle and no vowel found, set lastWasVowel to false;
        if (!foundVowel)
            lastWasVowel = false;
    }
    // Remove es, it's _usually? silent
    if (word.length() > 2 && 
            word.substring(word.length() - 2) == "es")
        numVowels--;
    // remove silent e
    else if (word.length() > 1 &&
            word.substring(word.length() - 1) == "e")
        numVowels--;
    return numVowels;
}

public static void main(String[] args) {
    String txt = "what";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "super";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "Maryland";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "American";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "disenfranchized";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
    txt = "Sophia";
    System.out.println("txt="+txt+" countSyllables="+countSyllables(txt));
}

Kết quả như mong đợi (nó hoạt động đủ tốt cho Flesch-Kincaid):

txt=what countSyllables=1
txt=super countSyllables=2
txt=Maryland countSyllables=3
txt=American countSyllables=3
txt=disenfranchized countSyllables=5
txt=Sophia countSyllables=2

5

Va chạm @Tihamer và @ joe-basirico. Chức năng rất hữu ích, không hoàn hảo , nhưng tốt cho hầu hết các dự án vừa và nhỏ. Joe, tôi đã viết lại một triển khai mã của bạn bằng Python:

def countSyllables(word):
    vowels = "aeiouy"
    numVowels = 0
    lastWasVowel = False
    for wc in word:
        foundVowel = False
        for v in vowels:
            if v == wc:
                if not lastWasVowel: numVowels+=1   #don't count diphthongs
                foundVowel = lastWasVowel = True
                        break
        if not foundVowel:  #If full cycle and no vowel found, set lastWasVowel to false
            lastWasVowel = False
    if len(word) > 2 and word[-2:] == "es": #Remove es - it's "usually" silent (?)
        numVowels-=1
    elif len(word) > 1 and word[-1:] == "e":    #remove silent e
        numVowels-=1
    return numVowels

Hy vọng ai đó thấy điều này hữu ích!


4

Perl có Lingua :: Âm vị học :: Âm tiết mô đun . Bạn có thể thử điều đó, hoặc thử nhìn vào thuật toán của nó. Tôi cũng thấy một vài mô-đun cũ khác ở đó.

Tôi không hiểu tại sao một biểu thức chính quy chỉ cung cấp cho bạn một số lượng âm tiết. Bạn sẽ có thể tự nhận các âm tiết bằng cách sử dụng dấu ngoặc đơn. Giả sử bạn có thể xây dựng một biểu thức chính quy hoạt động, đó là.


4

Hôm nay tôi tìm thấy cái này triển khai Java của thuật toán gạch nối của Frank Liang với mẫu cho tiếng Anh hoặc tiếng Đức, hoạt động khá tốt và có sẵn trên Maven Central.

Cave: Điều quan trọng là phải xóa các dòng cuối cùng của .textệp mẫu, vì nếu không, các tệp đó không thể được tải với phiên bản hiện tại trên Maven Central.

Để tải và sử dụng hyphenator, bạn có thể sử dụng đoạn mã Java sau. texTablelà tên của các .textệp chứa các mẫu cần thiết. Những tập tin có sẵn trên trang web github của dự án.

 private Hyphenator createHyphenator(String texTable) {
        Hyphenator hyphenator = new Hyphenator();
        hyphenator.setErrorHandler(new ErrorHandler() {
            public void debug(String guard, String s) {
                logger.debug("{},{}", guard, s);
            }

            public void info(String s) {
                logger.info(s);
            }

            public void warning(String s) {
                logger.warn("WARNING: " + s);
            }

            public void error(String s) {
                logger.error("ERROR: " + s);
            }

            public void exception(String s, Exception e) {
                logger.error("EXCEPTION: " + s, e);
            }

            public boolean isDebugged(String guard) {
                return false;
            }
        });

        BufferedReader table = null;

        try {
            table = new BufferedReader(new InputStreamReader(Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream((texTable)), Charset.forName("UTF-8")));
            hyphenator.loadTable(table);
        } catch (Utf8TexParser.TexParserException e) {
            logger.error("error loading hyphenation table: {}", e.getLocalizedMessage(), e);
            throw new RuntimeException("Failed to load hyphenation table", e);
        } finally {
            if (table != null) {
                try {
                    table.close();
                } catch (IOException e) {
                    logger.error("Closing hyphenation table failed", e);
                }
            }
        }

        return hyphenator;
    }

Sau đó, Hyphenatorđã sẵn sàng để sử dụng. Để phát hiện các âm tiết, ý tưởng cơ bản là phân chia thuật ngữ tại các dấu gạch nối được cung cấp.

    String hyphenedTerm = hyphenator.hyphenate(term);

    String hyphens[] = hyphenedTerm.split("\u00AD");

    int syllables = hyphens.length;

Bạn cần tách ra "\u00AD", vì API không trả về mức bình thường "-".

Cách tiếp cận này vượt trội hơn câu trả lời của Joe Basirico, vì nó hỗ trợ nhiều ngôn ngữ khác nhau và phát hiện dấu gạch nối tiếng Đức chính xác hơn.


4

Tôi đã gặp vấn đề chính xác này một chút trước đây.

Tôi đã kết thúc bằng cách sử dụng Từ điển phát âm CMU để tra cứu nhanh chóng và chính xác hầu hết các từ. Đối với các từ không có trong từ điển, tôi đã quay lại mô hình học máy có độ chính xác ~ 98% trong việc dự đoán số lượng âm tiết.

Tôi đã gói toàn bộ trong một mô-đun python dễ sử dụng ở đây: https://github.com/repp/big-phoney

Tải về: pip install big-phoney

Đếm số âm tiết:

from big_phoney import BigPhoney
phoney = BigPhoney()
phoney.count_syllables('triceratops')  # --> 4

Nếu bạn không sử dụng Python và bạn muốn thử cách tiếp cận dựa trên mô hình ML, tôi đã viết một chi tiết khá chi tiết về cách mô hình đếm âm tiết hoạt động trên Kaggle .


Điều này là siêu mát mẻ. Có ai gặp may mắn khi chuyển đổi mô hình Keras kết quả thành mô hình CoreML để sử dụng trên iOS không?
Alexsander Akers

2

Cảm ơn bạn @ joe-basirico và @tihamer. Tôi đã chuyển mã @ tihamer sang Lua 5.1, 5.2 và luajit 2 ( rất có thể sẽ chạy trên các phiên bản khác của lua ):

countsyllables.lua

function CountSyllables(word)
  local vowels = { 'a','e','i','o','u','y' }
  local numVowels = 0
  local lastWasVowel = false

  for i = 1, #word do
    local wc = string.sub(word,i,i)
    local foundVowel = false;
    for _,v in pairs(vowels) do
      if (v == string.lower(wc) and lastWasVowel) then
        foundVowel = true
        lastWasVowel = true
      elseif (v == string.lower(wc) and not lastWasVowel) then
        numVowels = numVowels + 1
        foundVowel = true
        lastWasVowel = true
      end
    end

    if not foundVowel then
      lastWasVowel = false
    end
  end

  if string.len(word) > 2 and
    string.sub(word,string.len(word) - 1) == "es" then
    numVowels = numVowels - 1
  elseif string.len(word) > 1 and
    string.sub(word,string.len(word)) == "e" then
    numVowels = numVowels - 1
  end

  return numVowels
end

Và một số thử nghiệm thú vị để xác nhận nó hoạt động ( nhiều như nó dự định ):

countsyllables.tests.lua

require "countsyllables"

tests = {
  { word = "what", syll = 1 },
  { word = "super", syll = 2 },
  { word = "Maryland", syll = 3},
  { word = "American", syll = 4},
  { word = "disenfranchized", syll = 5},
  { word = "Sophia", syll = 2},
  { word = "End", syll = 1},
  { word = "I", syll = 1},
  { word = "release", syll = 2},
  { word = "same", syll = 1},
}

for _,test in pairs(tests) do
  local resultSyll = CountSyllables(test.word)
  assert(resultSyll == test.syll,
    "Word: "..test.word.."\n"..
    "Expected: "..test.syll.."\n"..
    "Result: "..resultSyll)
end

print("Tests passed.")

Tôi đã thêm hai trường hợp thử nghiệm "Kết thúc" và "Tôi". Cách khắc phục là so sánh các trường hợp chuỗi không nhạy cảm. Ping'ing @ joe-basirico và tihamer trong trường hợp họ gặp phải vấn đề tương tự và muốn cập nhật chức năng của họ.
josefnpat

@tihamer Mỹ là 4 âm tiết!
josefnpat

2

Tôi không thể tìm thấy một cách thích hợp để đếm các âm tiết, vì vậy tôi đã tự thiết kế một phương pháp.

Bạn có thể xem phương pháp của tôi ở đây: https://stackoverflow.com/a/32784041/2734752

Tôi sử dụng kết hợp từ điển và phương pháp thuật toán để đếm các âm tiết.

Bạn có thể xem thư viện của tôi ở đây: https://github.com/troywatson/Lawrence-Style-Checker

Tôi vừa thử nghiệm thuật toán của mình và có tỷ lệ tấn công 99,4%!

Lawrence lawrence = new Lawrence();

System.out.println(lawrence.getSyllable("hyphenation"));
System.out.println(lawrence.getSyllable("computer"));

Đầu ra:

4
3


Xem đánh dấu cú pháp . Có một nút trợ giúp (dấu hỏi) trong trình soạn thảo SO sẽ đưa bạn đến trang được liên kết.
IKavanagh

0

Sau khi thực hiện rất nhiều thử nghiệm và thử các gói gạch nối, tôi đã tự viết dựa trên một số ví dụ. Tôi cũng đã thử pyhyphenpyphencác gói có giao diện với từ điển gạch nối, nhưng chúng tạo ra số lượng âm tiết sai trong nhiều trường hợp. Các nltkgói chỉ đơn giản là quá chậm đối với trường hợp sử dụng này.

Việc triển khai trong Python của tôi là một phần của lớp tôi đã viết và thói quen đếm âm tiết được dán bên dưới. Nó ước tính quá mức số lượng âm tiết một chút vì tôi vẫn chưa tìm thấy một cách tốt để giải thích cho các kết thúc từ im lặng.

Hàm trả về tỷ lệ âm tiết trên mỗi từ khi nó được sử dụng cho điểm dễ đọc Flesch-Kincaid. Con số không nhất thiết phải chính xác, chỉ đủ gần để ước tính.

Trên CPU i7 thế hệ thứ 7 của tôi, chức năng này mất 1,1-1,2 mili giây cho văn bản mẫu 759 từ.

def _countSyllablesEN(self, theText):

    cleanText = ""
    for ch in theText:
        if ch in "abcdefghijklmnopqrstuvwxyz'’":
            cleanText += ch
        else:
            cleanText += " "

    asVow    = "aeiouy'’"
    dExep    = ("ei","ie","ua","ia","eo")
    theWords = cleanText.lower().split()
    allSylls = 0
    for inWord in theWords:
        nChar  = len(inWord)
        nSyll  = 0
        wasVow = False
        wasY   = False
        if nChar == 0:
            continue
        if inWord[0] in asVow:
            nSyll += 1
            wasVow = True
            wasY   = inWord[0] == "y"
        for c in range(1,nChar):
            isVow  = False
            if inWord[c] in asVow:
                nSyll += 1
                isVow = True
            if isVow and wasVow:
                nSyll -= 1
            if isVow and wasY:
                nSyll -= 1
            if inWord[c:c+2] in dExep:
                nSyll += 1
            wasVow = isVow
            wasY   = inWord[c] == "y"
        if inWord.endswith(("e")):
            nSyll -= 1
        if inWord.endswith(("le","ea","io")):
            nSyll += 1
        if nSyll < 1:
            nSyll = 1
        # print("%-15s: %d" % (inWord,nSyll))
        allSylls += nSyll

    return allSylls/len(theWords)

-1

Tôi đã sử dụng jsoup để làm điều này một lần. Đây là một trình phân tích cú pháp âm tiết mẫu:

public String[] syllables(String text){
        String url = "https://www.merriam-webster.com/dictionary/" + text;
        String relHref;
        try{
            Document doc = Jsoup.connect(url).get();
            Element link = doc.getElementsByClass("word-syllables").first();
            if(link == null){return new String[]{text};}
            relHref = link.html(); 
        }catch(IOException e){
            relHref = text;
        }
        String[] syl = relHref.split("·");
        return syl;
    }

Làm thế nào đó là một trình phân tích cú pháp âm tiết chung? Có vẻ như mã này chỉ tìm kiếm các âm tiết trong từ điển
Nico Haase
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.