nykergoto’s blog

機械学習とpythonをメインに

画像の超解像度化をするモデル SRCNN を pytorch で実装してみた

画像の超解像度化というタスクがあります。 やることは低解像度(小さい画像)を高解像な画像に拡大するときにできるだけ綺麗に引き延ばす、というタスクです。

https://www.slideshare.net/HHiroto/deep-learning-106529202 ではDeep Learning の登場によって超解像度化技術がどのように進歩していったのかに関してとてもわかり易くまとまっていて良いです。

その中でも Deep Learning の活用が始まりだした 2015 に登場した SRCNN というモデルを実装してみました。

SRCNN の構造

SRCNN のアーキテクチャは3層の Convolution + Relu で構成されています。pytorch だと以下のような感じでとてもシンプルです。

from torch import nn


class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=9, padding=4)
        self.conv2 = nn.Conv2d(in_channels=64, out_channels=32, kernel_size=1, padding=0)
        self.conv3 = nn.Conv2d(in_channels=32, out_channels=3, kernel_size=5, padding=2)
        self.activate = nn.ReLU()

    def forward(self, x):
        h = self.activate(self.conv1(x))
        h = self.activate(self.conv2(h))
        return self.conv3(h)

ちょっと注意なのは、このネットワークでは入力される画像サイズと出力される画像サイズは同じだということです(このネットワークでは拡大はしません)。 入力されるのは低解像度画像を拡大した荒い画像で、それを同サイズの綺麗な画像にして返すというふうになっています。

学習時には、綺麗な画像と荒い画像のペアのデータが必要になります。
元の論文では、元の画像に対してガウシアンぼかしを適用して荒い画像を生成していました。 なんですが色々と実装を見ていると、画像を半分など小さく縮小して古典手法で拡大する、という方法をとっているものが大半だったので実装ではこちらを採用しています。

どれだけ綺麗に戻せたかのロス関数にはピクセル間の MSE を用います。

実装

Github にあげています。詳しくは readme に書いていますが docker/docker-compose が入っていれば動かせる、はず

github.com

実験

実際に上記の実装を使って学習を回して、超解像度をやってみました。

データセット

学習につかうデータセットはSRCNN 論文に合わせて Anchored Neighborhood Regression for Fast Example-Based Super-Resolution で使われている 91 枚の画像をつかいました。実際にはこれらの画像を 64x64 の大きさにランダムに切り出しています。

検証には Set5 と呼ばれている画像をつかいました。いろんな論文に登場するので、この分野ではよく使われるデータセットみたいですね。

実験

学習したモデルを使って実際に超解像度化を行います。左が元画像を半分に縮小して人工的に作った低解像度画像、真ん中が SRCNN で綺麗にした画像、右が元の画像です。低解像度画像は x0.5 にリサイズした後 BICUBIC で拡大して作成しました。

f:id:dette:20190518105510p:plain
Bird

f:id:dette:20190518105541p:plain
Baby

これだとちょっとわかりにくいので画像の一部分を拡大してみます。

f:id:dette:20190518105629p:plain
Baby 若干まつげがくっきりしてる?

感想

モデルがとても単純なわりに、しっかり綺麗になるのでやはり CNN の構造はとても良い性質を持っているんだなと改めて思いました。

モデルが小さいので推論だけならば CPU で十分動くので、既存のサーバーとかにも組み込みやすいように思えます。計算量が軽いことを活かして、フロントエンド側で画像を勝手に綺麗にして表示するとかも面白そうです。

あと実装的な観点でいうと torchvision の transform がとても便利で、画像の拡大縮小のコードがとってもすっきりかけて気分が良かったです😄

気になった点としてはネットワークに BatchNormalization がないので若干学習が不安定になりがちな点ですが、これは BatchNormalization が出来たのが 2015 年なので当たり前でした。Deep 界隈の進歩の速さを象徴しているなーとあらためて思いました。

つぎやりたいこと

  • 今僕は 2015 年にはこれたので, もうちょっと未来のアーキテクチャを実装して差分をみてみたい

参考文献

勾配ブースティングで大事なパラメータの気持ち

LightGBMXGBoost などで使われている勾配ブースティングのパラメータについて、チューニングノウハウというよりもそのパラメータがどういう意味を持っているのか、に焦点をあててまとめて見ました。

各ライブラリのパラメータすべては以下から確認できます。

