LoginSignup
2

More than 5 years have passed since last update.

素人の言語処理100本ノック:60

Last updated at Posted at 2017-01-09

言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。

第7章: データベース

artist.json.gzは,オープンな音楽データベースMusicBrainzの中で,アーティストに関するものをJSON形式に変換し,gzip形式で圧縮したファイルである.このファイルには,1アーティストに関する情報が1行にJSON形式で格納されている.JSON形式の概要は以下の通りである.

フィールド 内容
id ユニーク識別子 整数 20660
gid グローバル識別子 文字列 "ecf9f3a3-35e9-4c58-acaa-e707fba45060"
name アーティスト名 文字列 "Oasis"
sort_name アーティスト名(辞書順整列用) 文字列 "Oasis"
area 活動場所 文字列 "United Kingdom"
aliases 別名 辞書オブジェクトのリスト
aliases[].name 別名 文字列 "オアシス"
aliases[].sort_name 別名(整列用) 文字列 "オアシス"
begin 活動開始日 辞書
begin.year 活動開始年 整数 1991
begin.month 活動開始月 整数
begin.date 活動開始日 整数
end 活動終了日 辞書
end.year 活動終了年 整数 2009
end.month 活動終了月 整数 8
end.date 活動終了日 整数 28
tags タグ 辞書オブジェクトのリスト
tags[].count タグ付けされた回数 整数 1
tags[].value タグ内容 文字列 "rock"
rating レーティング 辞書オブジェクト
rating.count レーティングの投票数 整数 13
rating.value レーティングの値(平均値) 整数 86

artist.json.gzのデータをKey-Value-Store (KVS) およびドキュメント志向型データベースに格納・検索することを考える.KVSとしては,LevelDB,Redis,KyotoCabinet等を用いよ.ドキュメント志向型データベースとして,MongoDBを採用したが,CouchDBやRethinkDB等を用いてもよい.

60. KVSの構築

Key-Value-Store (KVS) を用い,アーティスト名(name)から活動場所(area)を検索するためのデータベースを構築せよ.

出来上がったコード:

main.py
# coding: utf-8
import gzip
import json
import leveldb

fname = 'artist.json.gz'
fname_db = 'test_db'

# LevelDBオープン、なければ作成
db = leveldb.LevelDB(fname_db)

# gzファイル読み込み、パース
with gzip.open(fname, 'rt') as data_file:
    for line in data_file:
        data_json = json.loads(line)

        # key=name+id、value=areaとしてDBへ追加
        key = data_json['name'] + '\t' + str(data_json['id'])
        value = data_json.get('area', '')       # areaはないことがある
        db.Put(key.encode(), value.encode())

# 確認のため登録件数を表示
print('{}件登録しました。'.format(len(list(db.RangeIter(include_value=False)))))

実行結果:

実行結果
921337件登録しました。

LevelDBについて

今回はKVSとしてLevelDBを使うことにしました。LevelDBはGoogleがBigTableのアーキテクチャをベースにOSS化したKVSだそうです。オフィシャルサイトはこちらです。日本語での解説は、yo-chanさんのブログのLevelDB入門 (基本編)が非常に分かりやすかったです。

LevelDBのインストール

問題に取り組む前に環境構築が必要です。インストールはcondaで見つかったので簡単にできました。LevelDBをpythonで使うためのpython-leveldbも同時に見つかります。

LevelDBのインストール
segavvy@ubuntu:~$ conda search leveldb
Fetching package metadata .......
leveldb                      1.19                          0  defaults        
python-leveldb               0.194                    py27_0  defaults        
                             0.194                    py34_0  defaults        
                             0.194                    py35_0  defaults        
segavvy@ubuntu:~$ conda install leveldb
Fetching package metadata .......
Solving package specifications: ..........

Package plan for installation in environment /home/segavvy/anaconda3:

The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    snappy-1.1.3               |                0         214 KB
    leveldb-1.19               |                0         320 KB
    conda-4.2.13               |           py35_0         402 KB
    ------------------------------------------------------------
                                           Total:         936 KB

The following NEW packages will be INSTALLED:

    leveldb: 1.19-0       
    snappy:  1.1.3-0      

The following packages will be UPDATED:

    conda:   4.2.12-py35_0 --> 4.2.13-py35_0

Proceed ([y]/n)? y

Fetching packages ...
snappy-1.1.3-0 100% |################################| Time: 0:00:00 436.20 kB/s
leveldb-1.19-0 100% |################################| Time: 0:00:00 586.67 kB/s
conda-4.2.13-p 100% |################################| Time: 0:00:00   8.57 MB/s
Extracting packages ...
[      COMPLETE      ]|############################################################| 100%
Unlinking packages ...
[      COMPLETE      ]|############################################################| 100%
Linking packages ...
[      COMPLETE      ]|############################################################| 100%
python-leveldbのインストール
segavvy@ubuntu:~$ conda install python-leveldb
Fetching package metadata .......
Solving package specifications: ..........

