All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.steveash.jg2p.seqvow.PartialPhones Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Steve Ash
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.steveash.jg2p.seqvow;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;

import com.github.steveash.jg2p.phoseq.Phonemes;
import com.github.steveash.jg2p.util.GramBuilder;

import java.util.Iterator;
import java.util.List;

import static com.github.steveash.jg2p.util.GramBuilder.SPLITTER;
import static org.apache.commons.lang3.StringUtils.isBlank;

/**
 * We distinguish between: (1) phonemes, (2) phoneme classes, (3) partialPhones (1) are actual phonemes (40 as used in
 * CMU Dict) (2) are the classes of phonemes (as in Phonemes.PhoneClasses) (3) are the symbols referring to the subset
 * of PhonemeClasses that we are going to re-tagg
 *
 * Graphones generally refer to the bijection of grouped graphemes to grouped final phonemes (i.e. the aligned X -> Y).
 * The phones here pass through a few layers and thus we need to distinguish between the list of symbols that are input
 * to the retagging process and the output of the retagging process.  Input to retagging will be called "Partial Tags"
 * and will be made up of Phonemes + partialPhones.  Output of retagging will be called "Final Tags"
 *
 * @author Steve Ash
 */
public class PartialPhones {

  public static final ImmutableSet partialPhones = ImmutableSet.of(
      "!" + Phonemes.PhoneClass.M.code().toUpperCase(),
      "!" + Phonemes.PhoneClass.D.code().toUpperCase()
  );

  public static final ImmutableSet partialPhoneClasses = ImmutableSet.of(
      Phonemes.PhoneClass.M.code().toUpperCase(),
      Phonemes.PhoneClass.D.code().toUpperCase()
  );

  public static String partialPhoneForPhone(String phone) {
    Preconditions.checkArgument(isPhoneEligibleAsPartial(phone));
    return "!" + Phonemes.getClassForPhone(phone);
  }

  public static boolean doesGramContainPartial(String gram) {
    if (GramBuilder.isUnaryGram(gram)) {
      return isPartialPhone(gram);
    }
    for (String s : SPLITTER.split(gram)) {
      if (isPartialPhone(s)) {
        return true;
      }
    }
    return false;
  }

  /**
   * For the given gram made up of partialPhones and real phones -- return a gram with only the partial phones; strip
   * all symbols that are NOT partialPhones
   */
  public static String extractPartialPhoneGramFromGram(String gram) {
    if (isBlank(gram)) {
      return "";
    }
    if (GramBuilder.isUnaryGram(gram)) {
      if (isPartialPhone(gram)) {
        return gram;
      } else {
        return "";
      }
    }
    GramBuilder sb = new GramBuilder();

    for (String s : SPLITTER.split(gram)) {
      // skip any non vowels in this graphone phone
      if (isPartialPhone(s)) {
        sb.append(s);
      }
    }
    return sb.make();
  }

  /**
   * For the given gram made up of only real phones, returns the gram of real phones such that each included phone is
   * eligible as a partialPhone.  This is useful for making the target field of training examples
   */
  public static String extractEligibleGramFromPhoneGram(String phoneGram) {
    if (isBlank(phoneGram)) {
      return "";
    }
    if (GramBuilder.isUnaryGram(phoneGram)) {
      if (isPhoneEligibleAsPartial(phoneGram)) {
        return phoneGram;
      } else {
        return "";
      }
    }
    GramBuilder sb = new GramBuilder();

    for (String s : SPLITTER.split(phoneGram)) {
      if (isPhoneEligibleAsPartial(s)) {
        sb.append(s);
      }
    }
    return sb.make();
  }

  /**
   * Given a gram made up of partialPhones + finalPhones (i.e. the input to retagging) return the gram containing only
   * real phones; strip all of the partial phones from the gram
   */
  public static String extractNotPartialPhoneGramFromGram(String gram) {
    if (isBlank(gram)) {
      return "";
    }
    if (GramBuilder.isUnaryGram(gram)) {
      if (isPartialPhone(gram)) {
        return "";
      } else {
        return gram;
      }
    }
    GramBuilder sb = new GramBuilder();
    for (String s : SPLITTER.split(gram)) {
      if (!isPartialPhone(s)) {
        sb.append(s);
      }
    }
    return sb.make();
  }

  public static boolean isPartialPhone(String maybePartial) {
    return partialPhones.contains(maybePartial.toUpperCase());
  }

  public static boolean isPhoneEligibleAsPartial(String phone) {
    return partialPhoneClasses.contains(Phonemes.getClassForPhone(phone));
  }

