LABOT 機械学習ブログ

本郷で機械学習の受託開発をしている会社のブログです。CEOの堀田がディープラーニング・データ分析周辺の技術情報を発信します。

サイバーエージェントのデータマイニング本がデータサイエンティスト必読書だった件

データマイニング、データサイエンス、python, R

堀田(@YoshiHotta)です。この記事はサイバーエージェントの秋葉原ラボの方が執筆された『データマイニングエンジニアの教科書』の書評です。

企業でデータマイニングをする人に必要な知識を俯瞰できる、しっかりしたデータマイニングの本だと思いました。データマイニングの初心者にも中級者にもぜひオススメしたい一冊だったので書評を書くことにしました。

また、データマイニングの独習に役に立つ書籍も多数紹介します。

データマイニングエンジニアの教科書

データマイニングエンジニアの教科書

  • 作者: 森下壮一郎,水上ひろき,高野雅典,數見拓朗,和田計也
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2019/06/27
  • メディア: Kindle版
  • この商品を含むブログを見る

この本は(特に Web 系の) データ分析の実務者に必要な事柄が網羅的に取り上げられています。300ページという厚さからすると扱っているテーマはとても幅広いです。一つ一つのテーマは重要なキーワードと概念を説明するに留め、詳しいことを勉強できるように参考文献が充実しています。

新しい分野に入るときはまず全体を俯瞰し、分野の地図を頭の中に作ることが大事です。 地図があれば闇雲に本を読み漁ることをせずに済み、学習時間を節約できます。 データマイニングという広大な分野を前に、怖気づくことなく、迷子になることもなくなるのです。

データマイニング初心者は『データマイニングエンジニアの教科書』を読むことで分野の地図を俯瞰し、データマイニングの学習プランを立てることができるでしょう。 前述の通り、この本の参考文献は充実しており、標準的な教科書が引用されています。 この本を読んでから引用されている文献に進むことで各テーマを本格的に学習できます。

又、この本はデータマイニングの既習者・実践者にも有益であると感じました。

第一に、この本をパラパラとめくることで知識に抜けがないか確かめることができ、復習になると思います。 第二に、この本の中には思わず唸ってしまう見事な説明や例示が散りばめられています。

データマイニングは横断的な分野です。 様々なバックグラウンドの人と関わりながら仕事をすることになります。 この本を読んで自分も分かりやすい説明ができるようになって、様々なバックグラウンドの人と楽しく雑談ができるようになった気分になりました。

データマイニングエンジニアに必要な知識は主に3つあると思います。統計学、プログラミングスキル、業務知識が必要です。

この本はこの3つ全てをバランスよく説明している点が素晴らしいと思いました。 これまでこういう本はありませんでした。

目次を見てみましょう。

目次
CHAPTER 01 データマイニングを始める前に
CHAPTER 02 統計学の基礎
CHAPTER 03 計算機上のデータ
CHAPTER 04 構造を持つデータ
CHAPTER 05 テーブル
CHAPTER 06 可視化
CHAPTER 07 パターンと距離
CHAPTER 08 多変量解析
CHAPTER 09 時系列解析
CHAPTER 10 計算量の見積もり
CHAPTER 11 エンジニア的財務会計
CHAPTER 12 指標を考える
CHAPTER 13 技術者倫理

ご覧の通り、統計学、コンピュータサイエンス、業務知識が全て取り上げられています。

研究とサービスのデータ分析の両方をしている、サイバーエージェントの秋葉原ラボの方々だからこそ書ける内容だと思いました。

データマイニングエンジニアになりたい人に最初の一冊としてぜひオススメしたい内容です。

この本に沿ってデータマイニングの勉強方法をご紹介します。

データマイニングとは何か

データマイニングという用語は曖昧で、時代や人によって定義は様々です。 概ね次のような意味です。データマイニングを定義しろと言われたら私は以下のように定義します。

「データマイニングとは仮説なしに集められたデータに統計学や機械学習の手法を適用し、有益な情報を抽出する行為である。」

この定義に従うならば、データマイニングをするにはまず、統計学や機械学習の手法を習得する必要があります。

有益な情報を抽出するために、現代ではコンピュータの使用が必須です。 近年、扱うデータの量は日に日に大きくなっており、並列計算をする機会が多くなっています。 有限の計算資源を使って、効率的な計算をするために、データマイニングをする人も最低限のプログラミングの知識を身につける必要があるでしょう。

データマイニングは有益な情報を抽出する行為であると書きましたが、有益な情報とはそもそも何でしょうか。 それは分野(ドメイン)によって異なります。 実務者としてデータマイニングをするには自分の働く業界の業務知識(ドメイン知識)を身につける必要があります。 どういった情報が所属する会社に有益であるかを理解していなければ、そもそもデータマイニングはしようがないのです。

従って、データマイニングをするには統計学、プログラミング、業務知識の3つを習得する必要があるのです。

統計学

データマイニング3本の柱の一本目、統計学から見てみましょう。 『データマイニングエンジニアの教科書』第2章、第6章、第7章、第8章、第9章がこのセクションに該当します。

データマイニングでは大量のデータから有益な情報を抽出し、記述するために統計学が用いられます。大学で統計学を学ばれた方も多いかと思いますが、私の意見では大学の教養科目で学ぶレベルと実務で必要なレベルのギャップは大きいです。

第一に、教養レベルの統計学では初歩的な統計学しか習いません。 私が大学で取った統計学の授業では、データのばらつきが等分散正規分布であることを仮定し、線形モデルを適用し、最尤法で直線を引いて、というレベルの知識さえあればとりあえず単位は取れました。

実際は、統計学の基礎を理解していなければ、結果を正しく解釈することができません。データマイニングをしても正しい解釈ができなければ「有益な情報」を抽出することはできないため、統計学に真面目に取り組む必要があります。

第二に、現代ではコンピュータを使って統計処理をすることが普通にも関わらず、どういうわけか授業でコンピュータを使った統計処理を学ばないことが多いです。統計処理はまず、データをプロットすることから始まります。 コンピュータを用いた統計処理ができなければ、データマイニングは始められないと言っても過言ではありません。

『データマイニングエンジニアの教科書』第2章 では統計学の基礎が取り上げられています。この章と同じレベルの統計学を説明した本として、以下の本がオススメです。

統計学入門 (基礎統計学?)

統計学入門 (基礎統計学?)

古い本ですが、大学の教養レベルの教科書でこの本を超える本は未だにないでしょう。

これ一冊を読めば、伝統的な統計学の基礎はマスターできます。 この本は残念ながらコンピュータを用いた統計処理については書かれていため、別に学習する必要があります。

以下の本は機械学習の本ですが、上巻は統計学の理論的な説明がよくまとまっており、中級者以上にもオススメです。 ベイズ統計学について分かりやすく説明されており、座右の書となるでしょう。

パターン認識と機械学習 上

パターン認識と機械学習 上

  • 作者: C.M.ビショップ,元田浩,栗田多喜夫,樋口知之,松本裕治,村田昇
  • 出版社/メーカー: 丸善出版
  • 発売日: 2012/04/05
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 33回
  • この商品を含むブログ (20件) を見る

この本もコンピュータを使って処理する方法が書かれていないのが残念な点です。

仮説検定だけに絞って説明した本としては以下の本がオススメです。

次の本がオススメです。

44の例題で学ぶ統計的検定と推定の解き方

44の例題で学ぶ統計的検定と推定の解き方

この本はよく使う検定を44個挙げた本で、非常に便利です。

可視化とコンピュータを使った統計処理について勉強したい人には次の本がオススメです。

みんなのR 第2版

みんなのR 第2版

  • 作者: Jared P. Lander,高柳慎一,津田真樹,牧山幸史,松村杏子,簑田高志
  • 出版社/メーカー: マイナビ出版
  • 発売日: 2018/12/28
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (1件) を見る

この本は R 言語という統計処理で標準的に用いられるソフトウェアの使い方を説明した本です。

『データマイニングエンジニアの教科書』第6章では可視化について取り上げられています。 前述の通り、データをプロットすることから統計処理は始まります。 まずはサクッとデータをプロットをしたいのに、ググるのに時間がかかって仕方がないという人には以下の本がおすすめです。

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

Rグラフィックスクックブック ―ggplot2によるグラフ作成のレシピ集

この本は R言語でグラフをプロットするためのレシピ集です。 私はR言語でプロットをしようと思ったらまずこの本を開きます。 この本からコードをコピペするだけで綺麗なグラフが一瞬で作れるのでオススメです。

単に可視化をするだけだったらPython より R の方がずっと簡単なのでオススメです。どうしても Python で可視化をしたい人はググりましょう (-.-;)。

『データマイニングエンジニアの教科書』第7章、第8章ではクラスタリングや多変量解析といった発展的な統計学が取り上げられています。 更に深く学びたい人には、次の本がオススメです。

はじめてのパターン認識

はじめてのパターン認識

この本は平易すぎず、数式が丁寧に書かれている本で大変オススメです。

『データマイニングエンジニアの教科書』第9章の時系列解析は、実務でデータ分析をしていて頻繁に出くわすのでぜひ押さえておきたいトピックです。 時系列解析の教科書としては、以下の本が最初の一冊としてよいです。

経済・ファイナンスデータの計量時系列分析 (統計ライブラリー)

経済・ファイナンスデータの計量時系列分析 (統計ライブラリー)

この本は経済学やファイナンスの知識が全くなくても読めますし、全ての分野の人にオススメできる一冊です。

プログラミング

次に、データマイニング3本の柱の二本目、プログラミングを見ていきましょう。『データマイニングエンジニアの教科書』第3章、第4章、第5章、第10章がこのテーマに該当します。

データマイニングをするために必要なプログラミングの知識は限定的です。

SQL とスクリプト言語について基礎的な知識をまず押さえましょう。

『データマイニングエンジニアの教科書』ではデータ構造やアルゴリズムについても取り上げらていますが、データマイニング初心者はまずは無視してもいいかと思います。 自分でアルゴリズムを実装するときは必須の知識になりますが、最初は無視してライブラリを使うだけで足ります。

初心者データマイニングエンジニアに必要なSQL はズバリSELECT 文だけです。以下の本はデータマイニングでよく使うSQL文のレシピで、非常に重宝します。

ビッグデータ分析・活用のためのSQLレシピ

ビッグデータ分析・活用のためのSQLレシピ

まずはこの本を読んで SELECT 文の書き方に習熟しましょう。

きちんとSQLを勉強したくなった方にはミックさんのSQLの本がオススメです。

SQL 第2版 ゼロからはじめるデータベース操作

SQL 第2版 ゼロからはじめるデータベース操作

達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ (CodeZine BOOKS)

達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ (CodeZine BOOKS)

データマイニングでよく使うスクリプト言語といえば、PythonとR言語です。 データマイニング向けのPythonの本は沢山あります。どれも似たり寄ったりだと思うので、適当に一冊選んで読みましょう。例えば以下の本がオススメです。

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

R言語は統計学、データマイニングに特化した言語です。 入門書としては、上でも取り上げたこの本がいいでしょう。

みんなのR 第2版

みんなのR 第2版

  • 作者: Jared P. Lander,高柳慎一,津田真樹,牧山幸史,松村杏子,簑田高志
  • 出版社/メーカー: マイナビ出版
  • 発売日: 2018/12/28
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (1件) を見る

データ分析に入る前に、データを綺麗にすることを前処理といいます。SQL/R/Python全ての言語で前処理にフォーカスして説明した本として、以下の本があります。 データマイニングの殆どの時間は前処理に費やすことになるので、持っていて損はないと思います。