Package plan for installation in environment /home/segavvy/anaconda3:

The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    python-leveldb-0.194       |           py35_0          75 KB

The following NEW packages will be INSTALLED:

    python-leveldb: 0.194-py35_0

Proceed ([y]/n)? y

Fetching packages ...
python-leveldb 100% |#########################################| Time: 0:00:00 459.40 kB/s
Extracting packages ...
[      COMPLETE      ]|############################################################| 100%
Linking packages ...
[      COMPLETE      ]|############################################################| 100%

これでimportできるようになります。

LevelDBインストールの確認
segavvy@ubuntu:~$ python
Python 3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  2 2016, 17:53:06) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import leveldb
>>> 

LevelDBの使い方

LevelDBは非常にシンプルなので、helpでだいたい使い方が把握できました。

LevelDBのヘルプの表示
segavvy@ubuntu:~$ python 
Python 3.5.2 |Anaconda 4.1.1 (64-bit)| (default, Jul  2 2016, 17:53:06) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import leveldb
>>> help(leveldb)
LevelDBのヘルプ(先頭部分)
Help on module leveldb:

NAME
    leveldb

CLASSES
    builtins.Exception(builtins.BaseException)
        LevelDBError
    builtins.object
        LevelDB
        Snapshot
        WriteBatch

    class LevelDB(builtins.object)
     |  LevelDB(filename, **kwargs) -> leveldb object
     |  
     |  Open a LevelDB database, from the given directory.
     |  
     |  Only the parameter filename is mandatory.
     |  
     |  filename                                    the database directory
     |  create_if_missing (default: True)           if True, creates a new database if none exists
     |  error_if_exists   (default: False)          if True, raises and error if the database already exists
     |  paranoid_checks   (default: False)          if True, raises an error as soon as an internal corruption is detected
     |  block_cache_size  (default: 8 * (2 << 20))  maximum allowed size for the block cache in bytes
     |  write_buffer_size (default  2 * (2 << 20))  
     |  block_size        (default: 4096)           unit of transfer for the block cache in bytes
     |  max_open_files:   (default: 1000)
     |  block_restart_interval           
     |  
     |  Snappy compression is used, if available.
     |  
     |  Some methods support the following parameters, having these semantics:
     |  
     |   verify_checksum: iff True, the operation will check for checksum mismatches
     |   fill_cache:      iff True, the operation will fill the cache with the data read
     |   sync:            iff True, the operation will be guaranteed to sync the operation to disk
     |  
     |  Methods supported are:
     |  
     |   Get(key, verify_checksums = False, fill_cache = True): get value, raises KeyError if key not found
     |  
     |      key: the query key
     |  
     |   Put(key, value, sync = False): put key/value pair
     |  
     |      key: the key
(以下略)

実装の流れは、まずleveldb.LevelDB()で保存先のディレクトリを指定してデータベースを作ります。デフォルトでは存在しなければ新規作成してくれます。あとは、LevelDB.Put()でkeyとvalueのペアを追加していくだけです。

活動場所の欠落

このデータの活動場所(area)の情報は、半分弱のアーティストにしか登録されていないようです。そのため、活動場所が登録されていないアーティストに関しては空文字列を登録するようにしました。

アーティスト名の重複

最初はアーティスト名が重複していることに気づかずに、keyとしてアーティスト名、valueとして活動場所を登録する、次のようなコードにしていました。

アーティスト名の重複を考慮していなかったコード
# gzファイル読み込み、パース
with gzip.open(fname, 'rt') as data_file:
    for line in data_file:
        data_json = json.loads(line)

        # nameとareaをDBへ追加
        name = data_json['name']            # nameはなければエラー
        value = data_json.get('area', '')       # areaはないことがある
        db.Put(name.encode(), area.encode())

しかし、対象データには問題の例で使われているOasisでさえ3件も存在していることに気づきました。
LevelDB.Put()はkeyが同じ場合にvalueを更新してしまうため、重複していると後勝ちになってしまい、先に登録した情報が欠落してしまいます。

そのため今回は、keyにアーティスト名とユニーク識別子(id)を\tで連結したものを使う形にしてみました。

文字列とバイト列の変換

LebelDBは、keyとvalueをどちらもバイト列で指定する必要があります。そのため、文字列からバイト列への変換が必要です。

文字列からバイト列への変換はstr.encode()でできます。デフォルトではUTF-8にしてくれます。逆にUTF-8のバイト列から文字列には、bytes.decode()で戻せます。

登録の確認

今回の問題は登録のみで、登録できているかどうかの確認は次の問題になりますが、最後のLevelDB.RangeIter()で登録内容全件にアクセスするためのイテレータを取得して、件数だけ表示してみました。件数を取得したいだけなので、include_value=Falseを指定して、keyのみを取得しています。

 
61本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。


実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第7章で用いているデータのライセンスはクリエイティブ・コモンズ 表示 - 非営利 - 継承 3.0 非移植日本語訳)です。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2