NOTE: 以下では lightGBM のパラメータの名前で説明しています。微妙に名前が違うものがあるので適宜読み替えてください。

勾配ブースティングについてざっくりと

一般的な決定木では木はひとつだけで、その木に対してたくさんの分割ルールを適用していきます。

勾配ブースティング木では、木をたくさん作ります。たくさん作る代わりに、一つ一つの木の分割をざっくりとしたものにします。 そして作った木すべての予測の合計を使うことで、ひとつの木では表せないような複雑な予測を可能にしています。

もうちょっとくわしく: Gradient Boosted Tree (Xgboost) の取り扱い説明書

木のパラメータ

たくさん作られるそれぞれの木をどのように作成するか、に関する制約条件についてです。

はじめに木の大きさや分割方法に関するパラメータから説明します。

  • max_depth: 各木の最大の深さです。普通 3 ~ 8 ぐらいを設定します。あまり大きい値を使用すると、ひとつの木がとても大きなものになってしまいオーバーフィッティングする可能性がありますから、あまりおおきい値は設定しないほうが良いでしょう。

木はたくさん作られるのでたとえ小さい値を指定したとしても、有用な分割方法であれば次の木が分割してくれるので、まあ問題はないという印象です。

  • max_leaves: 各木の末端ノードの数です。ひとつの分割だけがあるとノードは2つになり、もう一回分割が起こるとノードは3になります。というふうに分割数 + 1 だけ末端ノードは生成されます。 このノード数は max_depth と深い関係があります。というのも max_depth を指定すると、最大の末端ノード数は 2 ** max_depth で制限されるためです。(二分分割が depth だけ起こるので)
    この時 2 ** max_depth 以上の数の max_leaves を指定すると depth の指定によるノード数の上限よりも大きくなるため無意味になってしまうことに注意してください。 (lightGBM だとエラーメッセージが出ます)
    以上のような理由から int(.7 * max_depth ** 2) などを指定して depth からくる最大ノード数の 7 割だけ分割させる、とかをやったりします。

  • min_child_samples: 末端ノードに含まれる最小のデータ数。これを下回るような分割ルールでは分割されなくなります。 例えば 40 に設定したとすると、新しい分割を行ったあとの集合にデータ数が 40 を下回るようなルールでは分割できません。 最小の数をどのぐらいにすればよいかはデータの数に依存する部分も大きいため, データ数が大きい時は少し大きい値にすることを検討してみてください。

  • gamma: 分割を増やす際の目的関数の減少量の下限値です。分割を行うと、よりデータに適合できるようになるためからなず目的関数のデータへの当てはまり部分は改善します。一方で適合し過ぎるとオーバーフィットとなります。(バイアスバリアンス)
    gamma はこの当てはまり改善に対して制限をかけることに相当します。カジュアルに言うと、めっちゃロスが下がるような明確な違いで分割してもいいけど、微妙にロスが下がるような分割はゆるさないよ、というようなイメージでしょうか。

以下は使用するデータを制限することでロバストなモデルを作成しよう、という狙いのためのパラメータです。

  • colsample_bytree: 木を作成する際に使用する特徴量の数を選択する割合です。 これが 1 以下のとき、木を作成する際に使用する特徴量をこの確率を持って選び出し、選ばれた特徴量だけを使います。
  • subsamples: 使用するデータの選択割合です。 colsample とは反対にデータの選択を行います。

つぎに木を成長させるアルゴリズムについてです。

  • boosting: boosting アルゴリズムです。このパラメータはlightGBM のみ使えます。 基本は勾配ブースティングをしたいのでデフォルトの gbdt を使うと良いでしょう。 その他にも dropout の考え方を応用して新しい木を作る際の勾配/ヘシアンの計算に今まで作った木の一部を確率的に取り出して使う dart や, random foreset (rf) も指定できます。

    random forest は stacking をする際に勾配ブースティングのモデルと相性がいいことが経験的に知られているので, 一段目のモデルとして使うことは有効かもしれません。

目的関数のパラメータ

木構造の目的関数は一般的な機械学習モデルと同様にデータへのあてはまりと、正則化の項からできています。 正確には分割を作る際にできる2つのノードに対して割り当てる予測値の値を、正則化 (l1, l2) を使って 0 に近づけるような働きをします。

目的関数は objective で定義します。

目的関数の選択方針

