Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

모두의 말뭉치: 신문 말뭉치 loader #110

Merged
merged 17 commits into from
Oct 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Korpora/korpora.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ def get_all_texts(self):
def __str__(self):
attributes = ""
for var_name, var in self.__dict__.items():
if var_name not in {'dataname', 'description', 'self'}:
if var_name not in {'name', 'description', 'self'}:
attributes += f' - {self.name}.{var_name} : list[{var[0].__class__.__name__}]\n'
s = f"""{self.name}: size={len(self.texts)}\n{attributes}"""
return s

def __repr__(self):
return self.__str__()


@dataclass
class LabeledSentence:
Expand Down Expand Up @@ -154,7 +157,7 @@ def __str__(self):
classname = self.__class__.__name__
s = f"{classname}\n{self.description}\n\nAttributes\n----------\n"
for var_name, var in self.__dict__.items():
if var_name not in {'description', 'license', 'self'}:
if isinstance(var, KorpusData):
s += f'{str(var)}'
return s

Expand Down
37 changes: 0 additions & 37 deletions Korpora/korpus_modu.py

This file was deleted.

159 changes: 159 additions & 0 deletions Korpora/korpus_modu_news.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import json
import os
import re
from dataclasses import dataclass
from glob import glob
from tqdm import tqdm
from typing import List
from Korpora.korpora import Korpus, KorpusData


description = """ 모두의 말뭉치는 문화체육관광부 산하 국립국어원에서 제공하는 말뭉치로
총 13 개의 말뭉치로 이뤄져 있습니다.

해당 말뭉치를 이용하기 위해서는 국립국어원 홈페이지에 가셔서 "회원가입 > 말뭉치 신청 > 승인"의
과정을 거치셔야 합니다.

https://corpus.korean.go.kr/#none

모두의 말뭉치는 승인 후 다운로드 가능 기간 및 횟수 (3회) 에 제한이 있습니다.

로그인 기능 및 Korpora 패키지에서의 다운로드 기능을 제공하려 하였지만,
국립국어원에서 위의 이유로 이에 대한 기능은 제공이 불가함을 확인하였습니다.

Korpora==0.2.0 에서는 "개별 말뭉치 신청 > 승인"이 완료되었다고 가정,
로컬에 다운로드 된 말뭉치를 손쉽게 로딩하는 기능만 제공할 예정입니다

(Korpora 개발진 lovit@github, ratsgo@github)"""

license = """ 모두의 말뭉치의 모든 저작권은 `문화체육관광부 국립국어원
(National Institute of Korean Language)` 에 귀속됩니다.
정확한 라이센스는 확인 중 입니다."""


class ModuNewsKorpus(Korpus):
def __init__(self, root_dir_or_paths, load_light=True, force_download=False):
super().__init__(description, license)
paths = find_corpus_paths(root_dir_or_paths)
if load_light:
self.train = ModuNewsDataLight('모두의_뉴스_말뭉치(light).train', load_modu_news(paths, load_light))
else:
self.train = ModuNewsData('모두의_뉴스_말뭉치.train', load_modu_news(paths, load_light))
self.row_to_documentid = [news.document_id for news in self.train]
self.documentid_to_row = {document_id: idx for idx, document_id in enumerate(self.row_to_documentid)}


class ModuNewsData(KorpusData):
def __init__(self, name, news):
super().__init__(name, news)
self.document_ids = [doc.document_id for doc in news]
self.titles = [doc.title for doc in news]
self.authors = [doc.author for doc in news]
self.publishers = [doc.publisher for doc in news]
self.dates = [doc.date for doc in news]
self.topics = [doc.topic for doc in news]
self.original_topics = [doc.original_topic for doc in news]
self.texts = [doc.paragraph for doc in news]

def __getitem__(self, index):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 지금 테스트 중인데요. 사용자가 ModuNewsKorpus의 root_dir_or_paths를 잘못 입력하여 데이터가 전혀 로드되지 않을 경우(즉 self.text의 길이가 0) __getitem__이 다음과 같은 에러를 발생시키고 있음을 확인했습니다.

news_corpus.train[0]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/Users/david/works/Korpora/Korpora/korpus_modu_news.py", line 86, in __getitem__
    self.document_ids[index],
IndexError: list index out of range

아울러 같은 상황에서 news_corpus.train을 확인하면 다음과 같은 에러가 뜹니다.

news_corpus.train
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/Users/david/works/Korpora/Korpora/korpora.py", line 32, in __repr__
    return self.__str__()
  File "/Users/david/works/Korpora/Korpora/korpora.py", line 27, in __str__
    attributes += f'  - {self.name}.{var_name} : list[{var[0].__class__.__name__}]\n'
IndexError: list index out of range

따라서 root_dir_or_paths에 있는 모든 파일들을 읽어오는 기존 로직을 타되, 경로 설정 등이 잘못되어 그 내용이 전혀 로드되지 않을 경우에 해당하는 방어 로직을 추가하는 것이 어떨까 합니다.

