くろたんく雑記帳

日常とか、わんちゃんとか、機械学習とか、競プロとか、

MENU

GraphQL-based APIによるPDBのデータ取得(Ligand 情報編)

はじめに

はじめて(PDB)https://www.rcsb.org/というタンパク質のDBを触ったが、GUIではなくAPIを利用したデータ取得に少し手間取ったのでメモ。参考は以下

今回は主に、GraphQLによる取得について書く

GraphQL-based API

GraphQLは使ったことがなかったが、JSON形式でqueryを記述することで複雑なデーター構造の所望のデータを取得できるようで、今回の需要にあっていると感じたし、そもそもこっちで取得しろよっていう雰囲気を感じた。

https://data.rcsb.org/#data-api https://data.rcsb.org/migration-guide.html#legacy-fetch-api

経緯

今回は以下のような状況でLigand情報を取得しようと試みた。

目的はLigandの分子量、ALOGPなどの各種特性を計算したいが、 そのLigandの

  • ID
  • 名前
  • SMILES

が不明である。

ただし、

  • 論文上で複数のProtein+Ligand複合体のPDB IDがわかっている。
  • そのLigandとされる構造式も書かれている。

PDB IDからのLigand情報の取得

ドキュメントを読む限り、

Entry:特定のPDB構造関するデータ。

  • 4文字の英数字による識別子(PDB IDで例えば、1Q2W)
  • タイトル、寄託者のリスト、登録日、公開日、実験の詳細

Entity : PDBに存在する化学的な分子の内容。大きく分けて3種類ある

  • polymer_entity - タンパク質、DNA、RNA
  • Branched_entity - 直鎖状または分岐状の炭水化物(糖類およびオリゴ糖とか)
  • nonpolymer_entity - 低分子化学物質(酵素補酵素、Ligand、ionとか)

Entity Instance : PDBに存在するEntityのコピーでEntityと同様3種類ある

  • polymer_entity_instance
  • branched_entity_instance
  • nonpolymer_entity_instance

Assembly : 生物学的ユニットを形成する構造要素

例えば、以下のようなものが記述されている

Chemical Component : PDBエントリーに含まれる全ての残基や低分子のデータ

例えば、以下のようなものが記述されている

  • 化学記述子(SMILES & InChI)
  • 化学式
  • 系統的な化学名

ドキュメントを頼りに色々やったが、クエリ上で、フィルターするやり方がいまいちわからない。今回で言えば、pdbx_chem_comp_descriptor.type == 'SMILES_CANONICAL' & pdbx_chem_comp_descriptor.program == 'OpenEye OEToolkits' この様に絞りたいが、どの様にクエリを書けばいいのだろうか・・・それに、階層構造もスキーマを見てもよくわからん

しかし幸い、サンプルにLigandのSMILESを取得するQueryが書かれていたのでそちらを参考にPythonで取得できるようにした。

import requests

query = '''
{
  entry(entry_id:"1AZ1") {
    nonpolymer_entities {
      rcsb_nonpolymer_entity_container_identifiers {
        entry_id
      }
      nonpolymer_comp {
        chem_comp {
          id
          type
        }
        pdbx_chem_comp_descriptor {
          descriptor
          type
          program
        }
      }
    }
  }
}
'''

url = "https://data.rcsb.org/graphql?query=" + query
response = requests.get(url)
json_data = response.json()
json_data

とすると以下のような結果が返ってくる

{'data': {'entry': {'nonpolymer_entities': [{'nonpolymer_comp': {'chem_comp': {'id': 'NAP',
       'type': 'non-polymer'},
      'pdbx_chem_comp_descriptor': [{'descriptor': 'NC(=O)c1ccc[n+](c1)[C@@H]2O[C@H](CO[P]([O-])(=O)O[P@@](O)(=O)OC[C@H]3O[C@H]([C@H](O[P](O)(O)=O)[C@@H]3O)n4cnc5c(N)ncnc45)[C@@H](O)[C@H]2O',
        'program': 'CACTVS',
        'type': 'SMILES_CANONICAL'},
       {'descriptor': 'NC(=O)c1ccc[n+](c1)[CH]2O[CH](CO[P]([O-])(=O)O[P](O)(=O)OC[CH]3O[CH]([CH](O[P](O)(O)=O)[CH]3O)n4cnc5c(N)ncnc45)[CH](O)[CH]2O',
        'program': 'CACTVS',
        'type': 'SMILES'},
       {'descriptor': 'c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO[P@@](=O)([O-])O[P@](=O)(O)OC[C@@H]3[C@H]([C@H]([C@@H](O3)n4cnc5c4ncnc5N)OP(=O)(O)O)O)O)O)C(=O)N',
        'program': 'OpenEye OEToolkits',
        'type': 'SMILES_CANONICAL'},
       {'descriptor': 'c1cc(c[n+](c1)C2C(C(C(O2)COP(=O)([O-])OP(=O)(O)OCC3C(C(C(O3)n4cnc5c4ncnc5N)OP(=O)(O)O)O)O)O)C(=O)N',
        'program': 'OpenEye OEToolkits',
        'type': 'SMILES'},
       {'descriptor': 'InChI=1S/C21H28N7O17P3/c22-17-12-19(25-7-24-17)28(8-26-12)21-16(44-46(33,34)35)14(30)11(43-21)6-41-48(38,39)45-47(36,37)40-5-10-13(29)15(31)20(42-10)27-3-1-2-9(4-27)18(23)32/h1-4,7-8,10-11,13-16,20-21,29-31H,5-6H2,(H7-,22,23,24,25,32,33,34,35,36,37,38,39)/t10-,11-,13-,14-,15-,16-,20-,21-/m1/s1',
        'program': 'InChI',
        'type': 'InChI'},
       {'descriptor': 'XJLXINKUBYWONI-NNYOXOHSSA-N',
        'program': 'InChI',
        'type': 'InChIKey'}]},
     'rcsb_nonpolymer_entity_container_identifiers': {'entry_id': '1AZ1'}},
    {'nonpolymer_comp': {'chem_comp': {'id': 'ALR', 'type': 'non-polymer'},
      'pdbx_chem_comp_descriptor': [{'descriptor': 'O=C2c1c3c(ccc1)cccc3C(=O)N2CC(=O)O',
        'program': 'ACDLabs',
        'type': 'SMILES'},
       {'descriptor': 'OC(=O)CN1C(=O)c2cccc3cccc(C1=O)c23',
        'program': 'CACTVS',
        'type': 'SMILES_CANONICAL'},
       {'descriptor': 'OC(=O)CN1C(=O)c2cccc3cccc(C1=O)c23',
        'program': 'CACTVS',
        'type': 'SMILES'},
       {'descriptor': 'c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O',
        'program': 'OpenEye OEToolkits',
        'type': 'SMILES_CANONICAL'},
       {'descriptor': 'c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O',
        'program': 'OpenEye OEToolkits',
        'type': 'SMILES'},
       {'descriptor': 'InChI=1S/C14H9NO4/c16-11(17)7-15-13(18)9-5-1-3-8-4-2-6-10(12(8)9)14(15)19/h1-6H,7H2,(H,16,17)',
        'program': 'InChI',
        'type': 'InChI'},
       {'descriptor': 'GCUCIFQCGJIRNT-UHFFFAOYSA-N',
        'program': 'InChI',
        'type': 'InChIKey'}]},
     'rcsb_nonpolymer_entity_container_identifiers': {'entry_id': '1AZ1'}}]}}}

