Showing posts with label UIMA. Show all posts
Showing posts with label UIMA. Show all posts

Running Stanford Sentiment Analysis in UIMA


The Goal
In previous post, we introduced how to run Stanford NER(Named Entity Recognition) in UIMA, now we are integrating Stanford Sentiment Analysis in UIMA.

StanfordNLPAnnotator
Feature Structure: org.apache.uima.stanfordnlp.input:action
We use StanfordNLPAnnotator as the gateway or facade: client uses org.apache.uima.stanfordnlp.input:action to specify what to extract: action=ner - to run named entity extraction or action=sentimet to run sentiment analysis.

The feature org.apache.uima.stanfordnlp.output:type specifies the sentiment of the whole article: very negative, negative, neutral, positive or very positive.

The configuration parameter: SentiwordnetFile which specifies the path of sentiwordnet file.

How it Works
First it ignore sentence which doesn't contain opinionated  word. It uses Sentiwordnet to check whether this sentence contains non-neutral adjective.

The it calls Stanford NLP Sentiment Analysis tool to process the text.
Stanford NLP Sentiment Analysis has two model files: edu/stanford/nlp/models/sentiment/sentiment.ser.gz, which maps sentimentto 5 classes: very negative, negative, neutral, positive or very positive; edu/stanford/nlp/models/sentiment/sentiment.binary.ser.gz which maps sentiment to 2 classes: negative or positive.

We use edu/stanford/nlp/models/sentiment/sentiment.ser.gz, but seems sometimes it inclines to mistakenly map non-negative text to negative.

For example, it will map the following sentence to negative, but the binary mode will correctly map it to positive.
I was able to stream video and surf the internet for well over 7 hours without any hiccups .

So to fix this, when the 5 classes mode(sentiment.ser.gz) maps one sentence to negative, we will run the binay mode to recheck it, if the binary mode agrees(also report negative) then no change, otherwise change it to positive.

We calculate the score of all sentence, and map the average score to the 5 classes. We give negative sentence a smaller value as we don't trust it. 
package org.lifelongprogrammer.nlp;
public class StanfordNLPAnnotator extends JCasAnnotator_ImplBase {
	public static final String STANFORDNLP_ACTION_SENTIMENT = "sentiment";
	public static final String TYPE_STANDFORDNLP_OUTPUT = "org.apache.uima.standfordnlp.output";
	public static final String FS_STANDFORDNLP_OUTPUT_TYPE = TYPE_STANDFORDNLP_OUTPUT
			+ ":type";
	public static final String TYPE_STANFORDNLP_INPUT = "org.apache.uima.stanfordnlp.input";
	public static final String FS_STANFORDNLP_INPUT_ACTION = TYPE_STANFORDNLP_INPUT
			+ ":action";

	private static Splitter splitter = Splitter.on(",").trimResults()
			.omitEmptyStrings();
	public static final String SENTIWORDNET_FILE_PARAM = "SentiwordnetFile";

	private StanfordCoreNLP sentiment5ClassesPipeline,
			sentiment2ClassesPipeline;
	private SWN3 sentiwordnet;
	private ExecutorService threadpool;
	private Logger logger;
	public void initialize(UimaContext aContext)
			throws ResourceInitializationException {
		super.initialize(aContext);
		this.logger = getContext().getLogger();
		reconfigure();
	}

	public void reconfigure() throws ResourceInitializationException {
		try {
			threadpool = Executors.newCachedThreadPool();
			String dataPath = getContext().getDataPath();
			Properties props = new Properties();
			props.setProperty("annotators",
					"tokenize, ssplit, parse, sentiment");
			props.put("sentiment.model",
					"edu/stanford/nlp/models/sentiment/sentiment.ser.gz");

			sentiment5ClassesPipeline = new StanfordCoreNLP(props);
			props.put("sentiment.model",
					"edu/stanford/nlp/models/sentiment/sentiment.binary.ser.gz");
			sentiment2ClassesPipeline = new StanfordCoreNLP(props);

			String sentiwordnetFile = (String) getContext()
					.getConfigParameterValue(SENTIWORDNET_FILE_PARAM);
			sentiwordnet = new SWN3(
					new File(dataPath, sentiwordnetFile).getPath());
		} catch (Exception e) {
			logger.log(Level.SEVERE, e.getMessage());
			throw new ResourceInitializationException(e);
		}
	}
	public void process(JCas jcas) throws AnalysisEngineProcessException {
		CAS cas = jcas.getCas();
		ArrayList<String> action = getAction(cas);
		if (action.contains(STANFORDNLP_ACTION_SENTIMENT)) {
			Future<Void> future = threadpool.submit(new Callable<Void>() {
				@Override
				public Void call() throws Exception {
					checkSentiment(cas);
					return null;
				}
			});
			futures.add(future);
		}
		for (Future<Void> future : futures) {
			try {
				future.get();
			} catch (InterruptedException | ExecutionException e) {
				throw new AnalysisEngineProcessException(e);
			}
		}
		logger.log(Level.FINE, "StanfordNERAnnotator done.");
	}

  
	private void checkSentiment(CAS cas) {
		String sentimenTetx = getSentimentSentence(cas.getDocumentText())
				.toString();

		Annotation annotation = sentiment5ClassesPipeline.process(sentimenTetx);
		TypeSystem ts = cas.getTypeSystem();
		Type dyOutputType = ts.getType(TYPE_STANDFORDNLP_OUTPUT);
		org.apache.uima.cas.Feature dyOutputTypeFt = ts
				.getFeatureByFullName(FS_STANDFORDNLP_OUTPUT_TYPE);
        
		SentimentAccumulator accumulator = new SentimentAccumulator(false);
		for (CoreMap sentenceCore : annotation
				.get(CoreAnnotations.SentencesAnnotation.class)) {
			Tree tree = sentenceCore
					.get(SentimentCoreAnnotations.AnnotatedTree.class);
			int predictedClass = RNNCoreAnnotations.getPredictedClass(tree);
			String sentence = sentenceCore.toString();
			if (predictedClass == 1) {
				int old = predictedClass;
				predictedClass = checkNegative(sentence);
				System.out.println("Sentiment changed from " + old + " to "
						+ predictedClass + " String: " + sentence);
			} 
			accumulator.accumulate(predictedClass, sentence.length());
		}
		AnnotationFS dyAnnFS = cas.createAnnotation(dyOutputType, 0, 0);
		dyAnnFS.setStringValue(dyOutputTypeFt, accumulator.getResult());
		cas.getIndexRepository().addFS(dyAnnFS);
	}
  
	private ArrayList<String> getAction(CAS cas) {
		TypeSystem ts = cas.getTypeSystem();
		Type dyInputType = ts.getType(TYPE_STANFORDNLP_INPUT);
		org.apache.uima.cas.Feature dyInputTypesFt = ts
				.getFeatureByFullName(FS_STANFORDNLP_INPUT_ACTION);
		FSIterator<?> dyIt = cas.getAnnotationIndex(dyInputType).iterator();
		String action = "";
		while (dyIt.hasNext()) {
			// TODO this is kind of weird
			AnnotationFS afs = (AnnotationFS) dyIt.next();
			String str = afs.getStringValue(dyInputTypesFt);
			if (str != null) {
				action = str;
			}
		}
		return Lists.newArrayList(splitter.split(action));
	}
  
  

	class SentimentAccumulator {
		private double totalScore;
		private int sentCount;
		public SentimentAccumulator() {}
		public void accumulate(int type, int sentLen) {
		  clac5ClassModel(type);
		}
		private void clac5ClassModel(int type) {
			++sentCount;
			// very negative
			switch (type) {
			case 0:
				totalScore += -5;
				break;
			case 1:
				totalScore += -1; // give smaller value
				break;
			case 2:
				totalScore += 0;
				break;
			case 3:
				totalScore += 2;
				break;
			case 4:
				totalScore += 5;
				break;
			default:
				// ignore this
				logger.log(Level.SEVERE, "unkown type:" + type);
				--sentCount;
			}
		}

		public String getResult() {
      double avgScore = (double) totalScore / sentCount;
      logger.log(Level.INFO, "avgScore: " + avgScore
          + ", totalScore: " + totalScore + ", sentCount: "
          + sentCount);

      if (avgScore > 2) {
        return "very positove";
      } else if (avgScore > 0.5) {
        return "positove";
        // [-0.5 TO 0]: neutral
      } else if (avgScore > -0.5) {
        return "neutral";
      } else if (avgScore > -2) {
        return "negative";
      } else {
        return "very negative";
      }
		}
	}