目的関数の選択はターゲット変数の分布に大きく依存します。 カテゴリ変数の予測の時は普通に logloss を使えば事足りることが多いですが回帰問題の時はちょっと工夫すると良いモデルを作れることがあります。 以下でそれぞれ述べていきます。

普通の回帰問題であれば rmse を使います。(デフォルトの値は rmse です). rmse はノイズの分布に正規分布を仮定することになります。 例えば工場で特定の場所にドリルで穴を開ける作業があり、実際に開けられた穴の場所を予測するスクがあったとします。このときドリルの位置のズレは正規分布に従うことが想定されるため、目的関数として rmse を使うのが良いでしょう。

一方で年収やある出来事が起こる間隔など裾の広い分布に対しては gamma を使ったり, 目的変数をログ変換して擬似的に対数正規分布に対するあてはめに変えることを検討してください。これらの分布は正規分布にならないことが多いです。

また一定期間内に起こるランダムな現象のカウントを予測する場合には poisson を使うことを検討してみてください。 例えばサッカーの試合のゴール数などがこれに相当します。

その他

その他の目的関数に関するパラメータは主に正則化に関するものを設定することが多いです。

  • reg_alpha: L1 正則化に相当するものです。デフォルトでは 0.1 ぐらいを使うことが多いです
  • reg_lambda: L2 正則化に相当するものです。デフォルトでは L1 と同様に 0.1 ぐらいを指定します。

学習時のパラメータ

学習を行うときに validation データとともに渡すパラメータです。

  • learing_rate: たくさん作った木を足し合わせるときに使う重み係数です。
    これを大きくするとひとつひとつの木の予測を多く使うようになるため、一般に収束するまでの木の数 n_estimators は少なくなり学習にかかる時間は短くなります。一方で分割法が雑になるため、精度とのトレードオフとなります。
  • eval_metric: validation データを評価する評価基準です。何も指定しないと objective で指定したものが使われます。例えば rmse ならば validation set に対しても rmse を計算します。
    目的と異なるへんな評価基準を入れると early_stopping_round を指定している時に学習が収束する前に止まってしまうこともあるので注意してください。
  • early_stopping_rounds: 指定された回数木を作っても評価基準が改善されない時に学習を途中でやめるようになります。オーバーフィットを防ぐために指定します。
  • verbose: 指定された回数に一度コンソールに eval_metric を表示します。

チューニング

チューニングは optuna を使うとらくちんにできるのでおすすめです。 いろいろな場所で使いまわせるよう、パラメータを生成するような関数だけ切り出して定義しておくと便利だと思います。以下はその実装例です。

def get_default_parameter_suggestions(trial):
    """
    Get parameter sample for Boosting (like XGBoost, LightGBM)

    Args:
        trial(trial.Trial):

    Returns:
        dict: parameter sample generated by trial object
    """
    return {
        # L2 正則化
        'reg_lambda': trial.suggest_loguniform('reg_lambda', 1e-3, 1e3),
        # L1 正則化
        'reg_alpha': trial.suggest_loguniform('reg_alpha', 1e-3, 1e3),
        # 弱学習木ごとに使う特徴量の割合
        # 0.5 だと全体のうち半分の特徴量を最初に選んで, その範囲内で木を成長させる
        'colsample_bytree': trial.suggest_discrete_uniform('colsample_bytree', 0.5, 1.0, .1),
        # 学習データ全体のうち使用する割合
        # colsample とは反対に row 方向にサンプルする
        'subsample': trial.suggest_discrete_uniform('subsample', .5, 1., .1),
        # 木の最大の深さ
        # たとえば 5 の時各弱学習木の各データに対するルールは、最大でも5に制限される.
        'max_depth': trial.suggest_categorical('max_depth', [3, 5, 6, 7, 8]),
        # 末端ノードに含まれる最小のサンプル数
        # これを下回るような分割は作れなくなるため, 大きく設定するとより全体の傾向でしか分割ができなくなる
        # [NOTE]: 数であるのでデータセットの大きさ依存であることに注意
        'min_child_weight': trial.suggest_int('min_child_weight', 5, 40)
    }

文章の埋め込みモデル: Sparse Composite Document Vectors を読んで実装してみた

自然言語処理である単語の意味情報を数値化したいという場合に単語を特定のベクトルに埋め込む(分散表現)手法として word 2 vec があります。 この word2vec と同じような発想で文章自体をベクトル化するという発想があり Doc2Vec やそのたもろもろも方法が存在しています。 今回はその中の一つである SCDV (Sparse Composite Document Vector) を実装したのでその記録です。