前処理大全[データ分析のためのSQL/R/Python実践テクニック]

前処理大全[データ分析のためのSQL/R/Python実践テクニック]

データ構造やアルゴリズムについて勉強したくなった中級者の方には次の本がオススメです。

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造

プログラミングコンテスト攻略のためのアルゴリズムとデータ構造

この本はプログラミングコンテストに興味がない人にもオススメです。 アルゴリズムとデータ構造を理解する最短経路は実際に手を動かすことだと思います。 この本はAIZU ONLINE JUDGE というサイトを使って、コードが正しいかを動かして確かめながら勉強することができます。 この本の知識があれば、データマイニングのためのアルゴリズムとデータ構造の知識としては十分過ぎます。

業務知識

要するに、大人の事情です。 『データマイニングエンジニアの教科書』第11章、第12章がこのテーマに該当します。

実務者らしい内容が多いのもこの本の魅力です。例えば12章「指標を考える」はまさにそうです。 この章では環境分析から施策実施までの流れ、会計指標、指標のブレークダウンを説明しています。 企業では何らかのビジネス的な目標を達成するために、意思決定の材料を集める必要があり、そのためにデータ分析が行われることがあります。

実務でのデータマイニングはお金とは切っても切り離せません。 利益に結びつく有益な情報をデータマイニングで発見するには業務の知識が必要です。

業務知識はドメインによってよって異なります。また、独習するのも難しいです。 実際に働きながら身につけるよりほかはありません。

この本のメインの読者層と思われる Web 系の人向けの本を一冊挙げるなら、私は以下の本をオススメします。

ウェブ解析士認定試験公式テキスト2019(特典PDF付き)

ウェブ解析士認定試験公式テキスト2019(特典PDF付き)

  • 作者: 守口剛,一般社団法人ウェブ解析士協会
  • 出版社/メーカー: マイナビ出版
  • 発売日: 2018/12/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

まとめ

この記事ではサイバーエージェントの秋葉原ラボの方が執筆された『データマイニングエンジニアの教科書』を堀田(@YoshiHotta)がレビューし、データマイニングの学び方を解説しました。

まずは『データマイニングエンジニアの教科書』を読んでデータマイニングという広大な分野を俯瞰しましょう。次に、同書で引用している参考文献を読んだり、この記事で取り上げたオススメ書籍を読んで、データマイニングを覚えましょう!

データマイニングエンジニアの教科書

データマイニングエンジニアの教科書

  • 作者: 森下壮一郎,水上ひろき,高野雅典,數見拓朗,和田計也
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2019/06/27
  • メディア: Kindle版
  • この商品を含むブログを見る

この記事の著者をフォロー👇 記事をいいと思ったらリツイートしてね。

【永久保存版】Gitのあらゆるトラブルが解決する神ノウハウ集を翻訳した

git github

堀田(@YoshiHotta)です。この記事はGithubで3万スター⭐以上を集めた人気リポジトリ git-flight-rules の翻訳です。

Git はエンジニアが毎日何十回も使うコマンドであるにも関わらず、難しいツールです。 コマンドを間違えて、元に戻そうとあれこれ試しているうちにもっと悲惨な状況になり、復旧できなくなった経験が誰でも一度はあるのではないでしょうか。

Git でトラブルに見舞われてもこの Git フライトルールがあれば安心です! このガイドで対処策が必ず見つかります。

落ち着いてガイドの手順に従えばトラブルから脱出できます。

毎日Git を使う仕事を何年もしていますが、今でも困ることがよくあります。そういう時は git flight rules をすぐに参照していました。本家に日本語版がなかったので、仕方がなくずっと母語ではない英語のバージョンを読んでいました。塵も積もれば山となるで結構な時間をロスしていたと思います。読者の皆さんには Git で困って欲しくない、母語でこの素晴らしい Git フライトルールを読んで、git の操作ミスで苦労して書いたコードを吹き飛ばして欲しくないという思いから、この長い長いガイドを翻訳しました。

Git 初心者からベテランまでぜひ手元に置いておいて下さい。

🌍原著英語版

※ この記事は Attribution-ShareAlike 4.0 International ライセンス で配布します。

著者をフォロー👇


目次

「フライトルール」とは何ですか?

問題が発生した場合の対処方法についての、宇宙飛行士のためのガイドです。今の場合、Git を使用しているプログラマのためのガイドです。

フライトルールは苦労して手に入れた知識体系をリスト化してマニュアルにしたものです。「もし X が発生したら、なぜ、なにをやるか」の手順が書かれています。基本的に、これらは非常に詳細なシナリオ固有の標準的な操作手順です。 [...]

マーキュリー計画の地上チームが1960年代初頭に「学び」を最初に集め始めて以来、NASAはエンジンの故障から潰されたハッチまで、ミス、災害、そして解決策の概要をリスト化してきました。

— Chris Hadfield, An Astronaut's Guide to Life.

文書規約

この文書の表記規則を分かりやすくするために、この文書のすべての例で、現在のブランチとステージングした変更があるかを示すためにカスタマイズしたbashプロンプトを使用しています。ブランチは括弧で囲まれ、ブランチ名の横の*はステージングした変更を示します。すべてのコマンドは少なくともgitのバージョン2.13.0から動くはずです。ローカルのgitのバージョンを更新するにはgitのウェブサイト を見てください。

リポジトリ

ローカルのリポジトリを始めたい

既存のディレクトリをGitリポジトリとして初期化するには:

(my-folder) $ git init

リモートのリポジトリをクローンしたい

リモートリポジトリをクローン(コピー)するには、リポジトリのURLをコピーして、次のコマンドを実行します。

$ git clone [url]

これにより、リモートリポジトリと同じ名前のフォルダに保存されます。 複製元のリモートサーバーに接続していることを確認して下さい(ほとんどの場合、インターネットに接続していることを確認すれば大丈夫です)。

デフォルトのリポジトリ名と異なる名前のフォルダに複製するには、次の手順を実行します。

$ git clone [url] name-of-new-folder

誤ったリモートのリポジトリを設定してしまった

いくつかの問題が考えられます。

間違ったリポジトリを複製した場合は、 git cloneを実行した後に作成されたディレクトリを削除し、正しいリポジトリをクローンしてください。

既存のローカルリポジトリの origin として間違ったリポジトリを設定した場合は、次のコマンドを実行して origin の url を変更します。

$ git remote set-url origin [url of the actual repo]

更に詳しく知りたい場合は この StackOverflow のトピック を参照して下さい。

他人のリポジトリのコードを追加したい

Git は他の誰かのリポジトリにアクセス権なしでコードを追加することを許可していません。Github でも同様です。 Github は Git と全く同じではありませんが、 Git のホストサービスのようなものです。 ただし、パッチを使用してコードを提案することができ、GitHubではフォークしたりプルリクエストを出すことができます。

まず、フォークについて少々説明をしましょう。 フォークはリポジトリのコピーです。 これは git の操作ではありませんが、GitHub、Bitbucket、GitLab を含む Git リポジトリをホストする場所では一般的なアクションです。 ホストされたUIを通してリポジトリをフォークすることができます。

プルリクエストを出してコードを提案したい

リポジトリをフォークした後は、通常リポジトリを自分のマシンにクローンする必要があります。例えばクローンを作成せずにGitHubを少し編集することはできますが、この文書は Github フライトルールではないので、ローカルでこれを行う方法を試してみましょう。

# ssh を使っている場合
$ git clone git@github.com:k88hudson/git-flight-rules.git

# https を使っている場合
$ git clone https://github.com/k88hudson/git-flight-rules.git

作成されるディレクトリに cd して、git remote と入力すると、リモートの一覧が表示されます。 通常は1つのリモート、origin があり、それはk88hudson / git-flight-rulesを指します。 この場合、自分のフォークを指すリモートも欲しいと思います。

まず、Gitの慣例に従い、自分のリポジトリに originというリモート名を使い、フォークしたものにupstreamを使います。 originの名前をupstreamに変更しましょう。

$ git remote rename origin upstream

git remote set-urlを使ってこれを行うこともできますが、時間がかかり、手順が多いです。

次に、自分のプロジェクトを指す新しいリモートを設定します。

$ git remote add origin git@github.com:YourName/git-flight-rules.git

これで2つのリモートができました。

  • originは自分のリポジトリを参照します。
  • upstreamは元のリポジトリを参照します。

origin に対しては read / write することができます。 upstream に対しては read だけができます。

好きなように変更を加え終わったら、変更を(通常はブランチ内から) originという名前のリモートにプッシュします。ブランチにいるのなら、このブランチを使う将来のプッシュでリモートトラッキングブランチを指定するのを避けるために --set-upstreamを使うことができます。 例えば

$ (feature/my-feature) git push --set-upstream origin feature/my-feature

Gitを使用してCLIでプルリクエストを出す方法はありません(ただし、hubのようなツールがあります)。 そのため、プルリクエストを作成する準備が整ったら、GitHub(または他のGitホスト)に移動して新しいプルリクエストを作成します。 Gitホストが自動的に元のリポジトリとフォークしたリポジトリをリンクすることに注意してください。

コードレビューのフィードバックには必ず返信するようにしてください。

元のリポジトリの更新をフォークしたリポジトリに反映したい

しばらくして、 upstreamリポジトリが更新されたかもしれません。これらの更新を自分の origin リポジトリに取り込む必要があります。あなたと同様に他の人々もプロジェクトに貢献していることを忘れないでください。 自分のフィーチャーブランチにいて、元のリポジトリの更新を取り込む必要があるとしましょう。

恐らく元のプロジェクトを指すリモートを設定したでしょう。もし そうでなければ、今して下さい。 一般的にリモートの名前として upstreamを使います。

$ (master) git remote add upstream <link-to-original-repository>
# $ (master) git remote add upstream git@github.com:k88hudson/git-flight-rules.git

これで上流 (upstream) から fetch して最新のアップデートを入手できます。

$ (master) git fetch upstream
$ (master) git merge upstream/master

# 又は、一つのコマンドで行うなら
$ (master) git pull upstream master

コミットの編集

自分が何を今コミットしたか?

何も考えずに git commit -aを使って変更をコミットしただけで、今行ったコミットの実際の内容が何であるかはよく分からないとしましょう。 現在のHEADの指す最新のコミットを表示するには

(master)$ git show

又は

$ git log -n1 -p

とします。

特定のコミットでのファイルを見ることもできます( <commitid>は興味のあるコミットです)。

$ git show <commitid>:filename

コミットメッセージに間違ったことを書いた

間違ったコミットメッセージを書いたがコミットをまだプッシュしていない場合は、コミットの修正を変更せずにコミットメッセージだけを変更することができます。

$ git commit --amend --only

これによりデフォルトのテキストエディタが開き、そこでメッセージを編集できます。 一方、これをすべて1つのコマンドで行うこともできます。

$ git commit --amend --only -m 'xxxxxxx'

既にメッセージをプッシュしている場合は、コミットを修正して force push することができますがお勧めしません。

間違った設定の名前とメールアドレスでコミットしてしまった

それが単一のコミットであれば、次のようにして修正できます。

$ git commit --amend --no-edit --author "New Authorname <authoremail@mydomain.com>"

別の方法としては、 git config --global author。(name | email)で作者設定を正しく設定してから次のコマンドを実行します。

$ git commit --amend --reset-author --no-edit

履歴をすべて変更する必要がある場合は、 git filter-branchのmanページを参照してください。

前のコミットからファイルを削除したい

前のコミットからファイルの変更を削除するには、次の手順に従います。

$ git checkout HEAD^ myfile
$ git add myfile
$ git commit --amend --no-edit