	public StringBuilder getSentimentSentence(String text) {
		DocumentPreprocessor dp = new DocumentPreprocessor(new StringReader(
				text));
		// List<String> sentenceList = new LinkedList<String>();
		StringBuilder sentenceList = new StringBuilder();
		Iterator<List<HasWord>> it = dp.iterator();
		while (it.hasNext()) {
			StringBuilder sentenceSb = new StringBuilder();
			List<HasWord> sentence = it.next();

			boolean hasFeeling = false;
			Iterator<HasWord> inner = sentence.iterator();
			while (inner.hasNext()) {
				HasWord token = inner.next();
				sentenceSb.append(token.word());

				if (inner.hasNext()) {
					sentenceSb.append(" ");
				}
				String feeling = sentiwordnet.extractFelling(token.word(), "a");
				if (!"neutral".equals(feeling)) {
					hasFeeling = true;
					System.out.println(feeling + ":" + token);
				}
			}
			if (hasFeeling) {
				sentenceList.append(sentenceSb.toString());
			}
		}
		return sentenceList;
	}

	private int checkNegative(String sentence) {
		Annotation annotation = sentiment2ClassesPipeline.process(sentence);

		for (CoreMap sentenceCore : annotation
				.get(CoreAnnotations.SentencesAnnotation.class)) {

			Tree tree = sentenceCore
					.get(SentimentCoreAnnotations.AnnotatedTree.class);
			int newPredict = RNNCoreAnnotations.getPredictedClass(tree);
			// if binary checker still returns negative then use negative
			if (newPredict == 0) {
				return 1;
			} else {
				return 3;
			}
		}
		return 1;
	}  
}
Descriptor File: StanfordNLPAnnotator.xml
We define uima types: org.apache.uima.stanfordnlp.input and org.apache.uima.stanfordnlp.output, and the configuration parameter: SentiwordnetFile.
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
	<frameworkImplementation>org.apache.uima.java</frameworkImplementation>
	<primitive>true</primitive>
	<annotatorImplementationName>org.lifelongprogrammer.nlp.StanfordNLPAnnotator
	</annotatorImplementationName>
	<analysisEngineMetaData>
		<name>StanfordNLPAnnotatorAE</name>
		<description>StanfordNLPAnnotator Wrapper.</description>
		<version>1.0</version>
		<vendor>LifeLong Programmer, Inc.</vendor>
		<configurationParameters>
			<configurationParameter>
				<name>SentiwordnetFile</name>
				<description>Filename of the sentiwordnet file.</description>
				<type>String</type>
				<multiValued>false</multiValued>
				<mandatory>true</mandatory>
			</configurationParameter>
		</configurationParameters>
		<configurationParameterSettings>
			<nameValuePair>
				<name>SentiwordnetFile</name>
				<value>
					<string>dicts\SentiWordNet_3.0.0_20130122.txt</string>
				</value>
			</nameValuePair>
		</configurationParameterSettings>
		<typeSystemDescription>
			<typeDescription>
				<name>org.apache.uima.stanfordnlp.input</name>
				<description />
				<supertypeName>uima.tcas.Annotation</supertypeName>
				<features>
					<featureDescription>
						<name>action</name>
						<description />
						<rangeTypeName>uima.cas.String</rangeTypeName>
					</featureDescription>
				</features>
			</typeDescription>
			<typeDescription>
				<name>org.apache.uima.standfordnlp.output</name>
				<description />
				<supertypeName>uima.tcas.Annotation</supertypeName>
				<features>
					<featureDescription>
						<name>type</name>
						<description />
						<rangeTypeName>uima.cas.String</rangeTypeName>
					</featureDescription>
				</features>
			</typeDescription>
		</typeSystemDescription>
</analysisEngineDescription>
Annotator Test case
Check the previous post about how use sujitpal's UimaUtils.java to test the StanfordNLPAnnotator.

Running Stanford Named Entity Recognition in UIMA


The Goal
To improve our text analytic project, after integrated OpenNLP with UIMA, we are trying to integrate StanfordNLP NER(Named Entity Recognition) into UIMA.

StanfordNLPAnnotator
Feature Structure: org.apache.uima.stanfordnlp.input:action
We use StanfordNLPAnnotator as the gateway or facade: client uses org.apache.uima.stanfordnlp.input:action to specify what to extract: action=ner - to run named entity extraction or action=sentimet to run sentiment analysis.

We use dynamic output entity: org.apache.uima.stanfordnlp.output, its type specifies whether it's person or organization or etc.

The configuration parameter: ClassifierFile which specifies the  mode files NER uses.

package org.lifelongprogrammer.nlp;
public class StanfordNLPAnnotator extends JCasAnnotator_ImplBase {
 public static final String STANFORDNLP_ACTION_NER = "ner";
 public static final String TYPE_STANDFORDNLP_OUTPUT = "org.apache.uima.standfordnlp.output";
 public static final String FS_STANDFORDNLP_OUTPUT_TYPE = TYPE_STANDFORDNLP_OUTPUT
   + ":type";
 public static final String TYPE_STANFORDNLP_INPUT = "org.apache.uima.stanfordnlp.input";
 public static final String FS_STANFORDNLP_INPUT_ACTION = TYPE_STANFORDNLP_INPUT
   + ":action";

 // http://nlp.stanford.edu/software/CRF-NER.shtml
 private static final Set<String> NER_TYPES = new HashSet<String>(
   Arrays.asList("PERSON", "ORGANIZATION", "LOCATION", "MISC", "TIME",
     "MONEY", "PERCENT", "DATE"));
          
 private static Splitter splitter = Splitter.on(",").trimResults()
   .omitEmptyStrings();
 public static final String CLASSIFIER_FILE_PARAM = "ClassifierFile";
 private CRFClassifier<CoreLabel> crf;
 private ExecutorService threadpool;
 private Logger logger;

 public void initialize(UimaContext aContext)
   throws ResourceInitializationException {
  super.initialize(aContext);
  this.logger = getContext().getLogger();
  reconfigure();
 }
 public void reconfigure() throws ResourceInitializationException {
  try {
   threadpool = Executors.newCachedThreadPool();
   String dataPath = getContext().getDataPath();

   String classifierFile = (String) getContext()
     .getConfigParameterValue(CLASSIFIER_FILE_PARAM);
   System.out.println(classifierFile);
   crf = CRFClassifier
     .getClassifier(new File(dataPath, classifierFile));
  } catch (Exception e) {
   logger.log(Level.SEVERE, e.getMessage());
   throw new ResourceInitializationException(e);
  }
 }
  
 public void process(JCas jcas) throws AnalysisEngineProcessException {
  CAS cas = jcas.getCas();
  ArrayList<String> action = getAction(cas);
  List<Future<Void>> futures = new ArrayList<Future<Void>>();
  if (action.contains(STANFORDNLP_ACTION_NER)) {
   Future<Void> future = threadpool.submit(new Callable<Void>() {
    @Override
    public Void call() throws Exception {
     getNer(jcas);
     return null;
    }
   });

   futures.add(future);
  }
    //...
  for (Future<Void> future : futures) {
   try {
    future.get();
   } catch (InterruptedException | ExecutionException e) {
    throw new AnalysisEngineProcessException(e);
   }
  }
  logger.log(Level.FINE, "StanfordNERAnnotator done.");
 }
  
 private ArrayList<String> getAction(CAS cas) {
  TypeSystem ts = cas.getTypeSystem();
  Type dyInputType = ts.getType(TYPE_STANFORDNLP_INPUT);
  org.apache.uima.cas.Feature dyInputTypesFt = ts
    .getFeatureByFullName(FS_STANFORDNLP_INPUT_ACTION);

  FSIterator<?> dyIt = cas.getAnnotationIndex(dyInputType).iterator();
  String action = "";
  while (dyIt.hasNext()) {
   // TODO this is kind of weird
   AnnotationFS afs = (AnnotationFS) dyIt.next();
   String str = afs.getStringValue(dyInputTypesFt);
   if (str != null) {
    action = str;
   }
  }
  return Lists.newArrayList(splitter.split(action));
 }
  
 private void getNer(JCas jcas) {
    CAS cas=jcas.getCas();
  String docText = jcas.getDocumentText();
  List<List<CoreLabel>> classify = crf.classify(docText);

  MatchedNER preNER = null;

  TypeSystem ts = jcas.getTypeSystem();
  Type dyOutputType = ts.getType(TYPE_STANDFORDNLP_OUTPUT);
  org.apache.uima.cas.Feature dyOutputTypeFt = ts
    .getFeatureByFullName(FS_STANDFORDNLP_OUTPUT_TYPE);

  // merge co-located same entity
  for (List<CoreLabel> coreLabels : classify) {
   for (CoreLabel coreLabel : coreLabels) {
    String category = coreLabel
      .get(CoreAnnotations.AnswerAnnotation.class);
    if (NER_TYPES.contains(category)) {
     if (preNER == null) {
      preNER = new MatchedNER(category,
        coreLabel.beginPosition(),
        coreLabel.endPosition());
     } else if (category.equals(preNER.getCategory())) {
      preNER = new MatchedNER(category,
        preNER.getEntityBegin(),
        coreLabel.endPosition());
     } else {
      // add preNER
      addNER(preNER, cas, dyOutputType, dyOutputTypeFt);
      preNER = new MatchedNER(category,
        coreLabel.beginPosition(),
        coreLabel.endPosition());
     }
    } else {
     if (preNER != null) {
      addNER(preNER, cas, dyOutputType, dyOutputTypeFt);
      preNER = null;
     }

    }
   }
  }
  if (preNER != null) {
   addNER(preNER, cas, dyOutputType, dyOutputTypeFt);
  }
 }
 private void addNER(MatchedNER preNER, CAS cas, Type dyOutputType,
   org.apache.uima.cas.Feature dyOutputTypeFt) {
  AnnotationFS dyAnnFS = cas.createAnnotation(dyOutputType,
    preNER.getEntityBegin(), preNER.getEntityEnd());
  dyAnnFS.setStringValue(dyOutputTypeFt, preNER.getCategory()
    .toLowerCase());
  cas.getIndexRepository().addFS(dyAnnFS);
 }