そもそも何者か

文章を表現するベクトルを取得する手法です。

どうやってやるか

SCDV はいくつかのフェーズに分かれています。以下では5つのフェーズに分けて説明します。 若干論文の notation と違う所があるのでそこだけ注意していただければと思います。

1. 単語の分散表現を取得する

はじめに文章全体をつかって単語の分散表現を学習します。 Word2Vec や fasttext などが有名なところですね。

以下での話をわかりやすくするため、文章と単語に関してすこし数式を定義します. まず、文章中に現れるすべての単語の数を $N$ とおきます。 そして $i \in \{1, 2, ..., N\}$ 番目の単語に対して分散表現 $w_i \in {\mathbb R}^M$ を得たとします。ここで $M$ は埋め込みベクトルの次元数を表します.

2. 単語を更に複数のクラスタに分類する

1 で取得した単語ベクトルを更に $K$ 個のクラスタに分類します。 このときクラスタリングのモデルとして混合ガウス分布をつかいます。 これによって単語ごとに、先の埋め込みベクトルとは別のクラス分だけのベクトル $p_i \in \mathbb{R}^K$ をえることが出来ます。

混合ガウス分布の学習方法については論文中では特に言及はありませんでしたが、著者の実装では「各クラスタの共分散行列が同じである」という仮定のもとで推定を行うようになっていました。これはおそらく、各クラスタの領域を同じぐらいになるような制限をかけることで各単語の負担率 $p_i$ に極端な偏りがないようにするためなのかなーと考えています。単に計算量を減らすためかもなのであくまで推定ですが。

3. 埋め込み表現とクラス表現を掛けあわせた後に idf をかける

1 で得られた単語の分散表現に 2 で得られたクラスタへの割当を表すベクトルをおのおのかけて各単語ごとに $MK$ 次元のベクトルを作ります。 そして出来上がったベクトルに単語 $i$ の idf 特徴 ${\rm idf}_i \in \mathbb{R}$ を掛けあわせます ここで得られる新しい単語-クラスタ ベクトルを $u_i \in \mathbb{R}^{MK}$ とすると以下のようになります

$$ u_i = {\rm idf}_i \left( \begin{array}{c} p_{i 1} w_i \\ p_{i 2} w_i \\ \vdots \\ p_{i k} w_i \end{array} \right) $$

ここで idf を掛け算しているのは出現回数の多い単語の影響を低くしたいから、です。 とくに 2 でクラスタに分割して次元を多くしたので、出現回数の多い単語が大きい影響度を持つ次元の割合は増えることが予想されるためこの処理は必要なんだろうな、と僕は理解しています。*1

4. 文章ごとに単語クラスタベクトルを足しあわせて正規化する

ここではじめて文章という単位が登場します(ここまでの演算には文章が関係しません)。 $j$ 番目の文章に現れる単語を $L_j$ とし、全部で $J$ 個の文章があるとします。 先ほど得られた単語クラスタベクトルを文章ごとに足しあわせて文章ベクトル $v_j \in \mathbb{R}^{MK}$ を得ます

$$ v_j = \sum_{i \in L_j} u_i $$

さらにこれを各文章ごとに正規化します。論文には After normalizing the vector としか書かれていませんでしたが実装を見るとユークリッドノルムの意味で1になるようにしていました。 それに習うと, 正規化後のベクトルを $\hat{v_j} \in \mathbb{R}^{MK}$ とすると

$$ \hat{v_j} = \frac{v_j}{\| v_j \|_2} $$

となります。

5. ゼロに近いものを 0 に押しつぶす

4 の正規化を終えた時点でじつはほとんどの文章ベクトルの要素が 0 になります。 容量の圧縮や意味の鮮明化の為、ゼロに近いもののうち特定の条件を満たすものをゼロとみなします。具体的には

$$ \hat{v_i} = \begin{cases} \hat{v_i} & {\rm if} | \hat{v_i} | \ge t \\ 0 & {\rm otherwise} \end{cases} $$

とします。ここで $t$ は

$$ \begin{align} t = \frac{p}{100} \times \frac{ | a_{{\rm min}} | + | a_{{\rm max}} | } {2} \\ a_{{\rm min}} = \frac{1}{J} \sum_{j = 1}^{J} {\rm min}\ \hat{v_j} \\ a_{{\rm max}}= \frac{1}{J}\sum_{j = 1}^{J} {\rm max}\ \hat{v_j} \end{align} $$