対応するLigandは複数存在することもあるようだ。 今回はひとまずすべて取得し後に構造式から判断する。

以下のような関数を作成

  • PDB IDを引数とする
  • 特定のSMILESも絞れていないので取得後に、pdbx_chem_comp_descriptor.type == 'SMILES_CANONICAL' & pdbx_chem_comp_descriptor.program == 'OpenEye OEToolkits' となるデータのみ抽出する
import requests
def entry_to_ligand(entry_id):
    ligand_list = []
    query = '''
    {
    entry(entry_id:"'''  + entry_id + '''") {
        nonpolymer_entities {
        rcsb_nonpolymer_entity_container_identifiers {
            entry_id
        }
        nonpolymer_comp {
            chem_comp {
            id
            type
            }
            pdbx_chem_comp_descriptor {
            descriptor
            type
            program
            }
        }
        }
    }
    }
    '''
    url = "https://data.rcsb.org/graphql?query=" + query
    response = requests.get(url)
    json_data = response.json()
    for i in json_data.get('data').get('entry').get('nonpolymer_entities'):
        entry_id = i.get('rcsb_nonpolymer_entity_container_identifiers')
        nonpolymer_comp = i.get('nonpolymer_comp')
        ligand_id = nonpolymer_comp.get('chem_comp').get('id')
        for data in nonpolymer_comp.get('pdbx_chem_comp_descriptor'):
            if (data.get('type') == "SMILES_CANONICAL") and (data.get('program') == "OpenEye OEToolkits"):
                smiles = [data.get('descriptor')]
                type = [data.get('type')]
                program = [data.get('program')]
                d = {'entry':entry_id, 'ligand_id':ligand_id, 'smiles':smiles, 'type': type, 'program': program}
                ligand_list.append(d)
    return ligand_list


entry_to_ligand("1AZ1")

# [{'entry': {'entry_id': '1AZ1'},
#   'ligand_id': 'NAP',
#   'program': ['OpenEye OEToolkits'],
#   'smiles': ['c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO[P@@](=O)([O-])O[P@](=O)(O)OC[C@@H]3[C@H]([C@H]([C@@H](O3)n4cnc5c4ncnc5N)OP(=O)(O)O)O)O)O)C(=O)N'],
#   'type': ['SMILES_CANONICAL']},
#  {'entry': {'entry_id': '1AZ1'},
#   'ligand_id': 'ALR',
#   'program': ['OpenEye OEToolkits'],
#   'smiles': ['c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O'],
#   'type': ['SMILES_CANONICAL']}
# ]

DataFrameで扱いたいなら適宜pandasで

pd.DataFrame(entry_to_ligand("1AZ1"))

#  entry_id    ligand_id   smiles  type    program
#  1AZ1    NAP [c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO...   [SMILES_CANONICAL]  [OpenEye OEToolkits]
#  1AZ1    ALR [c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O]  [SMILES_CANONICAL]  [OpenEye OEToolkits]

あとは適宜、ProteinとLigand PDB IDを適宜流し込んであげれば欲しいデータは取得できた。(ただし、NAP, CL, URE, PO4などのようなLigandを弾きたければ適宜弾く必要はある。)

df = pd.DataFrame()
for entry_id in entry_ids:
    tmp = pd.DataFrame(entry_to_ligand(entry_id))
    df = pd.concat([df, tmp])
df.reset_index().drop('index', axis=1)