ファイルが新しくコミットに追加され、それを(Gitだけから)削除したい場合は、次のようにします。

$ git rm --cached myfile
$ git commit --amend --no-edit

まだマージされていないパッチがあり、不要なファイルをコミットしていて、リモートでパッチを更新するために force push する必要がある場合、これは特に便利です。 --no-editオプションは既存のコミットメッセージを保持するために使用されます。

直前のコミットを削除したい

プッシュされたコミットを削除する必要があるなら以下を使うことができます。 しかし、履歴を不可逆的に変えてしまい、既にリポジトリから pull した他の人の履歴はめちゃくちゃになります。 つまり、よく分からない場合は絶対にしないでください。

$ git reset HEAD^ --hard
$ git push --force-with-lease [remote] [branch]

まだプッシュしていない場合は、Gitを最後のコミットを行う前の状態にリセットします(ステージングされた変更を維持したまま)。

(my-branch*)$ git reset --soft HEAD@{1}

これはプッシュしていない場合にのみうまくいきます。 プッシュしていた場合、本当に安全なやり方は git revert SHAofBadCommitだけです。 これにより、以前のコミットの変更をすべて元に戻す新しいコミットが作成されます。 あるいは、プッシュしたブランチがリベースに対して安全な場合(つまり、他の開発者がそこから pull することは想定されていない場合)は、単に git push --force-with-leaseを使用できます。 詳しくは上記のセクションを見てください。

任意のコミットを削除する

上記と同じ警告が適用されます。 可能であれば絶対に行わないでください。

$ git rebase --onto SHA1_OF_BAD_COMMIT^ SHA1_OF_BAD_COMMIT
$ git push --force-with-lease [remote] [branch]

またはインタラクティブな rebase を実行して、削除したいコミットに対応する行を削除します。

修正したコミットをリモートにpushしようとしたが、エラーメッセージが出た

To https://github.com/yourusername/repo.git
! [rejected]        mybranch -> mybranch (non-fast-forward)
error: failed to push some refs to 'https://github.com/tanay1337/webmaker.org.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

なお rebase(下記参照)と同様に、amend は古いコミットを新しいものに置き換えるので、amend する前のコミットをリモートに既に push しているときは force push( --force-with-lease)する必要があります。 これを行うときは必ずブランチを必ず指定してください。

(my-branch)$ git push origin mybranch --force-with-lease

一般的に、force pushは避けてください。 修正したコミットを force push するのではなく、新しいコミットを作成してプッシュすることをお勧めします。問題のブランチまたは任意の子ブランチに取り組んだ他の開発者のソース履歴に矛盾が生じるためです。 他の誰か同じブランチに取り組んでいる場合、 --force-with-leaseは失敗します。

誰も同じブランチで作業していないこと、またはブランチの先端を無条件でアップデートしたいことを絶対に確信している場合は、 --force-f)を使用できます。 しかし一般的には避けるべきです。

誤って hard reset をしたので元に戻したい

誤って git reset --hardを実行しても、gitは数日間すべてのログを保存しているので、通常はまだコミットを取り戻すことができます。

注意:これは作業がバックアップされている、すなわちコミットされているか stash されている場合にのみ有効です。 git reset --hardはコミットされていない変更を削除しますので、注意して使ってください。 (より安全なオプションは git reset --keepです。)

(master)$ git reflog

過去のコミットのリストとリセットのためのコミットが表示されます。 戻りたいコミットのSHAを選択して、リセットします。

(master)$ git reset --hard SHA1234

これで大丈夫なはずです。

誤ってマージをコミットして push してしまった

マージする準備が整う前に誤ってフィーチャーブランチをメインの開発ブランチにマージした場合でも、マージを元に戻すことができます。 しかし落とし穴があります。マージコミットには複数の親(通常は2つ)があります。

使うコマンドは以下です。

(feature-branch)$ git revert -m 1 <commit>

ここで -m 1 オプションは、元に戻す親として親の番号1(マージが行われたブランチ)を選択するように指示します。

注:親番号はコミットIDではありません。 むしろ、マージコミットには Merge:8e2ce2d 86ac2e7と書かれます。 親番号はこの行の目的の親の1から始まるインデックス、最初の識別子は番号1、2番目の識別子は番号2というように続きます。

秘密情報を含むファイルをコミットして push してしまった

誤って秘密情報や個人データ(パスワード、キーなど)を含むファイルをプッシュした場合は、以前のコミットを修正できます。 一度コミットをプッシュしたら、それに含まれる全てのデータが危険に晒されていると考えるべきであることを覚えておいてください。 これらのステップは公開リポジトリまたは自分のローカルコピーから秘密情報を削除することができますが、他人の pull したコピーから秘密情報を削除することはできません。 パスワードをコミットした場合は、すぐに変更してください。 キーをコミットした場合は、すぐに再生成してください。 その間に誰かが秘密情報を含む元のコミットを pull した可能性があるので、プッシュされたコミットを修正するだけでは不十分です。

ファイルを編集して機密データを削除した場合は、次のコマンドを実行してください。

(feature-branch)$ git add edited_file
(feature-branch)$ git commit --amend --no-edit
(feature-branch)$ git push --force-with-lease origin [branch]

ファイル全体を削除したい(ローカルには保持する)場合は、次のコマンドを実行します。

(feature-branch)$ git rm --cached sensitive_file
echo sensitive_file >> .gitignore
(feature-branch)$ git add .gitignore
(feature-branch)$ git commit --amend --no-edit
(feature-branch)$ git push --force-with-lease origin [branch]

あるいは、秘密情報をローカル環境変数に格納してください。

ファイル全体を完全に削除したい(ローカルにも保存したくない)場合は、次のコマンドを実行してください。

(feature-branch)$ git rm sensitive_file
(feature-branch)$ git commit --amend --no-edit
(feature-branch)$ git push --force-with-lease origin [branch]

その間に他のコミットを行った場合(つまり、秘密情報が前回のコミットより前にコミットされている場合)、rebase する必要があります。

大きいファイルをリポジトリの全ての履歴から消したい

削除したいファイルが秘密または機密である場合は、代わりに秘密情報を含むファイルをコミットして push してしまったを参照してください。

最近のコミットで大きなファイルや不要なファイルを削除しても git の履歴に残っており、リポジトリの .gitフォルダに残っているので、git cloneは不要なファイルをダウンロードします。

この部分のフライトルールは force push を必要とし、リポジトリの履歴の大部分を書き換えるので、遠隔地にいるコラボレーターと仕事をしているのであれば、彼らがローカルの作業をプッシュしたことを確認して下さい。

履歴の書き換えには二つの選択肢があります。組み込みの git-filter-branchまたはbfg-repo-cleanerです。 bfgはとても綺麗で性能が優れていますが、サードパーティ製品をダウンロードしなければならず、javaを必要とします。両方の方法について説明します。最後のステップは、変更を force push することです。これには、大量のリポジトリの履歴が恒久的に変更されるため、通常の force push 以上に特に気をつける必要があります。

オススメのテク: サードーパーティーの bfg を使う

bfg-repo-cleanerを使用するにはjavaが必要です。 ここ からbfg jarをダウンロードしてください。 私たちの例では bfg.jarを使いますが、ダウンロードしたバイナリにはバージョン番号が付いているかもしれません。例えば bfg-1.13.0.jar のように。

特定のファイルを削除します。

(master)$ git rm path/to/filetoremove
(master)$ git commit -m "Commit removing filetoremove"
(master)$ java -jar ~/Downloads/bfg.jar --delete-files filetoremove

bfgでは、サブディレクトリにある場合でも、そのままのファイル名を使用する必要があります。

ファイルをパターンで削除することもできます。

(master)$ git rm *.jpg
(master)$ git commit -m "Commit removing *.jpg"
(master)$ java -jar ~/Downloads/bfg.jar --delete-files *.jpg

bfgを使用すると、最新のコミットに存在するファイルは影響を受けません。 たとえば、リポジトリに大きな.tgaファイルがいくつかあり、それらの一部を削除した場合、この呼び出しは最新のコミットにあるファイルには影響しません。

コミットでファイル名を変更した場合は注意してください。元々 LargeFileFirstName.mp4で、コミットがそれをLargeFileSecondName.mp4に変更した場合、 java -jar ~/Downloads/bfg.jar --delete-files LargeFileSecondName.mp4を実行しても git の履歴から削除されません。 両方のファイル名で、あるいはパターンマッチで --delete-filesコマンドを実行してください。

組み込みのテク: git-filter-branch を使う

git-filter-branchはもっと面倒で機能も少ないですが、bfgをインストールしたり実行することができない場合は使えます。

以下では、 filepatternの置き換えは特定の名前やパターンになります。 例えば \*.jpg のように。 これは全ての履歴とブランチからパターンにマッチするファイルを削除します。

(master)$ git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch filepattern' --prune-empty --tag-name-filter cat -- --all

何が起こっているかの説明:

--tag-name-filter cat は面倒ですが、cat コマンドを使って元のタグを新しいコミットに付けるシンプルな方法です。

--prune-empty は全ての空のコミットを取り除きます。

最後のステップ: 変更したリポジトリの履歴を push する

目的のファイルを削除したら、リポジトリのものが何も壊れていないかを慎重にテストします。壊れている場合は、リポジトリを複製して最初からやり直すのが最も簡単です。 終了するには、必要に応じてgit ガベージコレクションを使用してローカルの.gitフォルダサイズを最小化してから、force push します。

(master)$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
(master)$ git push origin --force --tags

gitリポジトリの履歴全体を書き換えたので、 git pushの操作は大きすぎるかもしれず、「リモートエンドが突然ハングアップしました」というエラーを返すかもしれません。 このような場合は、git post bufferを増やしてみてください。

(master)$ git config http.postBuffer 524288000
(master)$ git push --force

これでうまくいかない場合は、リポジトリの履歴を手動で分割してコミットする必要があります。 以下のコマンドで、push操作が成功するまで <number>を増やしてみてください。

(master)$ git push -u origin HEAD~<number>:refs/head/master --force

プッシュが最初に成功したら、通常の git pushが成功するまで<number>を徐々に減らします。

最後以外のコミットの内容を変更したい

いくつか(例えば3つ)のコミットを作成した後で、最初のコミットに文脈上属していることをし忘れたことに気づいたと思います。 これらの変更を含む新しいコミットを作成するのであれば、クリーンなコードベースになるはずですが、コミットはアトミックではありませんでした(つまり、お互いに属する変更が同じコミットに含まれていなかったということです)。 そのような状況では、これらの変更が属するコミットを変更し、それらを含め、以降のコミットはそのままにします。 そのような場合、 git rebaseは役に立つかもしれません。

最後に行った3番目のコミットを変更したい状況を考えてください。

(your-branch)$ git rebase -i HEAD~4

対話的なリベースモードに入ります。このモードでは、最後の3つのコミットを編集できます。 テキストエディタがポップアップして、次のようなことを表示します。

pick 9e1d264 The third last commit
pick 4b6e19a The second to last commit
pick f4037ec The last commit

これを以下のように変えます。

edit 9e1d264 The third last commit
pick 4b6e19a The second to last commit
pick f4037ec The last commit

これは、"The third last commit"を編集し、他の2つは変更しないでおくことをrebaseに伝えます。 その後、エディタを保存して閉じます。 Gitはその後リベースを開始します。 変更したいコミットで停止し、そのコミットを編集することができます。 これで、最初にコミットをコミットしたときに適用しなかった変更を適用できます。まず編集し、ステージングします。 その後以下を実行します。

(your-branch)$ git commit --amend

