Stanford CoreNLP находит однородные части предложения

Я пытаюсь построить алгоритм упрощения предложений на основе Stanford CoreNLP. Одно из упрощений я хочу сделать - преобразовать предложение с однородными частями предложения в несколько предложений. Например.

Я люблю маму, папу и сестру. -> Я люблю свою маму. Я люблю своего папу. Я люблю свою сестру.

Прежде всего я строю семантический граф для входной строки предложения

    final Sentence parsed = new Sentence(sentence);
    final SemanticGraph dependencies = parsed.dependencyGraph();

График зависимости для этого предложения

-> love/VBP (root)
  -> I/PRP (nsubj)
  -> mom/NN (dobj)
    -> my/PRP$ (nmod:poss)
    -> ,/, (punct)
    -> dad/NN (conj:and)
    -> and/CC (cc)
    -> sister/NN (conj:and)
  -> dad/NN (dobj)
  -> sister/NN (dobj)

Затем я нашел dobj ребер в графе и nsubj

for (SemanticGraphEdge edge : dependencies.edgeListSorted()) {
        if (edge.getRelation().getShortName().startsWith("dobj")) {
            modifiers.add(edge);
        } else if (edge.getRelation().getShortName().startsWith("nsubj")) {
            subj = edge;
        }
    }

Итак, теперь у меня есть 3 ребра в modifiers и nsubj со словом I. И теперь моя проблема заключается в том, как разделить семантический граф на 3 отдельных графика. Конечно, наивным решением было просто построить базу предложений на основе subj и губернатора/зависимого от dobj ребер, но я понимаю, что это плохая идея и не будет работать на более сложных примерах.

for (final SemanticGraphEdge edge : modifiers) {
                SemanticGraph semanticGraph = dependencies.makeSoftCopy();
                final IndexedWord governor = edge.getGovernor();
                final IndexedWord dependent = edge.getDependent();

                final String governorTag = governor.backingLabel().tag().toLowerCase();
                if (governorTag.startsWith("vb")) {
                    StringBuilder b = new StringBuilder(subj.getDependent().word());
                    b.append(" ")
                            .append(governor.word())
                            .append(" ")
                            .append(dependent.word())
                            .append(". ");
                    System.out.println(b);

                }
            }

Кто-нибудь может дать мне несколько советов? Может быть, я пропустил что-то полезное в документации coreNLP? Спасибо.


person Danila Zharenkov    schedule 23.05.2019    source источник
comment
Играю в адвоката дьявола: как вы упростите мою маму и папу, любящих меня и мою сестру? Вместо этого рассматривали ли вы попытку извлечь семантические отношения (очень сложные сами по себе), о которых вы заботитесь, а затем иметь простые шаблоны, которые вы можете создавать с помощью извлеченной информации?   -  person Josep Valls    schedule 23.05.2019
comment
@JosepValls, конечно, желаемый результат для вашего примера будет 4 предложения, моя мама любит меня, моя мама любит мою сестру и так далее. И я, конечно, понимаю, что это почти невозможно. Под шаблонами вы подразумеваете, что мне нужно построить схему части речи и искать что-то вроде NN, NN, NN?   -  person Danila Zharenkov    schedule 23.05.2019


Ответы (1)


Спасибо @JosepValls за прекрасную идею. Вот несколько примеров кода, как я упрощаю предложения с 3 или более однородными словами.

Прежде всего, я определил несколько регулярных выражений для случаев

jj(optional) nn, jj(optional) nn, jj(optional) nn and jj(optional) nn
jj(optional) nn, jj(optional) nn, jj(optional) nn , jj(optional) nn ...
jj , jj , jj
jj , jj and jj
vb nn(optional) , vb nn(optional) , vb nn(optional)
 and  so on

Регулярные выражения

Pattern nounAdjPattern = Pattern.compile("(((jj)\\s(nn)|(jj)|(nn))\\s((cc)|,)\\s){2,}((jj)\\s(nn)|(jj)|(nn))");
Pattern verbPatter = Pattern.compile("((vb\\snn|vb)\\s((cc)|,)\\s){2,}((vb\\snn)|vb)");

Этот шаблон будет использоваться для определения того, есть ли во входном предложении список однородных слов или нет, и для нахождения границ. После этого я создаю список поз на основе слов из исходного предложения.

final Sentence parsed = new Sentence(sentence);
final List<String> words = parsed.words();
List<String> pos = parsed.posTags().stream()
        .map(tag -> tag.length() < 2 ? tag.toLowerCase() : tag.substring(0, 2).toLowerCase())
        .collect(Collectors.toList());

Чтобы сопоставить эту структуру POS с регулярными выражениями - соедините список со строкой

String posString = pos.stream().collect(Collectors.joining(" "));

Если предложение не соответствует ни одному регулярному выражению - давайте вернем ту же строку, иначе - давайте упростим.

if (!matcher.find()) {
    return new SimplificationResult(Collections.singleton(sentence));
}
return new SimplificationResult(simplify(posString, matcher, words));

В методе упрощения я ищу границы однородной части и извлекаю из списка слов 3 части - начало и окончание, которые не изменятся, и однородную часть, которая будет разбиваться на части. А после разбиения однородной части на части - строю несколько упрощенных предложений типа начало+кусок+окончание.

 private Set<String> simplify(String posString, Matcher matcher, List<String> words) {
        String startPOS = posString.substring(0, matcher.start());
        String endPPOS = posString.substring(matcher.end());
        int wordsBeforeCnt = StringUtils.isEmpty(startPOS) ? 0 : startPOS.trim().split("\\s+").length;
        int wordsAfterCnt = StringUtils.isEmpty(endPPOS) ? 0 : endPPOS.trim().split("\\s+").length;
        String wordsBefore = words.subList(0, wordsBeforeCnt)
                .stream()
                .collect(Collectors.joining(" "));
        String wordsAfter = words.subList(words.size() - wordsAfterCnt, words.size())
                .stream()
                .collect(Collectors.joining(" "));
        List<String> homogeneousPart = words.subList(wordsBeforeCnt, words.size() - wordsAfterCnt);
        Set<String> splitWords = new HashSet<>(Arrays.asList(",", "and"));
        Set<String> simplifiedSentences = new HashSet<>();
        StringBuilder sb = new StringBuilder(wordsBefore);
        for (int i = 0; i < homogeneousPart.size(); i++) {
            String part = homogeneousPart.get(i);
            if (!splitWords.contains(part)) {
                sb.append(" ").append(part);
                if (i == homogeneousPart.size() - 1) {
                    sb.append(" ").append(wordsAfter).append(" ");
                    simplifiedSentences.add(sb.toString());
                }
            } else {
                sb.append(" ").append(wordsAfter).append(" ");
                simplifiedSentences.add(sb.toString());
                sb = new StringBuilder(wordsBefore);
            }
        }
        return simplifiedSentences;

Так, например. приговор

 I love and kiss and adore my beautiful mom, clever dad and sister.

будет упрощен до 9 предложений, если мы используем 2 регулярных выражения выше

I adore my clever dad . 
I love my clever dad . 
I love my sister . 
I kiss my sister . 
I kiss my clever dad . 
I adore my sister . 
I love my beautiful mom . 
I adore my beautiful mom . 
I kiss my beautiful mom . 

Этот код работает только с 3 и более однородными словами, потому что для 2 слов есть много исключений. Например.

Cat eats mouse, dog eats meat.

Чем предложение не может быть упрощено таким образом.

person Danila Zharenkov    schedule 24.05.2019