# entry_id ligand_id   smiles  type    program
# 1AZ1 NAP [c1cc(c[n+](c1)[C@H]2[C@@H]([C@@H]([C@H](O2)CO...   [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 1AZ1 ALR [c1cc2cccc3c2c(c1)C(=O)N(C3=O)CC(=O)O]  [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 1DDR CL  [[Cl-]] [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 1DDR MTX [CN(Cc1cnc2c(n1)c(nc(n2)N)N)c3ccc(cc3)C(=O)N[C...   [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 1DDR URE [C(=O)(N)N] [SMILES_CANONICAL]  [OpenEye OEToolkits]
# ...  ... ... ... ...
# 2BGD CL  [[Cl-]] [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 2BGD PO4 [[O-]P(=O)([O-])[O-]]   [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 2BGD NA  [[Na+]] [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 7STD CA  [[Ca+2]]    [SMILES_CANONICAL]  [OpenEye OEToolkits]
# 7STD CRP [CC[C@]1([C@H](C1(Cl)Cl)C)C(=O)N[C@H](C)c2ccc(...   [SMILES_CANONICAL]  [OpenEye OEToolkits]

終わりに

正直、Protein:Ligandの組が1:1対応と思っていたのだが、いわゆる低分子だけでなく、NAPとかイオンとかその他諸々も含むので複数もあり得るのかと思った。

仕方がないので一旦すべて取得して、不要なものはルールで除去し、差雌雄的にRDKitで構造式に直して、目視で論文に書かれている構造式と照らし合わせて、目的のLigandのSMILESを得ることができた。

GraphQLでの取得はDBの構造というかKeyの単語がスキーマをみてもよくわからなかったりするのでちょっと困る。ちょっとずつおぼえていくしかないか

社会人ドクター生活スタート

とてもバタバタしていて、ブログは放置していましたが、理由があって、

結論から言うと、博士後期課程の学生として、東京工業大学 情報理工学院 情報工学系の大上研究室に所属することになりました。

www.li.c.titech.ac.jp


経緯

以前より、創薬分野に関わる研究がしたいと思っていて、現職は続けながら、社会人として学位取得を目指し頑張っていことと思い立ったのが2020年の8-9月

Twitter上でいろんな方が教えてくれて、色々検討しつつ、所属先の指導教員である大上先生が声をかけてくれたのがきっかけで、受け入れについて相談にのってもらいました。

上司と家族に説明し、去年からTOEIC受けたり、10年くらい前の修士論文を引っ張り出して取りまとめたり、今年の2月の入試に向けての準備や実際の入試はとても大変だったけれど、3月に無事に合格発表を受けてホッとしました。

今後

これまでの経験は

これらの経験も活かしつつ、 コンピュータサイエンスの応用として、おもしろい生命科学の課題解決ができたらいいなと思っています。

2020年を振り返る

今年はコロナ情勢の中、いろんな事をやった一年だった。 結果的に、AtCoderにチャレンジしたのが大きかった。その他にも試験を何個か受けたり、本読んで感想書いたりして、まぁまぁ充実していた。

簿記(2020-02)

XBRLから財務分析を行う話があったのだがXBRLの取り扱い自体はできても、そもそも財務に関するドメイン知識が皆無だったので、基礎を学ぶためにまずは簿記の試験を1ヶ月程度かけて行った。無事合格。3級は基礎中の基礎でこれはこれで勉強になったが、いわゆる財務分析についても勉強していきたい。

blacktanktop.hatenablog.com

はてぶ(2020-06~)

とりあえず、自分がやってることというのはだいたい忘れていくし、なにかの折に何者なのかもわかりやすいように、当時興味あったことをログとして残すことにした。

blacktanktop.hatenablog.com

AtCoder (2020-05~)

時間もあったのと数学的思考をプログラミング化するいい題材がないか考えて、AtCoderを始めた。

blacktanktop.hatenablog.com

なんだかんだ半年くらいは続いて、今もコンテストには参加しているけど、とっさのひらめき・知識・実装力不足を実感した。そして半年もやってるのにまだ沼から抜け出せてないOrz

初めて慣れてきた頃に、良い書籍が出て勉強になった。 blacktanktop.hatenablog.com

今年は合計で数百問は解いたけど、コンテストの復習も兼ねて、再度解き直しを行った。

blacktanktop.hatenablog.com

書評 (2020-07~)

今年は、本を読むだけでなく、とりあえずまとめてみるっていうことを何回かやった。ただただ読むだけよりも、文章にすることで、細かいところをきちんと調べるようになったので、よりinputが捗った。これら以外の本も色々読んだけど、まとめられたのは、このくらいで、これからも続けていきたい。

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

blacktanktop.hatenablog.com

移住 (2020-07~)

なにげに、結構でかい話で一生に1−2度くらいしかな異買い物をすることに。去年秋くらいから家庭の事情もあり、移住計画スタート。いい場所に家を建てられそうだったので、速攻土地をおさえ、なんだかんだ、去年末から家全体の設計を考えて、インテリアはどうするだの、ZEHにするだのと進んで、諸々終えたのが今年の2−3月。4月の緊急事態宣言直前に地鎮祭。ものの数ヶ月建築工事が終わって、引き渡しってな感じである意味激動の数ヶ月が裏では動いてたっていう。まぁ計画当初は去年秋で、まさかコロナの騒ぎがここまで大きくなるとは想像もしていなかったけど、結果的には無事に移住もできてフルリモートで働けるしイイコトづくし。

スタサプ・TOEIC(2020-09-10)