これはGitにコミットを再度作成するように指示しますが、コミットメッセージは未編集のままにします。 これで難しい部分は解決されました。

(your-branch)$ git rebase --continue

これで残りの部分が実行されます。

ステージング

最後のコミットにステージングした変更を加えたい

(my-branch*)$ git commit --amend

コミットメッセージを変更したくないということが分かっている場合は、gitにコミットメッセージを再利用するように指示できます。

(my-branch*)$ git commit --amend -C HEAD

新しいファイルの一部をステージングしたい

通常、ファイルの一部をステージングしたい場合は、次のように実行します。

$ git add --patch filename.x

-pは短く動作します。 これにより対話モードが開きます。 コミットを分割するために sオプションを使うことができるでしょう。しかし、ファイルが新規のものなら、このオプションはありません。 新しいファイルを追加するには、以下を行います。

$ git add -N filename.x

それから、追加する行を手動で選択するために eオプションを使う必要があります。 git diff --cachedを実行するか git diff --stagedはどの行がステージされたかを、ローカルに保存したものと比較して表示します。

一つのファイルの変更2つのコミットに分けたい

git addはファイル全体をコミットに追加します。 git add -pで追加したい変更を対話的に選択できます。

大量の修正をステージングして、複数のコミットに分けたい

git reset -pはパッチモードのリセットダイアログを開きます。 これは git add -pに似ていますが、" yes "を選択すると変更のステージングが解除され、それが今後のコミットから削除される点が異なります。

ステージングしていない修正をステージングして、ステージングした修正をアンステージ (unstage) する

これはトリッキーです。 私が知っている最も良い方法は、ステージングされていない編集をstash することです。 その後、 reset してください。 その後、 stash した編集をポップして追加します。

$ git stash -k
$ git reset --hard
$ git stash pop
$ git add -A

ステージングしていない修正

ステージングしていない修正を新しいブランチに移したい

$ git checkout -b my-branch

ステージングしていない修正を別の既にあるブランチに移したい

$ git stash
$ git checkout my-branch
$ git stash pop

