python-wordsegment

repository·master·Indexed 18 days ago

https://github.com/grantjenks/python-wordsegment

A pure-Python module for English word segmentation based on a trillion-word corpus. It uses unigram and bigram frequency data to divide unsegmented strings into constituent words. Key features include the segment() and isegment() functions for text division, a clean() utility for preprocessing, and support for custom corpora and optimized binary data loading to improve import speeds.

Tokens
4K
Snippets
19
Records
23
Agent score
62%

What's inside python-wordsegment

  1. Quickstart with python-wordsegment

    master

    To use python-wordsegment for splitting unsegmented text into words, import the wordsegment module and use the segment function. This function takes a string and returns a list of words.

    from wordsegment import load, segment
    load()
    segment('thisisatest')
    # ['this', 'is', 'a', 'test']
  2. Optimize dictionary loading speed for wordsegment data

    master

    If you are experiencing slow module import times due to the time taken to construct the word/count dictionaries, you can implement a faster loading strategy by separating the keys (words) and values (counts) into different files.

    Standard text-based loading is relatively slow because it requires splitting strings by tabs and converting string counts to floats. A faster approach involves:

    1. Storing words in a plain text file (one per line).
    2. Storing counts in a binary file using Python's array module (double-precision floats).
    3. Using izip (or zip in Python 3) to combine them into a dictionary.

    This method can reduce loading time by approximately 60% compared to parsing a single tab-separated text file.

    from itertools import izip as zip
    from array import array
    
    # Fast loading implementation
    with open('words.txt', 'rb') as lines, open('counts.bin', 'rb') as counts:
        words = lines.read().split('\n')
        values = array('d')
        # Note: Replace 333333 with your actual count of items
        values.fromfile(counts, 333333)
        result_dict = dict(zip(words, values))
  3. Segment text into words using the Python API

    master

    To divide a continuous string into a list of its constituent words, use load() to initialize the data and segment() to perform the segmentation. Note that load() should only be called once in your application lifecycle.

    Before segmenting, it is recommended to use clean() to transform input text into a canonical form (lowercased and stripped of punctuation), as the underlying corpus is also lowercased and punctuation-free.

    from wordsegment import load, segment, clean
    
    # Initialize the unigram and bigram data
    load()
    
    # Clean text to remove punctuation and lowercase (optional but recommended)
    text = 'She said, "Python rocks!"'
    cleaned = clean(text)
    print(cleaned)  # 'shesaidpythonrocks'
    
    # Segment the string
    result = segment(cleaned)
    print(result)  # ['she', 'said', 'python', 'rocks']
  4. Run WordSegment as a server process

    master

    To use WordSegment as a server process, use Python's -u option for unbuffered output or set the PYTHONUNBUFFERED=1 environment variable. This ensures that segmented text is immediately available in stdout.

    import subprocess as sp
    
    # Start the wordsegment module as a subprocess
    wordsegment = sp.Popen(
        ['python', '-um', 'wordsegment'],
        stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.STDOUT
    )
    
    # Write input to stdin
    wordsegment.stdin.write('thisisatest\n')
    
    # Read segmented output from stdout
    print(wordsegment.stdout.readline().decode().strip())
    # Output: 'this is a test'
    
    wordsegment.stdin.close()
    wordsegment.wait()
  5. Understand the data format of unigram and bigram files

    master

    The wordsegment module relies on two text files to store unigram and bigram count data. These files use a specific format to allow for efficient parsing:

    • Records: Each record is separated by a newline character (\n).
    • Fields: Within each record, fields are separated by tab characters (\t).

    When the wordsegment module is imported, it reads these files from disk to construct a Python dict that maps word to count pairs.

    with open('../wordsegment_data/unigrams.txt', 'r') as reader:
        print repr(reader.readline())
  6. Optimize dictionary loading from unigram/bigram files

    master

    If you are experiencing slow import times due to the module loading large unigram/bigram text files, you can optimize the loading process by separating the keys (words) and values (counts) into two distinct files: a text file for words and a binary file for counts.

    1. Convert text files to optimized formats

    Convert the standard tab-separated text files into a newline-separated ASCII file for words and a binary file for double-precision floating-point counts using the array module.

    2. Fast loading implementation

    Use str.split for the words and array.fromfile for the binary counts to achieve significantly faster loading compared to standard line-by-line parsing.

    Note: This approach changes the data format, making the files harder to inspect with standard tools like grep.

    from itertools import izip as zip
    from array import array
    
    # Optimized loading pattern
    with open('words.txt', 'rb') as lines, open('counts.bin', 'rb') as counts:
        words = lines.read().split('\n')
        values = array('d')
        values.fromfile(counts, 333333)  # Replace 333333 with actual count
        result_dict = dict(zip(words, values))
  7. Use a custom corpus for word segmentation

    master

    To use a custom corpus instead of the default one, you must manually update three components of the wordsegment module: unigram_counts, bigram_counts, and the clean function.

    1. Update Unigram Counts: Replace wordsegment.unigram_counts with a collections.Counter object mapping individual words (unigrams) to their frequency in your corpus.
    2. Update Bigram Counts: Replace wordsegment.bigram_counts with a collections.Counter object mapping word pairs (bigrams) to their frequency.
    3. Update the clean function: By default, wordsegment.clean lowercases input and removes punctuation. If your corpus relies on specific casing or formatting, you may need to replace wordsegment.clean with a custom function (e.g., an identity function) to prevent the library from sanitizing your input.
    4. Update TOTAL (Optional): If segmentation quality is poor after updating counts, update wordsegment.TOTAL to be the sum of all values in your new unigram_counts dictionary.
    import wordsegment
    from collections import Counter
    import re
    
    # 1. Prepare your text and tokenizer
    text = "Your custom corpus text here"
    def tokenize(text):
        pattern = re.compile('[a-zA-Z]+')
        return (match.group(0) for match in pattern.finditer(text))
    
    # 2. Update unigram counts
    wordsegment.unigram_counts = Counter(tokenize(text))
    
    # 3. Update bigram counts
    def pairs(iterable):
        iterator = iter(iterable)
        values = [next(iterator)]
        for value in iterator:
            values.append(value)
            yield ' '.join(values)
            del values[0]
    
    wordsegment.bigram_counts = Counter(pairs(tokenize(text)))
    
    # 4. Update clean function if needed (e.g., to preserve case)
    wordsegment.clean = lambda value: value
    
    # 5. Update TOTAL if necessary
    wordsegment.TOTAL = float(sum(wordsegment.unigram_counts.values()))
    
    # Use the new segmentation
    print(wordsegment.segment('yourinput'))
  8. Convert text-based wordsegment data to optimized binary format

    master

    To use the optimized loading method described above, you must first convert the existing unigrams.txt (or bigrams) into a split format consisting of words.txt and counts.bin.

    # Conversion script
    with open('../wordsegment_data/unigrams.txt') as reader:
        pairs = [line.split('\t') for line in reader]
        words = [pair[0] for pair in pairs]
        counts = [float(pair[1]) for pair in pairs]
    
        # Write words to text file
        with open('words.txt', 'wb') as writer:
            writer.write('\n'.join(words))
    
        # Write counts to binary file
        from array import array
        values = array('d')
        values.fromlist(counts)
        with open('counts.bin', 'wb') as writer:
            values.tofile(writer)
  9. Explore unigram and bigram data counts

    master

    You can inspect the underlying frequency data directly. ws.UNIGRAMS and ws.BIGRAMS are Python dictionaries mapping words/phrases to their respective counts.

    Note: Some bigrams begin with <s> to indicate the start of a bigram.

    import wordsegment as ws
    ws.load()
    
    # Access unigram counts
    print(ws.UNIGRAMS['the'])
    
    # Access bigram counts (bigrams are joined by a space)
    print(ws.BIGRAMS['<s> where'])