諸事情でTOEICを受けざるを得ず、1ヶ月くらい真剣にスタサプと向き合った。これまでTOEICは受けたことがなくどんなものかもよくわかっていなかったが、結果はギリ700超えでまぁ初手ではこんなもんかと、一旦納得した(分布的には平均よりちょっといいってことになってる)。家で模試を解いてたときは素点計算で800位だったので、もう少し試験慣れが重要かなと思ったけど、ここ数年割と離れていた割には、やはりリスニングの方ができるのはNetflixを英語字幕で観まくったり、友人と会話しているおかげかな・・・っていうかReadingが悪すぎ。まぁ機会があればもう少し対策して実際に800くらいはいけそうと思ったので、まぁもうすこし頑張ってもいいかな。英語の試験対策はつまらない・・・TOEICできなくてもある程度の英語でのコミュニケーションはできるしね。まぁ逆にTOEICできても話す場面で全く話せないっていうのだけは避けたくて、日常会話に力を入れてきたのがとりあえず受けて成果を出すって言うところに単純に活きた感じだった。

ちなみに、スタサプは月3000円位だったけど割とコスパがいいと思った。集中して1−2ヶ月やるだけで感触はつかめる。ただ試験対策は模試を時間通りに解いてやり直してを繰り返さないと、試験の感覚を養えないのと、試験は音が聞きづらい(ホールに響く感じになる)のでそこらへんの慣れも重要な気がした。 f:id:black_tank_top:20201231074750j:plainf:id:black_tank_top:20201231074852j:plain

業績まとめ

諸事情で、過去の業績をまとめる必要があり、大学から大学院・その後理研・現職までをザーッとまとめるっていう作業をしていた。修論を整理しながら改めて、自分のやりたいことの方向性も固まってきたので、来年以降その方向で進んでいけるといいなと思っている。

終わりに

今年は、思いったら始めるっていう感じで通したので、来年はもう少し計画的にやりたい。来年からは何ヶ年計画で色々とやりたいことがあるので、年明けから結構忙しくなりそうだ。

Python3で解いた AtCoderの勉強になった問題 2020

サマリー

  • 6月くらいから、AtCoderやってみようと、ふと思い立って初めて約半年。
  • 年末時間ができたから復習していてそこで面白かった問題を上げていく。
  • 基本過去記事の再掲しつつ、もう一度振り返る。

AtCoder Beginner Contest

169 C-Multiplication 3 (2020-05-31)

問題は以下 atcoder.jp 初めての参戦で、いきなりこの問題でWAを超絶だした記憶。オーバーフローや計算精度に関する問題も出るのかと痛感した。

どういう時に問題が起きるのかをそもそも理解できずに、試行錯誤しつつ、以下のうように、浮動小数は実際には正しい値を保持していないことを改めて知って勉強になった。

0.07 * 100
# 7.000000000000001
0.29 * 100
# 28.999999999999996

でもDecimalモジュール使えばいいやと思って通した。

a, b = map(str, input().split())
res = int(a) * Decimal(b)
print(int(res))

けど本来は、数値をそのまま掛け合わせて後で割って整数部分だけ取得するとやってほしかったんだろうなぁ

a, b = map(str, input().split())
c,d = b.split('.')
# int(c+d)とするとbを文字通り100倍された結果になる。
res = a * int(c+d) 
print(res//100)

169 D - Div Game (2020-05-31)

問題は以下 atcoder.jp この当時は、素因数分解をする方法もコードに落としてなくて1から実装しなきゃで大変だったが、それさえできればあと一工夫でできる。


170 D - Not Divisible (2020-06-14)

問題は以下

atcoder.jp

この当時は、エラトステネスの篩的な考えもコードに落としてないし、発想としても持っていなかった。

  • リストに含まれるある値の定数倍の値をふるい落とすという考えを学んだ
  • また、inで探すときは対象をlistではなくsetにしておくことが速度として重要というのも学んだ
n = int(input())
a = list(map(int, input().split()))
a_s = sorted(a)
a_set = set(a_s)
maxv = max(a)
res = [0] * (maxv+1)
rep = 0
for i in a_s:
    # iを評価してない時はiを含むの定数倍のところは+1
    if res[i] == 0:
        for j in range(i, maxv+1, i):
            res[j] += 1
    # iを評価してあればiを+1
    # すでに定数倍として評価されていたら2以上にしたい
    else:
        res[i] += 1
cnt = 0
# 最終的に検索対象が定数倍リスト内で1になっているものだけをカウントする
for i in range(n):
    if res[a[i]] == 1:
        cnt += res[a[i]]
print(cnt)

172 C - Tsundoku (2020-06-27)

問題は以下 atcoder.jp

ここらへんから記事書き始めた。

  • しゃくとり
  • 累積和と二分探索

この時、これらのイメージを掴んだ感じ

blacktanktop.hatenablog.com

172 D - Sum of Divisors (2020-06-27)

問題は以下 atcoder.jp

Pythonだと{\displaystyle O(NlogN)}でも{\displaystyle N \geq 10^8)}だと通らないということを感じた回だった。

工夫すれば、

約数 {\displaystyle \times} 等差数列の和で計算できる。

blacktanktop.hatenablog.com


173 C - H and V (2020-07-05)

問題は以下 atcoder.jp

はじめてbitをコードで起こした。bit演算子である、'&'や '>'の意味を学んだ。

  • bit全探索
  • itertoolsのproduct の両者を学んだ。bitは未だに苦手でbitで全パターン検索するような場合は先にproductで作ってしまうほうが考えやすい。

blacktanktop.hatenablog.com


174 C - Repsept (2020-08-02)

問題は以下 atcoder.jp