ローカルのコミットしていない変更を破棄したい (ステージングしていても、していなくてもよい

ーカルのステージングされた変更とステージングされていない変更を全て破棄したい場合は、次のようにします。

(my-branch)$ git reset --hard
# 又は
(master)$ git checkout -f

これは git addでステージングした全てのファイルをアンステージします。

$ git reset

これにより、コミットされていないローカルの変更が全て元に戻されます(リポジトリのルートで実行する必要があります)。

$ git checkout .

コミットしていない変更を特定のファイルまたはディレクトリに戻すこともできます。

$ git checkout [some_dir|file.txt]

コミットされていないすべての変更を元に戻すもう1つの方法は(入力は長くなりますが、任意のサブディレクトリから動きます)、

$ git reset --hard HEAD

これは全てのローカルの追跡されていないファイルを削除するので、Gitによって追跡されているファイルだけが残ります。

$ git clean -fd

-x も全ての ignore されたファイルを消去します。

ステージングしていない特定の変更を破棄したい

作業コピーの変更の一部を削除したいが、全ては削除したくない場合です。

望ましくない変更をチェックアウトし、良い変更はそのままにします。

$ git checkout -p
# 削除したいコード全てに y と入力して下さい

他の戦略は stashを使うことです。 すべての良い変更を stash し、作業コピーをリセットし、良い変更を再度適用します。

$ git stash -p
# 保存したいコード全てを選択して下さい
$ git reset --hard
$ git stash pop

あるいは、不要な変更を stash してから、 stash を drop します。

$ git stash -p
# 保存したくないコード全てを選択して下さい
$ git stash drop

ステージングしていない特定のファイルを破棄したい

作業コピーから特定のファイルを削除したい場合。

$ git checkout myFile

あるいは、作業コピー内の複数のファイルを破棄するには、それら全てをリストします。

$ git checkout myFirstFile mySecondFile

ステージングしていないローカルの変更だけ破棄したい

ステージングされていないローカルのコミットされていない変更をすべて取り除きたい場合

$ git checkout .

追跡していない全てのファイルを破棄したい

未追跡のファイルをすべて削除したい場合

$ git clean -f

特定のステージングしたファイルをアンステージ (unstage) したい

誤ってステージングされてしまった1つ以上のファイルがあり、それらのファイルが以前にコミットされていないことがあります。 それらをステージングを解除するには

$ git reset -- <filename>

これにより、ファイルのステージングが解除され、ファイルが追跡されなくなります。

ブランチ

全てのブランチをリストする

ローカルのブランチの一覧を見るには、

$ git branch

リモートのブランチの一覧を見るには、

$ git branch -r

全てのブランチ(ローカルとリモート両方)の一覧を見るには、

$ git branch -a

コミットからブランチを作る

$ git checkout -b <branch> <SHA1_OF_COMMIT>

誤ったブランチから pull してしまった

git reflogを使って HEAD が誤った pull の前にどこを指していたかを見ればよいです。

(master)$ git reflog
ab7555f HEAD@{0}: pull origin wrong-branch: Fast-forward
c5bc55a HEAD@{1}: checkout: checkout message goes here

ブランチを望ましいコミットにリセットします。

$ git reset --hard c5bc55a

これで完了です。

ローカルのコミットを捨てて、ローカルのブランチがリモートと同じになるようにしたい

変更をサーバーにプッシュしていないことを確認してください。

git statusは何個のコミットが origin より進んでいるかを表示します。

(my-branch)$ git status
# On branch my-branch
# Your branch is ahead of 'origin/my-branch' by 2 commits.
#   (use "git push" to publish your local commits)
#

(リモートのものと同じにするために)origin と一致するようにリセットする方法の一つは以下を行うことです。

(master)$ git reset --hard origin/my-branch

新しいブランチではなく master にコミットしてしまった

master にいる間に新しいブランチを作成します。

(master)$ git branch my-branch

master を以前のコミットにリセットします。

(master)$ git reset --hard HEAD^

HEAD^HEAD^1の略です。 これは HEADの最初の親を表し、同様にHEAD^2はコミットの2番目の親を表します(マージは2つの親を持つことができます)。

HEAD^2HEAD~2同じではないことに注意してください(詳細はこのリンクを参照して下さい)。

あるいは、HEAD^を使いたくない場合は、masterブランチに設定したいコミットハッシュを調べてください(git logで分かります)。 それからそのハッシュにリセットします。 git pushでこの変更がリモートに反映します。

例えば、master ブランチがいるべきコミットのハッシュが a13b85eであれば

(master)$ git reset --hard a13b85e
HEAD is now at a13b85e

新しいブランチをチェックアウトして作業を続けます。

(master)$ git checkout my-branch

別のブランチからファイルを取ってきたい

何百もの変更を加えた、ワーキングスパイクがあり(注を参照)、 全てがうまくいっているとします。 さて、その作業を保存するために別のブランチにコミットします。

(solution)$ git add -A && git commit -m "Adding all changes from this spike into one big commit."

それをブランチ(おそらくフィーチャーブランチか develop)に入れたいときは、ファイル全体を保存しておきたいはずです。 大きなコミットを小さなコミットに分割したいのです。

たとえば、

  • スパイクの解決策を含むブランチ solutionsolutiondevelopよりも一つだけ進んでいます。
  • 変更を加えたいブランチdevelop

コンテンツをブランチに持っていくことで解決できます。

(develop)$ git checkout solution -- file1.txt

これでsolutionブランチのファイルの内容をdevelopmentブランチに移せます。

# On branch develop
# Your branch is up-to-date with 'origin/develop'.
# Changes to be committed:
#  (use "git reset HEAD <file>..." to unstage)
#
#        modified:   file1.txt

その後、いつものようにコミットします。

注:スパイクソリューションは、問題を分析または解決するために作ります。 ソリューションは推定に使用され、誰もが問題をはっきりと見えた時点で破棄されます。Wikipedia

別々のブランチするべき複数のコミットを一つのブランチにしてしまった

master ブランチにいるとしましょう。 git logを実行すると、2回コミットしたことがわかります。

(master)$ git log

commit e3851e817c451cc36f2e6f3049db528415e3c114
Author: Alex Lee <alexlee@example.com>
Date:   Tue Jul 22 15:39:27 2014 -0400

    Bug #21 - Added CSRF protection

commit 5ea51731d150f7ddc4a365437931cd8be3bf3131
Author: Alex Lee <alexlee@example.com>
Date:   Tue Jul 22 15:39:12 2014 -0400

    Bug #14 - Fixed spacing on title

commit a13b85e984171c6e2a1729bb061994525f626d14
Author: Aki Rose <akirose@example.com>
Date:   Tue Jul 21 01:12:48 2014 -0400

    First commit

各バグのコミットハッシュに注目しましょう(#21の場合は e3851e8、#14の場合は5ea5173)。

まず、master ブランチを正しいコミット( a13b85e)にリセットしましょう:

(master)$ git reset --hard a13b85e
HEAD is now at a13b85e

これで、バグ#21のために新しいブランチを作ることができます。

(master)$ git checkout -b 21
(21)$

それでは、ブランチ上にあるバグ#21のコミットをcherry-pickしましょう。 つまり、そのコミットだけを適用し、そのコミットをHEAD の上に置きます。

(21)$ git cherry-pick e3851e8

この時点で、競合がある可能性があります。 競合を解決する方法については、複数のコミットを一つにまとめたいの章のコンフリクトがある のセクションを見て下さい。

それでは master からバグ#14のための新しいブランチを作成しましょう。

(21)$ git checkout master
(master)$ git checkout -b 14
(14)$

そして最後に、バグ#14のコミットを cherry-pick しましょう。

(14)$ git cherry-pick 5ea5173

削除した上流 (upstream) のブランチのローカルブランチを削除したい

GitHubでプルリクエストをマージすると、マージされたブランチをフォークから削除することができます。 そのブランチで作業を続ける予定がない場合は、ブランチのローカルコピーを削除して、古くなったブランチで散らからないようにしましょう。

$ git fetch -p upstream

ここで upstream は fetch したいリモートです。

誤ってブランチを消してしまった

定期的にリモートにプッシュしているなら、ほとんどの場合なんとかなるでしょう。 しかしそれでも branch を削除してしまうこともあるでしょう。 ブランチを作成して新しいファイルを作成するとしましょう。

(master)$ git checkout -b my-branch
(my-branch)$ git branch
(my-branch)$ touch foo.txt
(my-branch)$ ls
README.md foo.txt

add してコミットします。

(my-branch)$ git add .
(my-branch)$ git commit -m 'foo.txt added'
(my-branch)$ foo.txt added
 1 files changed, 1 insertions(+)
 create mode 100644 foo.txt
(my-branch)$ git log

commit 4e3cd85a670ced7cc17a2b5d8d3d809ac88d5012
Author: siemiatj <siemiatj@example.com>
Date:   Wed Jul 30 00:34:10 2014 +0200

    foo.txt added

commit 69204cdf0acbab201619d95ad8295928e7f411d5
Author: Kate Hudson <katehudson@example.com>
Date:   Tue Jul 29 13:14:46 2014 -0400

    Fixes #6: Force pushing after amending commits

さて、master に戻って、「誤って」ブランチを削除してしまったとします。

(my-branch)$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
(master)$ git branch -D my-branch
Deleted branch my-branch (was 4e3cd85).
(master)$ echo oh noes, deleted my branch!
oh noes, deleted my branch!

改良されたロガーである 'reflog'に精通して下さい。 リポジトリ内のすべてのアクションの履歴を保存します。

(master)$ git reflog
69204cd HEAD@{0}: checkout: moving from my-branch to master
4e3cd85 HEAD@{1}: commit: foo.txt added
69204cd HEAD@{2}: checkout: moving from master to my-branch

ご覧のとおり、削除されたブランチからのコミットハッシュがあります。 削除したブランチを元に戻すことができるかどうか見てみましょう。

(master)$ git checkout -b my-branch-help
Switched to a new branch 'my-branch-help'
(my-branch-help)$ git reset --hard 4e3cd85
HEAD is now at 4e3cd85 foo.txt added
(my-branch-help)$ ls
README.md foo.txt

ほら! 削除したファイルを元に戻せました。 git reflogはリベースがひどく間違っているときにも役に立ちます。

ブランチを消したい

リモートブランチを削除するには

(master)$ git push origin --delete my-branch

別のやり方としては

(master)$ git push origin :my-branch

ローカルブランチを削除するには

(master)$ git branch -d my-branch

現在のブランチや上流 (upstream) にマージされてないローカルのブランチを削除するには

(master)$ git branch -D my-branch

複数のブランチを消したい

例えば、fix/ で始まるブランチを全て消すには

(master)$ git branch | grep 'fix/' | xargs git branch -d

ブランチ名を変えたい

現在の(ローカルの)ブランチの名前を変えるには

(master)$ git branch -m new-name

別の(ローカルの)ブランチの名前を変えるには

(master)$ git branch -m old-name new-name

他の人が作業中のリモートブランチをチェックアウトしたい

まずリモートから全てのブランチを fetch します。

(master)$ git fetch --all

例えば、リモートにあるブランチdavesをチェックアウトしたいとします。

(master)$ git checkout --track origin/daves
Branch daves set up to track remote branch daves from origin.
Switched to a new branch 'daves'

(--track is shorthand for git checkout -b [branch] [remotename]/[branch])

これでローカルにブランチdavesをコピーします。リモートにプッシュされた更新も表示されます。

現在のローカルブランチから新しいリモートブランチを作りたい

$ git push <remote> HEAD

もしリモートのブランチを現在のブランチの上流 (upstream) として設定もしたいなら、代わりに以下のコマンドを使います。

$ git push -u <remote> HEAD

push.defaultupstreamモードとsimpleモード (Git 2.0 のデフォルトの設定です)になっていると、-uで登録されていたリモートブランチに現在のブランチをプッシュします。

$ git push

git pushの他のモードの振る舞いは push.defaultのドキュメント に記載されています。

リモートブランチをローカルブランチの上流 (upstream) として設定したい

次のようにして、リモートブランチを現在のローカルブランチの上流 (upstream) として設定できます。

$ git branch --set-upstream-to [remotename]/[branch]
# 又は以下の略記も使えます
$ git branch -u [remotename]/[branch]

上流のリモートブランチを別のローカルブランチに設定するには

$ git branch -u [remotename]/[branch] [local-branch]

HEADがデフォルトのリモートブランチを追跡するようにしたい

リモートブランチをチェックすることによって、HEAD がどのリモートブランチを追跡しているかを見ることができます。 場合によっては、望ましいブランチではありません。

$ git branch -r
  origin/HEAD -> origin/gh-pages
  origin/master

origin/HEADorigin/masterを追跡するように変更するには、次のコマンドを実行します。

$ git remote set-head origin --auto
origin/HEAD set to master

誤ったブランチに変更を加えてしまった

変更をしたがまだコミットしていない段階で、間違ったブランチにいることに気づきました。 変更を stash して必要なブランチに適用します。

(wrong_branch)$ git stash
(wrong_branch)$ git checkout <correct_branch>
(correct_branch)$ git stash apply

rebase とマージ

rebase/マージを取り消す

現在のブランチに間違ったブランチをマージしたりリベースしたのに、何が起きたか理解できなかったりリベースやマージを完了することができないことがあります。 Gitは危険な操作をする前に元のHEADポインタをORIG_HEADと呼ばれる変数に保存するので、リベース/マージの前の状態にブランチを復元するのは簡単です。

(my-branch)$ git reset --hard ORIG_HEAD

rebase したが force push はしたくない

残念ながら、それらの変更をリモートブランチに反映させたい場合は、 force push する必要があります。 履歴を変えたからです。 force push しない限り、リモートブランチは変更を受付けません。 これが、多くの人がリベースワークフローではなくマージワークフローを使用する主な理由の1つです。大規模なチームが開発者の force push で問題に陥る可能性があります。 これするときは慎重になってください。 リベースを使用するより安全な方法は、リモートブランチに変更をまったく反映させず、代わりに以下をすることです:

(master)$ git checkout my-branch
(my-branch)$ git rebase -i master
(my-branch)$ git checkout master
(master)$ git merge --ff-only my-branch

この StackOverflow のスレッド を参照して下さい。

複数のコミットを一つにまとめたい

masterに対するプルリクエストとなるブランチに取り組んでいるとしましょう。 最も簡単な場合は、全てのコミットを1つにまとめてコミットタイムスタンプを気にする必要がない場合で、リセットして再度コミットできます。 master ブランチが最新のものであり、すべての変更がコミットされていることを確認してください。

(my-branch)$ git reset --soft master
(my-branch)$ git commit -am "New awesome feature"

もっと細かく制御したい、そしてタイムスタンプを保持したいのなら、インタラクティブリベースをする必要があります。

(my-branch)$ git rebase -i master

もし他のブランチに対して作業をしていないのであれば、HEADを基準にして rebase する必要があります。 たとえば、最後の2つのコミットを squash したい場合は、 HEAD~2に対してリベースする必要があります。 最後の3つをするなら、HEAD~3となります。

(master)$ git rebase -i HEAD~2

インタラクティブな rebase コマンドを実行すると、テキストエディタに次のようなものが表示されます。

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
pick b729ad5 fixup
pick e3851e8 another fix

# Rebase 8074d12..b729ad5 onto 8074d12
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

で始まる行はすべてコメントで、リベースには影響しません。

pickコマンドを上の一覧のどれかに置き換えたり、対応する行が削除してコミットを削除することができます。

たとえば、最も古い(最初の)コミットをそのままにして、その後に続くすべてのコミットを2番目に古いコミットと結合する場合は、最初と2番目以外の各コミットの横の文字を編集して fとします。 :

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
f b729ad5 fixup
f e3851e8 another fix

これらのコミットを組み合わせてコミットの名前を変更したい場合は、2番目のコミットの隣にさらに rを追加するか、単にfの代わりに sを使用してください。

pick a9c8a1d Some refactoring
pick 01b2fd8 New awesome feature
s b729ad5 fixup
s e3851e8 another fix

ポップアップされる次のテキストプロンプトでコミットの名前を変えられます。

Newer, awesomer features

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 8074d12
# You are currently editing a commit while rebasing branch 'master' on '8074d12'.
#
# Changes to be committed:
#   modified:   README.md
#

すべてが成功すれば、次のようになるはずです。

(master)$ Successfully rebased and updated refs/heads/master.

安全なマージの仕方

--no-commitはマージを実行しますが、失敗したマージのように見せかけて自動コミットしないので、コミットする前にマージ結果を調べてさらに微調整することができます。 no-ffはフィーチャーブランチがかつて存在していたという証拠を維持し、プロジェクト履歴を一貫したものにします。

(master)$ git merge --no-ff --no-commit my-branch

ブランチをマージして一つのコミットにしたい

(master)$ git merge --squash my-branch

push していない複数のコミットだけを一つのコミットにしたい

上流 (upstream) にプッシュする前に一つにしたいいくつかの進行中のコミットを持っていることがあります。 すでに上流にプッシュされているコミットを誤って一つにしたくない場合があります。他の誰かがすでにそれらを参照するコミットをした可能性があるからです。

(master)$ git rebase -i @{u}

プッシュしていないコミットだけの一覧を表示し、対話的なリベースが始まるでしょう。安全に一覧の中のコミットを並べ替え/修正/squash することができます。

マージを中断 (abort) する

マージによって特定のファイルに問題が発生することがあります。その場合はオプション abortを使って現在の競合解決プロセスを中止し、マージ前の状態を再構築します。

(my-branch)$ git merge --abort

このコマンドは バージョン 1.7.4 以上の Git で使えます。

ブランチのコミットの親を更新したい

master ブランチ、master から分岐したfeature-1ブランチ、およびfeature-1から分岐したfeature-2ブランチがあるとします。 feature-1にコミットすると、feature-2の親のコミットは正確ではなくなります(feature-2 はfeature-1 から分岐したので、feature-2 の親は feature-1の先頭になるはずです)。 これを git rebase --ontoで修正できます。

(feature-2)$ git rebase --onto feature-1 <the first commit in your feature-2 branch that you don't want to bring along> feature-2

まだマージされていない別のフィーチャーブランチからフィーチャーブランチを切り、feature-1 ブランチのバグ修正をfeature-2ブランチに反映させる必要がる厄介な場合にこの方法は役に立ちます。

ブランチの全てのコミットがマージされたか確かめる

ブランチ上のすべてのコミットが別のブランチにマージされているかどうかを確認するには、それらのブランチの先頭(または任意のコミット)の間で差分をとる必要があります。

(master)$ git log --graph --left-right --cherry-pick --oneline HEAD...feature/120-on-scroll

一方にコミットがあり、他方にはコミットがないかかを表示し、ブランチ間で共有できていない行の一覧を表示します。 もう1つの選択肢は次のようにすることです。

(master)$ git log master ^feature/120-on-scroll --no-merges

インタラクティブな rebase で起こりうる問題

リベースの修正画面に noop と表示された

次のようなメッセージを見たとします。

noop

これは、同じコミットのブランチ、または現在のブランチよりも進んでいるブランチに対して rebase しようとしているということです。 次のことをしてみましょう。

  • master ブランチがしかるべき場所にあることを確認する
  • 代わりに HEAD~2以前に対して rebase する

コンフリクトがある

rebase を正常に完了できない場合は、競合を解決する必要があります。

最初に git statusを実行してどのファイルが競合しているのかを確認してください。

(my-branch)$ git status
On branch my-branch
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

  both modified:   README.md

この例では、 README.mdが競合しています。 そのファイルを開き、以下を探します。

   <<<<<<< HEAD
   some code
   =========
   some code
   >>>>>>> new-commit

新しいコミットで追加されたコード(この例では、中央の行から new-commitまでの行すべて)とHEADの diff を解決する必要があります。

あるブランチのバージョンのコードをチェックアウトしたい場合は、 --oursまたは--theirsを使います。

(master*)$ git checkout --ours README.md
  • マージするときは、ローカルブランチからの変更を保持するには --oursを、他のブランチからの変更を保持するには--theirsを使用します。
  • rebase するときは、ローカルブランチからの変更を保持するには --theirs、他のブランチからの変更を保持するには--oursを使用します。

なぜ逆になるかの説明は、Gitドキュメントのこのメモを参照してください。

マージがもっと複雑な場合は、視覚的な diff エディタを使用できます。

(master*)$ git mergetool -t opendiff

すべての競合を解決してコードをテストしたら、変更したファイルを git addしてからgit rebase --continueで rebase を続けます。

(my-branch)$ git add README.md
(my-branch)$ git rebase --continue

すべての衝突を解決した後でコミット前と同じツリーになった場合は、代わりに git rebase --skipを実行する必要があります。

リベースを中止してブランチの元の状態に戻りたい場合は、いつでも次のようにして中止できます。

(my-branch)$ git rebase --abort

Stash

全ての修正を stash する

作業ディレクトリのすべての編集内容を stash するには

$ git stash

追跡されていないファイルも stash したい場合は、-uオプションを使用してください。

$ git stash -u

特定のファイルを stash する

作業ディレクトリからファイルを1つだけ stash するには

$ git stash push working-directory-path/filename.ext

作業ディレクトリから複数のファイルを stash するには

$ git stash push working-directory-path/filename1.ext working-directory-path/filename2.ext

メッセージ付きで stash する

$ git stash save <message>

リスト中の stash を適用する

まずメッセージ付きの stash の一覧を確認して下さい。

$ git stash list

そして一覧から特定の stash を適用します

$ git stash apply "stash@{n}"

ここで 'n'はスタック内の stash の位置を示します。 一番上の stash は位置0になります。

検索

任意のコミットから文字列を検索する

任意のコミットで入れられた特定の文字列を検索するために、以下の仕組みを使うことができます:

$ git log -S "string to find"

共通のパラメータ:

  • --sourceは各コミットが達成されたコマンドラインで与えられた参照名を表示することを意味します。

  • --allは全てのブランチから始まることを意味します。

  • --reverseは逆の順番で表示します。つまり、変更を加えた最初のコミットを表示します。

著者/コミッタで検索する

作者/コミッタごとにすべてのコミットを見つけるには、次のコマンドを使用できます。

$ git log --author=<name or email>
$ git log --committer=<name or email>

作者とコミッタは同じではないことに留意してください。 --authorは最初にコードを書いた人です。 一方、 --committerは最初の作者の代わりにコードをコミットした人です。

特定のファイルを含むコミットの一覧を出す

特定のファイルを含むすべてのコミットを見つけるには、次のコマンドを使用できます。

$ git log -- <path to file>

通常は正確なパスを指定しますが、パスとファイル名にはワイルドカードを使用することもできます。

$ git log -- **/*.js

ワイルドカードを使用するとき、コミットされたファイルの一覧を見るために --name-statusを使うと便利です。

$ git log --name-status -- **/*.js

特定の関数のコミット履歴を見る

ある関数の変化を追跡するには、次のようにします。

$ git log -L :FunctionName:FilePath

revision rangecommit limitsのようなgit logのオプションを更に組み合わせることができます。

コミットが参照されているタグを検索する

特定のコミットを含むすべてのタグを見つけるには

$ git tag --contains <commitid>

サブモジュール

全てのサブモジュールをクローンする

$ git clone --recursive git://github.com/foo/bar.git

もし既にクローンしているなら

$ git submodule update --init --recursive

サブモジュールを削除する

サブモジュールを作成するのはとても簡単ですが、削除するのはそれほど簡単ではありません。 必要なコマンドは次のとおりです。

$ git submodule deinit submodulename
$ git rm submodulename
$ git rm --cached submodulename
$ rm -rf .git/modules/submodulename

様々なオブジェクト

削除したファイルを復活する

まずファイルが存在した最後のコミットを見つけます。

$ git rev-list -n 1 HEAD -- filename

そのファイルをチェックアウトします。

git checkout deletingcommitid^ -- filename

タグを削除する

$ git tag -d <tag_name>
$ git push <remote> :refs/tags/<tag_name>

削除したタグを復活する

すでに削除されたタグを復元したい場合は、次の手順に従えばできます。まず、到達不能なタグを見つける必要があります。

$ git fsck --unreachable | grep tag

タグのハッシュを書き留めます。 その後、git update-refを利用して、削除したタグを次のように復元します。

$ git update-ref refs/tags/<tag_name> <hash>

タグは復元されたはずです。

削除されたパッチ

誰かが GitHub でプルリクエストを送ってから、元々のフォークを削除した場合、そのリポジトリをクローンすることも、[giff am]を.diff、.patchとして使うこともできないでしょう。しかし、GitHubの特殊な参照を使ってPR自体をチェックアウトすることができます。 PR#1の内容をpr_1という新しいブランチに fetch するには、次の手順を実行します。

$ git fetch origin refs/pull/1/head:pr_1
From github.com:foo/bar
 * [new ref]         refs/pull/1/head -> pr_1

リポジトリを zip ファイルとしてエクスポートする

$ git archive --format zip --output /full/path/to/zipfile.zip master

ブランチをプッシュしてタグを同じ名前にする

ブランチと同じ名前のタグがリモートリポジトリにある場合、標準の $ git push <remote> <branch>コマンドでそのブランチを push しようとすると以下のエラーが発生します。

$ git push origin <branch>
error: dst refspec same matches more than one.
error: failed to push some refs to '<git server>'

これを修正するには、ヘッド参照を push するように指示します。

$ git push origin refs/heads/<branch-name>

ブランチと同じ名前のリモートリポジトリにタグをプッシュしたい場合は、同様のコマンドを使用できます。

$ git push origin refs/tags/<tag-name>

ファイルの追跡

ファイル内容は変えずにファイル名の大文字/小文字を変えたい

(master)$ git mv --force myfile MyFile

pull するときにローカルのファイルを上書きしたい

(master)$ git fetch --all
(master)$ git reset --hard origin/master

Git からファイルを取り除きたいが、ファイルを消去したくない

(master)$ git rm --cached log.txt

特定のバージョンにファイルを戻したい (revert)

必要なコミットのハッシュがc5f567であると仮定します。

(master)$ git checkout c5f567 -- file1/to/restore file2/to/restore

c5f567の前に1コミットだけ行った変更に戻したい場合は、コミットハッシュをc5f567~1として渡します。

(master)$ git checkout c5f567~1 -- file1/to/restore file2/to/restore

コミット間やブランチ間の変更の一覧を見たい

前回のコミットとコミットc5f567のファイルを比較したいとします。

$ git diff HEAD:path_to_file/file c5f567:path_to_file/file

ブランチに対しても同様です。

$ git diff master:path_to_file/file staging:path_to_file/file

特定のファイルへの変更を無視するようにしたい

これは、設定のテンプレートや、コミットしてはいけない秘密情報をローカルに追加する必要がある場合に役に立ちます。

$ git update-index --assume-unchanged file-to-ignore

これはソース管理からファイルを削除しないことに注意してください。ローカルでのみ無視されます。 これを元に戻して、Gitに変更に再び気付くように指示するために、ignore フラグをクリアします。

$ git update-index --no-assume-unchanged file-to-stop-ignoring

Git でデバッグしたい

git-bisectコマンドは二部探索を使用して、Git履歴のどのコミットがバグを引き起こしたかを見つけます。

masterブランチにいて、何らかの機能を壊したコミットを見つけたいとしましょう。 二等分をし始めます。

$ git bisect start

そして、どのコミットが悪いのか、どのコミットが良いと分かっているのかを指定する必要があります。 あなたの現在のバージョンが悪く、 v1.1.1が良いと仮定します。

$ git bisect bad
$ git bisect good v1.1.1

git-bisectは今指定した範囲の真ん中でコミットを選択しチェックアウトして、それが良いか悪いかを尋ねます。 次のようなものが出るはずです。

$ Bisecting: 5 revision left to test after this (roughly 5 step)
$ [c44abbbee29cb93d8499283101fe7c8d9d97f0fe] Commit message
$ (c44abbb)$

今度はこのコミットが良いのか悪いのかを確認します。 もしそれが良ければ

$ (c44abbb)$ git bisect good

そして git-bisectは範囲から別のコミットを選択します。 このプロセス( goodまたはbadを選択する)は、検査するリビジョンがなくなるまで繰り返され、最後に最初の 悪いコミットの説明が出力されます。

設定

Git コマンドに別名を付けたい

OS XとLinuxでは、git設定ファイルは ~/.gitconfigに格納されています。 私がショートカットとして[alias]セクションで使用するエイリアスの例(及び私が よく犯してしまうタイプミス)を以下に挙げます。

[alias]
    a = add
    amend = commit --amend
    c = commit
    ca = commit --amend
    ci = commit -a
    co = checkout
    d = diff
    dc = diff --changed
    ds = diff --staged
    extend = commit --amend -C HEAD
    f = fetch
    loll = log --graph --decorate --pretty=oneline --abbrev-commit
    m = merge
    one = log --pretty=oneline
    outstanding = rebase -i @{u}
    reword = commit --amend --only
    s = status
    unpushed = log @{u}
    wc = whatchanged
    wip = rebase -i @{u}
    zap = fetch -p
    day = log --reverse --no-merges --branches=* --date=local --since=midnight --author=\"$(git config --get user.name)\"
    delete-merged-branches = "!f() { git checkout --quiet master && git branch --merged | grep --invert-match '\\*' | xargs -n 1 git branch --delete; git checkout --quiet @{-1}; }; f"

リポジトリに空のディレクトリを追加したい

できません。 Gitはこれをサポートしていませんが、ハックがあります。 ディレクトリに次の内容の.gitignoreファイルを作成できます。

 # ディレクトリ下の全てのファイルを ignore する
 *
 # ただしこのファイルは除く
 !.gitignore

もう1つの一般的な慣習は、.gitkeepという名前の空のファイルをフォルダ内に作成することです。

$ mkdir mydir
$ touch mydir/.gitkeep

ファイルの名前を.keepとすることもできます。その場合、上の2行目は touch mydir/ .keepになります。

リポジトリへのユーザ名とパスワードをキャッシュしたい

認証が必要なリポジトリがあるかもしれません。 その場合、ユーザー名とパスワードをキャッシュすることができるので、プッシュとプルのたびにそれを入力する必要はありません。 認証情報ヘルパーがこれをやってくれます。

$ git config --global credential.helper cache
# git に認証情報をキャッシュさせる
$ git config --global credential.helper 'cache --timeout=3600'
# キャッシュは一時間だけ有効(秒で指定する)

認証情報ヘルパーを検索するには

$ git help -a | grep credential
# 認証ヘルパーの候補を表示する

OS 固有の認証情報のキャッシュは

$ git config --global credential.helper osxkeychain
# OS X 向け
$ git config --global credential.helper manager
# Windows 2.7.3+ 向けの Git
$ git config --global credential.helper gnome-keyring
# Ubuntu や他のGNOMEベースのディストリビューション

様々なディストリビューションやオペレーティングシステムに対して、もっと多くの認証ヘルパーが見つかるかもしれません。

パーミッションとファイルモードの変更を Git に無視させたい

$ git config core.fileMode false

これをログインユーザーのデフォルトの動作にしたい場合は、次のようにします。

$ git config --global core.fileMode false

グローバルユーザを設定したい

全てのローカルリポジトリで使用されるユーザー情報を設定し、バージョン履歴を確認するときにクレジットとして分かる名前を設定するには

$ git config --global user.name “[firstname lastname]

各履歴マーカーに紐付けられるE メールアドレスを設定するには

git config --global user.email “[valid-email]

コマンドラインに Git 用の色を付けたい

簡単にレビューできるようにGitの自動のコマンドラインカラーリングを設定するには

$ git config --global color.ui auto

何が間違えたのかさっぱり分からない

つまり、あなたは混乱してしまいました。 何かを「リセット」したか、間違ったブランチをマージしたか、あるいは force push してコミットを見つけることができません。 ある時点で、大丈夫だったことを知っています。そして以前の状態に戻したいと思っているとします。

こういう時 git reflogはうってつけです。 reflogは、たとえその先端がブランチやタグによって参照されていなくても、ブランチの先端に対するいかなる変更も追跡します。 基本的に、HEADが変わるたびに、新しいエントリがreflogに追加されます。 残念ながら、これはローカルリポジトリでのみ機能し、動きだけ追跡します(例えば、どこにも記録されていないファイルへの変更は追跡されません)。

(master)$ git reflog
0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to master
c10f740 HEAD@{2}: checkout: moving from master to 2.2

上のreflogはmasterから2.2ブランチへのチェックアウトとその逆のチェックアウトを示しています。 そこから、古いコミットへのハードリセットがあります。 最新の動きは HEAD @ {0}とラベルされた一番上に表示されます。

誤って戻ったことが判明した場合は、、誤って2つのコミットを削除する前の(0254ea7)を指すコミットマスターがreflogに含まれます。

$ git reset --hard 0254ea7

git resetを使うと、master を以前のコミットに戻すことができます。 これは、履歴が誤って変更された場合の安全策となります。

Sourceからコピーして編集しました)。

Git ショートカット

Git Bash

上記のコマンドの動作に慣れたら、Git Bashのショートカットをいくつか作成したくなるかもしれません。 これにより、複雑な作業を、本当に短いコマンドで、本当に素速く行えるようになります。

alias sq=squash

function squash() {
    git rebase -i HEAD~$1
}

これらのコマンドを .bashrc や .bash_profileにコピーして下さい。

Windows の PowerShell

WindowsでPowerShellを使用している場合は、エイリアスと関数も設定できます。 これらのコマンドをプロファイルに追加してください。そのパスは $ profile変数で定義されています。 MicrosoftのドキュメントサイトのAbout Profilesページで詳細を確認してください。

Set-Alias sq Squash-Commits

function Squash-Commits {
  git rebase -i HEAD~$1
}

他の文献

書籍

チュートリアル

スクリプトとツール

  • firstaidgit.io 最もよく寄せられるGitの質問の検索可能なセレクション
  • git-extra-commands - 便利なGitスクリプトのコレクション
  • git-extras - GITユーティリティ - リポジトリの概要、REPL、更新履歴の統計、作者のコミット率など
  • git-fire - 緊急時にすべての現在のファイルを追加し、コミットし、(マージの競合を防ぐために)新しいブランチにプッシュしてくれるGitプラグインです。
  • git-tips - ちょっとしたGitのtips
  • git-town - 一般的な、ハイレベルのGitのワークフローのサポート http://www.git-town.com

GUI クライアント

  • GitKraken - Windows、MacおよびLinux用の、実に豪華なGitクライアント
  • git-cola - WindowsとOS X用の別のGitクライアント
  • GitUp - Git の複雑さへの独特の対処策を備えた新しいGUI
  • gitx-dev - OS X の別のグラフィカルなGit クライアント
  • Sourcetree - Windows と Mac 向けの美しさを兼ね備えた無料のパワフルなGit GUI
  • Tower - OS X 向けのグラフィカルなGitクライアント (有料)
  • tig - ターミナルのテキストベースのGitインターフェース
  • Magit - Emacs パッケージとして実装されたGitのインターフェース
  • GitExtensions - シェル拡張、Visual Studio 2010-2015プラグイン、スタンドアロンのGitリポジトリツール
  • Fork - 速くてフレンドリーなMac向けのGUI クライアント (ベータ版)
  • gmaster - 3者間マージ、リファクタリングの分析、セマンティックdiff、およびマージができるWindows 向けのGitクライアント(ベータ版)
  • gitk - リポジトリの状態を簡潔に表示できるLinux向けのGitクライアント。
  • SublimeMerge - 3者間マージ、強力な検索およびシンタックスハイライトを提供する、非常に高速で拡張可能なクライアント(アクティブに開発中)。

訳者:堀田(twitter: @YoshiHotta

著者をフォロー👇

【理論から実践まで】動かしながら学ぶ!ゼロからわかる再帰的ニューラルネットワーク(RNN)

この記事では再帰的ニューラルネットワーク (RNN) について解説をします。RNN の理論的な説明から入り、Keras を用いて実際に RNN を動かしてみます。単純RNN (SimpleRNN), LSTM, 双方向RNN (bidirectional RNN), deep RNN を用いてモデリングをします。なおこの記事はGoogle Colaboratory で動かすことができ、実行しながら読むことをおすすめします。

f:id:yoshihotta:20190628170824p:plain
ノートブックを開く

再帰的ニューラルネットワーク

再帰的ニューラルネットワーク(リカレントニューラルネットワーク、RNN))は系列データのモデルです。 各時刻 $t_1, t_2, \cdots,t_n$で$\vec{x_1}, \cdots, \vec{x_n}$が入力されたときベクトル$\vec{y_1}, \cdots, \vec{y_n}$ を予測するモデルです。 RNNは自然言語のモデルとして頻繁に使われます。 例えば「今日はいい天気で絶好の散歩日和です。」という文章があったとき「今日/は/いい/天気/で/絶好/の/散歩/日和/です/。 」と文を分割し(形態素解析といいます)、$\vec{x_1} = $今日, $\vec{x_2} =$ は, $\vec{x_3} =$ いい, $\vec{x_4} =$ 天気, $\vec{x_5} =$ で,$\vec{x_6} =$ 絶好,$\vec{x_7} =$ の,$\vec{x_8} =$ 散歩, $\vec{x_9} =$ 日和,$\vec {x _ {10}} =$です,$\vec{x _ {11}} =$ 。 とすると文は系列データになります。文の並びには意味があり、順番をバラバラにすると意味を捉えられなくなってしまうので、並びに意味があるデータ、つまり系列データとして扱わなければいけません。

RNNの利点としてマルコフ性を要求しないことがあります。 マルコフ性とは次の点が直前の$k$個の変数にのみ依存する性質のことです。 数式で書くと

P(x_n|x_1, \cdots, x_{n-1}) = P(x_n|x_{n-k}, \cdots, x_{n-1})

が満たされるということで、多くの系列データのモデルはマルコフ性を仮定します。 一方RNNはマルコフ性を仮定しないため、初めから直前のデータ全てを考慮して次の点を予測することができます。

再帰的ニューラルネットワークの一般的な表記

再帰的ニューラルネットワークのダイアグラムは以下です。

RNN-diagram

全ての時点を通じてRとOのパラメータが同じであることを強調するために$\theta$をわざと書いています。 $s_i$は状態と呼ばれ、ダイアグラムの横方向に伝搬していきます。 RNNは抽象的に以下のように書けます。


y_{1:n} = RNN(x_{1:n}, s_0) \\
y_i = O(s_i)\\
s_i = R(s_{i-1},x_i)

ここで$x_i, y_i, s_i$はベクトルで$x _ {1:n}$という記法は$x _ {1:n}=(x _ 1, \cdots, x _ n)$を表します。これからはベクトルと分かるときはベクトルの矢印を省略します

RNNは状態もRの引数にすることで過去の状態を考慮してモデリングすることができます。

RNNの使い方

RNNの典型的な使い方は系列データの特徴量抽出です。 RNNの最後の出力だけを使う方法と全ての時刻での出力を用いる方法があります。 それぞれ見ていきましょう。

RNNの最後の出力だけを用いる方法

この方法では各時点の情報が$s_i$を通じて右方向に伝搬し$y_n$に圧縮されます。

acceptor_diagram

図1: アクセプタ

時刻$1$から$n-1$の出力$y_i$は捨てます。 最後の出力$y_n$を予測値として直接用いることは通常しません。 $y_n$は予測を担う層の入力とすることが多いです。 例えば$y_n$を用いて2値分類するなら$\hat{t} = \mathrm{sigmoid}(\mathrm{Dense}(y_n))$とし、密な層とsigmoid関数をかけます。 このようなRNNの用途をアクセプタ (acceptor) と言います。

RNNの全ての時刻での出力を用いる方法

時刻$1$から$n$の出力$y_i$を全て用います。 各時刻で予測をします。 このようなRNNの利用法はtransducer (変換器)と呼ばれます。

transducer

図2: 変換器

典型的な応用に系列ラベリングがあります。 各時点の入力にクラスを割り当てるタスクです。 言語処理では言語モデルを作るのにtransducer としてのRNNが用いられます。 この例はRNNの言語処理での非常に重要な活用法ですので後で詳しく説明します。

単純RNN

まずは最も簡単なRNNから見ていきましょう。


s_i =R(x_i, s_{i-1}) = \tanh(s_{i-1}W^s + x_i W^x + b)\\
y_i = O(s_i) = s_i

と$R$と$O$を選びます。 つまり次の状態は前の状態と入力の和にし、時間方向は通常のニューラルネットワークと同様にします。 このRNNは単純RNN , Elman RNN, S-RNNと呼ばれます。 活性化関数$\tanh$があるため入力の順序を単純RNNは認識できます。 単純RNNの横方向は通常のニューラルネットワークと同じであるため、通常のニューラルネットワークと同様勾配消失問題が起こります。 つまり教師データが系列の最後に与えられると系列の初めの方には微弱なフィードバックしかかからなくなり、学習が進まなくなります。 単純RNNは長い系列をモデル化し、長距離の依存性を捉えることには向いていないのです。 勾配消失問題への対応策としてLSTM, GRUといったRNNが発明されました。

実践:客数を当てよう!単純RNN で回帰

RNNは系列データのモデリングに向いています。回帰では数字を当てることになります。株価、気温、客数といった数字を予測するのにRNNは向いています。航空会社の乗客数を予測してみましょう。まずはデータをここからダウンロードして、google drive にアップロードして下さい。

データをアップロードしたgoogle drive のディレクトリをdata_dirとします。私はMy Drive/Colab Notebooks/blog_data/passangers/にアップロードしたのでdata_dir = /content/gdrive/My Drive/Colab Notebooks/blog_data/passangers/となっています。これは人によって違うので書き換えて下さい。

from google.colab import drive
drive.mount('/content/gdrive')
Drive already mounted at /content/gdrive; to attempt to forcibly remount, call drive.mount("/content/gdrive", force_remount=True).
data_dir =  '/content/gdrive/My Drive/Colab Notebooks/blog_data/passangers/'

import pandas as pd
data = pd.read_csv(data_dir + 'international-airline-passengers.csv',sep=';', header=0, usecols=[1])
data.head()
Passengers
0 112
1 118
2 132
3 129
4 121

このデータはある航空会社の月ごとの客数で単位は千です。つまり最初のサンプルはある月の乗客数が $112,000$ 人いたことを表します。データをプロットしてみましょう。

import pandas as pd
import matplotlib.pyplot as plt
plt.plot(data)
[<matplotlib.lines.Line2D at 0x7f25c26c6978>]

png

データは右肩上がりで周期性を持っています。次のデータ点を当てるためにはこの2つの特徴を捉えられるくらい昔までデータを見れば大丈夫そうです。ここでは単純RNNを使います。

前処理をします。今回は目的変数のスケールを調整して[0, 1]の間に収まるようにします。scikit-learnMinMaxScalerを使えばできます。

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(feature_range=(0, 1))
data = scaler.fit_transform(data)
plt.plot(data)
[<matplotlib.lines.Line2D at 0x7f25c26a2cf8>]

png

データセットを訓練データとテストデータに7:3に分けます。時系列データなのである点の予測をするのに未来の点を用いてはならないため、ランダムにsplitしてはいけないことに注意して下さい。

n_train = int(len(data) * 0.7)
n_test = len(data) - n_train
train, test = data[0:n_train,:], data[n_train:len(data),:]

直前の10個の点を見て次の点を予測することにします。

import numpy as np

def create_dataset(data, input_size):
    x_data, y_data = [], []
    for i in range(len(data) - input_size-1):
        input_data = data[i:(i+input_size), 0]
        x_data.append(input_data)
        y_data.append(data[i + input_size, 0])
    return np.array(x_data), np.array(y_data)

input_size = 10
x_train, y_train = create_dataset(train, input_size)
x_test, y_test = create_dataset(test, input_size)

x_train = np.reshape(x_train, (x_train.shape[0], x_train.shape[1], 1))
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 1))