 class MatchedNER {
  private String cat;
  private int entityBegin, entityEnd;

  public MatchedNER(String cat, int entityBegin, int entityEnd) {
   this.cat = cat;
   this.entityBegin = entityBegin;
   this.entityEnd = entityEnd;
  }
 }
}
Descriptor File: StanfordNLPAnnotator.xml
We define uima types: org.apache.uima.stanfordnlp.input and org.apache.uima.stanfordnlp.output, and the configuration parameter: ClassifierFile.
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
 <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
 <primitive>true</primitive>
 <annotatorImplementationName>org.lifelongprogrammer.nlp.StanfordNLPAnnotator
 </annotatorImplementationName>
 <analysisEngineMetaData>
  <name>StanfordNLPAnnotatorAE</name>
  <description>StanfordNLPAnnotator Wrapper.</description>
  <version>1.0</version>
  <vendor>LifeLong Programmer, Inc.</vendor>
  <configurationParameters>
   <configurationParameter>
    <name>ClassifierFile</name>
    <description>Filename of the classifier file.</description>
    <type>String</type>
    <multiValued>false</multiValued>
    <mandatory>true</mandatory>
   </configurationParameter>
  </configurationParameters>
  <configurationParameterSettings>
   <nameValuePair>
    <name>ClassifierFile</name>
    <value>
     <!-- relative to pear resource file -->
     <string>models\classifiers\english.muc.7class.distsim.crf.ser.gz
     </string>
    </value>
   </nameValuePair>
  </configurationParameterSettings>
  <typeSystemDescription>
   <typeDescription>
    <name>org.apache.uima.stanfordnlp.input</name>
    <description />
    <supertypeName>uima.tcas.Annotation</supertypeName>
    <features>
     <featureDescription>
      <name>action</name>
      <description />
      <rangeTypeName>uima.cas.String</rangeTypeName>
     </featureDescription>
    </features>
   </typeDescription>

   <typeDescription>
    <name>org.apache.uima.standfordnlp.output</name>
    <description />
    <supertypeName>uima.tcas.Annotation</supertypeName>
    <features>
     <featureDescription>
      <name>type</name>
      <description />
      <rangeTypeName>uima.cas.String</rangeTypeName>
     </featureDescription>
    </features>
   </typeDescription>
  </typeSystemDescription>
</analysisEngineDescription>
Annotator Test case
Here we are using sujitpal's UimaUtils.java, it adds the feature org.apache.uima.stanfordnlp.input:action=ner to the CAS then send the case to UIMA server then check the org.apache.uima.stanfordnlp.output feature in the response.
private static final Joiner joiner = Joiner.on(",");
@Test
public void testStanfordNLPAnnotator() throws Exception {
  AnalysisEngine ae = UimaUtils.getAE("%ABS_PATH%\StanfordNLPAnnotator.xml", null);
  for (String input : INPUTS) {
    JCas jcas = ae.newJCas();
    addFSAction(jcas,Lists.newArrayList(StanfordNLPAnnotator.STANFORDNLP_ACTION_NER));
    jcas = UimaUtils.runAE(ae, input, UimaUtils.MIMETYPE_TEXT, jcas);

    Feature feature = jcas.getTypeSystem().getFeatureByFullName(
        "org.apache.uima.standfordnlp.output:type");
    org.apache.uima.cas.TypeSystem ts = jcas.getTypeSystem();
    org.apache.uima.cas.Type dyOutputType = ts
        .getType("org.apache.uima.standfordnlp.output");

    FSIndex<? extends Annotation> index = jcas
        .getAnnotationIndex(dyOutputType);
    for (Iterator<? extends Annotation> it = index.iterator(); it
        .hasNext();) {
      Annotation annotation = it.next();
      System.out.println("...(" + annotation.getBegin() + ","
          + annotation.getEnd() + "): "
          + annotation.getCoveredText() + ", type: "
          + annotation.getFeatureValueAsString(feature));
    }
  }
  ae.destroy();
}
private void addFSAction(JCas jcas, List<String> action) {
  TypeSystem ts = jcas.getTypeSystem();
  Feature ft = ts
      .getFeatureByFullName(StanfordNLPAnnotator.FS_STANFORDNLP_INPUT_ACTION);
  Type type = ts.getType(StanfordNLPAnnotator.TYPE_STANFORDNLP_INPUT);

  FeatureStructure fs = jcas.getCas().createFS(type);
  fs.setStringValue(ft, joiner.join(action));
  jcas.addFsToIndexes(fs);
}

UIMA: Run Custom Regex Dynamically


The Problem
Extend UIMA Regex Annotator to allow user run custom regex dynamically.

Regular Expression Annotator allows us to easily define entity name(such as credit card, email) and regex to extract these entities.

But we can never define all useful entities, so it's good to allow customers to add their own entities and regex, and the UIMA Regular Expression Annotator would run them dynamically.

We can create and deploy a new annotator, but we decide to just extend UIMA RegExAnnotator.

How it Works
Client Side
We create one type org.apache.uima.input.dynamicregex with feature types and regexes. 
In our http interface, client specifies the entity name and its regex: 
host:port/nlp?text=abcxxdef&customTypes=mytype1,mytype2&customRegexes=abc.*,def.*

Client will add Feature Structure: org.apache.uima.input.dynamicregex.types=mytype1,mytype2 and org.apache.uima.input.dynamicregex.regexes=abc.*,def.*
public void addCustomRegex(List<String> customTypes,
    List<String> customRegexes, CAS cas) {
  if (customTypes != null && customRegexes != null) {
    if (customTypes.size() != customRegexes.size()) {
      throw new IllegalArgumentException(
          "Size doesn't match: customTypes size: "
              + customTypes.size() + ", customRegexes size: "
              + customRegexes.size());
    }
    TypeSystem ts = cas.getTypeSystem();
    Feature ft = ts
        .getFeatureByFullName("org.apache.uima.input.dynamicregex:types");
    Type type = ts.getType("org.apache.uima.input.dynamicregex");

    if (type != null) {
      // if remote annotator or pear supports type
      // org.apache.uima.entities:entities, add it to indexes,
      // otherwise do nothing.
      FeatureStructure fs = cas.createFS(type);
      fs.setStringValue(ft, joiner.join(customTypes));
      cas.addFsToIndexes(fs);
    }

    ft = ts.getFeatureByFullName("org.apache.uima.input.dynamicregex:regexes");
    type = ts.getType("org.apache.uima.input.dynamicregex");

    if (type != null) {
      // if remote annotator or pear supports type
      // org.apache.uima.entities:entities, add it to indexes,
      // otherwise do nothing.
      FeatureStructure fs = cas.createFS(type);
      fs.setStringValue(ft, joiner.join(customRegexes));
      cas.addFsToIndexes(fs);
    }
  }
}
public Result process(String text, String lang, List<String> uimaTypes,
    List<String> customTypes, List<String> customRegexes,
    Long waitMillseconds) throws Exception {
  CAS cas = this.ae.getCAS();
  String casId;
  try {
    cas.setDocumentText(text);
    cas.setDocumentLanguage(lang);
    TypeSystem ts = cas.getTypeSystem();
    Feature ft = ts.getFeatureByFullName(UIMA_ENTITIES_FS);
    Type type = ts.getType(UIMA_ENTITIES);
    if (type != null) {
      // if remote annotator or pear supports type
      // org.apache.uima.entities:entities, add it to indexes,
      // otherwise do nothing.
      FeatureStructure fs = cas.createFS(type);
      fs.setStringValue(ft, joiner.join(uimaTypes));
      cas.addFsToIndexes(fs);
    }
    addCustomRegex(customTypes, customRegexes, cas);
    casId = this.ae.sendCAS(cas);
  } catch (ResourceProcessException e) {
    // http://t17251.apache-uima-general.apachetalk.us/uima-as-client-is-blocking-t17251.html
    cas.release();
    logger.error("Exception thrown when process cas " + cas, e);
    throw e;
  }
  Result rst = this.listener.waitFinished(casId, waitMillseconds);
  return rst;
}
Define Feature Structures in RegExAnnotator.xml
org.apache.uima.input.dynamicregex is used as input paramter, client can specify value for its features: types and regexes. org.apache.uima.output.dynamicrege is the output type.
<typeDescription>
  <name>org.apache.uima.input.dynamicregex</name>
  <description />
  <supertypeName>uima.tcas.Annotation</supertypeName>
  <features>
    <featureDescription>
      <name>types</name>
      <description />
      <rangeTypeName>uima.cas.String</rangeTypeName>
    </featureDescription>            
    <featureDescription>
      <name>regexes</name>
      <description />
      <rangeTypeName>uima.cas.String</rangeTypeName>
    </featureDescription>            
  </features>          