愚直に'7'をいくつも並べたものを順に割っていくっていうのはだめ。基本的に{\displaystyle x * 10^6}という様にたくさん文字列を羅列する形は遅い。

純粋に'7', '77', '777' ... なので初項7, 公比10の等比数列と考える。

  • 等比数列の和
  • 余りで考えれば良いのでモジュラの性質をうまく使って10倍されていく時にあまりを10倍して考えればオーバーフローなどを木にする必要がなくなる。

blacktanktop.hatenablog.com


176 D - Wizard in Maze (2020-08-22)

問題は以下 atcoder.jp

結構重くて、なかなか通らなかった。 対策としては、外側へのはみ出しを防止。結構むずくて、今解き直しても素直に実装できなそう。

  • 上下左右を'##'で埋める。
  • あとは歩いていけなくなるまで普通の迷路探索 。
  • ワープのパターン出いけるところに歩いていける範囲としてキューに加える。

blacktanktop.hatenablog.com


178 C - Ubiquity (2020-09-13)

問題は以下 atcoder.jp

愚直に条件に当てはまるものを求めるのではなく、包括原理より、全パターンから条件に合わないものを引くとしたほうがいいパターン。

ポイントとして、{\displaystyle x^n}という形は愚直に計算すると遅いので計算過程ではくり返し二乗法を使う

blacktanktop.hatenablog.com


180 C - Cream puff (2020-11-01)

問題は以下 atcoder.jp

単純な約数列挙だが、いちからコード書くと結構大変だった。考え方としては素因数分解と同じようなイメージでいけた。{\displaystyle 1)}から{\displaystyle \sqrt{n}))}まで試し割りして、 割り切れたものと、割り切れたときの商を別々に保持して後でリストを連結

blacktanktop.hatenablog.com


181 D - Hachi (2020-10-17)

問題は以下 atcoder.jp

単純な倍数判定だが、入れ替えを考慮するため侮れない。 結果から言うと、Counterで8の倍数の下3桁の文字列のパターンと一致するかどうかで判断。Counterの結果は引き算できることがポイント

blacktanktop.hatenablog.com


182 D - Wandering (2020-11-08)

問題は以下 atcoder.jp

その時までの値の累積和とその時までの値の最大値の和を考える。 図示しながら考えるとわかり良い。

blacktanktop.hatenablog.com


183 D - Water Heater (2020-11-15)

問題は以下 atcoder.jp

持ち方がポイントで、使うスケジュールの最初の時刻でプラス、終わりでマイナスしたリストを頭から順に足し合わせていくようにして、スケジュールの累積和として考える

blacktanktop.hatenablog.com


184 D - increment of coins (2020-11-22)

問題は以下 atcoder.jp

メモ化を自動的にやってくれるlru_cacheというデコレータを知った。 メモ化と比較するとスッキリすることがわかる。

blacktanktop.hatenablog.com


186 D - Sum of difference (2020-12-19)

問題は以下 atcoder.jp

久しぶりにコンテストの最中にDまで解けた。
引き算の絶対値がポイントで、事例を書き出して、ソートして考えても良いと考えられれば単純な数え上げとなる。

blacktanktop.hatenablog.com


AtCoder Regular Contest

106 B - Value (2020-10-24)

問題は以下

atcoder.jp

Union-Find問題。連結しているグループの値の和が一致するかを検討するだけ。このとき注意しなくてはいけないのは、find()でrootを求めていく。

blacktanktop.hatenablog.com


109 B - log (2020-11-28)

問題は以下

atcoder.jp

これまで、リストが存在する二分探索はやったことがあったが、この問題ではリストを作ってから行うと、TLEとなってしまうため、midの値を適切に変形し 探索したい値{\displaystyle n+1}と比較する。

blacktanktop.hatenablog.com


110 B - many (2020-12-05)

問題は以下

atcoder.jp

最初'0'にアサインできたら、最後、左から数えて何番目の'0'にアサインできるかを考える問題

blacktanktop.hatenablog.com


最後に

結局だいたい実際にコンテストで解いた問題全部あげただけみたいになってしまったがまとめながら、半日かけて、ここに書き出した問題をもう一度解き直しして良い復習ができた。

ブログにまとめながらやっていたおかげで数ヶ月前に書いたコードを少し見直しただけで思い出せたし、復習が比較的楽にできてよかった。

2020年06月以前の問題も実際には精進として解いていて、その中でも良さげな問題はあったが、とりあえず今回は取り扱わないでおいた。


参考書籍

けんちょんさんの本。非常に参考になる。行き詰まった時にもう一度読み直したりしてる。

こちらで、書籍を7章まで進めている。

blacktanktop.hatenablog.com

【Fast.ai Vision】Single-Label Classificationの基礎

サマリー

Fast.aiのversion2で関数が結構変わっていたので、Single-Label Classificationを行うために最低限の知識をメモとしてまとめる。 基本的にはこちらの書籍に書かれている。

本格的に使う場合は、csvからのデータ取得やaugumentationも既存ツールでないものを使ったほうがいい、学習の仕方の工夫などがあるが、今回は取り扱わず、別記事とする。

環境準備

condaでInstallすることが推奨されている。まっさらな環境のときはcondaをオススメする。colabの場合はPytorchも使える状況なので、pip installで問題ない。

# Colabの場合(すでにPytorch環境が整っている場合)
!pip install -Uqq fastai
# まっさらな環境の場合(Pytorch環境もなく、1から環境作る場合)
conda install -c fastai -c pytorch fastai

Colabではない環境でどうしてもpipで環境を作りたい場合は、以下で適切にPytorchのinstallの設定を調べてから行う。 pytorch.org

モジュールの読み込み

賛否両論あるが、Fastaiはこの様に一行ですべて読込させる方針。

from fastai.vision.all import *

どのような形で必要なモジュールを読み込んでいるのかを把握したい場合は、いかから追ってみるといい。必要なモジュールだけを、すべて個別で読み込もうと試みたが、ただ使うだけなら度々import errorが起きるため諦めて、Fastaiの方針にのることにした。

github.com

Data Blockについて

DataLoadersの準備

以前はDataBunchというクラスでDataSetsやDataLoadersを作成したがversion2以降では変更となったようだ

github.com

DataBlockは、以下の引数を与えるとのDatasetsまたはDataLoadersを作成できる。

今回は比較的簡単にpathを取得できる例で説明する。Multi-Label ClassificationやデータセットのPATHがcsvで提供されている場合のDataBlockの作り方は別記事にする。

  • blocksはsinglelabelならblocks = (ImageBlock, CategoryBlock)
  • get_itemsは画像ファイルのパスを取得する方法、例えばget_image_filesを使う。
  • splitterは、データを分割する方法、以下のsplitterから状況に応じて使い分ける。
  • get_xは、入力に何かを適用する必要があるなら記述。
  • get_yは、ターゲットに何かを適用する必要があるなら記述。多くは予測ラベルの取得方法の関数。例えば、画像ファイル名から0, 1にする関数や画像ファイル名から名前やカテゴリ名を取得する方法を記述。 例えば、RegexLabellerusing_attrを使うなどする。

  • item_tfmsは、batchが形成される前にアイテムに適用される変換を記述。GPUにコピーされる前に個々の画像に適用される。例えばresize。

  • batch_tfmsは、batchが形成された後に適用される変換を記述。GPU上で一括して適用される。例えばaug_transformsなどを使う。
    item_tfmsとbatch_tfmsの両者に使う関数は以下が参考になる。 Data augmentation in computer vision | fastai

  • インスタンスを作ったら、以下の様に画像のディレクトリを入力すればDataLoadersが作ることができる。

# 例えば、path/"images"以下に画像データそれぞれのPathが入ってるとすると
data = DataBlock(blocks = (ImageBlock, CategoryBlock),
                 get_items=get_image_files, 
                 splitter=RandomSplitter(seed=42),
                 get_y=using_attr(RegexLabeller(r'(.+)_\d+.jpg$'), 'name'),
                 item_tfms=Resize(460),
                 batch_tfms=aug_transforms(size=224, min_scale=0.75))

dls = data.dataloaders(path/"images")

# 実際のカテゴリ、カテゴリ数を表示
print(dls.vocab)
len(dls.vocab)

DataLoadersの確認

DataLoadersの中身をバッチ分確認したい場合は以下。 デフォルトでは、バッチサイズは64。

x, y = dls.one_batch()
# y 
# TensorCategory([18, 36, 35, 22, 17, 26,  0, 21, 32, 34,  1,  7,  1, 20, 31, 15, 31, 34, 5,  1, 22, 33, 21, 26,  0,  9, 15,  7, 13, 16, 28, 23, 18,  5,  1,  5, 15,  6,  5, 12,  1, 24,  2, 16,  6,  5,  8, 11, 30,  5, 22, 34, 11, 18, 0, 19, 25, 28, 32,  9, 21,  6, 17, 13], device='cuda:0')