Keras で 単純RNN を使うのは簡単です。以下のコードだけでモデルができます。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, SimpleRNN

model = Sequential()
model.add(SimpleRNN(10, input_shape=(input_size, 1))) 
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(x_train, y_train, epochs=100, batch_size=1, verbose=2)

モデルを可視化してみましょう。

from IPython.display import SVG
from tensorflow.python.keras.utils.vis_utils import model_to_dot
SVG(model_to_dot(model).create(prog='dot', format='svg'))

svg

この図は分かりにくいですが、図1と同じです。

プロットして予測値と実際の値を見比べましょう。

test_pred = model.predict(x_test)
test_pred = scaler.inverse_transform(test_pred)

test_pred_slug = np.empty_like(data)
test_pred_slug[:, :] = np.nan
test_pred_slug[len(y_train)+(input_size*2)+1:len(data)-1, :] = test_pred

plt.plot(scaler.inverse_transform(data), label='Actual data')
plt.plot(test_pred_slug, label='Prediction')
plt.legend()
plt.show()

png

biRNN

RNNは頭から順番に読んで出力をしました。 例えば"Sometimes people say neural networks model brain"という文中でのmodelの品詞を当てるタスクを考えます。 この文は「ニューラルネットワークは脳のモデルであると言われることがある」という意味で、modelは動詞です。 modelの前後に名詞があることがmodelが名詞であることの手がかりになっています。 もしmodelの後ろの単語 brain を知らなかったらmodelの品詞を当てるのは難しいのではないでしょうか。 系列にラベルを付けていくタスクにおいて頭からだけでなく、後ろから読んでいくことも有用であることを示唆しています。