news = ModuNews(
self.document_ids[index],
self.titles[index],
self.authors[index],
self.publishers[index],
self.dates[index],
self.topics[index],
self.original_topics[index],
self.texts[index].split('\n'))
return news


class ModuNewsDataLight(KorpusData):
def __init__(self, name, news):
super().__init__(name, news)
self.texts = [doc.paragraph for doc in news]
self.titles = [doc.title for doc in news]
self.document_ids = [doc.document_id for doc in news]

def __getitem__(self, index):
news = ModuNewsLight(
self.document_ids[index],
self.titles[index],
self.texts[index])
return news


@dataclass
class ModuNews:
document_id: str
title: str
author: str
publisher: str
date: str
topic: str
original_topic: str
paragraph: List[str]


@dataclass
class ModuNewsLight:
document_id: str
title: str
paragraph: str


def document_to_a_news(document):
document_id = document['id']
meta = document['metadata']
title = meta['title']
author = meta['author']
publisher = meta['publisher']
date = meta['date']
topic = meta['topic']
original_topic = meta['original_topic']
paragraph = '\n'.join([p['form'] for p in document['paragraph']])
return ModuNews(document_id, title, author, publisher, date, topic, original_topic, paragraph)


def document_to_a_news_light(document):
document_id = document['id']
meta = document['metadata']
title = meta['title']
paragraph = '\n'.join([p['form'] for p in document['paragraph']])
return ModuNewsLight(document_id, title, paragraph)


def find_corpus_paths(root_dir_or_paths):
prefix_pattern = re.compile('N[WLPIZ]RW')
def match(path):
prefix = path.split(os.path.sep)[-1][:4]
return prefix_pattern.match(prefix)

# directory + wildcard
if isinstance(root_dir_or_paths, str):
paths = sorted(glob(f'{root_dir_or_paths}/*.json') + glob(root_dir_or_paths))
else:
paths = root_dir_or_paths

paths = [path for path in paths if match(path)]
if not paths:
raise ValueError('Not found corpus files. Check `root_dir_or_paths`')
return paths


def load_modu_news(paths, load_light):
transform = document_to_a_news_light if load_light else document_to_a_news
news = []
for i_path, path in enumerate(tqdm(paths, desc='Loading ModuNews', total=len(paths))):
with open(path, encoding='utf-8') as f:
data = json.load(f)
documents = data['document']
news += [transform(document) for document in documents]
return news


def fetch_modu():
raise NotImplementedError(
"국립국어원에서 API 기능을 제공해 줄 수 없음을 확인하였습니다."
"\n이에 따라 모두의 말뭉치는 fetch 기능을 제공하지 않습니다"
)
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,3 +483,44 @@ koen_news.dev[0]
|---|---|
|text|`ko` 문장|
|pair|`en` 문장|

### 모두의 말뭉치: 신문 말뭉치 (loader)
- author: 국립국어원
- repository: https://corpus.korean.go.kr/
- example
```python
from Korpora.korpus_modu_news import ModuNewsKorpus

news_paths_or_news_dir = 'path/to/NIKL_NEWSPAPER(v1.0)/NPRW190000001*.json' # wildcard
news_paths_or_news_dir = 'path/to/NIKL_NEWSPAPER(v1.0)'

# LOAD ONYL TITLE & PARAGRAPH
news_corpus = ModuNewsKorpus(news_paths_or_news_dir, load_light=True)
news_corpus.train[0]
# ModuNewsLight(document_id='NPRW1900000010.1', title='한국경제 2018년 기사', paragraph='"라니냐로 겨울 가뭄 온다"… ...')
news_corpus.train[0].document_id
# 'NPRW1900000010.1'

# LOAD ALL ATTRIBUTES IN CORPUS
news_corpus = ModuNewsKorpus(news_paths_or_news_dir, load_light=False)
news_corpus.train[0]
# ModuNews(document_id='NPRW1900000010.1', title='한국경제 2018년 기사', author='김현석', publisher='한국경제신문사', date='20180101', topic='생활', original_topic='국제', paragraph=['"라니냐로 겨울 가뭄 온다"…', '...'])

# DOCUMENT ID INDEX
news_corpus.row_to_documentid[:3]
# ['NPRW1900000010.1', 'NPRW1900000010.2', 'NPRW1900000010.3']
news_corpus.documentid_to_row['NPRW1900000010.2']
# 1
```
- data structure

| 속성명 | 내용 |
| --- | --- |
| document_id | 뉴스 고유 아이디 |
| title | metadata 의 title (기사 제목이 아님) |
| author | 기사 작성자 |
| publisher | 언론사 |
| date | 기사 작성 일자 |
| topic | 통합 분류 ((정치, 경제, 사회, 생활, IT/과학, 연예, 스포츠, 문화, 미용/건강) |
| original_topic | 신문 매체의 자체 주제 분류 |
| paragraph | 뉴스 기사 본문 (첫 줄이 기사의 제목으로 추정) |