iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
自我挑戰組

基於自然語言處理的新聞意見提取應用開發筆記系列 第 15

[Day-15] 將意見提取標註導入到 Label Studio

  • 分享至 

  • xImage
  •  

Day-15 內容


以 Spacy 的 DependencyMatcher 找出意見持有者、動詞、句子範圍

本次使用新聞範例一樣式前幾天就出現過的 接手中介法草案?唐鳳:監理業務不屬於數位部

用於範例的新聞內容

將本篇新聞內容以 List[str] 表示:

content = ['媒體關注數位部部長唐鳳對數位中介服務法看法,唐鳳表示,監理業務不屬於數位部範圍,面對大型跨境數位平台,最重要的是要確保現實世界中覺得合理的價值,平台上也應該符合相關的社會價值,遵守常規。',
 '唐鳳今天中午接受震傳媒網路節目「新聞不芹菜」的視訊採訪,主持人黃光芹詢問唐鳳對於數位中介服務法(中介法)看法,是否反對立法。',
 '唐鳳表示,中介法主要是監理非法言論部份,在數位部揭牌當天就提到,數位部的工作是扮演採油門角色,監理部份並不是數位部業務。',
 '唐鳳直言,NCC日前提出數位中介服務法草案,還在NCC對外界徵詢草案的階段,目前NCC也提到會整理相關民間意見、歸零思考,把草案退回到工作小組。草案先前也還沒有提到政院討論,沒有要表態的問題。',
 '唐鳳指出,面對跨境大型數位平台時,最重要價值是要確保現實社會覺得合理的價值,在大型數位平台上也應該符合相關的社會價值。',
 '唐鳳舉例,監察院會公布競選期間政治獻金花費,選罷法也規範境外者不能給予候選人政治獻金,必須要符合透明度以及不能收國外的錢。前幾年有人透過數位平台下廣告、幫特定候選人打廣告,等於繞過政治獻金法的規範。',
 '唐鳳指出,後來也有跟平台溝通,不管在美國是什麼樣的規範,在台灣就是要揭露、不能收國外的錢,不能到平台上就不遵守相關常規。平台後來在2019年做修正,舉這個例子是要說明,還是要從社會怎麼樣合適出發,這些跨境科技平台要配合社會價值,而不是擾亂社會價值、反過來要求配合平台。']

方法程式碼與更動講解

這一段採用 [Day-13] 以 Spacy 的 DependencyMatcher 找出意見持有者、動詞、句子範圍 所介紹的方法,將其做些更動變成下面的程式碼:

import stanza
import spacy_stanza
from ckip_transformers.nlp import CkipPosTagger, CkipNerChunker
import spacy
from spacy import displacy
from spacy.tokens import Span
from spacy.matcher import DependencyMatcher
from itertools import chain
import json
import uuid

stanza.download("zh-hant")
nlp = spacy_stanza.load_pipeline("xx", lang='zh-hant')

def add_ner(doc):
    ner_driver = CkipNerChunker(model="bert-base")
    ner = ner_driver([str(doc)], show_progress=False)
    ner_spans = []
    for entity in ner[0]:
        span = doc.char_span(entity.idx[0], entity.idx[1], label=entity.ner)
        if span is None:
           span = doc.char_span(entity.idx[0], entity.idx[1] + 1, label=entity.ner) 
        ner_spans.append(span)
    orig_ents = list(doc.ents)
    doc.ents = orig_ents + ner_spans
    
def add_ckip_tag(doc):
    pos_driver = CkipPosTagger(model="bert-base")
    words = [[str(token) for token in doc]]
    pos = pos_driver(words, show_progress=False)
    for token, ckip_pos in zip(doc, pos[0]):
        token.tag_ = ckip_pos

version = "v0"    

pattern = [
  # anchor token: VE
  {
    "RIGHT_ID": "VE",
    "RIGHT_ATTRS": {"TAG":  "VE"}
  },
  # founded -> subject
  {
    "LEFT_ID": "VE",
    "REL_OP": ">",
    "RIGHT_ID": "who_root",
    "RIGHT_ATTRS": {"DEP": "nsubj"}
  },
  # "founded" follows "initially"
  {
    "LEFT_ID": "VE",
    "REL_OP": ">",
    "RIGHT_ID": "idea_root",
    "RIGHT_ATTRS": {"DEP": {"IN": ["ccomp", "parataxis"]}}
  }
]