頭とお尻の両方から系列を読むモデルを双方向再帰的ニューラルネットワーク (bidirectional RNN, biRNN)と呼びます。 biRNNは系列内の任意の過去と未来を見ることができるモデルなのです。

biRNN

図3: biRNN

系列$x _ {1:n}=(x _ 1, \cdots, x _ n)$が与えられたとき時刻$j$での出力を考えましょう。 biRNNでは前向き状態 (forward state) $s _ {i}^f$を$x _ {1:j}$基づいて計算し、後向き状態 (backward state) $s _ {i}^b$を$x _ {j:n}$に基づいて計算します。 そして$s _ {i}^f$と$s _ {i}^b$を合わせて時刻$j$での状態として利用します。 抽象的な書き方をすると

$$y _ i = (O ^ f(s _ {i} ^ f), O ^ b(s _ {i} ^ b))$$ $$s _ {i} ^ f = R ^ f(s _ {i} ^ f, x _ i)$$ $$s _ {i} ^ b = R ^ b(s _ {i} ^ b, x _ i)$$

となります。

Deep RNN

RNNは積み上げて性能を向上させることができます。 これを多層RNN (deep RNN) と言います。

deep-RNN

図4: Deep RNN

下の層の出力を次の層の入力とします。 横の一行が一つのRNNです。 行はRNNでなくbiRNNとしてもいいです。 最後の行の出力がこのモデルの出力になります。 前述と同様にdeep RNNをアクセプタとして用いるために$y _ {n}$だけを利用してもいいですし、transucer として用いるために$y _ {1:n}$全てを利用してもいいです。 なぜ性能が上がるのかは理論的にはよく分かっていない上に一層のRNN に比べてdeep RNNは計算コストがかかりますが、経験則としてdeep RNNは一層のRNNより性能がよくなることがあるので性能を追求したい場合は試してみるとよいでしょう。

