GPLinker: Joint Event Extraction Based on GlobalPointer
About two years ago, I first encountered the event extraction task in Baidu's "2020 Language and Intelligence Technology Competition," and in the post "With bert4keras in hand, I've got my baseline: Baidu LIC2020" I shared a simple baseline that converted the task into NER via BERT+CRF. But that baseline was really more of a half-finished thing thrown together to fill a slot, not a proper event extraction model. Over the past two years, relation extraction models have come thick and fast, with one SOTA after another, but event extraction hasn't really seen many exciting designs.
Recently I revisited the event extraction task, and building on my earlier relation extraction model GPLinker, combined with a complete-subgraph search, I designed a fairly simple yet reasonably comprehensive joint event extraction model — which I'm still calling GPLinker. I'd welcome everyone's comments on it.
Task Overview
Event extraction is a fairly comprehensive task. A standard event extraction sample looks like this:
A standard event extraction sample (image from Baidu DuEE's GitHub)more
Each event has an event type and a corresponding trigger word, along with arguments in various roles. The event type and argument roles are chosen from a pre-defined, finite set (the schema), while the trigger word and arguments are usually spans of the input sentence, though in a minority of cases they can also be enumerable classification targets (as seen in Baidu's DuEE-fin). In principle, the design of an event extraction model depends on the evaluation metric. In LIC2020, we were able to convert event extraction into an NER problem precisely because the evaluation metric at the time only examined triples of the form (event type, argument role, argument), which meant we could combine (event type, argument role) into one big category and thereby map it onto NER.
Of course, that was really just a shortcut tailored to that particular metric. For real-world event extraction scenarios, what we actually want is to extract events in their standard format — that is, to design a model that is as comprehensive as possible. Below I'll introduce the GPLinker model we use for event extraction, which largely meets the requirement of being both simple and comprehensive.
Unifying Arguments
We've repeatedly used the word "comprehensive" — what exactly do we mean by it? Specifically, we want the model we ultimately design to be applicable, at least in theory, to as many event extraction scenarios as possible. Traditional event extraction models are generally split into four subtasks: "trigger detection," "event/trigger type recognition," "event argument detection," and "argument role recognition." That is, you first detect the trigger word and then do further processing based on it — so if the training set doesn't annotate trigger words, this approach simply can't work. This shows that the traditional line of thinking isn't comprehensive enough.
To unify scenarios with and without trigger words, here we treat the trigger word itself as just another argument role of the event. This way, having or not having a trigger word just amounts to adding or removing one argument, so the crux of the matter really comes down to argument recognition and event partitioning. For argument recognition, we again combine (event type, argument role) into one big category and turn it into an NER problem. Note, however, that different entities may be nested, so the CRF-based NER we used before is actually insufficient here; instead we use GlobalPointer, which can recognize nested entities.
As mentioned earlier, tasks like DuEE-fin also feature classification-style argument types, where the argument isn't a span of the input text but rather one choice out of a finite set. Since there aren't many such argument types, we convert them into extraction-style arguments as well. Take DuEE-fin as an example: for the "环节" (phase) argument of the "公司上市" (company listing) event type, the four candidate values are "preparing for listing," "listing suspended," "officially listed," and "listing terminated." Rather than treating "环节" as an argument type, we treat "preparing for listing," "listing suspended," "officially listed," and "listing terminated" each as a separate argument type, with the corresponding entity being the trigger word. This way, instead of extracting a classification-style argument (company listing, phase, XX-listed), we extract an extraction-style argument (company listing, XX-listed, trigger word), and then convert it back during the model's post-processing stage.
Complete Subgraphs
As for event partitioning, a natural first idea is to simply aggregate all arguments sharing the same event type into a single event. But this isn't comprehensive enough either, because the same input may contain multiple events of the same type. What if we add trigger words into the mix? Still not enough — multiple events of the same type may even share the same trigger word. For instance, one DuEE sample reads "主要成员程杰、王绍伟被法院一审判处有期徒刑22年和20年" ("Key members Cheng Jie and Wang Shaowei were sentenced by the court in the first instance to 22 years and 20 years in prison respectively"), which contains two separate events, "Cheng Jie sentenced to 22 years in prison" and "Wang Shaowei sentenced to 20 years in prison," both sharing the trigger word "有期徒刑" (imprisonment) and the same event type "入狱" (imprisonment).
So we need to design an additional module for event partitioning. Our idea is that the various arguments of the same event are connected to one another, and this connectivity can be described with an undirected graph. That is, we treat each argument as a node in a graph, and any two arguments belonging to the same event can be connected by an edge, becoming adjacent nodes; if two arguments never co-occur in the same event, then there is no edge between the corresponding nodes (they are not adjacent). Consequently, any two nodes belonging to the same event are mutually adjacent — what we call a "complete graph," also known as a "clique" — and event partitioning is thereby reduced to searching for complete subgraphs in the graph.
Example of complete subgraphs in a directed graph
So how do we construct this undirected graph? We follow the approach of TPLinker: if two argument entities are related, then their (head, head) and (tail, tail) positions should both match. Just as in relation extraction with GPLinker, we can use GlobalPointer to predict these matching relations. In particular, since we only need to construct an undirected graph, we can mask out the lower-triangular part, describing all edges purely through the upper-triangular part.
Search Algorithm
Suppose we already have the directed graph describing argument relations — how do we search for all complete subgraphs? This looks a bit like graph partitioning, but it isn't quite the same, because in our setting nodes can be reused, meaning the same entity can simultaneously be an argument of multiple different events. For example, the 8 nodes in the figure above yield two complete subgraphs, where node $D$ appears in both subgraphs simultaneously — meaning we can identify two events that share a common argument $D$.
After some analysis, I came up with the following recursive search algorithm:
1. Enumerate all pairs of nodes in the graph. If every pair of nodes is adjacent, then the graph itself is a complete graph, and we return it directly; if there exists a pair of non-adjacent nodes, proceed to step 2.
2. For each pair of non-adjacent nodes, separately find the set of all nodes adjacent to each one (including itself), forming a subgraph, and then apply step 1 to each of these subgraphs.
Let's again use the figure above as an example. We can find that $B$ and $E$ are a pair of non-adjacent nodes, so we separately find their adjacency sets, $\{A,B,C,D\}$ and $\{D,E,F,G,H\}$, and then continue looking for non-adjacent pairs within $\{A,B,C,D\}$ and $\{D,E,F,G,H\}$. Finding none, we conclude that both $\{A,B,C,D\}$ and $\{D,E,F,G,H\}$ are complete subgraphs. Note that this doesn't depend on the order in which we pick the non-adjacent pair, because we perform the same operation for "every" non-adjacent pair. For instance, if we instead find that $A$ and $F$ are a non-adjacent pair, we likewise find their adjacency sets $\{A,B,C,D\}$ and $\{D,E,F,G,H\}$ and recurse on those. So over the course of the whole process, we may end up with many duplicate results, but we won't miss any result, nor will the search be affected by the order in which pairs are picked — we just deduplicate at the end.
Moreover, each search only needs to be run over nodes of the same event type, and in most cases there are only single digits worth of arguments per event type, so although the algorithm above looks complicated, in practice it runs quite fast.
Experimental Results
We've now finished introducing the design of our event extraction pipeline. In summary, we need a nested entity recognition model to identify arguments, and then, separately, a "head-head" matching model and a "tail-tail" matching model to build the relations between arguments. These modules turn out to coincide remarkably closely with the relation-extraction GPLinker model, and can likewise all be implemented with GlobalPointer — hence we're still calling it "GPLinker." The code is organized here:
GitHub repository: https://github.com/bojone/GPLinker
I ran some simple experiments on DuEE and DuEE-fin, and submitted the results to the Qianyan leaderboard; the results are as follows:
$$\begin{array}{c|ccc} \hline & \text{precision} & \text{recall} & \text{f1} \\ \hline \text{DuEE} & 82.65 & 80.31 & 81.47 \\ \hline \text{DuEE-fin} & 50.35 & 65.19 & 56.82 \\ \hline \end{array}$$
Looking at just these two scores, we'd rank 5th on the leaderboard — not exactly dazzling, and still some distance from 1st place. That said, this post isn't specifically aimed at chasing leaderboard rankings, so I haven't done any further optimization tailored to the competition data; the current results are still reasonably decent. As for why I haven't compared against other event extraction models — it's simply because I'm not very familiar with this area. I skimmed a few relation extraction papers and found the models inside unusually complicated, which didn't leave me particularly motivated to reproduce them.
Finally, the code uses Efficient GlobalPointer throughout. I also compared it against the standard version of GlobalPointer, and found that while the standard version converges faster early on, its final performance is worse. This once again confirms the effectiveness of Efficient GlobalPointer.
Reflections on the Model
In applying GPLinker to event extraction, our goal was simplicity and comprehensiveness — which may not necessarily be the most effective approach in practice. One obvious issue is that it depends fairly heavily on the amount of data, and performance may suffer in low-resource settings. This is essentially a consequence of GPLinker's very comprehensiveness: being comprehensive means having a large degree of freedom (so as to accommodate as many scenarios as possible), and the model doesn't get much prior information to lean on, which increases the learning difficulty.
That said, GPLinker does have the virtue of being simple and efficient, and in theory it doesn't suffer from exposure bias. So in practice, if GPLinker's performance falls short, one viable approach is to first build a model using some other method that can push performance higher (likely at the cost of efficiency), and then distill that model into GPLinker — this way you can have the best of both worlds, effectiveness and efficiency.
Summary
This post introduced the idea of applying GPLinker to event extraction: first extracting arguments via nested entity extraction, and then converting event partitioning into a complete-subgraph search problem. The resulting model is relatively simple and comprehensive, and in theory does not suffer from the exposure bias problem.
Translated automatically with claude-sonnet-5; all equations are reproduced verbatim from the source. Copyright remains with the original author.