  /**
   * Takes a partial gram containing one or more partialPhones and takes the output gram of final real phones from the
   * retagging process and returns a new grap with the retagged results applied
   */
  public static String partialGramUpdatedWithPredictedPhoneGram(String partialGram, String predictedPhoneGram) {
    // quick case first
    if (GramBuilder.isUnaryGram(partialGram) && GramBuilder.isUnaryGram(predictedPhoneGram)) {
      Preconditions.checkArgument(isPartialPhone(partialGram));
      Preconditions.checkArgument(Phonemes.isVowel(predictedPhoneGram));
      return predictedPhoneGram;
    }
    // if the seq vow model predicts more vowels than needed just leave off the extras; if too few, then just omit them
    Iterator predicts = SPLITTER.split(predictedPhoneGram).iterator();
    GramBuilder sb = new GramBuilder();
    for (String s : SPLITTER.split(partialGram)) {
      if (isPartialPhone(s)) {
        if (predicts.hasNext()) {
          sb.append(predicts.next());
        }
      } else {
        sb.append(s);
      }
    }
    return sb.make();
  }

  // these are final graphones for training
  public static boolean doesAnyGramContainPhoneEligibleAsPartial(List finalPhoneGrams) {
    for (String gram : finalPhoneGrams) {
      if (doesGramContainPhoneEligibleAsPartial(gram)) {
        return true;
      }
    }
    return false;
  }

  protected static boolean doesGramContainPhoneEligibleAsPartial(String gram) {
    if (isEps(gram)) {
      return false;
    }
    if (!GramBuilder.isUnaryGram(gram)) {
      for (String s : GramBuilder.SPLITTER.split(gram)) {
        if (isPhoneEligibleAsPartial(s)) {
          return true;
        }
      }
    } else {
      if (isPhoneEligibleAsPartial(gram)) {
        return true;
      }
    }
    return false;
  }

  // these are final graphones for training
  public static boolean doesAnyGramContainPartialPhone(List partialPhoneGrams) {
    for (String gram : partialPhoneGrams) {
      if (doesGramContainPartial(gram)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Take a phoneGram and return the same gram with every eligible phone replaced with its corresponding partialPhone
   * tag.  This is used to create training data from aligned dictionary entries
   */
  public static String phoneGramToPartialPhoneGram(String finalPhoneGram) {
    if (isEps(finalPhoneGram)) {
      return "";
    }

    if (GramBuilder.isUnaryGram(finalPhoneGram)) {
      if (isPhoneEligibleAsPartial(finalPhoneGram)) {
        return partialPhoneForPhone(finalPhoneGram);
      } else {
        return finalPhoneGram;
      }
    }
    // its a multi-gram graphone
    GramBuilder sb = new GramBuilder();
    for (String s : GramBuilder.SPLITTER.split(finalPhoneGram)) {
      if (isPhoneEligibleAsPartial(s)) {
        sb.append(partialPhoneForPhone(s));
      } else {
        sb.append(s);
      }
    }
    return sb.make();
  }

  protected static boolean isEps(String finalPhoneGram) {
    return isBlank(finalPhoneGram) || finalPhoneGram.equalsIgnoreCase(GramBuilder.EPS);
  }

  public static List phoneGramsToPartialPhoneGrams(List finalPhoneGrams) {
    return phoneGramsToPartialPhoneGrams(finalPhoneGrams, Integer.MAX_VALUE);
  }

  public static List phoneGramsToPartialPhoneGrams(List finalPhoneGrams, int maxToChange) {
    List output = Lists.newArrayListWithCapacity(finalPhoneGrams.size());
    int changed = 0;
    for (String phoneGram : finalPhoneGrams) {
      if (isEps(phoneGram)) {
        output.add("");
        continue;
      }
      String updated = phoneGramToPartialPhoneGram(phoneGram);
      if (!updated.equals(phoneGram) && changed < maxToChange) {
        output.add(updated);
        changed += 1;
        continue;
      }
      output.add(phoneGram);
    }
    return output;
  }

  public static String extractVowelOrPartialFromGram(String gram) {
    if (isBlank(gram)) {
      return "";
    }
    if (GramBuilder.isUnaryGram(gram)) {
      if (isPartialPhone(gram) || Phonemes.isVowel(gram)) {
        return gram;
      } else {
        return "";
      }
    }
    GramBuilder sb = new GramBuilder();
    for (String s : GramBuilder.SPLITTER.split(gram)) {
      if (isPartialPhone(s) || Phonemes.isVowel(s)) {
        sb.append(s);
      }
    }
    return sb.make();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy