ギークなエンジニアを目指す男 機械学習系の知識を蓄えようとするブログ 2024-03-16T20:39:19+09:00 taxa_program Hatena::Blog hatenablog://blog/6653458415121385372 pythonを使ってDynamoDBの複数テーブルから非同期でデータ取得してみる hatenablog://entry/6801883189091201508 2024-03-16T20:39:19+09:00 2024-03-16T20:43:46+09:00 こんにちは。たかぱい(@takapy0210)です。 DynamoDBの複数テーブルからなるべく高速にデータを取得するために、非同期でデータ取得することはできるのか?を少し調べてみたのですが、あまり事例が無かったのでメモ程度に残しておきます。 ユースケースとしては、例えば user_id をkeyとしたテーブルが複数あり、それぞれからデータを取得し、最終的なレスポンスを生成したい場合などに使えるかと思います。 一般的なデータ取得方法 boto3のAPIを非同期で使えるaioboto3ライブラリ 非同期でDynamoDBからデータ取得する 一般的なデータ取得方法 まず初めに、boto3を使ってD… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240316/20240316155240.png" width="1200" height="673" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>DynamoDBの複数テーブルからなるべく高速にデータを取得するために、非同期でデータ取得することはできるのか?を少し調べてみたのですが、あまり事例が無かったのでメモ程度に残しておきます。</p> <p>ユースケースとしては、例えば <code>user_id</code> をkeyとしたテーブルが複数あり、それぞれからデータを取得し、最終的なレスポンスを生成したい場合などに使えるかと思います。</p> <ul class="table-of-contents"> <li><a href="#一般的なデータ取得方法">一般的なデータ取得方法</a></li> <li><a href="#boto3のAPIを非同期で使えるaioboto3ライブラリ">boto3のAPIを非同期で使えるaioboto3ライブラリ</a></li> <li><a href="#非同期でDynamoDBからデータ取得する">非同期でDynamoDBからデータ取得する</a></li> </ul> <h1 id="一般的なデータ取得方法">一般的なデータ取得方法</h1> <p>まず初めに、boto3を使ってDynamoDBからデータを取得する方法をみていきます。</p> <p>後述する非同期処理の恩恵が分かりやすいように、sleepを入れて意図的に時間がかかるようにしています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> boto3 dynamodb = boto3.resource(<span class="synConstant">&quot;dynamodb&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">get_item_from_table_sync</span>(table_name, key, delay): <span class="synConstant">&quot;&quot;&quot;DynamoDBからデータを取得する&quot;&quot;&quot;</span> time.sleep(delay) <span class="synComment"># 意図的にsleepを入れている</span> table = dynamodb.Table(table_name) response = table.get_item(Key=key) <span class="synStatement">return</span> response.get(<span class="synConstant">'Item'</span>) <span class="synStatement">def</span> <span class="synIdentifier">main_sync</span>(): user_id = xxxx results = [] start_time = time.time() <span class="synComment"># 1つ目のテーブルから取得</span> dynamo_response = get_item_from_table_sync( table_name=<span class="synConstant">&quot;dynamo_tableA&quot;</span>, key={<span class="synConstant">&quot;user_id&quot;</span>: user_id}, delay=<span class="synConstant">10</span> ) results.append(dynamo_response) <span class="synComment"># 2つ目のテーブルから取得</span> dynamo_response = get_item_from_table_sync( table_name=<span class="synConstant">&quot;dynamo_tableB&quot;</span>, key={<span class="synConstant">&quot;user_id&quot;</span>: user_id}, delay=<span class="synConstant">7</span> ) results.append(dynamo_response) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;Total time:&quot;</span>, time.time() - start_time) <span class="synStatement">return</span> results result = main_sync() <span class="synComment"># &gt;&gt;&gt; Total time: 17.16487693786621</span> </pre> <p>上記処理は各テーブルからデータ取得する際に10s、7sのディレイを入れているので、合計の処理時間は17秒ほどかかります。</p> <p>次に、これを非同期処理に書き換えてみます。</p> <h1 id="boto3のAPIを非同期で使えるaioboto3ライブラリ">boto3のAPIを非同期で使えるaioboto3ライブラリ</h1> <p>探してみると、aiboto3という<a href="https://github.com/boto/boto3">boto3</a> と <a href="https://github.com/aio-libs/aiobotocore">aiobotocore</a> を組み合わせたaioboto3というラッパーがあったので、これを利用していきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fterrycain%2Faioboto3" title="GitHub - terrycain/aioboto3: Wrapper to use boto3 resources with the aiobotocore async backend" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/terrycain/aioboto3">github.com</a></cite></p> <p>今回はDynamoDBしか使用していませんが、READMEを読む限りS3やAthenaなども非同期で扱うことができるようです。</p> <h1 id="非同期でDynamoDBからデータ取得する">非同期でDynamoDBからデータ取得する</h1> <p>以下ではpythonのasyncioという標準ライブラリを使って非同期処理(並行処理)をしていきますが、asyncioそのものについては触れませんので、気になる方は公式ドキュメントなどを参照してみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fdocs.python.org%2Fja%2F3%2Flibrary%2Fasyncio.html" title="asyncio --- 非同期 I/O" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://docs.python.org/ja/3/library/asyncio.html">docs.python.org</a></cite></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> time <span class="synPreProc">import</span> asyncio <span class="synPreProc">import</span> aioboto3 <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">get_item_from_table_async</span>(dynamo_resource, table_name, key, delay): <span class="synConstant">&quot;&quot;&quot;DynamoDBからデータを取得する&quot;&quot;&quot;</span> <span class="synStatement">await</span> asyncio.sleep(delay) table = <span class="synStatement">await</span> dynamo_resource.Table(table_name) response = <span class="synStatement">await</span> table.get_item(Key=key) <span class="synStatement">return</span> response.get(<span class="synConstant">'Item'</span>) <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">main_async</span>(): user_id = xxxx start_time = time.time() session = aioboto3.Session() <span class="synStatement">async</span> <span class="synStatement">with</span> session.resource(<span class="synConstant">'dynamodb'</span>, region_name=<span class="synConstant">'ap-northeast-1'</span>) <span class="synStatement">as</span> dynamo_resource: <span class="synComment"># 同時に実行する非同期タスクのリスト</span> tasks = [ get_item_from_table_async(dynamo_resource, <span class="synConstant">'dynamo_tableA'</span>, {<span class="synConstant">'user_id'</span>: user_id}, <span class="synConstant">10</span>), get_item_from_table_async(dynamo_resource, <span class="synConstant">'dynamo_tableB'</span>, {<span class="synConstant">'user_id'</span>: user_id}, <span class="synConstant">7</span>), ] <span class="synComment"># asyncio.gatherを使用して複数のタスクを同時に実行し、結果を取得</span> results = <span class="synStatement">await</span> asyncio.gather(*tasks) <span class="synIdentifier">print</span>(<span class="synConstant">&quot;Total time:&quot;</span>, time.time() - start_time) <span class="synStatement">return</span> results <span class="synComment"># jupyterなどから実行する場合は関数に直接 await をつけて実行する必要があります</span> <span class="synComment"># .pyで実行する場合は. asyncio.run(main_async())のように記述します</span> result = <span class="synStatement">await</span> main_async() <span class="synComment"># &gt;&gt;&gt; Total time: 10.13998532295227</span> </pre> <p>上記処理を実行すると、処理時間は10秒ほどになり、うまく非同期処理ができていることが分かります。</p> taxa_program DataformをGoogle Cloud上から触ってみる(rawデータから集計テーブルを作るまで) hatenablog://entry/6801883189072260940 2024-01-04T17:43:21+09:00 2024-01-05T11:39:09+09:00 たかぱい(@takapy0210)です。 正月にGoogle CloudのDataformをゴニョゴニョ触っていたので、その備忘録を残しておこうと思います。 Dataformとは dbtとの違いは...? 使用したデータ 実際に動かしてみる 基本的な設定を記載する「dataform.json」 Dataformのディレクトリ構成はどうするのが良いのか 実際に記述するsqlxのコード definitions/sources/mansion.sqlx definitions/outputs/day_aggregated_by_city.sqlx スケジュール実行する Githubと連携してコード管… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104161827.png" width="1200" height="675" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>正月にGoogle CloudのDataformをゴニョゴニョ触っていたので、その備忘録を残しておこうと思います。</p> <ul class="table-of-contents"> <li><a href="#Dataformとは">Dataformとは</a><ul> <li><a href="#dbtとの違いは">dbtとの違いは...?</a></li> </ul> </li> <li><a href="#使用したデータ">使用したデータ</a></li> <li><a href="#実際に動かしてみる">実際に動かしてみる</a><ul> <li><a href="#基本的な設定を記載するdataformjson">基本的な設定を記載する「dataform.json」</a></li> <li><a href="#Dataformのディレクトリ構成はどうするのが良いのか">Dataformのディレクトリ構成はどうするのが良いのか</a></li> <li><a href="#実際に記述するsqlxのコード">実際に記述するsqlxのコード</a><ul> <li><a href="#definitionssourcesmansionsqlx">definitions/sources/mansion.sqlx</a></li> <li><a href="#definitionsoutputsday_aggregated_by_citysqlx">definitions/outputs/day_aggregated_by_city.sqlx</a></li> </ul> </li> <li><a href="#スケジュール実行する">スケジュール実行する</a></li> <li><a href="#Githubと連携してコード管理する">Githubと連携してコード管理する</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> </ul> <h1 id="Dataformとは">Dataformとは</h1> <p>SQL likeなコード(SQLX)でテーブルやビュー作成クエリを記述することで、テーブル間の依存関係を管理することができるデータモデリングツールです。同じようなツールではdbt<a href="#f-5a450f1b" id="fn-5a450f1b" name="fn-5a450f1b" title="https://www.getdbt.com/">*1</a>が有名だと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Foverview%3Fhl%3Dja" title="Dataform の概要  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/overview?hl=ja">cloud.google.com</a></cite></p> <p>Dataformは、以前は独立したSaaS サービスでしたが、2020年12月にGoogle傘下に加わり、2023年6月6日にGAになりました。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fblog%2Fja%2Fproducts%2Fdata-analytics%2Fintroducing-dataform-in-ga" title="Dataform の一般提供開始のお知らせ: BigQuery で SQL パイプラインの開発、バージョン管理、デプロイが可能に | Google Cloud 公式ブログ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/blog/ja/products/data-analytics/introducing-dataform-in-ga">cloud.google.com</a></cite></p> <p>2024/01 現在はBigQueryデータのモデリングツールとして、Google Cloud 管理コンソールから実行できるようになっています。</p> <h2 id="dbtとの違いは">dbtとの違いは...?</h2> <p>Googleで検索するといくつか記事がヒットすると思いますが、</p> <ul> <li>dbtは機能が豊富でコミュニティも大きいが、学習コストがそこそこ高い</li> <li>Dataformは学習コストや運用コストは低い(Google Cloud上であれば無料で実行できる)が、dbtと比べると機能面で劣る</li> </ul> <p>ということが言われているかと思います。</p> <p>今回は個人開発で使用する目的なので、学習コストと運用コストが低いDataformを導入しました。</p> <h1 id="使用したデータ">使用したデータ</h1> <p>今回は僕が趣味で集めている都内のマンション情報のデータが既にBigQueryにあるので、そのRawデータから区ごとの平均坪単価などをDataformで集計して、データマートに自動的に保存するところまでやってみます。</p> <p><figure class="figure-image figure-image-fotolife" title="データのイメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104145158.png" width="1200" height="813" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span><figcaption>データのイメージ</figcaption></figure></p> <h1 id="実際に動かしてみる">実際に動かしてみる</h1> <p>以下のクイックスタート通りに動かしてみると、全体的な動きがある程度理解できるかと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Fquickstart-create-workflow%3Fhl%3Dja" title="SQL ワークフローを作成して実行する  |  Dataform  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/quickstart-create-workflow?hl=ja">cloud.google.com</a></cite></p> <p><figure class="figure-image figure-image-fotolife" title="実際の開発画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104144622.png" width="1200" height="474" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span><figcaption>実際の開発画面</figcaption></figure></p> <h2 id="基本的な設定を記載するdataformjson">基本的な設定を記載する「dataform.json」</h2> <p>開発ワークスペースを初期化するとデフォルトでいくつかファイルが生成されますが、基本的な設定は <code>dataform.json</code> に記述されています。</p> <pre class="code lang-json" data-lang="json" data-unlink> <span class="synSpecial">{</span> &quot;<span class="synStatement">defaultSchema</span>&quot;: &quot;<span class="synConstant">dataform</span>&quot;, &quot;<span class="synStatement">assertionSchema</span>&quot;: &quot;<span class="synConstant">dataform_assertions</span>&quot;, &quot;<span class="synStatement">warehouse</span>&quot;: &quot;<span class="synConstant">bigquery</span>&quot;, &quot;<span class="synStatement">defaultDatabase</span>&quot;: &quot;<span class="synConstant">hoge</span>&quot;, &quot;<span class="synStatement">defaultLocation</span>&quot;: &quot;<span class="synConstant">asia-northeast1</span>&quot; <span class="synSpecial">}</span> </pre> <ul> <li>defaultSchema: Dataform がアセットを作成する BigQuery データセットを指定する</li> <li>assertionSchema: Dataform がアサーション結果を含むビューを作成する BigQuery データセットを指定する</li> <li>warehouse: Dataform がアセットを作成する BigQuery へのポインタ。”bigquery” を指定する</li> <li>defaultDatabase: Dataform がアセットを作成する Google Cloud プロジェクトIDを指定する</li> <li>defaultLocation: デフォルトの BigQuery データセットのロケーションを指定する</li> </ul> <h2 id="Dataformのディレクトリ構成はどうするのが良いのか">Dataformのディレクトリ構成はどうするのが良いのか</h2> <p>買収前の従来のDataformのドキュメント<a href="#f-532e1106" id="fn-532e1106" name="fn-532e1106" title="https://docs.dataform.co/best-practices/start-your-dataform-project">*2</a>には、definitions ディレクトリ配下に「Sources」、「Staging」、「Reporting」を用意することが推奨されていますが、Google Cloudのドキュメントには、ベストプラクティスとして以下のような構成が推奨されています。</p> <ul> <li><code>sources</code>: データソース宣言を格納</li> <li><code>intermediate</code>: データ変換ロジックを格納</li> <li><code>outputs</code>: 出力テーブルの定義を格納</li> <li><code>extras</code> - 追加のファイルを格納(省略可)</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Fbest-practices%3Fhl%3Dja" title="Dataform のベスト プラクティスの概要  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/best-practices?hl=ja">cloud.google.com</a></cite></p> <p>今回はGoogle Cloudのベスプラに倣い、以下の構成で開発しました。</p> <p><figure class="figure-image figure-image-fotolife" title="Google Cloud上の開発画面から見るディレクトリ構成"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104145741.png" width="328" height="351" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>Google Cloud上の開発画面から見るディレクトリ構成</figcaption></figure></p> <h2 id="実際に記述するsqlxのコード">実際に記述するsqlxのコード</h2> <p>今回は以下の2つを追加しています。</p> <ul> <li>definitions/sources/mansion.sqlx</li> <li>definitions/outputs/day_aggregated_by_city.sqlx</li> </ul> <h3 id="definitionssourcesmansionsqlx">definitions/sources/mansion.sqlx</h3> <p>ここではDataformで管理するテーブルを定義します。declarationを使ってテーブルを定義するのみで集計クエリなどは記載しません。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>config { <span class="synSpecial">type</span>: <span class="synSpecial">&quot;</span><span class="synConstant">declaration</span><span class="synSpecial">&quot;</span>, database: <span class="synSpecial">&quot;</span><span class="synConstant">hoge</span><span class="synSpecial">&quot;</span>, schema: <span class="synSpecial">&quot;</span><span class="synConstant">lake</span><span class="synSpecial">&quot;</span>, name: <span class="synSpecial">&quot;</span><span class="synConstant">mansion</span><span class="synSpecial">&quot;</span> } </pre> <p>Dataformのクエリで直接 <code>SELECT * FROM project-id.dataset.table</code> のように書くことはできますが、直接参照だとデータリネージで自動的に可視化されないので利用するテーブルはここで定義することをお勧めします。</p> <p>declarationで定義しておくと <code>SELECT * FROM ${ref(test_table)}</code> という書き方で参照できます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Fdefine-table%3Fhl%3Dja" title="テーブルを作成する  |  Dataform  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/define-table?hl=ja">cloud.google.com</a></cite></p> <h3 id="definitionsoutputsday_aggregated_by_citysqlx">definitions/outputs/day_aggregated_by_city.sqlx</h3> <p>ここでは、データレイク(マンション情報)からデータを集計して、データマートに新しいテーブルを作成するクエリを定義します。</p> <p>前述した通り、前提となるテーブルを「${ref("mansion")} 」という記法で参照できます。Dataform におけるテーブル間の依存関係管理はこれを書くだけでOKです。</p> <p>パーティションやクラスタ<a href="#f-be495521" id="fn-be495521" name="fn-be495521" title="https://cloud.google.com/dataform/docs/partitions-clusters?hl=ja">*3</a>の指定も <code>config</code>で行えます。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>config { <span class="synSpecial">type</span>: <span class="synSpecial">&quot;</span><span class="synConstant">table</span><span class="synSpecial">&quot;</span>, database: <span class="synSpecial">&quot;</span><span class="synConstant">hoge</span><span class="synSpecial">&quot;</span>, schema: <span class="synSpecial">&quot;</span><span class="synConstant">mart</span><span class="synSpecial">&quot;</span>, columns: { city: <span class="synSpecial">&quot;</span><span class="synConstant">市区町村ラベル(e.g. chuoku)</span><span class="synSpecial">&quot;</span>, <span class="synIdentifier">count</span>: <span class="synSpecial">&quot;</span><span class="synConstant">レコード数</span><span class="synSpecial">&quot;</span>, avg_price: <span class="synSpecial">&quot;</span><span class="synConstant">物件価格(万円) - 平均</span><span class="synSpecial">&quot;</span>, avg_price_per_unit: <span class="synSpecial">&quot;</span><span class="synConstant">坪単価(万円) - 平均</span><span class="synSpecial">&quot;</span>, avg_price_per_square_meter: <span class="synSpecial">&quot;</span><span class="synConstant">平米単価(万円) - 平均</span><span class="synSpecial">&quot;</span>, stddev_price: <span class="synSpecial">&quot;</span><span class="synConstant">物件価格(万円) - 標準偏差</span><span class="synSpecial">&quot;</span>, stddev_price_per_unit: <span class="synSpecial">&quot;</span><span class="synConstant">坪単価(万円) - 標準偏差</span><span class="synSpecial">&quot;</span>, stddev_price_per_square_meter: <span class="synSpecial">&quot;</span><span class="synConstant">平米単価(万円) - 標準偏差</span><span class="synSpecial">&quot;</span>, meta_execution_date: <span class="synSpecial">&quot;</span><span class="synConstant">処理実行日(yyyy-mm-dd)</span><span class="synSpecial">&quot;</span>, }, bigquery: { partitionBy: <span class="synSpecial">&quot;</span><span class="synConstant">meta_execution_date</span><span class="synSpecial">&quot;</span>, clusterBy: [<span class="synSpecial">&quot;</span><span class="synConstant">city</span><span class="synSpecial">&quot;</span>] }, } <span class="synStatement">SELECT</span> city, <span class="synIdentifier">COUNT</span>(*) <span class="synSpecial">AS</span> <span class="synIdentifier">count</span>, <span class="synIdentifier">ROUND</span>(<span class="synIdentifier">AVG</span>(price_yen/<span class="synConstant">10000</span>), <span class="synConstant">2</span>) <span class="synSpecial">AS</span> avg_price, <span class="synIdentifier">ROUND</span>(<span class="synIdentifier">AVG</span>(price_per_unit/<span class="synConstant">10000</span>), <span class="synConstant">2</span>) <span class="synSpecial">AS</span> avg_price_per_unit, <span class="synIdentifier">ROUND</span>(<span class="synIdentifier">AVG</span>(price_per_square_meter/<span class="synConstant">10000</span>), <span class="synConstant">2</span>) <span class="synSpecial">AS</span> avg_price_per_square_meter, <span class="synIdentifier">ROUND</span>(<span class="synIdentifier">STDDEV</span>(price_yen/<span class="synConstant">10000</span>), <span class="synConstant">2</span>) <span class="synSpecial">AS</span> stddev_price, <span class="synIdentifier">ROUND</span>(<span class="synIdentifier">STDDEV</span>(price_per_unit/<span class="synConstant">10000</span>), <span class="synConstant">2</span>) <span class="synSpecial">AS</span> stddev_price_per_unit, <span class="synIdentifier">ROUND</span>(<span class="synIdentifier">STDDEV</span>(price_per_square_meter/<span class="synConstant">10000</span>), <span class="synConstant">2</span>) <span class="synSpecial">AS</span> stddev_price_per_square_meter, meta_execution_date, <span class="synSpecial">FROM</span> ${ref(<span class="synSpecial">&quot;</span><span class="synConstant">mansion</span><span class="synSpecial">&quot;</span>)} <span class="synSpecial">WHERE</span> <span class="synConstant">0</span> = <span class="synConstant">0</span> <span class="synStatement">AND</span> city <span class="synSpecial">IS</span> <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> <span class="synSpecial">GROUP</span> <span class="synSpecial">BY</span> city, meta_execution_date </pre> <p>ここまでで、手動で新規テーブルを作成する準備は終わりです。コンソール上から手動実行すれば、BigQueryの <code>mart</code> 配下にテーブルができます。 <figure class="figure-image figure-image-fotolife" title="テーブルが作成されている様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104153428.png" width="1200" height="441" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span><figcaption>テーブルが作成されている様子</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="作成されたデータ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104153629.png" width="1200" height="653" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span><figcaption>作成されたデータ</figcaption></figure></p> <h2 id="スケジュール実行する">スケジュール実行する</h2> <p>以下の手順に沿って設定し、自動化を行います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Fquickstart-schedule-production-executions%3Fhl%3Dja" title="クイックスタート: 本番環境実行のスケジュール設定  |  Dataform  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/quickstart-schedule-production-executions?hl=ja">cloud.google.com</a></cite></p> <p>手順は大きく分けて「リリース構成」の作成と、「ワークフロー構築」の2ステップあります。</p> <p>リリース構成とは、リポジトリを定期的にコンパイルしてリリースする頻度を指定するものです。<br/> ワークフロー構築とは、実際に動くワークフローを指定するものです。</p> <p>注意点として、公式ドキュメント<a href="#f-615918c3" id="fn-615918c3" name="fn-615918c3" title="https://cloud.google.com/dataform/docs/quickstart-schedule-production-executions?hl=ja#create-workflow-config">*4</a>にも記載されているように、リリース構成で指定した時間より、ワークフロー構築で指定する実行時間を、最短でも1h開けておく必要があるようです。</p> <blockquote><p>Dataform が対応するリリース構成で最新のコンパイル結果を確実に実行するには、コンパイル結果の作成時刻とスケジュールされた実行時刻の間に少なくとも 1 時間の間隔を設けます。</p></blockquote> <p><figure class="figure-image figure-image-fotolife" title="実際の設定画面"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104152923.png" width="743" height="1200" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span><figcaption>実際の設定画面</figcaption></figure></p> <p>設定後、時間が過ぎると正しく動いていることが確認できました。<br/> ワークフロー実行のログも、コンソール上から確認できます。</p> <p><figure class="figure-image figure-image-fotolife" title="ワークフロー実行のログ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20240104/20240104153215.png" width="1006" height="210" loading="lazy" title="" class="hatena-fotolife" style="width:800px" itemprop="image"></span><figcaption>ワークフロー実行のログ</figcaption></figure></p> <h2 id="Githubと連携してコード管理する">Githubと連携してコード管理する</h2> <p>最後にGithubとの連携を行い、コードをリポジトリで管理できるようにします。</p> <p>こちらも公式ドキュメントが出ているので、基本的にはこの通りに設定すれば問題なく動くと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Fconnect-repository%3Fhl%3Dja" title="サードパーティの Git リポジトリに接続する  |  Dataform  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/connect-repository?hl=ja">cloud.google.com</a></cite></p> <h1 id="おわりに">おわりに</h1> <p>触ってみた感じ、WebUIでの開発環境がとっつきやすく、自動フォーマットもボタン1つで行なってくれるので開発効率は良さそうに感じました。</p> <p>増分モデルなども簡単に実装できそうなので、この辺もいじっていきたいと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fdataform%2Fdocs%2Fincremental-tables%3Fhl%3Dja" title="増分テーブルを構成する  |  Dataform  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/dataform/docs/incremental-tables?hl=ja">cloud.google.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-5a450f1b" id="f-5a450f1b" name="f-5a450f1b" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://www.getdbt.com/">https://www.getdbt.com/</a></span></p> <p class="footnote"><a href="#fn-532e1106" id="f-532e1106" name="f-532e1106" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.dataform.co/best-practices/start-your-dataform-project">https://docs.dataform.co/best-practices/start-your-dataform-project</a></span></p> <p class="footnote"><a href="#fn-be495521" id="f-be495521" name="f-be495521" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://cloud.google.com/dataform/docs/partitions-clusters?hl=ja">https://cloud.google.com/dataform/docs/partitions-clusters?hl=ja</a></span></p> <p class="footnote"><a href="#fn-615918c3" id="f-615918c3" name="f-615918c3" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://cloud.google.com/dataform/docs/quickstart-schedule-production-executions?hl=ja#create-workflow-config">https://cloud.google.com/dataform/docs/quickstart-schedule-production-executions?hl=ja#create-workflow-config</a></span></p> </div> taxa_program 予期的UXという概念がとてもしっくりきた話 hatenablog://entry/6801883189067048904 2023-12-17T08:27:33+09:00 2023-12-17T08:27:33+09:00 みなさんこんにちは。たかぱい(@takapy0210)です。 最近、UX白書*1で述べられている「予期的UX」という言葉を知り「まさにこのUXを上げるために試行錯誤してるんだよな〜」と、しっくりきたので、まだ完全に理解したフェーズ*2ではありますが、ここに今感じていることをメモしておこうと思います。 (ちなみに色々調べてみると、すでに素晴らしい記事が公開されていましたので、こちらを見ていただいた方が理解は進むと思います) この記事はコネヒト Advent Calendarのカレンダー 16日目の記事です。 adventar.org UX白書とは 予期的UXを紐解く ユーザーエクスペリエンスの期… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20231216/20231216003717.png" width="1200" height="801" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>みなさんこんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>最近、UX白書<a href="#f-8d540ff8" id="fn-8d540ff8" name="fn-8d540ff8" title="https://site.hcdvalue.org/docs">*1</a>で述べられている「予期的UX」という言葉を知り「まさにこのUXを上げるために試行錯誤してるんだよな〜」と、しっくりきたので、まだ完全に理解したフェーズ<a href="#f-2cb5be2f" id="fn-2cb5be2f" name="fn-2cb5be2f" title="https://jp.quora.com/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E7%8B%AC%E7%89%B9%E3%81%AA%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E7%94%A8%E8%AA%9E">*2</a>ではありますが、ここに今感じていることをメモしておこうと思います。<br/> (ちなみに色々調べてみると、<a href="https://mikihirocks.medium.com/%E4%BA%88%E6%9C%9F%E7%9A%84ux%E3%81%AB%E3%83%95%E3%82%A9%E3%83%BC%E3%82%AB%E3%82%B9%E3%81%97%E3%81%9F%E8%A1%8C%E5%8B%95%E8%A8%AD%E8%A8%88-c747291d5944#.vt035uec9">すでに素晴らしい記事</a>が公開されていましたので、こちらを見ていただいた方が理解は進むと思います)</p> <p>この記事は<a href="https://adventar.org/calendars/8994">コネヒト Advent Calendarのカレンダー</a> 16日目の記事です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fadventar.org%2Fcalendars%2F8994" title="コネヒト Advent Calendar 2023 - Adventar" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://adventar.org/calendars/8994">adventar.org</a></cite></p> <ul class="table-of-contents"> <li><a href="#UX白書とは">UX白書とは</a></li> <li><a href="#予期的UXを紐解く">予期的UXを紐解く</a><ul> <li><a href="#ユーザーエクスペリエンスの期間図を解釈する">ユーザーエクスペリエンスの期間図を解釈する</a></li> <li><a href="#つまるところ予期的UXとは何者なのか">つまるところ、予期的UXとは何者なのか?</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> <li><a href="#参考文献">参考文献</a></li> </ul> <h1 id="UX白書とは">UX白書とは</h1> <p>2010年にドイツでUX概念の議論が行われ、その内容が「UX白書」として世に広まったらしいです。</p> <p>このUX白書では、『UXは「現象( phenomenon)として」 「研究分野( afield of study)として」 「実践( practice)として」などの異なった視点から捉えることができる』と述べられており、その中でも 「現象としてのUX」と 「実践としてのUX」にフォーカスして記されています。</p> <p>また、UXとは何か、UXではないものは何か、といったUXの基礎的なな部分から、UXに影響を与える要素は何か?など、発展的な内容まで幅広く記されています。</p> <p>その中でも特に印象的だったのが、商品やサービスの「利用前」から「利用後」と「全体」を期間別に分類する手法である「ユーザエクスペリエンスの期間」という概念でした。 これは時間軸でUXを認識することで、「どの期間のUXなのか?」の認識合わせや相互理解を行うことができるものとなっており、この時間軸の最初に登場するのが、タイトルにもある「予期的UX」という言葉でした。</p> <p><figure class="figure-image figure-image-fotolife" title="https://drive.google.com/file/d/1H-cJgyUu_utpFS-l24g-Y4fz23ZLyfUi/view より引用"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20231215/20231215195243.png" width="738" height="248" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption><a href="https://drive.google.com/file/d/1H-cJgyUu_utpFS-l24g-Y4fz23ZLyfUi/view">UX白書</a> より引用</figcaption></figure></p> <p>UX白書の原文は<a href="https://experienceresearchsociety.org/ux/">こちら</a>から読むことができます。<br/> ありがたいことに<a href="https://drive.google.com/file/d/1H-cJgyUu_utpFS-l24g-Y4fz23ZLyfUi/view">日本語訳されたもの</a>も公開されています。</p> <h1 id="予期的UXを紐解く">予期的UXを紐解く</h1> <p>そもそもこの予期的UXってなんぞ?という話なのですが、僕は最初にみた時、サービス初回利用以前の期間の「どこでサービスを知り、どんな期待値を持ってサービスを使い始めるのか」という、いわゆる広告やプロモーション領域の話を指していると思いました。</p> <p>ところが、UX白書を読んでみると以下のような記述があります。</p> <blockquote><p> 「経験する」と 「ある経験」の間にある差とは、 UXに焦点を当てるときに適切な期間の差 なのかという疑問が生じます。 極端に言えば、 誰かが非常に短い一瞬に何を経験するか、 例えば利 用中の直感的な反応などに焦点を当てることも可能でしょう。 またその一方で、 何ヶ月かのもしく はそれ以上におよぶ体験の中で、 利用中のエピソ ードや利用していない時間を通して形成される、 累積された経験に焦点を当てることも可能でしょう。 その結果、UXは、インタラクション中に感 じる感情の特定の変化 (一時的UX )、ある特定の利用エピ ソ ードに関する評価 (エピソード的UX )、 特定のシステムをしばらくの期間利用した後の見方(累積的UX)で表されます。 <strong>予期的UXとは、ユー ザーにとっての初めての利用よりも前の期間、あるいは上述の3つのUXの期間よりも以前のこと だと言えます。</strong> なぜなら、 人はインタラクション中のある特定の瞬間、 利用エピソード、 システム の利用経験後の生活を想像するかも知れないからです。</p></blockquote> <p>なので、予期的UXは</p> <ol> <li>サービスの初回利用以前の期間</li> <li>「一時的UX」「エピソード的UX」「累積的UX」といった各UX期間の前</li> </ol> <p>のことを指していることが分かります。</p> <h2 id="ユーザーエクスペリエンスの期間図を解釈する">ユーザーエクスペリエンスの期間図を解釈する</h2> <p>ここでもう一度先に挙げた図を見てみます。</p> <p><figure class="figure-image figure-image-fotolife" title=""><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20231215/20231215195243.png" width="738" height="248" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption></figcaption></figure></p> <p>これを見てみると、いくつか矢印が出ていることが分かります。</p> <p>矢印は「自身からループしている矢印」と「前の区分から出ている矢印」の2種類があります。</p> <p>ここで注目したいのは、<strong>予期的UXには自身からのループ以外に、別の区分それぞれから3本の矢印が向かってきているということです。</strong></p> <p>Webサービスに限らず、日常的に、ある体験の前には必ず予期的な体験(いわゆる期待値的なもの)があり、それは、それ以前の体験から影響を受けるという感覚は自然に感じます。</p> <p>例えば、Youtubeを開くときは以前の体験から期待する予期的UXがあり起動という行動を起こしますし、そこでまた新しい体験をすることで、次回の予期的UXが創られているんだと思います。<br/> 「またディズニーランドに行きたい!」と思うのは、過去の体験が素晴らしいものであり、あのアトラクションに乗りたい、あのショーが見たい、日常を忘れたい、など「あの体験をもう一度」みたいなことを思う方が多いのではないでしょうか。</p> <p>UX白書の中でも以下のように述べられています。</p> <blockquote><p> UXはライフサイクルや旅のようなものとして構造化することもできます。 例えば、初めての出会いから、 何度か使ってみた時のエピソードを経て、 使っていた時のことを思い返すという構造です。 これまでに使ってきた経験はこれからの使い方に影響をおよぼします。 例えば、何かを使ってみた 一回の経験を思い返し、 それを語ることによって、 今後の使い方が予測されるでしょう。 さまざまな経験がいろいろな順番で互いに幾重にも重なり合い、 利用前に想像していた状態から利用後にあれこれ思い返す状態まで、UXに決まった流れはないのです。</p></blockquote> <p><figure class="figure-image figure-image-fotolife" title="利用と非利用の期間からなる、時の経過につれたUX"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20231216/20231216003306.png" width="613" height="806" loading="lazy" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span><figcaption>利用と非利用の期間からなる、時の経過につれたUX</figcaption></figure></p> <p>そう言った意味でも、<strong>「サービス初回利用以前の期間」だけでなく、サービスを継続的に利用している中でも、この「予期的UX」は必ず通る道となっていそうです。</strong></p> <h2 id="つまるところ予期的UXとは何者なのか">つまるところ、予期的UXとは何者なのか?</h2> <p>予期的UXは体験の中でも唯一行動(サービスの利用)に影響を及ぼせるものだと言えそうです。</p> <p>サービス内での素晴らしい成功体験(一時的UX・エピソード的UX)や、長期的に構築したブランド(累積的UX)なども、予期的UXを通して影響するものと言えそうです。</p> <p>そう考えると、何事もこの予期的UXにフォーカスして設計するのが大事そうです。</p> <h1 id="おわりに">おわりに</h1> <p>僕はこの概念を知る前までは「サービスを起動する前の期待値を上げる」みたいな言い方をしていましたが、それがまさに今回の予期的UXなのでは?と感じています。</p> <p>予期的UXも結局のところ考え方の1つで、「予期的UXを上げるための体験ってどういうものがあるのかな?」とか、「こういう予期的UXがあると行動してくれそうだよね」という会話の一部として使っていくのが良いのではないかと思いました。</p> <h1 id="参考文献">参考文献</h1> <ul> <li><a href="https://site.hcdvalue.org/docs">■公開資料 UX白書</a></li> <li><a href="https://experienceresearchsociety.org/ux/">User Experience • EXPRESSO</a></li> <li><a href="https://mikihirocks.medium.com/%E4%BA%88%E6%9C%9F%E7%9A%84ux%E3%81%AB%E3%83%95%E3%82%A9%E3%83%BC%E3%82%AB%E3%82%B9%E3%81%97%E3%81%9F%E8%A1%8C%E5%8B%95%E8%A8%AD%E8%A8%88-c747291d5944#.vt035uec9">行動を設計するなら予期的UXにフォーカスしよう. UXをKPIにヒットさせるための考え方</a></li> </ul> <div class="footnote"> <p class="footnote"><a href="#fn-8d540ff8" id="f-8d540ff8" name="f-8d540ff8" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://site.hcdvalue.org/docs">https://site.hcdvalue.org/docs</a></span></p> <p class="footnote"><a href="#fn-2cb5be2f" id="f-2cb5be2f" name="f-2cb5be2f" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://jp.quora.com/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E7%8B%AC%E7%89%B9%E3%81%AA%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E7%94%A8%E8%AA%9E">https://jp.quora.com/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E7%8B%AC%E7%89%B9%E3%81%AA%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E7%94%A8%E8%AA%9E</a></span></p> </div> taxa_program FastAPIで特定エンドポイント(Path)のログを出力しない方法 hatenablog://entry/820878482954197828 2023-07-31T18:13:15+09:00 2023-07-31T23:34:49+09:00 最近FastAPIに触る機会があり、ログ周りで少し躓いたので備忘録として残しておきます。 背景 補足 特定エンドポイントのログをフィルタリングする方法 おまけ Uvicornのログをjson形式で出力する方法 エンドポイント毎のレスポンスタイムを計測する方法 背景 UvicornでFastAPIアプリケーションを実行する場合、デフォルトではリクエストごとにログを出力し続けます。 実際に、AWS ECSにデプロイしたFastAPI サービスでCloudwatch へログ出力してみると、ALBがヘルスチェック用のエンドポイントにリクエストを送る度に、"200 OK "と出力されてしまい、ログの可読… <p>最近FastAPIに触る機会があり、ログ周りで少し躓いたので備忘録として残しておきます。</p> <ul class="table-of-contents"> <li><a href="#背景">背景</a><ul> <li><a href="#補足">補足</a></li> </ul> </li> <li><a href="#特定エンドポイントのログをフィルタリングする方法">特定エンドポイントのログをフィルタリングする方法</a></li> <li><a href="#おまけ">おまけ</a><ul> <li><a href="#Uvicornのログをjson形式で出力する方法">Uvicornのログをjson形式で出力する方法</a></li> <li><a href="#エンドポイント毎のレスポンスタイムを計測する方法">エンドポイント毎のレスポンスタイムを計測する方法</a></li> </ul> </li> </ul> <h1 id="背景">背景</h1> <p>UvicornでFastAPIアプリケーションを実行する場合、デフォルトではリクエストごとにログを出力し続けます。</p> <p>実際に、AWS ECSにデプロイしたFastAPI サービスでCloudwatch へログ出力してみると、ALBがヘルスチェック用のエンドポイントにリクエストを送る度に、"200 OK "と出力されてしまい、ログの可読性がなかなか辛い感じになりました。</p> <p>そこで、ヘルスチェックに使用するエンドポイントなど、出力したくないログをフィルタリングできないかな?と思い色々調べました。</p> <h2 id="補足">補足</h2> <p>Uvicornは以下のように起動することができます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># portryで実行する例</span> poetry run uvicorn app.api:app <span class="synSpecial">--host</span> <span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span> <span class="synSpecial">--port</span> <span class="synConstant">9010</span> <span class="synSpecial">--reload</span> </pre> <p>この状態でFastAPIの特定エンドポイントにアクセスすると、以下のようなログがデフォルトで出力されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>INFO: <span class="synConstant">127</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.1:34954 - <span class="synStatement">&quot;</span><span class="synConstant">GET /ping HTTP/1.1</span><span class="synStatement">&quot;</span> <span class="synConstant">200</span> OK </pre> <h1 id="特定エンドポイントのログをフィルタリングする方法">特定エンドポイントのログをフィルタリングする方法</h1> <p>以下のように、フィルタするクラスを作成します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> logging <span class="synStatement">class</span> <span class="synIdentifier">EndpointFilter</span>(logging.Filter): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, excluded_endpoints: <span class="synIdentifier">list</span>[<span class="synIdentifier">str</span>]) -&gt; <span class="synIdentifier">None</span>: self.excluded_endpoints = excluded_endpoints <span class="synStatement">def</span> <span class="synIdentifier">filter</span>(self, record: logging.LogRecord) -&gt; <span class="synIdentifier">bool</span>: <span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant"> Filter関数</span> <span class="synConstant"> record.argsには ('127.0.0.1:51632', 'GET', '/ping', '1.1', 200) のような内容が入っています</span> <span class="synConstant"> &quot;&quot;&quot;</span> <span class="synStatement">return</span> record.args <span class="synStatement">and</span> <span class="synIdentifier">len</span>(record.args) &gt;= <span class="synConstant">3</span> <span class="synStatement">and</span> record.args[<span class="synConstant">2</span>] <span class="synStatement">not</span> <span class="synStatement">in</span> self.excluded_endpoints </pre> <p>あとは、loggerを作成するタイミングで以下のようにフィルタを追加するだけでOKです。<br/> これで <code>/ping</code> にアクセスがあった場合はログ出力されず、 <code>/test</code>へアクセスがあった場合はログ出力されます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> logging <span class="synPreProc">from</span> fastapi <span class="synPreProc">import</span> FastAPI <span class="synPreProc">from</span> log_filters <span class="synPreProc">import</span> EndpointFilter app = FastAPI() <span class="synComment"># 除外したいエンドポイントを指定</span> excluded_endpoints = [<span class="synConstant">&quot;/ping&quot;</span>] <span class="synComment"># フィルターを追加</span> logging.getLogger(<span class="synConstant">&quot;uvicorn.access&quot;</span>).addFilter(EndpointFilter(excluded_endpoints)) <span class="synPreProc">@</span><span class="synIdentifier">app.get</span>(<span class="synConstant">'/ping'</span>) <span class="synStatement">def</span> <span class="synIdentifier">ping</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">'ping endpoint'</span>) <span class="synPreProc">@</span><span class="synIdentifier">app.get</span>(<span class="synConstant">'/test'</span>) <span class="synStatement">def</span> <span class="synIdentifier">test</span>(): <span class="synIdentifier">print</span>(<span class="synConstant">'test endpoint'</span>) </pre> <h1 id="おまけ">おまけ</h1> <p>ログ周りで今後使えそうなTipsもついでに残しておきます。</p> <h2 id="Uvicornのログをjson形式で出力する方法">Uvicornのログをjson形式で出力する方法</h2> <p>以下のようにログフォーマットファイルをyamlで定義しておくことで、json形式で出力することができます。<br/> Amazon CloudWatchなどにログ出力する場合、CloudWatchのコンソール上でjsonをいい感じにパースして見ることができるので、(場合にもよりますが)jsonで定義しておくとメリットが大きいと思います。</p> <p><code>pythonjsonlogger.jsonlogger.JsonFormatter</code> を使用するには、<a href="https://pypi.org/project/python-json-logger/">python-json-logger </a>のインストールが必要なので、あらかじめインストールしておきます。</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">version</span><span class="synSpecial">:</span> <span class="synConstant">1</span> <span class="synIdentifier">disable_existing_loggers</span><span class="synSpecial">:</span> <span class="synConstant">false</span> <span class="synIdentifier">formatters</span><span class="synSpecial">:</span> <span class="synIdentifier">json</span><span class="synSpecial">:</span> <span class="synIdentifier">format</span><span class="synSpecial">:</span> <span class="synConstant">&quot;%(asctime)s %(levelname)s %(message)s %(filename)s %(module)s %(funcName)s %(lineno)d&quot;</span> <span class="synIdentifier">class</span><span class="synSpecial">:</span> pythonjsonlogger.jsonlogger.JsonFormatter <span class="synIdentifier">handlers</span><span class="synSpecial">:</span> <span class="synIdentifier">console</span><span class="synSpecial">:</span> <span class="synIdentifier">class</span><span class="synSpecial">:</span> logging.StreamHandler <span class="synIdentifier">level</span><span class="synSpecial">:</span> INFO <span class="synIdentifier">formatter</span><span class="synSpecial">:</span> json <span class="synIdentifier">root</span><span class="synSpecial">:</span> <span class="synIdentifier">level</span><span class="synSpecial">:</span> INFO <span class="synIdentifier">handlers</span><span class="synSpecial">:</span> <span class="synSpecial">[</span>console<span class="synSpecial">]</span> </pre> <p>この状態で<code>--log-config</code>オプションをつけて実行することで、Uvicornのログもjson形式で出力されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># portryで実行する例</span> poetry run uvicorn app.api:app <span class="synSpecial">--host</span> <span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span> <span class="synSpecial">--port</span> <span class="synConstant">9010</span> <span class="synSpecial">--reload</span> <span class="synSpecial">--log-config</span> ./app/loggers/logger_config.yaml </pre> <h2 id="エンドポイント毎のレスポンスタイムを計測する方法">エンドポイント毎のレスポンスタイムを計測する方法</h2> <p>FastAPIには<a href="https://fastapi.tiangolo.com/ja/tutorial/middleware/">ミドルウェア</a>という概念があり、すべてのリクエストに対して、それがあらゆる特定のPath Operationによって処理される前に機能する関数を定義することができます。</p> <p>これを使い、以下のように記述することで、レスポンスタイムやその他の情報をログに出力しておくことができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> fastapi <span class="synPreProc">import</span> FastAPI app = FastAPI() <span class="synPreProc">@</span><span class="synIdentifier">app.middleware</span>(<span class="synConstant">&quot;http&quot;</span>) <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">process_time</span>(request: Request, call_next): <span class="synConstant">&quot;&quot;&quot;リクエストの処理時間などを表示するミドルウェア関数</span> <span class="synConstant"> /pingエンドポイントは除外している</span> <span class="synConstant"> &quot;&quot;&quot;</span> start_time = time.time() response = <span class="synStatement">await</span> call_next(request) request_log = { <span class="synConstant">&quot;method&quot;</span>: request.method, <span class="synConstant">&quot;url&quot;</span>: request.url.path, <span class="synConstant">&quot;query_params&quot;</span>: request.query_params, <span class="synConstant">&quot;path_params&quot;</span>: request.path_params, <span class="synConstant">&quot;status_code&quot;</span>: response.status_code, <span class="synConstant">&quot;response_time&quot;</span>: time.time() - start_time, <span class="synConstant">&quot;client&quot;</span>: request.client, } logger.info(<span class="synConstant">&quot;Response Log&quot;</span>, extra=request_log) <span class="synStatement">return</span> response </pre> <p>ちなみに、FastAPIを学ぶ際に以下の本に大変お世話になったので、これからFastAPIを触ってみるかたにはおすすめです!</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0C37HM2V4?tag=takapy00-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/513pstMXcaL._SL500_.jpg" class="hatena-asin-detail-image" alt="動かして学ぶ!Python FastAPI開発入門" title="動かして学ぶ!Python FastAPI開発入門"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0C37HM2V4?tag=takapy00-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">動かして学ぶ!Python FastAPI開発入門</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%C3%E6%C2%BC%20%E6%C6" class="keyword">中村 翔</a></li><li>翔泳社</li></ul><a href="https://www.amazon.co.jp/dp/B0C37HM2V4?tag=takapy00-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>おわり</p> taxa_program 2022年の振り返り hatenablog://entry/4207112889946293301 2022-12-19T01:52:30+09:00 2022-12-19T10:02:25+09:00 みなさんこんにちは。たかぱい(@takapy0210)です。 年末も近づいてきましたので、久しぶりに今年の振り返りでもしていこうと思います。 この記事はコネヒト Advent Calendarのカレンダー 17日目 の記事です。 お仕事 オンボーディング改善 A/Bテストの標準化 機械学習の取り組みを見える化 新しい役割へのチャレンジ 登壇 みんなのPython勉強会#85 PyConJP 2022 プライベート Podcastのエピソード数が100を超えた ポケモンユナイトというゲームにハマった 不動産を買った 2023年は? お仕事 夏頃まではガッツリ開発しつつ、ここ2ヶ月くらいは今までと… <p>みなさんこんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>年末も近づいてきましたので、久しぶりに今年の振り返りでもしていこうと思います。</p> <p>この記事は<a href="https://qiita.com/advent-calendar/2022/connehito">コネヒト Advent Calendarのカレンダー 17日目</a> の記事です。</p> <ul class="table-of-contents"> <li><a href="#お仕事">お仕事</a><ul> <li><a href="#オンボーディング改善">オンボーディング改善</a></li> <li><a href="#ABテストの標準化">A/Bテストの標準化</a></li> <li><a href="#機械学習の取り組みを見える化">機械学習の取り組みを見える化</a></li> <li><a href="#新しい役割へのチャレンジ">新しい役割へのチャレンジ</a></li> <li><a href="#登壇">登壇</a><ul> <li><a href="#みんなのPython勉強会85">みんなのPython勉強会#85</a></li> <li><a href="#PyConJP-2022">PyConJP 2022</a></li> </ul> </li> </ul> </li> <li><a href="#プライベート">プライベート</a><ul> <li><a href="#Podcastのエピソード数が100を超えた">Podcastのエピソード数が100を超えた</a></li> <li><a href="#ポケモンユナイトというゲームにハマった">ポケモンユナイトというゲームにハマった</a></li> <li><a href="#不動産を買った">不動産を買った</a></li> </ul> </li> <li><a href="#2023年は">2023年は?</a></li> </ul> <h1 id="お仕事">お仕事</h1> <p>夏頃まではガッツリ開発しつつ、ここ2ヶ月くらいは今までとは少し異なる働き方にチャレンジしてみています。</p> <h2 id="オンボーディング改善">オンボーディング改善</h2> <p>上半期は主にレコメンデーションの改善に取り組んでいましたが、中でもオンボーディングには力を入れて取り組んでいました。</p> <p>この施策は数値的にもポジティブでしたが、より良いプロダクトにしていくためにも改善を続けていこうと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.connehito.com%2Fentry%2F2022%2F09%2F06%2F183758" title="オンボーディング改善に機械学習を活用する〜トピックモデルによる興味選択編〜 - コネヒト開発者ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.connehito.com/entry/2022/09/06/183758">tech.connehito.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.connehito.com%2Fentry%2F2022%2F09%2F29%2F181152" title="オンボーディング改善に機械学習を活用する〜Graph Embedding(node2vec)による推薦アイテム計算〜 - コネヒト開発者ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.connehito.com/entry/2022/09/29/181152">tech.connehito.com</a></cite></p> <h2 id="ABテストの標準化">A/Bテストの標準化</h2> <p>開発以外の部分で言うと、社内のABテスト標準化へ向けた取り組みも行いました。<br/> 今ではこの標準化ドキュメントがたくさん蓄積されており、未来への資産がどんどん溜まっていっています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.connehito.com%2Fentry%2F2022%2F07%2F29%2F170913" title="A/Bテスト標準化へ取り組んだ話 - コネヒト開発者ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.connehito.com/entry/2022/07/29/170913">tech.connehito.com</a></cite></p> <h2 id="機械学習の取り組みを見える化">機械学習の取り組みを見える化</h2> <p>仕事で扱っているデータや機械学習タスクについてイメージしやすくするために、紹介資料を作ったりもしました。<br/> 実際に「この資料見て、カジュアル面談申し込みました!」など、いくつか反響も頂き嬉しかったです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftech.connehito.com%2Fentry%2Fml_data_info" title="コネヒトにおける機械学習関連業務の紹介資料を公開します - コネヒト開発者ブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://tech.connehito.com/entry/ml_data_info">tech.connehito.com</a></cite></p> <h2 id="新しい役割へのチャレンジ">新しい役割へのチャレンジ</h2> <p>冒頭でも触れましたが、直近2〜3ヶ月は、プロダクトマネジメントやピープルマネジメントといった役割にチャレンジしています。<br/> とはいえ、どちらもまだまだヒヨっ子なので、チームメンバーや先人の知恵をお借りしながら、トライ&amp;エラーの精神で取り組んでいたりします。</p> <p>書籍で言うと、有名なものもありますがこの辺を読みました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Famzn.to%2F3jf7pO8" title="良い戦略、悪い戦略 | リチャード P.ルメルト, 村井 章子 |本 | 通販 | Amazon" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://amzn.to/3jf7pO8">amzn.to</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Famzn.to%2F3uWyb0y" title="チームトポロジー 価値あるソフトウェアをすばやく届ける適応型組織設計 | マシュー・スケルトン, マニュエル・パイス, 原田 騎郎, 永瀬 美穂, 吉羽 龍太郎 |本 | 通販 | Amazon" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://amzn.to/3uWyb0y">amzn.to</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Famzn.to%2F3C7S4Wz" title="プロダクトマネジメントのすべて 事業戦略・IT開発・UXデザイン・マーケティングからチーム・組織運営まで | 及川 卓也, 曽根原 春樹, 小城 久美子 |本 | 通販 | Amazon" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://amzn.to/3C7S4Wz">amzn.to</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Famzn.to%2F3FZcvae" title="ハーバード・ビジネス・レビュー リーダーシップ論文ベスト10 リーダーシップの教科書 (Harvard Business Review Press) | ハーバード・ビジネス・レビュー編集部, DIAMONDハーバード・ビジネス・レビュー編集部 |本 | 通販 | Amazon" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://amzn.to/3FZcvae">amzn.to</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Famzn.to%2F3G3pzf0" title="Scaling Teams 開発チーム 組織と人の成長戦略 (Compass Booksシリーズ) | David Loftesness, Alexander Grosse | 人事・労務管理 | Kindleストア | Amazon" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://amzn.to/3G3pzf0">amzn.to</a></cite></p> <h2 id="登壇">登壇</h2> <p>振り返ると、2つのイベントで登壇の機会をいただきました。</p> <h3 id="みんなのPython勉強会85">みんなのPython勉強会#85</h3> <p>ありがたいことに、運営の方にお声がけいただきお話をしました! オンライン登壇でしたが、運営の方のおかげでストレスなく話すことができました。 参加者の方々含めてありがとうございました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fstartpython.connpass.com%2Fevent%2F248068%2F" title="みんなのPython勉強会#85 (2022/09/22 19:00〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://startpython.connpass.com/event/248068/">startpython.connpass.com</a></cite></p> <p>資料はこちらです</p> <p><iframe id="talk_frame_926576" class="speakerdeck-iframe" src="//speakerdeck.com/player/9a35341fef574629b13b8778a0bed1de" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/takapy/zi-ran-yan-yu-ke-shi-hua-raiburari-nlplot-nogoshao-jie">speakerdeck.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlplot" title="GitHub - takapy0210/nlplot: Visualization Module for Natural Language Processing" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlplot">github.com</a></cite></p> <h3 id="PyConJP-2022">PyConJP 2022</h3> <p>昨年に引き続き機会に恵まれたので、PyConJPでもお話ししてきました。</p> <p>僕自身、数年ぶりのオフラインイベントということもあり、お久しぶりの方はもちろん、初めましての方やいろんな企業の方と対面でコミュニケーションを取ることができて、とても良い体験でした。(この時のご縁で、新しいイベントをやる機運に恵まれたりしました・・・詳しくは年明けに発表できると思います!)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2F2022.pycon.jp%2F" title="PyCon JP 2022" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://2022.pycon.jp/">2022.pycon.jp</a></cite></p> <p><iframe id="talk_frame_936841" class="speakerdeck-iframe" src="//speakerdeck.com/player/a0bec04830bc457ca1561182fefc827b" width="710" height="399" style="aspect-ratio:710/399; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/takapy/nlpwohuo-yong-sitaonbodeingugai-shan-tokorudosutatowen-ti-henodui-ce">speakerdeck.com</a></cite></p> <p>こういうの、オフラインイベントみがありますよね〜</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">2等当たった<a href="https://twitter.com/hashtag/pycon_findy?src=hash&amp;ref_src=twsrc%5Etfw">#pycon_findy</a> <a href="https://t.co/cYWRUp7y2O">pic.twitter.com/cYWRUp7y2O</a></p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1580758352535392256?ref_src=twsrc%5Etfw">2022年10月14日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h1 id="プライベート">プライベート</h1> <p>最後に少しプライベートなことも</p> <h2 id="Podcastのエピソード数が100を超えた">Podcastのエピソード数が100を超えた</h2> <p><a href="https://twitter.com/yaginuuun">友人</a>と 2020年5月から軽い気持ちで始めたPodcastも、気づけば2年継続し、合計のエピソード数も100を超えました。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">\ <a href="https://twitter.com/hashtag/podcast?src=hash&amp;ref_src=twsrc%5Etfw">#podcast</a> 更新しました🎙 /<br>エピソード:#100 100回記念!過去エピソードを振り返りながらこれからの話とか<br><br>お便りはこちら↓からお待ちしております!<br>📮 <a href="https://t.co/N2VsZ8XdWS">https://t.co/N2VsZ8XdWS</a><br>🔖 <a href="https://twitter.com/hashtag/wipfm?src=hash&amp;ref_src=twsrc%5Etfw">#wipfm</a><a href="https://t.co/zfVeeims03">https://t.co/zfVeeims03</a></p>&mdash; Work In Progress / wip.fm 🎙 (@wipfm0509) <a href="https://twitter.com/wipfm0509/status/1564019968420741120?ref_src=twsrc%5Etfw">2022年8月28日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>ゲスト出演も大募集中ですので、「ちょっと話してみたいな〜」と言う方がいましたら、TwitterのDMまでご連絡お待ちしております!</p> <h2 id="ポケモンユナイトというゲームにハマった">ポケモンユナイトというゲームにハマった</h2> <p><a href="https://www.pokemonunite.jp/ja/">ポケモンユナイト</a> というMOBAジャンルのゲームハマり、いくつか大会もでたりしました。</p> <p>とある大会では準優勝したりもできました。決勝戦の緊張や興奮は、高校時代の部活を思い出してとても楽しかったです・・・!</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">準優勝でした🥈<br>対戦ありがとうございました!GG!<a href="https://twitter.com/hashtag/%E3%83%9D%E3%82%B1%E3%83%A2%E3%83%B3%E3%83%A6%E3%83%8A%E3%82%A4%E3%83%88?src=hash&amp;ref_src=twsrc%5Etfw">#ポケモンユナイト</a> <a href="https://t.co/IvA7wDZZS8">pic.twitter.com/IvA7wDZZS8</a></p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1563840895329284096?ref_src=twsrc%5Etfw">2022年8月28日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h2 id="不動産を買った">不動産を買った</h2> <p>おそらく人生で一番大きな支出となるであろう、不動産を買いました。</p> <p>元々は賃貸派だったのですが、会社のマンション大好きな人の意見や、いんたーねっとのマンションクラスタの方々の意見を参考に、半住半投の気持ちで購入しました(とはいえ今の市況的に実需寄りではあります)</p> <p>この辺はいろいろ試行錯誤したので、気が向いたらブログにでも書こうと思います。</p> <h1 id="2023年は">2023年は?</h1> <p>お仕事では、いろいろなご縁から楽しみなことがいくつか決まっていたりするので、今から楽しみです(本当に周りの人に恵まれているなぁとつくづく思います)</p> <p>あとは、いい加減に痩せようと思います。</p> taxa_program Luigiを使って機械学習パイプラインを構築する3つのメリット hatenablog://entry/13574176438032826417 2022-03-22T17:04:13+09:00 2022-03-22T17:04:13+09:00 みなさんこんにちは。たかぱい(@takapy0210)です。 本エントリでは、(今更ながら)Luigiを使ってみて感じたメリットをつらつら書いています。 最後にはTitanicのコードを使って実際の機械学習パイプラインを構築してみた例も載せているので、よければ参考にしてみてください。 Luigiとは Luigiを使うメリット コードのメンテナンス性向上 再現性の向上 実験回数の向上 使用する上でのTips requiresはデコレータを用いる 任意のフォーマットでログ出力する ブラウザからパイプラインの実行結果の可視化をしてみる 実際のコンペデータをLuigiフレームワークに載せてみる 最後に… <p>みなさんこんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリでは、(今更ながら)Luigiを使ってみて感じたメリットをつらつら書いています。<br /> 最後にはTitanicのコードを使って実際の機械学習パイプラインを構築してみた例も載せているので、よければ参考にしてみてください。</p> <ul class="table-of-contents"> <li><a href="#Luigiとは">Luigiとは</a></li> <li><a href="#Luigiを使うメリット">Luigiを使うメリット</a><ul> <li><a href="#コードのメンテナンス性向上">コードのメンテナンス性向上</a></li> <li><a href="#再現性の向上">再現性の向上</a></li> <li><a href="#実験回数の向上">実験回数の向上</a></li> </ul> </li> <li><a href="#使用する上でのTips">使用する上でのTips</a><ul> <li><a href="#requiresはデコレータを用いる">requiresはデコレータを用いる</a></li> <li><a href="#任意のフォーマットでログ出力する">任意のフォーマットでログ出力する</a></li> </ul> </li> <li><a href="#ブラウザからパイプラインの実行結果の可視化をしてみる">ブラウザからパイプラインの実行結果の可視化をしてみる</a></li> <li><a href="#実際のコンペデータをLuigiフレームワークに載せてみる">実際のコンペデータをLuigiフレームワークに載せてみる</a></li> <li><a href="#最後に">最後に</a></li> <li><a href="#参考資料">参考資料</a></li> </ul> <p>本ブログで記載しているコードはGithubにもあげています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fgeek_blog%2Fblob%2Fmaster%2Fluigi%2Fsample_luigi.py" title="geek_blog/sample_luigi.py at master · takapy0210/geek_blog" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/geek_blog/blob/master/luigi/sample_luigi.py">github.com</a></cite></p> <h1 id="Luigiとは">Luigiとは</h1> <p>Luigiとは、SpotifyがOSSとして開発しているpythonのバッチ処理フレームワークです。<br /> データ処理をTaskという単位で定義していき、依存関係の記述やワークフローの可視化などを行うことができます。</p> <p>以下、READMEから転記</p> <blockquote><p>Luigi is a Python (3.6, 3.7, 3.8, 3.9 tested) package that helps you build complex pipelines of batch jobs. It handles dependency resolution, workflow management, visualization, handling failures, command line integration, and much more.</p></blockquote> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fspotify%2Fluigi" title="GitHub - spotify/luigi: Luigi is a Python module that helps you build complex pipelines of batch jobs. It handles dependency resolution, workflow management, visualization etc. It also comes with Hadoop support built in." class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/spotify/luigi">github.com</a></cite></p> <p>基本的に各Taskには以下3つのメソッドを実装する必要があります。</p> <ul> <li>requires:自身の処理に依存する<strong>上位Task</strong>を記述</li> <li>output:自身の出力ファイルを記述</li> <li>run:自身の処理内容を記述</li> </ul> <p>処理のイメージとしては</p> <ul> <li>outputメソッドがTrueを返す(そのTaskが既に実行済み)場合は、runメソッドを実行しない。</li> <li>outputメソッドがFalseを返す(そのTaskが実行されていない)場合は、runメソッドを実行して、出力を作成する。<br /> →<strong>この機構により、既に実行済みのTaskを再実行しなくて済むので、実験回数を向上させることができる</strong></li> <li>requiresメソッドで依存TaskのoutputがFalseを返す(依存Taskが実行されていない)場合は、先に依存Taskのrunを実行する。</li> </ul> <p>という流れです。</p> <p>詳細は後述しますが、記述イメージはざっくり以下のようになります。<br /> (このタスクは依存する上位タスクが存在しないので、requiresは定義していません。)</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">class</span> <span class="synIdentifier">LoadDataset</span>(luigi.Task): <span class="synConstant">&quot;&quot;&quot;データセットをロードするクラス&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">output</span>(self): <span class="synComment"># return luigi.LocalTarget(&quot;data/titanic.csv&quot;) # csvで出力する場合</span> <span class="synStatement">return</span> luigi.LocalTarget(<span class="synConstant">&quot;data/titanic.pkl&quot;</span>, <span class="synIdentifier">format</span>=luigi.format.Nop) <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># titanicデータの読み込み</span> df = datasets.fetch_openml(<span class="synConstant">&quot;titanic&quot;</span>, version=<span class="synConstant">1</span>, as_frame=<span class="synIdentifier">True</span>, return_X_y=<span class="synIdentifier">False</span>).frame logger.info(f<span class="synConstant">'Data shape: {df.shape}'</span>) <span class="synComment"># pklで出力する</span> <span class="synStatement">with</span> self.output().open(<span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(pickle.dumps(df, protocol=pickle.HIGHEST_PROTOCOL)) </pre> <p>ドキュメントはこちらです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fluigi.readthedocs.io%2Fen%2Fstable%2Findex.html" title="Getting Started — Luigi 2.8.13 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://luigi.readthedocs.io/en/stable/index.html">luigi.readthedocs.io</a></cite></p> <h1 id="Luigiを使うメリット">Luigiを使うメリット</h1> <p>実際に使ってみて3つほどメリットを感じました。</p> <ul> <li>コードのメンテナンス性向上</li> <li>再現性の向上</li> <li>実験回数の向上</li> </ul> <h2 id="コードのメンテナンス性向上">コードのメンテナンス性向上</h2> <p>フレームワークに則ることでコーディングがある程度強制されるので、誰が書いても同じようなコードになります。</p> <p>これはコードの保守性の観点から考えても非常に良いことだと思いました。</p> <h2 id="再現性の向上">再現性の向上</h2> <p>notebookなどで処理を書いていると、セルの実行順によって処理結果が異なって発狂したことがある人も多いと思います。</p> <p>この辺りはスクリプト化することである程度回避できる + Luigiフレームワークに則ることでより一層堅牢になると感じました。</p> <h2 id="実験回数の向上">実験回数の向上</h2> <p>言葉で説明してもわかりづらいと思うので、図を書いてみました。</p> <p>下図は、TaskA(データの前処理)→TaskB(データの前処理)→TaskC(モデルの学習処理)とうパイプラインを表したものです。<br /> この場合<code>processing_A.pkl</code>というデータが存在しているので(TaskAは過去に実行済みと判断され)TaskAの実行はスキップされ、TaskB→TaskCという順に処理されます。</p> <p>このようにoutput()に定義したオブジェクトの存在有無により、自動的にTaskを実行すべきかを判断してくれます。<br /> 例えば、TaskAの処理が数十分〜数時間かかるような場合、パイプラインを実行するたびにTaskAから実行されるのは本意ではないと思います。(もちろん、TaskAの処理を変更した場合は再実行してほしいと思うので、その場合は該当のオブジェクトを削除して再実行する必要があります)</p> <p>そういった場合において、不要なTaskは自動的にスキップしてくれるので、いろいろ実験する際には役立ちそうです。</p> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20211125/20211125002232.png" alt="f:id:taxa_program:20211125002232p:plain" width="1200" height="838" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h1 id="使用する上でのTips">使用する上でのTips</h1> <p>実際に使用する際に使えそうなTipsをいくつか紹介します</p> <h2 id="requiresはデコレータを用いる">requiresはデコレータを用いる</h2> <p>冒頭で紹介した<code>requires()</code>メソッドですが以下で紹介するようにデコレートすることができます。</p> <p>例えば以下のような処理を</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> luigi <span class="synStatement">class</span> <span class="synIdentifier">TaskA</span>(luigi.Task): <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): hoge... <span class="synStatement">class</span> <span class="synIdentifier">TaskB</span>(luigi.Task): <span class="synStatement">def</span> <span class="synIdentifier">requires</span>(self): <span class="synStatement">return</span> TaskA() <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): hoge... </pre> <p>こんな感じで記述できます。ちょっとスッキリしますね!</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> luigi <span class="synPreProc">from</span> luigi.util <span class="synPreProc">import</span> requires <span class="synStatement">class</span> <span class="synIdentifier">TaskA</span>(luigi.Task): <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): hoge... <span class="synPreProc">@</span><span class="synIdentifier">requires</span>(TaskA) <span class="synStatement">class</span> <span class="synIdentifier">TaskB</span>(luigi.Task): <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): hoge... </pre> <h2 id="任意のフォーマットでログ出力する">任意のフォーマットでログ出力する</h2> <p><a href="https://github.com/takapy0210/geek_blog/tree/master/luigi">冒頭で紹介したリポジトリ</a>にあるように、実行時に <code>luigi.configuration.LuigiConfigParser.add_config_path('./luigi.cfg')</code> という形で設定ファイルを読み込んでいます。</p> <p>この中にloggerのconfigファイルを指定することで、任意のフォーマットでログ出力ができます。</p> <p>サンプルとして、リポジトリに上がっている例を載せておきます。</p> <p>luigi.cfg</p> <pre class="code" data-lang="" data-unlink>[core] # 不要なログを出力しないための設定 log_level=INFO logging_conf_file=logging.conf [retcode] already_running=10 missing_data=20 not_run=25 task_failed=30 scheduling_error=35 unhandled_exception=40</pre> <p>エラー通知の設定はドキュメントにもサンプルが載っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fluigi.readthedocs.io%2Fen%2Fstable%2Fconfiguration.html%3Fhighlight%3Dalready_running%23retcode" title="Configuration — Luigi 2.8.13 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://luigi.readthedocs.io/en/stable/configuration.html?highlight=already_running#retcode">luigi.readthedocs.io</a></cite></p> <p>logging.conf</p> <pre class="code" data-lang="" data-unlink>[loggers] keys=root [handlers] keys=streamHandler [logger_root] level=INFO handlers=streamHandler [formatters] keys=simpleFormatter [handler_streamHandler] class=logging.StreamHandler level=INFO formatter=simpleFormatter [formatter_simpleFormatter] format=[%(asctime)s] [%(levelname)5s] %(message)s datefmt=%Y-%m-%d %H:%M:%S</pre> <h1 id="ブラウザからパイプラインの実行結果の可視化をしてみる">ブラウザからパイプラインの実行結果の可視化をしてみる</h1> <p>実行時のコマンドで、local_schedulerを使わないようにすると、ブラウザから<code>http://localhost:8082/</code>にアクセスすることで、パイプラインの実行結果を可視化することができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: <span class="synComment"># 設定ファイルの読み込み</span> luigi.configuration.LuigiConfigParser.add_config_path(<span class="synConstant">'./luigi.cfg'</span>) <span class="synComment"># 実行</span> <span class="synComment"># luigi.build([MyInvokerTask()], local_scheduler=True)</span> luigi.build([MyInvokerTask()], local_scheduler=<span class="synIdentifier">False</span>) <span class="synComment"># ブラウザからチェックしたい場合はこちら</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="可視化1"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20211115/20211115091452.png" alt="f:id:taxa_program:20211115091452p:plain" width="1200" height="546" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>可視化1</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="可視化2"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20211115/20211115091522.png" alt="f:id:taxa_program:20211115091522p:plain" width="1200" height="539" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>可視化2</figcaption></figure></p> <p>今回の処理だと一瞬で終了してしまうので、実行中の動作は見れませんが、興味がある人は少し重い処理を実行してみると、パイプラインのステータスが変わっていく様子が見れると思うので、試してみてはいかがでしょうか。</p> <h1 id="実際のコンペデータをLuigiフレームワークに載せてみる">実際のコンペデータをLuigiフレームワークに載せてみる</h1> <p>有名なTitanicデータを用いてデータの読み込み・前処理〜学習までのコードをLuigiのフレームワークに則って記述してみます。</p> <p>コメントしているように、csvでの入出力もできたり、複数ファイルの入出力にも対応しています。(csvは型が崩れる可能性があるので非推奨ではありそうです)</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> warnings <span class="synPreProc">import</span> logging <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> luigi <span class="synPreProc">from</span> luigi.util <span class="synPreProc">import</span> requires <span class="synPreProc">from</span> sklearn <span class="synPreProc">import</span> datasets <span class="synPreProc">from</span> sklearn.preprocessing <span class="synPreProc">import</span> OneHotEncoder <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">from</span> sklearn.ensemble <span class="synPreProc">import</span> RandomForestClassifier <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report, accuracy_score warnings.filterwarnings(<span class="synConstant">&quot;ignore&quot;</span>) logger = logging.getLogger() <span class="synStatement">class</span> <span class="synIdentifier">LoadDataset</span>(luigi.Task): <span class="synConstant">&quot;&quot;&quot;データセットをロードするクラス&quot;&quot;&quot;</span> task_namespace = <span class="synConstant">'titanic_tasks'</span> <span class="synStatement">def</span> <span class="synIdentifier">output</span>(self): <span class="synComment"># return luigi.LocalTarget(&quot;data/titanic.csv&quot;) # csvで出力する場合</span> <span class="synStatement">return</span> luigi.LocalTarget(<span class="synConstant">&quot;data/titanic.pkl&quot;</span>, <span class="synIdentifier">format</span>=luigi.format.Nop) <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># titanicデータの読み込み</span> df = datasets.fetch_openml(<span class="synConstant">&quot;titanic&quot;</span>, version=<span class="synConstant">1</span>, as_frame=<span class="synIdentifier">True</span>, return_X_y=<span class="synIdentifier">False</span>).frame logger.info(f<span class="synConstant">'Data shape: {df.shape}'</span>) <span class="synComment"># pklで出力する</span> <span class="synStatement">with</span> self.output().open(<span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(pickle.dumps(df, protocol=pickle.HIGHEST_PROTOCOL)) <span class="synComment"># csvで出力したい場合は普通にpandasで出力する</span> <span class="synComment"># 型が崩れる可能性があるので非推奨ではある</span> <span class="synComment"># df.to_csv(&quot;data/titanic.csv&quot;, index=False)</span> <span class="synPreProc">@</span><span class="synIdentifier">requires</span>(LoadDataset) <span class="synStatement">class</span> <span class="synIdentifier">Processing</span>(luigi.Task): <span class="synConstant">&quot;&quot;&quot;データの加工を行う&quot;&quot;&quot;</span> task_namespace = <span class="synConstant">'titanic_tasks'</span> <span class="synStatement">def</span> <span class="synIdentifier">output</span>(self): <span class="synComment"># return luigi.LocalTarget(&quot;data/processing_titanic.csv&quot;) # csvで出力する場合</span> <span class="synStatement">return</span> luigi.LocalTarget(<span class="synConstant">&quot;data/processing_titanic.pkl&quot;</span>, <span class="synIdentifier">format</span>=luigi.format.Nop) <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># データの読み込み</span> <span class="synStatement">with</span> self.input().open() <span class="synStatement">as</span> f: <span class="synComment"># df = pd.read_csv(f) # pandasで読み込むパターン</span> df = pickle.load(f) <span class="synComment"># pickleで読み込むパターン</span> logger.info(f<span class="synConstant">'Before Data shape: {df.shape}'</span>) <span class="synComment"># 欠損値処理</span> df.loc[:, <span class="synConstant">'age'</span>] = df[<span class="synConstant">'age'</span>].fillna(df[<span class="synConstant">'age'</span>].mean()) df.loc[:, <span class="synConstant">'fare'</span>] = df[<span class="synConstant">'fare'</span>].fillna(df[<span class="synConstant">'fare'</span>].mean()) <span class="synComment"># カテゴリエンコード</span> categorical_cols = [<span class="synConstant">&quot;pclass&quot;</span>, <span class="synConstant">&quot;sex&quot;</span>, <span class="synConstant">&quot;embarked&quot;</span>] df = self.sklearn_oh_encoder(df=df, cols=categorical_cols, drop_col=<span class="synIdentifier">True</span>) logger.info(f<span class="synConstant">'After Data shape: {df.shape}'</span>) <span class="synComment"># 学習に使用するカラムのみを出力</span> use_cols = [ <span class="synConstant">'survived'</span>, <span class="synConstant">'age'</span>, <span class="synConstant">'sibsp'</span>, <span class="synConstant">'parch'</span>, <span class="synConstant">'fare'</span>, <span class="synConstant">'pclass_1.0'</span>, <span class="synConstant">'pclass_2.0'</span>, <span class="synConstant">'pclass_3.0'</span>, <span class="synConstant">'sex_female'</span>, <span class="synConstant">'sex_male'</span>, <span class="synConstant">'embarked_C'</span>, <span class="synConstant">'embarked_Q'</span>, <span class="synConstant">'embarked_S'</span>, <span class="synConstant">'embarked_nan'</span> ] df = df[use_cols] <span class="synComment"># 保存</span> <span class="synStatement">with</span> self.output().open(<span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(pickle.dumps(df, protocol=pickle.HIGHEST_PROTOCOL)) <span class="synStatement">def</span> <span class="synIdentifier">sklearn_oh_encoder</span>(self, df, cols, drop_col=<span class="synIdentifier">False</span>): <span class="synConstant">&quot;&quot;&quot;カテゴリ変換</span> <span class="synConstant"> sklearnのOneHotEncoderでEncodingを行う</span> <span class="synConstant"> Args:</span> <span class="synConstant"> df: カテゴリ変換する対象のデータフレーム</span> <span class="synConstant"> cols (list of str): カテゴリ変換する対象のカラムリスト</span> <span class="synConstant"> drop_col (bool): エンコード対象のカラムを削除するか否か</span> <span class="synConstant"> Returns:</span> <span class="synConstant"> pd.Dataframe: dfにカテゴリ変換したカラムを追加したデータフレーム</span> <span class="synConstant"> &quot;&quot;&quot;</span> output_df = df.copy() <span class="synStatement">for</span> col <span class="synStatement">in</span> cols: ohe = OneHotEncoder(sparse=<span class="synIdentifier">False</span>, handle_unknown=<span class="synConstant">'ignore'</span>) ohe_df = pd.DataFrame((ohe.fit_transform(output_df[[col]])), columns=ohe.categories_[<span class="synConstant">0</span>]) ohe_df = ohe_df.add_prefix(f<span class="synConstant">'{col}_'</span>) <span class="synComment"># 元のDFに結合</span> output_df = pd.concat([output_df, ohe_df], axis=<span class="synConstant">1</span>) <span class="synStatement">if</span> drop_col: output_df = output_df.drop(col, axis=<span class="synConstant">1</span>) <span class="synStatement">return</span> output_df <span class="synPreProc">@</span><span class="synIdentifier">requires</span>(Processing) <span class="synStatement">class</span> <span class="synIdentifier">TrainTestSplit</span>(luigi.Task): <span class="synConstant">&quot;&quot;&quot;データを学習データと検証データに分割する&quot;&quot;&quot;</span> task_namespace = <span class="synConstant">'titanic_tasks'</span> <span class="synStatement">def</span> <span class="synIdentifier">output</span>(self): <span class="synStatement">return</span> [luigi.LocalTarget(<span class="synConstant">&quot;data/processing_titanic_train.pkl&quot;</span>, <span class="synIdentifier">format</span>=luigi.format.Nop), luigi.LocalTarget(<span class="synConstant">&quot;data/processing_titanic_test.pkl&quot;</span>, <span class="synIdentifier">format</span>=luigi.format.Nop)] <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># データの読み込み</span> <span class="synStatement">with</span> self.input().open() <span class="synStatement">as</span> f: df = pickle.load(f) <span class="synComment"># pickleで読み込むパターン</span> train, test = train_test_split(df, test_size=<span class="synConstant">0.3</span>, shuffle=<span class="synIdentifier">True</span>, stratify=df[<span class="synConstant">'survived'</span>], random_state=<span class="synConstant">42</span>) logger.info(f<span class="synConstant">'Train shape: {train.shape}'</span>) logger.info(f<span class="synConstant">'Test shape: {test.shape}'</span>) <span class="synStatement">with</span> self.output()[<span class="synConstant">0</span>].open(<span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(pickle.dumps(train, protocol=pickle.HIGHEST_PROTOCOL)) <span class="synStatement">with</span> self.output()[<span class="synConstant">1</span>].open(<span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(pickle.dumps(test, protocol=pickle.HIGHEST_PROTOCOL)) <span class="synPreProc">@</span><span class="synIdentifier">requires</span>(TrainTestSplit) <span class="synStatement">class</span> <span class="synIdentifier">Training</span>(luigi.Task): <span class="synConstant">&quot;&quot;&quot;学習&quot;&quot;&quot;</span> task_namespace = <span class="synConstant">'titanic_tasks'</span> <span class="synStatement">def</span> <span class="synIdentifier">output</span>(self): <span class="synStatement">return</span> luigi.LocalTarget(<span class="synConstant">&quot;model/random_forest.model&quot;</span>, <span class="synIdentifier">format</span>=luigi.format.Nop) <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># データの読み込み</span> <span class="synStatement">with</span> self.input()[<span class="synConstant">0</span>].open() <span class="synStatement">as</span> f: train = pickle.load(f) logger.info(f<span class="synConstant">'Train shape: {train.shape}'</span>) target_col = <span class="synConstant">'survived'</span> X_train = train.drop(target_col, axis=<span class="synConstant">1</span>) y_train = train[target_col] model = RandomForestClassifier(random_state=<span class="synConstant">1</span>) model.fit(X_train, y_train) <span class="synComment"># 保存</span> <span class="synStatement">with</span> self.output().open(<span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(pickle.dumps(model, protocol=pickle.HIGHEST_PROTOCOL)) <span class="synPreProc">@</span><span class="synIdentifier">requires</span>(TrainTestSplit, Training) <span class="synStatement">class</span> <span class="synIdentifier">Predict</span>(luigi.Task): <span class="synConstant">&quot;&quot;&quot;推論&quot;&quot;&quot;</span> task_namespace = <span class="synConstant">'titanic_tasks'</span> <span class="synStatement">def</span> <span class="synIdentifier">output</span>(self): <span class="synStatement">return</span> luigi.LocalTarget(<span class="synConstant">&quot;data/predict_data.csv&quot;</span>) <span class="synStatement">def</span> <span class="synIdentifier">run</span>(self): <span class="synComment"># データの読み込み</span> <span class="synStatement">with</span> self.input()[<span class="synConstant">0</span>][<span class="synConstant">1</span>].open() <span class="synStatement">as</span> f: valid = pickle.load(f) <span class="synComment"># モデルの読み込み</span> <span class="synStatement">with</span> self.input()[<span class="synConstant">1</span>].open() <span class="synStatement">as</span> f: model = pickle.load(f) logger.info(f<span class="synConstant">'Valid data shape: {valid.shape}'</span>) target_col = <span class="synConstant">'survived'</span> X_valid = valid.drop(target_col, axis=<span class="synConstant">1</span>) y_valid = valid[target_col] <span class="synComment"># 予測</span> y_pred = model.predict(X_valid) logger.info(f<span class="synConstant">'Accuracy Score: {accuracy_score(y_valid, y_pred)}'</span>) logger.info(<span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span> + classification_report(y_valid, y_pred)) <span class="synComment"># # 保存</span> valid.loc[:, <span class="synConstant">'y_pred'</span>] = y_pred valid.to_csv(<span class="synConstant">'data/predict_data.csv'</span>, index=<span class="synIdentifier">False</span>) <span class="synPreProc">@</span><span class="synIdentifier">requires</span>(Predict) <span class="synStatement">class</span> <span class="synIdentifier">MyInvokerTask</span>(luigi.WrapperTask): task_namespace = <span class="synConstant">'titanic_tasks'</span> <span class="synStatement">pass</span> <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: <span class="synComment"># 設定ファイルの読み込み</span> luigi.configuration.LuigiConfigParser.add_config_path(<span class="synConstant">'./luigi.cfg'</span>) <span class="synComment"># 実行</span> luigi.build([MyInvokerTask()], local_scheduler=<span class="synIdentifier">True</span>) <span class="synComment"># luigi.build([MyInvokerTask()], local_scheduler=False) # ブラウザからチェックしたい場合はこちら</span> </pre> <h1 id="最後に">最後に</h1> <p>Luigiフレームワークに則ることで、いろいろなメリットを享受することができそうでした。</p> <p>個人的には実験回数の向上が一番のメリットかなぁとも感じたので、コンペや実務で積極的に使っていこうと思いました。</p> <h1 id="参考資料">参考資料</h1> <ul> <li><a href="https://www.nogawanogawa.com/entry/luigi_intro">luigiを使ってみた - Re:ゼロから始めるML生活</a></li> <li><a href="https://qiita.com/hagino3000/items/b9a7761dad1f352ec723">Luigi逆引きリファレンス - Qiita</a></li> <li><a href="https://techblog.locoguide.co.jp/entry/2019/10/04/181808">ヘルパを使ってLuigiワークフローの依存関係をスッキリ書こう! - ロコガイド テックブログ</a></li> <li><a href="https://cpp-learning.com/luigi/#Luigi">【Luigi】Pipelineライブラリによるデータ処理ワークフローの開発</a></li> <li><a href="https://qiita.com/keisuke-nakata/items/0717c0c358658964f81e">PythonとLuigiによるデータパイプライン構築 - Qiita</a></li> </ul> taxa_program M1 Macのdocker環境にテクニカル指標計算ライブラリ「TA-Lib」をインストールする方法 hatenablog://entry/13574176438044214567 2021-12-19T16:34:41+09:00 2021-12-19T16:34:41+09:00 みなさんこんにちは。たかぱい(@takapy0210)です 本日はM1 Macの分析コンテナ環境に株価分析ライブラリのTA-Lib*1をインストールする際に結構苦労したので、その備忘です。 Dockerfileの内容とエラー内容 こちらのサイト*2などを参考に以下のようなDockerfileを記述してインストールしてみましたが、M1 Mac環境だとエラーが発生します。(Intel版 Macでは問題なくインストールできました) Dockerfile ... RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.ta… <p>みなさんこんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です</p> <p>本日はM1 Macの分析コンテナ環境に株価分析ライブラリのTA-Lib<a href="#f-871e2879" name="fn-871e2879" title="https://ta-lib.org/">*1</a>をインストールする際に結構苦労したので、その備忘です。</p> <h1>Dockerfileの内容とエラー内容</h1> <p>こちらのサイト<a href="#f-55e2db5c" name="fn-55e2db5c" title="https://qiita.com/kainogen/items/4b587a8dad830dea03fb">*2</a>などを参考に以下のようなDockerfileを記述してインストールしてみましたが、M1 Mac環境だとエラーが発生します。(Intel版 Macでは問題なくインストールできました)</p> <h2>Dockerfile</h2> <pre class="code" data-lang="" data-unlink>... RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz RUN tar -zxvf ta-lib-0.4.0-src.tar.gz &amp;&amp; \ cd ta-lib &amp;&amp; \ ./configure --prefix=/usr &amp;&amp; \ make &amp;&amp; \ sudo make install RUN python3 -m pip install TA-Lib ... </pre> <h2>エラー内容</h2> <pre class="code" data-lang="" data-unlink>Could not build wheels for Ta-Lib, which is required to install pyproject.toml-based projects</pre> <h1>回避方法</h1> <p>Dockerfileの記述方法を以下のように変更します</p> <pre class="code" data-lang="" data-unlink>... RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz RUN tar -zxvf ta-lib-0.4.0-src.tar.gz &amp;&amp; \ cd ta-lib &amp;&amp; \ cp /usr/share/automake-1.16/config.guess . &amp;&amp; \ ./configure --prefix=/usr &amp;&amp; \ make &amp;&amp; \ sudo make install RUN python3 -m pip install TA-Lib ... </pre> <p>これでimportすれば使用できるようになると思います。</p> <h1>おまけ:「numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 from PyObject」エラーが発生する場合</h1> <p>この場合はnumpyのバージョンを上げると回避できる可能性があります。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>pip3 install <span class="synIdentifier">numpy</span>=<span class="synStatement">=</span><span class="synConstant">1</span>.<span class="synConstant">20</span>.<span class="synConstant">0</span> </pre> <h1>参考</h1> <p>※以下を参考に試行錯誤しましたが、結局この中に解決できる方法はありませんでした</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ffreqtrade%2Ffreqtrade%2Fissues%2F4712" title="Ta-Lib dependencies broken on Apple M1: Symbol not found: _TA_ACOS · Issue #4712 · freqtrade/freqtrade" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/freqtrade/freqtrade/issues/4712">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=http%3A%2F%2Fmrjbq7.github.io%2Fta-lib%2Finstall.html" title="TA-Lib" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="http://mrjbq7.github.io/ta-lib/install.html">mrjbq7.github.io</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-871e2879" name="f-871e2879" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ta-lib.org/">https://ta-lib.org/</a></span></p> <p class="footnote"><a href="#fn-55e2db5c" name="f-55e2db5c" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://qiita.com/kainogen/items/4b587a8dad830dea03fb">https://qiita.com/kainogen/items/4b587a8dad830dea03fb</a></span></p> </div> taxa_program TensorFlow × HuggingFace Transformers(TFBertModel)を用いたモデルの保存時に発生するエラーの回避方法 hatenablog://entry/13574176438040164702 2021-12-11T14:57:51+09:00 2021-12-11T14:57:51+09:00 みなさんこんにちは。たかぱい(@takapy0210)です。 本日はTensorFlow×Transformers周りでエラーに遭遇した内容とそのWAです。 環境 実装内容 エラー内容 エラーの原因 ワークアラウンド なんでこれで解消できるのか? モデルの保存方法 参考 環境 実行環境は以下の通りです python 3.7.10 transformers 4.12.5 tensorflow 2.3.0 実装内容 一部抜粋ですが、TransformersのTFBertModel*1に、独自のレイヤーをいくつか追加した2値分類モデルの学習を行いました。 import tensorflow as t… <p>みなさんこんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本日はTensorFlow×Transformers周りでエラーに遭遇した内容とそのWAです。</p> <ul class="table-of-contents"> <li><a href="#環境">環境</a></li> <li><a href="#実装内容">実装内容</a></li> <li><a href="#エラー内容">エラー内容</a><ul> <li><a href="#エラーの原因">エラーの原因</a></li> </ul> </li> <li><a href="#ワークアラウンド">ワークアラウンド</a><ul> <li><a href="#なんでこれで解消できるのか">なんでこれで解消できるのか?</a></li> <li><a href="#モデルの保存方法">モデルの保存方法</a></li> </ul> </li> <li><a href="#参考">参考</a></li> </ul> <h1 id="環境">環境</h1> <p>実行環境は以下の通りです</p> <blockquote><p>python 3.7.10<br /> transformers 4.12.5<br /> tensorflow 2.3.0</p></blockquote> <h1 id="実装内容">実装内容</h1> <p>一部抜粋ですが、TransformersのTFBertModel<a href="#f-b84f7334" name="fn-b84f7334" title="https://huggingface.co/docs/transformers/model_doc/bert#transformers.TFBertModel">*1</a>に、独自のレイヤーをいくつか追加した2値分類モデルの学習を行いました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synPreProc">from</span> tensorflow <span class="synPreProc">import</span> keras <span class="synPreProc">from</span> transformers <span class="synPreProc">import</span> TFBertModel <span class="synStatement">def</span> <span class="synIdentifier">build_model</span>(model_name, num_classes, max_length): <span class="synComment"># BERTへ入力する情報</span> input_shape = (max_length, ) input_ids = tf.keras.layers.Input(input_shape, dtype=tf.int32) attention_mask = tf.keras.layers.Input(input_shape, dtype=tf.int32) token_type_ids = tf.keras.layers.Input(input_shape, dtype=tf.int32) inputs = [input_ids, attention_mask, token_type_ids] <span class="synComment"># BERTモデル</span> bert_model = TFBertModel.from_pretrained(model_name) <span class="synComment"># 2種類の出力がある。今回使用するのはTFBertForSequenceClassificationにならってpooler_outputを使用</span> base_output = bert_model(inputs) sequence_output, pooler_output = base_output[<span class="synConstant">0</span>], base_output[<span class="synConstant">1</span>] output = tf.keras.layers.Dense(num_classes, activation=<span class="synConstant">&quot;sigmoid&quot;</span>)(pooler_output) model = tf.keras.Model(inputs=inputs, outputs=output) optimizer = tf.keras.optimizers.Adam(learning_rate=<span class="synConstant">3e-5</span>, epsilon=<span class="synConstant">1e-08</span>, clipnorm=<span class="synConstant">1.0</span>) model.compile(optimizer=optimizer, loss=<span class="synConstant">&quot;binary_crossentropy&quot;</span>, metrics=[<span class="synConstant">&quot;acc&quot;</span>]) <span class="synStatement">return</span> model <span class="synComment"># データの前処理</span> <span class="synComment"># (省略)</span> ... <span class="synComment"># 学習</span> model.fit( X_train, y_train, validation_data=(X_valid, y_valid), batch_size=batch_size, epochs=epochs, callbacks=callbacks ) model.save(<span class="synConstant">'bert_model.h5'</span>) </pre> <h1 id="エラー内容">エラー内容</h1> <p><code>model.save('bert_model.h5')</code> の部分で以下のようなエラーが発生します。</p> <pre class="code" data-lang="" data-unlink>(省略) /usr/local/lib/python3.7/site-packages/transformers/modeling_tf_utils.py in input_processing(func, config, input_ids, **kwargs) 408 output[tensor_name] = input 409 else: --&gt; 410 output[parameter_names[i]] = input 411 elif isinstance(input, allowed_types) or input is None: 412 output[parameter_names[i]] = input IndexError: list index out of range</pre> <h2 id="エラーの原因">エラーの原因</h2> <p>コードを見てみると、inputsとして配列が想定されていないからかな〜と思います(多分)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fhuggingface%2Ftransformers%2Fblob%2Fmaster%2Fsrc%2Ftransformers%2Fmodeling_tf_utils.py%23L341" title="transformers/modeling_tf_utils.py at master · huggingface/transformers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/huggingface/transformers/blob/master/src/transformers/modeling_tf_utils.py#L341">github.com</a></cite></p> <h1 id="ワークアラウンド">ワークアラウンド</h1> <p>BERTモデルを読み込む部分の処理を以下のように修正すればOKです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># エラーになるコード</span> base_output = bert_model(inputs) <span class="synComment"># WA</span> base_output = bert_model.bert(inputs) </pre> <h2 id="なんでこれで解消できるのか">なんでこれで解消できるのか?</h2> <p>実装されているコード<a href="#f-53660974" name="fn-53660974" title="https://github.com/huggingface/transformers/blob/32e94cff64ea87cb2df2699bca960962fe676b62/src/transformers/modeling_tf_bert.py#L699">*2</a>をみると、bert layerというのは TFBertMainLayer<a href="#f-77fae012" name="fn-77fae012" title="https://github.com/huggingface/transformers/blob/32e94cff64ea87cb2df2699bca960962fe676b62/src/transformers/modeling_tf_bert.py#L494">*3</a> というクラスです。<br /> これを直接呼ぶことで、Save時も <code>TFBertMainLayer.call</code> が呼び出され、<code>LayerCall.__call__(args, **kwargs)</code> が呼び出されることを回避することができるので、保存処理がうまくいくのだと思います。</p> <h2 id="モデルの保存方法">モデルの保存方法</h2> <p>保存するときはcallback関数に設定しても、<code>model.save()</code> メソッドを用いても、どちらでも問題なく保存できました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># callback関数を使う場合</span> checkpoint = tf.keras.callbacks.ModelCheckpoint( <span class="synConstant">'bert_model.h5'</span>, monitor=<span class="synConstant">'val_loss'</span>, verbose=<span class="synConstant">2</span>, save_best_only=<span class="synIdentifier">True</span>, mode=<span class="synConstant">'auto'</span> ) result = model.fit( X_train, y_train, validation_data=(X_valid, y_valid), batch_size=batch_size, epochs=epochs, callbacks=[checkpoint] ) <span class="synComment"># saveメソッドで保存する場合</span> model.save(<span class="synConstant">'bert_model.h5'</span>) </pre> <p>保存したモデルを読み込む際は普通に <code>load_model()</code> を使えば読み込めます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> tensorflow.keras.models <span class="synPreProc">import</span> load_model model = load_model(<span class="synConstant">'bert_model.h5'</span>) pred = model.predict(test, verbose=<span class="synConstant">1</span>) </pre> <h1 id="参考">参考</h1> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fhuggingface%2Ftransformers%2Fissues%2F3627" title="Failing to load saved TFBertModel · Issue #3627 · huggingface/transformers" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/huggingface/transformers/issues/3627">github.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-b84f7334" name="f-b84f7334" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://huggingface.co/docs/transformers/model_doc/bert#transformers.TFBertModel">https://huggingface.co/docs/transformers/model_doc/bert#transformers.TFBertModel</a></span></p> <p class="footnote"><a href="#fn-53660974" name="f-53660974" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/huggingface/transformers/blob/32e94cff64ea87cb2df2699bca960962fe676b62/src/transformers/modeling_tf_bert.py#L699">https://github.com/huggingface/transformers/blob/32e94cff64ea87cb2df2699bca960962fe676b62/src/transformers/modeling_tf_bert.py#L699</a></span></p> <p class="footnote"><a href="#fn-77fae012" name="f-77fae012" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/huggingface/transformers/blob/32e94cff64ea87cb2df2699bca960962fe676b62/src/transformers/modeling_tf_bert.py#L494">https://github.com/huggingface/transformers/blob/32e94cff64ea87cb2df2699bca960962fe676b62/src/transformers/modeling_tf_bert.py#L494</a></span></p> </div> taxa_program Google ColabとVSCodeを用いた分析環境運用方法 〜kaggle Tipsを添えて〜 hatenablog://entry/26006613797234259 2021-08-17T18:50:47+09:00 2021-08-18T17:11:42+09:00 こんにちは。takapy(@takapy0210)です。 本エントリは下記イベントでLTした内容の元に、補足事項やコードスニペットなどをまとめたものになります。 kaggle-friends.connpass.com ちなみに今回LTしようと思ったきっかけは以下のような出来事からだったので、みなさんのTipsなども教えていただけると嬉しいです! 情報出回ってる感あるけど、colab pro × vscode ssh のオレオレ運用方法を晒すことにより、もっと良い方法のフィードバックもらえるのではドリブンでLTするのはありなのかもしれない・・・?— takapy | たかぱい (@takapy0… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210817/20210817181613.png" alt="f:id:taxa_program:20210817181613p:plain" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは下記イベントでLTした内容の元に、補足事項やコードスニペットなどをまとめたものになります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkaggle-friends.connpass.com%2Fevent%2F220927%2F" title="第3回分析コンペLT会 、オンライン開催 (2021/08/17 20:00〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://kaggle-friends.connpass.com/event/220927/">kaggle-friends.connpass.com</a></cite></p> <p>ちなみに今回LTしようと思ったきっかけは以下のような出来事からだったので、みなさんのTipsなども教えていただけると嬉しいです!</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">情報出回ってる感あるけど、colab pro × vscode ssh のオレオレ運用方法を晒すことにより、もっと良い方法のフィードバックもらえるのではドリブンでLTするのはありなのかもしれない・・・?</p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1421841284818345984?ref_src=twsrc%5Etfw">2021年8月1日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul class="table-of-contents"> <li><a href="#LT資料">LT資料</a></li> <li><a href="#当日みなさんから頂いたコメント">当日みなさんから頂いたコメント</a></li> <li><a href="#環境構築手順">環境構築手順</a><ul> <li><a href="#ngrokアカウント作成と認証キーの取得">ngrokアカウント作成と認証キーの取得</a></li> <li><a href="#ColabにGoogleドライブを接続ngroksshサーバー起動">ColabにGoogleドライブを接続、ngrok、sshサーバー起動</a><ul> <li><a href="#ngrokのインストール設定">ngrokのインストール・設定</a></li> <li><a href="#sshサーバーの起動">sshサーバーの起動</a></li> <li><a href="#Colabサーバーの環境設定">Colabサーバーの環境設定</a></li> <li><a href="#接続情報の取得">接続情報の取得</a></li> </ul> </li> </ul> </li> <li><a href="#運用Tips">運用Tips</a><ul> <li><a href="#setupshスクリプトを作る">setup.shスクリプトを作る</a></li> <li><a href="#kaggleで使う場合のTipsコードコンペを例に">kaggleで使う場合のTips(コードコンペを例に)</a><ul> <li><a href="#学習推論スクリプト内での工夫ポイント">学習/推論スクリプト内での工夫ポイント</a></li> </ul> </li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="LT資料">LT資料</h1> <p>こちらからご覧いただけます。</p> <script async class="speakerdeck-embed" data-id="5f604747a9374f50a4684eca3d7cf597" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script> <p><a href="https://speakerdeck.com/takapy/googlecolabtovscodewoyong-itafen-xi-huan-jing-yun-yong-tips">https://speakerdeck.com/takapy/googlecolabtovscodewoyong-itafen-xi-huan-jing-yun-yong-tips</a></p> <h1 id="当日みなさんから頂いたコメント">当日みなさんから頂いたコメント</h1> <p>反応いただいた方ありがとうございました!<br /> 当日Twitterで頂いたコメントをいくつかこちらにまとめておこうと思います。</p> <hr /> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT?src=hash&amp;ref_src=twsrc%5Etfw">#分析コンペLT</a><br>ngrokはセキュリティリスクあるので自己責任で<a href="https://t.co/4xWvIV3oqF">https://t.co/4xWvIV3oqF</a></p>&mdash; しんちろ (@sinchir0) <a href="https://twitter.com/sinchir0/status/1427594751474233345?ref_src=twsrc%5Etfw">2021年8月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 使用する場合はしんちろさんのおっしゃるとおり、自己責任でお願いできればと思います🙏</p> <hr /> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">推論スクリプトをデータセットに入れているとのことですが、最終サブでもそうしてますか?<br>自分も同じようなことをしてるのですが、external dataset のルールが曖昧なせいで最終サブだけ Kaggle Notebook にベタ書きしてるんですよね。。<a href="https://twitter.com/hashtag/%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT?src=hash&amp;ref_src=twsrc%5Etfw">#分析コンペLT</a></p>&mdash; oɹɐʇuǝʞ (@cfiken) <a href="https://twitter.com/cfiken/status/1427593691850440714?ref_src=twsrc%5Etfw">2021年8月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> たしかにちょっと不安な部分ではありますね...<br /> CommonLitコンペ<a href="#f-64e62a4a" name="fn-64e62a4a" title="https://www.kaggle.com/c/commonlitreadabilityprize">*1</a>は大丈夫でした(が、安全策をとるならnotebookにちゃんと移行した方がよいかもしれません)</p> <hr /> <p>以下のような事例も共有いただきました。ありがとうございます!</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ノウハウというほどではないけど、notebookから.py呼ぶ方式でcolab使った時は、keepsakeで実験管理したら.py一式を自動でgcsに保存&amp;いつでもcheckoutでロールバックできて体験が良かった <a href="https://twitter.com/hashtag/%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT?src=hash&amp;ref_src=twsrc%5Etfw">#分析コンペLT</a></p>&mdash; Nomi (@nyanp) <a href="https://twitter.com/nyanp/status/1427593289847377927?ref_src=twsrc%5Etfw">2021年8月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">踏み台をawsやgcpに立てて、colabから踏み台にsshアクセス(ポートフォワードあり)→ローカルからsshで踏み台にアクセス(ポートフォワードあり)→別sshでフォワードされたポートにアクセス<br>ってやってる<a href="https://twitter.com/hashtag/%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT?src=hash&amp;ref_src=twsrc%5Etfw">#分析コンペLT</a></p>&mdash; K-NKSM (@KNKSM5) <a href="https://twitter.com/KNKSM5/status/1427591765272326144?ref_src=twsrc%5Etfw">2021年8月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">colab proにcolab-sshで接続してる<br>データの解答とか接続とか諸々設定書いたnotebook用意しておいてrun-allして接続してる。毎回コンテナ変わるからgitのconfigとかパッケージのインストールとかMakefileにまとめておいて何回かコマンド叩くだけで環境できるようにしてる<br> <a href="https://twitter.com/hashtag/%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT?src=hash&amp;ref_src=twsrc%5Etfw">#分析コンペLT</a></p>&mdash; 名無し。 (@496_nnc) <a href="https://twitter.com/496_nnc/status/1427591148550250496?ref_src=twsrc%5Etfw">2021年8月17日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <hr /> <p>全てのツイートは以下から見れます↓</p> <p><a href="https://twitter.com/search?q=%23%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT%20since%3A2021-08-17_20%3A00%3A00_JST%20until%3A2021-08-17_20%3A33%3A00_JST&src=typeahead_click&f=live">https://twitter.com/search?q=%23%E5%88%86%E6%9E%90%E3%82%B3%E3%83%B3%E3%83%9ALT%20since%3A2021-08-17_20%3A00%3A00_JST%20until%3A2021-08-17_20%3A33%3A00_JST&src=typeahead_click&f=live</a></p> <hr /> <p>ここからは、スライドの補足やサンプルコードの紹介をしていきます。</p> <h1 id="環境構築手順">環境構築手順</h1> <p>資料内にある環境構築手順について、コードを交えながら補足していきます。</p> <p>LT資料の冒頭にも記載していますが、ここで紹介する方法はGoogle側が推奨している使用方法ではないので、あらかじめご了承ください。(急に使えなくなったりする可能性もあると思っていますので、使用する際は自己責任でお願いします)</p> <h2 id="ngrokアカウント作成と認証キーの取得">ngrokアカウント作成と認証キーの取得</h2> <p>ngrokというサービスに無料アカウントを作成します。</p> <p>このサービスを使用することで、ローカルIPアドレスしか持たないホストに、外部のネットワークからアクセスすることができるようになったりします。 また、SSHのトンネルとしても使用することができるサービスです。</p> <p>Colabを使用する際にはGoogleアカウントが必要となりますので、Googleアカウントで紐づけるのが無難かと思います。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fngrok.com%2F" title="ngrok - secure introspectable tunnels to localhost" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://ngrok.com/">ngrok.com</a></cite></p> <p>アカウントが作成できたら、ログインし、<code>Your Authtoken</code> の値をコピーしておきます。(次に説明するsshサーバーを起動する部分で使用します)</p> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210816/20210816224632.png" alt="f:id:taxa_program:20210816224632p:plain" width="1200" height="236" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="ColabにGoogleドライブを接続ngroksshサーバー起動">ColabにGoogleドライブを接続、ngrok、sshサーバー起動</h2> <p>Colabを起動し、GPU接続ON、Googleドライブ接続を行ったあと、下記手順に沿ってnotebook上でコードを実行していきます。<br /> このnotebookは一度作ってしまえば、2回目以降は同じnotebookを実行すればOKです。</p> <p>まずはGPUがうまく接続できているかチェックしておきます(これはやらなくてもOKです)</p> <pre class="code lang-python" data-lang="python" data-unlink>!cat /etc/lsb-release !nvcc -V !nvidia-smi </pre> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 出力結果例</span> <span class="synStatement">&gt;&gt;</span> <span class="synIdentifier">DISTRIB_ID</span>=Ubuntu <span class="synIdentifier">DISTRIB_RELEASE</span>=<span class="synConstant">18</span>.<span class="synConstant">04</span> <span class="synIdentifier">DISTRIB_CODENAME</span>=bionic <span class="synIdentifier">DISTRIB_DESCRIPTION</span>=<span class="synStatement">&quot;</span><span class="synConstant">Ubuntu 18.04.5 LTS</span><span class="synStatement">&quot;</span> nvcc: NVIDIA <span class="synPreProc">(</span><span class="synSpecial">R</span><span class="synPreProc">)</span> Cuda compiler driver Copyright <span class="synPreProc">(</span><span class="synSpecial">c</span><span class="synPreProc">)</span> 2005-2020 NVIDIA Corporation Built on Wed_Jul_22_19:09:09_PDT_2020 Cuda compilation tools, release <span class="synConstant">11</span>.<span class="synConstant">0</span>, V11.<span class="synConstant">0</span>.<span class="synConstant">221</span> Build cuda_11.0_bu.TC445_37.28845127_0 Mon Aug <span class="synConstant">16</span> 13:49:54 <span class="synConstant">2021</span> +-----------------------------------------------------------------------------+ | NVIDIA-SMI <span class="synConstant">470</span>.<span class="synConstant">42</span>.<span class="synConstant">01</span> Driver Version: <span class="synConstant">460</span>.<span class="synConstant">32</span>.<span class="synConstant">03</span> CUDA Version: <span class="synConstant">11</span>.<span class="synConstant">2</span> | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |<span class="synStatement">===============================+======================+======================</span>| | <span class="synConstant">0</span> Tesla P100-PCIE... Off | 00000000:00:04.<span class="synConstant">0</span> Off | <span class="synConstant">0</span> | | N/A 37C P0 27W / 250W | 0MiB / 16280MiB | <span class="synConstant">0</span>% Default | | | | N/A | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |<span class="synStatement">=============================================================================</span>| | No running processes found | +-----------------------------------------------------------------------------+ </pre> <h3 id="ngrokのインストール設定">ngrokのインストール・設定</h3> <pre class="code lang-sh" data-lang="sh" data-unlink>!apt-get <span class="synSpecial">-y</span> update !wget <span class="synSpecial">-q</span> <span class="synSpecial">-c</span> <span class="synSpecial">-nc</span> https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip !unzip <span class="synSpecial">-qq</span> <span class="synSpecial">-n</span> ngrok-stable-linux-amd64.zip <span class="synComment"># sshの設定</span> !apt-get install <span class="synSpecial">-qq</span> <span class="synSpecial">-o</span><span class="synStatement">=</span>Dpkg::Use-Pty<span class="synStatement">=</span><span class="synConstant">0</span> openssh-server pwgen <span class="synStatement">&gt;</span> /dev/null </pre> <h3 id="sshサーバーの起動">sshサーバーの起動</h3> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> random, string, urllib.request, json, getpass <span class="synComment"># Generate root password</span> password = <span class="synConstant">''</span>.join(random.choice(string.ascii_letters + string.digits) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">20</span>)) <span class="synComment"># Set root password</span> !echo root:$password | chpasswd !mkdir -p /var/run/sshd !echo <span class="synConstant">&quot;PermitRootLogin yes&quot;</span> &gt;&gt; /etc/ssh/sshd_config !echo <span class="synConstant">&quot;PasswordAuthentication yes&quot;</span> &gt;&gt; /etc/ssh/sshd_config !echo <span class="synConstant">&quot;LD_LIBRARY_PATH=/usr/lib64-nvidia&quot;</span> &gt;&gt; /root/.bashrc !echo <span class="synConstant">&quot;export LD_LIBRARY_PATH&quot;</span> &gt;&gt; /root/.bashrc <span class="synComment"># Run sshd</span> get_ipython().system_raw(<span class="synConstant">'/usr/sbin/sshd -D &amp;'</span>) <span class="synComment"># アクセストークンの設定</span> <span class="synComment"># https://dashboard.ngrok.com/auth/your-authtoken</span> authtoken=<span class="synConstant">&quot;上記で取得したYour Authtokenの値&quot;</span> <span class="synComment"># Create tunnel</span> get_ipython().system_raw(<span class="synConstant">'./ngrok authtoken $authtoken &amp;&amp; ./ngrok tcp 22 &amp;'</span>) </pre> <h3 id="Colabサーバーの環境設定">Colabサーバーの環境設定</h3> <p>下記設定を行うことで、VSCodeから接続した際にローカル環境と同等の環境を実現することができます。<br /> この例ではzshを使っていますが、ここはお好きなシェル(ローカルで日常的に使っている物が良い)をインストールしてください。</p> <p>シェルの設定ファイルも、ローカルのものをGoogle Driveにアップロードしておき、それをコピーしておくことで同じ環境を構築できます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># シンボリックリンクを作成</span> !ln <span class="synSpecial">-sfn</span> /content/drive/MyDrive/workspace /root/workspace <span class="synComment"># zshのインストール</span> !sudo apt-get install zsh <span class="synComment"># oh-my-zshのインストール</span> !wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh <span class="synSpecial">-O</span> - | zsh || <span class="synStatement">true</span> <span class="synComment"># zshをデフォルトに設定</span> !chsh <span class="synSpecial">-s</span> /usr/bin/zsh <span class="synComment"># bashファイルの作成</span> !<span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">export PATH=/usr/local/cuda/bin:$PATH</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> /root/.bash_profile !<span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">export LD_LIBRARY_PATH=/usr/lib64-nvidia</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> /root/.bash_profile !<span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">export PROMPT_COMMAND=&quot;history -a&quot;</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> /root/.bash_profile !<span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">export HISTFILE=/root/.zsh-history</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> /root/.bash_profile !<span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">export PYTHONDONTWRITEBYTECODE=1</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> /root/.bash_profile !<span class="synStatement">echo</span><span class="synConstant"> </span><span class="synStatement">'</span><span class="synConstant">export TF_CPP_MIN_LOG_LEVEL=2</span><span class="synStatement">'</span><span class="synConstant"> </span><span class="synStatement">&gt;&gt;</span> /root/.bash_profile <span class="synComment"># ファイルをgoogle driveからサーバーへコピー</span> !cp /content/drive/MyDrive/workspace/.gitconfig .gitconfig !cp /content/drive/MyDrive/workspace/.zshrc .zshrc </pre> <h3 id="接続情報の取得">接続情報の取得</h3> <p>最後に、VSCodeからssh接続するために必要な情報を取得します。<br /> Calabの接続が切れてしまうと、このホスト名とポート番号も変わってしまうので、その場合はconfig情報も更新した後、再度VSCodeからssh接続する必要があります。</p> <pre class="code lang-python" data-lang="python" data-unlink> <span class="synComment"># Get public address and print connect command</span> <span class="synStatement">with</span> urllib.request.urlopen(<span class="synConstant">'http://localhost:4040/api/tunnels'</span>) <span class="synStatement">as</span> response: data = json.loads(response.read().decode()) (host, port) = data[<span class="synConstant">'tunnels'</span>][<span class="synConstant">0</span>][<span class="synConstant">'public_url'</span>][<span class="synConstant">6</span>:].split(<span class="synConstant">':'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;&quot;&quot;Host google-colab-ssh</span> <span class="synConstant">HostName {host}</span> <span class="synConstant">Port {port}</span> <span class="synConstant">User root</span> <span class="synConstant">&quot;&quot;&quot;</span>) <span class="synComment"># Print root password</span> <span class="synIdentifier">print</span>(f<span class="synConstant">'Root password: {password}'</span>) </pre> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># 出力結果例</span> <span class="synStatement">&gt;&gt;</span> Host google-colab-ssh HostName <span class="synConstant">0</span>.tcp.ngrok.io Port <span class="synConstant">9999</span> User root Root password: HOGEHOGE </pre> <p>以下は実際の接続手順となっています。(LT資料から抜粋)</p> <p><figure class="figure-image figure-image-fotolife" title="ssh接続手順"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210816/20210816230345.png" alt="f:id:taxa_program:20210816230345p:plain" width="1200" height="674" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>手順</figcaption></figure></p> <h1 id="運用Tips">運用Tips</h1> <p>最後に、<strong>ここまでで構築した環境を、より快適に運用するためのTipsをご紹介します。</strong></p> <h2 id="setupshスクリプトを作る">setup.shスクリプトを作る</h2> <p>VSCodeの拡張機能やpipでインストールしたいpythonのライブラリを1つのスクリプトにまとめておくといろいろ捗ります。</p> <p>自分は<code>setup.sh</code>という名前でGoogle Driveのworkspaceディレクトリ(ssh接続先ディレクトリ)に保存しています。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment">#!/bin/bash</span> <span class="synComment"># pip install</span> python3 <span class="synSpecial">-m</span> pip install <span class="synSpecial">-r</span> requirements.txt <span class="synComment"># Visual Studio Code :: Package list</span> <span class="synIdentifier">pkglist</span>=<span class="synPreProc">(</span> <span class="synSpecial">ms-python.python</span> <span class="synSpecial">tabnine.tabnine-vscode</span> <span class="synSpecial">njpwerner.autodocstring</span> <span class="synSpecial">oderwat.indent-rainbow</span> <span class="synPreProc">)</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synPreProc">${pkglist[</span>@<span class="synPreProc">]}</span>; <span class="synStatement">do</span> code <span class="synSpecial">--install-extension</span> <span class="synPreProc">$i</span> <span class="synStatement">done</span> </pre> <p><code>requirements.txt</code>の中身は以下のようになっていて、Colabにはプリインストールされていないpythonライブラリを記載しています。</p> <pre class="code txt" data-lang="txt" data-unlink>kaggle transformers texthero flake8</pre> <p>このようなスクリプトを用意しておくことで、VSCodeからssh接続した後、</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ bash setup.sh </pre> <p>を実行するだけで、<strong>毎回同じ環境を構築することができます。(便利!)</strong></p> <h2 id="kaggleで使う場合のTipsコードコンペを例に">kaggleで使う場合のTips(コードコンペを例に)</h2> <p>学習/推論スクリプトの中に、”スクリプト本体”と”学習済みモデル”をkaggle Datasetにアップロードする処理を仕込んでおきます。<br /> 実際にコンペにsubmitする時は、kaggleのnotebook上からアップロードしたスクリプトを実行するだけで推論処理が行われるようになります。(kaggleのnotebookから<code>!python3 exp001.py</code> みたいな形で実行するイメージです)<br /> 後述していますが、このスクリプト内部も若干工夫が必要です。</p> <p>こうすることで以下のようなメリットを享受できると思っています。</p> <ul> <li>Colabで作ったモデルを都度手動でDatasetsにアップロードしなくて良い</li> <li>Colabでモデルは作ったけど、推論専用のnotebookが無いから0から作らなきゃ…という事態を回避できる</li> <li>推論処理のコードもGithubで管理できる</li> <li>kaggleのDatasetsをうまく使うことで、「モデル」と「そのモデルを生成したスクリプト」をセットで管理することもできる(このモデル、どのスクリプトで作ったやつだっけ・・・みたいなことが無くなる)</li> </ul> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210817/20210817174212.png" alt="f:id:taxa_program:20210817174212p:plain" width="1200" height="393" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>kaggleのデータセットにアップロードする処理は以下のように実装することができます。<br /> 下記のサンプルだと、<code>upload_target_dir</code>に対象のGoogle Driveのパスを指定して実行するイメージです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">upload_kaggle_dataset</span>(user_id, exp_no, upload_target_dir, logger): <span class="synConstant">&quot;&quot;&quot;kaggleのデータセットにデータをuploadする関数&quot;&quot;&quot;</span> <span class="synPreProc">from</span> kaggle.api.kaggle_api_extended <span class="synPreProc">import</span> KaggleApi _id = f<span class="synConstant">'{user_id}/{exp_no}'</span> dataset_metadata = {} dataset_metadata[<span class="synConstant">'id'</span>] = _id dataset_metadata[<span class="synConstant">'licenses'</span>] = [{<span class="synConstant">'name'</span>: <span class="synConstant">'CC0-1.0'</span>}] dataset_metadata[<span class="synConstant">'title'</span>] = f<span class="synConstant">'{exp_no}'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(upload_target_dir + <span class="synConstant">'/dataset-metadata.json'</span>, <span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: json.dump(dataset_metadata, f, indent=<span class="synConstant">4</span>) api = KaggleApi() api.authenticate() <span class="synComment"># データセットがない場合</span> <span class="synStatement">if</span> _id <span class="synStatement">not</span> <span class="synStatement">in</span> [<span class="synIdentifier">str</span>(d) <span class="synStatement">for</span> d <span class="synStatement">in</span> api.dataset_list(user=user_id, search=exp_no)]: logger.info(<span class="synConstant">'No data set, so create a new one.'</span>) logger.info(f<span class="synConstant">'URL: https://www.kaggle.com/{_id}'</span>) api.dataset_create_new(folder=upload_target_dir, convert_to_csv=<span class="synIdentifier">False</span>, dir_mode=<span class="synConstant">'skip'</span>) <span class="synComment"># データセットがある場合</span> <span class="synStatement">else</span>: logger.info(<span class="synConstant">'Generate a new version because of the data set.'</span>) logger.info(f<span class="synConstant">'URL: https://www.kaggle.com/{_id}'</span>) api.dataset_create_version(folder=upload_target_dir, version_notes=<span class="synConstant">'update'</span>, convert_to_csv=<span class="synIdentifier">False</span>, delete_old_versions=<span class="synIdentifier">True</span>, dir_mode=<span class="synConstant">'skip'</span>) <span class="synStatement">return</span> <span class="synIdentifier">None</span> </pre> <h3 id="学習推論スクリプト内での工夫ポイント">学習/推論スクリプト内での工夫ポイント</h3> <p>実際のスクリプト(上図でいうところの<code>exp001.py</code>)の中身ですが、以下のように条件分岐をしておくことで、kaggleのnotebook上では推論処理のみを、その他の環境の時は学習→推論といった一連の処理を行うコードを書くことができます。(場合によってはtestデータ含めて学習し直すケースもあると思いますが)</p> <p>このようにすることで、学習/推論処理を1つのスクリプトで管理できます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> os <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: <span class="synComment"># 学習</span> <span class="synStatement">if</span> <span class="synConstant">'KAGGLE_URL_BASE'</span> <span class="synStatement">not</span> <span class="synStatement">in</span> <span class="synIdentifier">set</span>(os.environ.keys()): <span class="synComment"># kaggleのnotebook上では学習しない</span> train = load_data(DATA_DIR + <span class="synConstant">'train.csv'</span>) train = preprocessing(train) training(train) <span class="synComment"># 推論</span> test = load_data(DATA_DIR + <span class="synConstant">'test.csv'</span>) test = preprocessing(test, is_train=<span class="synIdentifier">False</span>) submit_df = inference(test) submit_df.to_csv(<span class="synConstant">'submission.csv'</span>, index=<span class="synIdentifier">False</span>) </pre> <h1 id="最後に">最後に</h1> <p>今回は私が行っているColabとVSCodeの運用方法についてまとめてみました。<br /> もっといい感じの環境にできるのでは?と思っていたりするので、みなさんのColab Tipsも教えていただけると嬉しいです!</p> <p>そして、本エントリが「Colabうまく使いこなせないなぁ」と思っていた人の一助になれば嬉しいです。</p> <hr /> <p>参考資料</p> <ul> <li><a href="https://qiita.com/hazigin/items/c291adf5dc9ccc13d11f">Google Colab をSSH と VS Code で使う</a></li> <li><a href="https://qiita.com/Never_/items/3dfcc8cf5c2765832c74">Google Colab proをVSCodeでssh接続してV100(GPU)を早速回してみた。</a></li> </ul> <div class="footnote"> <p class="footnote"><a href="#fn-64e62a4a" name="f-64e62a4a" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://www.kaggle.com/c/commonlitreadabilityprize">https://www.kaggle.com/c/commonlitreadabilityprize</a></span></p> </div> taxa_program PandasからBigQueryにデータを保存する際に「Resources exceeded during query execution: UDF out of memory. ..... columns is too large」エラーが出た時の対処方法 hatenablog://entry/26006613795827888 2021-08-10T21:02:36+09:00 2021-08-10T21:02:36+09:00 こんにちは。takapy(@takapy0210)です。 表題の件で少し困ったので、備忘がてら記事に残しておこうと思います。 やろうとしていたこと エラー内容 該当箇所のコード work around 最後に やろうとしていたこと BigQueryのPython SDKを用いて、Pandasで読み込んだデータをBigQueryのテーブルに保存する処理で、後述するエラーが発生しました。 ちなみに下記のBigQueryクライアントライブラリ(google-cloud-bigquery)を使っています。 cloud.google.com エラー内容 以下のようなエラーが発生しました。 400 Res… <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>表題の件で少し困ったので、備忘がてら記事に残しておこうと思います。</p> <ul class="table-of-contents"> <li><a href="#やろうとしていたこと">やろうとしていたこと</a></li> <li><a href="#エラー内容">エラー内容</a></li> <li><a href="#該当箇所のコード">該当箇所のコード</a><ul> <li><a href="#work-around">work around</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="やろうとしていたこと">やろうとしていたこと</h1> <p>BigQueryのPython SDKを用いて、Pandasで読み込んだデータをBigQueryのテーブルに保存する処理で、後述するエラーが発生しました。</p> <p>ちなみに下記のBigQueryクライアントライブラリ(<code>google-cloud-bigquery</code>)を使っています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fcloud.google.com%2Fbigquery%2Fdocs%2Freference%2Flibraries%3Fhl%3Dja" title="BigQuery API クライアント ライブラリ  |  Google Cloud" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://cloud.google.com/bigquery/docs/reference/libraries?hl=ja">cloud.google.com</a></cite></p> <h1 id="エラー内容">エラー内容</h1> <p>以下のようなエラーが発生しました。</p> <blockquote><p>400 Resources exceeded during query execution: UDF out of memory.; Failed to read Parquet file /bigstore/hogehoge. This might happen if the file contains a row that is too large, or if the total size of the pages loaded for the queried columns is too large.</p></blockquote> <p>ファイルに大きすぎる行が含まれている場合などに発生するエラーのようです。<br /> 今回のデータ全体のサイズが大き過ぎるため発生したと思われます。(詳細は不明...)</p> <p><strong>ちなみに今回BigQueryに保存しようとしていたデータは、CSVファイルのサイズが3GBくらい、データ件数が300万件ほどのテキストデータを含むDataFrameになっています。</strong></p> <h1 id="該当箇所のコード">該当箇所のコード</h1> <p>基本的には公式サンプル<a href="#f-0a98e581" name="fn-0a98e581" title="https://github.com/GoogleCloudPlatform/python-docs-samples/blob/HEAD/bigquery/pandas-gbq-migration/samples_test.py">*1</a>通りの実装です。</p> <p>下記コードの <code>job.result()</code> の処理で今回のエラーが発生していました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># データをimportする関数例</span> <span class="synStatement">def</span> <span class="synIdentifier">import_to_bigquery</span>(df, schema, schema_definition): <span class="synConstant">&quot;&quot;&quot;BQにデータを保存する&quot;&quot;&quot;</span> client = bigquery.Client() table_id = f<span class="synConstant">'{schema.project}.{schema.dataset_id}.{schema.table_id}'</span> job_config = bigquery.LoadJobConfig( write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE, <span class="synComment"># 上書き</span> schema=schema_definition, ) job = client.load_table_from_dataframe( df, table_id, location=<span class="synConstant">'US'</span>, job_config=job_config, ) job.result() <span class="synComment"># ここでエラー発生</span> table = client.get_table(table_id) LOGGER.info(f<span class="synConstant">'Imported data: {table.num_rows}rows and {len(table.schema)}columns to {schema.table_id}'</span>) </pre> <h2 id="work-around">work around</h2> <p>試しにデータ件数を10000サンプルで実行してみると問題なく動いたので、<strong>DataFrameをchunk化しながらBigQueryに保存すればいけるのでは?ということで下記のような修正して実行したところ、期待する動作になりました。</strong></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">import_to_bigquery</span>(df, schema, schema_definition): <span class="synConstant">&quot;&quot;&quot;BQにデータを保存する&quot;&quot;&quot;</span> client = bigquery.Client() table_id = f<span class="synConstant">'{schema.project}.{schema.dataset_id}.{schema.table_id}'</span> <span class="synStatement">for</span> index, df_chunk <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(np.array_split(df, <span class="synConstant">10</span>)): <span class="synComment"># 10分割しながらBigQueryにimportする. 最初の1回は上書きをし、残りは追加を行う.</span> <span class="synStatement">if</span> index == <span class="synConstant">0</span>: job_config = bigquery.LoadJobConfig( write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE, <span class="synComment"># 上書き</span> schema=schema_definition, ) <span class="synStatement">else</span>: job_config = bigquery.LoadJobConfig( write_disposition=bigquery.WriteDisposition.WRITE_APPEND, <span class="synComment"># 追加</span> schema=schema_definition, ) job = client.load_table_from_dataframe( df_chunk, table_id, location=<span class="synConstant">'US'</span>, job_config=job_config, ) job.result() table = client.get_table(table_id) LOGGER.info(f<span class="synConstant">'Imported data: {table.num_rows}rows and {len(table.schema)}columns to {schema.table_id}'</span>) </pre> <p>コードを見てわかるように、chunk化には<code>numpy.array_split</code><a href="#f-2ce9bd06" name="fn-2ce9bd06" title="https://numpy.org/doc/stable/reference/generated/numpy.array_split.html">*2</a> を使用しました。<br /> <code>read_csv()</code>の<code>chunksize</code> 引数を指定しても良かったのですが、前処理を行っている都合もあり今回は不採用としました。</p> <h1 id="最後に">最後に</h1> <p>公式リポジトリのISSUE<a href="#f-cf93bc80" name="fn-cf93bc80" title="https://github.com/googleapis/python-bigquery/issues">*3</a> などを漁っても解決方法が見つからなかったので、現状はこのようにデータを分割しながら回避するしかなさそうです。</p> <div class="footnote"> <p class="footnote"><a href="#fn-0a98e581" name="f-0a98e581" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/GoogleCloudPlatform/python-docs-samples/blob/HEAD/bigquery/pandas-gbq-migration/samples_test.py">https://github.com/GoogleCloudPlatform/python-docs-samples/blob/HEAD/bigquery/pandas-gbq-migration/samples_test.py</a></span></p> <p class="footnote"><a href="#fn-2ce9bd06" name="f-2ce9bd06" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://numpy.org/doc/stable/reference/generated/numpy.array_split.html">https://numpy.org/doc/stable/reference/generated/numpy.array_split.html</a></span></p> <p class="footnote"><a href="#fn-cf93bc80" name="f-cf93bc80" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/googleapis/python-bigquery/issues">https://github.com/googleapis/python-bigquery/issues</a></span></p> </div> taxa_program 【言語処理100本ノック 2020】 8章をPythonで解いた(TensorFlowを使用) hatenablog://entry/26006613782543585 2021-07-03T12:59:11+09:00 2021-07-03T12:59:11+09:00 こんにちは。takapy(@takapy0210)です。 本エントリは言語処理100本ノック2020の8章を解いてみたので、それの備忘です。 簡単な解説をつけながら紹介していきます。 ネット上に掲載されている解答例はPytorchによる解法が多かったので、TensorFlowを用いて解いてみました。 nlp100.github.io コードはGithubに置いてあります。 github.com 第8章: ニューラルネット 第6章で取り組んだニュース記事のカテゴリ分類を題材として,ニューラルネットワークでカテゴリ分類モデルを実装する.なお,この章ではPyTorch, TensorFlow, Ch… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" width="1200" height="436" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック2020の8章を解いてみたので、それの備忘です。<br /> 簡単な解説をつけながら紹介していきます。</p> <p>ネット上に掲載されている解答例はPytorchによる解法が多かったので、TensorFlowを用いて解いてみました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch08.html" title="第8章: ニューラルネット" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch08.html">nlp100.github.io</a></cite></p> <p>コードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020">github.com</a></cite></p> <blockquote><p>第8章: ニューラルネット</p> <p>第6章で取り組んだニュース記事のカテゴリ分類を題材として,ニューラルネットワークでカテゴリ分類モデルを実装する.なお,この章ではPyTorch, TensorFlow, Chainerなどの機械学習プラットフォームを活用せよ</p></blockquote> <ul class="table-of-contents"> <li><a href="#70-単語ベクトルの和による特徴量">70. 単語ベクトルの和による特徴量</a></li> <li><a href="#71-単層ニューラルネットワークによる予測">71. 単層ニューラルネットワークによる予測</a></li> <li><a href="#72-損失と勾配の計算">72. 損失と勾配の計算</a></li> <li><a href="#73-確率的勾配降下法による学習">73. 確率的勾配降下法による学習</a></li> <li><a href="#74-正解率の計測">74. 正解率の計測</a></li> <li><a href="#75-損失と正解率のプロット--76-チェックポイント--77-ミニバッチ化">75. 損失と正解率のプロット / 76. チェックポイント / 77. ミニバッチ化</a></li> <li><a href="#79-多層ニューラルネットワーク">79. 多層ニューラルネットワーク</a></li> </ul> <h1 id="70-単語ベクトルの和による特徴量">70. 単語ベクトルの和による特徴量</h1> <p>SWEMを用いて単語の平均ベクトルを計算しています. <br /> SWEMのコード部分には<a href="https://github.com/takapy0210/nlp_2020/blob/master/chapter8/swem.py">こちらのGithub</a>に掲載しています.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">70. 単語ベクトルの和による特徴量</span> <span class="synConstant">問題50で構築した学習データ,検証データ,評価データを行列・ベクトルに変換したい.</span> <span class="synConstant">i番目の事例の記事見出しを,その見出しに含まれる単語のベクトルの平均で表現したものがxiである.今回は単語ベクトルとして,問題60でダウンロードしたものを用いればよい.</span> <span class="synConstant">以下の行列・ベクトルを作成し,ファイルに保存せよ.</span> <span class="synConstant">学習データの特徴量行列: Xtrain∈ℝNt×d</span> <span class="synConstant">学習データのラベルベクトル: Ytrain∈ℕNt</span> <span class="synConstant">検証データの特徴量行列: Xvalid∈ℝNv×d</span> <span class="synConstant">検証データのラベルベクトル: Yvalid∈ℕNv</span> <span class="synConstant">評価データの特徴量行列: Xtest∈ℝNe×d</span> <span class="synConstant">評価データのラベルベクトル: Ytest∈ℕNe</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synPreProc">from</span> swem <span class="synPreProc">import</span> SWEM <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'../chapter6/train.txt'</span>, <span class="synConstant">'valid'</span>: <span class="synConstant">'../chapter6/valid.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'../chapter6/test.txt'</span>, } dfs = {} use_cols = [<span class="synConstant">'title'</span>, <span class="synConstant">'category'</span>] <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) dfs[k] = dfs[k][use_cols] <span class="synStatement">return</span> dfs <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(text) -&gt; <span class="synIdentifier">str</span>: <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># chapter6で生成したデータを読み込む</span> dfs = load_data() <span class="synComment"># 事前学習済みモデルのロード</span> <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'../chapter7/GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synComment"># 前処理</span> dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'title'</span>] = dfs[<span class="synConstant">'train'</span>][[<span class="synConstant">'title'</span>]].apply(preprocess) dfs[<span class="synConstant">'valid'</span>][<span class="synConstant">'title'</span>] = dfs[<span class="synConstant">'valid'</span>][[<span class="synConstant">'title'</span>]].apply(preprocess) dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'title'</span>] = dfs[<span class="synConstant">'test'</span>][[<span class="synConstant">'title'</span>]].apply(preprocess) <span class="synComment"># 説明変数の生成(SWEMの計算)</span> swem = SWEM(model) X_train = swem.calculate_emb(df=dfs[<span class="synConstant">'train'</span>], col=<span class="synConstant">'title'</span>, window=<span class="synConstant">3</span>, swem_type=<span class="synConstant">1</span>) X_valid = swem.calculate_emb(df=dfs[<span class="synConstant">'valid'</span>], col=<span class="synConstant">'title'</span>, window=<span class="synConstant">3</span>, swem_type=<span class="synConstant">1</span>) X_test = swem.calculate_emb(df=dfs[<span class="synConstant">'test'</span>], col=<span class="synConstant">'title'</span>, window=<span class="synConstant">3</span>, swem_type=<span class="synConstant">1</span>) <span class="synComment"># 目的変数の生成</span> y_train = dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>].map({<span class="synConstant">'b'</span>: <span class="synConstant">0</span>, <span class="synConstant">'e'</span>: <span class="synConstant">1</span>, <span class="synConstant">'t'</span>: <span class="synConstant">2</span>, <span class="synConstant">'m'</span>: <span class="synConstant">3</span>}) y_valid = dfs[<span class="synConstant">'valid'</span>][<span class="synConstant">'category'</span>].map({<span class="synConstant">'b'</span>: <span class="synConstant">0</span>, <span class="synConstant">'e'</span>: <span class="synConstant">1</span>, <span class="synConstant">'t'</span>: <span class="synConstant">2</span>, <span class="synConstant">'m'</span>: <span class="synConstant">3</span>}) y_test = dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'category'</span>].map({<span class="synConstant">'b'</span>: <span class="synConstant">0</span>, <span class="synConstant">'e'</span>: <span class="synConstant">1</span>, <span class="synConstant">'t'</span>: <span class="synConstant">2</span>, <span class="synConstant">'m'</span>: <span class="synConstant">3</span>}) <span class="synComment"># 保存</span> X_train.to_pickle(<span class="synConstant">'X_train.pkl'</span>) X_valid.to_pickle(<span class="synConstant">'X_valid.pkl'</span>) X_test.to_pickle(<span class="synConstant">'X_test.pkl'</span>) y_train.to_pickle(<span class="synConstant">'y_train.pkl'</span>) y_valid.to_pickle(<span class="synConstant">'y_valid.pkl'</span>) y_test.to_pickle(<span class="synConstant">'y_test.pkl'</span>) </pre> <h1 id="71-単層ニューラルネットワークによる予測">71. 単層ニューラルネットワークによる予測</h1> <p>TensorFlowを用いて、単層ニューラルネットワークを構築し、指示された内容を計算しています.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">71. 単層ニューラルネットワークによる予測</span> <span class="synConstant">問題70で保存した行列を読み込み,学習データについて以下の計算を実行せよ.</span> <span class="synConstant">ŷ 1=softmax(x1W),Ŷ =softmax(X[1:4]W)</span> <span class="synConstant">ただし,softmaxはソフトマックス関数,X[1:4]∈ℝ4×dは特徴ベクトルx1,x2,x3,x4を縦に並べた行列である.</span> <span class="synConstant">X[1:4]=⎛⎝⎜⎜⎜⎜x1x2x3x4⎞⎠⎟⎟⎟⎟</span> <span class="synConstant">行列W∈ℝd×Lは単層ニューラルネットワークの重み行列で,ここではランダムな値で初期化すればよい(問題73以降で学習して求める).</span> <span class="synConstant">なお,ŷ 1∈ℝLは未学習の行列Wで事例x1を分類したときに,各カテゴリに属する確率を表すベクトルである.</span> <span class="synConstant">同様に,Ŷ ∈ℝn×Lは,学習データの事例x1,x2,x3,x4について,各カテゴリに属する確率を行列として表現している.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synStatement">class</span> <span class="synIdentifier">SimpleNet</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, feature_dim, target_dim): self.input = tf.keras.layers.Input(shape=(feature_dim), name=<span class="synConstant">'input'</span>) self.output = tf.keras.layers.Dense(target_dim, activation=<span class="synConstant">'softmax'</span>, name=<span class="synConstant">'output'</span>) <span class="synStatement">def</span> <span class="synIdentifier">build</span>(self): input_layer = self.input output_layer = self.output(input_layer) model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer) <span class="synStatement">return</span> model <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: X_train = pd.read_pickle(<span class="synConstant">'X_train.pkl'</span>) model = SimpleNet(X_train.shape[<span class="synConstant">1</span>], <span class="synConstant">4</span>).build() <span class="synIdentifier">print</span>(model(X_train.values[:<span class="synConstant">1</span>])) <span class="synIdentifier">print</span>(model(X_train.values[:<span class="synConstant">4</span>])) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>tf.Tensor([[0.2661007 0.25712514 0.2329659 0.2438082 ]], shape=(1, 4), dtype=float32) tf.Tensor( [[0.2661007 0.25712514 0.23296592 0.2438082 ] [0.27437785 0.25097498 0.23388673 0.24076048] [0.27228996 0.25715688 0.234876 0.23567708] [0.27745858 0.25357178 0.22825074 0.24071899]], shape=(4, 4), dtype=float32)</pre> <h1 id="72-損失と勾配の計算">72. 損失と勾配の計算</h1> <p>損失の計算には<code>tf.keras.losses.CategoricalCrossentropy()</code>を使っています.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">72. 損失と勾配の計算</span> <span class="synConstant">学習データの事例x1と事例集合x1,x2,x3,x4に対して,クロスエントロピー損失と,行列Wに対する勾配を計算せよ.なお,ある事例xiに対して損失は次式で計算される.</span> <span class="synConstant">li=−log[事例xiがyiに分類される確率]</span> <span class="synConstant">ただし,事例集合に対するクロスエントロピー損失は,その集合に含まれる各事例の損失の平均とする.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synPreProc">from</span> tensorflow.keras.utils <span class="synPreProc">import</span> to_categorical <span class="synStatement">class</span> <span class="synIdentifier">SimpleNet</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, feature_dim, target_dim): self.input = tf.keras.layers.Input(shape=(feature_dim), name=<span class="synConstant">'input'</span>) self.output = tf.keras.layers.Dense(target_dim, activation=<span class="synConstant">'softmax'</span>, name=<span class="synConstant">'output'</span>) <span class="synStatement">def</span> <span class="synIdentifier">build</span>(self): input_layer = self.input output_layer = self.output(input_layer) model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer) <span class="synStatement">return</span> model <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> X_train = pd.read_pickle(<span class="synConstant">'X_train.pkl'</span>) y_train = pd.read_pickle(<span class="synConstant">'y_train.pkl'</span>) <span class="synComment"># モデル構築</span> model = SimpleNet(X_train.shape[<span class="synConstant">1</span>], <span class="synIdentifier">len</span>(y_train.unique())).build() preds = model(X_train.values[:<span class="synConstant">4</span>]) <span class="synComment"># 目的変数をone-hotに変換</span> y_true = to_categorical(y_train) y_true = y_true[:<span class="synConstant">4</span>] <span class="synComment"># 計算</span> cce = tf.keras.losses.CategoricalCrossentropy() <span class="synIdentifier">print</span>(cce(y_true, preds.numpy()).numpy()) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>1.4818511</pre> <h1 id="73-確率的勾配降下法による学習">73. 確率的勾配降下法による学習</h1> <p>ラベルはone-hotに変換していないので、lossには<code>SparseCategoricalCrossentropy()</code>を用いています.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">73. 確率的勾配降下法による学習</span> <span class="synConstant">確率的勾配降下法(SGD: Stochastic Gradient Descent)を用いて,行列Wを学習せよ.なお,学習は適当な基準で終了させればよい(例えば「100エポックで終了」など)</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synStatement">class</span> <span class="synIdentifier">SimpleNet</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, feature_dim, target_dim): self.input = tf.keras.layers.Input(shape=(feature_dim), name=<span class="synConstant">'input'</span>) self.output = tf.keras.layers.Dense(target_dim, activation=<span class="synConstant">'softmax'</span>, name=<span class="synConstant">'output'</span>) <span class="synStatement">def</span> <span class="synIdentifier">build</span>(self): input_layer = self.input output_layer = self.output(input_layer) model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer) <span class="synStatement">return</span> model <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> X_train = pd.read_pickle(<span class="synConstant">'X_train.pkl'</span>) y_train = pd.read_pickle(<span class="synConstant">'y_train.pkl'</span>) <span class="synComment"># モデル構築</span> model = SimpleNet(X_train.shape[<span class="synConstant">1</span>], <span class="synIdentifier">len</span>(y_train.unique())).build() opt = tf.optimizers.SGD() model.compile( optimizer=opt, loss=tf.keras.losses.SparseCategoricalCrossentropy() ) <span class="synComment"># 学習</span> tf.keras.backend.clear_session() model.fit( X_train, y_train, epochs=<span class="synConstant">50</span>, batch_size=<span class="synConstant">32</span>, verbose=<span class="synConstant">1</span> ) <span class="synComment"># モデルの保存</span> model.save(<span class="synConstant">&quot;tf_model.h5&quot;</span>) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>Epoch 1/50 334/334 [==============================] - 0s 1ms/step - loss: 1.1319 Epoch 2/50 334/334 [==============================] - 0s 1ms/step - loss: 1.1315 ... Epoch 48/50 334/334 [==============================] - 0s 1ms/step - loss: 1.1124 Epoch 49/50 334/334 [==============================] - 1s 2ms/step - loss: 1.1121 Epoch 50/50 334/334 [==============================] - 0s 1ms/step - loss: 1.1118</pre> <h1 id="74-正解率の計測">74. 正解率の計測</h1> <p>推論結果に関しては、そのままだと各クラスの確率が返却されるので、<code>np.argmax</code>で一番確率の高いクラスを取得して正解率を計算しています.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">74. 正解率の計測</span> <span class="synConstant">問題73で求めた行列を用いて学習データおよび評価データの事例を分類したとき,その正解率をそれぞれ求めよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> accuracy_score <span class="synStatement">class</span> <span class="synIdentifier">SimpleNet</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, feature_dim, target_dim): self.input = tf.keras.layers.Input(shape=(feature_dim), name=<span class="synConstant">'input'</span>) self.output = tf.keras.layers.Dense(target_dim, activation=<span class="synConstant">'softmax'</span>, name=<span class="synConstant">'output'</span>) <span class="synStatement">def</span> <span class="synIdentifier">build</span>(self): input_layer = self.input output_layer = self.output(input_layer) model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer) <span class="synStatement">return</span> model <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> X_train = pd.read_pickle(<span class="synConstant">'X_train.pkl'</span>) y_train = pd.read_pickle(<span class="synConstant">'y_train.pkl'</span>) X_valid = pd.read_pickle(<span class="synConstant">'X_valid.pkl'</span>) y_valid = pd.read_pickle(<span class="synConstant">'y_valid.pkl'</span>) <span class="synComment"># モデルのロード</span> model = tf.keras.models.load_model(<span class="synConstant">&quot;tf_model.h5&quot;</span>) <span class="synComment"># 推論</span> y_train_preds = model.predict(X_train, verbose=<span class="synConstant">1</span>) y_valid_preds = model.predict(X_valid, verbose=<span class="synConstant">1</span>) <span class="synComment"># 一番確率の高いクラスを取得</span> y_train_preds = np.argmax(y_train_preds, <span class="synConstant">1</span>) y_valid_preds = np.argmax(y_valid_preds, <span class="synConstant">1</span>) <span class="synComment"># 正解率を出力</span> <span class="synIdentifier">print</span>(f<span class="synConstant">'Train Accuracy: {accuracy_score(y_train, y_train_preds)}'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'Valid Accuracy: {accuracy_score(y_valid, y_valid_preds)}'</span>) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>334/334 [==============================] - 0s 695us/step 42/42 [==============================] - 0s 680us/step Train Accuracy: 0.5493815592203898 Valid Accuracy: 0.5374812593703149</pre> <h1 id="75-損失と正解率のプロット--76-チェックポイント--77-ミニバッチ化">75. 損失と正解率のプロット / 76. チェックポイント / 77. ミニバッチ化</h1> <p>3つ一気に実装しています.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">75. 損失と正解率のプロット</span> <span class="synConstant">問題73のコードを改変し,各エポックのパラメータ更新が完了するたびに,訓練データでの損失,正解率,検証データでの損失,正解率をグラフにプロットし,学習の進捗状況を確認できるようにせよ.</span> <span class="synConstant">76. チェックポイント</span> <span class="synConstant">問題75のコードを改変し,各エポックのパラメータ更新が完了するたびに,チェックポイント(学習途中のパラメータ(重み行列など)の値や最適化アルゴリズムの内部状態)をファイルに書き出せ.</span> <span class="synConstant">77. ミニバッチ化</span> <span class="synConstant">問題76のコードを改変し,B事例ごとに損失・勾配を計算し,行列Wの値を更新せよ(ミニバッチ化).Bの値を1,2,4,8,…と変化させながら,1エポックの学習に要する時間を比較せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synStatement">class</span> <span class="synIdentifier">SimpleNet</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, feature_dim, target_dim): self.input = tf.keras.layers.Input(shape=(feature_dim), name=<span class="synConstant">'input'</span>) self.output = tf.keras.layers.Dense(target_dim, activation=<span class="synConstant">'softmax'</span>, name=<span class="synConstant">'output'</span>) <span class="synStatement">def</span> <span class="synIdentifier">build</span>(self): input_layer = self.input output_layer = self.output(input_layer) model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer) <span class="synStatement">return</span> model <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> X_train = pd.read_pickle(<span class="synConstant">'X_train.pkl'</span>) y_train = pd.read_pickle(<span class="synConstant">'y_train.pkl'</span>) <span class="synComment"># モデル構築</span> model = SimpleNet(X_train.shape[<span class="synConstant">1</span>], <span class="synIdentifier">len</span>(y_train.unique())).build() opt = tf.optimizers.SGD() model.compile( optimizer=opt, loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics=[<span class="synConstant">'accuracy'</span>] ) <span class="synComment"># チェックポイント</span> checkpoint_path = <span class="synConstant">'ck_tf_model.h5'</span> cb_checkpt = tf.keras.callbacks.ModelCheckpoint( checkpoint_path, monitor=<span class="synConstant">'loss'</span>, save_best_only=<span class="synIdentifier">True</span>, mode=<span class="synConstant">'min'</span>, verbose=<span class="synConstant">1</span> ) <span class="synComment"># 学習</span> tf.keras.backend.clear_session() history = model.fit( X_train, y_train, epochs=<span class="synConstant">100</span>, batch_size=<span class="synConstant">32</span>, callbacks=[cb_checkpt], verbose=<span class="synConstant">1</span> ) <span class="synComment"># 学習曲線の保存</span> pd.DataFrame(history.history).plot(figsize=(<span class="synConstant">10</span>, <span class="synConstant">6</span>)) plt.grid(<span class="synIdentifier">True</span>) plt.savefig(<span class="synConstant">&quot;learning_curves.png&quot;</span>) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>Epoch 1/100 334/334 [==============================] - 1s 918us/step - loss: 1.2599 - accuracy: 0.4254 Epoch 00001: loss improved from inf to 1.21570, saving model to ck_tf_model.h5 Epoch 2/100 334/334 [==============================] - 0s 968us/step - loss: 1.1667 - accuracy: 0.4256 Epoch 00002: loss improved from 1.21570 to 1.16557, saving model to ck_tf_model.h5 ... Epoch 00098: loss improved from 1.11229 to 1.11196, saving model to ck_tf_model.h5 Epoch 99/100 334/334 [==============================] - 0s 1ms/step - loss: 1.1190 - accuracy: 0.5538 Epoch 00099: loss improved from 1.11196 to 1.11144, saving model to ck_tf_model.h5 Epoch 100/100 334/334 [==============================] - 0s 1ms/step - loss: 1.1135 - accuracy: 0.5518 Epoch 00100: loss improved from 1.11144 to 1.11131, saving model to ck_tf_model.h5</pre> <p><figure class="figure-image figure-image-fotolife" title="学習曲線"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210703/20210703014541.png" alt="f:id:taxa_program:20210703014541p:plain" width="1000" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>学習曲線</figcaption></figure></p> <h1 id="79-多層ニューラルネットワーク">79. 多層ニューラルネットワーク</h1> <p>単層のときより、若干スコアが改善しました.</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">79. 多層ニューラルネットワーク</span> <span class="synConstant">問題78のコードを改変し,バイアス項の導入や多層化など,ニューラルネットワークの形状を変更しながら,高性能なカテゴリ分類器を構築せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> accuracy_score <span class="synStatement">class</span> <span class="synIdentifier">MLPNet</span>: <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, feature_dim, target_dim): self.input = tf.keras.layers.Input(shape=(feature_dim), name=<span class="synConstant">'input'</span>) self.hidden1 = tf.keras.layers.Dense(<span class="synConstant">128</span>, activation=<span class="synConstant">'relu'</span>, name=<span class="synConstant">'hidden1'</span>) self.hidden2 = tf.keras.layers.Dense(<span class="synConstant">32</span>, activation=<span class="synConstant">'relu'</span>, name=<span class="synConstant">'hidden2'</span>) self.dropout = tf.keras.layers.Dropout(<span class="synConstant">0.2</span>, name=<span class="synConstant">'dropout'</span>) self.output = tf.keras.layers.Dense(target_dim, activation=<span class="synConstant">'softmax'</span>, name=<span class="synConstant">'output'</span>) <span class="synStatement">def</span> <span class="synIdentifier">build</span>(self): input_layer = self.input hidden1 = self.hidden1(input_layer) dropout1 = self.dropout(hidden1) hidden2 = self.hidden2(dropout1) dropout2 = self.dropout(hidden2) output_layer = self.output(dropout2) model = tf.keras.models.Model(inputs=input_layer, outputs=output_layer) <span class="synStatement">return</span> model <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> X_train = pd.read_pickle(<span class="synConstant">'X_train.pkl'</span>) y_train = pd.read_pickle(<span class="synConstant">'y_train.pkl'</span>) X_valid = pd.read_pickle(<span class="synConstant">'X_valid.pkl'</span>) y_valid = pd.read_pickle(<span class="synConstant">'y_valid.pkl'</span>) <span class="synComment"># モデル構築</span> model = MLPNet(X_train.shape[<span class="synConstant">1</span>], <span class="synIdentifier">len</span>(y_train.unique())).build() opt = tf.optimizers.SGD() model.compile( optimizer=opt, loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics=[<span class="synConstant">'accuracy'</span>] ) <span class="synComment"># チェックポイント</span> checkpoint_path = <span class="synConstant">'ck_tf_model.h5'</span> cb_checkpt = tf.keras.callbacks.ModelCheckpoint( checkpoint_path, monitor=<span class="synConstant">'loss'</span>, save_best_only=<span class="synIdentifier">True</span>, mode=<span class="synConstant">'min'</span>, verbose=<span class="synConstant">1</span> ) <span class="synComment"># 学習</span> tf.keras.backend.clear_session() history = model.fit( X_train, y_train, epochs=<span class="synConstant">100</span>, batch_size=<span class="synConstant">32</span>, callbacks=[cb_checkpt], verbose=<span class="synConstant">1</span> ) <span class="synComment"># 推論</span> y_train_preds = model.predict(X_train, verbose=<span class="synConstant">1</span>) y_valid_preds = model.predict(X_valid, verbose=<span class="synConstant">1</span>) <span class="synComment"># 一番確率の高いクラスを取得</span> y_train_preds = np.argmax(y_train_preds, <span class="synConstant">1</span>) y_valid_preds = np.argmax(y_valid_preds, <span class="synConstant">1</span>) <span class="synComment"># 正解率を出力</span> <span class="synIdentifier">print</span>(f<span class="synConstant">'Train Accuracy: {accuracy_score(y_train, y_train_preds)}'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'Valid Accuracy: {accuracy_score(y_valid, y_valid_preds)}'</span>) <span class="synComment"># 学習曲線の保存</span> pd.DataFrame(history.history).plot(figsize=(<span class="synConstant">10</span>, <span class="synConstant">6</span>)) plt.grid(<span class="synIdentifier">True</span>) plt.savefig(<span class="synConstant">&quot;learning_curves.png&quot;</span>) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>... Train Accuracy: 0.5812406296851574 Valid Accuracy: 0.5704647676161919</pre> <p><figure class="figure-image figure-image-fotolife" title="学習曲線"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210703/20210703015418.png" alt="f:id:taxa_program:20210703015418p:plain" width="1000" height="600" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>学習曲線</figcaption></figure></p> taxa_program 【言語処理100本ノック 2020】 7章をPythonで解いた hatenablog://entry/26006613775487414 2021-06-20T19:16:06+09:00 2021-06-20T19:21:32+09:00 こんにちは。takapy(@takapy0210)です。 本エントリは言語処理100本ノック2020の7章を解いてみたので、それの備忘です。 簡単な解説をつけながら紹介していきます。 nlp100.github.io コードはGithubに置いてあります。 github.com 第7章: 機械学習 単語の意味を実ベクトルで表現する単語ベクトル(単語埋め込み)に関して,以下の処理を行うプログラムを作成せよ. 60. 単語ベクトルの読み込みと表示 61. 単語の類似度 62. 類似度の高い単語10件 63. 加法構成性によるアナロジー 64. アナロジーデータでの実験 65. アナロジータスクでの… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" width="1200" height="436" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック2020の7章を解いてみたので、それの備忘です。<br /> 簡単な解説をつけながら紹介していきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch07.html" title="第7章: 単語ベクトル" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch07.html">nlp100.github.io</a></cite></p> <p>コードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020">github.com</a></cite></p> <blockquote><p>第7章: 機械学習</p> <p>単語の意味を実ベクトルで表現する単語ベクトル(単語埋め込み)に関して,以下の処理を行うプログラムを作成せよ.</p></blockquote> <ul class="table-of-contents"> <li><a href="#60-単語ベクトルの読み込みと表示">60. 単語ベクトルの読み込みと表示</a></li> <li><a href="#61-単語の類似度">61. 単語の類似度</a></li> <li><a href="#62-類似度の高い単語10件">62. 類似度の高い単語10件</a></li> <li><a href="#63-加法構成性によるアナロジー">63. 加法構成性によるアナロジー</a></li> <li><a href="#64-アナロジーデータでの実験">64. アナロジーデータでの実験</a></li> <li><a href="#65-アナロジータスクでの正解率">65. アナロジータスクでの正解率</a></li> <li><a href="#66-WordSimilarity-353での評価">66. WordSimilarity-353での評価</a></li> <li><a href="#67-k-meansクラスタリング">67. k-meansクラスタリング</a></li> <li><a href="#68-Ward法によるクラスタリング">68. Ward法によるクラスタリング</a></li> <li><a href="#69-t-SNEによる可視化">69. t-SNEによる可視化</a></li> </ul> <h1 id="60-単語ベクトルの読み込みと表示">60. 単語ベクトルの読み込みと表示</h1> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">60. 単語ベクトルの読み込みと表示</span> <span class="synConstant">Google Newsデータセット(約1,000億単語)での学習済み単語ベクトル(300万単語・フレーズ,300次元)をダウンロードし,</span> <span class="synConstant">”United States”の単語ベクトルを表示せよ.ただし,”United States”は内部的には”United_States”と表現されていることに注意せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synIdentifier">print</span>(model[<span class="synConstant">'United_States'</span>]) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>[-3.61328125e-02 -4.83398438e-02 2.35351562e-01 1.74804688e-01 -1.46484375e-01 -7.42187500e-02 -1.01562500e-01 -7.71484375e-02 ...... -8.49609375e-02 1.57470703e-02 7.03125000e-02 1.62353516e-02 -2.27050781e-02 3.51562500e-02 2.47070312e-01 -2.67333984e-02]</pre> <h1 id="61-単語の類似度">61. 単語の類似度</h1> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">61. 単語の類似度</span> <span class="synConstant">“United States”と”U.S.”のコサイン類似度を計算せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synIdentifier">print</span>(model.similarity(<span class="synConstant">'United_States'</span>, <span class="synConstant">'U.S.'</span>)) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>0.73107743</pre> <h1 id="62-類似度の高い単語10件">62. 類似度の高い単語10件</h1> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">62. 類似度の高い単語10件</span> <span class="synConstant">“United States”とコサイン類似度が高い10語と,その類似度を出力せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synIdentifier">print</span>(model.most_similar(<span class="synConstant">'United_States'</span>, topn=<span class="synConstant">10</span>)) </pre> <p>most_similar関数で類似度が高いTopN個の単語を取得できます。<br /> 以下の結果を見る限り、typoが多いようです。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>[(&#39;Unites_States&#39;, 0.7877248525619507), (&#39;Untied_States&#39;, 0.7541370987892151), (&#39;United_Sates&#39;, 0.7400724291801453), (&#39;U.S.&#39;, 0.7310774326324463), (&#39;theUnited_States&#39;, 0.6404393911361694), (&#39;America&#39;, 0.6178410053253174), (&#39;UnitedStates&#39;, 0.6167312264442444), (&#39;Europe&#39;, 0.6132988929748535), (&#39;countries&#39;, 0.6044804453849792), (&#39;Canada&#39;, 0.601906955242157)]</pre> <h1 id="63-加法構成性によるアナロジー">63. 加法構成性によるアナロジー</h1> <p>実行結果を見ると、Greece(ギリシャ)がTOPにきており、直感的に良いベクトルが計算できていそうです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">63. 加法構成性によるアナロジー</span> <span class="synConstant">“Spain”の単語ベクトルから”Madrid”のベクトルを引き,”Athens”のベクトルを足したベクトルを計算し,</span> <span class="synConstant">そのベクトルと類似度の高い10語とその類似度を出力せよ</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synIdentifier">print</span>(model.most_similar(positive=[<span class="synConstant">'Spain'</span>, <span class="synConstant">'Athens'</span>], negative=[<span class="synConstant">'Madrid'</span>], topn=<span class="synConstant">10</span>)) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>[(&#39;Greece&#39;, 0.6898480653762817), (&#39;Aristeidis_Grigoriadis&#39;, 0.560684859752655), (&#39;Ioannis_Drymonakos&#39;, 0.5552908778190613), (&#39;Greeks&#39;, 0.545068621635437), (&#39;Ioannis_Christou&#39;, 0.5400862097740173), (&#39;Hrysopiyi_Devetzi&#39;, 0.5248445272445679), (&#39;Heraklio&#39;, 0.5207759737968445), (&#39;Athens_Greece&#39;, 0.516880989074707), (&#39;Lithuania&#39;, 0.5166865587234497), (&#39;Iraklion&#39;, 0.5146791338920593)]</pre> <p>1つ疑問に思ったこととして、ベクトルを別で計算してmost_similarで見てみると上記と結果が違いました。<br /> これはなぜだろう...</p> <pre class="code lang-python" data-lang="python" data-unlink>vec = model[<span class="synConstant">'Spain'</span>] - model[<span class="synConstant">'Madrid'</span>] + model[<span class="synConstant">'Athens'</span>] <span class="synIdentifier">print</span>(model.most_similar([vec], topn=<span class="synConstant">10</span>)) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>[(&#39;Athens&#39;, 0.7528455853462219), (&#39;Greece&#39;, 0.6685472130775452), (&#39;Aristeidis_Grigoriadis&#39;, 0.5495778322219849), (&#39;Ioannis_Drymonakos&#39;, 0.5361457467079163), (&#39;Greeks&#39;, 0.5351786017417908), (&#39;Ioannis_Christou&#39;, 0.5330225825309753), (&#39;Hrysopiyi_Devetzi&#39;, 0.5088489055633545), (&#39;Iraklion&#39;, 0.5059264302253723), (&#39;Greek&#39;, 0.5040615797042847), (&#39;Athens_Greece&#39;, 0.5034108757972717)]</pre> <h1 id="64-アナロジーデータでの実験">64. アナロジーデータでの実験</h1> <p>ここでダウンロードしたアナロジー評価データには、(Athens-Greece, Tokyo-Japan)のように、意味的アナロジーを評価するための組と、(walk-walks, write-writes)のように文法的アナロジーを評価する組が含まれます。</p> <p>txtファイルの中身をみると分かりますが、<code>gram</code>という単語が入っている行以降は文法的アナロジーを評価する組が含まれているデータになっています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">64. アナロジーデータでの実験</span> <span class="synConstant">単語アナロジーの評価データをダウンロードし,vec(2列目の単語) - vec(1列目の単語) + vec(3列目の単語)を計算し,</span> <span class="synConstant">そのベクトルと類似度が最も高い単語と,その類似度を求めよ.求めた単語と類似度は,各事例の末尾に追記せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">'./questions-words.txt'</span>, <span class="synConstant">'r'</span>) <span class="synStatement">as</span> f1, <span class="synIdentifier">open</span>(<span class="synConstant">'./questions-words-add.txt'</span>, <span class="synConstant">'w'</span>) <span class="synStatement">as</span> f2: <span class="synStatement">for</span> line <span class="synStatement">in</span> f1: <span class="synComment"># f1から1行ずつ読込み、求めた単語と類似度を追加してf2に書込む</span> line = line.split() <span class="synStatement">if</span> line[<span class="synConstant">0</span>] == <span class="synConstant">':'</span>: category = line[<span class="synConstant">1</span>] <span class="synStatement">else</span>: word, cos = model.most_similar(positive=[line[<span class="synConstant">1</span>], line[<span class="synConstant">2</span>]], negative=[line[<span class="synConstant">0</span>]], topn=<span class="synConstant">1</span>)[<span class="synConstant">0</span>] f2.write(<span class="synConstant">' '</span>.join([category] + line + [word, <span class="synIdentifier">str</span>(cos) + <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>])) </pre> <h1 id="65-アナロジータスクでの正解率">65. アナロジータスクでの正解率</h1> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">65. アナロジータスクでの正解率</span> <span class="synConstant">64の実行結果を用い,意味的アナロジー(semantic analogy)と文法的アナロジー(syntactic analogy)の正解率を測定せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">'./questions-words-add.txt'</span>, <span class="synConstant">'r'</span>) <span class="synStatement">as</span> f: sem_cnt = <span class="synConstant">0</span> sem_cor = <span class="synConstant">0</span> syn_cnt = <span class="synConstant">0</span> syn_cor = <span class="synConstant">0</span> <span class="synStatement">for</span> line <span class="synStatement">in</span> f: line = line.split() <span class="synStatement">if</span> <span class="synStatement">not</span> line[<span class="synConstant">0</span>].startswith(<span class="synConstant">'gram'</span>): sem_cnt += <span class="synConstant">1</span> <span class="synStatement">if</span> line[<span class="synConstant">4</span>] == line[<span class="synConstant">5</span>]: sem_cor += <span class="synConstant">1</span> <span class="synStatement">else</span>: syn_cnt += <span class="synConstant">1</span> <span class="synStatement">if</span> line[<span class="synConstant">4</span>] == line[<span class="synConstant">5</span>]: syn_cor += <span class="synConstant">1</span> <span class="synIdentifier">print</span>(f<span class="synConstant">'意味的アナロジー正解率: {sem_cor/sem_cnt:.3f}'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'文法的アナロジー正解率: {syn_cor/syn_cnt:.3f}'</span>) </pre> <h1 id="66-WordSimilarity-353での評価">66. WordSimilarity-353での評価</h1> <p>相関係数は約0.7くらいになりました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">66. WordSimilarity-353での評価</span> <span class="synConstant">The WordSimilarity-353 Test Collectionの評価データをダウンロードし,単語ベクトルにより計算される類似度のランキングと,</span> <span class="synConstant">人間の類似度判定のランキングの間のスピアマン相関係数を計算せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synPreProc">from</span> tqdm <span class="synPreProc">import</span> tqdm tqdm.pandas() <span class="synStatement">def</span> <span class="synIdentifier">cos_sim</span>(v1, v2): <span class="synStatement">return</span> np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)) <span class="synStatement">def</span> <span class="synIdentifier">calc_cos_sim</span>(row): w1v = model[row[<span class="synConstant">'Word 1'</span>]] w2v = model[row[<span class="synConstant">'Word 2'</span>]] <span class="synStatement">return</span> cos_sim(w1v, w2v) <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synStatement">global</span> model <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) combined_df = pd.read_csv(<span class="synConstant">'combined.csv'</span>) combined_df[<span class="synConstant">'cos_sim'</span>] = combined_df.progress_apply(calc_cos_sim, axis=<span class="synConstant">1</span>) spearman_corr = combined_df[[<span class="synConstant">'Human (mean)'</span>, <span class="synConstant">'cos_sim'</span>]].corr(method=<span class="synConstant">'spearman'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'spearman corr: {spearman_corr}'</span>) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>spearman corr: Human (mean) cos_sim Human (mean) 1.000000 0.700017 cos_sim 0.700017 1.000000</pre> <h1 id="67-k-meansクラスタリング">67. k-meansクラスタリング</h1> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">67. k-meansクラスタリング</span> <span class="synConstant">国名に関する単語ベクトルを抽出し,k-meansクラスタリングをクラスタ数k=5として実行せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> KMeans <span class="synPreProc">from</span> tqdm <span class="synPreProc">import</span> tqdm tqdm.pandas() <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://www.worldometers.info/geography/alphabetical-list-of-countries/</span> countries_df = pd.read_csv(<span class="synConstant">'countries.tsv'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synComment"># モデルに含まれる国だけを抽出</span> conclusion_model_countries = [country <span class="synStatement">for</span> country <span class="synStatement">in</span> countries_df[<span class="synConstant">'Country'</span>].tolist() <span class="synStatement">if</span> country <span class="synStatement">in</span> model] countries_df = countries_df[countries_df[<span class="synConstant">'Country'</span>].isin(conclusion_model_countries)].reset_index(drop=<span class="synIdentifier">True</span>) <span class="synComment"># 国ベクトルの取得</span> countries_vec = [model[country] <span class="synStatement">for</span> country <span class="synStatement">in</span> countries_df[<span class="synConstant">'Country'</span>].tolist()] <span class="synComment"># k-meansクラスタリング</span> n = <span class="synConstant">5</span> kmeans = KMeans(n_clusters=n, random_state=<span class="synConstant">42</span>) kmeans.fit(countries_vec) <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n): cluster = np.where(kmeans.labels_ == i)[<span class="synConstant">0</span>] <span class="synIdentifier">print</span>(f<span class="synConstant">'cluster: {i}'</span>) <span class="synIdentifier">print</span>(countries_df.iloc[cluster][<span class="synConstant">&quot;Country&quot;</span>].tolist()) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>cluster: 0 [&#39;Algeria&#39;, &#39;Angola&#39;, &#39;Benin&#39;, &#39;Botswana&#39;, &#39;Burundi&#39;, &#39;Cameroon&#39;, &#39;Chad&#39;, &#39;Comoros&#39;, &#39;Djibouti&#39;, &#39;Egypt&#39;, &#39;Eritrea&#39;, &#39;Ethiopia&#39;, &#39;Gabon&#39;, &#39;Gambia&#39;, &#39;Ghana&#39;, &#39;Guinea&#39;, &#39;Kenya&#39;, &#39;Lesotho&#39;, &#39;Liberia&#39;, &#39;Libya&#39;, &#39;Madagascar&#39;, &#39;Malawi&#39;, &#39;Mali&#39;, &#39;Mauritania&#39;, &#39;Morocco&#39;, &#39;Mozambique&#39;, &#39;Namibia&#39;, &#39;Niger&#39;, &#39;Nigeria&#39;, &#39;Rwanda&#39;, &#39;Senegal&#39;, &#39;Somalia&#39;, &#39;Sudan&#39;, &#39;Tanzania&#39;, &#39;Togo&#39;, &#39;Tunisia&#39;, &#39;Uganda&#39;, &#39;Yemen&#39;, &#39;Zambia&#39;, &#39;Zimbabwe&#39;] cluster: 1 [&#39;Australia&#39;, &#39;Bahamas&#39;, &#39;Bahrain&#39;, &#39;Bangladesh&#39;, &#39;Barbados&#39;, &#39;Belize&#39;, &#39;Bhutan&#39;, &#39;Brunei&#39;, &#39;Cambodia&#39;, &#39;Dominica&#39;, &#39;Fiji&#39;, &#39;Grenada&#39;, &#39;Guyana&#39;, &#39;Indonesia&#39;, &#39;Jamaica&#39;, &#39;Kiribati&#39;, &#39;Laos&#39;, &#39;Malaysia&#39;, &#39;Maldives&#39;, &#39;Mauritius&#39;, &#39;Micronesia&#39;, &#39;Nauru&#39;, &#39;Nepal&#39;, &#39;Oman&#39;, &#39;Palau&#39;, &#39;Philippines&#39;, &#39;Qatar&#39;, &#39;Samoa&#39;, &#39;Seychelles&#39;, &#39;Singapore&#39;, &#39;Suriname&#39;, &#39;Thailand&#39;, &#39;Tonga&#39;, &#39;Tuvalu&#39;, &#39;Vanuatu&#39;] cluster: 2 [&#39;Afghanistan&#39;, &#39;Argentina&#39;, &#39;Bolivia&#39;, &#39;Brazil&#39;, &#39;Canada&#39;, &#39;Chile&#39;, &#39;China&#39;, &#39;Colombia&#39;, &#39;Cuba&#39;, &#39;Ecuador&#39;, &#39;Guatemala&#39;, &#39;Haiti&#39;, &#39;Honduras&#39;, &#39;India&#39;, &#39;Iraq&#39;, &#39;Japan&#39;, &#39;Jordan&#39;, &#39;Kuwait&#39;, &#39;Lebanon&#39;, &#39;Mexico&#39;, &#39;Mongolia&#39;, &#39;Nicaragua&#39;, &#39;Pakistan&#39;, &#39;Panama&#39;, &#39;Paraguay&#39;, &#39;Peru&#39;, &#39;Uruguay&#39;, &#39;Venezuela&#39;, &#39;Vietnam&#39;] cluster: 3 [&#39;Armenia&#39;, &#39;Azerbaijan&#39;, &#39;Belarus&#39;, &#39;Georgia&#39;, &#39;Iran&#39;, &#39;Israel&#39;, &#39;Kazakhstan&#39;, &#39;Kyrgyzstan&#39;, &#39;Moldova&#39;, &#39;Russia&#39;, &#39;Syria&#39;, &#39;Tajikistan&#39;, &#39;Turkey&#39;, &#39;Turkmenistan&#39;, &#39;Ukraine&#39;, &#39;Uzbekistan&#39;] cluster: 4 [&#39;Albania&#39;, &#39;Andorra&#39;, &#39;Austria&#39;, &#39;Belgium&#39;, &#39;Bulgaria&#39;, &#39;Croatia&#39;, &#39;Cyprus&#39;, &#39;Denmark&#39;, &#39;Estonia&#39;, &#39;Finland&#39;, &#39;France&#39;, &#39;Germany&#39;, &#39;Greece&#39;, &#39;Hungary&#39;, &#39;Iceland&#39;, &#39;Ireland&#39;, &#39;Italy&#39;, &#39;Latvia&#39;, &#39;Liechtenstein&#39;, &#39;Lithuania&#39;, &#39;Luxembourg&#39;, &#39;Malta&#39;, &#39;Monaco&#39;, &#39;Montenegro&#39;, &#39;Netherlands&#39;, &#39;Norway&#39;, &#39;Poland&#39;, &#39;Portugal&#39;, &#39;Romania&#39;, &#39;Serbia&#39;, &#39;Slovakia&#39;, &#39;Slovenia&#39;, &#39;Spain&#39;, &#39;Sweden&#39;, &#39;Switzerland&#39;]</pre> <h1 id="68-Ward法によるクラスタリング">68. Ward法によるクラスタリング</h1> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">68. Ward法によるクラスタリング</span> <span class="synConstant">国名に関する単語ベクトルに対し,Ward法による階層型クラスタリングを実行せよ.さらに,クラスタリング結果をデンドログラムとして可視化せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> KMeans <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> scipy.cluster.hierarchy <span class="synPreProc">import</span> dendrogram, linkage <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://www.worldometers.info/geography/alphabetical-list-of-countries/</span> countries_df = pd.read_csv(<span class="synConstant">'countries.tsv'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synComment"># モデルに含まれる国だけを抽出(195ヵ国→155ヵ国になる)</span> conclusion_model_countries = [country <span class="synStatement">for</span> country <span class="synStatement">in</span> countries_df[<span class="synConstant">'Country'</span>].tolist() <span class="synStatement">if</span> country <span class="synStatement">in</span> model] countries_df = countries_df[countries_df[<span class="synConstant">'Country'</span>].isin(conclusion_model_countries)].reset_index(drop=<span class="synIdentifier">True</span>) <span class="synComment"># 国ベクトルの取得</span> countries_vec = [model[country] <span class="synStatement">for</span> country <span class="synStatement">in</span> countries_df[<span class="synConstant">'Country'</span>].tolist()] <span class="synComment"># Ward法によるクラスタリング</span> Z = linkage(countries_vec, method=<span class="synConstant">'ward'</span>) dendrogram(Z, labels=countries_df[<span class="synConstant">'Country'</span>].tolist()) plt.figure(figsize=(<span class="synConstant">15</span>, <span class="synConstant">5</span>)) plt.show() </pre> <p>実行結果 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210620/20210620182717.png" alt="f:id:taxa_program:20210620182717p:plain" width="1200" height="671" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h1 id="69-t-SNEによる可視化">69. t-SNEによる可視化</h1> <p>通常の可視化と、adjust_text<a href="#f-89f9fcd0" name="fn-89f9fcd0" title="https://adjusttext.readthedocs.io/en/latest/Examples.html">*1</a>を用いてちょっと見やすくした可視化を比較してみました。<br /> 最後に前項で行ったクラスタ情報で色分けもしています。それなりに良い圧縮ができていそうです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">69. t-SNEによる可視化</span> <span class="synConstant">ベクトル空間上の国名に関する単語ベクトルをt-SNEで可視化せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> gensim.models <span class="synPreProc">import</span> KeyedVectors <span class="synPreProc">from</span> sklearn.cluster <span class="synPreProc">import</span> KMeans <span class="synPreProc">from</span> sklearn.manifold <span class="synPreProc">import</span> TSNE <span class="synPreProc">from</span> matplotlib <span class="synPreProc">import</span> pyplot <span class="synStatement">as</span> plt <span class="synPreProc">from</span> adjustText <span class="synPreProc">import</span> adjust_text <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># ref. https://www.worldometers.info/geography/alphabetical-list-of-countries/</span> countries_df = pd.read_csv(<span class="synConstant">'countries.tsv'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synComment"># ref. https://radimrehurek.com/gensim/models/word2vec.html#usage-examples</span> model = KeyedVectors.load_word2vec_format(<span class="synConstant">'./GoogleNews-vectors-negative300.bin.gz'</span>, binary=<span class="synIdentifier">True</span>) <span class="synComment"># モデルに含まれる国だけを抽出(195ヵ国→155ヵ国になる)</span> conclusion_model_countries = [country <span class="synStatement">for</span> country <span class="synStatement">in</span> countries_df[<span class="synConstant">'Country'</span>].tolist() <span class="synStatement">if</span> country <span class="synStatement">in</span> model] countries_df = countries_df[countries_df[<span class="synConstant">'Country'</span>].isin(conclusion_model_countries)].reset_index(drop=<span class="synIdentifier">True</span>) <span class="synComment"># 国ベクトルの取得</span> countries_vec = [model[country] <span class="synStatement">for</span> country <span class="synStatement">in</span> countries_df[<span class="synConstant">'Country'</span>].tolist()] <span class="synComment"># 圧縮</span> tsne = TSNE(random_state=<span class="synConstant">42</span>, n_iter=<span class="synConstant">15000</span>, metric=<span class="synConstant">'cosine'</span>) embs = tsne.fit_transform(countries_vec) <span class="synComment"># プロット</span> plt.figure(figsize=(<span class="synConstant">10</span>, <span class="synConstant">10</span>)) plt.scatter(np.array(embs).T[<span class="synConstant">0</span>], np.array(embs).T[<span class="synConstant">1</span>]) <span class="synStatement">for</span> (x, y), name <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(embs, countries_df[<span class="synConstant">'Country'</span>].tolist()): plt.annotate(name, (x, y)) plt.show() <span class="synComment"># adjust_textを用いてちょっとみやすくプロット</span> texts = [] fig, ax = plt.subplots(figsize=(<span class="synConstant">10</span>, <span class="synConstant">10</span>)) <span class="synStatement">for</span> x, y, name <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(np.array(embs).T[<span class="synConstant">0</span>], np.array(embs).T[<span class="synConstant">1</span>], countries_df[<span class="synConstant">'Country'</span>].tolist()): ax.plot(x, y, marker=<span class="synConstant">'o'</span>, linestyle=<span class="synConstant">''</span>, ms=<span class="synConstant">10</span>, color=<span class="synConstant">'blue'</span>) plt_text = ax.annotate(name, (x, y), fontsize=<span class="synConstant">10</span>, color=<span class="synConstant">'black'</span>) texts.append(plt_text) adjust_text(texts, arrowprops=<span class="synIdentifier">dict</span>(arrowstyle=<span class="synConstant">'-&gt;'</span>, color=<span class="synConstant">'red'</span>)) plt.show() <span class="synComment"># クラスタごとに色分けして出力</span> n = <span class="synConstant">5</span> kmeans = KMeans(n_clusters=n, random_state=<span class="synConstant">42</span>) kmeans.fit(countries_vec) countries_df.loc[:, <span class="synConstant">'cluster'</span>] = kmeans.labels_ texts = [] fig, ax = plt.subplots(figsize=(<span class="synConstant">10</span>, <span class="synConstant">10</span>)) <span class="synStatement">for</span> x, y, name, cluster <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(np.array(embs).T[<span class="synConstant">0</span>], np.array(embs).T[<span class="synConstant">1</span>], countries_df[<span class="synConstant">'Country'</span>].tolist(), countries_df[<span class="synConstant">'cluster'</span>].tolist()): <span class="synStatement">if</span> cluster == <span class="synConstant">0</span>: ax.plot(x, y, marker=<span class="synConstant">'o'</span>, linestyle=<span class="synConstant">''</span>, ms=<span class="synConstant">10</span>, color=<span class="synConstant">'g'</span>) plt_text = ax.annotate(name, (x, y), fontsize=<span class="synConstant">10</span>, color=<span class="synConstant">'g'</span>) <span class="synStatement">elif</span> cluster == <span class="synConstant">1</span>: ax.plot(x, y, marker=<span class="synConstant">'o'</span>, linestyle=<span class="synConstant">''</span>, ms=<span class="synConstant">10</span>, color=<span class="synConstant">'b'</span>) plt_text = ax.annotate(name, (x, y), fontsize=<span class="synConstant">10</span>, color=<span class="synConstant">'b'</span>) <span class="synStatement">elif</span> cluster == <span class="synConstant">2</span>: ax.plot(x, y, marker=<span class="synConstant">'o'</span>, linestyle=<span class="synConstant">''</span>, ms=<span class="synConstant">10</span>, color=<span class="synConstant">'m'</span>) plt_text = ax.annotate(name, (x, y), fontsize=<span class="synConstant">10</span>, color=<span class="synConstant">'m'</span>) <span class="synStatement">elif</span> cluster == <span class="synConstant">3</span>: ax.plot(x, y, marker=<span class="synConstant">'o'</span>, linestyle=<span class="synConstant">''</span>, ms=<span class="synConstant">10</span>, color=<span class="synConstant">'c'</span>) plt_text = ax.annotate(name, (x, y), fontsize=<span class="synConstant">10</span>, color=<span class="synConstant">'c'</span>) <span class="synStatement">else</span>: ax.plot(x, y, marker=<span class="synConstant">'o'</span>, linestyle=<span class="synConstant">''</span>, ms=<span class="synConstant">10</span>, color=<span class="synConstant">'y'</span>) plt_text = ax.annotate(name, (x, y), fontsize=<span class="synConstant">10</span>, color=<span class="synConstant">'y'</span>) texts.append(plt_text) adjust_text(texts, arrowprops=<span class="synIdentifier">dict</span>(arrowstyle=<span class="synConstant">'-&gt;'</span>, color=<span class="synConstant">'r'</span>)) plt.show() </pre> <p>実行結果 <figure class="figure-image figure-image-fotolife" title="左:通常のプロット / 右:adjust_textを用いたプロット"><div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210620/20210620190916.png" alt="f:id:taxa_program:20210620190916p:plain" width="629" height="583" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210620/20210620190956.png" alt="f:id:taxa_program:20210620190956p:plain" width="606" height="576" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></div><figcaption>左:通常のプロット / 右:adjust_textを用いたプロット</figcaption></figure></p> <p>青色とピンク色のクラスタが若干ばらついていますが、それ以外は良さそうです。</p> <p><figure class="figure-image figure-image-fotolife" title="クラスタ情報も付与したプロット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210620/20210620191051.png" alt="f:id:taxa_program:20210620191051p:plain" width="605" height="579" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>クラスタ情報も付与したプロット</figcaption></figure></p> <div class="footnote"> <p class="footnote"><a href="#fn-89f9fcd0" name="f-89f9fcd0" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://adjusttext.readthedocs.io/en/latest/Examples.html">https://adjusttext.readthedocs.io/en/latest/Examples.html</a></span></p> </div> taxa_program 【言語処理100本ノック 2020】 6章をPythonで解いた hatenablog://entry/26006613772423272 2021-06-06T09:55:12+09:00 2021-06-06T09:58:53+09:00 こんにちは。takapy(@takapy0210)です。 本エントリは言語処理100本ノック2020の6章を解いてみたので、それの備忘です。 途中まで解いて放置していました()が、続きをやる機会を得たので簡単な解説をつけながら紹介していきます。 nlp100.github.io 例によってコードはGithubに置いてあります。 github.com 第6章: 機械学習 50. データの入手・整形 51. 特徴量抽出 52. 学習 53. 予測 54. 正解率の計測 55. 混同行列の作成 56. 適合率,再現率,F1スコアの計測 57. 特徴量の重みの確認 58. 正則化パラメータの変更 59… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック2020の6章を解いてみたので、それの備忘です。<br /> 途中まで解いて放置していました()が、続きをやる機会を得たので簡単な解説をつけながら紹介していきます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch06.html" title="第6章: 機械学習" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch06.html">nlp100.github.io</a></cite></p> <p>例によってコードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020">github.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#第6章-機械学習">第6章: 機械学習</a><ul> <li><a href="#50-データの入手整形">50. データの入手・整形</a></li> <li><a href="#51-特徴量抽出">51. 特徴量抽出</a></li> <li><a href="#52-学習">52. 学習</a></li> <li><a href="#53-予測">53. 予測</a></li> <li><a href="#54-正解率の計測">54. 正解率の計測</a></li> <li><a href="#55-混同行列の作成">55. 混同行列の作成</a></li> <li><a href="#56-適合率再現率F1スコアの計測">56. 適合率,再現率,F1スコアの計測</a></li> <li><a href="#57-特徴量の重みの確認">57. 特徴量の重みの確認</a></li> <li><a href="#58-正則化パラメータの変更">58. 正則化パラメータの変更</a></li> <li><a href="#59-ハイパーパラメータの探索">59. ハイパーパラメータの探索</a></li> </ul> </li> </ul> <h1 id="第6章-機械学習">第6章: 機械学習</h1> <blockquote><p>本章では,Fabio Gasparetti氏が公開しているNews Aggregator Data Setを用い,ニュース記事の見出しを「ビジネス」「科学技術」「エンターテイメント」「健康」のカテゴリに分類するタスク(カテゴリ分類)に取り組む.</p></blockquote> <h2 id="50-データの入手整形">50. データの入手・整形</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">News Aggregator Data Setをダウンロードし、以下の要領で学習データ(train.txt),検証データ(valid.txt),評価データ(test.txt)を作成せよ.</span> <span class="synConstant">1. ダウンロードしたzipファイルを解凍し,readme.txtの説明を読む.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> zipfile <span class="synStatement">with</span> zipfile.ZipFile(<span class="synConstant">'NewsAggregatorDataset.zip'</span>) <span class="synStatement">as</span> existing_zip: existing_zip.extractall() </pre> <p>zipfileを用いて解凍します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">News Aggregator Data Setをダウンロードし、以下の要領で学習データ(train.txt),検証データ(valid.txt),評価データ(test.txt)を作成せよ.</span> <span class="synConstant">2. 情報源(publisher)が”Reuters”, “Huffington Post”, “Businessweek”, “Contactmusic.com”, “Daily Mail”の事例(記事)のみを抽出する.</span> <span class="synConstant">3. 抽出された事例をランダムに並び替える.</span> <span class="synConstant">4. 抽出された事例の80%を学習データ,残りの10%ずつを検証データと評価データに分割し,それぞれtrain.txt,valid.txt,test.txtというファイル名で保存する.</span> <span class="synConstant"> ファイルには,1行に1事例を書き出すこととし,カテゴリ名と記事見出しのタブ区切り形式とせよ(このファイルは後に問題70で再利用する).</span> <span class="synConstant">学習データと評価データを作成したら,各カテゴリの事例数を確認せよ</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synComment"># 2.</span> df = pd.read_csv(<span class="synConstant">'newsCorpora.csv'</span>, header=<span class="synIdentifier">None</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, names=[<span class="synConstant">'id'</span>, <span class="synConstant">'title'</span>, <span class="synConstant">'url'</span>, <span class="synConstant">'publisher'</span>, <span class="synConstant">'category'</span>, <span class="synConstant">'story'</span>, <span class="synConstant">'hostname'</span>, <span class="synConstant">'timestamp'</span>]) cols = [<span class="synConstant">'Reuters'</span>, <span class="synConstant">'Huffington Post'</span>, <span class="synConstant">'Businessweek'</span>, <span class="synConstant">'Contactmusic.com'</span>, <span class="synConstant">'Daily Mail'</span>] df = df[df[<span class="synConstant">'publisher'</span>].isin(cols)] <span class="synComment"># 3.</span> df = df.sample(frac=<span class="synConstant">1</span>, random_state=<span class="synConstant">42</span>).reset_index(drop=<span class="synIdentifier">True</span>) <span class="synIdentifier">print</span>(df.head()) <span class="synComment"># 4.</span> <span class="synComment"># カテゴリに分類するタスク(カテゴリ分類)に取り組む.とあるので、カテゴリで層化抽出する.</span> train, test = train_test_split(df, test_size=<span class="synConstant">0.2</span>, random_state=<span class="synConstant">42</span>, stratify=df[<span class="synConstant">'category'</span>]) valid, test = train_test_split(test, test_size=<span class="synConstant">0.5</span>, random_state=<span class="synConstant">42</span>, stratify=test[<span class="synConstant">'category'</span>]) <span class="synComment"># データの保存</span> train.to_csv(<span class="synConstant">'train.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>) valid.to_csv(<span class="synConstant">'valid.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>) test.to_csv(<span class="synConstant">'test.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>) <span class="synIdentifier">print</span>(<span class="synConstant">'train ---- '</span>, train.shape) <span class="synIdentifier">print</span>(train[<span class="synConstant">'category'</span>].value_counts()) <span class="synIdentifier">print</span>(<span class="synConstant">'valid ---- '</span>, valid.shape) <span class="synIdentifier">print</span>(valid[<span class="synConstant">'category'</span>].value_counts()) <span class="synIdentifier">print</span>(<span class="synConstant">'test ----'</span>, test.shape) <span class="synIdentifier">print</span>(test[<span class="synConstant">'category'</span>].value_counts()) </pre> <p>カテゴリを予測するモデルを生成するので、train_test_splitのstratifyにカテゴリを指定して分割しています。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink> id title ... hostname timestamp 0 173934 Taco Bell reveals &#39;secret&#39; ingredients of myst... ... www.dailymail.co.uk 1398870059991 1 41713 RPT-UPDATE 2-Carlyle hires JPMorgan&#39;s Cavanagh... ... www.reuters.com 1395771699595 2 322477 Argentina Deposits $1 Billion For June 30 Bond... ... www.businessweek.com 1403853546347 3 114448 Banksy art work showing government agents spyi... ... www.dailymail.co.uk 1397518333460 4 178913 An acrobatic stunt went horribly wrong on Sund... ... www.dailymail.co.uk 1399320829491 [5 rows x 8 columns] train ---- (10672, 8) b 4502 e 4223 t 1219 m 728 Name: category, dtype: int64 valid ---- (1334, 8) b 562 e 528 t 153 m 91 Name: category, dtype: int64 test ---- (1334, 8) b 563 e 528 t 152 m 91 Name: category, dtype: int64</pre> <h2 id="51-特徴量抽出">51. 特徴量抽出</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">51. 特徴量抽出</span> <span class="synConstant">学習データ,検証データ,評価データから特徴量を抽出し,それぞれtrain.feature.txt,valid.feature.txt,test.feature.txtというファイル名で保存せよ. </span> <span class="synConstant">なお,カテゴリ分類に有用そうな特徴量は各自で自由に設計せよ.記事の見出しを単語列に変換したものが最低限のベースラインとなるであろう.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synPreProc">from</span> sklearn.feature_extraction.text <span class="synPreProc">import</span> TfidfVectorizer <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'valid'</span>: <span class="synConstant">'valid.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'test.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synComment"># データチェック</span> <span class="synStatement">for</span> k <span class="synStatement">in</span> inputs.keys(): <span class="synIdentifier">print</span>(k, <span class="synConstant">'---'</span>, dfs[k].shape) <span class="synIdentifier">print</span>(dfs[k].head()) <span class="synStatement">return</span> dfs <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(text) -&gt; <span class="synIdentifier">str</span>: <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">class</span> <span class="synIdentifier">FeatureExtraction</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, min_df=<span class="synConstant">1</span>, max_df=<span class="synConstant">1</span>) -&gt; <span class="synIdentifier">None</span>: self.tfidf_vec = TfidfVectorizer(min_df=min_df, max_df=max_df, ngram_range=(<span class="synConstant">1</span>, <span class="synConstant">2</span>)) <span class="synStatement">def</span> <span class="synIdentifier">fit</span>(self, input_text) -&gt; <span class="synIdentifier">None</span>: self.tfidf_vec.fit(input_text) <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text) -&gt; pd.DataFrame: tfidf_vec = self.tfidf_vec.transform(input_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: dfs = load_data() <span class="synComment"># trainとtestを生成</span> train = pd.concat([dfs[<span class="synConstant">'train'</span>], dfs[<span class="synConstant">'valid'</span>]], axis=<span class="synConstant">0</span>).reset_index(drop=<span class="synIdentifier">True</span>) test = dfs[<span class="synConstant">'test'</span>] <span class="synComment"># 前処理</span> train[<span class="synConstant">'clean_title'</span>] = train[[<span class="synConstant">'title'</span>]].apply(preprocess) test[<span class="synConstant">'clean_title'</span>] = test[[<span class="synConstant">'title'</span>]].apply(preprocess) <span class="synComment"># 特徴量抽出</span> feat = FeatureExtraction(min_df=<span class="synConstant">10</span>, max_df=<span class="synConstant">0.1</span>) feat.fit(train[<span class="synConstant">'clean_title'</span>]) X_train = feat.transform(train[<span class="synConstant">'clean_title'</span>]) X_test = feat.transform(test[<span class="synConstant">'clean_title'</span>]) pickle.dump(feat.tfidf_vec, <span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'wb'</span>)) <span class="synComment"># 推論時にも使用するため、保存</span> <span class="synComment"># DFに変換</span> X_train = pd.DataFrame(X_train.toarray(), columns=feat.tfidf_vec.get_feature_names()) X_test = pd.DataFrame(X_test.toarray(), columns=feat.tfidf_vec.get_feature_names()) <span class="synComment"># 分割して保存</span> X_valid = X_train[<span class="synIdentifier">len</span>(dfs[<span class="synConstant">'train'</span>]):].reset_index(drop=<span class="synIdentifier">True</span>) X_train = X_train[:<span class="synIdentifier">len</span>(dfs[<span class="synConstant">'train'</span>])].reset_index(drop=<span class="synIdentifier">True</span>) X_train.to_csv(<span class="synConstant">'X_train.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>) X_valid.to_csv(<span class="synConstant">'X_valid.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>) X_test.to_csv(<span class="synConstant">'X_test.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>) <span class="synIdentifier">print</span>(<span class="synConstant">'X_train ---- '</span>, X_train.shape) <span class="synIdentifier">print</span>(<span class="synConstant">'X_valid ---- '</span>, X_valid.shape) <span class="synIdentifier">print</span>(<span class="synConstant">'X_test ---- '</span>, X_test.shape) </pre> <p>texthere<a href="#f-2c5f0b0b" name="fn-2c5f0b0b" title="https://texthero.org/">*1</a>を用いてテキストの前処理を行っています。</p> <p>特徴量抽出はシンプルにTFIDFを使いました。<br /> <code>FeatureExtraction</code>クラスを生成して、<code>fit()</code>と<code>trainform()</code>を分けることで、検証データに含まれるテキストは含まれないようにしています。<br /> また、TfidfVectorizerのオブジェクトは以降のコードで使用するため、pkl形式で出力しています。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>train --- (10672, 8) id title ... hostname timestamp 0 104130 UPDATE 1-Outkast goes back to 1990s hip hop at... ... www.reuters.com 1397300285495 1 353755 China&#39;s Stocks Head for Weekly Gain on Economi... ... www.businessweek.com 1404455149471 2 10240 Rare Diamond Shows Earth&#39;s Interior Is All Wet... ... www.huffingtonpost.com 1394715425065 3 208228 China Credit Gauge Declines as Officials Seek ... ... www.businessweek.com 1399970872744 4 288066 Angelina Jolie, Daniel Day Lewis &amp; Dame Maggie... ... www.contactmusic.com 1402817031792 [5 rows x 8 columns] valid --- (1334, 8) id title ... hostname timestamp 0 230506 PRECIOUS-Gold ends flat as S&amp;P 500 rises; plat... ... in.reuters.com 1400683341700 1 8444 CORRECTED-China Premier Li calls for relevant ... ... in.reuters.com 1394707522801 2 132330 Asia stocks subdued, Nikkei weak on profit taking ... www.businessweek.com 1397825631720 3 74570 Is &#39;How I Met Your Mother&#39; The Best Ensemble C... ... www.contactmusic.com 1396349515070 4 56261 Russia says Ukrainian troops loyal to Kiev hav... ... www.reuters.com 1396011284905 [5 rows x 8 columns] test --- (1334, 8) id title ... hostname timestamp 0 306565 T-Mobile Just Did What Amazon&#39;s Fire Phone Cou... ... www.businessweek.com 1403198584880 1 228972 Seth McFarlane takes aim at western genre ... www.dailymail.co.uk 1400653319735 2 62224 Home &gt; Kim Kardashian &gt; Kim Kardashian To Try ... ... www.contactmusic.com 1396074886259 3 322698 GoPro&#39;s IPO priced at $24 per share: underwriter ... www.reuters.com 1403854662724 4 76854 Facebook&#39;s Mark Zuckerberg earned $3.3billion ... ... www.dailymail.co.uk 1396367931628 X_train ---- (10672, 2364) X_valid ---- (1334, 2364) X_test ---- (1334, 2364)</pre> <h2 id="52-学習">52. 学習</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">52. 学習</span> <span class="synConstant">51で構築した学習データを用いて,ロジスティック回帰モデルを学習せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> pickle <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'X_train'</span>: <span class="synConstant">'X_train.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synStatement">assert</span> dfs[<span class="synConstant">'train'</span>].shape[<span class="synConstant">0</span>] == dfs[<span class="synConstant">'X_train'</span>].shape[<span class="synConstant">0</span>], <span class="synConstant">'長さが不正です'</span> <span class="synComment"># モデルの学習</span> lg = LogisticRegression(random_state=<span class="synConstant">42</span>, max_iter=<span class="synConstant">10000</span>) lg.fit(dfs[<span class="synConstant">'X_train'</span>], dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>]) <span class="synComment"># モデルの保存</span> pickle.dump(lg, <span class="synIdentifier">open</span>(<span class="synConstant">'logreg.pkl'</span>, <span class="synConstant">'wb'</span>)) </pre> <p>ここは特に工夫点はないです。素直に学習させています。</p> <h2 id="53-予測">53. 予測</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">53. 予測</span> <span class="synConstant">52で学習したロジスティック回帰モデルを用い,与えられた記事見出しからカテゴリとその予測確率を計算するプログラムを実装せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synStatement">class</span> <span class="synIdentifier">PredictAPI</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self): self.tfidf = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'rb'</span>)) self.logreg = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'logreg.pkl'</span>, <span class="synConstant">'rb'</span>)) <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(self, input_text): <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(input_text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text): clean_text = self.preprocess(input_text) tfidf_vec = self.tfidf.transform(clean_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, input_text): tfidf_vec = self.transform(input_text) <span class="synComment"># 推論</span> predict = [np.max(self.logreg.predict_proba(tfidf_vec), axis=<span class="synConstant">1</span>), self.logreg.predict(tfidf_vec)] <span class="synStatement">return</span> predict <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synComment"># テキストを与えるとそのカテゴリを予測できるようにする</span> api = PredictAPI() pred = api.predict(dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'title'</span>]) dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'pred_proba'</span>] = pred[<span class="synConstant">0</span>] dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'pred'</span>] = pred[<span class="synConstant">1</span>] <span class="synIdentifier">print</span>(dfs[<span class="synConstant">'train'</span>][[<span class="synConstant">'title'</span>, <span class="synConstant">'category'</span>, <span class="synConstant">'pred_proba'</span>, <span class="synConstant">'pred'</span>]].head()) </pre> <p>ここは実際のAPIをイメージして、<strong>生のテキストデータをapiに渡して推論できるようにPredictAPIクラスを生成しています。</strong><br /> (※以降のコードにも<code>PredictAPI</code>が出現します)</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink> title category pred_proba pred 0 UPDATE 1-Outkast goes back to 1990s hip hop at... e 0.881668 e 1 China&#39;s Stocks Head for Weekly Gain on Economi... b 0.982703 b 2 Rare Diamond Shows Earth&#39;s Interior Is All Wet... t 0.729923 t 3 China Credit Gauge Declines as Officials Seek ... b 0.944189 b 4 Angelina Jolie, Daniel Day Lewis &amp; Dame Maggie... e 0.942834 e</pre> <h2 id="54-正解率の計測">54. 正解率の計測</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">54. 正解率の計測Permalink</span> <span class="synConstant">52で学習したロジスティック回帰モデルの正解率を,学習データおよび評価データ上で計測せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synStatement">class</span> <span class="synIdentifier">PredictAPI</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self): self.tfidf = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'rb'</span>)) self.logreg = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'logreg.pkl'</span>, <span class="synConstant">'rb'</span>)) <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(self, input_text): <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(input_text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text): clean_text = self.preprocess(input_text) tfidf_vec = self.tfidf.transform(clean_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, input_text): tfidf_vec = self.transform(input_text) <span class="synComment"># 推論</span> predict = [np.max(self.logreg.predict_proba(tfidf_vec), axis=<span class="synConstant">1</span>), self.logreg.predict(tfidf_vec)] <span class="synStatement">return</span> predict <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'test.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synComment"># テキストを与えるとそのカテゴリを予測できるようにする</span> api = PredictAPI() train_score = api.logreg.score(api.transform(dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'title'</span>]), dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>]) test_score = api.logreg.score(api.transform(dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'title'</span>]), dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'category'</span>]) <span class="synIdentifier">print</span>(f<span class="synConstant">'train score: {train_score}'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'test score: {test_score}'</span>) </pre> <p>シンプルな特徴量の割にはそこそこの精度がでています。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>train score: 0.9284107946026986 test score: 0.8755622188905547</pre> <h2 id="55-混同行列の作成">55. 混同行列の作成</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">55. 混同行列の作成</span> <span class="synConstant">52で学習したロジスティック回帰モデルの混同行列(confusion matrix)を,学習データおよび評価データ上で作成せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> confusion_matrix <span class="synStatement">class</span> <span class="synIdentifier">PredictAPI</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self): self.tfidf = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'rb'</span>)) self.logreg = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'logreg.pkl'</span>, <span class="synConstant">'rb'</span>)) <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(self, input_text): <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(input_text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text): clean_text = self.preprocess(input_text) tfidf_vec = self.tfidf.transform(clean_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, input_text): tfidf_vec = self.transform(input_text) <span class="synComment"># 推論</span> predict = [np.max(self.logreg.predict_proba(tfidf_vec), axis=<span class="synConstant">1</span>), self.logreg.predict(tfidf_vec)] <span class="synStatement">return</span> predict <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'test.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synComment"># テキストを与えるとそのカテゴリを予測できるようにする</span> api = PredictAPI() y_train = dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>] y_test = dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'category'</span>] train_pred = api.predict(dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] test_pred = api.predict(dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] <span class="synIdentifier">print</span>(f<span class="synConstant">'train confusion matrix:</span><span class="synSpecial">\n</span><span class="synConstant"> {confusion_matrix(y_train, train_pred)}'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'test confusion matrix:</span><span class="synSpecial">\n</span><span class="synConstant"> {confusion_matrix(y_test, test_pred)}'</span>) </pre> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>train confusion matrix: [[4366 71 8 57] [ 50 4159 5 9] [ 102 123 493 10] [ 191 127 11 890]] test confusion matrix: [[526 24 2 11] [ 17 506 2 3] [ 12 24 55 0] [ 43 27 1 81]]</pre> <h2 id="56-適合率再現率F1スコアの計測">56. 適合率,再現率,F1スコアの計測</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">56. 適合率,再現率,F1スコアの計測</span> <span class="synConstant">52で学習したロジスティック回帰モデルの適合率,再現率,F1スコアを,評価データ上で計測せよ.</span> <span class="synConstant">カテゴリごとに適合率,再現率,F1スコアを求め,カテゴリごとの性能をマイクロ平均(micro-average)とマクロ平均(macro-average)で統合せよ</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> classification_report <span class="synStatement">class</span> <span class="synIdentifier">PredictAPI</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self): self.tfidf = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'rb'</span>)) self.logreg = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'logreg.pkl'</span>, <span class="synConstant">'rb'</span>)) <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(self, input_text): <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(input_text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text): clean_text = self.preprocess(input_text) tfidf_vec = self.tfidf.transform(clean_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, input_text): tfidf_vec = self.transform(input_text) <span class="synComment"># 推論</span> predict = [np.max(self.logreg.predict_proba(tfidf_vec), axis=<span class="synConstant">1</span>), self.logreg.predict(tfidf_vec)] <span class="synStatement">return</span> predict <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'test.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synComment"># テキストを与えるとそのカテゴリを予測できるようにする</span> api = PredictAPI() y_train = dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>] y_test = dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'category'</span>] train_pred = api.predict(dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] test_pred = api.predict(dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] <span class="synIdentifier">print</span>(f<span class="synConstant">'train classification_report:</span><span class="synSpecial">\n</span><span class="synConstant"> {classification_report(y_train, train_pred)}'</span>) <span class="synIdentifier">print</span>(f<span class="synConstant">'test classification_report:</span><span class="synSpecial">\n</span><span class="synConstant"> {classification_report(y_test, test_pred)}'</span>) </pre> <p>classification_report便利ですね。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>train classification_report: precision recall f1-score support b 0.93 0.97 0.95 4502 e 0.93 0.98 0.96 4223 m 0.95 0.68 0.79 728 t 0.92 0.73 0.81 1219 accuracy 0.93 10672 macro avg 0.93 0.84 0.88 10672 weighted avg 0.93 0.93 0.93 10672 test classification_report: precision recall f1-score support b 0.88 0.93 0.91 563 e 0.87 0.96 0.91 528 m 0.92 0.60 0.73 91 t 0.85 0.53 0.66 152 accuracy 0.88 1334 macro avg 0.88 0.76 0.80 1334 weighted avg 0.88 0.88 0.87 1334</pre> <h2 id="57-特徴量の重みの確認">57. 特徴量の重みの確認</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">57. 特徴量の重みの確認</span> <span class="synConstant">52で学習したロジスティック回帰モデルの中で,重みの高い特徴量トップ10と,重みの低い特徴量トップ10を確認せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'X_train'</span>: <span class="synConstant">'X_train.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() features = dfs[<span class="synConstant">'X_train'</span>].columns.values index = [i <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1</span>, <span class="synConstant">11</span>)] <span class="synComment"># モデルのロード</span> logreg = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'logreg.pkl'</span>, <span class="synConstant">'rb'</span>)) <span class="synStatement">for</span> c, coef <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(logreg.classes_, logreg.coef_): <span class="synIdentifier">print</span>(f<span class="synConstant">'category: {c}'</span>) best10 = pd.DataFrame(features[np.argsort(coef)[::-<span class="synConstant">1</span>][:<span class="synConstant">10</span>]], columns=[<span class="synConstant">'TOP'</span>], index=index).T worst10 = pd.DataFrame(features[np.argsort(coef)[:<span class="synConstant">10</span>]], columns=[<span class="synConstant">'LOW'</span>], index=index).T <span class="synIdentifier">print</span>(pd.concat([best10, worst10], axis=<span class="synConstant">0</span>)) <span class="synIdentifier">print</span>(<span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) </pre> <p>カテゴリは<code>b = business, t = science and technology, e = entertainment, m = health</code> なので、そこそこ直感的に重み付けされていそうです。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>category: b 1 2 3 4 5 6 7 8 9 10 TOP fed ecb bank stocks china euro dollar obamacare profit ipo LOW ebola microsoft facebook virus heart video star aereo mother fda category: e 1 2 3 4 5 6 7 8 9 10 TOP kardashian chris movie beyonce film star trailer kim paul miley LOW us google china study gm billion buy sales apple microsoft category: m 1 2 3 4 5 6 7 8 9 10 TOP ebola study cancer fda mers drug health cdc brain cases LOW gm facebook apple deal sales twitter ceo bank fed climate category: t 1 2 3 4 5 6 7 8 9 10 TOP google facebook apple microsoft climate nasa gm tesla comcast heartbleed LOW stocks drug fed cancer percent american shares day ecb ukraine</pre> <h2 id="58-正則化パラメータの変更">58. 正則化パラメータの変更</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">58. 正則化パラメータの変更Permalink</span> <span class="synConstant">ロジスティック回帰モデルを学習するとき,正則化パラメータを調整することで,学習時の過学習(overfitting)の度合いを制御できる.</span> <span class="synConstant">異なる正則化パラメータでロジスティック回帰モデルを学習し,学習データ,検証データ,および評価データ上の正解率を求めよ.実験の結果は,正則化パラメータを横軸,正解率を縦軸としたグラフにまとめよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> accuracy_score <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synStatement">class</span> <span class="synIdentifier">PredictAPI</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, logreg_model): self.tfidf = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'rb'</span>)) self.logreg = logreg_model <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(self, input_text): <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(input_text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text): clean_text = self.preprocess(input_text) tfidf_vec = self.tfidf.transform(clean_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, input_text): tfidf_vec = self.transform(input_text) <span class="synComment"># 推論</span> predict = [np.max(self.logreg.predict_proba(tfidf_vec), axis=<span class="synConstant">1</span>), self.logreg.predict(tfidf_vec)] <span class="synStatement">return</span> predict <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'valid'</span>: <span class="synConstant">'valid.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'test.txt'</span>, <span class="synConstant">'X_train'</span>: <span class="synConstant">'X_train.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synStatement">assert</span> dfs[<span class="synConstant">'train'</span>].shape[<span class="synConstant">0</span>] == dfs[<span class="synConstant">'X_train'</span>].shape[<span class="synConstant">0</span>], <span class="synConstant">'長さが不正です'</span> C_candidate = [<span class="synConstant">0.1</span>, <span class="synConstant">1.0</span>, <span class="synConstant">10</span>, <span class="synConstant">100</span>] result = [] y_train = dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>] y_valid = dfs[<span class="synConstant">'valid'</span>][<span class="synConstant">'category'</span>] y_test = dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'category'</span>] <span class="synStatement">for</span> C <span class="synStatement">in</span> C_candidate: <span class="synComment"># モデルの学習</span> lg = LogisticRegression(random_state=<span class="synConstant">42</span>, max_iter=<span class="synConstant">10000</span>, C=C) lg.fit(dfs[<span class="synConstant">'X_train'</span>], dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>]) <span class="synComment"># 予測値の取得</span> api = PredictAPI(lg) train_pred = api.predict(dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] valid_pred = api.predict(dfs[<span class="synConstant">'valid'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] test_pred = api.predict(dfs[<span class="synConstant">'test'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] <span class="synComment"># 正解率の算出</span> train_accuracy = accuracy_score(y_train, train_pred) valid_accuracy = accuracy_score(y_valid, valid_pred) test_accuracy = accuracy_score(y_test, test_pred) <span class="synComment"># 結果の格納</span> result.append([C, train_accuracy, valid_accuracy, test_accuracy]) result = np.array(result).T plt.plot(result[<span class="synConstant">0</span>], result[<span class="synConstant">1</span>], label=<span class="synConstant">'train'</span>) plt.plot(result[<span class="synConstant">0</span>], result[<span class="synConstant">2</span>], label=<span class="synConstant">'valid'</span>) plt.plot(result[<span class="synConstant">0</span>], result[<span class="synConstant">3</span>], label=<span class="synConstant">'test'</span>) plt.ylim(<span class="synConstant">0</span>, <span class="synConstant">1.1</span>) plt.ylabel(<span class="synConstant">'Accuracy'</span>) plt.xscale(<span class="synConstant">'log'</span>) plt.xlabel(<span class="synConstant">'C'</span>) plt.legend() plt.savefig(<span class="synConstant">'ans_58.png'</span>) </pre> <p>実行結果 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210605/20210605162130.png" alt="f:id:taxa_program:20210605162130p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="59-ハイパーパラメータの探索">59. ハイパーパラメータの探索</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">59. ハイパーパラメータの探索</span> <span class="synConstant">学習アルゴリズムや学習パラメータを変えながら,カテゴリ分類モデルを学習せよ.検証データ上の正解率が最も高くなる学習アルゴリズム・パラメータを求めよ.また,その学習アルゴリズム・パラメータを用いたときの評価データ上の正解率を求めよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> numpy <span class="synStatement">as</span> np <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> pickle <span class="synPreProc">import</span> texthero <span class="synStatement">as</span> hero <span class="synPreProc">from</span> sklearn.linear_model <span class="synPreProc">import</span> LogisticRegression <span class="synPreProc">from</span> sklearn.metrics <span class="synPreProc">import</span> accuracy_score <span class="synPreProc">import</span> optuna <span class="synStatement">class</span> <span class="synIdentifier">PredictAPI</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, model): self.tfidf = pickle.load(<span class="synIdentifier">open</span>(<span class="synConstant">'tfidf_vec.pkl'</span>, <span class="synConstant">'rb'</span>)) self.model = model <span class="synStatement">def</span> <span class="synIdentifier">preprocess</span>(self, input_text): <span class="synConstant">&quot;&quot;&quot;前処理&quot;&quot;&quot;</span> clean_text = hero.clean(input_text, pipeline=[ hero.preprocessing.fillna, hero.preprocessing.lowercase, hero.preprocessing.remove_digits, hero.preprocessing.remove_punctuation, hero.preprocessing.remove_diacritics, hero.preprocessing.remove_stopwords ]) <span class="synStatement">return</span> clean_text <span class="synStatement">def</span> <span class="synIdentifier">transform</span>(self, input_text): clean_text = self.preprocess(input_text) tfidf_vec = self.tfidf.transform(clean_text) <span class="synStatement">return</span> tfidf_vec <span class="synStatement">def</span> <span class="synIdentifier">predict</span>(self, input_text): tfidf_vec = self.transform(input_text) <span class="synComment"># 推論</span> predict = [np.max(self.model.predict_proba(tfidf_vec), axis=<span class="synConstant">1</span>), self.model.predict(tfidf_vec)] <span class="synStatement">return</span> predict <span class="synStatement">def</span> <span class="synIdentifier">load_data</span>() -&gt; <span class="synIdentifier">dict</span>: <span class="synConstant">&quot;&quot;&quot;データの読み込み&quot;&quot;&quot;</span> <span class="synComment"># 読み込むファイルを定義</span> inputs = { <span class="synConstant">'train'</span>: <span class="synConstant">'train.txt'</span>, <span class="synConstant">'valid'</span>: <span class="synConstant">'valid.txt'</span>, <span class="synConstant">'test'</span>: <span class="synConstant">'test.txt'</span>, <span class="synConstant">'X_train'</span>: <span class="synConstant">'X_train.txt'</span>, } dfs = {} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> inputs.items(): dfs[k] = pd.read_csv(v, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) <span class="synStatement">return</span> dfs <span class="synStatement">class</span> <span class="synIdentifier">HyperparameterSearch</span>(): <span class="synStatement">def</span> <span class="synIdentifier">__init__</span>(self, dfs): self.dfs = dfs <span class="synStatement">def</span> <span class="synIdentifier">objective_lg</span>(self, trial): <span class="synConstant">&quot;&quot;&quot;最適化&quot;&quot;&quot;</span> l1_ratio = trial.suggest_uniform(<span class="synConstant">'l1_ratio'</span>, <span class="synConstant">0</span>, <span class="synConstant">1</span>) C = trial.suggest_loguniform(<span class="synConstant">'C'</span>, <span class="synConstant">1e-4</span>, <span class="synConstant">1e2</span>) <span class="synComment"># モデルの学習</span> lg = LogisticRegression(random_state=<span class="synConstant">42</span>, max_iter=<span class="synConstant">10000</span>, penalty=<span class="synConstant">'elasticnet'</span>, solver=<span class="synConstant">'saga'</span>, l1_ratio=l1_ratio, C=C) lg.fit(self.dfs[<span class="synConstant">'X_train'</span>], dfs[<span class="synConstant">'train'</span>][<span class="synConstant">'category'</span>]) <span class="synComment"># 予測値の取得</span> api = PredictAPI(lg) valid_pred = api.predict(dfs[<span class="synConstant">'valid'</span>][<span class="synConstant">'title'</span>])[<span class="synConstant">1</span>] <span class="synComment"># 正解率の算出</span> valid_accuracy = accuracy_score(dfs[<span class="synConstant">'valid'</span>][<span class="synConstant">'category'</span>], valid_pred) <span class="synStatement">return</span> valid_accuracy <span class="synStatement">def</span> <span class="synIdentifier">search_optuna</span>(self): study = optuna.create_study(direction=<span class="synConstant">'maximize'</span>) study.optimize(self.objective_lg, timeout=<span class="synConstant">3600</span>) <span class="synStatement">return</span> study <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: <span class="synComment"># データのロード</span> dfs = load_data() <span class="synStatement">assert</span> dfs[<span class="synConstant">'train'</span>].shape[<span class="synConstant">0</span>] == dfs[<span class="synConstant">'X_train'</span>].shape[<span class="synConstant">0</span>], <span class="synConstant">'長さが不正です'</span> <span class="synComment"># 最適化</span> tuner = HyperparameterSearch(dfs) study = tuner.search_optuna() <span class="synComment"># 結果の表示</span> <span class="synIdentifier">print</span>(<span class="synConstant">'Best trial:'</span>) trial = study.best_trial <span class="synIdentifier">print</span>(<span class="synConstant">' Value: {:.3f}'</span>.format(trial.value)) <span class="synIdentifier">print</span>(<span class="synConstant">' Params: '</span>) <span class="synStatement">for</span> key, value <span class="synStatement">in</span> trial.params.items(): <span class="synIdentifier">print</span>(<span class="synConstant">' {}: {}'</span>.format(key, value)) </pre> <p>optuna<a href="#f-8a8961e1" name="fn-8a8961e1" title="https://github.com/optuna/optuna">*2</a>を用いてパラメータ探索を行いました。(上記のコードは結構時間がかかります)<br /> 結果としては</p> <ul> <li>l1_ratio: 0.190283773569564</li> <li>C: 2.97492047697017</li> </ul> <p>が良いパラメータとして推定されました。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink>..... [I 2021-06-05 16:45:50,322] Trial 25 finished with value: 0.8823088455772113 and parameters: {&#39;l1_ratio&#39;: 0.39957785978633487, &#39;C&#39;: 2.820148509505984}. Best is trial 14 with value: 0.8845577211394303. [I 2021-06-05 16:46:14,130] Trial 26 finished with value: 0.7931034482758621 and parameters: {&#39;l1_ratio&#39;: 0.4399758132783767, &#39;C&#39;: 0.17773660929932344}. Best is trial 14 with value: 0.8845577211394303. [I 2021-06-05 16:49:42,323] Trial 27 finished with value: 0.8718140929535232 and parameters: {&#39;l1_ratio&#39;: 0.22609331565604585, &#39;C&#39;: 22.130119756833977}. Best is trial 14 with value: 0.8845577211394303. Best trial: Value: 0.885 Params: l1_ratio: 0.190283773569564 C: 2.97492047697017</pre> <p>実際にこのパラメータで再度学習させてみると下記のようにスコアが改善しました。</p> <pre class="code" data-lang="" data-unlink>train score: 0.9562406296851574(元々のスコア:0.9284107946026986) test score: 0.883808095952024(元々のスコア:0.8755622188905547)</pre> <div class="footnote"> <p class="footnote"><a href="#fn-2c5f0b0b" name="f-2c5f0b0b" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://texthero.org/">https://texthero.org/</a></span></p> <p class="footnote"><a href="#fn-8a8961e1" name="f-8a8961e1" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/optuna/optuna">https://github.com/optuna/optuna</a></span></p> </div> taxa_program atmaCup振り返り回でLTをしました(word2vecを利用した埋め込み分析と SWEMを用いた比較実験) hatenablog://entry/26006613696960341 2021-02-27T12:23:00+09:00 2021-02-27T12:33:27+09:00 概要 2021.02.18に行われた「atmaCup#9 オンサイトデータコンペ振り返り回」*1でLTをしました。 運営の方に許可をいただいたので、発表資料を公開します。 SWEMのサンプルコード スライド中で紹介しているSWEMのコードはGithubにあげていますので、よければ参考にしてみてください。 github.com *1:atma.connpass.com <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210227/20210227122530.png" alt="f:id:taxa_program:20210227122530p:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <h1>概要</h1> <p>2021.02.18に行われた「atmaCup#9 オンサイトデータコンペ振り返り回」<a href="#f-8142e8aa" name="fn-8142e8aa" title="[https://atma.connpass.com/event/204173/:embed:cite]">*1</a>でLTをしました。<br /> 運営の方に許可をいただいたので、発表資料を公開します。</p> <script async class="speakerdeck-embed" data-id="ca082f3671854e0e912ebc7d9e3c1074" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script> <h2>SWEMのサンプルコード</h2> <p>スライド中で紹介しているSWEMのコードはGithubにあげていますので、よければ参考にしてみてください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fgeek_blog%2Fblob%2Fmaster%2Fnlp%2Fswem.py" title="takapy0210/geek_blog" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/geek_blog/blob/master/nlp/swem.py">github.com</a></cite></p> <div class="footnote"> <p class="footnote"><a href="#fn-8142e8aa" name="f-8142e8aa" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fatma.connpass.com%2Fevent%2F204173%2F" title="atmaCup#9 オンサイトデータコンペ振り返り回 (2021/02/18 19:00〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://atma.connpass.com/event/204173/">atma.connpass.com</a></cite></span></p> </div> taxa_program 【書籍メモ】分析者のためのデータ解釈学入門を読んだ hatenablog://entry/26006613679545550 2021-01-17T20:13:48+09:00 2021-01-17T20:15:08+09:00 こんにちは。takapyです。 本日は「分析者のためのデータ解釈学入門」を読んだので、そのメモ書きです。 (完全に自分用の備忘録なので、雑になっています) 分析者のためのデータ解釈学入門 データの本質をとらえる技術作者:江崎貴裕発売日: 2020/12/15メディア: Kindle版 1部 データの性質に関する基礎知識 データを観測すること 様々なバイアス 測定基準に関するバイアス 選択バイアス 観測介入に起因するバイアス データの扱いに起因するバイアス 交絡因子と因果関係 データサンプリングの方法論 2部 データの分析に関する基礎知識 一変数データの振る舞い 変数間の関係を調べる 多変量デー… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117184640.png" alt="f:id:taxa_program:20210117184640p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。<a href="https://twitter.com/takapy0210">takapy</a>です。</p> <p>本日は「分析者のためのデータ解釈学入門」を読んだので、そのメモ書きです。<br /> (完全に自分用の備忘録なので、雑になっています)</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B08Q7RN6XL/takapy00-22/"><img src="https://m.media-amazon.com/images/I/51T6qR1bLSL.jpg" class="hatena-asin-detail-image" alt="分析者のためのデータ解釈学入門 データの本質をとらえる技術" title="分析者のためのデータ解釈学入門 データの本質をとらえる技術"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B08Q7RN6XL/takapy00-22/">分析者のためのデータ解釈学入門 データの本質をとらえる技術</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%B9%BE%BA%EA%B5%AE%CD%B5" class="keyword">江崎貴裕</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2020/12/15</li><li><span class="hatena-asin-detail-label">メディア:</span> Kindle版</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <ul class="table-of-contents"> <li><a href="#1部-データの性質に関する基礎知識">1部 データの性質に関する基礎知識</a><ul> <li><a href="#データを観測すること">データを観測すること</a></li> <li><a href="#様々なバイアス">様々なバイアス</a><ul> <li><a href="#測定基準に関するバイアス">測定基準に関するバイアス</a></li> <li><a href="#選択バイアス">選択バイアス</a></li> <li><a href="#観測介入に起因するバイアス">観測介入に起因するバイアス</a></li> <li><a href="#データの扱いに起因するバイアス">データの扱いに起因するバイアス</a></li> </ul> </li> <li><a href="#交絡因子と因果関係">交絡因子と因果関係</a></li> <li><a href="#データサンプリングの方法論">データサンプリングの方法論</a></li> </ul> </li> <li><a href="#2部-データの分析に関する基礎知識">2部 データの分析に関する基礎知識</a><ul> <li><a href="#一変数データの振る舞い">一変数データの振る舞い</a></li> <li><a href="#変数間の関係を調べる">変数間の関係を調べる</a></li> <li><a href="#多変量データを解釈する">多変量データを解釈する</a><ul> <li><a href="#各種手法の整理">各種手法の整理</a></li> </ul> </li> <li><a href="#数理モデリングの要点">数理モデリングの要点</a></li> </ul> </li> <li><a href="#3部-データの解釈活用に関する基礎知識">3部 データの解釈・活用に関する基礎知識</a><ul> <li><a href="#データ分析の罠">データ分析の罠</a></li> <li><a href="#データ解釈の罠">データ解釈の罠</a></li> <li><a href="#データ活用の罠">データ活用の罠</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="1部-データの性質に関する基礎知識">1部 データの性質に関する基礎知識</h1> <p>1部では、モデリングを行う手前の作業でもある、データをどのように取得してくるか、その時に気をつけておくポイントなどが綺麗にまとめられていました。<br /> データ分析コンペなどでは、この「データ取得」という作業はすでに行われていることが多いと思いますが、実務を行う上でもデータそのものは、分析の土台であり、garbage in garbage out<a href="#f-1f8544b9" name="fn-1f8544b9" title="https://ja.wikipedia.org/wiki/Garbage_in,_garbage_out">*1</a>という言葉があるくらいとても重要なフローだったりするので、要点がまとめられている本書は貴重だと思いました。</p> <p>個人的にはデータのバイアスに関するポイントは知らないことも多く、読んでいて楽しかったです。</p> <h2 id="データを観測すること">データを観測すること</h2> <ul> <li>データ分析は観測したデータを人間が利用したすいように変換する作業 <ul> <li>入力データの質が分析結果に</li> </ul> </li> <li>もともと数値化されていないものをデータ化しようとする際には、「測れる何か」で代替する必要がある</li> <li>データを誤って解釈しないために <ul> <li>観測によって図られているものは何なのか</li> <li>「本当に測りたいもの」の中でとらえられていない要素は何か <ul> <li>を明示的に把握することが大切</li> </ul> </li> </ul> </li> <li>バイアスを取り除く方法 <ul> <li>そのバイアスがそもそも何から生じているものなのかを特定する</li> <li>そのバイアスの影響を何らかの方法で排除する</li> </ul> </li> </ul> <h2 id="様々なバイアス">様々なバイアス</h2> <h3 id="測定基準に関するバイアス">測定基準に関するバイアス</h3> <ul> <li>データが一定の基準で取得できていない場合に発生するバイアス</li> <li>時間経過によりデータが変化することによるバイアス</li> </ul> <h3 id="選択バイアス">選択バイアス</h3> <ul> <li>有名なのは、戦闘機の生存バイアスの話<a href="#f-9d0752f4" name="fn-9d0752f4" title="https://ja.wikipedia.org/wiki/%E7%94%9F%E5%AD%98%E8%80%85%E3%83%90%E3%82%A4%E3%82%A2%E3%82%B9">*2</a></li> <li>ほかにも「サンプリングバイアス」「志願者バイアス」「出版バイアス」などがある</li> </ul> <p>データ分析や統計調査の報告自体にもバイアスがかかっている(出版バイアス)話は、たしかにと思った。(仮説に合わないような分析結果になってしまった場合、それは公表されることはない、というお話)</p> <h3 id="観測介入に起因するバイアス">観測介入に起因するバイアス</h3> <ul> <li>人間を対象とするデータ分析では、心理学的な効果によって、知りたい情報がうまくデータとして測定できないことがある <ul> <li>例えばアンケートに答えてもらう形で個人の考えをデータとして取得する場合には、以下のようなバイアスがかかる可能性がある</li> </ul> </li> <li>黙従傾向 <ul> <li>「はい/いいえ」で応える質問などで肯定的な選択肢を答えやすい</li> </ul> </li> <li>中心化傾向 <ul> <li>「・全く同意できない ・やや同意できない ・どちらともいえない ・やや同意できる ・非常に同意できる」から1つ選ぶ場合、真ん中の選択肢が選ばれやすい</li> </ul> </li> <li>キャリーオーバー効果 <ul> <li>前の質問への回答が次の質問への回答に影響する</li> </ul> </li> <li>質問文での誘導 <ul> <li>質問文に余計な情報を付与して、誘導回答させるもの</li> </ul> </li> <li>回答者が後ろめたいと思っている事柄に関しては、正確なデータが得られないことがある <ul> <li>その場合は、ランダム回答法で解消できる</li> </ul> </li> </ul> <h3 id="データの扱いに起因するバイアス">データの扱いに起因するバイアス</h3> <ul> <li>データを扱う人が意図的または無意識的にデータを歪めてしまうこと</li> <li>一般的に、目標が決められていて、本人の裁量で簡単にコントロールできる数値については、こうした不正によってデータが不正確になる可能性がある</li> </ul> <p>機械学習のプロジェクトは、データありきで推進されることが多いが、そのデータ自体がゴミだと、プロジェクトもゴミになってしまうので、改めてまずは疑うことを意識しようと思った。</p> <p>また、自分のコンロトールできるバイアスとして、「データの扱いに起因するバイアス」はありそうだと思ったので、気を付けていきたい。</p> <h2 id="交絡因子と因果関係">交絡因子と因果関係</h2> <ul> <li>観察データを用いた研究では、交絡因子を十分にコントロールすることが一般に難しく、注意が必要</li> </ul> <p>武器軟膏<a href="#f-48c6b6d2" name="fn-48c6b6d2" title="https://ja.wikipedia.org/wiki/%E6%AD%A6%E5%99%A8%E8%BB%9F%E8%86%8F">*3</a>の話はとても面白かった。<br /> A/Bテストの基本ともいえるランダム化比較実験(RCT)から始まり、RCTが使えない場面での変数間の関係性を分析する手法について述べられている点も良いと思った。</p> <h2 id="データサンプリングの方法論">データサンプリングの方法論</h2> <ul> <li>どういったことに注意してデータをサンプリングすればよいか</li> <li>サンプルサイズの決め方</li> <li>さまざまなサンプリング方法</li> </ul> <h1 id="2部-データの分析に関する基礎知識">2部 データの分析に関する基礎知識</h1> <p>2部では具体的なデータ分析手法や考え方、問題設定毎にどんな手法に頼れば良いのかについて解説されていました。中でも、1変数や多変量など、変数の数に応じた分析アプローチの方法は参考になる部分も多かったです。<br /> 本書にも記載されていますが、各分析手法の細かい内容については述べられていないので、モデル毎の詳細は別の参考書にてキャッチアップする必要がありそうです。</p> <h2 id="一変数データの振る舞い">一変数データの振る舞い</h2> <ul> <li>経験分布と理論分布を比較する際、ヒストグラムを利用してもよいが、定量的に分布の形を計りたいときは累積分布関数を用いると良い。</li> </ul> <h2 id="変数間の関係を調べる">変数間の関係を調べる</h2> <ul> <li>なぜ「相関があるか」という分析手続きを踏む必要があるか</li> <li>仮説検定の方法</li> <li>2変数間で相関があるかどうかを検定することを、<strong>無相関検定</strong>という</li> </ul> <h2 id="多変量データを解釈する">多変量データを解釈する</h2> <ul> <li>探索的に相関をいろいろ調べていくと、本来は関係のない変数の間にも相関が見えてしまうことがある <ul> <li>例えば下図は正規分布からランダムに生成されたデータだが、1と2に相関があるように見える</li> </ul> </li> </ul> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117192304.png" alt="f:id:taxa_program:20210117192304p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></p> <ul> <li>検定の多重性を補正する方法 <ul> <li>Bonferroni法</li> <li>Holm法 <ul> <li>これによって、データに見られる性質がどれだけ「たまたま」でないのかを正しく評価することができる</li> </ul> </li> </ul> </li> <li>探索的データ分析で見つかった特徴が、実際に存在するものかどうかを調べる確証的データ分析を追加で行えば、仮説が1つに定まるので、多重比較の問題は発生しない</li> </ul> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117192404.png" alt="f:id:taxa_program:20210117192404p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></p> <ul> <li>3つ以上の比較をする際には、分散分析が有効</li> <li>偏相関で他の変数の効果を考慮することも方法の1つ</li> </ul> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117192729.png" alt="f:id:taxa_program:20210117192729p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></p> <ul> <li>より複雑な相関の構造を分析する手法として、因子分析があげられる。 <ul> <li>因子分析では、それぞれの変数を、少ない数の共通因子の和でうまく表現することを目指す</li> <li>共通因子とは、勉強熱心かどうか、語学と理数科目のどちらが得意なタイプか、といった抽象的な要因のこと</li> </ul> </li> </ul> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117192843.png" alt="f:id:taxa_program:20210117192843p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></p> <ul> <li>より複雑な関係性を分析する手法として下記があげられる <ul> <li>フラフィカルモデリング</li> <li>パス解析</li> <li>共分散構造分析</li> <li>構造方程式モデリング</li> </ul> </li> </ul> <h3 id="各種手法の整理">各種手法の整理</h3> <ul> <li>探索的な分析をする場合 <ul> <li>各種ペアの散布図および相関行列 <ul> <li>必要に応じて偏相関係数を計算して、特定の変数の影響を除くことも有効</li> </ul> </li> <li>多くの変数を少ない変数で表現しなおすことで、本質的な特徴を見つけたい場合に有効なのが、因子分析、主成分分析、クラスタリング <ul> <li>因子得点、主成分得点に変換することで、さらなる分析も可能</li> </ul> </li> <li>データのまとまりを見たい場合は、種々のクラスタリング手法を用いる。 <ul> <li>多次元尺度構成法などもある</li> </ul> </li> </ul> </li> <li>説明変数としての影響を見たい場合 <ol> <li> 目的変数も説明変数も両的変数の場合 <ul> <li>重回帰分析などを使用できる</li> </ul> </li> <li> 目的変数がカテゴリで、説明変数が量的変数の場合 <ul> <li>ロジスティック回帰</li> </ul> </li> <li> 目的変数が量的変数で説明変数がカテゴリ <ul> <li>分散分析や多重比較分析</li> <li>説明変数をダミー変数として、回帰分析を行うことも可能</li> </ul> </li> <li> 目的変数も説明変数もカテゴリ <ul> <li>クロス集計</li> </ul> </li> </ol> </li> </ul> <h2 id="数理モデリングの要点">数理モデリングの要点</h2> <ul> <li>数理モデリングは大きく分けて2つある <ul> <li>理解思考型モデリング <ul> <li>データの背後に存在する現象のメカニズムを理解するために行われるモデル</li> </ul> </li> <li>応用志向型モデリング <ul> <li>予測やデータ生成などデータ活用を行うためのモデリング</li> </ul> </li> </ul> </li> <li>理解思考型モデリングでは、データが足りない状況などに応用が効く <ul> <li>例えば、データの生成過程からボトムアップ的に分布を推定して、モデル化することができる</li> <li>一方で、妥当な仮説から導かれた論理を重視するので、家庭に含められない要因が大きな影響を与えているケースでは、パフォーマンスが低くなってしまうということもありえる</li> </ul> </li> </ul> <h1 id="3部-データの解釈活用に関する基礎知識">3部 データの解釈・活用に関する基礎知識</h1> <p>3部ではデータ分析の結果を解釈・応用する際に注意するべきポイントについてまとめられていました。<br /> 分析では、その手法や手続きそのものに注意が向きがちですが、本当に重要なのはその後、それをどう利用するかなので、データを正しく活用するためにも、迷った時に立ち返ってきたいと思える内容がまとめられていました。</p> <h2 id="データ分析の罠">データ分析の罠</h2> <ul> <li>実数を見たら割合を疑え、割合を見たら実数を疑え</li> <li>変数Xが変数Yに影響を与えているかどうか、ということをデータ分析によって調べたい場合、得られるパターンは次の3つ。 <ul> <li>影響を与えていると考えられる</li> <li>影響を与えいないと考えられる</li> <li>このデータからでは何とも言えない <ul> <li>この3つ目である「このデータからでは何とも言えない」を主張するのが結構難しい(悪いことではない)</li> </ul> </li> </ul> </li> <li>「結論が出せないのは、分析がうまくいっていないからでは?」という批判に耐える程度に、「分析のプロセスはベストを尽くした上で、それでもなんとも言えない」状況を作る必要がある <ul> <li>特にEDAでは「まだ試していない分析法を用いると、何か特徴が見つかるかもしれない」という誘惑に駆られるが、このような場合は「このくらいの基本的な分析で出ない程度の特徴」ということで分析を打ち切ることも必要 <ul> <li>分析を打ち切る基準を明確に持っておくことが大切</li> </ul> </li> </ul> </li> <li>目的に応じた分析のデザインは大きく3つに分けられる<br /> <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117194303.png" alt="f:id:taxa_program:20210117194303p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></li> </ul> <h2 id="データ解釈の罠">データ解釈の罠</h2> <ul> <li>データ分析の再現性を担保するのは一般的に難しい <ul> <li>同じデータであっても、分析する人によって結果が異なることが多い。</li> </ul> </li> <li>データの再現性 <ul> <li>たまたま仮説通りの結果が出たら良いが、仮説に合わない結果が出た場合は基本的に公開されないので、発表された結果だけしか見ないと第一種誤認が濃縮された状態になる</li> </ul> </li> <li>HARKing <ul> <li>実験や分析を行った後に、その結果に沿うような仮説を立案し、あたかもその仮説を検証するためにデータを取得したかのように報告すること<br /> <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117194435.png" alt="f:id:taxa_program:20210117194435p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></li> </ul> </li> <li>有意水準を下回るp値を得るために何度も検定を行うことをp-hakingと言う <ul> <li>p-hackingを避けるためのガイドライン <ol> <li> データ取得を始める前に、どこまでデータをとるのかを決定し報告する</li> <li> 一つの条件につき、最低でも20の観測値を集める</li> <li> 収集した全ての変数について報告する</li> <li> データを取得した全ての実験条件を報告する</li> <li> もし観測値を取り除く場合は、それを取り除かなかった場合の分析結果も示す</li> <li> 分析で、ある変数の影響を取り除く操作(共変量の統制)を行った場合は、そうしなかった場合の結果も示す</li> </ol> </li> </ul> </li> <li>Hillの基準 <ul> <li>因果関係を判定するための基準<br /> <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117194611.png" alt="f:id:taxa_program:20210117194611p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></li> </ul> </li> <li>人間は本来、意味のないたまたま生じたパターンを敏感に選び取り、それに合致する理由付けを行う動物なので、データ解釈時に認知バイアスが生じることが多々ある</li> <li>利得と損失の非対称性 <ul> <li>数学的には同一の選択肢でも、利得と損失で感じ方が異なる</li> <li>データ分析結果の受け取り手の解釈にも影響を与えるので、レポーティングなどでも注意する必要がある <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117194739.png" alt="f:id:taxa_program:20210117194739p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></li> </ul> </li> </ul> <h2 id="データ活用の罠">データ活用の罠</h2> <ul> <li>フィードバックのあるシステム構築をすることが重要 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20210117/20210117194921.png" alt="f:id:taxa_program:20210117194921p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></li> </ul> <h1 id="最後に">最後に</h1> <p>本書は、帯にも記載されているように「データ取得・分析・解釈・活用の各段階で知っておくべき技術を網羅的に解説」の通りの内容でした。<br /> これからデータ分析に関わる方、実際に業務でデータ分析を行っている方にとっても一読の価値があると思います。</p> <p>個人的には、データにさまざまな偏りを生じさせる行動心理学の話や、各種分析の考え方、データの解釈における認知バイアスや数理モデリングのポイントあたりの話が網羅的かつ平易に記述されており、要所を掴むことができました。</p> <p>気になった方は是非一度読んでみてはいかがでしょうか。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B08Q7RN6XL/takapy00-22/"><img src="https://m.media-amazon.com/images/I/51T6qR1bLSL.jpg" class="hatena-asin-detail-image" alt="分析者のためのデータ解釈学入門 データの本質をとらえる技術" title="分析者のためのデータ解釈学入門 データの本質をとらえる技術"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/exec/obidos/ASIN/B08Q7RN6XL/takapy00-22/">分析者のためのデータ解釈学入門 データの本質をとらえる技術</a></p><ul><li><span class="hatena-asin-detail-label">作者:</span><a href="http://d.hatena.ne.jp/keyword/%B9%BE%BA%EA%B5%AE%CD%B5" class="keyword">江崎貴裕</a></li><li><span class="hatena-asin-detail-label">発売日:</span> 2020/12/15</li><li><span class="hatena-asin-detail-label">メディア:</span> Kindle版</li></ul></div><div class="hatena-asin-detail-foot"></div></div></p> <div class="footnote"> <p class="footnote"><a href="#fn-1f8544b9" name="f-1f8544b9" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ja.wikipedia.org/wiki/Garbage_in,_garbage_out">https://ja.wikipedia.org/wiki/Garbage_in,_garbage_out</a></span></p> <p class="footnote"><a href="#fn-9d0752f4" name="f-9d0752f4" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ja.wikipedia.org/wiki/%E7%94%9F%E5%AD%98%E8%80%85%E3%83%90%E3%82%A4%E3%82%A2%E3%82%B9">https://ja.wikipedia.org/wiki/%E7%94%9F%E5%AD%98%E8%80%85%E3%83%90%E3%82%A4%E3%82%A2%E3%82%B9</a></span></p> <p class="footnote"><a href="#fn-48c6b6d2" name="f-48c6b6d2" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://ja.wikipedia.org/wiki/%E6%AD%A6%E5%99%A8%E8%BB%9F%E8%86%8F">https://ja.wikipedia.org/wiki/%E6%AD%A6%E5%99%A8%E8%BB%9F%E8%86%8F</a></span></p> </div> taxa_program 2021年の目標と行動指針 hatenablog://entry/26006613672973135 2021-01-01T23:22:55+09:00 2021-01-10T11:52:42+09:00 年末に振り返りやすいように、目標や行動指針をデプロイしておきます。 早寝早起き 適度な運動 音声入力を積極的に使う 行動のログを残す 月末にその月のざっくり振り返りを行う 本を読む 分析コンペのNLP&Tableデータでメダルを取る 統計検定2級を取る 仕事で成果を出す 最後に 早寝早起き 流行病以降、通勤時間がなくなったのを良いことに起きる時間が遅くなってしまいました。 0時前には就寝、6時〜7時には起床する、を意識していきます。 適度な運動 家から出ない生活が続き運動不足にも陥っています。 高校時代まで体重を落とすことを目標に、年末まで継続的に運動しようと思います。 (筋力を当時まで戻すの… <p>年末に振り返りやすいように、目標や行動指針をデプロイしておきます。</p> <ul class="table-of-contents"> <li><a href="#早寝早起き">早寝早起き</a></li> <li><a href="#適度な運動">適度な運動</a></li> <li><a href="#音声入力を積極的に使う">音声入力を積極的に使う</a></li> <li><a href="#行動のログを残す">行動のログを残す</a></li> <li><a href="#月末にその月のざっくり振り返りを行う">月末にその月のざっくり振り返りを行う</a></li> <li><a href="#本を読む">本を読む</a></li> <li><a href="#分析コンペのNLPTableデータでメダルを取る">分析コンペのNLP&amp;Tableデータでメダルを取る</a></li> <li><a href="#統計検定2級を取る">統計検定2級を取る</a></li> <li><a href="#仕事で成果を出す">仕事で成果を出す</a></li> <li><a href="#最後に">最後に</a></li> </ul> <h2 id="早寝早起き">早寝早起き</h2> <p>流行病以降、通勤時間がなくなったのを良いことに起きる時間が遅くなってしまいました。<br /> 0時前には就寝、6時〜7時には起床する、を意識していきます。</p> <h2 id="適度な運動">適度な運動</h2> <p>家から出ない生活が続き運動不足にも陥っています。<br /> 高校時代まで体重を落とすことを目標に、年末まで継続的に運動しようと思います。<br /> (筋力を当時まで戻すのは難しいけど、とりあえず-5kgを目指す)</p> <h2 id="音声入力を積極的に使う">音声入力を積極的に使う</h2> <p>昨年からPodcastを始めてみたりなど、音声にはいろいろ期待をしているわけですが、<strong>今年は音声"入力"にチャレンジしていきたいと思っています。</strong><br /> ブログを書くのもそうですが、後述するログを残す過程でも積極的に音声入力を使っていこうと思います。</p> <h2 id="行動のログを残す">行動のログを残す</h2> <p>どんなことを考えたか、どんな行動をしたか、などの行動・思考をログとして残そうと思います。</p> <p>具体的には、LINEにログ専用の部屋を作り、AppleWatchなどの音声入力を使ってロギングしていこうと思います。<br /> スマホは手元にない時もありますが、時計はお風呂以外の時間つけているので、多分いけるはず。</p> <h2 id="月末にその月のざっくり振り返りを行う">月末にその月のざっくり振り返りを行う</h2> <p>どんなインプットをして、どんなアウトプットをして、どんなことを学んで、という感じのことを毎月振り返りたいと思います。<br /> 昨年はあっという間に1年が過ぎ去ってしまい、自分自身の成長具合が可視化できていなかったのが反省点だったので、今年は見えるところにポストしていこうと思います。<br /> (YWTフレームワークを使ってやるかな)</p> <h2 id="本を読む">本を読む</h2> <p>昨年はあまり本を読めなかったので、毎日最低でも30分本を読む時間を設けます。<br /> 思考、A/Bテスト、データ×ビジネス、プロダクトマネジメント系に興味があるので、その辺りの本を読みたいと思ってます。<br /> (もちろん、技術書も読んでいきます)</p> <h2 id="分析コンペのNLPTableデータでメダルを取る">分析コンペのNLP&amp;Tableデータでメダルを取る</h2> <p>趣味を兼ねたスキルアップです。<br /> 昨年は2つのメダルを取れたので、今年も継続していきたいです。</p> <h2 id="統計検定2級を取る">統計検定2級を取る</h2> <p>抜けている知識も多いと感じているので、基礎的なところから勉強し直して、行間を埋めていこうと思います。</p> <h2 id="仕事で成果を出す">仕事で成果を出す</h2> <p>自分が関わったプロジェクトが会社のプレスリリースに出るくらいの成果を出したい、というお気持ち表明です。</p> <h2 id="最後に">最後に</h2> <p>年末は本エントリをベースに振り返りを行おうと思います。</p> <p>本年もよろしくお願いします。</p> taxa_program 分析コンペをチームで戦うための技術 hatenablog://entry/26006613668408460 2020-12-22T22:57:15+09:00 2020-12-22T23:29:35+09:00 こんにちは。takapy(@takapy0210)です。 本記事はKaggle Advent Calendar 2020 22日目の記事です。 明日は、本エントリで紹介するMoAコンペでチームを組んだsinchir0さんの予定です。 タイトルからしてとても楽しみです!(プレッシャー) qiita.com はじめに チームでやったこと コミュニケーション submit回数を無駄にしないために 知見の共有 次にチームで参加するなら submitのIssueはいらないかも Issueのタイトルだけで判断しやすくする 最後に はじめに 本エントリは、先日行われたMoAコンペ*1に5人チームで参加したと… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222223932.png" alt="f:id:taxa_program:20201222223932p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本記事はKaggle Advent Calendar 2020 22日目の記事です。<br /> 明日は、本エントリで紹介するMoAコンペでチームを組んだ<a href="https://twitter.com/sinchir0">sinchir0さん</a>の予定です。<br /> タイトルからしてとても楽しみです!<s>(プレッシャー)</s></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2020%2Fkaggle" title="Kaggle Advent Calendar 2020 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2020/kaggle">qiita.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#チームでやったこと">チームでやったこと</a><ul> <li><a href="#コミュニケーション">コミュニケーション</a><ul> <li><a href="#submit回数を無駄にしないために">submit回数を無駄にしないために</a></li> </ul> </li> <li><a href="#知見の共有">知見の共有</a></li> </ul> </li> <li><a href="#次にチームで参加するなら">次にチームで参加するなら</a><ul> <li><a href="#submitのIssueはいらないかも">submitのIssueはいらないかも</a></li> <li><a href="#Issueのタイトルだけで判断しやすくする">Issueのタイトルだけで判断しやすくする</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>本エントリは、先日行われたMoAコンペ<a href="#f-20ccda39" name="fn-20ccda39" title="https://www.kaggle.com/c/lish-moa">*1</a>に5人チームで参加したときに行っていたアレコレについて書いたものです。</p> <p>5人チームで取り組むのは私自身初めての経験であり、どのようにコンペを進めて行ったのか(主に知見共有などのTips)について残しておこうと思います。</p> <p>今後、分析コンペなどをチームで取り組む際の参考になれば幸いです。<br /> (GithubでのIssue管理などはソロで参加する際にも役立つものになるのではないかと思っています)</p> <p>ちなみに結果は34/4373 で銀メダルでした。解法については下記を参照ください。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.kaggle.com%2Fc%2Flish-moa%2Fdiscussion%2F200798" title="Mechanisms of Action (MoA) Prediction | Kaggle" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.kaggle.com/c/lish-moa/discussion/200798">www.kaggle.com</a></cite></p> <p>また、後日談としてTawaraさんが興味深い実験を行ってくださったので、興味のある方はこちらもどうぞ。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftawara.hatenablog.com%2Fentry%2F2020%2F12%2F16%2F132415" title="黒魔術への招待:Neural Network Stacking の探求 - 俵言" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://tawara.hatenablog.com/entry/2020/12/16/132415">tawara.hatenablog.com</a></cite></p> <h1 id="チームでやったこと">チームでやったこと</h1> <p>下記リポジトリに歴史が詰まっています。(懐かしい・・・)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fteam90s%2Fkaggle-MoA" title="team90s/kaggle-MoA" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/team90s/kaggle-MoA">github.com</a></cite></p> <p>工夫というほどの工夫でもないですが、ざっくり下記のような方針で行いました。</p> <ul> <li><strong>「こんな実験をしようと思っている」「このDiscussionを読んだ」「このnotebookをサブするぞ」はGithubのIssueで管理</strong></li> <li><strong>ローカルCVとPublicLBのスコアはスプレッドシートで管理</strong> <ul> <li>ここはMLFlowとか使うと楽になるよな〜というのはありつつ、コードコンペということで今回はアナログ管理です</li> </ul> </li> <li><strong>日常のコミュニケーションはslackで行う</strong> <ul> <li>Githubの対象リポジトリをsubscribeさせて、Issueやコメントなどはslackの方にも自動的に流れてくるようにしました</li> </ul> </li> <li><strong>毎週1回、オンラインでのチームMTGを行う</strong> <ul> <li>この議事録もGithubのIssueに残しました</li> </ul> </li> </ul> <p>※今回はコードコンペだったこともあり、各自のコードをGithubで管理するということは意図的に行っていません。あくまで、情報共有ツールとしての位置付けでGithubを用いました。</p> <h2 id="コミュニケーション">コミュニケーション</h2> <p>slackのワークスペースを作成して、そこで諸々やりとりをしました。<br /> ですが、あくまでslackはコミュニケーションツールなので、知見の蓄積などはGithubにて行っていました。</p> <p>slackのチャンネルは下記の3つを作りました。</p> <ul> <li><code>#general</code> <ul> <li>主に雑談用</li> </ul> </li> <li><code>#kaggle_moa</code> <ul> <li>主にコンペの内容を話す場所。先述したGithubに登録された内容もここに流すように設定。</li> </ul> </li> <li><code>#submission</code> <ul> <li>submissionする時に「今日分のサブ使いますね〜」と報告する場所。</li> </ul> </li> </ul> <h3 id="submit回数を無駄にしないために">submit回数を無駄にしないために</h3> <p>kaggleは日本時間のAM9時に、その日のsubmit回数が初期化される仕様<a href="#f-79ed560a" name="fn-79ed560a" title="2020/12/22現在">*2</a>になっているため、<br /> <code>#submission</code>チャンネルでは毎朝8時30分にリマインダーを仕込み、submit回数をなるべく無駄にしないようにしました。<br /> (本コンペでは1日にsubmitできる回数が3回と少なかったため、なるべくこの3回を毎日使い切りたいよね〜という想いもありました) <figure class="figure-image figure-image-fotolife" title="slackに毎朝8時30分に通知してくれるリマインダー"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222210905.png" alt="f:id:taxa_program:20201222210905p:plain:w900" title="" class="hatena-fotolife" style="width:900px" itemprop="image"></span><figcaption>slackに毎朝8時30分に通知してくれるリマインダー</figcaption></figure></p> <p>slackのリマインダーは下記を使うと簡単に作れたりします</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fslack-remind-creator.netlify.app%2F" title="Slack remind creator | Slackのリマインダーを簡単作成" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://slack-remind-creator.netlify.app/">slack-remind-creator.netlify.app</a></cite></p> <h2 id="知見の共有">知見の共有</h2> <p>チームで取り組むメリットはいくつかあると思うのですが、例えばチーム内で同じような実験を複数人がおこなうと、「単位時間あたりの試行回数が増える」というメリットが活かせなくなってしまいます。<br /> そこで、各人がやろうとしていることなどはGithubのIssueを作成するルールにしました。</p> <p>このIssueの作成ですが、少しでも負担を下げるために Issueテンプレート<a href="#f-c7569e24" name="fn-c7569e24" title="https://docs.github.com/ja/free-pro-team@latest/github/building-a-strong-community/configuring-issue-templates-for-your-repository">*3</a>を設定しておくと良いと思います。</p> <p>今回は下記3つを作成していました。 <br /> (後述しますが、submissionは別にIssueにしなくてもexpetimentのコメントに紐づける形で良いかもと思いました)</p> <ul> <li><strong>discussion</strong>(kaggleで気になるdiscussionの概要を共有するもの。同じdiscussionを複数人で読んじゃった〜という事態を防ぐため)</li> <li><strong>expetiment</strong>(どんな実験をしようとしているかを共有するもの)</li> <li><strong>submission</strong>(どの実験・notebookをsubmitしたかを共有するもの) <figure class="figure-image figure-image-fotolife" title="用意したテンプレート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222212624.png" alt="f:id:taxa_program:20201222212624p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>用意したテンプレート</figcaption></figure></li> </ul> <p>それぞれのIssueにどのような内容を書いていたかは<a href="https://github.com/team90s/kaggle-MoA">先述したリポジトリ</a>を見ていただきたいのですが、 <strong>Issueで管理する方式は、Issue起点でコミュニケーションが生まれたり、関連しているものを簡単に紐付けられたりと、とてもよかったと思います。 </strong></p> <p><figure class="figure-image figure-image-fotolife" title="Github上でもコミュニケーションが生まれて良い感じだった図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222225514.png" alt="f:id:taxa_program:20201222225514p:plain:w700" title="" class="hatena-fotolife" style="width:700px" itemprop="image"></span><figcaption><a href="https://github.com/team90s/kaggle-MoA/issues/86">Github上でもコミュニケーションが生まれて良い感じだった図</a></figcaption></figure></p> <hr /> <p>submitした結果(スコア)は下記のようにスプレッドシートでアナログ管理していました。<br /> (これは結構煩雑だったので、どうにかしたいところ)</p> <p><figure class="figure-image figure-image-fotolife" title="ローカルCVとPublicLBのスプレッドシート"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222214359.png" alt="f:id:taxa_program:20201222214359p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ローカルCVとPublicLBのスプレッドシート</figcaption></figure></p> <p>また、チームMTGの議事録はこんな感じで残していたりしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fteam90s%2Fkaggle-MoA%2Fissues%2F82" title="2020/11/08 21時〜 MTG アジェンダ · Issue #82 · team90s/kaggle-MoA" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/team90s/kaggle-MoA/issues/82">github.com</a></cite></p> <h1 id="次にチームで参加するなら">次にチームで参加するなら</h1> <p>基本的には上記のやり方で良さそう〜とは思うのですが、細かい点をいくつか改善できるかな、と思っています。</p> <h2 id="submitのIssueはいらないかも">submitのIssueはいらないかも</h2> <p>今回はsubmitする際に、<code>expetiment</code>とは別に<code>submission</code>のIssueを作ろうというルールでやっていましたが、これは結構手間だったりしたので、<code>expetiment</code>のIssueコメントに紐づける形で管理しても良いかもしれません。<br /> (そもそもコードもGithubで管理できれば、Issueに紐づくPRを作れば良いことなのですが、今回のようなコードコンペの場合はそうもいかないと思うので)</p> <h2 id="Issueのタイトルだけで判断しやすくする">Issueのタイトルだけで判断しやすくする</h2> <p>これはatmacupに参加していた時に作ったIssueなのですが、 Issueテンプレートのタイトルに絵文字を入れることで実験やドキュメントの区別が容易になりました。<br /> なので、タイトルには良い感じに分かりやすい印を付けられると良いのかなぁ、と思います。</p> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222222203.png" alt="f:id:taxa_program:20201222222203p:plain:w700" title="" class="hatena-fotolife" style="width:700px" itemprop="image"></span><figcaption> </figcaption></figure></p> <h1 id="最後に">最後に</h1> <p>やっぱりチームで参加するとモチベーションも保てますし、議論もたくさんできたのでとても楽しかったです。<br /> Team90'sで戦ったくださったみなさん、ありがとうございました!!!&amp;またどこかでチーム組めたら嬉しいです!</p> <p><figure class="figure-image figure-image-fotolife" title="黒魔術でスコアが伸びた時の様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201222/20201222223352.png" alt="f:id:taxa_program:20201222223352p:plain:w500" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>黒魔術でスコアが伸びた時の様子</figcaption></figure></p> <p>そして本エントリが分析コンペをチームで戦う人たちの一助になれば嬉しいです!</p> <div class="footnote"> <p class="footnote"><a href="#fn-20ccda39" name="f-20ccda39" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://www.kaggle.com/c/lish-moa">https://www.kaggle.com/c/lish-moa</a></span></p> <p class="footnote"><a href="#fn-79ed560a" name="f-79ed560a" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">2020/12/22現在</span></p> <p class="footnote"><a href="#fn-c7569e24" name="f-c7569e24" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://docs.github.com/ja/free-pro-team@latest/github/building-a-strong-community/configuring-issue-templates-for-your-repository">https://docs.github.com/ja/free-pro-team@latest/github/building-a-strong-community/configuring-issue-templates-for-your-repository</a></span></p> </div> taxa_program 家族としてレベルアップするために行っていること hatenablog://entry/26006613666058585 2020-12-17T08:37:04+09:00 2020-12-17T09:02:48+09:00 本記事はコネヒト Advent Calendar 2020の17日目の記事です。 qiita.com こんにちは。takapy(@takapy0210)です。 急に寒くなってきましたね。 在宅勤務していると、どうしても足下が寒くなるので最近は遠赤外線デスクヒーターを買うか迷っています。 みなさんのおすすめ防寒器具があればぜひ教えてください。 はじめに 本エントリは、家族としてレベルアップするために使っているツールやルールについて勝手に話すポエムです。 今働いているコネヒトでは先日リブランディングが行われ、新たなビジョンとして「あなたの家族像が実現できる社会をつくる」を掲げています。 これに伴い… <p>本記事はコネヒト Advent Calendar 2020の17日目の記事です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2020%2Fconnehito" title="コネヒト Advent Calendar 2020 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2020/connehito">qiita.com</a></cite></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>急に寒くなってきましたね。<br /> 在宅勤務していると、どうしても足下が寒くなるので最近は<a href="https://www.amazon.co.jp/dp/B07NDNGQ5H/?coliid=IYCVD9Q019WPW&amp;colid=3G9B82E3D6Z53&amp;psc=1&amp;ref_=lv_ov_lig_dp_it">遠赤外線デスクヒーター</a>を買うか迷っています。<br /> みなさんのおすすめ防寒器具があればぜひ教えてください。</p> <h1 id="はじめに">はじめに</h1> <p>本エントリは、家族としてレベルアップするために使っているツールやルールについて勝手に話すポエムです。</p> <p>今働いている<a href="https://connehito.com/">コネヒト</a>では先日リブランディングが行われ、新たなビジョンとして「あなたの家族像が実現できる社会をつくる」を掲げています。<br /> これに伴い、<strong>"家族を話そう"</strong>という合言葉もできたので、まずは自分の家族について話すことから始めてみるかと思い、書いてみることにしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fconnehito.com%2Fnews%2Fnew-departure%2F" title="あなたの家族像が実現できる社会をつくる〜「家族を話そう」を合言葉に行政や企業との連携を強化〜 | コネヒト株式会社" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://connehito.com/news/new-departure/">connehito.com</a></cite></p> <p>他人の家のことなんて興味ないわ!という方もいると思いますが、この目次を見て少しでも興味が湧いた項目があれば、読む価値があるかもしれませんし、ないかもしれません。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#家族で使っているツールルール">家族で使っているツール・ルール</a><ul> <li><a href="#slackコミュニケーション">slack(コミュニケーション)</a></li> <li><a href="#Google-Calenderスケジュール共有">Google Calender(スケジュール共有)</a></li> <li><a href="#Zaim家計簿">Zaim(家計簿)</a></li> <li><a href="#Github家族会議の議事録など">Github(家族会議の議事録など)</a></li> </ul> </li> <li><a href="#家族会議について">家族会議について</a><ul> <li><a href="#先月の振り返りと今月の目標">先月の振り返りと今月の目標</a><ul> <li><a href="#お金パート">お金パート</a></li> <li><a href="#お互いのパート">お互いのパート</a></li> </ul> </li> <li><a href="#なんでも共有タイム">なんでも共有タイム</a></li> </ul> </li> <li><a href="#おわりに">おわりに</a></li> </ul> <p>ちなみに2020/12/16現在の我が家の構成は下記です。</p> <ul> <li>ワイ:IT企業でエンジニア</li> <li>妻:IT企業でバックオフィス全般</li> </ul> <h1 id="家族で使っているツールルール">家族で使っているツール・ルール</h1> <p>家族でどんなツールをどのようなルールで運用しているかについて紹介します。</p> <h2 id="slackコミュニケーション">slack(コミュニケーション)</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fslack.com%2Fintl%2Fja-jp%2F" title="ようこそ、新しい職場へ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://slack.com/intl/ja-jp/">slack.com</a></cite></p> <p>普段のコミュニケーションにはslackを用いることが多いです。 身内との連絡はLINEなどを用いる人が多いと思いますが、我が家ではslackの方がメッセージのやりとりは多いです。<br /> 理由はいくつかありますが、ざっくり下記のようなことが挙げられます。</p> <ul> <li>お互い仕事でもslackを使っているので慣れている</li> <li>平日昼間の緊急連絡などはslackの方が取りやすい</li> <li>いろんな外部ツールと連携できる <ul> <li>GoogleCalender(後述)</li> <li>Github(後述)</li> </ul> </li> <li>リマインダーが気軽に登録できる</li> </ul> <p>以降でも述べる諸々のコミュニケーションについて、slackを土台としていろいろやっています。</p> <h2 id="Google-Calenderスケジュール共有">Google Calender(スケジュール共有)</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsupport.google.com%2Fcalendar%2Fanswer%2F2465776%3Fco%3DGENIE.Platform%253DDesktop%26hl%3Dja" title="Google カレンダー スタートガイド - パソコン - カレンダー ヘルプ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://support.google.com/calendar/answer/2465776?co=GENIE.Platform%3DDesktop&hl=ja">support.google.com</a></cite></p> <p>お互いのスケジュールはGoogle Calenderに登録して共有しています。<br /> また、Calenderに登録するだけだと見逃してしまう可能性もあるため、Google Calendar for Team Events<a href="#f-567cc5ce" name="fn-567cc5ce" title="https://slack.com/intl/ja-jp/help/articles/360047938054-Slack-%E5%90%91%E3%81%91-Google-Calendar-for-Team-Events">*1</a>をslackに連携させて、新規で予定が登録された場合はslackに流れてくるようになっています。</p> <p>こうすることで、予定を起点としたコミュニケーションが生まれたりするので、認識の齟齬が少なくなり、とても便利です。</p> <p><a href="https://timetreeapp.com/intl/ja/">TimeTree</a>などを使うともっと便利なのかもしれませんが、使うツールはあまり増やしたくない + 現状特に不満がないので、スケジュール共有は上記の運用をしています。</p> <p><figure class="figure-image figure-image-fotolife" title="『鬼滅の刃』柱合会議・蝶屋敷編を忘れずに見るためのスケジュールが通知される様子"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201216/20201216200349.png" alt="f:id:taxa_program:20201216200349p:plain:w400" title="" class="hatena-fotolife" style="width:400px" itemprop="image"></span><figcaption>『鬼滅の刃』柱合会議・蝶屋敷編を忘れずに見るためのスケジュールが通知される様子</figcaption></figure></p> <h2 id="Zaim家計簿">Zaim(家計簿)</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fzaim.net%2F" title="家計簿 Zaim:シンプル・簡単、無料で使える人気の家計改善アプリ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://zaim.net/">zaim.net</a></cite></p> <p>これは家計簿を管理するために使っています。</p> <p>我が家では給料はお互い把握してますし、家計簿アプリに金の在りどころ全ての口座を連携しているので、今どのくらい資産があって、今月どのくらい使ったのかが一目でわかります。</p> <p>といっても、<strong>現金の管理だけは厳密におこなっていません笑</strong></p> <p>家計簿でよくあるのが、張り切って厳密に管理しようとして途中で挫折するパターンです。<br /> 我が家でも使ったお金を一円単位で入力する・・・ということをやっていた時期がありましたが、1ヶ月と持ちませんでした。</p> <p>なので現金の管理は諦める代わりに、<strong>現金をなるべく使わない方向に舵を切りました。</strong></p> <p>Zaimなどの家計簿アプリはクレジットカードや電子マネーと連携することで、自動的に家計簿を付けてくれます。<br /> なので、支払いを極力クレジットカード or 電子マネーで行うことにより、家計簿に入力する手間を省いています。</p> <p>そうなると気になるのが現金ですが、これは後述する家族会議の場で使途不明金として精算しています。<br /> (それでいいんかい!というツッコミがきそうですが、流行病もあり最近は現金を使わないのでなんとかなっています)</p> <h2 id="Github家族会議の議事録など">Github(家族会議の議事録など)</h2> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.co.jp%2F" title="GitHub" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.co.jp/">github.co.jp</a></cite></p> <p>後述する家族会議の議事録などを残しておくのに使っています。<br /> すこし前までは、Scrapbox<a href="#f-cab601aa" name="fn-cab601aa" title="https://scrapbox.io/">*2</a>を使っていましたが、アジェンダを自動的に作るのがちょっと面倒だったので、現在はGithubに移行しています。 (privateリポジトリを使っています)</p> <p>月末になると、下記のように自動的に家族会議のISSUEが作られて、slackに通知がくる<a href="#f-77ae3137" name="fn-77ae3137" title="https://slack.com/intl/ja-jp/help/articles/232289568-GitHub-%E3%81%A8-Slack-%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B">*3</a>ようになっています。<br /> (AWS lambdaでGithub ISSUE生成のスクリプトを月末に動くように設定して、そこからslackに通知がきます)</p> <p><figure class="figure-image figure-image-fotolife" title="slackにはこんな感じで通知されます"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201216/20201216195807.png" alt="f:id:taxa_program:20201216195807p:plain:w600" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption>slackにはこんな感じで通知されます</figcaption></figure></p> <h1 id="家族会議について">家族会議について</h1> <p>最後に家族会議について書いてみます。</p> <p>これは基本的に毎月第一日曜日の11時から行うもので、"会議"と名前がついているので仰々しく感じるかもしれませんが、<br /> 要は<strong>お互いどう?家族の状態どう?を確認する場です。</strong></p> <p>主なアジェンダは</p> <ul> <li>先月の振り返り</li> <li>今月の目標</li> <li>なんでも共有タイム</li> </ul> <p>です。</p> <h2 id="先月の振り返りと今月の目標">先月の振り返りと今月の目標</h2> <p>先月の振り返りと今月の目標は2部構成になっています。</p> <h3 id="お金パート">お金パート</h3> <p>振り返りの部分では、前月の支出についてZaimを見ながら話します。<br /> また、先述したように現金だけはZaimで管理できていない(意図的にしていない)ので、お互いの財布の中の現金を計算してアプリとの差額を使途不明金として精算します。<br /> 上記を踏まえて、今月はこんなことを節約するように意識するか〜や、こんな大きな出費ありそうだね〜などを話します。</p> <p>例えば、先月はUber Eatsを使いすぎたから、今月は週1回までにしようかとか、そんな感じのことを目標に盛り込みます。</p> <h3 id="お互いのパート">お互いのパート</h3> <p>自分・妻、共に毎月簡単な目標を立てます。</p> <p>例えば</p> <ul> <li>本を読む</li> <li>試験の勉強をする</li> <li>歯医者を予約する</li> </ul> <p>などです。</p> <p>ここで立てた目標を翌月に振り返ります。</p> <p>達成/未達だったらどうこう、というものは現段階では何もありませんが、やろうとしていることを目に見えるところに明記しておくのは効果的だと思います。 <br /> (ちなみにここで書いた目標は、slackのリマインダーで定期的に目に見るところに露出させています)</p> <h2 id="なんでも共有タイム">なんでも共有タイム</h2> <p>ここはその名の通りなんでも気になることを話す場です。何もなければ特に話しません。</p> <p>例えば今月(12月)だと、11月にkaggleに結構時間を溶かしていたのでそのお礼だったり、家をどうするか(引っ越すか)や、大掃除の話がでたりしました。<br /> 大掃除に関しては、この会議中にどこを掃除するかの割り振りまで決められたので、今年はスムーズにいきそうです。</p> <p><figure class="figure-image figure-image-fotolife" title="12月に話したこと"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201216/20201216212055.png" alt="f:id:taxa_program:20201216212055p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>12月に話したこと</figcaption></figure></p> <h1 id="おわりに">おわりに</h1> <p>こんなしっかりやるのめんどくさそう・・・と思われた方もいると思います。<br /> 個人的には毎月30分〜1時間、「ちゃんと話す場」を半強制的に設定することで、お互いのモヤモヤや困っていることなどを共有できるので、今のところは気に入っていたりします。</p> <p>「家族」と言ってもその形やルールは様々あると思うので、みなさんの家族・家庭にあった方法で、みなさんが思い描く家族像を目指してみてはいかがでしょうか。</p> <div class="footnote"> <p class="footnote"><a href="#fn-567cc5ce" name="f-567cc5ce" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://slack.com/intl/ja-jp/help/articles/360047938054-Slack-%E5%90%91%E3%81%91-Google-Calendar-for-Team-Events">https://slack.com/intl/ja-jp/help/articles/360047938054-Slack-%E5%90%91%E3%81%91-Google-Calendar-for-Team-Events</a></span></p> <p class="footnote"><a href="#fn-cab601aa" name="f-cab601aa" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://scrapbox.io/">https://scrapbox.io/</a></span></p> <p class="footnote"><a href="#fn-77ae3137" name="f-77ae3137" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://slack.com/intl/ja-jp/help/articles/232289568-GitHub-%E3%81%A8-Slack-%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B">https://slack.com/intl/ja-jp/help/articles/232289568-GitHub-%E3%81%A8-Slack-%E3%82%92%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B</a></span></p> </div> taxa_program レコメンデーションに用いられるMatrix Factorization(行列分解)をTensorFlow.kerasで実装してみる hatenablog://entry/26006613662650718 2020-12-10T09:01:31+09:00 2021-02-25T15:11:29+09:00 Vectorpouch - jp.freepik.com こんにちは。takapy(@takapy0210)です。 本記事はコネヒト Advent Calendar 2020の10日目の記事です。 qiita.com みなさんハイキューという漫画(アニメ)はご存知でしょうか。 高校バレーボールを題材にしたスポーツ青春漫画なのですが、ところどころでめっちゃ染みるセリフがあったりして、高校生ではないおじさんでも、バレーにそんなに詳しくない人でも楽しむことができるので、是非読んでみてください。(自分は最近アニメで見ています) Twitterでも回っていた個人的に好きなシーン さて本日は、レコメンデー… <p><figure class="figure-image figure-image-fotolife" title="Vectorpouch - jp.freepik.com"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209235425.jpg" alt="f:id:taxa_program:20201209235425j:plain:w500" title="" class="hatena-fotolife" style="width:500px" itemprop="image"></span><figcaption>Vectorpouch - jp.freepik.com</figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本記事はコネヒト Advent Calendar 2020の10日目の記事です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fqiita.com%2Fadvent-calendar%2F2020%2Fconnehito" title="コネヒト Advent Calendar 2020 - Qiita" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://qiita.com/advent-calendar/2020/connehito">qiita.com</a></cite></p> <p>みなさんハイキューという漫画(アニメ)はご存知でしょうか。<br /> 高校バレーボールを題材にしたスポーツ青春漫画なのですが、ところどころでめっちゃ染みるセリフがあったりして、高校生ではないおじさんでも、バレーにそんなに詳しくない人でも楽しむことができるので、是非読んでみてください。(自分は最近アニメで見ています)</p> <p><figure class="figure-image figure-image-fotolife" title="好きなシーン"><div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209225310.png" alt="f:id:taxa_program:20201209225310p:plain" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209225336.png" alt="f:id:taxa_program:20201209225336p:plain" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209225418.png" alt="f:id:taxa_program:20201209225418p:plain" title="" class="hatena-fotolife" itemprop="image"></span></div><figcaption><a href="https://twitter.com/kuto_bopro/status/1335484056243355649">Twitter</a>でも回っていた個人的に好きなシーン</figcaption></figure></p> <hr /> <p>さて本日は、レコメンデーションの文献をいくつかサーベイした中から、TensorFlowを用いた行列分解モデルについてご紹介できればと思います。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#今回実装する行列分解モデルについて">今回実装する行列分解モデルについて</a><ul> <li><a href="#なぜTensorFlow深層学習フレームワークを使う必要があるのか">なぜTensorFlow(深層学習フレームワーク)を使う必要があるのか</a></li> </ul> </li> <li><a href="#実装">実装</a><ul> <li><a href="#データのサンプリングとindexの付与">データのサンプリングとindexの付与</a></li> <li><a href="#TensorFlowkerasでの学習">TensorFlow.kerasでの学習</a></li> <li><a href="#学習結果">学習結果</a></li> </ul> </li> <li><a href="#レコメンドに活かす方法">レコメンドに活かす方法</a><ul> <li><a href="#推論結果を利用するパターン">推論結果を利用するパターン</a></li> <li><a href="#Embbeding-Layerのweightを利用するパターン">Embbeding Layerのweightを利用するパターン</a><ul> <li><a href="#ジュラシックパークと似ている--似ていない映画">ジュラシック・パークと似ている / 似ていない映画</a></li> <li><a href="#トイストーリーと似ている--似ていない映画">トイ・ストーリーと似ている / 似ていない映画</a></li> </ul> </li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>Matrix Factorizationはその名前の通り、行列分解を行うものです。<br /> レコメンデーションシステムの文脈では、Rating行列をuserの特徴量行列(P)とitemの特徴量行列(Q)に分解する手法として知られています。</p> <p>例えば、m人のユーザーとn個のアイテムを考えたときに、m > k > 0であるk次元に次元削減して変換することを目的とします。<br /> これは、評価値を表すRating行列(R)を、ユーザー要素を表すk × mの行列(P)と、アイテム要素を表すk × nの行列(Q)に近似していることになります。</p> <p>図にすると下記のようなイメージ <figure class="figure-image figure-image-fotolife" title="行列分解のイメージ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209152633.png" alt="f:id:taxa_program:20201209152633p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>行列分解のイメージ</figcaption></figure></p> <p>また、今回解説する実装はGithubにもあげております。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fgeek_blog%2Ftree%2Fmaster%2Frecommendation%2Fmatrix_factorization" title="takapy0210/geek_blog" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/geek_blog/tree/master/recommendation/matrix_factorization">github.com</a></cite></p> <h1 id="今回実装する行列分解モデルについて">今回実装する行列分解モデルについて</h1> <p>こちらの論文(<a href="https://datajobs.com/data-science-repo/Recommender-Systems-[Netflix].pdf">MATRIX FACTORIZATION TECHNIQUES FOR RECOMMENDER SYSTEMS</a> )などを参考に、上図の行列分解モデルをベースとしてユーザーとアイテムそれぞれのbiasを考慮したものを、TensorFlowを用いて実装してみます。</p> <p>ユーザー行列(上図P)とアイテム行列(上図Q)および、ユーザー・アイテムそれぞれのbiasを表現するために、ニューラルネットワークのLayterの1つであるEmbedding Layer<a href="#f-3d3f2026" name="fn-3d3f2026" title="https://www.tensorflow.org/api_docs/python/tf/keras/layers/Embedding">*1</a>を用いて実装していきます。<br /> Embedding Layerは、有名どころだと単語の埋め込み表現などを計算するときにも用いられたりするものです。</p> <p>お気持ちとしては、下記式でユーザーの嗜好がスコアリングできると仮定して学習させています。</p> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20r%20_%20%7Bij%7D%3Dv%20_%20%7Bi%7D%5Ccdot%20v%20_%20%7Bj%7D%2Bb%20_%20%7Bi%7D%2Bb%20_%20%7Bj%7D%20" alt=" r _ {ij}=v _ {i}\cdot v _ {j}+b _ {i}+b _ {j} "/></p> <p><img src="https://chart.apis.google.com/chart?cht=tx&chl=%20r%20_%20%7Bij%7D" alt=" r _ {ij}"/>:ユーザiによるアイテムjに対する評価値<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20b%20_%20%7Bi%7D" alt=" b _ {i}"/>:ユーザiによる評価値のバイアス。このユーザがつける評価値が全体的に高いか低いかを表す。<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20b%20_%20%7Bj%7D" alt=" b _ {j}"/>:アイテムjに対する評価値のバイアス。このアイテムに対する評価値が全体的に高いか低いかを表す。<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20v%20_%20%7Bi%7D" alt=" v _ {i}"/>:ユーザiの特徴ベクトル。<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20v%20_%20%7Bj%7D" alt=" v _ {j}"/>:アイテムjの特徴ベクトル。<br /> <img src="https://chart.apis.google.com/chart?cht=tx&chl=%20v%20_%20%7Bi%7D%5Ccdot%20v%20_%20%7Bj%7D" alt=" v _ {i}\cdot v _ {j}"/>:ユーザiとアイテムjの特徴ベクトルの内積。</p> <p>また、今回モデリングするネットワークは下記のようなものになります。 <figure class="figure-image figure-image-fotolife" title="行列分解のネットワーク構造"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209154347.png" alt="f:id:taxa_program:20201209154347p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>TensorFlowの行列分解ネットワーク構造例</figcaption></figure></p> <p>学習後のEmbedding Layerには、ユーザー・アイテムそれぞれの分散表現が格納されているイメージです。<br /> (上図のembedding, embedding_1の部分)</p> <p>本ポストの後半では、この分散表現を用いたレコメンデーションについても触れています。</p> <h2 id="なぜTensorFlow深層学習フレームワークを使う必要があるのか">なぜTensorFlow(深層学習フレームワーク)を使う必要があるのか</h2> <p>行列分解は、numpy<a href="#f-72328725" name="fn-72328725" title="https://numpy.org/">*2</a>やsklearnのNMF<a href="#f-4c6b4760" name="fn-4c6b4760" title="https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html">*3</a>などを用いれば比較的容易に実装することができます。<br /> しかし、使用できる最適化アルゴリズムやバイアス項の制限もあり、少し使いづらい部分もあります。</p> <p>そこでTensorFlowなどの深層学習フレームワークを用いることで、好きな最適化アルゴリズム<a href="#f-0a01c71e" name="fn-0a01c71e" title="tensorflow.kerasではこのような最適化アルゴリズムが用意されています。https://www.tensorflow.org/api_docs/python/tf/keras/optimizers">*4</a>やバイアス項を比較的簡単に実装することができます。</p> <p>また、ニューラルネットワークの構造に落とし込むことができれば、後からよりDeepなモデルにしたりなど、アーキテクチャを容易に変更することも可能になり、PDCAのスピードも速くなるというメリットがあるのかなと思います。</p> <h1 id="実装">実装</h1> <p>今回使用するデータは、お馴染みのmovie lensデータセットです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgrouplens.org%2Fdatasets%2Fmovielens%2F" title="MovieLens" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://grouplens.org/datasets/movielens/">grouplens.org</a></cite></p> <p><code>MovieLens 25M Dataset</code> のデータセットをダウンロードし、そこからいくつかデータをサンプリングして実装していきます。</p> <h2 id="データのサンプリングとindexの付与">データのサンプリングとindexの付与</h2> <p>今回は実験のため、出現回数の多いデータのみをサンプリングします。<br /> また、TensorFlowのEmbeddingを利用できるようにするために、前処理としてuserとmovieそれぞれに0〜のindex情報を付与します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> Counter <span class="synPreProc">import</span> tensorflow <span class="synStatement">as</span> tf <span class="synPreProc">from</span> sklearn.model_selection <span class="synPreProc">import</span> train_test_split <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt <span class="synPreProc">import</span> seaborn <span class="synStatement">as</span> sns <span class="synPreProc">from</span> tqdm.notebook <span class="synPreProc">import</span> tqdm <span class="synComment"># データの読み込み</span> DATA_DIR = <span class="synConstant">'./data/ml-25m/'</span> df = pd.read_csv(DATA_DIR + <span class="synConstant">'ratings.csv'</span>) <span class="synComment"># 出現回数の多いuserとmovieに絞る</span> n = <span class="synConstant">10000</span> <span class="synComment"># userはTOP:10000</span> m = <span class="synConstant">2000</span> <span class="synComment"># movieはTOP:2000</span> user_ids_count = Counter(df.userId) movie_ids_count = Counter(df.movieId) user_ids = [u <span class="synStatement">for</span> u, c <span class="synStatement">in</span> user_ids_count.most_common(n)] movie_ids = [m <span class="synStatement">for</span> m, c <span class="synStatement">in</span> movie_ids_count.most_common(m)] df_small = df[df.userId.isin(user_ids) &amp; df.movieId.isin(movie_ids)] <span class="synComment"># indexを付与する</span> user_id_map = {} <span class="synStatement">for</span> i, u_id <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(user_ids): user_id_map[u_id] = i movie_id_map = {} <span class="synStatement">for</span> i, m_id <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(movie_ids): movie_id_map[m_id] = i df_small.loc[:, <span class="synConstant">'user_idx'</span>] = df_small.progress_apply(<span class="synStatement">lambda</span> row: user_id_map[row.userId], axis=<span class="synConstant">1</span>) df_small.loc[:, <span class="synConstant">'movie_idx'</span>] = df_small.progress_apply(<span class="synStatement">lambda</span> row: movie_id_map[row.movieId], axis=<span class="synConstant">1</span>) <span class="synComment"># 保存しておく</span> df_small.to_csv(DATA_DIR + <span class="synConstant">'edited_ratings.csv'</span>, index=<span class="synIdentifier">False</span>) </pre> <p>今回使用した<code>rating.csv</code>は、処理前と処理後で下記のようなデータになっています。</p> <p><figure class="figure-image figure-image-fotolife" title="左:元々のDF | 右:処理後のDF"><div class="images-row mceNonEditable"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209161550.png" alt="f:id:taxa_program:20201209161550p:plain" title="" class="hatena-fotolife" itemprop="image"></span><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209161516.png" alt="f:id:taxa_program:20201209161516p:plain" title="" class="hatena-fotolife" itemprop="image"></span></div><figcaption>左:元々のDF | 右:前処理後のDF</figcaption></figure></p> <p>ここまでで学習に使用できるデータができました。</p> <h2 id="TensorFlowkerasでの学習">TensorFlow.kerasでの学習</h2> <p>上記でデータ生成ができたので、TensorFlowで学習させていきます。</p> <p>冒頭でも少し紹介しましたが、Embbeding Layerではuserとmovieそれぞれの埋め込みベクトルが計算されます。<br /> そこでEmbbeding Layerの形状を指定するために、userとmovieのユニーク数を取得しそれをモデルに渡します。<br /> ちなみに、今回は埋め込みベクトルの次元数(冒頭の図でいうところの<code>k</code>)は10にしています。</p> <pre class="code lang-python" data-lang="python" data-unlink>user_num = df_small.user_idx.max() + <span class="synConstant">1</span> <span class="synComment"># number of users</span> movie_num = df_small.movie_idx.max() + <span class="synConstant">1</span> <span class="synComment"># number of movies</span> <span class="synIdentifier">print</span>(user_num, movie_num) <span class="synComment"># -&gt; 10000 2000</span> <span class="synComment"># train, testの分割</span> train, test = train_test_split(df_small, test_size=<span class="synConstant">0.3</span>, shuffle=<span class="synIdentifier">True</span>, random_state=<span class="synConstant">42</span>) <span class="synIdentifier">print</span>(train.shape, test.shape) <span class="synComment"># -&gt;(4027373, 6) (1726017, 6)</span> <span class="synStatement">def</span> <span class="synIdentifier">create_model</span>(user_num: <span class="synIdentifier">int</span>, movie_num: <span class="synIdentifier">int</span>, k: <span class="synIdentifier">int</span> = <span class="synConstant">10</span>) -&gt; tf.keras.models.Model: <span class="synConstant">&quot;&quot;&quot;kerasでMatrix Factorizationのモデルを構築する</span> <span class="synConstant"> Args:</span> <span class="synConstant"> user_num (int): ユニークユーザー数</span> <span class="synConstant"> movie_num (int): ユニーク映画数</span> <span class="synConstant"> k (int): 埋め込み層の次元数</span> <span class="synConstant"> Returns:</span> <span class="synConstant"> tf.keras.models.Model: モデルインスタンス</span> <span class="synConstant"> &quot;&quot;&quot;</span> u = tf.keras.layers.Input(shape=(<span class="synConstant">1</span>,)) m = tf.keras.layers.Input(shape=(<span class="synConstant">1</span>,)) u_embedding = tf.keras.layers.Embedding(user_num, k)(u) m_embedding = tf.keras.layers.Embedding(movie_num, k)(m) u_bias = tf.keras.layers.Embedding(user_num, <span class="synConstant">1</span>)(u) m_bias = tf.keras.layers.Embedding(movie_num, <span class="synConstant">1</span>)(m) x = tf.keras.layers.Dot(axes=<span class="synConstant">2</span>)([u_embedding, m_embedding]) x = tf.keras.layers.Add()([x, u_bias, m_bias]) x = tf.keras.layers.Flatten()(x) model = tf.keras.models.Model(inputs=[u, m], outputs=x) opt = tf.keras.optimizers.SGD(learning_rate=<span class="synConstant">0.1</span>, momentum=<span class="synConstant">0.9</span>) model.compile( loss=tf.keras.losses.MeanSquaredError(), optimizer=opt, metrics=[tf.keras.metrics.RootMeanSquaredError()], ) <span class="synStatement">return</span> model <span class="synComment"># モデルの定義</span> model = create_model(user_num, movie_num) tf.keras.utils.plot_model(model, show_shapes=<span class="synIdentifier">True</span>) <span class="synComment"># ネットワーク構造をプロットできる</span> <span class="synComment"># callback関数を定義</span> early_stopping = tf.keras.callbacks.EarlyStopping( monitor=<span class="synConstant">'val_loss'</span>, patience=<span class="synConstant">5</span>, restore_best_weights=<span class="synIdentifier">True</span>, verbose=<span class="synConstant">0</span>, ) checkpoint = tf.keras.callbacks.ModelCheckpoint( <span class="synConstant">'keras.h5'</span>, monitor=<span class="synConstant">'val_loss'</span>, save_best_only=<span class="synIdentifier">True</span>, save_weights_only=<span class="synIdentifier">True</span>, mode=<span class="synConstant">'min'</span>, verbose=<span class="synConstant">0</span>, ) <span class="synComment"># 学習</span> result = model.fit( x=[train.user_idx.values, train.movie_idx.values], y=train.rating.values, epochs=<span class="synConstant">200</span>, batch_size=<span class="synConstant">1024</span>, validation_data=( [test.user_idx.values, test.movie_idx.values], test.rating.values ), callbacks=[early_stopping, checkpoint], verbose=<span class="synConstant">1</span>, ) <span class="synComment"># -&gt; Epoch 1/200</span> <span class="synComment"># -&gt; 3933/3933 [==============================] - 9s 2ms/step - loss: 2.0342 - root_mean_squared_error: 1.4263 - val_loss: 0.7522 - val_root_mean_squared_error: 0.8673</span> <span class="synComment"># -&gt; Epoch 2/200</span> <span class="synComment"># -&gt; 3933/3933 [==============================] - 10s 3ms/step - loss: 0.7092 - root_mean_squared_error: 0.8421 - val_loss: 0.6880 - val_root_mean_squared_error: 0.8295</span> <span class="synComment"># -&gt; ...</span> <span class="synComment"># -&gt; Epoch 124/200</span> <span class="synComment"># -&gt; 3933/3933 [==============================] - 9s 2ms/step - loss: 0.5016 - root_mean_squared_error: 0.7083 - val_loss: 0.5360 - val_root_mean_squared_error: 0.7321</span> </pre> <p>暫くすると学習が終わります。</p> <h2 id="学習結果">学習結果</h2> <p>学習曲線をプロットしてみます。<br /> epoch=60くらいでサチっているように見えますが、悪くはなさそうです。<br /> (実際には、val_loss: 0.5360でearly_stoppingがかかっています)</p> <pre class="code lang-python" data-lang="python" data-unlink>sns.set_context({<span class="synConstant">&quot;lines.linewidth&quot;</span>: <span class="synConstant">4</span>}) plt.subplots(figsize=(<span class="synConstant">15</span>, <span class="synConstant">6</span>)) sns.lineplot(data=result.history[<span class="synConstant">'loss'</span>], label=<span class="synConstant">&quot;train loss&quot;</span>, color=colors_nude[<span class="synConstant">0</span>]) sns.lineplot(data=result.history[<span class="synConstant">'val_loss'</span>], label=<span class="synConstant">&quot;test loss&quot;</span>, color=colors_nude[<span class="synConstant">1</span>]) plt.legend() plt.show() </pre> <p><figure class="figure-image figure-image-fotolife" title="学習曲線"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209193656.png" alt="f:id:taxa_program:20201209193656p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>学習曲線</figcaption></figure></p> <h1 id="レコメンドに活かす方法">レコメンドに活かす方法</h1> <p>今回は行列分解がレコメンデーションにどのように活用できそうか、という投稿なので、上記の結果・モデルをどのようにレコメンデーションに活かすことができるかについて考察してみます。</p> <p>結果を分かりやすくするために、ひとまずmovieのタイトル情報をtrain, testにマージしておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink>movie = pd.read_csv(DATA_DIR + <span class="synConstant">'movies.csv'</span>) <span class="synComment"># train, testとマージする</span> train = pd.merge(train, movie, how=<span class="synConstant">'left'</span>, on=<span class="synConstant">'movieId'</span>) test = pd.merge(test, movie, how=<span class="synConstant">'left'</span>, on=<span class="synConstant">'movieId'</span>) </pre> <p>ちなみに、<code>movie.csv</code>は以下のようなデータになっています。 <figure class="figure-image figure-image-fotolife" title="movie.csv"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209204644.png" alt="f:id:taxa_program:20201209204644p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>movie.csv</figcaption></figure></p> <h2 id="推論結果を利用するパターン">推論結果を利用するパターン</h2> <p>今回は、user_idxとmovie_idxが分かればuserがどのmovieに興味があるのかを推論することができます。<br /> したがって、単純に予測した値の高いmovieをレコメンドする、という方法が考えられます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># テストデータに対して推論</span> test.loc[:, <span class="synConstant">'pred_rating'</span>] = model.predict([test.user_idx.values, test.movie_idx.values], verbose=<span class="synConstant">1</span>) </pre> <p>推論結果が付与され、testデータは以下のようになりました。 <figure class="figure-image figure-image-fotolife" title="推論結果を付与したtestデータ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209204930.png" alt="f:id:taxa_program:20201209204930p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>推論結果を付与したtestデータ</figcaption></figure></p> <p>例として、<code>user_id = 91660</code>のユーザーに対して、どのようなmovieがレコメンドされるのかを見てみましょう。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 実験</span> user_id = <span class="synConstant">91660</span> <span class="synComment"># trainで見ているmovieでratingの高いものTOP:20</span> train.query(<span class="synConstant">'userId == @user_id'</span>).sort_values(<span class="synConstant">'rating'</span>, ascending=<span class="synIdentifier">False</span>).head(<span class="synConstant">20</span>) </pre> <p>学習データを見てみると、<code>ジュラシック・パーク</code>, <code>ガーディアンズ・オブ・ギャラクシー</code>, <code>ロード・オブ・ザ・リング</code>などの映画に高いratingをつけています。<br /> また、ジャンルをみると、<code>Action</code>, <code>Drama</code>, <code>Thriller</code> などの単語が頻出しています。</p> <p>このことから、このユーザーは恋愛映画のような穏やかな作品より、バトルものなどの作品が好みだということが言えそうです。</p> <p><figure class="figure-image figure-image-fotolife" title="学習データrating上位20件"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209211338.png" alt="f:id:taxa_program:20201209211338p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>学習データrating上位20件</figcaption></figure></p> <p>では、推論結果(testデータ)ではどうでしょうか。<br /> 予測されたratingが高いものを見てみると、<code>インセプション</code>, <code>プリズナーズ</code>, <code>アベンジャーズ</code> など、ハラハラするバトルものなどがレコメンドされそうです。<br /> また、ジャンルを見ても定性的ではありますが比較的好みを当てていそうではあります。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># レコメンドされるmovie TOP:20</span> test.query(<span class="synConstant">'userId == @user_id'</span>).sort_values(<span class="synConstant">'pred_rating'</span>, ascending=<span class="synIdentifier">False</span>).head(<span class="synConstant">20</span>) </pre> <p><figure class="figure-image figure-image-fotolife" title="レコメンドされるmovie例"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209211010.png" alt="f:id:taxa_program:20201209211010p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>レコメンドされるmovie例</figcaption></figure></p> <h2 id="Embbeding-Layerのweightを利用するパターン">Embbeding Layerのweightを利用するパターン</h2> <p>もう1つレコメンドに使えるものとして、Embbeding Layerの埋め込みベクトル(重み)が挙げられるかな、と思います。<br /> Embbeding Layerには各userと各movieの分散表現が計算されているので、例えばコサイン類似度などを用いて類似映画を計算できそうです。</p> <p>ここでは<code>ジュラシック・パーク</code>、<code>トイ・ストーリー</code>と似ている / 似ていない映画をそれぞれ計算してみます。</p> <p>まずは諸々の準備をします。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># utils</span> _df = df_small[[<span class="synConstant">'movieId'</span>, <span class="synConstant">'movie_idx'</span>]].drop_duplicates() <span class="synComment"># 重複を排除したデータ</span> <span class="synStatement">def</span> <span class="synIdentifier">sim_movie</span>(movie_df, movie_idx, asc=<span class="synIdentifier">False</span>, N=<span class="synConstant">10</span>): <span class="synConstant">&quot;&quot;&quot;類似映画IDTOP:N件を返却する関数</span> <span class="synConstant"> &quot;&quot;&quot;</span> sim_movie_df = cos_df.iloc[:, movie_idx:movie_idx+<span class="synConstant">1</span>].sort_values(movie_idx, ascending=asc)[:N].reset_index().rename(columns={movie_idx: <span class="synConstant">'cos_sim'</span>, <span class="synConstant">'index'</span>: <span class="synConstant">'movie_idx'</span>}) sim_movie_df = pd.merge(sim_movie_df, _df, how=<span class="synConstant">'left'</span>, on=<span class="synConstant">'movie_idx'</span>) <span class="synStatement">return</span> pd.merge(sim_movie_df, movie_df, how=<span class="synConstant">'left'</span>, on=<span class="synConstant">'movieId'</span>) <span class="synStatement">def</span> <span class="synIdentifier">cos_sim_matrix</span>(matrix): <span class="synConstant">&quot;&quot;&quot;コサイン類似度を計算する関数</span> <span class="synConstant"> &quot;&quot;&quot;</span> d = matrix @ matrix.T <span class="synComment"># item-vector 同士の内積を要素とする行列</span> norm = (matrix * matrix).sum(axis=<span class="synConstant">1</span>, keepdims=<span class="synIdentifier">True</span>) ** <span class="synConstant">.5</span> <span class="synStatement">return</span> d / norm / norm.T <span class="synComment"># 全movieのコサイン類似度行列を計算</span> cos_df = cos_sim_matrix(movie_emb_layer.get_weights()[<span class="synConstant">0</span>]) cos_df = pd.DataFrame(cos_df) </pre> <h3 id="ジュラシックパークと似ている--似ていない映画">ジュラシック・パークと似ている / 似ていない映画</h3> <p> まずはジュラシック・パークを例に見てみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># ジュラシック・パークのmovie_idxを取得</span> movie_id = <span class="synConstant">480</span> <span class="synComment"># ジュラシック・パーク</span> movie_idx = train.query(<span class="synConstant">'movieId == @movie_id'</span>)[<span class="synConstant">'movie_idx'</span>].unique()[<span class="synConstant">0</span>] <span class="synComment"># 類似度の高い順</span> sim_df = sim_movie(movie, movie_idx, <span class="synIdentifier">False</span>, <span class="synConstant">20</span>) </pre> <p><figure class="figure-image figure-image-fotolife" title=" ジュラシック・パークと類似度が高いmovie TOP:20"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209222242.png" alt="f:id:taxa_program:20201209222242p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> ジュラシック・パークと類似しているmovie TOP:20</figcaption></figure></p> <p>比較的良さそうな結果が出てきました。 <br /> 類似度が低いmovieも見てみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 類似度が低い順</span> sim_df = sim_movie(movie, movie_idx, <span class="synIdentifier">True</span>, <span class="synConstant">20</span>) </pre> <p><figure class="figure-image figure-image-fotolife" title=" ジュラシック・パークと類似度が低いmovie TOP:20"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209222432.png" alt="f:id:taxa_program:20201209222432p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> ジュラシック・パークと類似度が低いmovie TOP:20</figcaption></figure></p> <p>こちらはコメディーもののmovieが多く、定性的ではありますがジュラシック・パークとは性質の異なるmovieが計算されているように感じます。</p> <h3 id="トイストーリーと似ている--似ていない映画">トイ・ストーリーと似ている / 似ていない映画</h3> <p>トイ・ストーリーでもチェックしてみます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># トイ・ストーリーのmovie_idxを取得</span> movie_id = <span class="synConstant">1</span> <span class="synComment"># トイ・ストーリー</span> movie_idx = train.query(<span class="synConstant">'movieId == @movie_id'</span>)[<span class="synConstant">'movie_idx'</span>].unique()[<span class="synConstant">0</span>] <span class="synComment"># 類似度の高い順</span> sim_df = sim_movie(movie, movie_idx, <span class="synIdentifier">False</span>, <span class="synConstant">20</span>) </pre> <p><figure class="figure-image figure-image-fotolife" title="トイ・ストーリーと類似度が高いmovie TOP:20"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209222810.png" alt="f:id:taxa_program:20201209222810p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>トイ・ストーリーと類似しているmovie TOP:20</figcaption></figure></p> <p>トイ・ストーリー2, トイ・ストーリー3, といったシリーズや、ディズニー作品が上位に多く出てきているので、こちらも比較的良い結果になったと言えそうです。</p> <p>類似度が低いmovieも見ておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 類似度が低い順</span> sim_df = sim_movie(movie, movie_idx, <span class="synIdentifier">True</span>, <span class="synConstant">20</span>) </pre> <p><figure class="figure-image figure-image-fotolife" title="トイ・ストーリーと類似度が低いmovie TOP:20"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20201209/20201209223119.png" alt="f:id:taxa_program:20201209223119p:plain" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>トイ・ストーリーと類似度が低いmovie TOP:20</figcaption></figure></p> <p>こちらはスリラーやコメディー系のmovieが多くあり、ジュラシック・パーク同様に良い結果が出ていると言えそうです。</p> <h1 id="最後に">最後に</h1> <p>本日は、レコメンデーションの1つの手法であるMatrix Factorization(行列分解)について、tf.kerasを用いて実装してみました。<br /> レコメンデーションは奥が深く、まだまだ学習すべきことは多いですが、今関わっているプロダクトを通じて、ユーザーに価値を届けられるようにチャレンジし続けたいと思います。</p> <div class="footnote"> <p class="footnote"><a href="#fn-3d3f2026" name="f-3d3f2026" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://www.tensorflow.org/api_docs/python/tf/keras/layers/Embedding">https://www.tensorflow.org/api_docs/python/tf/keras/layers/Embedding</a></span></p> <p class="footnote"><a href="#fn-72328725" name="f-72328725" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://numpy.org/">https://numpy.org/</a></span></p> <p class="footnote"><a href="#fn-4c6b4760" name="f-4c6b4760" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html">https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html</a></span></p> <p class="footnote"><a href="#fn-0a01c71e" name="f-0a01c71e" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text">tensorflow.kerasではこのような最適化アルゴリズムが用意されています。<a href="https://www.tensorflow.org/api_docs/python/tf/keras/optimizers">https://www.tensorflow.org/api_docs/python/tf/keras/optimizers</a></span></p> </div> taxa_program Data Engineering Study #1「DWH・BIツールのこれまでとこれから」 参加ログ hatenablog://entry/26006613598959899 2020-07-15T21:44:19+09:00 2020-07-19T15:40:19+09:00 こんにちは。takapy(@takapy0210)です。 今回は本日参加したイベントの備忘です。 forkwell.connpass.com どのLTもとても勉強になるイベントでした。 資料は後ほど公開されるものもあるみたいなので、公開され次第追記しようと思います。 Twitterでは #DataEngineeringStudy で盛り上がっていました。 事業を成長させるデータ基盤を作るには memo スポンサーLT1 memo ZOZOTOWNの事業を支えるBigQueryの話 memo スポンサーLT2 memo freeeのデータ基盤におけるDWH/BIの運用事例紹介 memo まとめ … <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200719/20200719154003.png" alt="f:id:taxa_program:20200719154003p:plain" title="f:id:taxa_program:20200719154003p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>今回は本日参加したイベントの備忘です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fforkwell.connpass.com%2Fevent%2F179786%2F" title="Data Engineering Study #1「DWH・BIツールのこれまでとこれから」 (2020/07/15 19:30〜)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://forkwell.connpass.com/event/179786/">forkwell.connpass.com</a></cite></p> <p>どのLTもとても勉強になるイベントでした。<br /> 資料は後ほど公開されるものもあるみたいなので、公開され次第追記しようと思います。</p> <p>Twitterでは <strong>#DataEngineeringStudy</strong> で盛り上がっていました。</p> <ul class="table-of-contents"> <li><a href="#事業を成長させるデータ基盤を作るには">事業を成長させるデータ基盤を作るには</a><ul> <li><a href="#memo">memo</a></li> </ul> </li> <li><a href="#スポンサーLT1">スポンサーLT1</a><ul> <li><a href="#memo-1">memo</a></li> </ul> </li> <li><a href="#ZOZOTOWNの事業を支えるBigQueryの話">ZOZOTOWNの事業を支えるBigQueryの話</a><ul> <li><a href="#memo-2">memo</a></li> </ul> </li> <li><a href="#スポンサーLT2">スポンサーLT2</a><ul> <li><a href="#memo-3">memo</a></li> </ul> </li> <li><a href="#freeeのデータ基盤におけるDWHBIの運用事例紹介">freeeのデータ基盤におけるDWH/BIの運用事例紹介</a><ul> <li><a href="#memo-4">memo</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="事業を成長させるデータ基盤を作るには">事業を成長させるデータ基盤を作るには</h1> <ul> <li>データ基盤をなぜ作るのか</li> <li>データ基盤には何が必要か</li> <li>データ基盤をどのように実現するか</li> </ul> <p><iframe id="talk_frame_652191" src="//speakerdeck.com/player/df39dc79c2164b45bc1660fa97825217" width="710" height="399" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/yuzutas0/20200715">speakerdeck.com</a></cite></p> <h2 id="memo">memo</h2> <ul> <li>データ基盤大事だよな〜と改めて思った <ul> <li>正しい意思決定するため</li> <li>現場と経営をつないで、顧客価値を提供するため</li> </ul> </li> <li>データの品質・可用性の観点で最初に議論しておくの大事そう</li> <li>modelとviewで分けて考える <ul> <li>model:データの蓄積や加工</li> <li>view:データの参照や活用 <ul> <li>ある程度データ基盤できてきたら、view(BIツール)で試行錯誤して定常的に使うようになったらDWH(model)側に組み込む、みたいな運用が良さそう</li> </ul> </li> </ul> </li> <li>部署ごとに売り上げがずれる問題、めっちゃ分かる <ul> <li>見るツールによって数値が若干ズレてるのでどこかで統一したいなぁ</li> <li>KPIの指標とか</li> </ul> </li> <li>データ基盤を構築する際はデータの階層を分けて考える <ul> <li>データレイク <ul> <li>RAWデータをそのまま格納したもの(汚いデータもそもままおくのが重要)</li> </ul> </li> <li>データウェアハウス <ul> <li>複数のデータを統合・蓄積して分析向けに整理したもの <ul> <li>顧客情報テーブル、など</li> </ul> </li> </ul> </li> <li>データマート <ul> <li>特定の利用者ごとに分離したもの</li> </ul> </li> <li>上記を全部BQで構築する際、命名規則で管理する、といったことが可能</li> </ul> </li> <li>DWH構築・運用する際には、申請手順のワークフロー組んでおいた方がよさそう(同じようなテーブルが乱立しないためにも) <ul> <li>こんなデータを分析したいので</li> <li>こんな集計テーブルが欲しい</li> <li>期間はこのくらい</li> <li>といった内容を明記したフォーマット的なもの</li> </ul> </li> </ul> <h1 id="スポンサーLT1">スポンサーLT1</h1> <ul> <li>Embulkのマネージドサービス troccoの話</li> </ul> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.trocco.io%2F" title="trocco | 「日本発」の分析基盤向けデータ統合自動化サービス" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://blog.trocco.io/">blog.trocco.io</a></cite></p> <p>@資料は公開され次第追加</p> <h2 id="memo-1">memo</h2> <ul> <li>DWHを1から構築するには480時間ほどかかるみたい</li> <li>作ったら終わりではなく、そこからの運用も結構大変</li> <li>あとからどんどん要望が湧いてくる・・・ <ul> <li>分析するためのDWHなのに、いつまでも分析できない・・・</li> </ul> </li> <li><p>そこでtrocco(トロッコ)を使ってみては?</p> <ul> <li>Embulkのマネージドサービス</li> <li>好きな言語で開発できる</li> <li>GUIで操作も可能</li> <li>Githubでコード管理もできる</li> </ul> </li> <li><p>以下で体験記が見れる<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.trocco.io%2Fdev-articles%2Fyuzutas0-trocco-review-1" title="【trocco体験記】第1回「troccoを使ってみた」by yuzutas0(ゆずたそ) | trocco(トロッコ)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://blog.trocco.io/dev-articles/yuzutas0-trocco-review-1">blog.trocco.io</a></cite></p></li> </ul> <h1 id="ZOZOTOWNの事業を支えるBigQueryの話">ZOZOTOWNの事業を支えるBigQueryの話</h1> <p>ロビンマスクが登場しました・・・(見てた人しか分からないネタ)</p> <ul> <li>ZOZOのデータ基盤のお話</li> <li>BigQueryの辛いところ</li> <li>現在PoC中の基盤のお話</li> </ul> <p>@資料は公開され次第追加</p> <h2 id="memo-2">memo</h2> <ul> <li>全社のKPIが1つのツールで共有されているの良さそう</li> <li>オンプレとクラウドのDWHを比較すると、圧倒的にクラウドが便利 <ul> <li>BQは容量無限大!これは確かに強いよな〜</li> </ul> </li> <li>redashのver up追従するの辛いの分かる・・</li> <li>BIチームが専属でいるの強いな</li> <li>LookMLはGithubでコード管理できて便利そう</li> <li>BigQueryの辛いところ <ul> <li>いろんな人が SELECT * を試し始めてお金が飛んだ</li> <li>Service Usage APIでスキャン量が多いクエリを検知できる</li> <li>コスト予測がし辛い <ul> <li>Flat-rate pricingを入れるとコストを固定できる</li> <li>クエリのバースト性能が落ちる可能性がある</li> </ul> </li> </ul> </li> </ul> <h1 id="スポンサーLT2">スポンサーLT2</h1> <ul> <li>Forkwellのお話</li> </ul> <p><a href="https://portfolio.forkwell.com/about">https://portfolio.forkwell.com/about</a></p> <h2 id="memo-3">memo</h2> <ul> <li>使ってみると面白そう(某prasと似ている感じなのかな?)</li> <li>スポンサーの回数が500回を超えてるらしい(すごい)</li> </ul> <h1 id="freeeのデータ基盤におけるDWHBIの運用事例紹介">freeeのデータ基盤におけるDWH/BIの運用事例紹介</h1> <ul> <li>AWSを用いてデータ基盤を構築している <ul> <li>Athena</li> <li>Redshift</li> </ul> </li> <li>Redshiftとredashの運用事例紹介</li> </ul> <p><iframe id="talk_frame_652661" src="//speakerdeck.com/player/e772b2b22a56456b8dccdd2c42c874c1" width="710" height="399" style="border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe> <cite class="hatena-citation"><a href="https://speakerdeck.com/yusuken/bifalseyun-yong-shi-li-shao-jie">speakerdeck.com</a></cite></p> <h2 id="memo-4">memo</h2> <ul> <li>freeeさんは社員全員SQLがかけるみたい(?)</li> <li><p>Redshift</p> <ul> <li>マスク処理、カラム除外をしたデータ</li> <li>クラスタ3台を使っている</li> <li>Redshiftの良いところ <ul> <li>コストの見通し建てやすい</li> <li>S3との相性が良い</li> <li>集計クエリは比較的回しやすい</li> </ul> </li> <li>Redshiftで苦労しているところ <ul> <li>キャパプラ難しい</li> <li>テーブルのチューニングが必要<br /> →このあたりはBigQueryと対比してそう</li> </ul> </li> </ul> </li> <li><p>redash</p> <ul> <li>EC2インスタンス上にdocker入れて運用</li> <li>監視はMackerel</li> <li>良いところ <ul> <li>OSSなので運用費がやすい!</li> <li>spread sheetへの連携も簡単</li> </ul> </li> <li>苦労しているところ <ul> <li>SQLが書けないと使えない</li> <li>思いクエリが多発するとQueteが詰まる</li> </ul> </li> </ul> </li> </ul> <h1 id="まとめ">まとめ</h1> <p>オンラインイベントには何回か参加していますが、やっぱり気軽に参加できるのが良いですね。</p> <p>今回のイベントは参加者数が1000人を超えていることもあり、データ基盤への期待だったり課題を抱えている人が多いんだなぁと思いました。</p> <p>機械学習とデータは切っても切れない関係だと思うので、自分でもデータ基盤構築・データマネジメントの知識はキャッチアップしていきたいと思います。</p> taxa_program ProbSpace「YouTube動画視聴回数予測コンペ」参加メモ 〜MLflow Trackingによる実験管理を添えて〜 hatenablog://entry/26006613593949019 2020-07-05T12:55:07+09:00 2020-07-05T13:13:17+09:00 こんにちは。takapy(@takapy0210)です。 今回はProbSpaceで開催されていた「YouTube動画視聴回数予測」コンペに参加し、その中でMLflow Trackingで実験管理を行ってみましたので、簡単に振り返りをしようと思います。 ちなみに結果はPrublic 13th → Private 10thでした。 prob.space コンペの概要 YouTube APIとして公開されているメタデータを用いて動画の視聴回数を予測する、というものでした。 データには 動画コンテンツの質的指標となるlike/dislike・コメント数 SEOとして重要とされているタイトル名・説明文… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200705/20200705095550.png" alt="f:id:taxa_program:20200705095550p:plain:w800" title="f:id:taxa_program:20200705095550p:plain:w800" class="hatena-fotolife" style="width:800px" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>今回はProbSpaceで開催されていた「YouTube動画視聴回数予測」コンペに参加し、その中でMLflow Trackingで実験管理を行ってみましたので、簡単に振り返りをしようと思います。</p> <p>ちなみに<strong>結果はPrublic 13th → Private 10thでした。</strong></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fprob.space%2Fcompetitions%2Fyoutube-view-count" title="YouTube動画視聴回数予測" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://prob.space/competitions/youtube-view-count">prob.space</a></cite></p> <h1>コンペの概要</h1> <p>YouTube APIとして公開されているメタデータを用いて動画の視聴回数を予測する、というものでした。</p> <p>データには</p> <ul> <li>動画コンテンツの質的指標となるlike/dislike・コメント数</li> <li>SEOとして重要とされているタイトル名・説明文・タグ・投稿時間</li> </ul> <p>といった情報が含まれており、いわゆる"マルチモーダル"なデータセットで、かつ身近なサービスのデータということもあり、分析していてとても楽しかったです。</p> <p>評価指標はRMSLE(Root Mean Squared Log Error)でした。</p> <p>開催直後に1subだけしたものの、その後あまり参加できず、最後の10日間くらいでフルコミットしました。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">youtubeコンペ1ヶ月振りにsubした <a href="https://t.co/kuCVpHzfcA">pic.twitter.com/kuCVpHzfcA</a></p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1274045431463329794?ref_src=twsrc%5Etfw">2020年6月19日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h1>Solution</h1> <p>簡単にSolutionをまとめておきます。</p> <p>モデルはLightGBM / Catboostを試しましたが、自分の環境ではLightGBMの方がスコアが良かった(CV / LB共に)ため、最終的にはLightGBM * 5モデルを用いました。</p> <p>詳細はコードを公開しましたので、こちらを参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fprobspace_youtube" title="takapy0210/probspace_youtube" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/probspace_youtube">github.com</a></cite></p> <h2>特徴量</h2> <h3>日付</h3> <ul> <li>年, 月, 日に変換</li> <li>曜日, 週末</li> <li>それぞれを三角関数を通して変換</li> <li>データ収集日と動画公開日の差分</li> <li>1日あたりのlike数 / dislike数 / comment数</li> </ul> <p>など</p> <h3>テキスト(主にtitle, description, tags)</h3> <ul> <li>文字列の長さ</li> <li>BERT<a href="#f-86a9b009" name="fn-86a9b009" title="[https://github.com/google-research/bert:embed:cite]">*1</a>とTF-IDF<a href="#f-0a96f747" name="fn-0a96f747" title="[https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html:embed:cite]">*2</a>で埋め込み→UMAP<a href="#f-8e90fdae" name="fn-8e90fdae" title="[https://github.com/lmcinnes/umap:embed:cite]">*3</a>(10dim)&amp; t-SNE<a href="#f-0af3f89c" name="fn-0af3f89c" title="[https://github.com/dominiek/python-bhtsne:embed:cite]">*4</a>(2dim)で圧縮</li> <li><code>music</code>や<code>nursery</code>などの特定ワードが含まれているか</li> </ul> <p>など</p> <p>BERTを用いてテキストの埋め込みを取得する方法は下記を参照にしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fkaeru-nantoka.hatenablog.com%2Fentry%2F2020%2F05%2F29%2F144745" title=" BERT で簡単に日本語の文章の特徴ベクトルを取得できるクラス作った - かえるのプログラミングブログ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://kaeru-nantoka.hatenablog.com/entry/2020/05/29/144745">kaeru-nantoka.hatenablog.com</a></cite></p> <h3>集計特徴量</h3> <ul> <li>カテゴリごと</li> <li>動画公開日の「年, 月」ごと</li> <li>動画公開日からデータ収集日までの月数をbin分割したものごと</li> </ul> <p>に sum, mean, std, ,max, min, meanと自身との差分を算出</p> <p>など</p> <h2>CV</h2> <ul> <li>KFold(k=5)</li> </ul> <p>ここはもっと工夫の余地があったと思います。反省。</p> <h2>その他の工夫</h2> <p>自分の環境では、特徴量を増減させることでカテゴリごとのスコアがかなり変動しました。<br /> そこで、LBのスコアが同等で使用した特徴量が異なる5つのモデルの中から、カテゴリごとに一番良いスコアを出しているモデルを選択し、そのカテゴリの予測値は選択したモデルの予測値を用いる、ということをしました。</p> <p>結果的にはこれが一番スコアが良かったです。</p> <p>カテゴリ毎にスコアを出して、それぞれベストなモデルを選択する、といった部分では後述するMLflow Trackingを用いることで比較的低コストで行うことができました。</p> <p>comments_disabled, ratings_disabledそれぞれのコメント数やlikes/dislikesを予測して欠損値補完する、といった工夫は他の参加者の方も結構やっていたのですが、自分の頭からはすっかり抜け落ちていました。。。</p> <h1>MLflow Trackingによる実験管理</h1> <p>以降で簡単に MLflow<a href="#f-f9877ed3" name="fn-f9877ed3" title="[https://www.mlflow.org/docs/latest/tracking.html:embed:cite]">*5</a>の使用感などをお伝えします。</p> <p>使用するにあたり、以下を参考にしました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fupura.hatenablog.com%2Fentry%2F2020%2F06%2F15%2F120400" title="「atmaCup#5 振り返り会」で「MLflow Tracking を用いた実験管理」について発表しました - u++の備忘録" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://upura.hatenablog.com/entry/2020/06/15/120400">upura.hatenablog.com</a></cite></p> <p><a href="https://www.slideshare.net/maropu0804/mlflow">MLflow&#x306B;&#x3088;&#x308B;&#x6A5F;&#x68B0;&#x5B66;&#x7FD2;&#x30E2;&#x30C7;&#x30EB;&#x306E;&#x30E9;&#x30A4;&#x30D5;&#x30B5;&#x30A4;&#x30AF;&#x30EB;&#x306E;&#x7BA1;&#x7406;</a></p> <p>また、Podcastでも使用感などを話しましたので、よければ聞いてみてください。</p> <p><iframe src="https://anchor.fm/geek-engineer-future/embed/episodes/7-mlflow-efvshf" height="102px" width="400px" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://anchor.fm/geek-engineer-future/episodes/7-mlflow-efvshf">anchor.fm</a></cite></p> <h2>ML flowとは</h2> <p>機械学習ライフサイクル(実験・再現・デプロイ)を支援するためのオープンソースプラットフォームであり、大別して以下3点の機能があります。</p> <ul> <li>MLflow Tracking <ul> <li>実験周りのコードや設定・結果の記録</li> </ul> </li> <li>MLflow Projects <ul> <li>どこでも再現できるようにするためのパッケージング</li> </ul> </li> <li>MLflow Models <ul> <li>モデルを各環境にデプロイするための方法やフォーマット</li> </ul> </li> </ul> <h2>クイックスタート</h2> <p>公式のサンプル<a href="#f-f987b280" name="fn-f987b280" title="[https://mlflow.org/docs/latest/quickstart.html#using-the-tracking-api:embed:cite]">*6</a>を動かしてみるのが一番手っ取り早いと思います。</p> <p>以下で公式サンプルを少し改変したコードで簡単にみていきます。<br /> pipでインストール後以下のようなサンプルコードを作成しこれを実行します。</p> <p>実験のログを残す箇所は</p> <ul> <li>log_param</li> <li>log_metric</li> <li>log_artifact</li> <li>set_tag</li> </ul> <p>あたりです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> mlflow <span class="synPreProc">from</span> mlflow <span class="synPreProc">import</span> log_metric, log_param, log_artifact, set_tag <span class="synStatement">if</span> __name__ == <span class="synConstant">&quot;__main__&quot;</span>: tracking_uri = <span class="synConstant">'~/mlflow/mlruns'</span> mlflow.set_tracking_uri(tracking_uri) mlflow.set_experiment(<span class="synConstant">&quot;test-experiment&quot;</span>) mlflow.start_run(run_name=<span class="synConstant">'run_name001'</span>) <span class="synComment"># Log a parameter (key-value pair)</span> log_param(<span class="synConstant">'param1'</span>, <span class="synConstant">42</span>) <span class="synComment"># Log a metric; metrics can be updated throughout the run</span> log_metric(<span class="synConstant">'fold1_score'</span>, <span class="synConstant">9.99</span>) log_metric(<span class="synConstant">'fold2_score'</span>, <span class="synConstant">9.92</span>) log_metric(<span class="synConstant">'fold3_score'</span>, <span class="synConstant">9.78</span>) <span class="synComment"># Log an artifact (output file)</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">&quot;output.txt&quot;</span>, <span class="synConstant">&quot;w&quot;</span>) <span class="synStatement">as</span> f: f.write(<span class="synConstant">&quot;Hello world sample!&quot;</span>) log_artifact(<span class="synConstant">&quot;output.txt&quot;</span>) set_tag(<span class="synConstant">'tag1'</span>, <span class="synConstant">'this is tag1'</span>) set_tag(<span class="synConstant">'tag2'</span>, <span class="synConstant">'this is tag2'</span>) mlflow.end_run() </pre> <p>例えば上記をsample.pyというファイルで保存している場合</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ python sample.py </pre> <p>その後、<code>~/mlflow</code>に移動し下記コマンドでmlflowのダッシュボードが起動します</p> <pre class="code" data-lang="" data-unlink>$ mlflow ui</pre> <p>この状態でブラウザで<code>http://localhost:5000</code> にアクセスすると、上記で実行した実験の結果が表示されます。</p> <p><figure class="figure-image figure-image-fotolife" title="MLflow UIのサンプル"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200705/20200705121321.png" alt="f:id:taxa_program:20200705121321p:plain" title="f:id:taxa_program:20200705121321p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>MLflow UIのサンプル</figcaption></figure></p> <h2>コンペでの使用例と所感</h2> <p>今回のコンペでは、今までloggerで出力していたもの(CVのスコアなど)に加えて、モデルのハイパーパラメータなどを保存する形で使用しました。</p> <p>以下のように<code>log_param</code>にはCVやモデルのパラメータ、各種設定を辞書形式で保存し、</p> <p><figure class="figure-image figure-image-fotolife" title="ProbSpaceで使用した管理例"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200705/20200705121538.png" alt="f:id:taxa_program:20200705121538p:plain" title="f:id:taxa_program:20200705121538p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>管理例(メトリクス)</figcaption></figure></p> <p>log_metricには、各CVスコアとカテゴリごとのスコア、tagには<code>LB</code>というKeyに対して、LBでのスコアを記載するように管理しました。 <figure class="figure-image figure-image-fotolife" title="管理例(CVスコア)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200705/20200705121739.png" alt="f:id:taxa_program:20200705121739p:plain" title="f:id:taxa_program:20200705121739p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>管理例(CVスコア)</figcaption></figure></p> <p>これにより、<strong>どのモデルが「どんなパラメータ, 特徴量, CVで学習させたもの」か、そして「それぞれのCVスコアとLBのスコア」を一覧で確認することができ、とても便利でした。</strong></p> <p>「その他の工夫」項でも述べましたが、このダッシュボードのおかげて、モデルごとのカテゴリのスコアが管理することができたので、<strong>「このカテゴリの予測値は、このモデルのものを使用する」</strong>といったことが比較的低コストで実践できました。</p> <p>また、モデルごとにスコアを比較し簡単なプロットもできたりします。</p> <p><figure class="figure-image figure-image-fotolife" title="7つのモデルのfoldのスコアと全データスコアの散布図"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200705/20200705122715.png" alt="f:id:taxa_program:20200705122715p:plain" title="f:id:taxa_program:20200705122715p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>fold0のスコアと全データのスコアの散布図</figcaption></figure></p> <p>今回紹介した管理方法(log_paramにどのような値を保存するか、など)はあくまで自分が試してみた方法なので、いくつも改善できる箇所はあると思います。今後も試行錯誤しながらより良い形にしていければと思います。</p> <h1>最後に</h1> <p>参加者及び運営の皆様、楽しいコンペをありがとうございました。</p> <p>今回は本格的に参加してから締め切りまでの期間が短く、サムネイル(画像)の特徴量はほぼ手付かずでした。また、CVの切り方も特に工夫していなかったのが反省点です。</p> <p>この辺りは他の参加者の方の解法を参考に勉強しようと思います。</p> <div class="footnote"> <p class="footnote"><a href="#fn-86a9b009" name="f-86a9b009" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgoogle-research%2Fbert" title="google-research/bert" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/google-research/bert">github.com</a></cite></span></p> <p class="footnote"><a href="#fn-0a96f747" name="f-0a96f747" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fscikit-learn.org%2Fstable%2Fmodules%2Fgenerated%2Fsklearn.feature_extraction.text.TfidfVectorizer.html" title="sklearn.feature_extraction.text.TfidfVectorizer — scikit-learn 0.23.1 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.TfidfVectorizer.html">scikit-learn.org</a></cite></span></p> <p class="footnote"><a href="#fn-8e90fdae" name="f-8e90fdae" class="footnote-number">*3</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Flmcinnes%2Fumap" title="lmcinnes/umap" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/lmcinnes/umap">github.com</a></cite></span></p> <p class="footnote"><a href="#fn-0af3f89c" name="f-0af3f89c" class="footnote-number">*4</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fdominiek%2Fpython-bhtsne" title="dominiek/python-bhtsne" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/dominiek/python-bhtsne">github.com</a></cite></span></p> <p class="footnote"><a href="#fn-f9877ed3" name="f-f9877ed3" class="footnote-number">*5</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.mlflow.org%2Fdocs%2Flatest%2Ftracking.html" title="MLflow Tracking — MLflow 1.9.1 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.mlflow.org/docs/latest/tracking.html">www.mlflow.org</a></cite></span></p> <p class="footnote"><a href="#fn-f987b280" name="f-f987b280" class="footnote-number">*6</a><span class="footnote-delimiter">:</span><span class="footnote-text"><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmlflow.org%2Fdocs%2Flatest%2Fquickstart.html%23using-the-tracking-api" title="Quickstart — MLflow 1.9.1 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://mlflow.org/docs/latest/quickstart.html#using-the-tracking-api">mlflow.org</a></cite></span></p> </div> taxa_program 自然言語を簡単に可視化・分析できるライブラリ「nlplot」を公開しました hatenablog://entry/26006613569197918 2020-05-17T19:29:47+09:00 2022-09-21T23:46:58+09:00 こんにちは。たかぱい(@takapy0210)です。 本日は自然言語の可視化を手軽にできるようにしたパッケージnlplotをPyPIに公開したので、これのご紹介です。 nlplotとは? nlplotで何ができるか 使い方 使用データ 事前準備 ストップワードの計算 N-gram bar chart N-gram tree Map Histogram of the word count wordcloud co-occurrence networks sunburst chart まとめ nlplotとは? 自然言語の基本的な可視化を手軽にできるようにしたパッケージです。 現在は日本語と英語で… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517182641.png" width="1200" height="676" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本日は自然言語の可視化を手軽にできるようにしたパッケージ<strong>nlplot</strong>をPyPIに公開したので、これのご紹介です。</p> <ul class="table-of-contents"> <li><a href="#nlplotとは">nlplotとは?</a></li> <li><a href="#nlplotで何ができるか">nlplotで何ができるか</a></li> <li><a href="#使い方">使い方</a><ul> <li><a href="#使用データ">使用データ</a></li> <li><a href="#事前準備">事前準備</a></li> <li><a href="#ストップワードの計算">ストップワードの計算</a></li> <li><a href="#N-gram-bar-chart">N-gram bar chart</a></li> <li><a href="#N-gram-tree-Map">N-gram tree Map</a></li> <li><a href="#Histogram-of-the-word-count">Histogram of the word count</a></li> <li><a href="#wordcloud">wordcloud</a></li> <li><a href="#co-occurrence-networks">co-occurrence networks</a></li> <li><a href="#sunburst-chart">sunburst chart</a></li> </ul> </li> <li><a href="#まとめ">まとめ</a></li> </ul> <h1 id="nlplotとは">nlplotとは?</h1> <p>自然言語の基本的な可視化を手軽にできるようにしたパッケージです。</p> <p>現在は日本語と英語で動作確認済みです。<br /> 基本的な描画は<a href="https://plotly.com/python/">plotly</a>を用いているため、notebook上からインタラクティブにグラフを操作することができます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlplot" title="GitHub - takapy0210/nlplot: Visualization Module for Natural Language Processing" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlplot">github.com</a></cite> (スター★お待ちしております🙇‍♂️)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fpypi.org%2Fproject%2Fnlplot%2F" title="nlplot" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://pypi.org/project/nlplot/">pypi.org</a></cite></p> <h1 id="nlplotで何ができるか">nlplotで何ができるか</h1> <p>現時点(ver 1.6.0)では下記のグラフをプロットできます。(リンク先で動的なグラフを表示できます)</p> <ul> <li><a href="https://htmlpreview.github.io/?https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/2020-05-17_uni-gram.html">N-gram bar chart</a></li> <li><a href="https://htmlpreview.github.io/?https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/2020-05-17_Tree%20of%20Most%20Common%20Words.html">N-gram tree Map</a></li> <li><a href="https://htmlpreview.github.io/?https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/2020-05-17_number%20of%20words%20distribution.html">Histogram of the word count</a></li> <li><a href="https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/wordcloud.png">wordcloud</a></li> <li><a href="https://htmlpreview.github.io/?https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/2020-05-17_Co-occurrence%20network.html">co-occurrence networks</a></li> <li><a href="https://htmlpreview.github.io/?https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/2020-05-17_sunburst%20chart.html">sunburst chart</a></li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">自然言語入れたらよしなに可視化してくれるやつの大枠はできたので、PyPIへ登録やっていき <a href="https://t.co/waPhLC0ugA">pic.twitter.com/waPhLC0ugA</a></p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1257958868446896132?ref_src=twsrc%5Etfw">2020年5月6日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>以降で簡単に使い方をご紹介します。</p> <h1 id="使い方">使い方</h1> <p>詳しい使用方法のコード・今回使用したデータは<a href="https://github.com/takapy0210/takapy_blog/blob/master/nlp/twitter_analytics_using_nlplot/introduction_nlplot_twitter.ipynb">Github</a>にあげてあります。 <br /> また、Tweet Sentiment Extractionコンペの<a href="https://www.kaggle.com/takanobu0210/twitter-sentiment-analysis-eda-using-nlplot">カーネル</a>も公開しておきました。</p> <h2 id="使用データ">使用データ</h2> <p>今回は、Twitterからハッシュタグ「#kaggle」と「#データサイエンティスト」がついているツイートをスクレイピングし、そのタグを分析してみました。</p> <p>データの形式は下記のようなデータフレームを想定しています。<br /> textのカラムはスペース区切りの文字列 or リスト型のカラムを想定しています。</p> <p>日本語の場合はお好きな形態素解析器で事前にtokenizeをお願いします。<br /> (このパッケージにtokenizerも含めるか迷いましたが、あくまで<code>可視化</code>のみを責務としているので、含めていません)</p> <table> <thead> <tr> <th> </th> <th> searched_for </th> <th> hashtags </th> </tr> </thead> <tbody> <tr> <td> 0 </td> <td> #データサイエンティスト </td> <td> データマイニング データサイエンス データサイエンティスト </td> </tr> <tr> <td> 1 </td> <td> #データサイエンティスト </td> <td> 統計学 人工知能 ダイヤモンド データサイエンティスト プログラミング </td> </tr> <tr> <td> 2 </td> <td> #データサイエンティスト </td> <td> 筋トレ 今日の積み上げ 駆け出しエンジニアと繋がりたい データサイエンティスト </td> </tr> <tr> <td> ... </td> <td> ... </td> <td> ... </td> </tr> <tr> <td> N </td> <td> #kaggle </td> <td> python kaggle タイタニック </td> </tr> </tbody> </table> <h2 id="事前準備">事前準備</h2> <p>インストールはpipで可能です。</p> <pre class="code" data-lang="" data-unlink>pip install nlplot</pre> <p>事前にデータを読み込み、nlplotのインスタンスを生成しておきます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> nlplot <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> plotly <span class="synPreProc">from</span> plotly.subplots <span class="synPreProc">import</span> make_subplots <span class="synPreProc">from</span> plotly.offline <span class="synPreProc">import</span> iplot <span class="synPreProc">import</span> matplotlib.pyplot <span class="synStatement">as</span> plt %matplotlib inline df = pd.read_csv(<span class="synConstant">'sample_twitter.csv'</span>) <span class="synComment"># 全データ・#データサイエンティスト・#kaggleをそれぞれインスタンス化</span> npt = nlplot.NLPlot(df, target_col=<span class="synConstant">'hashtags'</span>) npt_ds = nlplot.NLPlot(df.query(<span class="synConstant">'searched_for == &quot;#データサイエンティスト&quot;'</span>), target_col=<span class="synConstant">'hashtags'</span>) npt_kaggle = nlplot.NLPlot(df.query(<span class="synConstant">'searched_for == &quot;#kaggle&quot;'</span>), target_col=<span class="synConstant">'hashtags'</span>) </pre> <h2 id="ストップワードの計算">ストップワードの計算</h2> <p>下記のようにストップワードの計算ができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># top_nで頻出上位単語, min_freqで頻出下位単語を指定できる</span> <span class="synComment"># 今回は上位2単語(データサイエンティスト・kaggle)をストップワードに指定</span> stopwords = npt.get_stopword(top_n=<span class="synConstant">2</span>, min_freq=<span class="synConstant">0</span>) </pre> <h2 id="N-gram-bar-chart">N-gram bar chart</h2> <p>よく見聞きするアレです。</p> <p><code>ngram</code>の引数に与える数値により、いくつ隣り合わせの単語までを考慮するかを指定できます。</p> <pre class="code lang-python" data-lang="python" data-unlink>fig_unigram = npt.bar_ngram( title=<span class="synConstant">'uni-gram'</span>, xaxis_label=<span class="synConstant">'word_count'</span>, yaxis_label=<span class="synConstant">'word'</span>, ngram=<span class="synConstant">1</span>, top_n=<span class="synConstant">50</span>, width=<span class="synConstant">800</span>, height=<span class="synConstant">1100</span>, color=<span class="synIdentifier">None</span>, horizon=<span class="synIdentifier">True</span>, stopwords=stopwords, verbose=<span class="synIdentifier">False</span>, save=<span class="synIdentifier">False</span>, ) fig_unigram.show() </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191217.png" width="799" height="1072" loading="lazy" title="" class="hatena-fotolife" style="width:600px" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>下記のようにすることで、データをラベルごとに比較することもできます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># #データサイエンティストのfigを取得</span> fig_unigram_ds = npt_ds.bar_ngram( title=<span class="synConstant">'uni-gram'</span>, xaxis_label=<span class="synConstant">'word_count'</span>, yaxis_label=<span class="synConstant">'word'</span>, ngram=<span class="synConstant">1</span>, top_n=<span class="synConstant">50</span>, stopwords=stopwords, ) <span class="synComment"># #kaggleのfigを取得</span> fig_unigram_kaggle = npt_kaggle.bar_ngram( title=<span class="synConstant">'uni-gram'</span>, xaxis_label=<span class="synConstant">'word_count'</span>, yaxis_label=<span class="synConstant">'word'</span>, ngram=<span class="synConstant">1</span>, top_n=<span class="synConstant">50</span>, stopwords=stopwords, ) <span class="synComment"># subplot</span> trace1 = fig_unigram_ds[<span class="synConstant">'data'</span>][<span class="synConstant">0</span>] trace2 = fig_unigram_kaggle[<span class="synConstant">'data'</span>][<span class="synConstant">0</span>] fig = make_subplots(rows=<span class="synConstant">1</span>, cols=<span class="synConstant">2</span>, subplot_titles=(<span class="synConstant">'#データサイエンティスト'</span>, <span class="synConstant">'#kaggle'</span>), shared_xaxes=<span class="synIdentifier">False</span>) fig.update_xaxes(title_text=<span class="synConstant">'word count'</span>, row=<span class="synConstant">1</span>, col=<span class="synConstant">1</span>) fig.update_xaxes(title_text=<span class="synConstant">'word count'</span>, row=<span class="synConstant">1</span>, col=<span class="synConstant">2</span>) fig.update_layout(height=<span class="synConstant">1100</span>, width=<span class="synConstant">1900</span>, title_text=<span class="synConstant">'unigram #データサイエンティスト vs. #kaggle'</span>) fig.add_trace(trace1, row=<span class="synConstant">1</span>, col=<span class="synConstant">1</span>) fig.add_trace(trace2, row=<span class="synConstant">1</span>, col=<span class="synConstant">2</span>) plotly.offline.plot(fig, filename=<span class="synConstant">'unigram #データサイエンティストvs#kaggle.html'</span>, auto_open=<span class="synIdentifier">False</span>) fig.show() </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191250.png" width="1200" height="681" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>以降のグラフも上記のようにコーディングすることで、特定のラベルごとに比較することができます。</p> <h2 id="N-gram-tree-Map">N-gram tree Map</h2> <p>こちらも同様、<code>ngram</code>の引数に与える数値により、いくつ隣り合わせの単語までを考慮するかを指定できます。</p> <pre class="code lang-python" data-lang="python" data-unlink>fig_treemap = npt.treemap( title=<span class="synConstant">'Tree map'</span>, ngram=<span class="synConstant">1</span>, top_n=<span class="synConstant">50</span>, width=<span class="synConstant">1300</span>, height=<span class="synConstant">600</span>, stopwords=stopwords, verbose=<span class="synIdentifier">False</span>, save=<span class="synIdentifier">False</span> ) fig_treemap.show() </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191308.png" width="1200" height="518" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="Histogram-of-the-word-count">Histogram of the word count</h2> <p>単語の出現頻度のヒストグラムです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 単語数の分布</span> fig_histgram = npt.word_distribution( title=<span class="synConstant">'word distribution'</span>, xaxis_label=<span class="synConstant">'count'</span>, yaxis_label=<span class="synConstant">''</span>, width=<span class="synConstant">1000</span>, height=<span class="synConstant">500</span>, color=<span class="synIdentifier">None</span>, template=<span class="synConstant">'plotly'</span>, bins=<span class="synIdentifier">None</span>, save=<span class="synIdentifier">False</span>, ) fig_histgram.show() </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191330.png" width="938" height="472" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="wordcloud">wordcloud</h2> <p>みんな大好きwordcloudです。日本語でもそのままプロットできます。</p> <pre class="code lang-python" data-lang="python" data-unlink>fig_wc = npt.wordcloud( width=<span class="synConstant">1000</span>, height=<span class="synConstant">600</span>, max_words=<span class="synConstant">100</span>, max_font_size=<span class="synConstant">100</span>, colormap=<span class="synConstant">'tab20_r'</span>, stopwords=stopwords, mask_file=<span class="synIdentifier">None</span>, save=<span class="synIdentifier">False</span> ) plt.figure(figsize=(<span class="synConstant">15</span>, <span class="synConstant">25</span>)) plt.imshow(fig_wc, interpolation=<span class="synConstant">&quot;bilinear&quot;</span>) plt.axis(<span class="synConstant">&quot;off&quot;</span>) plt.show() </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191352.png" width="1017" height="605" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="co-occurrence-networks">co-occurrence networks</h2> <p>共起ネットワークです。</p> <p>このネットワークとサンバーストチャートを描画する場合は、事前にビルド処理が必要です。<br /> このビルド処理の<code>min_edge_frequency</code>引数でプロットするノードの数を制限します。<br /> (指定数以下のエッジ(辺)しか存在しないノードはプロット対象から除外することができます)</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># ビルド(データ件数によっては処理に時間を要します)</span> npt.build_graph(stopwords=stopwords, min_edge_frequency=<span class="synConstant">25</span>) <span class="synComment"># ビルド後にノードとエッジの数が表示される。ノードの数が100前後になるようにするとネットワークが綺麗に描画できる</span> &gt;&gt; node_size:<span class="synConstant">63</span>, edge_size:<span class="synConstant">63</span> fig_co_network = npt.co_network( title=<span class="synConstant">'Co-occurrence network'</span>, sizing=<span class="synConstant">100</span>, node_size=<span class="synConstant">'adjacency_frequency'</span>, color_palette=<span class="synConstant">'hls'</span>, width=<span class="synConstant">1100</span>, height=<span class="synConstant">700</span>, save=<span class="synIdentifier">False</span> ) iplot(fig_co_network) </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191534.png" width="1200" height="785" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>ノードの色は、networkxのcommunitiesで計算したコミュニティを表しています。<br /> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnetworkx.github.io%2Fdocumentation%2Fstable%2Freference%2Falgorithms%2Fcommunity.html%23module-networkx.algorithms.community.modularity_max" title="Communities — NetworkX 2.8.6 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://networkx.github.io/documentation/stable/reference/algorithms/community.html#module-networkx.algorithms.community.modularity_max">networkx.github.io</a></cite></p> <p>ノードの大きさは、networkxのGraph.adjacencyで算出した値の大きさに比例しています。(隣接エッジが多ければ多いほど大きくなります) <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnetworkx.github.io%2Fdocumentation%2Fstable%2Freference%2Fclasses%2Fgenerated%2Fnetworkx.Graph.adjacency.html%3Fhighlight%3Dadjacency%23networkx.Graph.adjacency" title="Graph.adjacency — NetworkX 2.8.6 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://networkx.github.io/documentation/stable/reference/classes/generated/networkx.Graph.adjacency.html?highlight=adjacency#networkx.Graph.adjacency">networkx.github.io</a></cite></p> <p>ちなみにビルド処理で生成されたデータフレーム にもアクセスできます。</p> <pre class="code lang-python" data-lang="python" data-unlink>display( npt.node_df.head(), npt.node_df.shape, npt.edge_df.head(), npt.edge_df.shape ) </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517232013.png" width="783" height="452" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="sunburst-chart">sunburst chart</h2> <p>上記共起ネットワークのコミュニティと、それに属する単語をサンバーストチャートで表示しています。</p> <pre class="code lang-python" data-lang="python" data-unlink>fig_sunburst = npt.sunburst( title=<span class="synConstant">'sunburst chart'</span>, colorscale=<span class="synIdentifier">True</span>, color_continuous_scale=<span class="synConstant">'Oryel'</span>, width=<span class="synConstant">1000</span>, height=<span class="synConstant">800</span>, save=<span class="synIdentifier">False</span> ) fig_sunburst.show() </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200517/20200517191557.png" width="970" height="747" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>色の濃さはnetworkxのbetweenness_centralityで算出した値が大きいほど濃い色で表示されています。(ネットワークの媒介中心性が高ければ高いほど濃い色になります)<br /> 幅は、前述したnetworkxのGraph.adjacencyで算出した値に比例しています。(隣接エッジが多ければ多いほど大きくなります)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnetworkx.github.io%2Fdocumentation%2Fnetworkx-1.10%2Freference%2Fgenerated%2Fnetworkx.algorithms.centrality.betweenness_centrality.html%23betweenness-centrality" title="betweenness_centrality — NetworkX 1.10 documentation" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://networkx.github.io/documentation/networkx-1.10/reference/generated/networkx.algorithms.centrality.betweenness_centrality.html#betweenness-centrality">networkx.github.io</a></cite></p> <h1 id="まとめ">まとめ</h1> <p>不便な部分はどんどん更新していこうと思っていますので、気になる部分や要望などあれば、PR/ISSUEお待ちしております!</p> <p>業務でもKaggleなどのコンペでも短時間でデータの全体像を把握したい時に使っていただければと思います。</p> taxa_program PyPIへのアップロード時に「HTTPError: 400 Client Error: The description failed to render in the default format of reStructuredText.」が出る場合の対処方法 hatenablog://entry/26006613564963970 2020-05-10T15:58:56+09:00 2020-05-10T15:58:56+09:00 こんにちは。takapy(@takapy0210)です。 自作パッケージをPyPIにアップロードしようとしたところ簡易的なミスで数時間溶かしたので、その備忘です。 エラー内容 エラー発生時のsetup.pyの内容 解決方法 やったこと 各種パッケージのアップデート 公式の書き方をもう一度確認 long_descriptionをコメントアウト 最後の希望Twitterへ 最後に エラー内容 $ twine upload -r testpypi dist/* Uploading distributions to https://test.pypi.org/legacy/ Enter your pa… <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>自作パッケージをPyPIにアップロードしようとしたところ簡易的なミスで数時間溶かしたので、その備忘です。</p> <ul class="table-of-contents"> <li><a href="#エラー内容">エラー内容</a><ul> <li><a href="#エラー発生時のsetuppyの内容">エラー発生時のsetup.pyの内容</a></li> </ul> </li> <li><a href="#解決方法">解決方法</a></li> <li><a href="#やったこと">やったこと</a><ul> <li><a href="#各種パッケージのアップデート">各種パッケージのアップデート</a></li> <li><a href="#公式の書き方をもう一度確認">公式の書き方をもう一度確認</a></li> <li><a href="#long_descriptionをコメントアウト">long_descriptionをコメントアウト</a></li> <li><a href="#最後の希望Twitterへ">最後の希望Twitterへ</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="エラー内容">エラー内容</h1> <pre class="code" data-lang="" data-unlink>$ twine upload -r testpypi dist/* Uploading distributions to https://test.pypi.org/legacy/ Enter your password: Uploading nlplot-1.0.0-py3-none-any.whl 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 948k/948k [00:02&lt;00:00, 443kB/s] NOTE: Try --verbose to see response content. HTTPError: 400 Client Error: The description failed to render in the default format of reStructuredText. See https://test.pypi.org/help/#description-content-type for more information. for url: https://test.pypi.org/legacy/ </pre> <h2 id="エラー発生時のsetuppyの内容">エラー発生時のsetup.pyの内容</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> os, sys <span class="synPreProc">from</span> setuptools <span class="synPreProc">import</span> setup, find_packages <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">'README.md'</span>, <span class="synConstant">'r'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: long_description = f.read() <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">'LICENSE.txt'</span>, <span class="synConstant">'r'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: license = f.read() <span class="synStatement">def</span> <span class="synIdentifier">read_requirements</span>(): <span class="synConstant">&quot;&quot;&quot;Parse requirements from requirements.txt.&quot;&quot;&quot;</span> reqs_path = os.path.join(<span class="synConstant">'.'</span>, <span class="synConstant">'requirements.txt'</span>) <span class="synStatement">with</span> <span class="synIdentifier">open</span>(reqs_path, <span class="synConstant">'r'</span>) <span class="synStatement">as</span> f: requirements = [line.rstrip() <span class="synStatement">for</span> line <span class="synStatement">in</span> f] <span class="synStatement">return</span> requirements setup( name=<span class="synConstant">'nlplot'</span>, version=<span class="synConstant">'1.0.0'</span>, description=<span class="synConstant">'Visualization Module for Natural Language Processing'</span>, long_description=long_description, long_description_content_type=<span class="synConstant">'text/markdown'</span>, author=<span class="synConstant">'Takanobu Nozawa'</span>, author_email=<span class="synConstant">'takanobu.030210@gmail.com'</span>, url=<span class="synConstant">'https://github.com/takapy0210/nlplot'</span>, license=license, install_requires=read_requirements(), packages=find_packages(exclude=(<span class="synConstant">'tests'</span>)), package_data={<span class="synConstant">'nlplot'</span>:[<span class="synConstant">'data/*'</span>]}, python_requires=<span class="synConstant">'~=3.6'</span> ) </pre> <p>この状態で <code>twine check</code> を実施すると下記のようなエラーが発生し、<code>twine upload</code> しても冒頭のエラーが発生します。</p> <p>エラー内容をみるに<code>long_description_content_type</code>がうまく機能していない(?)ように見えます。</p> <pre class="code lang-python" data-lang="python" data-unlink> $ twine check dist/* Checking dist/nlplot-<span class="synConstant">1.0</span>.<span class="synConstant">0</span>-py3-none-<span class="synIdentifier">any</span>.whl: FAILED `long_description` has syntax errors <span class="synStatement">in</span> markup <span class="synStatement">and</span> would <span class="synStatement">not</span> be rendered on PyPI. line <span class="synConstant">3</span>: Error: Unexpected indentation. warning: `long_description_content_type` missing. defaulting to `text/x-rst`. Checking dist/nlplot-<span class="synConstant">1.0</span>.<span class="synConstant">0</span>.tar.gz: FAILED `long_description` has syntax errors <span class="synStatement">in</span> markup <span class="synStatement">and</span> would <span class="synStatement">not</span> be rendered on PyPI. line <span class="synConstant">3</span>: Error: Unexpected indentation. warning: `long_description_content_type` missing. defaulting to `text/x-rst`. </pre> <h1 id="解決方法">解決方法</h1> <p><code>license</code>を外部ファイルから読み込むのではなく、文字列をハードコーディングしました。 <br /> (上記エラーの「long_description has syntax errors in markup and would not be rendered on PyPI」とはいったい...)<br /> これだけ見るとなんでこんなことに数時間も気づかなかったのか・・・という感じです。</p> <p>そもそもこの<code>license</code>を外部ファイルから読み込もうとしていたのがダメだったようです。</p> <p>下記が修正後の<code>setup.py</code>です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> os <span class="synPreProc">from</span> setuptools <span class="synPreProc">import</span> setup, find_packages <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">'README.md'</span>, <span class="synConstant">'r'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: long_description = f.read() <span class="synStatement">def</span> <span class="synIdentifier">read_requirements</span>(): <span class="synConstant">&quot;&quot;&quot;Parse requirements from requirements.txt.&quot;&quot;&quot;</span> reqs_path = os.path.join(<span class="synConstant">'.'</span>, <span class="synConstant">'requirements.txt'</span>) <span class="synStatement">with</span> <span class="synIdentifier">open</span>(reqs_path, <span class="synConstant">'r'</span>) <span class="synStatement">as</span> f: requirements = [line.rstrip() <span class="synStatement">for</span> line <span class="synStatement">in</span> f] <span class="synStatement">return</span> requirements setup( name=<span class="synConstant">'nlplot'</span>, version=<span class="synConstant">'1.0.1'</span>, description=<span class="synConstant">'Visualization Module for Natural Language Processing'</span>, long_description=long_description, long_description_content_type=<span class="synConstant">'text/markdown'</span>, author=<span class="synConstant">'Takanobu Nozawa'</span>, author_email=<span class="synConstant">'takanobu.030210@gmail.com'</span>, url=<span class="synConstant">'https://github.com/takapy0210/nlplot'</span>, <span class="synComment"># license=license,</span> license=<span class="synConstant">'MIT License'</span>, install_requires=read_requirements(), packages=find_packages(exclude=(<span class="synConstant">'tests'</span>)), package_data={<span class="synConstant">'nlplot'</span>:[<span class="synConstant">'data/*'</span>]}, python_requires=<span class="synConstant">'~=3.6'</span> ) </pre> <p>testpypiへのアップロードも上手くいきました。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>twine upload <span class="synSpecial">-r</span> testpypi dist/* Uploading distributions to https://<span class="synStatement">test</span>.pypi.org/legacy/ Enter your password: Uploading nlplot-1.<span class="synConstant">0</span>.1-py3-none-any.whl <span class="synConstant">100</span>%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 947k/947k <span class="synStatement">[</span>00:02<span class="synStatement">&lt;</span>00:00, 358kB/s<span class="synStatement">]</span> Uploading nlplot-1.<span class="synConstant">0</span>.<span class="synConstant">1</span>.tar.gz <span class="synConstant">100</span>%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 945k/945k <span class="synStatement">[</span>00:01<span class="synStatement">&lt;</span>00:00, 705kB/s<span class="synStatement">]</span> View at: https://<span class="synStatement">test</span>.pypi.org/project/nlplot/<span class="synConstant">1</span>.<span class="synConstant">0</span>.<span class="synConstant">1</span>/ </pre> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200510/20200510152631.png" alt="f:id:taxa_program:20200510152631p:plain" title="f:id:taxa_program:20200510152631p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>後述していますが、LICENSEファイルを作り方は<a href="[https://help.github.com/en/github/building-a-strong-community/adding-a-license-to-a-repository#including-an-open-source-license-in-your-repository:embed:cite]">Github公式ドキュメント</a>があるので、こちらを参考に作るのが良さそうです。</p> <p>以下、試行錯誤の履歴です。</p> <h1 id="やったこと">やったこと</h1> <p>基本的には下記手順でチェックしました。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>$ python setup.py sdist $ python setup.py bdist_wheel $ twine check dist/* </pre> <p>都度<code>twine upload -r testpypi dist/*</code>コマンドでtestpypiへアップロードできるかどうかチェックしても良いですが、<code>twine check</code>を使ってアップロード前に確認すると、無駄にversionをインクリメントする必要もないので、オススメです。</p> <h2 id="各種パッケージのアップデート">各種パッケージのアップデート</h2> <p>やりましたが、うまくいかず。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>pip install <span class="synSpecial">--upgrade</span> setuptools wheel twine </pre> <h2 id="公式の書き方をもう一度確認">公式の書き方をもう一度確認</h2> <p><code>description</code>周りの書き方をコピーしてみましたが、上手くいかず。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fpackaging.python.org%2Ftutorials%2Fpackaging-projects%2F%23uploading-your-project-to-pypi" title="Packaging Python Projects — Python Packaging User Guide" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://packaging.python.org/tutorials/packaging-projects/#uploading-your-project-to-pypi">packaging.python.org</a></cite></p> <h2 id="long_descriptionをコメントアウト">long_descriptionをコメントアウト</h2> <pre class="code lang-python" data-lang="python" data-unlink>... setup( name=<span class="synConstant">'nlplot'</span>, version=<span class="synConstant">'1.0.0'</span>, description=<span class="synConstant">'Visualization Module for Natural Language Processing'</span>, <span class="synComment"># long_description=long_description,</span> <span class="synComment"># long_description_content_type='text/markdown',</span> author=<span class="synConstant">'Takanobu Nozawa'</span>, author_email=<span class="synConstant">'takanobu.030210@gmail.com'</span>, url=<span class="synConstant">'https://github.com/takapy0210/nlplot'</span>, license=license, install_requires=read_requirements(), packages=find_packages(exclude=(<span class="synConstant">'tests'</span>)), package_data={<span class="synConstant">'nlplot'</span>:[<span class="synConstant">'data/*'</span>]}, python_requires=<span class="synConstant">'~=3.6'</span> ) ... </pre> <p><code>twine check</code>で<code>warning</code>は出るものの、<code>Error</code>は無くなった。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>twine check dist/* Checking dist/nlplot-1.<span class="synConstant">0</span>.0-py3-none-any.whl: PASSED, with warnings warning: <span class="synSpecial">`long_description_content_type`</span> missing. defaulting to <span class="synSpecial">`text/x-rst`</span>. Checking dist/nlplot-1.<span class="synConstant">0</span>.<span class="synConstant">0</span>.tar.gz: PASSED, with warnings warning: <span class="synSpecial">`long_description_content_type`</span> missing. defaulting to <span class="synSpecial">`text/x-rst`</span>. </pre> <p>ここでアップロードしてみると、無事に終了した。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>twine upload <span class="synSpecial">-r</span> testpypi dist/* Uploading distributions to https://<span class="synStatement">test</span>.pypi.org/legacy/ Enter your password: Uploading nlplot-1.<span class="synConstant">0</span>.0-py3-none-any.whl <span class="synConstant">100</span>%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 947k/947k <span class="synStatement">[</span>00:03<span class="synStatement">&lt;</span>00:00, 278kB/s<span class="synStatement">]</span> Uploading nlplot-1.<span class="synConstant">0</span>.<span class="synConstant">0</span>.tar.gz <span class="synConstant">100</span>%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 945k/945k <span class="synStatement">[</span>00:01<span class="synStatement">&lt;</span>00:00, 583kB/s<span class="synStatement">]</span> View at: https://<span class="synStatement">test</span>.pypi.org/project/nlplot/<span class="synConstant">1</span>.<span class="synConstant">0</span>.<span class="synConstant">0</span>/ </pre> <p>test PyPIでみてみると、なんかおかしい。</p> <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200510/20200510145000.png" alt="f:id:taxa_program:20200510145000p:plain" title="f:id:taxa_program:20200510145000p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="最後の希望Twitterへ">最後の希望Twitterへ</h2> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">PyPIへのアップロード、<a href="https://t.co/NUUYlDjJ3G">https://t.co/NUUYlDjJ3G</a>のlong_descriptionにREADMEをうまく設定できないので、long_descriptionの設定は一旦諦めるか・・・😢</p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1259351117139329024?ref_src=twsrc%5Etfw">2020年5月10日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>すると、u++さんから<code>japanize-matplotlib</code>を参考にしてみては?との助言を頂きました。 <br /> (u++さんありがとうございます。)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fuehara1414%2Fjapanize-matplotlib%2Fblob%2Fmaster%2Fsetup.py" title="uehara1414/japanize-matplotlib" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/uehara1414/japanize-matplotlib/blob/master/setup.py">github.com</a></cite></p> <p>ん〜・・・<code>long_description</code>周りの記述は悪くなさそうだな...</p> <p><code>license</code>の書き方がちょっと違うから、念のためここを変更して確認してみることに。</p> <pre class="code lang-python" data-lang="python" data-unlink>... setup( name=<span class="synConstant">'nlplot'</span>, version=<span class="synConstant">'1.0.0'</span>, description=<span class="synConstant">'Visualization Module for Natural Language Processing'</span>, long_description=long_description, long_description_content_type=<span class="synConstant">'text/markdown'</span>, author=<span class="synConstant">'Takanobu Nozawa'</span>, author_email=<span class="synConstant">'takanobu.030210@gmail.com'</span>, url=<span class="synConstant">'https://github.com/takapy0210/nlplot'</span>, <span class="synComment"># license=license,</span> license=<span class="synConstant">'MIT License'</span>, install_requires=read_requirements(), packages=find_packages(exclude=(<span class="synConstant">'tests'</span>)), package_data={<span class="synConstant">'nlplot'</span>:[<span class="synConstant">'data/*'</span>]}, python_requires=<span class="synConstant">'~=3.6'</span> ) ... </pre> <p><code>twine check</code>をしてみると、warningもErrorも出ない。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>twine check dist/* Checking dist/nlplot-1.<span class="synConstant">0</span>.0-py3-none-any.whl: PASSED Checking dist/nlplot-1.<span class="synConstant">0</span>.<span class="synConstant">0</span>.tar.gz: PASSED </pre> <p>ちなみに元々読み込んでいた<code>LICENSE.txt</code>の中身はこちら。</p> <pre class="code" data-lang="" data-unlink>MIT License Copyright (c) 2020, Takanobu Nozawa</pre> <p>こちらが悪さをしていた様子。(このファイルを1行だけにすると上手くいきました)</p> <p>検索してみると、Githubの公式にこのLICENSEファイルの作り方がありました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fhelp.github.com%2Fen%2Fgithub%2Fbuilding-a-strong-community%2Fadding-a-license-to-a-repository%23including-an-open-source-license-in-your-repository" title="Adding a license to a repository - GitHub Help" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://help.github.com/en/github/building-a-strong-community/adding-a-license-to-a-repository#including-an-open-source-license-in-your-repository">help.github.com</a></cite></p> <p>そもそも <code>setup()</code>の<code>license</code>のこのファイルの内容を設定しようしていたのがダメだったようです。</p> <h1 id="最後に">最後に</h1> <p>同じようなことで数時間溶かす人がいなくなりますように。</p> taxa_program Podcastをはじめました。 hatenablog://entry/26006613564386827 2020-05-09T13:25:43+09:00 2020-06-14T11:23:43+09:00 Stories - jp.freepik.com こんにちは。たかぱい(@takapy0210)です。 本日はお知らせっぽい宣伝です。 Podcastをはじめました どんなことを配信するの? なぜはじめたの? 最後に Podcastをはじめました ご縁があり、Yagiさん(@yaginuuun)とPodcastの配信をはじめました。 興味のある方はリスナー登録だったり、視聴してみていただけると喜びます。 anchor.fm 配信内容は下記Githubにまとめています。 GitHub - shyaginuma/geek-engineer-future: ギークなエンジニアを目指す.fm (Pod… <p><figure class="figure-image figure-image-fotolife" title="Stories - jp.freepik.com"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200509/20200509123752.jpg" alt="" title="f:id:taxa_program:20200509123752j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>Stories - jp.freepik.com</figcaption></figure></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本日はお知らせっぽい宣伝です。</p> <ul class="table-of-contents"> <li><a href="#Podcastをはじめました">Podcastをはじめました</a><ul> <li><a href="#どんなことを配信するの">どんなことを配信するの?</a></li> <li><a href="#なぜはじめたの">なぜはじめたの?</a></li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="Podcastをはじめました">Podcastをはじめました</h1> <p>ご縁があり、Yagiさん(<a href="https://twitter.com/yaginuuun">@yaginuuun</a>)とPodcastの配信をはじめました。</p> <p>興味のある方はリスナー登録だったり、視聴してみていただけると喜びます。</p> <p><iframe src="https://anchor.fm/geek-engineer-future/embed/episodes/1-edqera" height="102px" width="400px" frameborder="0" scrolling="no"></iframe><cite class="hatena-citation"><a href="https://anchor.fm/geek-engineer-future/episodes/1-edqera">anchor.fm</a></cite></p> <p>配信内容は下記Githubにまとめています。</p> <p><a href="https://github.com/shyaginuma/geek-engineer-future">GitHub - shyaginuma/geek-engineer-future: &#x30AE;&#x30FC;&#x30AF;&#x306A;&#x30A8;&#x30F3;&#x30B8;&#x30CB;&#x30A2;&#x3092;&#x76EE;&#x6307;&#x3059;.fm</a></p> <p><del>(Podcast名は「ギークなエンジニアを目指す.fm」となっていますが、僕がゴリ押しした訳ではないです。念のため笑)</del></p> <p>以下ポエムです。</p> <h2 id="どんなことを配信するの">どんなことを配信するの?</h2> <p>技術、実務、キャリアなどについてカジュアルに話していく予定です。</p> <p>僕らは元々DS / ML Eng 出身ではないところから、この業界へと足を踏み入れました。</p> <p>そんな僕らが興味のある技術だったり、実務の難しさ・楽しさだったり、キャリアの考え方などを配信していければと思っています。<br /> (ゆくゆくはゲストをお招きしたり、輪読会の様子などの配信もしてきたいなぁ・・・)</p> <h2 id="なぜはじめたの">なぜはじめたの?</h2> <p>Yagiさん(<a href="https://twitter.com/yaginuuun">@yaginuuun</a>)からのご提案で。</p> <p>もともと2人で論文や書籍の輪読会を行っており、会話の中でPodcastやってみませんかということになりました。</p> <p>僕個人の話だけすると、転職してから1年が経ち業務にも慣れてきた一方、<br /> 環境を変えたことでいくつかの気付きもありました。</p> <p>例えばこの1年間で、ありがたいことにいくつか対外的な活動をする機会をいただきました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Faws.amazon.com%2Fjp%2Fblogs%2Fstartup%2Fevent-report-ml-at-loft-5%2F" title="【開催報告】ML@Loft #5 (NLP) | Amazon Web Services" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://aws.amazon.com/jp/blogs/startup/event-report-ml-at-loft-5/">aws.amazon.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Femployment.en-japan.com%2Fengineerhub%2Fentry%2F2020%2F03%2F24%2F103000" title="機械学習を記事配信に採用したママリ - 0から構築したレコメンドエンジンのアーキテクチャ設計 - エンジニアHub|若手Webエンジニアのキャリアを考える!" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://employment.en-japan.com/engineerhub/entry/2020/03/24/103000">employment.en-japan.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftrainz.jp%2Fmedia%2Faicareer%2F453%2F" title="目指すはアプリを開くだけで悩みが解決できる状態。ママのためのQ&amp;Aアプリ「ママリ」を支える機械学習エンジニア、野澤哲照 | TRaiNZ" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://trainz.jp/media/aicareer/453/">trainz.jp</a></cite></p> <p>このような経験を通して <strong>自分のことを話すのがあまり得意ではないな</strong> ということをなんとなく感じてきました。<a href="#f-a904c75b" name="fn-a904c75b" title="極端に自分語りする人が苦手なこともあり、自然とそうならないように意識付けされているのかもしれません">*1</a><br /> 特に取材だと「ワイが一方的に話しているけど、これで良いのかな・・・<a href="#f-bbd5c2f8" name="fn-bbd5c2f8" title="取材だから当たり前なのは承知しています">*2</a>」という思いや「うまく伝わっているかな・・・」という不安があったりしました。</p> <p>一方、対外的な発信をすることで、「参考になった」などの嬉しいコメントを頂けたのも事実であり、 このような活動は今後も続けていきたいと思っています。</p> <p>得意なことをやるのはもちろん大切なのですが、<strong>やりたいことを得意にしていくことも大切だよね</strong> ということでこのお話を快諾させていただきました。<br /> あと純粋に「自分という人間を音声だけでどう表現できるのか」にも興味がありました。<br /> (何はともあれ、Yagiさんお誘いありがとうございます!)</p> <h1 id="最後に">最後に</h1> <p><a href="https://www.amazon.co.jp/dp/B01GDS0994">SOFT SKILLS ソフトウェア開発者の人生マニュアル</a>に</p> <blockquote><p>何かを新たに始めようとするときには、「それらがどのようにして、他人に価値をもたらすか」という視点から考えるようにすべきだ。</p></blockquote> <p>という1節があります。</p> <p>まだまだ不慣れで拙い部分もありますが、ゆくゆくは価値のあるものに育てていければと思っています!</p> <div class="footnote"> <p class="footnote"><a href="#fn-a904c75b" name="f-a904c75b" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">極端に自分語りする人が苦手なこともあり、自然とそうならないように意識付けされているのかもしれません</span></p> <p class="footnote"><a href="#fn-bbd5c2f8" name="f-bbd5c2f8" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">取材だから当たり前なのは承知しています</span></p> </div> taxa_program 【言語処理100本ノック 2020】 4章をPythonで解いた hatenablog://entry/26006613561448395 2020-05-04T21:00:45+09:00 2020-05-04T21:03:53+09:00 こんにちは。takapy(@takapy0210)です。 本エントリは言語処理100本ノック 2020の4章を解いてみたので、それの備忘です。 nlp100.github.io 例によってコードはGithubに置いてあります。 github.com 第4章: 形態素解析 30. 形態素解析結果の読み込み 31. 動詞 32. 動詞の原形 33. 「AのB」 34. 名詞の連接 35. 単語の出現頻度 36. 頻度上位10語 37. 「猫」と共起頻度の高い上位10語 38. ヒストグラム 39. Zipfの法則 第4章: 形態素解析 夏目漱石の小説『吾輩は猫である』の文章(neko.txt)をM… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" title="f:id:taxa_program:20200502163654p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。takapy(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック 2020の4章を解いてみたので、それの備忘です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch04.html" title="第4章: 形態素解析" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch04.html">nlp100.github.io</a></cite></p> <p>例によってコードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020%2Ftree%2Fmaster%2Fchapter4" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020/tree/master/chapter4">github.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#第4章-形態素解析">第4章: 形態素解析</a><ul> <li><a href="#30-形態素解析結果の読み込み">30. 形態素解析結果の読み込み</a></li> <li><a href="#31-動詞">31. 動詞</a></li> <li><a href="#32-動詞の原形">32. 動詞の原形</a></li> <li><a href="#33-AのB">33. 「AのB」</a></li> <li><a href="#34-名詞の連接">34. 名詞の連接</a></li> <li><a href="#35-単語の出現頻度">35. 単語の出現頻度</a></li> <li><a href="#36-頻度上位10語">36. 頻度上位10語</a></li> <li><a href="#37-猫と共起頻度の高い上位10語">37. 「猫」と共起頻度の高い上位10語</a></li> <li><a href="#38-ヒストグラム">38. ヒストグラム</a></li> <li><a href="#39-Zipfの法則">39. Zipfの法則</a></li> </ul> </li> </ul> <h1 id="第4章-形態素解析">第4章: 形態素解析</h1> <blockquote><p>夏目漱石の小説『吾輩は猫である』の文章(<a href="https://nlp100.github.io/data/neko.txt">neko.txt</a>)をMeCabを使って形態素解析し,その結果をneko.txt.mecabというファイルに保存せよ.このファイルを用いて,以下の問に対応するプログラムを実装せよ.</p> <p>なお,問題37, 38, 39はmatplotlibもしくはGnuplotを用いるとよい.</p></blockquote> <hr /> <p>始めに.txtファイルを形態素解析したファイル(.mecab)に出力してからスタートします。</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># $ mecab INPUT -o OUTPUT の形式でファイルを引数に取って形態素解析を実行できます</span> mecab neko.txt <span class="synSpecial">-o</span> neko.txt.mecab </pre> <p>出力された<code>neko.txt.mecab</code>は下記のようになっているはずです。</p> <pre class="code" data-lang="" data-unlink>一 名詞,数,*,*,*,*,一,イチ,イチ EOS EOS   記号,空白,*,*,*,*, , ,  吾輩 名詞,代名詞,一般,*,*,*,吾輩,ワガハイ,ワガハイ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 猫 名詞,一般,*,*,*,*,猫,ネコ,ネコ で 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ ある 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル 。 記号,句点,*,*,*,*,。,。,。 EOS 名前 名詞,一般,*,*,*,*,名前,ナマエ,ナマエ</pre> <p>mecabの詳細については下記を参照してください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Ftaku910.github.io%2Fmecab%2F" title="MeCab: Yet Another Part-of-Speech and Morphological Analyzer" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://taku910.github.io/mecab/">taku910.github.io</a></cite></p> <p>また、今回の可視化にはplotlyを使用しています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fplotly.com%2Fpython%2F%23fundamentals" title="Plotly Python Graphing Library" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://plotly.com/python/#fundamentals">plotly.com</a></cite></p> <h2 id="30-形態素解析結果の読み込み">30. 形態素解析結果の読み込み</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">形態素解析結果(neko.txt.mecab)を読み込むプログラムを実装せよ.</span> <span class="synConstant">ただし,各形態素は表層形(surface),基本形(base),品詞(pos),品詞細分類1(pos1)をキーとするマッピング型に格納し,</span> <span class="synConstant">1文を形態素(マッピング型)のリストとして表現せよ.第4章の残りの問題では,ここで作ったプログラムを活用せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) <span class="synIdentifier">print</span>(ans_list[:<span class="synConstant">5</span>]) </pre> <p>ファイルを読み込んだ後、不要な値('')を除外して、指定の形態素を辞書型に格納しています。<br /> 出力は先頭5行のみ表示させるようにしました。</p> <p>実行結果</p> <blockquote><p>[{'surface': '一', 'base': '一', 'pos': '名詞', 'pos1': '数'}, {'surface': '\u3000', 'base': '\u3000', 'pos': '記号', 'pos1': '空白'}, {'surface': '吾輩', 'base': '吾輩', 'pos': '名詞', 'pos1': '代名詞'}, {'surface': 'は', 'base': 'は', 'pos': '助詞', 'pos1': '係助詞'}, {'surface': '猫', 'base': '猫', 'pos': '名詞', 'pos1': '一般'}]</p></blockquote> <h2 id="31-動詞">31. 動詞</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">動詞の表層形をすべて抽出せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items, get_type, key, value): <span class="synStatement">return</span> [x[get_type] <span class="synStatement">for</span> x <span class="synStatement">in</span> items <span class="synStatement">if</span> key <span class="synStatement">in</span> x <span class="synStatement">and</span> get_type <span class="synStatement">in</span> x <span class="synStatement">and</span> x[key] == value] <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list, <span class="synConstant">'surface'</span>, <span class="synConstant">'pos'</span>, <span class="synConstant">'動詞'</span>) <span class="synIdentifier">print</span>(ans[:<span class="synConstant">5</span>]) </pre> <p><code>get_value</code>関数を実装し、動詞のsurfaceを抽出しています。</p> <p>実行結果</p> <blockquote><p>['生れ', 'つか', 'し', '泣い', 'し']</p></blockquote> <h2 id="32-動詞の原形">32. 動詞の原形</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">動詞の原形をすべて抽出せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items, get_type, key, value): <span class="synStatement">return</span> [x[get_type] <span class="synStatement">for</span> x <span class="synStatement">in</span> items <span class="synStatement">if</span> key <span class="synStatement">in</span> x <span class="synStatement">and</span> get_type <span class="synStatement">in</span> x <span class="synStatement">and</span> x[key] == value] <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list, <span class="synConstant">'base'</span>, <span class="synConstant">'pos'</span>, <span class="synConstant">'動詞'</span>) <span class="synIdentifier">print</span>(ans[:<span class="synConstant">5</span>]) </pre> <p>31のコードの<code>get_value</code>関数に渡す引数を<code>base</code>に変更しただけです。</p> <p>実行結果</p> <blockquote><p>['生れる', 'つく', 'する', '泣く', 'する']</p></blockquote> <h2 id="33-AのB">33. 「AのB」</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">2つの名詞が「の」で連結されている名詞句を抽出せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items): <span class="synStatement">return</span> [items[i-<span class="synConstant">1</span>][<span class="synConstant">'surface'</span>] + x[<span class="synConstant">'surface'</span>] + items[i+<span class="synConstant">1</span>][<span class="synConstant">'surface'</span>] <span class="synStatement">for</span> i, x <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(items) <span class="synStatement">if</span> x[<span class="synConstant">'surface'</span>] == <span class="synConstant">'の'</span> <span class="synStatement">and</span> items[i-<span class="synConstant">1</span>][<span class="synConstant">'pos'</span>] == <span class="synConstant">'名詞'</span> <span class="synStatement">and</span> items[i+<span class="synConstant">1</span>][<span class="synConstant">'pos'</span>] == <span class="synConstant">'名詞'</span>] <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list) <span class="synIdentifier">print</span>(ans[:<span class="synConstant">5</span>]) </pre> <p><code>get_value</code>関数内でリスト内包表記を用いて<code>名詞</code> + <code>の</code> + <code>名詞</code>を抽出しています。</p> <p>実行結果</p> <blockquote><p>['彼の掌', '掌の上', '書生の顔', 'はずの顔', '顔の真中']</p></blockquote> <h2 id="34-名詞の連接">34. 名詞の連接</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">名詞の連接(連続して出現する名詞)を最長一致で抽出せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items): ret = [] noun_list = [] <span class="synStatement">for</span> i, x <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(items): <span class="synStatement">if</span> x[<span class="synConstant">'pos'</span>] == <span class="synConstant">'名詞'</span>: <span class="synStatement">if</span> items[i+<span class="synConstant">1</span>][<span class="synConstant">'pos'</span>] == <span class="synConstant">'名詞'</span>: noun_list.append(x[<span class="synConstant">'surface'</span>]) <span class="synStatement">else</span>: <span class="synStatement">if</span> <span class="synIdentifier">len</span>(noun_list) &gt;= <span class="synConstant">1</span>: noun_list.append(x[<span class="synConstant">'surface'</span>]) ret.append(noun_list) noun_list = [] <span class="synStatement">return</span> ret <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list) <span class="synIdentifier">print</span>(ans[:<span class="synConstant">5</span>]) </pre> <p><code>get_value</code>関数で連接を抽出しています。<br /> 名詞が連続している場合はそれらを<code>noun_list</code>に格納し、連続が途切れた段階で<code>ret</code>に詰めています。</p> <p>実行結果</p> <blockquote><p>[['人間', '中'], ['一番', '獰悪'], ['時', '妙'], ['一', '毛'], ['その後', '猫']]</p></blockquote> <h2 id="35-単語の出現頻度">35. 単語の出現頻度</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">文章中に出現する単語とその出現頻度を求め,出現頻度の高い順に並べよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> defaultdict <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items): <span class="synStatement">return</span> [x[<span class="synConstant">'surface'</span>] <span class="synStatement">for</span> x <span class="synStatement">in</span> items] <span class="synStatement">def</span> <span class="synIdentifier">get_freq</span>(value): <span class="synStatement">def</span> <span class="synIdentifier">generate_ngrams</span>(text, n_gram=<span class="synConstant">1</span>): token = [token <span class="synStatement">for</span> token <span class="synStatement">in</span> text.lower().split(<span class="synConstant">&quot; &quot;</span>) <span class="synStatement">if</span> token != <span class="synConstant">&quot;&quot;</span> <span class="synStatement">if</span> token] ngrams = <span class="synIdentifier">zip</span>(*[token[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_gram)]) <span class="synStatement">return</span> [<span class="synConstant">&quot; &quot;</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> ngrams] freq_dict = defaultdict(<span class="synIdentifier">int</span>) <span class="synStatement">for</span> sent <span class="synStatement">in</span> value: <span class="synStatement">for</span> word <span class="synStatement">in</span> generate_ngrams(<span class="synIdentifier">str</span>(sent)): freq_dict[word] += <span class="synConstant">1</span> fd_sorted = pd.DataFrame(<span class="synIdentifier">sorted</span>(freq_dict.items(), key=<span class="synStatement">lambda</span> x: x[<span class="synConstant">1</span>])[::-<span class="synConstant">1</span>]) fd_sorted.columns = [<span class="synConstant">'word'</span>, <span class="synConstant">'word_count'</span>] <span class="synStatement">return</span> fd_sorted <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list) ans = get_freq(ans) <span class="synIdentifier">print</span>(ans.head()) </pre> <p><code>get_freq</code>関数で出現頻度を計算しています。</p> <p>実行結果</p> <pre class="code" data-lang="" data-unlink> word word_count 0 の 9194 1 。 7486 2 て 6868 3 、 6772 4 は 6420 </pre> <h2 id="36-頻度上位10語">36. 頻度上位10語</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">出現頻度が高い10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> defaultdict <span class="synPreProc">import</span> plotly.express <span class="synStatement">as</span> px <span class="synPreProc">from</span> plotly.offline <span class="synPreProc">import</span> plot <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items): <span class="synStatement">return</span> [x[<span class="synConstant">'surface'</span>] <span class="synStatement">for</span> x <span class="synStatement">in</span> items] <span class="synStatement">def</span> <span class="synIdentifier">get_freq</span>(value): <span class="synStatement">def</span> <span class="synIdentifier">generate_ngrams</span>(text, n_gram=<span class="synConstant">1</span>): token = [token <span class="synStatement">for</span> token <span class="synStatement">in</span> text.lower().split(<span class="synConstant">&quot; &quot;</span>) <span class="synStatement">if</span> token != <span class="synConstant">&quot;&quot;</span> <span class="synStatement">if</span> token] ngrams = <span class="synIdentifier">zip</span>(*[token[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_gram)]) <span class="synStatement">return</span> [<span class="synConstant">&quot; &quot;</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> ngrams] freq_dict = defaultdict(<span class="synIdentifier">int</span>) <span class="synStatement">for</span> sent <span class="synStatement">in</span> value: <span class="synStatement">for</span> word <span class="synStatement">in</span> generate_ngrams(<span class="synIdentifier">str</span>(sent)): freq_dict[word] += <span class="synConstant">1</span> fd_sorted = pd.DataFrame(<span class="synIdentifier">sorted</span>(freq_dict.items(), key=<span class="synStatement">lambda</span> x: x[<span class="synConstant">1</span>])[::-<span class="synConstant">1</span>]) fd_sorted.columns = [<span class="synConstant">'word'</span>, <span class="synConstant">'word_count'</span>] <span class="synStatement">return</span> fd_sorted.head(<span class="synConstant">10</span>) <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list) ans = get_freq(ans) fig = px.bar( ans.sort_values(<span class="synConstant">'word_count'</span>), y=<span class="synConstant">'word'</span>, x=<span class="synConstant">'word_count'</span>, text=<span class="synConstant">'word_count'</span>, orientation=<span class="synConstant">'h'</span>, ) fig.update_traces( texttemplate=<span class="synConstant">'%{text:.2s}'</span>, textposition=<span class="synConstant">'auto'</span>, ) fig.update_layout( title=<span class="synIdentifier">str</span>(<span class="synConstant">'頻度上位10語'</span>), xaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'出現数'</span>), yaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'単語'</span>), width=<span class="synConstant">1000</span>, height=<span class="synConstant">500</span>, ) plot(fig, filename=<span class="synConstant">'ans_36_plot.html'</span>, auto_open=<span class="synIdentifier">False</span>) </pre> <p><code>get_freq</code>のreturnでhead(10)として、上位10単語のみを抽出しています。<br /> 今回はplotlyを用いて可視化しました。<br /> 実行すると、実行ディレクトリ に<code>ans_36_plot.html</code>ファイルが出力され、それをブラウザで開くと画像が確認できます。</p> <p>実行結果 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200504/20200504184135.png" alt="f:id:taxa_program:20200504184135p:plain" title="f:id:taxa_program:20200504184135p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="37-猫と共起頻度の高い上位10語">37. 「猫」と共起頻度の高い上位10語</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">「猫」とよく共起する(共起頻度が高い)10語とその出現頻度をグラフ(例えば棒グラフなど)で表示せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> defaultdict <span class="synPreProc">import</span> plotly.express <span class="synStatement">as</span> px <span class="synPreProc">from</span> plotly.offline <span class="synPreProc">import</span> plot <span class="synStatement">def</span> <span class="synIdentifier">parseMecab</span>(block): res = [] <span class="synStatement">for</span> line <span class="synStatement">in</span> block.split(<span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>): <span class="synStatement">if</span> line == <span class="synConstant">''</span>: <span class="synStatement">return</span> res (surface, attr) = line.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) lineDict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } res.append(lineDict) <span class="synStatement">def</span> <span class="synIdentifier">extract</span>(block): <span class="synStatement">return</span> [b[<span class="synConstant">'base'</span>] <span class="synStatement">for</span> b <span class="synStatement">in</span> block] filename = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(filename, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: blockList = f.read().split(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) blockList = <span class="synIdentifier">list</span>(<span class="synIdentifier">filter</span>(<span class="synStatement">lambda</span> x: x != <span class="synConstant">''</span>, blockList)) blockList = [parseMecab(block) <span class="synStatement">for</span> block <span class="synStatement">in</span> blockList] wordList = [extract(block) <span class="synStatement">for</span> block <span class="synStatement">in</span> blockList] wordList = <span class="synIdentifier">list</span>(<span class="synIdentifier">filter</span>(<span class="synStatement">lambda</span> x: <span class="synConstant">'猫'</span> <span class="synStatement">in</span> x, wordList)) d = defaultdict(<span class="synIdentifier">int</span>) <span class="synStatement">for</span> word <span class="synStatement">in</span> wordList: <span class="synStatement">for</span> w <span class="synStatement">in</span> word: <span class="synStatement">if</span> w != <span class="synConstant">'猫'</span>: d[w] += <span class="synConstant">1</span> ans = <span class="synIdentifier">sorted</span>(d.items(), key=<span class="synStatement">lambda</span> x: x[<span class="synConstant">1</span>], reverse=<span class="synIdentifier">True</span>)[:<span class="synConstant">10</span>] ans = pd.DataFrame(ans) ans.columns = [<span class="synConstant">'word'</span>, <span class="synConstant">'word_count'</span>] fig = px.bar( ans.sort_values(<span class="synConstant">'word_count'</span>), y=<span class="synConstant">'word'</span>, x=<span class="synConstant">'word_count'</span>, text=<span class="synConstant">'word_count'</span>, orientation=<span class="synConstant">'h'</span>, ) fig.update_traces( texttemplate=<span class="synConstant">'%{text:.2s}'</span>, textposition=<span class="synConstant">'auto'</span>, ) fig.update_layout( title=<span class="synIdentifier">str</span>(<span class="synConstant">'「猫」との共起回数上位10語'</span>), xaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'「猫」との共起数'</span>), yaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'単語'</span>), width=<span class="synConstant">1000</span>, height=<span class="synConstant">500</span>, ) plot(fig, filename=<span class="synConstant">'ans_37_plot.html'</span>, auto_open=<span class="synIdentifier">False</span>) </pre> <p>共起頻度を計算する想定でデータの読み込みを行っていなかったので、前半の処理部分は<a href="https://upura.hatenablog.com/entry/2020/04/20/122445">u++さんのコード</a>をカンニングしました・・・🙇‍♂️<br /> (さすがに30から解き直す気力もなく)</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">言語処理100本ノック、4章の37問目でこれ状態になった<br>(共起回数を計算できる形式でデータを読み込んで無かった) <a href="https://t.co/4R4iWKReuL">pic.twitter.com/4R4iWKReuL</a></p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1257254255892721666?ref_src=twsrc%5Etfw">2020年5月4日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>実行すると、実行ディレクトリ に<code>ans_37_plot.html</code>ファイルが出力され、それをブラウザで開くと画像が確認できます。</p> <p>実行結果 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200504/20200504205346.png" alt="f:id:taxa_program:20200504205346p:plain" title="f:id:taxa_program:20200504205346p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="38-ヒストグラム">38. ヒストグラム</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">単語の出現頻度のヒストグラム(横軸に出現頻度,縦軸に出現頻度をとる単語の種類数を棒グラフで表したもの)を描け.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> defaultdict <span class="synPreProc">import</span> plotly.express <span class="synStatement">as</span> px <span class="synPreProc">from</span> plotly.offline <span class="synPreProc">import</span> plot <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items): <span class="synStatement">return</span> [x[<span class="synConstant">'surface'</span>] <span class="synStatement">for</span> x <span class="synStatement">in</span> items] <span class="synStatement">def</span> <span class="synIdentifier">get_freq</span>(value): <span class="synStatement">def</span> <span class="synIdentifier">generate_ngrams</span>(text, n_gram=<span class="synConstant">1</span>): token = [token <span class="synStatement">for</span> token <span class="synStatement">in</span> text.lower().split(<span class="synConstant">&quot; &quot;</span>) <span class="synStatement">if</span> token != <span class="synConstant">&quot;&quot;</span> <span class="synStatement">if</span> token] ngrams = <span class="synIdentifier">zip</span>(*[token[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_gram)]) <span class="synStatement">return</span> [<span class="synConstant">&quot; &quot;</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> ngrams] freq_dict = defaultdict(<span class="synIdentifier">int</span>) <span class="synStatement">for</span> sent <span class="synStatement">in</span> value: <span class="synStatement">for</span> word <span class="synStatement">in</span> generate_ngrams(<span class="synIdentifier">str</span>(sent)): freq_dict[word] += <span class="synConstant">1</span> fd_sorted = pd.DataFrame(<span class="synIdentifier">sorted</span>(freq_dict.items(), key=<span class="synStatement">lambda</span> x: x[<span class="synConstant">1</span>])[::-<span class="synConstant">1</span>]) fd_sorted.columns = [<span class="synConstant">'word'</span>, <span class="synConstant">'word_count'</span>] <span class="synStatement">return</span> fd_sorted <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list) ans = get_freq(ans) fig = px.histogram(ans, x=<span class="synConstant">'word_count'</span>, nbins=<span class="synConstant">50</span>) fig.update_layout( title=<span class="synIdentifier">str</span>(<span class="synConstant">'単語の出現頻度のヒストグラム'</span>), xaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'出現頻度'</span>), yaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'単語の種類数'</span>), width=<span class="synConstant">1000</span>, height=<span class="synConstant">500</span>, ) plot(fig, filename=<span class="synConstant">'ans_38_plot.html'</span>, auto_open=<span class="synIdentifier">False</span>) </pre> <p>実行すると、実行ディレクトリ に<code>ans_38_plot.html</code>ファイルが出力され、それをブラウザで開くと画像が確認できます。</p> <p>実行結果 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200504/20200504202151.png" alt="f:id:taxa_program:20200504202151p:plain" title="f:id:taxa_program:20200504202151p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <h2 id="39-Zipfの法則">39. Zipfの法則</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">単語の出現頻度順位を横軸,その出現頻度を縦軸として,両対数グラフをプロットせよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> math <span class="synPreProc">from</span> collections <span class="synPreProc">import</span> defaultdict <span class="synPreProc">import</span> plotly.express <span class="synStatement">as</span> px <span class="synPreProc">from</span> plotly.offline <span class="synPreProc">import</span> plot <span class="synStatement">def</span> <span class="synIdentifier">parse_morpheme</span>(morpheme): (surface, attr) = morpheme.split(<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>) attr = attr.split(<span class="synConstant">','</span>) morpheme_dict = { <span class="synConstant">'surface'</span>: surface, <span class="synConstant">'base'</span>: attr[<span class="synConstant">6</span>], <span class="synConstant">'pos'</span>: attr[<span class="synConstant">0</span>], <span class="synConstant">'pos1'</span>: attr[<span class="synConstant">1</span>] } <span class="synStatement">return</span> morpheme_dict <span class="synStatement">def</span> <span class="synIdentifier">get_value</span>(items): <span class="synStatement">return</span> [x[<span class="synConstant">'surface'</span>] <span class="synStatement">for</span> x <span class="synStatement">in</span> items] <span class="synStatement">def</span> <span class="synIdentifier">get_freq</span>(value): <span class="synStatement">def</span> <span class="synIdentifier">generate_ngrams</span>(text, n_gram=<span class="synConstant">1</span>): token = [token <span class="synStatement">for</span> token <span class="synStatement">in</span> text.lower().split(<span class="synConstant">&quot; &quot;</span>) <span class="synStatement">if</span> token != <span class="synConstant">&quot;&quot;</span> <span class="synStatement">if</span> token] ngrams = <span class="synIdentifier">zip</span>(*[token[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_gram)]) <span class="synStatement">return</span> [<span class="synConstant">&quot; &quot;</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> ngrams] freq_dict = defaultdict(<span class="synIdentifier">int</span>) <span class="synStatement">for</span> sent <span class="synStatement">in</span> value: <span class="synStatement">for</span> word <span class="synStatement">in</span> generate_ngrams(<span class="synIdentifier">str</span>(sent)): freq_dict[word] += <span class="synConstant">1</span> fd_sorted = pd.DataFrame(<span class="synIdentifier">sorted</span>(freq_dict.items(), key=<span class="synStatement">lambda</span> x: x[<span class="synConstant">1</span>])[::-<span class="synConstant">1</span>]) fd_sorted.columns = [<span class="synConstant">'word'</span>, <span class="synConstant">'word_count'</span>] <span class="synStatement">return</span> fd_sorted <span class="synIdentifier">file</span> = <span class="synConstant">'neko.txt.mecab'</span> <span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synIdentifier">file</span>, mode=<span class="synConstant">'rt'</span>, encoding=<span class="synConstant">'utf-8'</span>) <span class="synStatement">as</span> f: morphemes_list = [s.strip(<span class="synConstant">'EOS</span><span class="synSpecial">\n</span><span class="synConstant">'</span>) <span class="synStatement">for</span> s <span class="synStatement">in</span> f.readlines()] morphemes_list = [s <span class="synStatement">for</span> s <span class="synStatement">in</span> morphemes_list <span class="synStatement">if</span> s != <span class="synConstant">''</span>] ans_list = <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(parse_morpheme, morphemes_list)) ans = get_value(ans_list) ans = get_freq(ans) ans[<span class="synConstant">'rank_log'</span>] = [math.log(r + <span class="synConstant">1</span>) <span class="synStatement">for</span> r <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synIdentifier">len</span>(ans))] ans[<span class="synConstant">'count_log'</span>] = [math.log(v) <span class="synStatement">for</span> v <span class="synStatement">in</span> ans[<span class="synConstant">'word_count'</span>]] fig = px.scatter(ans, x=<span class="synConstant">'rank_log'</span>, y=<span class="synConstant">'count_log'</span>) fig.update_layout( title=<span class="synIdentifier">str</span>(<span class="synConstant">'単語の出現頻度のヒストグラム'</span>), xaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'単語の出現頻度順位'</span>), yaxis_title=<span class="synIdentifier">str</span>(<span class="synConstant">'出現頻度'</span>), width=<span class="synConstant">800</span>, height=<span class="synConstant">600</span>, ) plot(fig, filename=<span class="synConstant">'ans_39_plot.html'</span>, auto_open=<span class="synIdentifier">False</span>) </pre> <p>両対数グラフとは、「x軸:対数目盛、y軸:対数目盛」のようにx軸とy軸の両方が対数目盛となっているグラフのことです。 <code>rank_log</code>と<code>count_log</code>でそれぞれ計算しています。</p> <p>実行結果 <figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200504/20200504204018.png" alt="f:id:taxa_program:20200504204018p:plain" title="f:id:taxa_program:20200504204018p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> taxa_program 【言語処理100本ノック 2020】 3章をPythonで解いた hatenablog://entry/26006613560063327 2020-05-03T09:55:34+09:00 2020-05-04T21:03:53+09:00 こんにちは。たかぱい(@takapy0210)です。 本エントリは言語処理100本ノック 2020の3章を解いてみたので、それの備忘です。 nlp100.github.io 例によってコードはGithubに置いてあります。 github.com 第3章: 正規表現 20. JSONデータの読み込み 21. カテゴリ名を含む行を抽出 22. カテゴリ名の抽出 23. セクション構造 24. ファイル参照の抽出 25. テンプレートの抽出 26. 強調マークアップの除去 27. 内部リンクの除去 28. MediaWikiマークアップの除去 29. 国旗画像のURLを取得する 第3章: 正規表現 … <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" title="f:id:taxa_program:20200502163654p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック 2020の3章を解いてみたので、それの備忘です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch03.html" title="第3章: 正規表現" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch03.html">nlp100.github.io</a></cite></p> <p>例によってコードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020%2Ftree%2Fmaster%2Fchapter3" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020/tree/master/chapter3">github.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#第3章-正規表現">第3章: 正規表現</a><ul> <li><a href="#20-JSONデータの読み込み">20. JSONデータの読み込み</a></li> <li><a href="#21-カテゴリ名を含む行を抽出">21. カテゴリ名を含む行を抽出</a></li> <li><a href="#22-カテゴリ名の抽出">22. カテゴリ名の抽出</a></li> <li><a href="#23-セクション構造">23. セクション構造</a></li> <li><a href="#24-ファイル参照の抽出">24. ファイル参照の抽出</a></li> <li><a href="#25-テンプレートの抽出">25. テンプレートの抽出</a></li> <li><a href="#26-強調マークアップの除去">26. 強調マークアップの除去</a></li> <li><a href="#27-内部リンクの除去">27. 内部リンクの除去</a></li> <li><a href="#28-MediaWikiマークアップの除去">28. MediaWikiマークアップの除去</a></li> <li><a href="#29-国旗画像のURLを取得する">29. 国旗画像のURLを取得する</a></li> </ul> </li> </ul> <h1 id="第3章-正規表現">第3章: 正規表現</h1> <blockquote><p>Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.</p> <ul> <li>1行に1記事の情報がJSON形式で格納される</li> <li>各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される</li> <li>ファイル全体はgzipで圧縮される</li> </ul> <p>以下の処理を行うプログラムを作成せよ.</p></blockquote> <h2 id="20-JSONデータの読み込み">20. JSONデータの読み込み</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">Wikipedia記事のJSONファイルを読み込み,「イギリス」に関する記事本文を表示せよ.</span> <span class="synConstant">問題21-29では,ここで抽出した記事本文に対して実行せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] <span class="synIdentifier">print</span>(uk_wiki) </pre> <p><code>read_json</code>で圧縮されているデータも読み込めます。便利ですね。</p> <h2 id="21-カテゴリ名を含む行を抽出">21. カテゴリ名を含む行を抽出</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">記事中でカテゴリ名を宣言している行を抽出せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] pattern = re.compile(<span class="synConstant">r'^(.*\[\[Category:.*\]\].*)$'</span>, re.MULTILINE) ans = <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>.join(pattern.findall(uk_wiki)) <span class="synIdentifier">print</span>(ans) </pre> <p><a href="https://qiita.com/luohao0404/items/7135b2b96f9b0b196bf3">この辺りの記事</a>を参考に正規表現を使って抽出しました。</p> <h2 id="22-カテゴリ名の抽出">22. カテゴリ名の抽出</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] pattern = re.compile(<span class="synConstant">r'^.*\[\[Category:(.*?)(?:\|.*)?\]\].*$'</span>, re.MULTILINE) ans = <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>.join(pattern.findall(uk_wiki)) <span class="synIdentifier">print</span>(ans) </pre> <h2 id="23-セクション構造">23. セクション構造</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">記事中に含まれるセクション名とそのレベル(例えば”== セクション名 ==”なら1)を表示せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] pattern = re.compile(<span class="synConstant">r'^(\={2,})\s*(.+?)\s*(\={2,}).*$'</span>, re.MULTILINE) ans = <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>.join(i[<span class="synConstant">1</span>] + <span class="synConstant">':'</span> + <span class="synIdentifier">str</span>(<span class="synIdentifier">len</span>(i[<span class="synConstant">0</span>])-<span class="synConstant">1</span>) <span class="synStatement">for</span> i <span class="synStatement">in</span> pattern.findall(uk_wiki)) <span class="synIdentifier">print</span>(ans) </pre> <p><code>==</code>も抽出対象とし、文字列の長さをセクション名と同時に表示させています。</p> <h2 id="24-ファイル参照の抽出">24. ファイル参照の抽出</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">記事から参照されているメディアファイルをすべて抜き出せ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] pattern = re.compile(<span class="synConstant">r'\[\[ファイル:(.+?)\|'</span>) ans = <span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>.join(pattern.findall(uk_wiki)) <span class="synIdentifier">print</span>(ans) </pre> <p><code>File</code> は無さそうだったので<code>ファイル</code>だけにしています。</p> <h2 id="25-テンプレートの抽出">25. テンプレートの抽出</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し,辞書オブジェクトとして格納せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] <span class="synComment"># 基礎情報テンプレートの抽出</span> pattern = re.compile(<span class="synConstant">r'^\{\{基礎情報.*?$(.*?)^\}\}'</span>, re.MULTILINE + re.S) base = pattern.findall(uk_wiki) <span class="synComment"># 抽出結果からのフィールド名と値の抽出</span> pattern = re.compile(<span class="synConstant">r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))'</span>, re.MULTILINE + re.S) ans = pattern.findall(base[<span class="synConstant">0</span>]) ans = <span class="synIdentifier">dict</span>(ans) <span class="synStatement">for</span> k, v <span class="synStatement">in</span> ans.items(): <span class="synIdentifier">print</span>(k + <span class="synConstant">':'</span> + v) </pre> <p>はじめに<code>base</code>変数に基礎情報テンプレートの値を設定し、その情報をさらに正規表現を用いて<code>ans</code>に格納します。<br /> 後半の正規表現では、<code>=</code>の前後に空白があったり無かったりするので<code>\s*</code>(空白文字0文字以上)を指定しています。<br /> <code>ans</code>にはtuple型で格納されるため、最後にdictへと変形して表示させています。</p> <h2 id="26-強調マークアップの除去">26. 強調マークアップの除去</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">25の処理時に,テンプレートの値からMediaWikiの強調マークアップ(弱い強調,強調,強い強調のすべて)</span> <span class="synConstant">を除去してテキストに変換せよ(参考: マークアップ早見表).</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] <span class="synComment"># 基礎情報テンプレートの抽出</span> pattern = re.compile(<span class="synConstant">r'^\{\{基礎情報.*?$(.*?)^\}\}'</span>, re.MULTILINE + re.S) base = pattern.findall(uk_wiki) <span class="synComment"># 抽出結果からのフィールド名と値の抽出</span> pattern = re.compile(<span class="synConstant">r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))'</span>, re.MULTILINE + re.S) ans = pattern.findall(base[<span class="synConstant">0</span>]) <span class="synComment"># 強調マークアップの除去</span> pattern = re.compile(<span class="synConstant">r'\'{2,5}'</span>, re.MULTILINE + re.S) ans = {i[<span class="synConstant">0</span>]:pattern.sub(<span class="synConstant">''</span>, i[<span class="synConstant">1</span>]) <span class="synStatement">for</span> i <span class="synStatement">in</span> ans} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> ans.items(): <span class="synIdentifier">print</span>(k + <span class="synConstant">':'</span> + v) </pre> <p>25のコードに強調マークアップの除去処理を追加</p> <h2 id="27-内部リンクの除去">27. 内部リンクの除去</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">26の処理に加えて,テンプレートの値からMediaWikiの内部リンクマークアップを除去し,</span> <span class="synConstant">テキストに変換せよ(参考: マークアップ早見表)</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] <span class="synComment"># 基礎情報テンプレートの抽出</span> pattern = re.compile(<span class="synConstant">r'^\{\{基礎情報.*?$(.*?)^\}\}'</span>, re.MULTILINE + re.S) base = pattern.findall(uk_wiki) <span class="synComment"># 抽出結果からのフィールド名と値の抽出</span> pattern = re.compile(<span class="synConstant">r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))'</span>, re.MULTILINE + re.S) ans = pattern.findall(base[<span class="synConstant">0</span>]) <span class="synComment"># 強調マークアップの除去</span> pattern = re.compile(<span class="synConstant">r'\'{2,5}'</span>, re.MULTILINE + re.S) ans = {i[<span class="synConstant">0</span>]:pattern.sub(<span class="synConstant">''</span>, i[<span class="synConstant">1</span>]) <span class="synStatement">for</span> i <span class="synStatement">in</span> ans} <span class="synComment"># 内部リンクの除去</span> pattern = re.compile(<span class="synConstant">r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]'</span>, re.MULTILINE + re.S) ans = {k:pattern.sub(<span class="synConstant">''</span>, v) <span class="synStatement">for</span> k, v <span class="synStatement">in</span> ans.items()} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> ans.items(): <span class="synIdentifier">print</span>(k + <span class="synConstant">':'</span> + v) </pre> <p>26のコードに内部リンクの除去処理を追加</p> <h2 id="28-MediaWikiマークアップの除去">28. MediaWikiマークアップの除去</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">27の処理に加えて,テンプレートの値からMediaWikiマークアップを可能な限り除去し,国の基本情報を整形せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re <span class="synStatement">def</span> <span class="synIdentifier">rm_markup</span>(target): <span class="synComment"># 強調マークアップの除去</span> pattern = re.compile(<span class="synConstant">r'\'{2,5}'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># 内部リンクの除去</span> pattern = re.compile(<span class="synConstant">r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># Template:Langの除去 {{lang|言語タグ|文字列}}</span> pattern = re.compile(<span class="synConstant">r'\{\{lang(?:[^|]*?\|)*?([^|]*?)\}\}'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># 外部リンクの除去 [http://xxxx]/[http://xxx xxx]</span> pattern = re.compile(<span class="synConstant">r'\[http:\/\/(?:[^\s]*?\s)?([^]]*?)\]'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># &lt;br&gt;、&lt;ref&gt;の除去</span> pattern = re.compile(<span class="synConstant">r'&lt;\/?[br|ref][^&gt;]*?&gt;'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) pattern = re.compile(<span class="synConstant">r'({{Cite.*?}})$'</span>) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># 改行コードの除去</span> target = target.replace(<span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>, <span class="synConstant">''</span>) <span class="synStatement">return</span> target df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] <span class="synComment"># 基礎情報テンプレートの抽出</span> pattern = re.compile(<span class="synConstant">r'^\{\{基礎情報.*?$(.*?)^\}\}'</span>, re.MULTILINE + re.S) base = pattern.findall(uk_wiki) <span class="synComment"># 抽出結果からのフィールド名と値の抽出</span> pattern = re.compile(<span class="synConstant">r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))'</span>, re.MULTILINE + re.S) ans = pattern.findall(base[<span class="synConstant">0</span>]) ans = {i[<span class="synConstant">0</span>]:rm_markup(i[<span class="synConstant">1</span>]) <span class="synStatement">for</span> i <span class="synStatement">in</span> ans} <span class="synStatement">for</span> k, v <span class="synStatement">in</span> ans.items(): <span class="synIdentifier">print</span>(k + <span class="synConstant">':'</span> + v) </pre> <p>今までの処理を<code>rm_markup</code>関数にまとめました。<br /> 頑張ればもう少し綺麗になると思いますが、今回はいったんこれで妥協しました汗</p> <h2 id="29-国旗画像のURLを取得する">29. 国旗画像のURLを取得する</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">テンプレートの内容を利用し,国旗画像のURLを取得せよ.</span> <span class="synConstant">(ヒント: MediaWiki APIのimageinfoを呼び出して,ファイル参照をURLに変換すればよい)</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> re <span class="synPreProc">import</span> requests <span class="synStatement">def</span> <span class="synIdentifier">rm_markup</span>(target): <span class="synComment"># 強調マークアップの除去</span> pattern = re.compile(<span class="synConstant">r'\'{2,5}'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># 内部リンクの除去</span> pattern = re.compile(<span class="synConstant">r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># Template:Langの除去 {{lang|言語タグ|文字列}}</span> pattern = re.compile(<span class="synConstant">r'\{\{lang(?:[^|]*?\|)*?([^|]*?)\}\}'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># 外部リンクの除去 [http://xxxx]/[http://xxx xxx]</span> pattern = re.compile(<span class="synConstant">r'\[http:\/\/(?:[^\s]*?\s)?([^]]*?)\]'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># &lt;br&gt;、&lt;ref&gt;の除去</span> pattern = re.compile(<span class="synConstant">r'&lt;\/?[br|ref][^&gt;]*?&gt;'</span>, re.MULTILINE) target = pattern.sub(<span class="synConstant">''</span>, target) pattern = re.compile(<span class="synConstant">r'({{Cite.*?}})$'</span>) target = pattern.sub(<span class="synConstant">''</span>, target) <span class="synComment"># 改行コードの除去</span> target = target.replace(<span class="synConstant">'</span><span class="synSpecial">\n</span><span class="synConstant">'</span>, <span class="synConstant">''</span>) <span class="synStatement">return</span> target <span class="synStatement">def</span> <span class="synIdentifier">get_url</span>(text): url_file = text[<span class="synConstant">'国旗画像'</span>].replace(<span class="synConstant">' '</span>, <span class="synConstant">'_'</span>) url = <span class="synConstant">'https://commons.wikimedia.org/w/api.php?action=query&amp;titles=File:'</span> + url_file + <span class="synConstant">'&amp;prop=imageinfo&amp;iiprop=url&amp;format=json'</span> data = requests.get(url) <span class="synStatement">return</span> re.search(<span class="synConstant">r'&quot;url&quot;:&quot;(.+?)&quot;'</span>, data.text).group(<span class="synConstant">1</span>) df = pd.read_json(<span class="synConstant">'jawiki-country.json.gz'</span>, lines=<span class="synIdentifier">True</span>) uk_wiki = df.query(<span class="synConstant">'title == &quot;イギリス&quot;'</span>)[<span class="synConstant">'text'</span>].values[<span class="synConstant">0</span>] <span class="synComment"># 基礎情報テンプレートの抽出</span> pattern = re.compile(<span class="synConstant">r'^\{\{基礎情報.*?$(.*?)^\}\}'</span>, re.MULTILINE + re.S) base = pattern.findall(uk_wiki) <span class="synComment"># 抽出結果からのフィールド名と値の抽出</span> pattern = re.compile(<span class="synConstant">r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))'</span>, re.MULTILINE + re.S) ans = pattern.findall(base[<span class="synConstant">0</span>]) ans = {i[<span class="synConstant">0</span>]:rm_markup(i[<span class="synConstant">1</span>]) <span class="synStatement">for</span> i <span class="synStatement">in</span> ans} <span class="synIdentifier">print</span>(get_url(ans)) </pre> taxa_program 【言語処理100本ノック 2020】 2章をPythonで解いた hatenablog://entry/26006613560148181 2020-05-02T16:51:47+09:00 2020-05-04T21:03:53+09:00 こんにちは。たかぱい(@takapy0210)です。 本エントリは言語処理100本ノック 2020の2章を解いてみたので、それの備忘です。 nlp100.github.io 例によってコードはGithubに置いてあります。 github.com 第2章: UNIXコマンド 10. 行数のカウント 11. タブをスペースに置換 12. 1列目をcol1.txtに,2列目をcol2.txtに保存 13. col1.txtとcol2.txtをマージ 14. 先頭からN行を出力 15. 末尾のN行を出力 16. ファイルをN分割する 17. 1列目の文字列の異なり 18. 各行を3コラム目の数値の降順… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" title="f:id:taxa_program:20200502163654p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック 2020の2章を解いてみたので、それの備忘です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch02.html" title="第2章: UNIXコマンド" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch02.html">nlp100.github.io</a></cite></p> <p>例によってコードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020%2Ftree%2Fmaster%2Fchapter1" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020/tree/master/chapter1">github.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#第2章-UNIXコマンド">第2章: UNIXコマンド</a><ul> <li><a href="#10-行数のカウント">10. 行数のカウント</a></li> <li><a href="#11-タブをスペースに置換">11. タブをスペースに置換</a></li> <li><a href="#12-1列目をcol1txtに2列目をcol2txtに保存">12. 1列目をcol1.txtに,2列目をcol2.txtに保存</a></li> <li><a href="#13-col1txtとcol2txtをマージ">13. col1.txtとcol2.txtをマージ</a></li> <li><a href="#14-先頭からN行を出力">14. 先頭からN行を出力</a></li> <li><a href="#15-末尾のN行を出力">15. 末尾のN行を出力</a></li> <li><a href="#16-ファイルをN分割する">16. ファイルをN分割する</a></li> <li><a href="#17-1列目の文字列の異なり">17. 1列目の文字列の異なり</a></li> <li><a href="#18-各行を3コラム目の数値の降順にソート">18. 各行を3コラム目の数値の降順にソート</a></li> <li><a href="#19-各行の1コラム目の文字列の出現頻度を求め出現頻度の高い順に並べる">19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる</a></li> </ul> </li> </ul> <h1 id="第2章-UNIXコマンド">第2章: UNIXコマンド</h1> <blockquote><p>popular-names.txtは,アメリカで生まれた赤ちゃんの「名前」「性別」「人数」「年」をタブ区切り形式で格納したファイルである.以下の処理を行うプログラムを作成し,popular-names.txtを入力ファイルとして実行せよ.さらに,同様の処理をUNIXコマンドでも実行し,プログラムの実行結果を確認せよ.</p></blockquote> <h2 id="10-行数のカウント">10. 行数のカウント</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">行数をカウントせよ.</span> <span class="synConstant">確認にはwcコマンドを用いよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) <span class="synIdentifier">print</span>(<span class="synIdentifier">len</span>(df.index)) </pre> <h2 id="11-タブをスペースに置換">11. タブをスペースに置換</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">タブ1文字につきスペース1文字に置換せよ.</span> <span class="synConstant">確認にはsedコマンド,trコマンド,もしくはexpandコマンドを用いよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) df.to_csv(<span class="synConstant">'ans_11.txt'</span>, sep=<span class="synConstant">' '</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) </pre> <h2 id="12-1列目をcol1txtに2列目をcol2txtに保存">12. 1列目をcol1.txtに,2列目をcol2.txtに保存</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">各行の1列目だけを抜き出したものをcol1.txtに,2列目だけを抜き出したものをcol2.txtとしてファイルに保存せよ.</span> <span class="synConstant">確認にはcutコマンドを用いよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) df.iloc[:,<span class="synConstant">0</span>].to_csv(<span class="synConstant">'col1.txt'</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) df.iloc[:,<span class="synConstant">1</span>].to_csv(<span class="synConstant">'col2.txt'</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) </pre> <h2 id="13-col1txtとcol2txtをマージ">13. col1.txtとcol2.txtをマージ</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">12で作ったcol1.txtとcol2.txtを結合し,元のファイルの1列目と2列目をタブ区切りで並べたテキストファイルを作成せよ.</span> <span class="synConstant">確認にはpasteコマンドを用いよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df_col1 = pd.read_csv(<span class="synConstant">'col1.txt'</span>, header=<span class="synIdentifier">None</span>) df_col2 = pd.read_csv(<span class="synConstant">'col2.txt'</span>, header=<span class="synIdentifier">None</span>) df = pd.concat([df_col1, df_col2], axis=<span class="synConstant">1</span>) df.to_csv(<span class="synConstant">'ans_13.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) </pre> <h2 id="14-先頭からN行を出力">14. 先頭からN行を出力</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">自然数Nをコマンドライン引数などの手段で受け取り,入力のうち先頭のN行だけを表示せよ.</span> <span class="synConstant">確認にはheadコマンドを用いよ.</span> <span class="synConstant">Usage</span> <span class="synConstant">&gt;&gt; python ans_14.py --n=5</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> fire <span class="synStatement">def</span> <span class="synIdentifier">main</span>(n): df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>, nrows=n) <span class="synIdentifier">print</span>(df) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: fire.Fire(main) </pre> <p><code>fire</code>を使うと、コマンドライン引数の取り扱いが楽になるのでオススメです</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fgoogle%2Fpython-fire" title="google/python-fire" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/google/python-fire">github.com</a></cite></p> <h2 id="15-末尾のN行を出力">15. 末尾のN行を出力</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">自然数Nをコマンドライン引数などの手段で受け取り,入力のうち末尾のN行だけを表示せよ.</span> <span class="synConstant">確認にはtailコマンドを用いよ.</span> <span class="synConstant">Usage</span> <span class="synConstant">&gt;&gt; python ans_15.py --n=5</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> fire <span class="synStatement">def</span> <span class="synIdentifier">main</span>(n): df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) <span class="synIdentifier">print</span>(df.tail(n)) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: fire.Fire(main) </pre> <h2 id="16-ファイルをN分割する">16. ファイルをN分割する</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">自然数Nをコマンドライン引数などの手段で受け取り,入力のファイルを行単位でN分割せよ.</span> <span class="synConstant">同様の処理をsplitコマンドで実現せよ.</span> <span class="synConstant">Usage</span> <span class="synConstant">&gt;&gt; python ans_16.py --n=5</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd <span class="synPreProc">import</span> fire <span class="synStatement">def</span> <span class="synIdentifier">main</span>(n): df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) <span class="synStatement">for</span> i, df <span class="synStatement">in</span> df.groupby(df.index // (<span class="synIdentifier">len</span>(df.index)/(n))): df.to_csv(<span class="synConstant">&quot;ans_16-{}.txt&quot;</span>.format(<span class="synIdentifier">int</span>(i+<span class="synConstant">1</span>)), sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: fire.Fire(main) </pre> <p>groupbyを使って分割しています。</p> <h2 id="17-1列目の文字列の異なり">17. 1列目の文字列の異なり</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">1列目の文字列の種類(異なる文字列の集合)を求めよ.</span> <span class="synConstant">確認にはcut, sort, uniqコマンドを用いよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) <span class="synIdentifier">print</span>(<span class="synIdentifier">set</span>(df.iloc[:,<span class="synConstant">0</span>].tolist())) </pre> <h2 id="18-各行を3コラム目の数値の降順にソート">18. 各行を3コラム目の数値の降順にソート</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">各行を3コラム目の数値の逆順で整列せよ(注意: 各行の内容は変更せずに並び替えよ).</span> <span class="synConstant">確認にはsortコマンドを用いよ(この問題はコマンドで実行した時の結果と合わなくてもよい).</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) df.sort_values(<span class="synConstant">2</span>, ascending=<span class="synIdentifier">False</span>).to_csv(<span class="synConstant">'ans_18.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) </pre> <h2 id="19-各行の1コラム目の文字列の出現頻度を求め出現頻度の高い順に並べる">19. 各行の1コラム目の文字列の出現頻度を求め,出現頻度の高い順に並べる</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">各行の1列目の文字列の出現頻度を求め,その高い順に並べて表示せよ.</span> <span class="synConstant">確認にはcut, uniq, sortコマンドを用いよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> pandas <span class="synStatement">as</span> pd df = pd.read_csv(<span class="synConstant">'popular-names.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, header=<span class="synIdentifier">None</span>) df[<span class="synConstant">'count'</span>] = df.groupby(<span class="synConstant">0</span>)[<span class="synConstant">0</span>].transform(<span class="synConstant">'count'</span>) df.sort_values([<span class="synConstant">'count'</span>, <span class="synConstant">0</span>], ascending=<span class="synIdentifier">False</span>).to_csv(<span class="synConstant">'ans_19.txt'</span>, sep=<span class="synConstant">'</span><span class="synSpecial">\t</span><span class="synConstant">'</span>, index=<span class="synIdentifier">False</span>, header=<span class="synIdentifier">False</span>) </pre> taxa_program 【言語処理100本ノック 2020】 1章をPythonで解いた hatenablog://entry/26006613560141816 2020-05-02T16:35:48+09:00 2020-05-04T21:03:53+09:00 こんにちは。たかぱい(@takapy0210)です。 本エントリは言語処理100本ノック 2020の1章を解いてみたので、それの備忘です。 nlp100.github.io コードはGithubに置いてあります。 github.com 第1章: 準備運動 00. 文字列の逆順 01. 「パタトクカシーー」 02. 「パトカー」+「タクシー」=「パタトクカシーー」 03. 円周率 04. 元素記号 05. n-gram 06. 集合 07. テンプレートによる文生成 08. 暗号文 09. Typoglycemia 第1章: 準備運動 00. 文字列の逆順 """ 文字列”stressed”の文… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200502/20200502163654.png" alt="f:id:taxa_program:20200502163654p:plain" title="f:id:taxa_program:20200502163654p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本エントリは言語処理100本ノック 2020の1章を解いてみたので、それの備忘です。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlp100.github.io%2Fja%2Fch01.html" title="第1章: 準備運動" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://nlp100.github.io/ja/ch01.html">nlp100.github.io</a></cite></p> <p>コードはGithubに置いてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fnlp_2020%2Ftree%2Fmaster%2Fchapter1" title="takapy0210/nlp_2020" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/nlp_2020/tree/master/chapter1">github.com</a></cite></p> <ul class="table-of-contents"> <li><a href="#第1章-準備運動">第1章: 準備運動</a><ul> <li><a href="#00-文字列の逆順">00. 文字列の逆順</a></li> <li><a href="#01-パタトクカシーー">01. 「パタトクカシーー」</a></li> <li><a href="#02-パトカータクシーパタトクカシーー">02. 「パトカー」+「タクシー」=「パタトクカシーー」</a></li> <li><a href="#03-円周率">03. 円周率</a></li> <li><a href="#04-元素記号">04. 元素記号</a></li> <li><a href="#05-n-gram">05. n-gram</a></li> <li><a href="#06-集合">06. 集合</a></li> <li><a href="#07-テンプレートによる文生成">07. テンプレートによる文生成</a></li> <li><a href="#08-暗号文">08. 暗号文</a></li> <li><a href="#09-Typoglycemia">09. Typoglycemia</a></li> </ul> </li> </ul> <h1 id="第1章-準備運動">第1章: 準備運動</h1> <h2 id="00-文字列の逆順">00. 文字列の逆順</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ.</span> <span class="synConstant">&quot;&quot;&quot;</span> text = <span class="synConstant">'stressed'</span> <span class="synIdentifier">print</span>(text[::-<span class="synConstant">1</span>]) </pre> <h2 id="01-パタトクカシーー">01. 「パタトクカシーー」</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ.</span> <span class="synConstant">&quot;&quot;&quot;</span> text = <span class="synConstant">'パタトクカシーー'</span> <span class="synIdentifier">print</span>(text[:<span class="synConstant">7</span>:<span class="synConstant">2</span>]) </pre> <h2 id="02-パトカータクシーパタトクカシーー">02. 「パトカー」+「タクシー」=「パタトクカシーー」</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ.</span> <span class="synConstant">&quot;&quot;&quot;</span> text1 = <span class="synConstant">'パトカー'</span> text2 = <span class="synConstant">'タクシー'</span> ret = <span class="synConstant">''</span> <span class="synStatement">for</span> t1, t2 <span class="synStatement">in</span> <span class="synIdentifier">zip</span>(text1, text2): ret += t1 + t2 <span class="synIdentifier">print</span>(ret) </pre> <h2 id="03-円周率">03. 円周率</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">“Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.”</span> <span class="synConstant">という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> text = <span class="synConstant">'Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.'</span> ret = [<span class="synIdentifier">len</span>(i.strip(<span class="synConstant">',.'</span>)) <span class="synStatement">for</span> i <span class="synStatement">in</span> text.split()] <span class="synIdentifier">print</span>(ret) </pre> <h2 id="04-元素記号">04. 元素記号</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">“Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.”</span> <span class="synConstant">という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭に2文字を取り出し,</span> <span class="synConstant">取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> text = <span class="synConstant">'Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.'</span> ret = {} <span class="synStatement">for</span> i, word <span class="synStatement">in</span> <span class="synIdentifier">enumerate</span>(text.replace(<span class="synConstant">'.'</span>,<span class="synConstant">''</span>).split()): <span class="synStatement">if</span> i+<span class="synConstant">1</span> <span class="synStatement">in</span> [<span class="synConstant">1</span>, <span class="synConstant">5</span>, <span class="synConstant">6</span>, <span class="synConstant">7</span>, <span class="synConstant">8</span>, <span class="synConstant">9</span>, <span class="synConstant">15</span>, <span class="synConstant">16</span>, <span class="synConstant">19</span>]: ret[i+<span class="synConstant">1</span>] = word[<span class="synConstant">0</span>] <span class="synStatement">else</span>: ret[i+<span class="synConstant">1</span>] = word[:<span class="synConstant">2</span>] <span class="synIdentifier">print</span>(ret) </pre> <h2 id="05-n-gram">05. n-gram</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.</span> <span class="synConstant">この関数を用い,”I am an NLPer”という文から単語bi-gram,文字bi-gramを得よ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">generate_ngrams</span>(text, n_gram=<span class="synConstant">2</span>): ngrams = <span class="synIdentifier">zip</span>(*[text[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_gram)]) <span class="synStatement">return</span> [<span class="synConstant">''</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> ngrams] text = <span class="synConstant">'I am an NLPer'</span> <span class="synIdentifier">print</span>(generate_ngrams(text)) text = [text <span class="synStatement">for</span> text <span class="synStatement">in</span> text.split()] <span class="synIdentifier">print</span>(generate_ngrams(text)) </pre> <h2 id="06-集合">06. 集合</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">“paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,</span> <span class="synConstant">XとYの和集合,積集合,差集合を求めよ.さらに,’se’というbi-gramがXおよびYに含まれるかどうかを調べよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">generate_ngrams</span>(text, n_gram=<span class="synConstant">2</span>): ngrams = <span class="synIdentifier">zip</span>(*[text[i:] <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n_gram)]) <span class="synStatement">return</span> <span class="synIdentifier">list</span>(ngrams) text1 = <span class="synConstant">'paraparaparadise'</span> text2 = <span class="synConstant">'paragraph'</span> X = generate_ngrams(text1) Y = generate_ngrams(text2) <span class="synIdentifier">print</span>(<span class="synConstant">'union: {}'</span>.format(<span class="synIdentifier">set</span>(X) | <span class="synIdentifier">set</span>(Y))) <span class="synIdentifier">print</span>(<span class="synConstant">'intersection: {}'</span>.format(<span class="synIdentifier">set</span>(X) &amp; <span class="synIdentifier">set</span>(Y))) <span class="synIdentifier">print</span>(<span class="synConstant">'diff: {}'</span>.format(<span class="synIdentifier">set</span>(X) - <span class="synIdentifier">set</span>(Y))) <span class="synIdentifier">print</span>(<span class="synConstant">'X include'</span> <span class="synStatement">if</span> <span class="synConstant">'se'</span> <span class="synStatement">in</span> [<span class="synConstant">''</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> X] <span class="synStatement">else</span> <span class="synConstant">'X not include'</span>) <span class="synIdentifier">print</span>(<span class="synConstant">'Y include'</span> <span class="synStatement">if</span> <span class="synConstant">'se'</span> <span class="synStatement">in</span> [<span class="synConstant">''</span>.join(ngram) <span class="synStatement">for</span> ngram <span class="synStatement">in</span> Y] <span class="synStatement">else</span> <span class="synConstant">'Y not include'</span>) </pre> <h2 id="07-テンプレートによる文生成">07. テンプレートによる文生成</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y=”気温”, z=22.4として,実行結果を確認せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">xyz</span>(x, y, z): <span class="synStatement">return</span> <span class="synConstant">'{}時の{}は{}'</span>.format(x, y, z) x = <span class="synConstant">12</span> y = <span class="synConstant">'気温'</span> z = <span class="synConstant">22.4</span> <span class="synIdentifier">print</span>(xyz(x, y, z)) </pre> <h2 id="08-暗号文">08. 暗号文</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ.</span> <span class="synConstant">英小文字ならば(219 - 文字コード)の文字に置換</span> <span class="synConstant">その他の文字はそのまま出力</span> <span class="synConstant">この関数を用い,英語のメッセージを暗号化・復号化せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synStatement">def</span> <span class="synIdentifier">cipher</span>(text): ret = <span class="synConstant">''</span>.join(<span class="synIdentifier">chr</span>(<span class="synConstant">219</span>-<span class="synIdentifier">ord</span>(c)) <span class="synStatement">if</span> c.islower() <span class="synStatement">else</span> c <span class="synStatement">for</span> c <span class="synStatement">in</span> text) <span class="synStatement">return</span> ret text = <span class="synConstant">'Never let your memories be greater than your dreams. If you can dream it, you can do it.'</span> <span class="synIdentifier">print</span>(cipher(text)) <span class="synIdentifier">print</span>(cipher(cipher(text))) </pre> <h2 id="09-Typoglycemia">09. Typoglycemia</h2> <pre class="code lang-python" data-lang="python" data-unlink><span class="synConstant">&quot;&quot;&quot;</span> <span class="synConstant">スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,</span> <span class="synConstant">それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.</span> <span class="synConstant">ただし,長さが4以下の単語は並び替えないこととする.</span> <span class="synConstant">適当な英語の文(例えば”I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind .”)</span> <span class="synConstant">を与え,その実行結果を確認せよ.</span> <span class="synConstant">&quot;&quot;&quot;</span> <span class="synPreProc">import</span> random text = <span class="synConstant">'I couldn’t believe that I could actually understand what I was reading : the phenomenal power of the human mind .'</span> text_list = text.split() <span class="synIdentifier">print</span>(<span class="synConstant">' '</span>.join(i[<span class="synConstant">0</span>] + <span class="synConstant">''</span>.join(random.sample(i[<span class="synConstant">1</span>:-<span class="synConstant">1</span>], <span class="synIdentifier">len</span>(i[<span class="synConstant">1</span>:-<span class="synConstant">1</span>]))) + i[-<span class="synConstant">1</span>] <span class="synStatement">if</span> <span class="synIdentifier">len</span>(i) &gt; <span class="synConstant">4</span> <span class="synStatement">else</span> i <span class="synStatement">for</span> i <span class="synStatement">in</span> text_list)) </pre> taxa_program 【書籍メモ】Python実践入門を読了したので機械学習PJにも使えそうなところをメモる hatenablog://entry/26006613528445554 2020-03-01T18:55:28+09:00 2020-03-01T19:04:08+09:00 こんにちは。たかぱい(@takapy0210)です。 本日は【Python実践入門】を読了したので、それの備忘です。 はじめに 全体を通して Docstring Docstringの例 ジェネレータ、デコレータ、コンテキストマネージャー ジェネレータ 具体的な使用例 デコレータ 具体的な使用例(関数の結果をキャッシュ) 具体的な使用例(関数の処理時間を計測する自作デコレータ) コンテキストマネージャー 具体的な使用例 並行処理 マルチスレッドベースの非同期実行が効果的なケース(ThreadPoolExecutorクラスを使用) 具体的な使用例 マルチプロセスベースの非同期実行が効果的なケース(… <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20200301/20200301183114.png" alt="f:id:taxa_program:20200301183114p:plain" title="f:id:taxa_program:20200301183114p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>こんにちは。たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本日は【Python実践入門】を読了したので、それの備忘です。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#全体を通して">全体を通して</a></li> <li><a href="#Docstring">Docstring</a><ul> <li><a href="#Docstringの例">Docstringの例</a></li> </ul> </li> <li><a href="#ジェネレータデコレータコンテキストマネージャー">ジェネレータ、デコレータ、コンテキストマネージャー</a><ul> <li><a href="#ジェネレータ">ジェネレータ</a><ul> <li><a href="#具体的な使用例">具体的な使用例</a></li> </ul> </li> <li><a href="#デコレータ">デコレータ</a><ul> <li><a href="#具体的な使用例関数の結果をキャッシュ">具体的な使用例(関数の結果をキャッシュ)</a></li> <li><a href="#具体的な使用例関数の処理時間を計測する自作デコレータ">具体的な使用例(関数の処理時間を計測する自作デコレータ)</a></li> </ul> </li> <li><a href="#コンテキストマネージャー">コンテキストマネージャー</a><ul> <li><a href="#具体的な使用例-1">具体的な使用例</a></li> </ul> </li> </ul> </li> <li><a href="#並行処理">並行処理</a><ul> <li><a href="#マルチスレッドベースの非同期実行が効果的なケースThreadPoolExecutorクラスを使用">マルチスレッドベースの非同期実行が効果的なケース(ThreadPoolExecutorクラスを使用)</a><ul> <li><a href="#具体的な使用例-2">具体的な使用例</a></li> </ul> </li> <li><a href="#マルチプロセスベースの非同期実行が効果的なケースProcessPoolExecutorクラスを使用">マルチプロセスベースの非同期実行が効果的なケース(ProcessPoolExecutorクラスを使用)</a><ul> <li><a href="#具体的な使用例-3">具体的な使用例</a></li> </ul> </li> <li><a href="#イベントループを利用した並行処理asyncioモジュール">イベントループを利用した並行処理(asyncioモジュール)</a><ul> <li><a href="#具体的な使用例-4">具体的な使用例</a></li> <li><a href="#コルーチンを動かすために必要なイベントループとタスク">コルーチンを動かすために必要なイベントループとタスク</a><ul> <li><a href="#イベントループ">イベントループ</a></li> <li><a href="#タスク">タスク</a></li> </ul> </li> </ul> </li> </ul> </li> <li><a href="#最後に">最後に</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>僕自身、Pythonは機械学習を学ぶためのいちツールとして勉強をしてきました。<br /> そのため、Pythonの学習は機械学習に関する書籍だったり、kaggleを始めとしたデータ分析プラットフォームに公開されているkernelを写経したりすることから始めました。</p> <p>学生時代にC++やobjective-cを触っていたりしたため、Python(≒機械学習)を勉強し始めた時にも、言語としての基本的な構文などには困りませんでしたが、お世辞にもPythonを体系的に学んできたか、と問われたときにYESとは言い辛く、(良くも悪くも)偏った情報源から蓄積された知識しかない状況でした。</p> <p>そこで今回は、Pythonという言語そのものを体系的に取得できたら、と思い本書籍を読みました。</p> <p>本の構成や対象読者に関しては、筆者の方のブログをご参照ください。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.rhoboro.com%2F2020%2F01%2F24%2Fpython-practice-book-1.html" title="Python実践入門を執筆しました" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.rhoboro.com/2020/01/24/python-practice-book-1.html">www.rhoboro.com</a></cite></p> <p>本記事は読了してみての所感や、Pythonを利用した機械学習プロジェクトでも使えそうな部分についての備忘録として残したものです。</p> <h1 id="全体を通して">全体を通して</h1> <p>Pythonの基本的な構文は網羅しつつ、開発環境やパッケージ周りの管理手法、Python特有の様々な機能まで網羅されており、Python歴が浅い自分にとってはとても有用な書籍でした。</p> <p>個人的には、Kaggleなどでコーディングする際にはあまり意識していなかった下記について学べたのはとても大きかったです。</p> <ul> <li>DocString</li> <li>ジェネレータ、デコレータ、コンテキストマネージャー</li> <li>並行処理</li> <li>ユニットテスト</li> </ul> <p>これらについて、<strong>機械学習プロジェクトなどでも使えそうなもの</strong>として簡単にまとめていければと思います。<br /> (ユニットテストに関しては毛色が異なるため、別記事で書こうと思います)</p> <h1 id="Docstring">Docstring</h1> <p>Pythonの開発は、特定の企業や個人ではなく、コミュニティによって支えられおり、 このPythonコミュニティの特徴とPythonの開発を支えている、<a href="https://www.python.org/dev/peps/">PEP(PythonEnhancementProposals)</a>と呼ばれる仕組みがあります。</p> <p>Docstringは<a href="https://www.python.org/dev/peps/pep-0257/#one-line-docstrings">PEP 257</a>で定義されているドキュメントの書き方です。</p> <p>Docstringは通常のコメントと同様にソースコードを読むときに役立ちますが、 それだけでなく組み込み関数help()などプログラムからも参照して活用できます。</p> <p>kaggleなどではあまり意識していませんでした、業務などのプロダクションで動かすコードなどには意識して書くようにしています。</p> <h2 id="Docstringの例">Docstringの例</h2> <p>Docstringを書くときは、次のように3つのクオート("""または''')で囲んで記述します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">increment</span>(n): <span class="synConstant">&quot;&quot;&quot;引数に1を加えて返す関数</span> <span class="synConstant"> </span> <span class="synConstant"> :paramn:数値</span> <span class="synConstant"> &quot;&quot;&quot;</span> <span class="synStatement">return</span> n + <span class="synConstant">1</span> </pre> <p>PEP 257は<strong>文法を規定しているものではない</strong>ので「引数や戻り値の情報をどう書くか」などはプロジェクト毎に決めて良さそうです。</p> <p>よく使われる手法には下記があると紹介されていました。<br /> (個人的にはGoogle Python StyleGuideが見易く好みです)</p> <ul> <li><p>Google Python StyleGuide</p> <ul> <li><a href="https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings">https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings</a></li> </ul> </li> <li><p>reStructuredText</p> <ul> <li><a href="https://www.sphinx-doc.org/en/2.0/usage/restructuredtext/domains.html#info-field-lists">https://www.sphinx-doc.org/en/2.0/usage/restructuredtext/domains.html#info-field-lists</a></li> </ul> </li> <li><p>numpy Docs</p> <ul> <li><a href="https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard">https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard</a></li> </ul> </li> </ul> <h1 id="ジェネレータデコレータコンテキストマネージャー">ジェネレータ、デコレータ、コンテキストマネージャー</h1> <p>Python特有の機能でジェネレータ、デコレータ、コンテキストマネージャーというものがあります。<br /> 使わなくてもコーディングはできますが、上手く活用することで、コード量の削減、パフォーマンスの向上や可読性の向上などにつながりそうだな、と感じました。</p> <p>以下で簡単にまとめます。</p> <h2 id="ジェネレータ">ジェネレータ</h2> <p>ジェネレータはリストやタプルのように、for文で利用できるイテラブルなオブジェクトです。<br /> リストやタプルは全ての要素をメモリ上に保持するため、要素数が増えれば増えるほどメモリ使用量も増える欠点があります。<br /> これに対しジェネレータは、次の要素が求められるたびに新たな要素を生成して返せます。</p> <p>つまり、<strong>要素数にかかわらずメモリ使用量を小さく保つことのできる、メモリ効率の良いイテラブルなオブジェクト</strong>です。</p> <h3 id="具体的な使用例">具体的な使用例</h3> <p>ファイルの内容を変換するジェネレータの実例として、ファイルの中身を大文字に変換するプログラムを書いてみます。</p> <p>このプログラムではファイルを1行ずつ読み込むジェネレータ関数<code>reader()</code>を作成し、その戻り値を<code>writer()</code>関数に渡します。<br /> <code>writer()</code>関数は、受け取ったイテレータを利用してファイルを1行ずつ読み込み、<code>convert()</code>関数で変換しながら、結果を新しいファイルに1行ずつ書き込んでいきます。</p> <p>読み込み→変換→書き込みの一連の流れを1行ずつ行うため、<strong>元のファイルのサイズが大きくてもメモリを圧迫することなく処理させることが可能になります。</strong></p> <p>ここでは、<code>reader()</code>関数の内部にある<strong>yield式がジェネレータの目印です。</strong></p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">convert</span>(line): <span class="synStatement">return</span> line.upper() <span class="synStatement">def</span> <span class="synIdentifier">writer</span>(dest, reader): <span class="synStatement">with</span> <span class="synIdentifier">open</span>(dest, <span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: <span class="synStatement">for</span> line <span class="synStatement">in</span> reader: f.write(convert(line)) <span class="synStatement">def</span> <span class="synIdentifier">reader</span>(src): <span class="synStatement">with</span> <span class="synIdentifier">open</span>(src) <span class="synStatement">as</span> f: <span class="synStatement">for</span> line <span class="synStatement">in</span> f: <span class="synStatement">yield</span> line <span class="synComment"># ジェネレータ</span> <span class="synComment"># 存在する任意のファイルのパスを渡す</span> r = reader(<span class="synConstant">'src.txt'</span>) writer(<span class="synConstant">'dest.txt'</span>, r) </pre> <p>ジェネレータは値を無限に返したい時や大きなデータを扱いたいときに効果を発揮しそうです。</p> <p>データ分析の文脈でも、大量のテキストや画像データを扱うシーンが少なくないと思うので、そのようなときにジェネレータを使うことでパフォーマンスの向上が期待できそうです。</p> <p>また、本書では<strong>実装中のコードでリストを返却している箇所</strong>がある場合は積極的にジェネレータに置き換えることを推奨しています。<br /> たしかにメモリ効率の面で受ける恩恵を大きそうなので、この辺りも意識して実装していこうと思いました。</p> <p>また、ジェネレータ式というものもあります。<br /> ジェネレータ式はリストやタプルなどのイテラブルがあるときは、内包表記を使ってイテラブルからジェネレータを作成できるというものです。<br /> 生成方法はリスト内包表記と同じ構文で、[]の代わりに()を使います。</p> <pre class="code lang-python" data-lang="python" data-unlink>x = [<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>] <span class="synComment"># これはリスト内包表記</span> listcomp = [i**<span class="synConstant">2</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> x] listcomp <span class="synComment"># すべての要素がメモリ上にすぐ展開される</span> &gt;&gt; [<span class="synConstant">1</span>,<span class="synConstant">4</span>,<span class="synConstant">9</span>,<span class="synConstant">16</span>,<span class="synConstant">25</span>] <span class="synComment"># これはジェネレータ式</span> gen = (i**<span class="synConstant">2</span> <span class="synStatement">for</span> i <span class="synStatement">in</span> x) gen <span class="synComment"># 各要素は必要になるまで計算されない</span> &gt;&gt; <span class="synConstant">'&lt;generatorobject&lt;genexpr&gt;at0x10bc10408&gt;'</span> <span class="synComment"># リストにしたときに初めて最後の要素まで計算される</span> <span class="synIdentifier">list</span>(gen) &gt;&gt; [<span class="synConstant">1</span>,<span class="synConstant">4</span>,<span class="synConstant">9</span>,<span class="synConstant">16</span>,<span class="synConstant">25</span>] </pre> <h2 id="デコレータ">デコレータ</h2> <p>デコレータは、関数やクラスの前後に任意の処理を追加できるシンプルな機能です。<br /> 用途は多岐に渡り、例えば次の用途でよく利用されるようです。</p> <ul> <li>関数の引数チェック</li> <li>関数の呼び出し結果のキャッシュ</li> <li>関数の実行時間の計測</li> <li>WebAPIでのハンドラの登録、ログイン状態による制限</li> </ul> <p>デコレータは関数やクラスの定義の前に@で始まる文字列を記述するだけで使用できます。</p> <p>業務ではFlaskでML APIの開発をしているので、<code>@app.route('/')</code>を付けてWebAPIのハンドラを指定する、みたいな事はやっていたのですが、本書の中には実際に知らない使い方もありとても勉強になりました。</p> <p>複数のデコレータを使ってすごい事(語彙力)もできそうでしたが、沼にハマりそうなのでここではサラッと触れます。</p> <h3 id="具体的な使用例関数の結果をキャッシュ">具体的な使用例(関数の結果をキャッシュ)</h3> <p>ここではキャッシュの例を紹介します。 下記のように<code>functools.lru_cache()</code>を利用することで、関数の結果をキャッシュすることができます。<br /> 実際に2回目に呼び出した際には、キャッシュがヒットし、すぐに結果を得ることができました。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> functools <span class="synPreProc">import</span> lru_cache <span class="synPreProc">from</span> time <span class="synPreProc">import</span> sleep <span class="synComment"># 最近の呼び出し最大16回分までキャッシュ</span> <span class="synPreProc">@</span><span class="synIdentifier">lru_cache</span>(maxsize=<span class="synConstant">16</span>) <span class="synStatement">def</span> <span class="synIdentifier">heavy_funcion</span>(n): sleep(<span class="synConstant">3</span>) <span class="synComment"># 重い処理をシミュレート</span> <span class="synStatement">return</span> n + <span class="synConstant">1</span> <span class="synComment"># 初回は時間がかかる</span> heavy_funcion(<span class="synConstant">2</span>) <span class="synComment"># キャッシュにヒットするのですぐに結果を得られる</span> heavy_funcion(<span class="synConstant">2</span>) </pre> <h3 id="具体的な使用例関数の処理時間を計測する自作デコレータ">具体的な使用例(関数の処理時間を計測する自作デコレータ)</h3> <p>デコレータの2つ目の実例として、関数の処理時間を計測するデコレータを自作してみます。<br /> デコレータ<code>elapsed_time()</code>を次のように定義します。</p> <p>こうすることで、処理時間を計測したい関数に<code>@elapsed_time</code>をつけることで計測することができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> functools <span class="synPreProc">import</span> wraps <span class="synPreProc">import</span> time <span class="synStatement">def</span> <span class="synIdentifier">elapsed_time</span>(f): <span class="synPreProc">@</span><span class="synIdentifier">wraps</span>(f) <span class="synStatement">def</span> <span class="synIdentifier">wrapper</span>(*args, **kwargs): start = time.time() v = f(*args, **kwargs) <span class="synIdentifier">print</span>(<span class="synConstant">'{} done in {:.2f} s'</span>.format(f.__name__, (time.time() - start))) <span class="synStatement">return</span> v <span class="synStatement">return</span> wrapper <span class="synComment"># 0からn-1までの総和を計算する関数</span> <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">def</span> <span class="synIdentifier">func_hoge</span>(n): <span class="synStatement">return</span> <span class="synIdentifier">sum</span>(i <span class="synStatement">for</span> i <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n)) <span class="synIdentifier">print</span>(func_hoge(<span class="synConstant">1000000</span>)) &gt;&gt; func_hoge done <span class="synStatement">in</span> <span class="synConstant">0.06</span> s </pre> <p>実際に関数の処理時間を計測したい場面は多々あると思います。<br /> そんなときに上記のようなデコレータを1つ定義しておくことで、手軽に計測できるのはとても便利だと思いました。</p> <h2 id="コンテキストマネージャー">コンテキストマネージャー</h2> <p>with文に対応したオブジェクトをコンテキストマネージャーと呼びます。<br /> コンテキストマネージャーを利用している代表例に<code>open()</code>があり、これは多くの人が使ったことのある使い方だと思います。</p> <p>with文はよく<code>try: finally:</code>の置き換えで利用されますが、その本質はサンドイッチのように、<strong>ある処理の前後の処理をまとめて再利用可能にしてくれる点にあります。</strong></p> <p><code>open()</code>関数での利用例</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">with</span> <span class="synIdentifier">open</span>(<span class="synConstant">'some.txt'</span>, <span class="synConstant">'w'</span>) <span class="synStatement">as</span> f: f.write(<span class="synConstant">'python'</span>) </pre> <h3 id="具体的な使用例-1">具体的な使用例</h3> <p>例えば下記のようなコンテキストマネージャーを設定することで、特定箇所のデバッグレベルを変更することができます。</p> <p>最初にINFOレベルを指定しているため、DEBUGレベルのログを出力するlogger.debug()のログは無視されます。<br /> しかし、withブロック内に限っては、一時的にログレベルをDEBUGレベルまで引き下げているため、withブロック内で実行したlogger.debug()のログは特別に出力されます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> logging <span class="synPreProc">from</span> contextlib <span class="synPreProc">import</span> contextmanager logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) <span class="synComment"># デフォルトをINFOレベルとし、DEBUGレベルのログは無視する</span> logger.setLevel(logging.INFO) <span class="synPreProc">@</span><span class="synIdentifier">contextmanager</span> <span class="synStatement">def</span> <span class="synIdentifier">debug_context</span>(): level = logger.level <span class="synStatement">try</span>: <span class="synComment"># ログレベルを変更する</span> logger.setLevel(logging.DEBUG) <span class="synStatement">yield</span> <span class="synStatement">finally</span>: <span class="synComment"># もとのログレベルに戻す</span> logger.setLevel(level) <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): logger.info(<span class="synConstant">'before: info log'</span>) logger.debug(<span class="synConstant">'before: debug log'</span>) <span class="synComment"># DEBUGログを見たい処理をwithブロック内で実行する</span> <span class="synStatement">with</span> debug_context(): logger.info(<span class="synConstant">'inside the block: info log'</span>) logger.debug(<span class="synConstant">'inside the block: debug log'</span>) logger.info(<span class="synConstant">'after: info log'</span>) logger.debug(<span class="synConstant">'after: debug log'</span>) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: main() </pre> <p>コンテキストマネージャーは、ある処理の前後の処理をまとめて再利用可能にしてくれます。<br /> この視点で見ると、活用できるシーンは非常に多そうです。</p> <p>たとえば、次の処理はコンテキストマネージャーを使って実現できると本書では述べています。</p> <ul> <li>開始/終了のステータス変更や通知</li> <li>ネットワークやDBの接続/切断処理</li> </ul> <p>デコレータと被ってしまいますが、たとえば下記のようなコンテキストマネージャーを定義することで、処理の開始/終了通知と処理時間を出力させることができます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">@</span><span class="synIdentifier">contextmanager</span> <span class="synStatement">def</span> <span class="synIdentifier">timer</span>(name): <span class="synStatement">try</span>: t0 = time.time() logging.info(<span class="synConstant">'{} start'</span>.format(name)) <span class="synStatement">yield</span> <span class="synStatement">finally</span>: logging.info(<span class="synConstant">'{} done in {:.2f} s'</span>.format(name, (time.time() - t0))) <span class="synStatement">with</span> timer(<span class="synConstant">'前処理'</span>): train = preprosessing(train) &gt;&gt; 前処理 start &gt;&gt; 前処理 done <span class="synStatement">in</span> <span class="synConstant">0</span>.05s </pre> <h1 id="並行処理">並行処理</h1> <p>並行処理は、プログラム実行時のパフォーマンスを向上させるために必要不可欠です。<br /> kaggleなどの分析コンペにおいても、実行時間に制限のあるコンペもあり学んでおいて損はない分野だと思います。</p> <p>この章では</p> <ul> <li>マルチスレッドを使う方法</li> <li>マルチプロセスを使う方法</li> <li>イベントループを使う方法</li> </ul> <p>の3つの方法が説明されています。</p> <p>並行処理を導入する際、どの方法が適しているかはその処理が <strong>「CPUバウンドな処理か」</strong> <strong>「I/Oバウンドな処理か」</strong> で分かれます。</p> <p>それぞれの特徴は次のとおりです。</p> <ul> <li><p>CPUバウンドな処理(暗号化など)<br /> 数値計算などCPUのリソースを使って計算を行う処理複数のコアを同時に使って並列処理を行える<strong>マルチプロセスが有効です。</strong> Pythonには<a href="https://qiita.com/ttiger55/items/5e1d5a3405d2b3ef8f40">GIL</a>があるため、マルチスレッド、イベントループによる処理高速化は期待できないようです。</p></li> <li><p>I/Oバウンドな処理(データベースへの接続など)<br /> WebAPIの利用など通信による待ち時間が発生する処理に対して、<strong>マルチプロセス、マルチスレッド、イベントループいずれも有効です。</strong> どの方法を選択するかはオーバーヘッドや実装しやすさを考慮して決めると良いようです。 一般的に、実行時のオーバーヘッドは大きいものから順にマルチプロセス、マルチスレッド、イベントループとなります。</p></li> </ul> <p>ここでもそれぞれについて簡単にまとめます。</p> <h2 id="マルチスレッドベースの非同期実行が効果的なケースThreadPoolExecutorクラスを使用">マルチスレッドベースの非同期実行が効果的なケース(ThreadPoolExecutorクラスを使用)</h2> <p>前述したようにI/Oバウンドな処理では、マルチスレッド化は有効な選択肢になります。<br /> I/Oを伴う処理は、その処理にかかる時間がハードウェアやネットワークなどの外部に依存します。<br /> そのため、プログラムを書き換えても個々の処理の高速化は期待できません。</p> <p>しかし、複数の処理がある場合は非同期実行で並行化すると、通信中の待ち時間を有効活用できて合計時間を短縮できます。</p> <h3 id="具体的な使用例-2">具体的な使用例</h3> <p>複数のサイトのトップページをダウンロードする処理を考えます。<br /> ダウンロード処理は待ち時間が発生するI/O処理の典型的な例です。<br /> 比較のためまずは逐次処理で実装し、その後マルチスレッド処理に変更します。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> hashlib <span class="synPreProc">import</span> md5 <span class="synPreProc">from</span> pathlib <span class="synPreProc">import</span> Path <span class="synPreProc">from</span> urllib <span class="synPreProc">import</span> request <span class="synPreProc">import</span> time <span class="synPreProc">from</span> functools <span class="synPreProc">import</span> wraps <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ( ThreadPoolExecutor, as_completed ) <span class="synComment"># 対象ページのURL一覧</span> urls = [ <span class="synConstant">'https://twitter.com'</span>, <span class="synConstant">'https://facebook.com'</span>, <span class="synConstant">'https://instagram.com'</span>, ] <span class="synComment"># 処理時間を計測するデコレータ</span> <span class="synStatement">def</span> <span class="synIdentifier">elapsed_time</span>(f): <span class="synPreProc">@</span><span class="synIdentifier">wraps</span>(f) <span class="synStatement">def</span> <span class="synIdentifier">wrapper</span>(*args, **kwargs): start = time.time() v = f(*args, **kwargs) <span class="synIdentifier">print</span>(<span class="synConstant">'{} done in {:.2f} s'</span>.format(f.__name__, (time.time() - start))) <span class="synStatement">return</span> v <span class="synStatement">return</span> wrapper <span class="synStatement">def</span> <span class="synIdentifier">download</span>(url): req = request.Request(url) <span class="synComment"># ファイル名に/などが含まれないようにする</span> name = md5(url.encode(<span class="synConstant">'utf-8'</span>)).hexdigest() file_path = <span class="synConstant">'./'</span> + name <span class="synStatement">with</span> request.urlopen(req) <span class="synStatement">as</span> res: Path(file_path).write_bytes(res.read()) <span class="synStatement">return</span> url, file_path <span class="synComment"># 逐次処理</span> <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">def</span> <span class="synIdentifier">get_sequential</span>(): <span class="synStatement">for</span> url <span class="synStatement">in</span> urls: <span class="synIdentifier">print</span>(download(url)) <span class="synComment"># 並行処理</span> <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">def</span> <span class="synIdentifier">get_multi_thread</span>(): <span class="synComment"># max_workersのデフォルトはコア数x5</span> <span class="synStatement">with</span> ThreadPoolExecutor(max_workers=<span class="synConstant">3</span>) <span class="synStatement">as</span> executor: futures = [executor.submit(download, url) <span class="synStatement">for</span> url <span class="synStatement">in</span> urls] <span class="synStatement">for</span> future <span class="synStatement">in</span> as_completed(futures): <span class="synComment"># 完了したものから取得できる</span> <span class="synIdentifier">print</span>(future.result()) <span class="synComment"># 逐次処理</span> get_sequential() &gt;&gt; (<span class="synConstant">'https://twitter.com'</span>, <span class="synConstant">'./be8b09f7f1f66235a9c91986952483f0'</span>) &gt;&gt; (<span class="synConstant">'https://facebook.com'</span>, <span class="synConstant">'./a023cfbf5f1c39bdf8407f28b60cd134'</span>) &gt;&gt; (<span class="synConstant">'https://instagram.com'</span>, <span class="synConstant">'./09f8b89478d7e1046fa93c7ee4afa99e'</span>) &gt;&gt; get_sequential: <span class="synConstant">2.929287910461426</span> <span class="synComment"># 並行処理</span> get_multi_thread() &gt;&gt; (<span class="synConstant">'https://twitter.com'</span>, <span class="synConstant">'./be8b09f7f1f66235a9c91986952483f0'</span>) &gt;&gt; (<span class="synConstant">'https://facebook.com'</span>, <span class="synConstant">'./a023cfbf5f1c39bdf8407f28b60cd134'</span>) &gt;&gt; (<span class="synConstant">'https://instagram.com'</span>, <span class="synConstant">'./09f8b89478d7e1046fa93c7ee4afa99e'</span>) &gt;&gt; get_multi_thread: <span class="synConstant">1.8627910614013672</span> </pre> <p>実際に実行してみると、並行処理の方が1sほど時間を短縮することができました。</p> <h2 id="マルチプロセスベースの非同期実行が効果的なケースProcessPoolExecutorクラスを使用">マルチプロセスベースの非同期実行が効果的なケース(ProcessPoolExecutorクラスを使用)</h2> <p>マルチプロセスは、I/Oバウンドな処理だけでなく<strong>数値計算などのCPUバウンドな処理の高速化にも有効です。</strong> <br /> これは、マルチプロセスであればGILの制約を受けずに、複数コアを同時に使って並列処理を行えるためだそうです。</p> <h3 id="具体的な使用例-3">具体的な使用例</h3> <p>CPUバウンドな処理には暗号化(復号)や次元数の大きい行列演算などがありますが、今回は本書の例に従いフィボナッチ数列を計算します。<br /> (マルチプロセスは対話モードでの実行は適しません。そのため、ここで実行するマルチプロセス処理はスクリプトとして実行する必要があります)</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> os <span class="synPreProc">import</span> time <span class="synPreProc">import</span> sys <span class="synPreProc">from</span> concurrent.futures <span class="synPreProc">import</span> ( ThreadPoolExecutor, as_completed ) <span class="synStatement">def</span> <span class="synIdentifier">fibonacci</span>(n): a, b = <span class="synConstant">0</span>, <span class="synConstant">1</span> <span class="synStatement">for</span> _ <span class="synStatement">in</span> <span class="synIdentifier">range</span>(n): a, b = b, b + a <span class="synStatement">else</span>: <span class="synStatement">return</span> a <span class="synStatement">def</span> <span class="synIdentifier">elapsed_time</span>(f): <span class="synStatement">def</span> <span class="synIdentifier">wrapper</span>(*args, **kwargs): st = time.time() v = f(*args, **kwargs) <span class="synIdentifier">print</span>(f<span class="synConstant">&quot;{f.__name__}: {time.time() - st}&quot;</span>) <span class="synStatement">return</span> v <span class="synStatement">return</span> wrapper <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">def</span> <span class="synIdentifier">get_sequential</span>(nums): <span class="synStatement">for</span> num <span class="synStatement">in</span> nums: fibonacci(num) <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">def</span> <span class="synIdentifier">get_multi_thread</span>(nums): <span class="synStatement">with</span> ThreadPoolExecutor() <span class="synStatement">as</span> e: futures = [e.submit(fibonacci, num) <span class="synStatement">for</span> num <span class="synStatement">in</span> nums] <span class="synStatement">for</span> future <span class="synStatement">in</span> as_completed(futures): future.result() <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): n = <span class="synIdentifier">int</span>(sys.argv[<span class="synConstant">1</span>]) nums = [n] * os.cpu_count() get_sequential(nums) get_multi_thread(nums) <span class="synStatement">if</span> __name__ == <span class="synConstant">'__main__'</span>: main() </pre> <p>私の環境で実行してみたところ、マルチプロセスの方が若干時間がかかる、という結果になりました。<br /> 物理コア数:6, 論理コア数:12 なので結構速くなることを期待しておりましたが、これはなぜか分かりません。。。<br /> (引数の値をもう少し大きくすれば、また違った結果が得られたのかもしれません。気が向いたら調査してみようと思います)</p> <pre class="code lang-sh" data-lang="sh" data-unlink>python fib.py <span class="synConstant">500000</span> <span class="synStatement">&gt;&gt;</span> get_sequential: <span class="synConstant">27</span>.<span class="synConstant">83519196510315</span> <span class="synStatement">&gt;&gt;</span> get_multi_thread: <span class="synConstant">28</span>.<span class="synConstant">488017320632935</span> </pre> <h2 id="イベントループを利用した並行処理asyncioモジュール">イベントループを利用した並行処理(asyncioモジュール)</h2> <p>asyncioモジュールを用いる事で、途中で処理を中断させ、別の処理を実行させることができます。<br /> 処理を中断させるポイントは、<strong>I/O処理による待ち時間が発生する箇所とその処理を呼び出している箇所</strong>です。</p> <p>asyncioモジュールを使ううえで、欠かせない要素が<strong>コルーチン</strong>です。<br /> コルーチンとはサブルーチンと同じく一連の処理をまとめたものです。</p> <blockquote><p>サブルーチンはPythonでは関数にあたり、一度呼び出されると先頭から最後まで(または途中で何かが返されるまで)一気に実行されます。<br /> 一方、コルーチンは処理の途中で中断、再開ができる性質を持ちます。この性質を利用すると、複数の処理を並行して動作させられます。</p></blockquote> <h3 id="具体的な使用例-4">具体的な使用例</h3> <p>例としてここではWebAPIの利用を想定します。 まずWebAPIを利用する処理をコルーチンとして定義します。 そして、そのコルーチンを呼び出す処理もまたコルーチンとして定義します。</p> <p>このように実装していくと必然的にコルーチンの中でコルーチンを呼び出す箇所が出てきます。<br /> そこが処理を中断できるポイントで、コード上ではawaitキーワードを記述します。awaitキーワードがあっても、戻り値は通常の関数呼び出しと同様に扱えます。</p> <p>下記例では、普通に<code>call_web_api()</code>を定義し呼び出すと、レスポンスが返ってくるまでは次の処理に進めません。<br /> しかし、コルーチンを定義して呼び出すことで、レスポンスは先に返ってきたものから順に処理されます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> asyncio <span class="synPreProc">import</span> random <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">call_web_api</span>(url): <span class="synComment"># Web APIの処理をここではスリープで代用</span> <span class="synIdentifier">print</span>(f<span class="synConstant">'send a request: {url}'</span>) <span class="synStatement">await</span> asyncio.sleep(random.random()) <span class="synIdentifier">print</span>(f<span class="synConstant">'got a response: {url}'</span>) <span class="synStatement">return</span> url <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">async_download</span>(url): <span class="synComment"># awaitを使ってコルーチンを呼び出す</span> response = <span class="synStatement">await</span> call_web_api(url) <span class="synStatement">return</span> response <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): task = asyncio.gather( async_download(<span class="synConstant">'https://twitter.com/'</span>), async_download(<span class="synConstant">'https://facebook.com'</span>), async_download(<span class="synConstant">'https://instagram.com'</span>), ) <span class="synStatement">return</span> <span class="synStatement">await</span> task <span class="synComment"># スクリプトから実行する場合はasyncioを使用する</span> result = asyncio.run(main()) <span class="synComment"># notebookで実行する場合はawaitを使用する</span> result = <span class="synStatement">await</span> main() </pre> <p>ここでの処理の流れをみてみます。</p> <ol> <li>gather()関数の引数に渡した最初のコルーチンが実行されます。その処理中に、asyncio.sleep()まで進むと処理が中断され、2番目のコルーチンが動き始めます。asyncio.sleep()で処理が中断される理由は、I/O処理による待ち時間が発生するためです。</li> <li>同様に2番目のコルーチンもasyncio.sleep()まで進むと処理が中断され、3番目のコルーチンが動き始めます。</li> <li>そして、3番目のコルーチンがasyncio.sleep()まで進み処理が中断されたあとは、レスポンスが返ってくるまで待機されます。</li> <li>そのあと、レスポンスが返ってきて再開可能になったコルーチンから順次処理が再開されています。最後のレスポンスの処理まで終わると、gather()関数は戻り値となるリストを返します。</li> </ol> <p>resultの中身をみてみると、リストの順番は処理順に関わらず、gather()関数に渡したコルーチンの順番と一致します。</p> <pre class="code lang-python" data-lang="python" data-unlink>result &gt;&gt; [<span class="synConstant">'https://twitter.com/'</span>,<span class="synConstant">'https://facebook.com'</span>,<span class="synConstant">'https://instagram.com'</span>] </pre> <h3 id="コルーチンを動かすために必要なイベントループとタスク">コルーチンを動かすために必要なイベントループとタスク</h3> <p>コルーチンを動かすために必要なものが<strong>イベントループとタスク</strong>です。<br /> コルーチンはその実行がスケジューリングされるとタスクになります。 そして、イベントループがI/Oイベントに応じてタスクの実行を制御します。</p> <h4 id="イベントループ">イベントループ</h4> <p><code>acyncio.run()</code>関数を呼び出すと新しいイベントループが作成され、このイベントループがコルーチンの実行を制御します。<br /> コルーチンの内部では、現在実行中のイベントループを<code>asyncio.get_running_loop()</code>関数で取得できます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">import</span> asyncio <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): loop = asyncio.get_running_loop() <span class="synIdentifier">print</span>(loop) <span class="synStatement">await</span> main() &gt;&gt; &lt;_UnixSelectorEventLoop running=<span class="synIdentifier">True</span> closed=<span class="synIdentifier">False</span> debug=<span class="synIdentifier">False</span>&gt; </pre> <p>イベントループが実行されていることが分かります。<br /> このイベントループがさまざまなI/Oイベントに応じてスケジューリングされた処理を実行してくれます。</p> <h4 id="タスク">タスク</h4> <p>コルーチンを実行する方法は3つ用意されています。</p> <ul> <li>1つ目はasyncio.run()に渡す方法</li> <li>2つ目はコルーチンの内部でawaitコルーチンとする方法</li> <li>3つ目は後述するタスクを作成して実行する方法</li> </ul> <p>タスクとは、実行がスケジューリングされたコルーチンをカプセル化したものです。<br /> タスクの作成は、次のようにasyncio.create_task()関数を使います。この呼び出しの裏では、先ほどのasyncio.get_running_loop()関数で取得されるループを使ってタスクが作成されます。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">coro</span>(n): <span class="synStatement">await</span> asyncio.sleep(n) <span class="synStatement">return</span> n <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): task = asyncio.create_task(coro(<span class="synConstant">1</span>)) <span class="synIdentifier">print</span>(task) <span class="synStatement">return</span> <span class="synStatement">await</span> task <span class="synComment"># print()時点ではまだPending状態</span> <span class="synComment"># awaitで初めて実行されていることが分かる</span> <span class="synStatement">await</span> main() &gt;&gt; &lt;Task pending coro=&lt;coro() running at &lt;ipython-<span class="synIdentifier">input</span>-<span class="synConstant">49</span>-adc0461ab5af&gt;:<span class="synConstant">1</span>&gt;&gt; &gt;&gt; <span class="synConstant">1</span> </pre> <p>タスクを作成すると、次のように並行して実行できます。<br /> コルーチンのまま呼び出した場合と比較するとその違いを感じられます。(コルーチンのまま呼び出しても並行処理はされない)</p> <p>どちらもawaitを使ってコルーチンを呼び出しているので、main処理自体は一瞬で終了していることが分かります。</p> <p>タスクを作成して実行</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 3秒で完了する</span> <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): task1 = asyncio.create_task(coro(<span class="synConstant">1</span>)) task2 = asyncio.create_task(coro(<span class="synConstant">2</span>)) task3 = asyncio.create_task(coro(<span class="synConstant">3</span>)) <span class="synIdentifier">print</span>(<span class="synStatement">await</span> task1) <span class="synIdentifier">print</span>(<span class="synStatement">await</span> task2) <span class="synIdentifier">print</span>(<span class="synStatement">await</span> task3) <span class="synStatement">await</span> main() &gt;&gt; main: <span class="synConstant">9.5367431640625e-07</span> &gt;&gt; <span class="synConstant">1</span> &gt;&gt; <span class="synConstant">2</span> &gt;&gt; <span class="synConstant">3</span> </pre> <p>コルーチンのまま実行</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># 並行処理できないため6秒かかる</span> <span class="synPreProc">@</span><span class="synIdentifier">elapsed_time</span> <span class="synStatement">async</span> <span class="synStatement">def</span> <span class="synIdentifier">main</span>(): <span class="synIdentifier">print</span>(<span class="synStatement">await</span> coro(<span class="synConstant">1</span>)) <span class="synIdentifier">print</span>(<span class="synStatement">await</span> coro(<span class="synConstant">2</span>)) <span class="synIdentifier">print</span>(<span class="synStatement">await</span> coro(<span class="synConstant">3</span>)) <span class="synStatement">await</span> main() &gt;&gt; main: <span class="synConstant">1.9073486328125e-06</span> &gt;&gt; <span class="synConstant">1</span> &gt;&gt; <span class="synConstant">2</span> &gt;&gt; <span class="synConstant">3</span> </pre> <h1 id="最後に">最後に</h1> <p>冒頭でも述べましたが、Pythonを体系的には学んできていない人にとっては一読する価値のある本だと思いました。</p> <p>並行処理など、まだ詳細を把握仕切れていない部分はありつつも、パフォーマンスを考慮する際には避けては通れない箇所だと思うので実践を通して身につけていこう思います。</p> taxa_program データ分析コンペで使っているワイの学習・推論パイプラインを晒します hatenablog://entry/26006613483229154 2019-12-14T16:51:19+09:00 2019-12-14T17:09:47+09:00 こんにちは!たかぱい(@takapy0210)です。 本記事はKaggle Advent Calendar 2019の14日目の記事です。 昨日はkaggle masterのアライさんのKaggleコード遺産の記事でした! とても参考になり、いくつか自分の遺産にも取り入れさせていただきました。 さて本日は、以前から何回かLTさせていただいていたオレオレパイプラインを公開しましたので、簡単にご紹介できればと思います。 はじめに パイプラインについて 実行方法 補足 config.yamlについて 15_show_all_features.pyについて 作成される画像ファイルについて 終わりに は… <p><figure class="figure-image figure-image-fotolife" title=" "><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20191214/20191214164035.png" alt="f:id:taxa_program:20191214164035p:plain" title="f:id:taxa_program:20191214164035p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption> </figcaption></figure></p> <p>こんにちは!たかぱい(<a href="https://twitter.com/takapy0210">@takapy0210</a>)です。</p> <p>本記事は<a href="https://qiita.com/advent-calendar/2019/kaggle">Kaggle Advent Calendar 2019</a>の14日目の記事です。</p> <p>昨日は<a href="https://qiita.com/kaggle_master-arai-san">kaggle masterのアライさん</a>の<a href="https://qiita.com/kaggle_master-arai-san/items/d59b2fb7142ec7e270a5">Kaggleコード遺産</a>の記事でした!<br/> とても参考になり、いくつか自分の遺産にも取り入れさせていただきました。</p> <p>さて本日は、以前から何回かLTさせていただいていたオレオレパイプラインを公開しましたので、簡単にご紹介できればと思います。</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#パイプラインについて">パイプラインについて</a><ul> <li><a href="#実行方法">実行方法</a></li> <li><a href="#補足">補足</a><ul> <li><a href="#configyamlについて">config.yamlについて</a></li> <li><a href="#15_show_all_featurespyについて">15_show_all_features.pyについて</a></li> <li><a href="#作成される画像ファイルについて">作成される画像ファイルについて</a></li> </ul> </li> </ul> </li> <li><a href="#終わりに">終わりに</a></li> </ul> <h1 id="はじめに">はじめに</h1> <p>元々、パイプラインが良い感じにできたら公開したいな〜と思っていたのですが、何回かLTする中で、ありがたいことに以下のようなお声をいただき、<strong>あっ、Kaggleのアドベントカレンダーで公開すれば丁度よいのでは?</strong>と思い、急いで準備してみました。</p> <p><blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">です笑<br>自分でもイケてないなぁと思いつつ、、、<br><br>まだ改良したい部分があり、そこが良い感じで整ったら公開しようと思っています!(年内には頑張ろうと思います💪)</p>&mdash; takapy | たかぱい (@takapy0210) <a href="https://twitter.com/takapy0210/status/1200962022105468928?ref_src=twsrc%5Etfw">2019年12月1日</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><del>(が、思っていたより準備が進まず、コードもそこまでキレイになっていないことをご了承ください・・・🙇‍♂️)</del></p> <h1 id="パイプラインについて">パイプラインについて</h1> <p>下記Githubにて公開しています。<br/> タイタニックのデータを入力として、LightGBM、xgboostを利用して学習・推論の一通りの流れを実行できるようにしてあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Ftakapy0210%2Fml_pipeline" title="takapy0210/ml_pipeline" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/takapy0210/ml_pipeline">github.com</a></cite></p> <p>どのようなものかは以前LTした下記資料をみていただければイメージがつきやすいかと思います。</p> <p><a href="https://speakerdeck.com/takapy/detafen-xi-konpenioite-te-zheng-liang-guan-li-nipi-bi-siteiruquan-ren-lei-nichuan-etaixiang-i">&#x30C7;&#x30FC;&#x30BF;&#x5206;&#x6790;&#x30B3;&#x30F3;&#x30DA;&#x306B;&#x304A;&#x3044;&#x3066; &#x7279;&#x5FB4;&#x91CF;&#x7BA1;&#x7406;&#x306B;&#x75B2;&#x5F0A;&#x3057;&#x3066;&#x3044;&#x308B;&#x5168;&#x4EBA;&#x985E;&#x306B;&#x4F1D;&#x3048;&#x305F;&#x3044;&#x60F3;&#x3044; - Speaker Deck</a></p> <p><a href="https://speakerdeck.com/takapy/xue-xi-tui-lun-paipurainwogou-zhu-surushang-deda-qie-nisiteirukoto">&#x5B66;&#x7FD2;&#x30FB;&#x63A8;&#x8AD6;&#x30D1;&#x30A4;&#x30D7;&#x30E9;&#x30A4;&#x30F3;&#x3092;&#x69CB;&#x7BC9;&#x3059;&#x308B;&#x4E0A;&#x3067;&#x5927;&#x5207;&#x306B;&#x3057;&#x3066;&#x3044;&#x308B;&#x3053;&#x3068; - Speaker Deck</a></p> <p>また、資料中にも記載していますが、下記にて言及されているものを参考に自分なりに少しカスタマイズしたパイプラインとなっています。(筆者の方々ありがとうございます)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Famalog.hateblo.jp%2Fentry%2Fkaggle-feature-management" title="Kaggleで使えるFeather形式を利用した特徴量管理法 - 天色グラフィティ" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://amalog.hateblo.jp/entry/kaggle-feature-management">amalog.hateblo.jp</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgihyo.jp%2Fbook%2F2019%2F978-4-297-10843-4" title="Kaggleで勝つデータ分析の技術" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://gihyo.jp/book/2019/978-4-297-10843-4">gihyo.jp</a></cite></p> <h2 id="実行方法">実行方法</h2> <p>下記コマンドで、タイタニックデータを使って学習・推論ができます。<br/> (GitHubのREADMEにも記載しています)</p> <pre class="code lang-sh" data-lang="sh" data-unlink><span class="synComment"># クローン</span> git clone https://github.com/takapy0210/ml_pipeline.git <span class="synComment"># フォルダ移動</span> <span class="synStatement">cd</span> ml_pipeline/code <span class="synComment"># 特徴量生成</span> python 10_titanic_fe.py <span class="synComment"># 学習・推論・sub作成</span> python 20_run.py </pre> <h2 id="補足">補足</h2> <p>パイプラインについて、冒頭のスライドではあまり触れられていない部分について簡単に補足します。</p> <h3 id="configyamlについて">config.yamlについて</h3> <p>下記のようにパスを設定しています。<br/> こうすることで、このyamlファイルを読み込むだけで複数のスクリプトファイルから同じ変数値が使用できます。(逆にこのファイルを変更すれば、全スクリプトに反映されます)</p> <pre class="code lang-yaml" data-lang="yaml" data-unlink><span class="synIdentifier">SETTING</span><span class="synSpecial">:</span> <span class="synIdentifier">MODEL_DIR_NAME</span><span class="synSpecial">:</span> <span class="synConstant">'../models/'</span><span class="synComment"> # 学習結果の諸々を保存するディレクトリ</span> <span class="synIdentifier">SUB_DIR_NAME</span><span class="synSpecial">:</span> <span class="synConstant">'../data/submission/'</span><span class="synComment"> # sample提出ファイル保存ディレクトリ</span> <span class="synIdentifier">RAW_DATA_DIR_NAME</span><span class="synSpecial">:</span> <span class="synConstant">'../data/raw/'</span><span class="synComment"> # オリジナルデータの格納先ディレクトリ</span> <span class="synIdentifier">FEATURE_PATH</span><span class="synSpecial">:</span> <span class="synConstant">'../data/features/'</span><span class="synComment"> # オリジナルデータ</span> </pre> <h3 id="15_show_all_featurespyについて">15_show_all_features.pyについて</h3> <p>これは単純に、<code>10_titanic_fe.py</code>で生成した特徴量のカラム名を取得するためのスクリプトです。<br/> 実行すると、ターミナル上にカラム名が下記のように出力されます。</p> <pre class="code lang-sh" data-lang="sh" data-unlink>~~~ <span class="synStatement">'</span><span class="synConstant">age_mis_val_median</span><span class="synStatement">'</span> ,<span class="synStatement">'</span><span class="synConstant">family_size</span><span class="synStatement">'</span> ~~~ </pre> <p>これを、<code>20_run.py</code>で特徴量を指定している箇所(下記)に貼り付けることで、作業負荷を軽減しようというものです。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synComment"># pklからロードする特徴量の指定</span> features = [ <span class="synConstant">'age_mis_val_median'</span> ,<span class="synConstant">'family_size'</span> ... ] </pre> <h3 id="作成される画像ファイルについて">作成される画像ファイルについて</h3> <p>例えばLightGBMを実行すると、下記のようなfeature importanceをプロットした画像ファイルが出力されます。</p> <p><figure class="figure-image figure-image-fotolife" title="feature importance(gain)"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/t/taxa_program/20191214/20191214161142.png" alt="f:id:taxa_program:20191214161142p:plain" title="f:id:taxa_program:20191214161142p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>feature importance(gain)</figcaption></figure></p> <p>青いバーは各foldのfeature importanceの平均を示しており、オレンジのバーは各foldのfeature importanceの標準偏差となっています。</p> <p>赤丸は、上の平均と標準偏差から計算した変動係数(標準偏差/平均)です。この変動係数が大きいと、各foldでのfeature importanceのブレが大きい(あるfoldではとても重要だが、別のfoldではそこまで重要じゃない)ということが分かるので、特徴量選択する際の参考になるのではないかと思います。</p> <h1 id="終わりに">終わりに</h1> <p>このパイプラインが少しでもみなさんの参考になれば嬉しい限りです。</p> <p>今後もコンペに取り組みつつ、自分なりに使いやすいパイプラインにアップデートしていければと思っています。コードも汚いのでリファクタリングしつつ・・・汗<br/> (何かオカシイところあればツッコミお願いします🙇‍♂️)</p> <p>明日は本記事でも参考にしているブログを書いている<a href="https://qiita.com/amaotone">amaotoneさん</a>です。</p> <p>楽しみ!</p> taxa_program