</typeDescription>
<!-- output params -->
<typeDescription>
  <name>org.apache.uima.output.dynamicregex</name>
  <description />
  <supertypeName>uima.tcas.Annotation</supertypeName>
  <features>
    <featureDescription>
      <name>type</name>
      <description />
      <rangeTypeName>uima.cas.String</rangeTypeName>
    </featureDescription>
  </features>
</typeDescription>

Run Custom Regex and Return Extracted Entities in  RegExAnnotator
Next, in RegExAnnotator.process method, we get value of the input types and regex, run custom regex and add found entities to CAS indexes.
public void process(CAS cas) throws AnalysisEngineProcessException {
  procressCutsomRegex(cas);
  //... omitted
}
private void procressCutsomRegex(CAS cas) {
  TypeSystem ts = cas.getTypeSystem();
  Type dyInputType = ts.getType("org.apache.uima.input.dynamicregex");
  org.apache.uima.cas.Feature dyInputTypesFt = ts
      .getFeatureByFullName("org.apache.uima.input.dynamicregex:types");
  org.apache.uima.cas.Feature dyInputRegexesFt = ts
      .getFeatureByFullName("org.apache.uima.input.dynamicregex:regexes");
  String dyTypes = null, dyRegexes = null;
  FSIterator<?> dyIt = cas.getAnnotationIndex(dyInputType).iterator();

  AnnotationFS dyInputTypesFs = null, dyInputRegexesFs = null;
  while (dyIt.hasNext()) {
    // TODO this is kind of weird
    AnnotationFS afs = (AnnotationFS) dyIt.next();
    if (afs.getStringValue(dyInputTypesFt) != null) {
      dyTypes = afs.getStringValue(dyInputTypesFt);
      dyInputTypesFs = afs;
    }
    if (afs.getStringValue(dyInputRegexesFt) != null) {
      dyRegexes = afs.getStringValue(dyInputRegexesFt);
      dyInputRegexesFs = afs;
    }
  }
  if (dyInputTypesFs != null) {
    cas.removeFsFromIndexes(dyInputTypesFs);
  }
  if (dyInputRegexesFs != null) {
    cas.removeFsFromIndexes(dyInputRegexesFs);
  }
  String[] dyTypesArr = dyTypes.split(","), dyRegexesArr = dyRegexes
      .split(",");
  if (dyTypesArr.length != dyRegexesArr.length) {
    throw new IllegalArgumentException(
        "Size of custom regex doesn't match. types: "
            + dyTypesArr.length + ",  regexes: "
            + dyRegexesArr.length);
  }
  if (dyTypesArr.length == 0)
    return;
  logger.log(Level.FINE, "User specifies custom regex: type: " + dyTypes
      + ", regexes: " + dyRegexes);
  String docText = cas.getDocumentText();
  Type dyOutputType = ts.getType("org.apache.uima.output.dynamicregex");
  org.apache.uima.cas.Feature dyOutputTypeFt = ts
      .getFeatureByFullName("org.apache.uima.output.dynamicregex:type");
  FSIndexRepository indexRepository = cas.getIndexRepository();
  for (int i = 0; i < dyTypesArr.length; i++) {
    Pattern pattern = Pattern.compile(dyRegexesArr[i]);
    Integer captureGroupPos = getNamedGrpupPosition(pattern, "capture");
    Matcher matcher = pattern.matcher(docText);

    while (matcher.find()) {
      AnnotationFS dyAnnFS;
      // if named group capture exists
      if (captureGroupPos != null) {
        dyAnnFS = cas.createAnnotation(dyOutputType,
            matcher.start(captureGroupPos),
            matcher.end(captureGroupPos));
      } else {
        dyAnnFS = cas.createAnnotation(dyOutputType,
            matcher.start(), matcher.end());
      }
      dyAnnFS.setStringValue(dyOutputTypeFt, dyTypesArr[i]);
      indexRepository.addFS(dyAnnFS);
    }
  }
}
/**
 * Use reflection to call namedGroups in JDK7
 */