のように定義される値です。全体の最大最小の $p$ %よりも小さかったら 0 にしましょうということみたいですね。(何故これで良いのかは特に記述がありませんでした。流石にノリで決めては無いと思うんですがよくわかりません)。

どのぐらいいいの?

定量評価のため論文中では SDCV と他の特徴量を SVM で訓練した結果が乗っています。

f:id:dette:20190224031410p:plain
Table1

これを見る限りわかりやすく一番つよいですね。

実装してみる

ここまで読んで楽しそうだったので自分でも実装をやってみました。

github.com

使うのはライブドアニュースのデータセットです。 docker-compose を使うと以下のように環境が作れます。

cp project.env .env
docker-compose build

docker-compose up -d

docker exec -it scdv-jupyter bash

SCDV の作成

作成は src/create.py で行います。著者実装のコードでは単語の分散表現もスクラッチで学習させていましたが今回は学習済みの fasttext 特徴量を用いています。 学習済みモデルは https://qiita.com/Hironsan/items/513b9f93752ecee9e670 よりお借りしました。感謝!!

def main():
    args = vars(get_arguments())

    word_vec = ja_word_vector()

    output_dir = os.path.join(setting.PROCESSED_ROOT)
    n_wv_embed = word_vec.vector_size
    n_components = args['components']

    docs, _ = livedoor_news()
    parsed_docs = create_parsed_document(docs)

    # w2v model と corpus の語彙集合を作成
    vocab_model = set(k for k in word_vec.vocab.keys())
    vocab_docs = set([w for doc in parsed_docs for w in doc])
    out_of_vocabs = len(vocab_docs) - len(vocab_docs & vocab_model)
    print('out of vocabs: {out_of_vocabs}'.format(**locals()))

    # 使う文章に入っているものだけ学習させるため共通集合を取得してその word vector を GMM の入力にする
    use_words = list(vocab_docs & vocab_model)

    # 使う単語分だけ word vector を取得. よって shape = (n_vocabs, n_wv_embed,)
    use_word_vectors = np.array([word_vec[w] for w in use_words])

    # 公式実装: https://github.com/dheeraj7596/SCDV/blob/master/20news/SCDV.py#L32 により tied で学習
    # 共分散行列全部推定する必要が有るほど低次元ではないという判断?
    # -> 多分各クラスの分散を共通化することで各クラスに所属するデータ数を揃えたいとうのがお気持ちっぽい
    clf = GaussianMixture(n_components=n_components, covariance_type='tied', verbose=2)
    clf.fit(use_word_vectors)

    # word probs は各単語のクラスタへの割当確率なので shape = (n_vocabs, n_components,)
    word_probs = clf.predict_proba(use_word_vectors)

    # 単語ごとにクラスタへの割当確率を wv に対して掛け算する
    # shape = (n_vocabs, n_components, n_wv_embed) になる
    word_cluster_vector = use_word_vectors[:, None, :] * word_probs[:, :, None]

    # はじめに文章全体の idf を作成した後, use_word だけの df と left join して
    # 使用している単語の idf を取得
    df_use = pd.DataFrame()
    df_use['word'] = use_words
    df_idf = create_idf_dataframe(parsed_docs)
    df_use = pd.merge(df_use, df_idf, on='word', how='left')
    idf = df_use['idf'].values

    # topic vector を計算するときに concatenation するとあるが
    # 単に 二次元のベクトルに変形して各 vocab に対して idf をかければ OK
    topic_vector = word_cluster_vector.reshape(-1, n_components * n_wv_embed) * idf[:, None]
    # nanで影響が出ないように 0 で埋める
    topic_vector[np.isnan(topic_vector)] = 0
    word_to_topic = dict(zip(use_words, topic_vector))

    np.save(os.path.join(output_dir, 'word_topic_vector.npy'), topic_vector)

    topic_vector = np.load(os.path.join(output_dir, 'word_topic_vector.npy'))
    n_embedding = topic_vector.shape[1]

    cdv_vector = create_document_vector(parsed_docs, word_to_topic, n_embedding)
    np.save(os.path.join(output_dir, 'raw_document_vector.npy'), cdv_vector)

    compressed = compress_document_vector(cdv_vector)
    np.save(os.path.join(output_dir, 'compressed_document_vector.npy'), compressed)

実験