画像自体を確認するときは[show_batch] (https://docs.fast.ai/data.core.html#TfmdDL.show_batch)で表示できる。以下は、The Oxford-IIIT Pet Datasetを読み込んだときの例。

dls.show_batch(nrows=4, ncols=4, max_n=16)

f:id:black_tank_top:20201213142431p:plain
The Oxford-IIIT Pet Datasetを表示した例

学習(Fine turning)

learnerの設定

基本的に最低3つ設定する。

モデルは自分で構築したものも使えるし、以下のpretrainedされたモデルアーキテクチャも使える。 pytorch.org

Metricsは自分で構築したものも使えるし、以下の用意されたMetricsを使える。

docs.fast.ai

learn = cnn_learner(dls, resnet34, metrics=error_rate)

モデル構造のチェック

cnn_learnerで作っている場合は、選択したモデルの最後の層だけcutして、DataLoaderに合わせた数にセットされる。以下で確認できる。

learn.model

転移学習とFine turning

転移学習は、

事前に学習した重みや他のレイヤーの重みを変更せずに(freezing)、目的のタスクを正しく達成するように最終層の完全結合層(fully conect layer)はランダム重みを持つ新しいものに置き換えてこの重みを置き換えた最終層のみの重みだけを更新するように学習すること。

Fine turningは、

転移学習後の微調整で、転移学習後に他のレイヤーの重みの変更を許して(unfreezing)、目的のタスクを正しく達成する重みに置き換えて学習すること。

多くの場合で、以下のやり方で転移学習・Fine turningすることでよりLossが下がることが知られている。

  • 最終層以外をfreezingして学習する。
  • unfreezeする。
  • 更に学習する。

Fastai version1のときは

learn.fit_one_cycle(1)
unfreeze()
learn.fit_one_cycle(1)

のようにやっていたが、version2から新たにfine_tuneというmethodが追加された。unfreezingで何epoch学習するかが引数。freezingで何epoch学習するかは別途設定できる(デフォルトでは1)

なので、上記と同義なのが以下

learn.fine_tune(1)

ただ、基本的には、fit_one_cyclelr_findを組み合わせて、freezing時でも最適な学習率を設定し、unfreezingでも最適な学習率を設定し(最初は大きな値、徐々に小さく)学習することが望ましい。

以下に、適切な学習率を探索する方法について書いた。

適切な学習率の探索

Leslie Smithによって、learning rate finderというアイディアが考案された。 arxiv.org 内容の詳細は省くがその論文は以下なので、参考にするといい。学習率の良い値は、

learnerを設定したら、以下のようにすれば、上記の2つの値が返ってくる。

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.lr_find()
# SuggestedLRs(lr_min=0.012022644281387329, lr_steep=0.0063095735386013985)

f:id:black_tank_top:20201213001730p:plain
learning rate finderの結果の例

まず、freezingしている状態で実際にその値を見た上で適切な学習率を考える。上記の例だと、以下のようにするのが例となる。

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(2, base_lr=3e-3)

fit_one_cycleを使った改善

fit_one_cyclefine_tuneを使わずにモデルを学習する方法として用意されていて、より自由度高く学習の設計ができる。低い学習率で学習を開始し、最初の部分では徐々に学習率を上げ、最後の部分では再び学習率を徐々に下げていくことが可能。

  1. 学習率を探索する。
  2. freezingして、最終層の重みだけを更新するように学習する。
  3. unfreezingする。再度学習率を探索する。
  4. すべての層の重みを更新するように学習する。
# 1. 学習率を探索する。
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.lr_find()
SuggestedLRs(lr_min=0.012022644281387329, 
lr_steep=0.0063095735386013985)

f:id:black_tank_top:20201213001730p:plain
learning rate finderの結果の例

# 2. 最終層の重みだけを更新するように学習する。
learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fit_one_cycle(3, 3e-3)
# 3. unfreezingする。再度学習率を探索する。
learn.unfreeze()
learn.lr_find()

f:id:black_tank_top:20201213005202p:plain
最終層の学習・unfreezing後learning rate finderの結果の例

上記の例だと、freezing前の探索時のLossの落ち込みに比べて、unfreezing後の探索時の急激なlossの落ち込みがないのは、学習がある程度進んでいるからと考えられる。この場合だと、以下のように学習をすすめるなど、考えられる。

# 4. すべての層の重みを更新するように学習する。
learn.fit_one_cycle(6, lr_max=1e-5)

Discriminative Learning Rates

Jason Yosinskiらによって、支持されているものとして、ネットワークの初期層には低い学習率を使い、最終層には高い学習率を使うのが良いとされている。論文は以下。 arxiv.org

例えば、これまでの内容をふまえると、以下のような流れで行うことができる。

learn = cnn_learner(dls, resnet34, metrics=error_rate)
learn.fit_one_cycle(3, 3e-3)
learn.unfreeze()
learn.fit_one_cycle(12, lr_max=slice(1e-6,1e-4))
# lossのプロット
learn.recorder.plot_loss()

f:id:black_tank_top:20201213010758p:plain
学習と評価データのlossのプロット

Mixed Precision

より、深いネットワークを使ったときの問題点としては、

  1. メモリ不足
  2. 時間がかかる

解決策として、以下が考えられる。

  1. メモリ不足に関してはバッチサイズを減らして学習させる。
  2. 時間がかかることに関しては、Mixed Precisionとして、半精度浮動小数点(fp16)を使うことで数倍早くなる。

Fastai version2では半精度浮動小数点はfrom fastai.callback.fp16 import *を読み込むことで使うことができる。
(fastaiのバージョンにもよる。callbackのうちfp16についてはfrom fastai.vision.all import *で自動的に読み込まれることもある)

from fastai.callback.fp16 import *
learn = cnn_learner(dls, resnet50, metrics=error_rate).to_fp16()
learn.fine_tune(6, freeze_epochs=3)

精度解釈

学習時にvalidation lossで自分で決めたmetricで精度は理解できているものの、実際にどのクラスとどのクラスが間違えやすいか、その程度はどの程度かを把握するために、confusion matrixで予測があっているかを直感的に理解する。

confusion matrix

interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix(figsize=(12,12), dpi=60)

f:id:black_tank_top:20201213105319p:plain
Confusion Matrixの例

クラスを間違っているかのチェック

間違っているクラス同士をチェックするにはmost_confusedというメソッドが用意されている

interp.most_confused(min_val=5)

参考になる書籍

FastaiとPytorchを使って画像認識や協調フィルタリングNLP関連のタスクを例として、かなり詳細まで書かれている。Fastaiを使っていくなら一読することをおすすめする。

Python3で解く AtCoder Beginner Contest 186 D - Sum of difference

結構単純な、数え上げ。 ソートして考えることにきづければ簡単。

目次

概要

問題

問題概要は省略。


解くときに考えた内容

  • すべてのパターンは{\displaystyle \frac{(n)\times (n-1)}{2}} でも制約的に、線形で考えなくてはいけないので二重のforではだめ。
  • 差の絶対値をとったものの和なので順番をソートしても問題ない。
  • 大きい順にする。
  • あとはindexと対応する値を適切に足し合わせたり、引いたりする。
    • 簡易的な例を考えると、{\displaystyle A = [2, 3, 5, 1, 4]}だとした時に、順番に引き算すると {\displaystyle |2-3|, |2-5|, |2-1|, |2-4| ... |5-1|, |5-4|, |1-4|}となる。図を見ると、大きい順にソートしてから考えた時と、計算結果は変わらないことがわかる。(左右が入れ替わったパターンが出てくるだけでそれは絶対値によって和にするならば一致するため)
    • ここまで理解できれば、あとは規則性。
    • 全体でどれがどれだけ+なのか-なのかを考える。
    • {\displaystyle 5}{\displaystyle 4}回加算される(図では+){\displaystyle 0}回減算される(図では-)。
    • {\displaystyle 4}{\displaystyle 3}回加算される(図では+){\displaystyle 1}回減算される(図では-)。
    • 一般に{\displaystyle sorted_A[index]}{\displaystyle n-index -1}回加算される(図では+){\displaystyle index}回減算される(図では-)。

f:id:black_tank_top:20201220003525j:plain
パターンのイメージ図

最近は実際のコンテストの最中にイメージ図を書くようにしている。その方が、indexとの関係などをが明白になってミスが減っている気がしている。上の図も実際の場面で書いたもの。


コード

n = int(input())
A = sorted([int(x) for x in input().split()], reverse=True)
ans = 0
for i in range(n):
    ans += A[i] * ((n-i-1) - i)
print(ans)

参考になる書籍

けんちょんさんこと、大槻さんの書籍である、「問題解決力を鍛える!アルゴリズムとデータ構造」 ご本人の記事の中で難易度は、螺旋本ということで初学者でも非常に結構読みやすい!!!(実際に、けんちょんさんの記事はC++に疎い自分でも、その思考プロセスを学べるくらい丁寧に書かれているし、この書籍も非常に読みやすい)どのようなステータスの人にもおすすめの一冊になっている。

とうとう発売され自分の手元にも来た(2020-10-03)。現在読み中なのである程度読み込んだら感想を書く予定。めっちゃ図が多いし、章末問題もあるしかなりイケてる。答えはC++で書かれているがそのうちPythonでも書かれるとのことなので、自分で前もってやってしまおうと思う(答え合わせできるのはありがたい)。

7章までは別記事に残してある。 blacktanktop.hatenablog.com

AtCoder Beginner Contest 186 参加ログ・感想


内容

  • Python3でやっている。
  • 参加ログ。
  • 所感。
  • コンテスト中に何を考えたか。
  • コンテスト後に解説をみたり、少し整理したりしたくらいの内容。
  • 問題の詳細で細かく書こうと思うものは別記事とする。

結果

久しぶりにABCD完。Eも方針が立つくらいにまでは時間があった。 少しは成長している感触だった。Cはn進数をリストで持つようにして対応。制約的にDは線形で考えなくてはいけないと考え、絶対値がポイントになると踏んで対応した。


どう考えたか + α

問題

A - Brick

問題タイプ:商

文意から商が答え

n, w = map(int, input().split())
ans = n // w
print(ans)

B - Blocks on Grid

問題タイプ:最小値・ループ処理

  • {\displaystyle A}を多重リストで持つと面倒なので、flattenして、1次元のリストにする。
  • {\displaystyle A}の最小値を求める。
  • {\displaystyle A}をループで回しながら最小値をとの差を加算していく。
import itertools
def flatten(l):
    return list(itertools.chain.from_iterable(l))
h, w = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(h)]
a = flatten(A)
min_a = min(a)
cnt = 0
for i in a:
    cnt += i - min_a