matcher = DependencyMatcher(nlp.vocab, validate=True)
matcher.add(f"{version}", [pattern])

pre_annotations = []
final_docs = []

for doc in nlp.pipe(content):

      pre_annotation_data = {
          "data": {
              "title": title,
              "paragraph": str(doc)
          },
          "predictions": [
            {
              "model_version": f"{version}",
              "result": []
            }
          ]
      } 

      add_ner(doc)
      add_ckip_tag(doc)
      
      
      matches = matcher(doc)
      matches_sorted = sorted(matches, key=lambda x: abs(x[1][0] - x[1][1]))
      if len(matches_sorted) > 1:
        matches_sorted = [match for match in matches_sorted if (match[1][0] == matches_sorted[0][1][0] and match[1][1] == matches_sorted[0][1][1])]
      
      if len(matches_sorted) > 0:
        first_match = matches_sorted[0]
        VE_id = first_match[1][0]
        who_root_id = first_match[1][1]

        VE_span = Span(doc, VE_id, VE_id+1, label="VERB")
        who_root_span = Span(doc, doc[who_root_id].left_edge.i, doc[who_root_id].right_edge.i+1, label="WHO")

        idea_spans = []
        for match in matches_sorted:
          match_id, token_ids = match
          
          idea_root_id = token_ids[2]
          idea_spans.append(Span(doc, doc[idea_root_id].left_edge.i, doc[idea_root_id].right_edge.i+1, label="OPINION"))

        
        doc.spans["sc"] = spacy.util.filter_spans([VE_span, who_root_span] + idea_spans)
        for span in doc.spans["sc"]:
          print(span.text, span.label_)

          pre_annotation_data["predictions"][0]["result"].append(
            {
              "id": str(uuid.uuid1()),
              "from_name": "opinion_label",
              "to_name": "a_paragraph",
              "type": "labels",
              "value": {
                "start": span.start_char,
                "end": span.end_char,
                "text": span.text,
                "labels": [
                  span.label_
                ]
              }
            }
          )
      else:
        doc.spans["sc"] = []
        
      final_docs.append(doc)
  
      pre_annotations.append(pre_annotation_data)

html = displacy.render(final_docs, style="span", jupyter=False, page=True)
file_name = f"opinion_html/{version}/{title}.html"
with open(f"{file_name}", "w") as f:
    f.write(html)

with open(f"pre_annotations/{version}/{title}.json", "w") as f:
  json.dump(pre_annotations, f, ensure_ascii=False)

上方程式碼在意見持有者、意見動詞、意見句的標注任務上添加下列新功能:

  1. 賦予目前的意見持有者、意見動詞、意見句的標注方法一個版本號:

    • 透過 version = "v0" 來設定
    • 用於 json 與 html 儲存時區別儲存的路徑
  2. 將意見持有者、意見動詞、意見句的標注存成 html

    • final_docs = [] 初始化一個用於儲存 doc 的 list 。
    • 透過 doc in nlp.pipe(content) 迴圈,doc 將依序被賦予 content 中的每一段文字內容,並在 final_docs.append(doc) 的位置被加入到 final_docs
    • 透過 html = displacy.render(final_docs, style="span", jupyter=False, page=True)final_docs 中每個 doc 的標注結果轉成 html,再透過以下程式碼儲存:
      file_name = f"opinion_html/{version}/{title}.html"
      with open(f"{file_name}", "w") as f:
          f.write(html)
      
  3. 將意見持有者、意見動詞、意見句的標注以 Label Studio 的輸入格式存成 json

    • pre_annotations = [] 初始化一個用於儲存整篇新聞標註結果的 list。
    • 以下程式碼初始化一個用於儲存一段文字的所有標註的結構:
      pre_annotation_data = {
           "data": {
               "title": title,
               "paragraph": str(doc)
           },
           "predictions": [
             {
               "model_version": f"{version}",
               "result": []
             }
           ]
       } 
      
    • 下面程式碼將一個 span 標註存入上方的結構當中:
      pre_annotation_data["predictions"][0]["result"].append(
          {
            "id": str(uuid.uuid1()),
            "from_name": "opinion_label",
            "to_name": "a_paragraph",
            "type": "labels",
            "value": {
              "start": span.start_char,
              "end": span.end_char,
              "text": span.text,
              "labels": [
                span.label_
              ]
            }
          }
        )
      
    • pre_annotations.append(pre_annotation_data) 將一段文字的的標注結果存到整篇新聞標註結果的 pre_annotations
    • 下面的程式碼將 pre_annotations 所儲存的標注結果儲存成 json
      with open(f"pre_annotations/{version}/{title}.json", "w") as f:
        json.dump(pre_annotations, f, ensure_ascii=False)
      