著者の実験では SVM を使っていました。 普段モデリングするときには勾配ブースティング or Neural Network を使うことがおおいので、今回は lightGBM をつかってベンチマークを実装しました。 (実行するスクリプトsrc/SCDV_vs_SWEM.py にあります。)

タスクはライブドアニュースの文章から、そのカテゴリを予測する問題です。 カテゴリは全部で8個あり文章数はおおよそ7000程度です。

戦わせる特徴量は個人的に押しの論文 Baseline Needs More Love: On Simple Word-Embedding-Based Models and Associated Pooling Mechanisms で提案されている Simple Word Embedding Model (SWEM) をつかいます。

SWEM の文章 $k$ の特徴量 $z^k \in \mathbb{R}^{M}$ は以下で表されます

$$ z_j^{k} = \max_{i \in L_k} \ w_{ij}. $$

ここで $w_{ij}$ は単語 $i$ の $j$ 番目の要素であり $L_k$ は $k$ 番目の文章に含まれる単語の添字集合です. 要するに文章中の単語に対して埋め込み次元方向に max をとったものです。 得られる文章ベクトルは単語のものと同じ $M$ 次元になります。

これの n-gram version である SWEM-hier なども提案されていて文章分類などの単純なタスクにおいては CNN や LSTM をつかったリッチなモデルと匹敵、場合によっては勝つ場合もある、単純ですがあまり侮れない特徴量であったりします。

モデルはLightGBM で k=5 の Stratified Fold を行い accuracy により評価します。

結果と感想

Out of Fold の Accuracy を特徴量ごとにプロットしたのが以下のグラフです。

f:id:dette:20190224035914p:plain
学習結果

SCDV の圧縮を行わないバージョンがもっともよいスコアになりました。CV全てで二番目となった SWEM の max-pooling version よりも良い値となりこの特徴量の強さが伺えます。 一方 PCA で圧縮したものはあまりワークしませんでした。これはせっかく混合ガウス分布まで持ちだして拡張したことによって得られる微妙な差分が PCA によって押しつぶされてしまったことが原因と考えられます。

また SCDV は学習がおおげさになるというのも浮き彫りになりました。というのも特徴量の次元が $MK$ なので fasttext の埋め込み次元が 300, 混合ガウス分布の次元が 60 なので各文章ごとに 1800 次元もあるのです。

ですので今回のタスクのような小さい文章セット (1万以下) であっても numpy でなにも考えずに保存すると約 7GB 程度になります。また学習も SWEM に比べて体感 10 ~ 20 倍の時間がかかりました。 以上のことからメモリや計算資源に余裕が有る場合に SCDV を使いさくっとそれなりのベンチマークがほしい時は SWEM という使い分けも良いかも知れません。

まとめ

  • SCDV では単語情報を $M$ 次元ベクトルから更に $K$ 次元の情報を引き出して $MK$ 次元に拡張する。そこから先は普通(たして正規化)
  • livedoor ニュースのデータ・セットを用いた実験では SWEM よりも精度がよかった。が時間とメモリは要る。

*1:SWEMでは特に出現回数に関する考慮はなく精度が出ているため次元拡張の影響を打ち消すという意味合いが強いのかも

Pandas で Index を dictionary で更新したい

pandas のデータフレームで以下のようなものが有るとします。

In [1]: import pandas as pd
In [2]: import numpy as np

In [3]: df_train = pd.DataFrame(data=np.random.uniform(size=(3, 2)), index=['one
   ...: ', 'two', 'three'])

In [4]: df_train
Out[4]: 
              0         1
one    0.289881  0.649603
two    0.229427  0.811377
three  0.498204  0.779105

でこの index を例えば日本語の "いち", "に", "さん" になおしたいなという時。愚かな方法だと index.str.replace につらつらと書いていく方法があります.

df_train.index = df_train.index.str.replace('one', 'いち').str.replace('two', 'に')

でもなんかださいですね。dict で対応関係を記述して, それを元に変換するというふうにコードの責任分離をやりたいところです。

調べた所 pandas.DataFrame.rename を index にたいして使えとありました。

In [5]: en_ja_map = dict(one='いち', two='に', three='さん')

In [6]: df_train = df_train.rename(en_ja_map, axis='index')

In [7]: df_train
Out[7]: 
           0         1
いち  0.289881  0.6496030.229427  0.811377
さん  0.498204  0.779105

おしゃれでいいですね;)

さんこう