print(cnt)

C - Unlucky 7

問題タイプ:n進数

もしかしたら、組み込み関数でいい感じにできるかもしれなかったが、知らんかったのと、入力値とn進数を入れたら、各桁のリストで管理したかったので、その様にやった。あとは10進数と8進数どちらも出てきたの中に{\displaystyle 7}がないものだけカウントするだけ。

def shinsu(N, shinsu=8):
    keta=0
    for i in range(10**5+1):
        if N<shinsu**i:
            keta+=i
            break
    ans=[0]*keta
    check=0
    for i in range(1,keta+1):
        j=N//(shinsu**(keta-i))
        ans[check]=j
        check+=1
        N-=(j)*(shinsu**(keta-i))
    return ans
n = int(input())
cnt = 0
for i in range(1, n+1):
    ans10 = ch(i, 10)
    ans8 = shinsu(i)
    if 7 not in ans10 and 7 not in ans8:
        cnt += 1
print(cnt)

ちなみに、調べたらoct()とすれば8進数にできるらしい。 10進数はstrにすればいいので、そうすると以下のように可能。 oct()のために超絶めんどいことしてしてしまった。

n = int(input())
ans = 0
for i in range(1, n+1):
  if ('7' not in str(i)) and ('7' not in oct(i)):
    ans += 1
print(ans)

D - Sum of difference

問題タイプ:ソート・累積和

すべてのパターンは{\displaystyle \frac{(n)\times (n-1)}{2}} でも制約的に、線形で考えなくてはいけないので二重のforではだめ。 ポイントは、順番をソートする。その後順番に数ごとに加える量や引く量を線形に計算していく。別記事にする。


おわりに

今回は、比較的できた。今までやってきたことが、問題に多く出てきたという感じ。やはり慣れが重要だし、ぱっと思いつける様に精進が重要。 が沼はまだ深い・・・。


参考になる書籍

けんちょんさんこと、大槻さんの書籍である、「問題解決力を鍛える!アルゴリズムとデータ構造」 ご本人の記事の中で難易度は、螺旋本ということで初学者でも非常に結構読みやすい!!!(実際に、けんちょんさんの記事はC++に疎い自分でも、その思考プロセスを学べるくらい丁寧に書かれているし、この書籍も非常に読みやすい)どのようなステータスの人にもおすすめの一冊になっている。

7章までPythonで章末問題をこなしていっている。

blacktanktop.hatenablog.com