方法標注結果

點選此連結,以網頁形式查看標注結果

以 Label Studio 輸入格式表示的標注結果

觀察用上面程式碼所產生的 json,每段文字內容都會有以下結構:

  • data(包含要在 Label Studio 中顯示的資訊)
  • predictions(包含 data 的標注結果)
    • model_version(方法版本號)
    • result(包含每個筆標註的資訊)
[
    {
        "data": {
            "title": "接手中介法草案?唐鳳:監理業務不屬於數位部",
            "paragraph": "媒體關注數位部部長唐鳳對數位中介服務法看法,唐鳳表示,監理業務不屬於數位部範圍,面對大型跨境數位平台,最重要的是要確保現實世界中覺得合理的價值,平台上也應該符合相關的社會價值,遵守常規。"
        },
        "predictions": [
            {
                "model_version": "v0",
                "result": [
                    {
                        "id": "c4db7030-40d0-11ed-98a8-acde48001122",
                        "from_name": "opinion_label",
                        "to_name": "a_paragraph",
                        "type": "labels",
                        "value": {
                            "start": 22,
                            "end": 24,
                            "text": "唐鳳",
                            "labels": [
                                "WHO"
                            ]
                        }
                    },
                    {
                        "id": "c4db72f6-40d0-11ed-98a8-acde48001122",
                        "from_name": "opinion_label",
                        "to_name": "a_paragraph",
                        "type": "labels",
                        "value": {
                            "start": 24,
                            "end": 26,
                            "text": "表示",
                            "labels": [
                                "VERB"
                            ]
                        }
                    },
                    {
                        "id": "c4db74e0-40d0-11ed-98a8-acde48001122",
                        "from_name": "opinion_label",
                        "to_name": "a_paragraph",
                        "type": "labels",
                        "value": {
                            "start": 27,
                            "end": 92,
                            "text": "監理業務不屬於數位部範圍,面對大型跨境數位平台,最重要的是要確保現實世界中覺得合理的價值,平台上也應該符合相關的社會價值,遵守常規",
                            "labels": [
                                "OPINION"
                            ]
                        }
                    }
                ]
            }
        ]
    },
    {
        "data": {
            "title": "接手中介法草案?唐鳳:監理業務不屬於數位部",
            "paragraph": "唐鳳今天中午接受震傳媒網路節目「新聞不芹菜」的視訊採訪,主持人黃光芹詢問唐鳳對於數位中介服務法(中介法)看法,是否反對立法。"
        },
        "predictions": [
            {
                "model_version": "v0",
                "result": []
            }
        ]
    },
    ...
    省略
    ...
]

將標注結果導入 Label Studio

啟動 Label Studio

label-studio start

Create Project

導入剛剛產生的 json

  1. 點選 Upload Files
  2. 選擇 剛剛產生的 json
  3. 按右上角的藍色 Save
    https://ithelp.ithome.com.tw/upload/images/20220930/20152690x6pJd2TqM2.png
    https://ithelp.ithome.com.tw/upload/images/20220930/20152690Lm59nouzFD.png

更改 Template

  1. 點選右上角的 Settings
  2. 點選左側選單的 Labeling Interface
  3. 點選 code,並且替換為下面的 template
<View>
  <Labels name="opinion_label" toName="a_paragraph">
    <Label value="VERB" background="red"/>
    <Label value="OPINION" background="orange"/>
    <Label value="WHO" background="green"/>
  </Labels>
  <Header value="$title"/>
  <Text name="a_paragraph" value="$paragraph"/>
</View>
  1. 記得按 Save

查看導入的標注結果

回到主頁面,點選其中一個即可查看標注結果
https://ithelp.ithome.com.tw/upload/images/20220930/20152690aPxui4f5Gb.png
https://ithelp.ithome.com.tw/upload/images/20220930/201526902Iq3nTXuh0.png


上一篇
[Day-14] 開始使用 Label Studio
下一篇
[Day-16] 改進 Label Studio 的標註方式(達到在同一頁面中標註每個新聞段落)
系列文
基於自然語言處理的新聞意見提取應用開發筆記17
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言