@SuppressWarnings("unchecked")
private Integer getNamedGrpupPosition(Pattern pattern, String namedGroup) {
  try {
    Method namedGroupsMethod = Pattern.class.getDeclaredMethod(
        "namedGroups", null);
    namedGroupsMethod.setAccessible(true);

    Map<String, Integer> namedGroups = (Map<String, Integer>) namedGroupsMethod
        .invoke(pattern, null);
    return namedGroups.get(namedGroup);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
References
UIMA References
Apache UIMA Regular Expression Annotator Documentation

UIMA: Using Dedicated Feature Structure to Control Annotator Behavior


The Problem
In previous post: Using ResultSpecification to Filter Annotator to Boost Opennlp UIMA Performance, I introduced how to use ResultSpecification to make OpenNLP.pear only run needed annotators.

But recently, we changed our content analzyer project to use UIMA-AS for better scale out. But UIMA-AS doesn't support specify ResultSpecification at client side, so we have to find other solutions.

Luckily UIMA provides a more common mechanism: feature structures to allow us to control annotator's behavioral characteristics.

Using Dedicated Feature Structure to Control Server Behavioral
This time, we will take RegExAnnotator.pear as example, as we have defined more than 10+ regex and entities in RegExAnnotator, and the client would specify which entities they are interested. 

Client specify  values of the feature: org.apache.uima.entities:entities, such as "ssn,creditcard,email", RegExAnnotator will check the setting and run only needed regex.

Specify Feature Value at Client Side
First we have one properties file uima.properties which define the mapping of entity name to the UIMA type: 
regex_type_ssn=org.apache.uima.ssn
regex_type_CreditCard=org.apache.uima.CreditCardNumber
regex_type_Email=org.apache.uima.EmailAddress


public class UIMAASService extends AbstractService {
 private static final String UIMA_ENTITIES = "org.apache.uima.entities";
 private static final String UIMA_ENTITIES_FS = UIMA_ENTITIES + ":entities";
 private static Joiner joiner = Joiner.on(",");

 public Result process(String text, String lang, List<String> types,
   Long waitMillseconds) throws Exception {
  CAS cas = this.ae.getCAS();
  String casId;
  try {
   cas.setDocumentText(text);
   cas.setDocumentLanguage(lang);
   TypeSystem ts = cas.getTypeSystem();
   Feature ft = ts.getFeatureByFullName(UIMA_ENTITIES_FS);

   Type type = ts.getType(UIMA_ENTITIES);
   if (type != null) {
    // if remote annotator or pear supports type
    // org.apache.uima.entities:entities, add it to indexes,
    // otherwise do nothing.
    FeatureStructure fs = cas.createFS(type);
    fs.setStringValue(ft, joiner.join(types));
    cas.addFsToIndexes(fs);
   }
   casId = this.ae.sendCAS(cas);
  } catch (ResourceProcessException e) {
   // http://t17251.apache-uima-general.apachetalk.us/uima-as-client-is-blocking-t17251.html
   // The UIMA AS framework code throws an
   // Exception and the application must catch it and release a CAS
   // before continuing. 
   cas.release();
   logger.error("Exception thrown when process cas " + cas, e);
   throw e;
  }
  Result rst = this.listener.waitFinished(casId, waitMillseconds);
  return rst;
 }
 protected static final Logger logger = LoggerFactory
   .getLogger(UIMAASService.class);

 private UimaAsynchronousEngine ae = null;
 protected UimaAsListener listener;
 private String serverUrl;
 private String endpoint;
 private static final int RETRIES = 10;

 public UIMAASService(String serverUrl, String endpoint) {
  this.serverUrl = serverUrl;
  this.endpoint = endpoint;
 }
 public void configureAE() throws SimpleServerException, IOException,
   XmlException, ResourceInitializationException {
  boolean success = false;
  for (int i = 0; (i < RETRIES) && (!success); i++) {
   try {
    this.ae = new BaseUIMAAsynchronousEngine_impl();
    this.listener = new UimaAsListener(this);
    this.ae.addStatusCallbackListener(this.listener);
    Map<String, Object> deployCtx = new HashMap<String, Object>();
    deployCtx.put("ServerURI", this.serverUrl);
    deployCtx.put("Endpoint", this.endpoint);
    deployCtx.put("Timeout", 60000);
    deployCtx.put("CasPoolSize", 20);
    deployCtx.put("GetMetaTimeout", 20000);

    if (StringUtils.isNotBlank(System.getProperty("uimaDebug"))) {
     deployCtx.put("-uimaEeDebug", Boolean.valueOf(true));
    }

    this.ae.initialize(deployCtx);
    success = true;
   } catch (ResourceInitializationException e) {
    if (i < 10) {
     logger.error(
       getName()
         + " configureAE failed when deploy , will retry, retried times: "
         + i, e);
    } else {
     logger.error(getName()
       + " configureAE failed, retried times: " + i, e);
     throw e;
    }
   }
  }
  configure(null);
 }

 public UimaAsListener getListener() {
  return this.listener;
 }

 public void deployPear(String appHome, File pearFile, File installationDir,
   String deployFileName) throws Exception {
  PackageBrowser instPear = PackageInstaller.installPackage(
    installationDir, pearFile, true);

  File deployFile = new File(instPear.getRootDirectory(), deployFileName);

  logger.info(getName() + " deployFile: " + deployFile);
  updateDeployFile(deployFile, this.serverUrl, this.endpoint);

  Map<String, Object> deployCtx = new HashMap<String, Object>();
  deployCtx.put("DD2SpringXsltFilePath", new File(appHome,
    "resources/uima/config/dd2spring.xsl").getAbsolutePath());

  deployCtx.put(
    "SaxonClasspath",
    "file:"
      + new File(appHome, "resources/uima/lib/saxon8.jar")
        .getAbsolutePath());

  BaseUIMAAsynchronousEngine_impl tmpAE = new BaseUIMAAsynchronousEngine_impl();
  tmpAE.deploy(deployFile.getAbsolutePath(), deployCtx);

  logger.info(getName() + " deployed " + pearFile.getAbsolutePath());
 }
 private static void updateDeployFile(File deployFile, String serverUrl,
   String endpoint) throws FileNotFoundException, IOException {
  String fileContext = null;
  InputStream is = new FileInputStream(deployFile);
  try {
   fileContext = IOUtils.toString(is);
  } finally {
   IOUtils.closeQuietly(is);
  }
  fileContext = fileContext.replace("${endpoint}", endpoint);
  fileContext = fileContext.replace("${brokerURL}", serverUrl);

  Object os = new FileOutputStream(deployFile);
  try {
   IOUtils.write(fileContext, (OutputStream) os);
  } finally {
   IOUtils.closeQuietly((OutputStream) os);
  }
 }
 public void deployPear(File pearFile, File installationDir,
   String deployFileName) throws Exception {
  String appHome = System.getProperty("cv.app.running.home").trim();
  deployPear(appHome, pearFile, installationDir, deployFileName);
 } 
}
Add Feature in RegExAnnotator.xml
<typeSystemDescription>
  <typeDescription>
    <name>org.apache.uima.entities</name>
    <description />
    <supertypeName>uima.tcas.Annotation</supertypeName>
    <features>
      <featureDescription>
        <name>entities</name>
        <description/>
        <rangeTypeName>uima.cas.String</rangeTypeName>
      </featureDescription>
    </features>
  </typeDescription>
</typeSystemDescription>
Check Feature Value at RegExAnnotator
RegExAnnotator will get the value of org.apache.uima.entities:entities, if it's set, then it will check all configured regex Concepts and add it to runConcepts if the concept produces one of the uima types of these entities.
public class RegExAnnotator extends CasAnnotator_ImplBase {
 private static final String UIMA_ENTITIES = "org.apache.uima.entities";
 private static final String UIMA_ENTITIES_FS = UIMA_ENTITIES + ":entities";
  
 public void process(CAS cas) throws AnalysisEngineProcessException {
  TypeSystem ts = cas.getTypeSystem();
  org.apache.uima.cas.Type entitiesType = ts.getType(UIMA_ENTITIES);
  FSIterator<?> it = cas.getAnnotationIndex(entitiesType).iterator();

  org.apache.uima.cas.Feature ft = ts
      .getFeatureByFullName(UIMA_ENTITIES_FS);
  String onlyRegexStr = null;
  AnnotationFS entitiesFs = null;
  while (it.hasNext()) {
    // TODO this is kind of weird
    AnnotationFS afs = (AnnotationFS) it.next();
    if (afs.getStringValue(ft) != null) {
      System.out.println(afs.getType().getName());
      onlyRegexStr = afs.getStringValue(ft).trim();
      entitiesFs = afs;
    }
    // onlyRegexStr = afs.getStringValue(ft).trim();
    logger.log(Level.FINE, "Only run " + onlyRegexStr);
  }
  if (entitiesFs != null) {
    cas.removeFsFromIndexes(entitiesFs);
  }

  List<String> types = null;
  List<Concept> runConcepts = new ArrayList<Concept>();

  if (onlyRegexStr != null) {
    onlyRegexStr.split(",");
    types = Arrays.asList(onlyRegexStr.split(","));
    for (Concept concept : regexConcepts) {
      Annotation[] annotations = concept.getAnnotations();
      if (annotations != null) {
        for (Annotation annotation : annotations) {
          if (types.contains(annotation.getAnnotationType()
              .getName())) {
            runConcepts.add(concept);
            break;
          }
        }
      }
    }
  } else {
    runConcepts = this.regexConcepts;
  }
  // change this.regexConcepts.length to local variable: runConcepts
  for (int i = 0; i < runConcepts.size(); i++) { 
       // same and omitted...
    }
  }
}  
References
UIMA References - feature structures
Apache UIMA Regular Expression Annotator Documentation
http://comments.gmane.org/gmane.comp.apache.uima.general/5866

Using ResultSpecification to Filter Annotator to Boost Opennlp UIMA Performance


The Problem:
We use opennlp-uima to extract entities such as person, organization, location, date, time, money, percentage. But in most cases, client just wants to extract one or several kinds of entities: for example just person and location.

In OpenNlpTextAnalyzer.pear, it will run all 12 annotators in sequence. This is not good from performance perspective. Check flowConstraints/fixedFlow definition in OpenNlpTextAnalyzer.xml:

We want to opennlp-uima to only run needed annotators to boost its performance.

The solution: Using ResultSpecification
UIMA's descriptors include a section under the XML capabilities element where the descriptor may specify inputs and outputs.  These end up informing the ResultSpecification which is provided to the annotator.  The ResultSpecification can be queried by the annotator code to see what the annotator ought to produce.

PersonTitleAnnotator and TutorialDateTime in uimaj-examples project uses ResultSpecification to check whether it need run the annotator to boost the performance:

public void process(CAS aCAS) throws AnalysisEngineProcessException {
    // If the ResultSpec doesn't include the PersonTitle type, we have nothing to do.
    if (!getResultSpecification().containsType("example.PersonTitle",aCAS.getDocumentLanguage())) {
      if (!warningMsgShown) {
        logger.log(Level.WARNING, m);
        warningMsgShown = true;
      }
      return;
    }
}
We need make the following change to make opennlp-uima to honor ResultSpecification to filter annotators.
1. Update Annotator's analysisEngineDescription outputs to reflect its capabilities
Take PersonNameFinder.xml as an exmple: we need add opennlp.uima.Person like below:
Do simliar change in these files: PersonNameFinder.xml, LocationNameFinder.xml, OrganizationNameFinder.xml, DateNameFinder.xml, TimeNameFinder.xml, MoneyNameFinder.xml, PercentageNameFinder.xml, PosTagger.xml, Tokenizer.xml,Parser.xml, Chunker.xml.
<capabilities>
  <capability>
    <inputs />
    <outputs>
      <type>opennlp.uima.Person</type>
    </outputs>
    <languagesSupported>
      <language>en</language>
    </languagesSupported>
  </capability>
</capabilities>
Due to a bug in opennlp-uima, we need change NameType in nameValuePair from opennlp.uima.Person to opennlp.uima.Time.
Please refer to Wrong NameType in TimeNameFinder.xml, otherwise the annotator would classify time phrases such as "this afternoon" and "tomorrow morning" as Persons instead of Times.

2. Change Annotator's code to honor ResultSpecification
PersonNameFinder.xml, LocationNameFinder.xml, OrganizationNameFinder.xml, DateNameFinder.xml, TimeNameFinder.xml extends same parent class: opennlp.uima.namefind.AbstractNameFinder. We can change its process method like below:
public final void process(CAS cas) {
 ResultSpecification rs = getResultSpecification();  
 boolean run = rs.containsType(mNameType.getName())
   || rs.containsType(mNameType.getName(),cas.getDocumentLanguage());
 if (!run) {
  return;
 }
  // omitted ....
} 
opennlp.uima.parser.Parser:
public void process(CAS cas) {
    ResultSpecification rs = getResultSpecification();  
 boolean run = rs.containsType("opennlp.uima.Parse") || rs.containsType("opennlp.uima.Parse", cas.getDocumentLanguage());
 if (!run) {
  return;
 }
} 
opennlp.uima.chunker.Chunker:
public void process(CAS tcas) {
 ResultSpecification rs = getResultSpecification();  
 boolean run = rs.containsType("opennlp.uima.Chunk") 
   || rs.containsType("opennlp.uima.Chunk", tcas.getDocumentLanguage());
 if (!run) {
  return;
 } 
}
opennlp.uima.postag.POSTagger:
public void process(CAS tcas) {
 ResultSpecification rs = getResultSpecification();
 boolean run = rs.containsType("opennlp.uima.Token:pos")
   || rs.containsType("opennlp.uima.Token:pos", tcas.getDocumentLanguage());
 if (!run) {
  return;
 }
}  
Change in Client Side
In client side, we need add result type in ResultSpecification when call org.apache.uima.analysis_engine.AnalysisEngine.process(CAS, ResultSpecification):
  ResultSpecification rs = UIMAFramework.getResourceSpecifierFactory()
      .createResultSpecification();
  rs.addResultType("opennlp.uima.Person", true);
  rs.addResultType("opennlp.uima.Location", true);
  this.ae.process(this.cas, rsf);
In our project, we use uima's Regular Expression Annotator to extract entities such as ssn, phone number, credit card etc. We define more than 20 entities and their corresponding regex in its concepts.xml

Resources
UIMA Result Specifications
UIMA References
http://comments.gmane.org/gmane.comp.apache.uima.general/5670

Text Mining: Integrate UIMA Regular Expression Annotator with Solr


UIMA RegexAnnotator
UIMA RegexAnnotator is an Apache UIMA analysis engine that uses regular expression to detect entities such as email addresses, URLs, phone numbers, zip codes or any other entity.

This article will introduce how to deploy RegexAnnotator as SOAP web service, add extra regex to extract other types of entities and integrate it with Solr.
Deploy RegexAnnotator as SOAP Web Service
For detailed steps, please refer to this post.
First copy all jars in %uima-addons-home%\addons\annotator\RegularExpressionAnnotator\lib\ to axis.war\WEB-INF\lib, copy RegularExpressionAnnotator\desc\concepts.xml to axis.war\WEB-INF\classes.

Then we need create web services deployment descriptor. Example WSDD files are provided in the examples/deploy/soap directory of the UIMA SDK. All we need do is to copy one wsdd(for example:Deploy_NamesAndPersonTitles.wsdd): change service name to urn:RegExAnnotator, change resourceSpecifierPath to point to %PEARS_HOME_REPLACE_THIS%\addons/annotator/RegularExpressionAnnotator/desc/RegExAnnotator.xml.

<deployment name="RegExAnnotator">
 <service name="urn:RegExAnnotator" provider="java:RPC">
  <parameter name="resourceSpecifierPath" value="%PEARS_HOME_REPLACE_THIS%\addons/annotator/RegularExpressionAnnotator/desc/RegExAnnotator.xml"/>
 </service>
</deployment>
If we are using tomcat, set CATALINA_HOME to the location where Tomcat is installed. if we are using other application server, we may update UIMA_CLASSPATH in runUimaClass.bat to include axis\WEB-INF\lib, axis\WEB-INF\classes.

Then run deploytool %FOLDER%\RegExAnnotator.wsdd to deploy the pear as SOAP service.
Test RegexAnnotator SOAP Service in CVD
Check How to Call a UIMA Service for detail.

We need define one SOAP Service Client Descriptor: RegExAnnotatorSoapServiceClient.xml
<uriSpecifier xmlns="http://uima.apache.org/resourceSpecifier">
 <resourceType>AnalysisEngine</resourceType>
 <uri>http://localhost:8080/axis/services/urn:RegExAnnotator</uri>
 <protocol>SOAP</protocol>
</uriSpecifier>
Then in CVD, click "Run" -> "Load AE" to load RegExAnnotatorSoapServiceClient.xml, then test it.
Adding RegEx to extract other types of entities
Regular expression can be used to extract many types of enties. 
We can use regex from this post: \(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4}) to extract North American Phone Numbers.

To add this feature to UIMA, we create one ExtraRegExAnnotator.xml - similar as RegExAnnotator.xml except using different concept xml file and type definition:
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
  <primitive>true</primitive>
  <annotatorImplementationName>org.apache.uima.annotator.regex.impl.RegExAnnotator</annotatorImplementationName>
  <analysisEngineMetaData>
    <name>ExtraRegExAnnotator</name>
    <!--configurationParameters omitted here -->
    <configurationParameterSettings>
      <nameValuePair>
        <name>ConceptFiles</name>
        <value><array><string>extra-concepts.xml</string></array></value>
      </nameValuePair>      
    </configurationParameterSettings>
    <typeSystemDescription>
      <types>
        <typeDescription>
          <name>org.lifelongprogrammer.USAPhoneNumber</name>
          <description/>
          <supertypeName>uima.tcas.Annotation</supertypeName>
          <features>
            <featureDescription>
              <name>confidence</name>
              <description/>
              <rangeTypeName>uima.cas.Float</rangeTypeName>
            </featureDescription>            
          </features>
        </typeDescription>
      </types>
    </typeSystemDescription>
    <capabilities>
      <capability>
        <inputs/>
        <outputs>
          <type>org.lifelongprogrammer.USAPhoneNumber</type>
        </outputs>
        <languagesSupported/>
      </capability>
    </capabilities>
    <!-- operationalProperties omited here -->
  </analysisEngineMetaData>
</analysisEngineDescription>
extra-concepts.xml: - and copy it to axis.war\WEB-INF\classes
<conceptSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://incubator.apache.org/uima/regex"
 xsi:schemaLocation="concept.xsd">
  <concept name="usaPhoneNumberDetection">
    <rules>
      <rule regEx="\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})"
    matchStrategy="matchAll" matchType="uima.tcas.DocumentAnnotation"
    confidence="1.0" />
    </rules>
    <createAnnotations>
      <annotation id="usaPhoneNumber"
    type="org.lifelongprogrammer.USAPhoneNumber">
        <begin group="0" />
        <end group="0" />
        <setFeature name="confidence" type="Confidence" />
      </annotation>
    </createAnnotations>
  </concept>  
</conceptSet>
The web services deployment descriptor: Deploy_ExtraRegExAnnotator.wsdd: similar as Deploy_RegExAnnotator.wsdd.

The SOAP Service Client Descriptor: ExtraRegExAnnotatorSoapServiceClient.xml(similar as RegExAnnotatorSoapServiceClient.xml). Load it to CVD, and test it.
Integrate UIMA RegexAnnotator with Solr
Solr uses UIMAUpdateRequestProcessorFactory to send the text to SOAP web service and parse the soap response when add a document to Solr, UIMAUpdateRequestProcessorFactory will save the UIMA extracted information into Solr.


In order to call SOAP web service, we first need put the SOAP Service Client Descriptor: RegExAnnotatorSoapServiceClient.xml and ExtraRegExAnnotatorSoapServiceClient.xml in solr/collection1/conf folder.

Then wrap the two SOAP services urn:RegExAnnotator and urn:ExtraRegExAnnotator in an aggregate analysis engine.
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
  <primitive>false</primitive>
  <delegateAnalysisEngineSpecifiers>
    <delegateAnalysisEngine key="RegExAnnotatorService">
      <import location="RegExAnnotatorSoapServiceClient.xml"/>
    </delegateAnalysisEngine>   
    <delegateAnalysisEngine key="ExtraRegExAnnotatorService">
      <import location="ExtraRegExAnnotatorSoapServiceClient.xml"/>
    </delegateAnalysisEngine>  
  </delegateAnalysisEngineSpecifiers>
  <analysisEngineMetaData>
    <name>AllRegExAnnotatorService</name>
    <description/>
    <version>1.0</version>
    <vendor/>
    <configurationParameters searchStrategy="language_fallback">
    </configurationParameters>
    <flowConstraints>
      <fixedFlow>
        <node>RegExAnnotatorService</node>
        <node>ExtraRegExAnnotatorService</node>
      </fixedFlow>
    </flowConstraints>
    <fsIndexCollection/>
    <!-- capabilities omitted -->
    <!-- operationalProperties omitted -->
  </analysisEngineMetaData>
  <resourceManagerConfiguration/>
</analysisEngineDescription>
Then define update chain: uima-regex in solrconfig.xml:
<updateRequestProcessorChain name="uima-regex" default="true">
    <processor class="org.apache.solr.uima.processor.UIMAUpdateRequestProcessorFactory">
      <lst name="uimaConfig">
        <lst name="runtimeParameters">
        </lst>javax.xml.rpc.ServiceException
        <str name="analysisEngine">file:///%REPLACE_THIS%\solr\collection1\conf\RegExAnnotatorAE.xml</str>
        <bool name="ignoreErrors">false</bool>
        <lst name="analyzeFields">
          <bool name="merge">false</bool>
          <arr name="fields">
            <str>content</str>
          </arr>
        </lst>
        <lst name="fieldMappings">
        <lst name="type">
            <str name="name">org.lifelongprogrammer.USAPhoneNumber</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">usaphone_mxf</str>
            </lst>
          </lst>
          <lst name="type">
            <str name="name">org.apache.uima.EmailAddress</str>
            <lst name="mapping">
              <str name="feature">normalizedEmail</str>
              <str name="field">email_mxf</str>
            </lst>
          </lst>          
          <lst name="type">
            <str name="name">org.apache.uima.ISBNNumber</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">isbn_mxf</str>
            </lst>
          </lst>

          <lst name="type">
            <str name="name">org.apache.uima.MoneyAmount</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">money_mxf</str>
            </lst>
          </lst>
          <lst name="type">
            <str name="name">org.apache.uima.CreditCardNumber</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">creditcard_mxf</str>
            </lst>
            <lst name="mapping">
              <str name="feature">cardType</str>
              <str name="field">creditcardType_mxf</str>
            </lst>
          </lst>
        </lst>
      </lst>
    </processor>
    <processor class="solr.LogUpdateProcessorFactory" />
    <processor class="solr.RunUpdateProcessorFactory" />
  </updateRequestProcessorChain>
After that we can call 
http://localhost:8080/solr/update?update.chain=uima-regex&commit=true&stream.body=<add><doc><field name="id">1</field><field name="content">some text here</field></doc></add>
Then run http://localhost:8080/solr/select?q=id:1, we can see it extracts entities like phone number, email, credit card, isbn etc.
Resources
Text Mining: Integrate OpenNLP, UIMA and Solr via SOAP Web Service

Text Mining: Integrate OpenNLP, UIMA AS and Solr


In this series, I will introduce how to integrate OpenNLP, UIMA and Solr.
Integrate OpenNLP with UIMA
Talk about how to install UIMA, build OpenNLP pear, and run OpenNLP pear in CVD or UIMA Simple Server. 
Integrate OpenNLP, UIMA and Solr via SOAP Web Service
Talk about how to deploy OpenNLP UIMA pear as SOAP web service, and integrate it with Solr.
Integrate OpenNLP, UIMA AS and Solr
Talk about how to deploy OpenNLP UIMA pear as UIMA AS Service, and integrate it with Solr.

Please refer to the part1 about how to install UIMA, build OpenNLP UIMA.
Deploy OpenNLP Pear as UIMA AS Service
UIMA AS(Asynchronous Scaleout) is the next generation scalability replacement for the Collection Processing Manager (CPM).

Download UIMA AS binary package, unzip it, then run bin/startBroker.bat to starts the ActiveMQ broker, which must be running before UIMA AS services can be deployed.

Then use deployAsyncService.bat to deploy UIMA-AS services: deployAsyncService.sh [testDD.xml] [-brokerURL url]

In order to deploy pear, we have to use 2.4.2 or newer UIMA AS version - 2.3.1 doesn't work.

First unzip the OpenNlpTextAnalyzer.pear to %PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer. 
Create pear descriptor: opennlp.uima.OpenNlpTextAnalyzer_pear.xml in%PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer.
<?xml version="1.0" encoding="UTF-8"?>
<pearSpecifier xmlns="http://uima.apache.org/resourceSpecifier">
    <pearPath>%PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer</pearPath>
</pearSpecifier>
Then %PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer, create one UIMA-AS deployment descriptor: Deploy_OpenNLP.xml like below. We can refer AS deploy descriptors in uima-as-%version%-bin\examples\deploy\as.
<?xml version="1.0" encoding="UTF-8"?>
<analysisEngineDeploymentDescription
  xmlns="http://uima.apache.org/resourceSpecifier">
  <name>OpenNLP Text Analyzer</name>
  <description>Deploys OpenNLP text analyzer.</description>

  <deployment protocol="jms" provider="activemq">
    <service>
      <inputQueue endpoint="OpenNLP-service"
brokerURL="tcp://localhost:61616"/>
      <topDescriptor>
       <import location="opennlp.uima.OpenNlpTextAnalyzer_pear.xml"/>
      </topDescriptor>
    </service>
  </deployment> 
</analysisEngineDeploymentDescription>
Then run: deployAsyncService.cmd %PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer\Deploy_OpenNLP.xml
Test OpenNLP Pear UIMA AS Service in CVD
Refer to uima-as-version-bin\examples\descriptors\as\MeetingDetectorAsyncAE.xml, we need create client descriptor, OpenNLPAsyncAEClient.xml: we just ned change endpoint to OpenNLP-service.
<customResourceSpecifier xmlns="http://uima.apache.org/resourceSpecifier">
   <resourceClassName>org.apache.uima.aae.jms_adapter.JmsAnalysisEngineServiceAdapter</resourceClassName>
   <parameters>
     <parameter name="brokerURL" value="tcp://localhost:61616"/>
     <parameter name="endpoint" value="OpenNLP-service"/>
     <parameter name="timeout" value="5000"/>
     <parameter name="getmetatimeout" value="5000"/>
     <parameter name="cpctimeout" value="5000"/>
   </parameters>
</customResourceSpecifier>
Then in CVD, click "Run" -> "Load AE" to load OpenNLPServiceClient.xml, then test it.
Integrate OpenNLP-UIMA with Solr
We can use Solr UIMAUpdateRequestProcessorFactory to send the text to OpenNLP-UIMA SOAP web service to analyze it when add a document to Solr, UIMAUpdateRequestProcessorFactory will save the UIMA extracted information into Solr.

In order to call SOAP web service, we first need put the SOAP Service Client Descriptor: OpenNLPAsyncAEClient.xml in solr/collection1/conf folder.
Then wrap the UIMA AS service to be a part of aggregate analysis engine.

We create an analysis engine descriptor file: AggragateOpenNLPAsyncService.xml like below.
<?xml version="1.0" encoding="UTF-8"?>
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
  <primitive>false</primitive>
  <delegateAnalysisEngineSpecifiers>
    <delegateAnalysisEngine key="OpenNLPAsyncAE">
      <import location="OpenNLPAsyncAEClient.xml"/>
    </delegateAnalysisEngine>    
  </delegateAnalysisEngineSpecifiers>
  <analysisEngineMetaData>
    <name>ExtServicesAE</name>
    <description/>
    <version>1.0</version>
    <vendor/>
    <configurationParameters searchStrategy="language_fallback">
    </configurationParameters>
    <flowConstraints>
      <fixedFlow>
         <node>OpenNLPAsyncAE</node>
      </fixedFlow>
    </flowConstraints>
    <fsIndexCollection/>
    <capabilities>
      <capability>
        <inputs/>
        <outputs/>
        <languagesSupported/>
      </capability>
    </capabilities>
    <operationalProperties>
      <modifiesCas>true</modifiesCas>
      <multipleDeploymentAllowed>false</multipleDeploymentAllowed>
      <outputsNewCASes>false</outputsNewCASes>
    </operationalProperties>
  </analysisEngineMetaData>
  <resourceManagerConfiguration/>
</analysisEngineDescription>

Define dynamicField *_mxf in schema.xml:
<dynamicField name="*_mxf" type="text" indexed="true" stored="true"  multiValued="true"/>
Now we update solrconfig.xml to include this UIAM analysis engine.
<updateRequestProcessorChain name="opennlp-uima-as" default="true">
    <processor class="org.apache.solr.uima.processor.UIMAUpdateRequestProcessorFactory">
      <lst name="uimaConfig">
        <lst name="runtimeParameters">
        </lst>
        <str name="analysisEngine">%REPLACE_THIS%\AggragateOpenNLPAsyncService.xml.xml</str>
        <bool name="ignoreErrors">false</bool>
        <lst name="analyzeFields">
          <bool name="merge">false</bool>
          <arr name="fields">
            <str>content</str>
          </arr>
        </lst>
        <lst name="fieldMappings">
          <lst name="type">
            <str name="name">opennlp.uima.Date</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">date_mxf</str>
            </lst>
          </lst>           
          <lst name="type">
            <str name="name">opennlp.uima.Location</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">location_mxf</str>
            </lst>
          </lst> 

          <lst name="type">
            <str name="name">opennlp.uima.Money</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">money_mxf</str>
            </lst>
          </lst>
          <lst name="type">
            <str name="name">opennlp.uima.Organization</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">organization_mxf</str>
            </lst>
        </lst>
          <lst name="type">
            <str name="name">opennlp.uima.Percentage</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">percentage_mxf</str>
            </lst>
          </lst> 
          <lst name="type">
            <str name="name">opennlp.uima.Sentence</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">sentence_mxf</str>
            </lst>
          </lst> 
          <lst name="type">
            <str name="name">opennlp.uima.Time</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">time_mxf</str>
            </lst>
          </lst>           
          <lst name="type">
            <str name="name">opennlp.uima.Person</str>
            <lst name="mapping">
              <str name="feature">coveredText</str>
              <str name="field">person_mxf</str>
            </lst>
          </lst>           
        </lst>
      </lst>
    </processor>
After that we can call
http://localhost:8080/solr/update?update.chain=opennlp-uima-soap&commit=true&stream.body=<add><doc><field name="id">1</field><field name="content">some text here</field></doc></add>

Then run http://localhost:8080/solr/select?q=id:1, we can see it extracts some entity like organization, person name, location, time, date, money, percentage, etc.
Resources
UIMA Documentation Overview
UIMA Asynchronous Scaleout Documentation Overview
Refer to Re: Error deploying pear on AS 2.4.2

Text Mining: Integrate OpenNLP, UIMA and Solr via SOAP Web Service


In this series, I will introduce how to integrate OpenNLP, UIMA and Solr.
Integrate OpenNLP with UIMA
Talk about how to install UIMA, build OpenNLP pear, and run OpenNLP pear in CVD or UIMA Simple Server. 
Integrate OpenNLP, UIMA and Solr via SOAP Web Service
Talk about how to deploy OpenNLP UIMA pear as SOAP web service, and integrate it with Solr.
Integrate OpenNLP, UIMA AS and Solr
Talk about how to deploy OpenNLP UIMA pear as UIMA AS Service, and integrate it with Solr.

Please refer to the part1 about how to install UIMA, build OpenNLP UIMA.
Deploy OpenNLP Pear as SOAP Web Service
Check Working with Remote Services to figure out how to deploy a UIMA component as a SOAP web service. 
First unzip the OpenNlpTextAnalyzer.pear to %PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer. 
Create pear descriptor: opennlp.uima.OpenNlpTextAnalyzer_pear.xml in%PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer like below.

<?xml version="1.0" encoding="UTF-8"?>
<pearSpecifier xmlns="http://uima.apache.org/resourceSpecifier">
  <pearPath>%REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer</pearPath>
</pearSpecifier>
Then create the web services deployment descriptor, Deploy_OpenNLP.wsdd. Example WSDD files are provided in the examples/deploy/soap directory of the UIMA SDK. All we need do is to copy one wsdd(for example:Deploy_NamesAndPersonTitles.wsdd): change service name to urn:OpenNLP, change resourceSpecifierPath to point to %PEARS_HOME_REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer\opennlp.uima.OpenNlpTextAnalyzer_pear.xml. Replace %PEARS_HOME_REPLACE_THIS% with real location.
<deployment name="OpenNLP" xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <service name="urn:OpenNLP" provider="java:RPC">
    <paramater name="scope" value="Request"/>
    <parameter name="className" value="org.apache.uima.adapter.soap.AxisAnalysisEngineService_impl"/>
    <parameter name="allowedMethods" value="getMetaData process"/>
    <parameter name="allowedRoles" value="*"/>
    <parameter name="resourceSpecifierPath" value="%REPLACE_THIS%\opennlp.uima.OpenNlpTextAnalyzer_pear.xml"/>
    <parameter name="numInstances" value="3"/>
    <parameter name="enableLogging" value="true"/>
    <!-- typeMapping omitted -->
  </service>
</deployment>
If we are using tomcat, set CATALINA_HOME to the location where Tomcat is installed. if we are using other application server, we may update UIMA_CLASSPATH in runUimaClass.bat to include axis\WEB-INF\lib, axis\WEB-INF\classes.

Then run deploytool %FOLDER%\Deploy_OpenNLP.wsdd to deploy the pear as SOAP service.
Test OpenNLP SOAP Service in CVD
Check How to Call a UIMA Service for detail.
We need define one SOAP Service Client Descriptor: OpenNLPSOAPServiceClient.xml
<?xml version="1.0" encoding="UTF-8" ?> 
<uriSpecifier xmlns="http://uima.apache.org/resourceSpecifier">
 <resourceType>AnalysisEngine</resourceType>
 <uri>http://localhost:8080/axis/services/urn:OpenNLP</uri>
 <protocol>SOAP</protocol>
</uriSpecifier>
Then in CVD, click "Run" -> "Load AE" to load OpenNLPSOAPServiceClient.xml, then test it.

Integrate OpenNLP-UIMA with Solr
We can use Solr UIMAUpdateRequestProcessorFactory to send the text to OpenNLP-UIMA SOAP web service to analyze it when add a document to Solr, UIMAUpdateRequestProcessorFactory will save the UIMA extracted information into Solr.

In order to call SOAP web service, we first need put the SOAP Service Client Descriptor: OpenNLPSOAPServiceClient.xml in solr/collection1/conf folder.
Then wrap the SOAP service to be a part of aggragate analysis engine.

We create an analysis engine descriptor file: AggragateOpenNLPSOAPService.xml like below.
<?xml version="1.0" encoding="UTF-8"?>
<analysisEngineDescription xmlns="http://uima.apache.org/resourceSpecifier">
  <frameworkImplementation>org.apache.uima.java</frameworkImplementation>
  <primitive>false</primitive>
  <delegateAnalysisEngineSpecifiers>
    <delegateAnalysisEngine key="OpenNLPSOAPService">
      <import location="OpenNLPSOAPServiceClient.xml"/>
    </delegateAnalysisEngine>    
  </delegateAnalysisEngineSpecifiers>
  <analysisEngineMetaData>
    <name>ExtServicesAE</name>
    <description/>
    <version>1.0</version>
    <vendor/>
    <configurationParameters searchStrategy="language_fallback">
    </configurationParameters>
    <flowConstraints>
      <fixedFlow>
         <node>OpenNLPSOAPService</node>
      </fixedFlow>
    </flowConstraints>
    <fsIndexCollection/>
    <capabilities>
      <capability>
        <inputs/>
        <outputs/>
        <languagesSupported/>
      </capability>
    </capabilities>
    <operationalProperties>
      <modifiesCas>true</modifiesCas>
      <multipleDeploymentAllowed>false</multipleDeploymentAllowed>
      <outputsNewCASes>false</outputsNewCASes>
    </operationalProperties>
  </analysisEngineMetaData>
  <resourceManagerConfiguration/>
</analysisEngineDescription>
Define dynamicField *_mxf in schema.xml:
<dynamicField name="*_mxf" type="text" indexed="true" stored="true"  multiValued="true"/>

Now we update solrconfig.xml to include this UIAM analysis engine.
<updateRequestProcessorChain name="opennlp-uima-soap" default="true">
  <processor class="org.apache.solr.uima.processor.UIMAUpdateRequestProcessorFactory">
    <lst name="uimaConfig">
      <lst name="runtimeParameters">
      </lst>javax.xml.rpc.ServiceException
      <str name="analysisEngine">%REPLACE_THIS%\AggragateOpenNLPSOAPService.xml</str>
      <bool name="ignoreErrors">false</bool>
      <lst name="analyzeFields">
        <bool name="merge">false</bool>
        <arr name="fields">
          <str>content</str>
        </arr>
      </lst>
      <lst name="fieldMappings">
        <lst name="type">
          <str name="name">opennlp.uima.Date</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">date_mxf</str>
          </lst>
        </lst>           
        <lst name="type">
          <str name="name">opennlp.uima.Location</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">location_mxf</str>
          </lst>
        </lst> 

        <lst name="type">
          <str name="name">opennlp.uima.Money</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">money_mxf</str>
          </lst>
        </lst>
        <lst name="type">
          <str name="name">opennlp.uima.Organization</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">organization_mxf</str>
          </lst>
        </lst>
        <lst name="type">
          <str name="name">opennlp.uima.Percentage</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">percentage_mxf</str>
          </lst>
        </lst> 
        <lst name="type">
          <str name="name">opennlp.uima.Sentence</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">sentence_mxf</str>
          </lst>
        </lst> 
        <lst name="type">
          <str name="name">opennlp.uima.Time</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">time_mxf</str>
          </lst>
        </lst> 
        <lst name="type">
          <str name="name">opennlp.uima.Person</str>
          <lst name="mapping">
            <str name="feature">coveredText</str>
            <str name="field">person_mxf</str>
          </lst>
        </lst>           
      </lst>
    </lst>
  </processor>
  <processor class="solr.LogUpdateProcessorFactory" />
  <processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain> 
After that we can call 
http://localhost:8080/solr/update?update.chain=opennlp-uima-soap&commit=true&stream.body=<add><doc><field name="id">1</field><field name="content">some text here</field></doc></add>

Then run http://localhost:8080/solr/select?q=id:1, we can see it extracts some entity like organization, person name, location, time, date, money, percentage, etc.

Labels

ANT (6) Algorithm (69) Algorithm Series (35) Android (7) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) JSON (7) Java (186) JavaScript (27) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) adsense (5) bat (8) regex (5) xml (5)