実践:スパムをはじけ!RNNで分類

RNNの分類問題の例としてスパムフィルターの実装を説明します。まずはここからデータをダウンロードしてgoogle drive にアップロードしましょう。アップロードするのはSMSSpamCollectionです。

$wget https://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip
$unzip msspamcollection.zip
$ls msspamcollection.zip
>SMSSpamCollection readme

このデータセットはSMSのデータセットです。SMSSpamCollectionを見てみましょう。

from google.colab import drive
drive.mount('/content/gdrive')
Drive already mounted at /content/gdrive; to attempt to forcibly remount, call drive.mount("/content/gdrive", force_remount=True).
home = '/content/gdrive/My Drive/Colab Notebooks/'
data_dir = home + 'blog_data/spam/'
import pandas as pd
data = pd.read_csv(data_dir + 'SMSSpamCollection',sep='\t', header=-1, names=['type', 'text']) 
data.head()
type text
0 ham Go until jurong point, crazy.. Available only ...
1 ham Ok lar... Joking wif u oni...
2 spam Free entry in 2 a wkly comp to win FA Cup fina...
3 ham U dun say so early hor... U c already then say...
4 ham Nah I don't think he goes to usf, he lives aro...
len(data)
5572

このデータセットは5572件のSMSのメッセージを含んでいます。一列目がメッセージがスパム (spam)か普通のメッセージ (ham) を表し、二列目がメッセージです。 一列目の変数 (ham/spam)をダミー変数 (0/1)に変換します。

import numpy as np
data['class'] = np.where(data['type']=='ham', 0, 1)
data.head()
type text class
0 ham Go until jurong point, crazy.. Available only ... 0
1 ham Ok lar... Joking wif u oni... 0
2 spam Free entry in 2 a wkly comp to win FA Cup fina... 1
3 ham U dun say so early hor... U c already then say... 0
4 ham Nah I don't think he goes to usf, he lives aro... 0

データを訓練データとテストデータに7:3で分けます。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data['text'], data['class'], test_size=0.30, random_state=42)

前処理の流れは以下のようになります。 1. トークン化。文をスペースで区切り、単語に分けます。単語は数字に置換されます。例えばI have a pen で I -> 1, have -> 4, a -> 10, pen -> 2 と置換することにすると [1, 4, 10, 2] となります。 2. パディング。Keras で LSTM に系列データを流すには全てのサンプルが同じ長さである必要があります。最大長を決めて、サンプルの長さが最大長に満たなければ0で埋めます。これをパディングと言います。

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences

max_len = 100  

tokenizer = Tokenizer()
tokenizer.fit_on_texts(data['text'])
x_train = tokenizer.texts_to_sequences(X_train)
x_test = tokenizer.texts_to_sequences(X_test)

print('トークン化した後:')
print(x_train[0])

x_train = pad_sequences(x_train, maxlen=max_len)
x_test = pad_sequences(x_test, maxlen=max_len)

print('パディングした後:')
print(x_train[0])
トークン化した後:
[372, 233, 336, 557, 963, 434, 1, 1342, 184, 2105]
パディングした後:
[   0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0    0    0    0    0    0    0    0    0
    0    0    0    0    0    0  372  233  336  557  963  434    1 1342
  184 2105]

ここではbidirectional LSTMを使いましょう。LSTMの説明はしていませんが、LSTMがRNNの一種であることだけ覚えておけば十分です。KerasではSimpleRNNを使うのもLSTMのような複雑なモデルを使うのも同じくらい簡単です。実際下のコードでLSTMをSimpleRNNに変えても動きます。bidirectional LSTM は RNN の一種である LSTM 2つを図3のように双方向に繋げたものです。Keras で bidirectional LSTM の層を作るには model.add(Bidirectional(LSTM(16, return_sequences=False))) と書くだけです。

※以下のコードの実行には時間がかかります。Google Colaboratory を使っている方は「ランタイム>ランタイムのタイプを変更」から GPU を使うようにすることをお勧めします。

from keras.models import Sequential
from keras.layers import Bidirectional, Dense, Embedding, LSTM

vocabulary_size = len(tokenizer.word_index) + 1  

model = Sequential()
model.add(Embedding(input_dim=vocabulary_size, output_dim=32))
model.add(Bidirectional(LSTM(16, return_sequences=False)))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()

# 学習
history = model.fit(
    x_train, y_train, batch_size=32, epochs=10,
    validation_data=(x_test, y_test)
)

pred = model.predict_classes(x_test)
print(confusion_matrix(y_test, pred))
[[1446    2]
 [  10  214]]
len(tokenizer.word_index)
9009
import matplotlib.pyplot as plt
plt.plot(history.history['acc'])
[<matplotlib.lines.Line2D at 0x7f25c0b79898>]

png

from IPython.display import SVG
from tensorflow.python.keras.utils.vis_utils import model_to_dot
SVG(model_to_dot(model).create(prog='dot', format='svg'))

svg

正しく分類できた文と間違って分類した文の数を見てみましょう。これはconfusion matrix を使うと分かります。confution matrix の要素を$c _ {ij}$とします。$c _ {ij}$は予測クラスが$j$で正解クラスが$i$であったテストデータのサンプル数です。

from sklearn.metrics import confusion_matrix
pred = model.predict_classes(x_test)
print(confusion_matrix(y_test, pred))
[[1446    2]
 [  10  214]]

実際にはスパムなのに普通のメッセージ (ham)だと判定したメッセージが11個あります。このような予測を偽陰性といいます。偽陰性のサンプルを見てみましょう。

false_negative = X_test[y_test > pred.reshape(-1)]
for fn in false_negative:
  print(fn)
Babe: U want me dont u baby! Im nasty and have a thing 4 filthyguys. Fancy a rude time with a sexy bitch. How about we go slo n hard! Txt XXX SLO(4msgs)
Hello darling how are you today? I would love to have a chat, why dont you tell me what you look like and what you are in to sexy?
Do you realize that in about 40 years, we'll have thousands of old ladies running around with tattoos?
Sorry I missed your call let's talk when you have the time. I'm on 07090201529
PRIVATE! Your 2003 Account Statement for 078
Email AlertFrom: Jeri StewartSize: 2KBSubject: Low-cost prescripiton drvgsTo listen to email call 123
100 dating service cal;l 09064012103 box334sk38ch
Did you hear about the new "Divorce Barbie"? It comes with all of Ken's stuff!
The current leading bid is 151. To pause this auction send OUT. Customer Care: 08718726270
You'll not rcv any more msgs from the chat svc. For FREE Hardcore services text GO to: 69988 If u get nothing u must Age Verify with yr network & try again

人間でも一見スパムだと分からない文が多いですね。LSTMも間違えてしまいました。

Deep RNN も使ってみましょう。ここでは RNN として LSTM を使います。図4 のような4層のアーキテクチャを作ってみます。最初の3層は各時点での出力を次の層の入力とします。最後の1層だけ最後の時点の出力だけをDense層に渡し、それ以外の出力は使いません。return_sequences = Trueとすると各時点での出力が次の層に渡り、Falseとすると最後の出力だけが使われます。

model = Sequential()
model.add(Embedding(input_dim=vocabulary_size, output_dim=32))
model.add(LSTM(16, return_sequences=True))
model.add(LSTM(16, return_sequences=True))
model.add(LSTM(16, return_sequences=True))
model.add(LSTM(16, return_sequences=False))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()

# 学習
history = model.fit(
    x_train, y_train, batch_size=32, epochs=10,
    validation_data=(x_test, y_test)
)

pred = model.predict_classes(x_test)
print(confusion_matrix(y_test, pred))
[[1441    7]
 [  13  211]]
SVG(model_to_dot(model).create(prog='dot', format='svg'))

svg

最新のモデルに行く前に

これまでRNNの一般的な表記について解説しました。 RNNは一つのモデルを指すわけではなく、この記事中のダイアグラムで書けるモデル全般を指します。 単純RNN, LSTM, GRUは全てRNNの一種です。 LSTMやGRUは込み入った工夫がなされているので複雑ですが、混乱したらこれらはRNNの一種であることを思い出すと迷わずに済むでしょう。LSTMとGRUの解説は今後執筆する予定です。乞うご期待!