🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

1761閲覧

pythonで行列の内積をリスト内包表記を用いて求めるには

esklia

総合スコア81

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

0クリップ

投稿2020/04/04 10:33

編集2020/04/04 12:52

下記コードで行列の内積を求めることができるのですが、
res_mat = [[sum([mat_1[i][j]*mat_2[j][k] for j in range(m)]) for k in range(l)] for i in range(n)]
が何をやっているかはわかる(内積の求め方は知っているので内積を求めていることが分かるということ)のですが、どうやって動作しているかがよくわかりません。自分では多分階層構造が良くわかっていないのだと思います。
それから、
sum([mat_1[i][j]*mat_2[j][k] for j in range(m)])

sum([mat_1[i][j]*mat_2[j][k]) for j in range(m)]
の間違いではないかと思っていたのですが、普通に動作するのでなぜこれで合っているのでしょうか?

追記
イメージ説明

n,m,l = [eval(x) for x in input().split()] mat_1 = [[eval(x) for x in input().split()] for _ in range(n)] mat_2 = [[eval(x) for x in input().split()] for _ in range(m)] res_mat = [[sum([mat_1[i][j]*mat_2[j][k] for j in range(m)]) for k in range(l)] for i in range(n)] print("---------------------------") [print(*res_mat[i]) for i in range(n)] [print(res_mat[i]) for i in range(n)]
teamikl👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

meg_

2020/04/04 10:57

内積の求め方は理解されていますか?
esklia

2020/04/04 12:48 編集

はい。追記しておきましたのでご確認くださいませ
guest

回答3

0

ベストアンサー

こんにちは

ご質問を拝読して、

python

1res_mat = [[sum([mat_1[i][j]*mat_2[j][k] for j in range(m)]) for k in range(l)] for i in range(n)]

によって、2つの行列mat_1mat_2との積行列を求めることができていることを、段階を踏んで示すことが、回答になり得るのではと思ったので、以下はその趣旨による回答になります。

ちなみに、いきなり上記の式が出てきても、これが行列の積として正しいのかは、ぱっと見ただけでは分からない、というのはごく自然だと思います。このような場合、どうしたらよいかというと、行列の積を求めるプログラムを、初めはリスト内包といった可読性が損なわれる(場合もある)記法を使わずに、forループを使ったり適宜、まとまった処理を関数に切り出すなどして愚直に書いていって、愚直でもロジックとしては納得がいくコードが完了したら、それを詰めていって冒頭のようなコードに近づけていけばよいかと思います。

そこで、この回答では、以下のようなコードを含む main.py から始めます。

python

1n, m, l = 3, 2, 4 2 3mat_1 = [ 4 [1, 2], 5 [2, 4], 6 [3, 6] 7] 8 9mat_2 = [ 10 [10, 20, 30, 40], 11 [20, 30, 40, 50] 12] 13 14res_mat = None 15 16print(res_mat)

上記は、3行2列の mat_1 と 2行4列の mat_2 の積 res_mat を求めることを意図しています。なお、この回答で作成した main.py は、

に上げてあるので、お手元で試す際には、cloneするなりforkするなりご利用ください。上記の最初の main.py は、

で作成しています。以下、段階を踏んで、main.py を実装していきます。

(1) res_mat は、 n行l列の行列になるので、とりあえずn×l のゼロ行列にしておきます。

python

1res_mat = [[0 for col_index in range(l)] for row_index in range(n)]

(2) 積の行列の成分を算出する関数 dot_product_element を作ります。

関数の名前の意図は、2つの行列、mat_amat_b の積の行列の(ri, ci)成分ということです。とりあえずゼロを返すようにしておきます。

python

1def dot_product_element(mat_a, mat_b, ri, ci): 2 return 0 3 4 5res_mat = [[dot_product_element(mat_1, mat_2, row_index, col_index) for col_index in range(l)] for row_index in range(n)]

(3) dot_product_element をforループで作ります。

dot_product_elementを実装しますが、初めから内包表記を使ったりせずに、愚直なコードで書きます。eskliaさんもご理解のとおり、行列AとBの積の (i, j)成分は、Aのi行の行ベクトルと、Bのj列の列ベクトルとの内積なので、内積の定義どおりに、各対応成分の積の合計を出すように for ループで書きます。合計値の変数s を初期値0で用意して、ループするごとに加算していきます。

python

1def dot_product_element(mat_a, mat_b, ri, ci): 2 s = 0 3 for i in range(len(mat_a[ri])): 4 s += mat_a[ri][i] * mat_b[i][ci] 5 6 return s

ここまで出来ると、とりあえず積の行例を求めることができ、main.pyを実行すると、結果として以下が表示されます。

[[50, 80, 110, 140], [100, 160, 220, 280], [150, 240, 330, 420]]

この後は、コードを詰めていき、冒頭のコードに近づけていきます。

(4) 成分ごとの積の合計を求めるのに、sum() を使うように修正します。

python

1def dot_product_element(mat_a, mat_b, ri, ci): 2 products = [] 3 for i in range(len(mat_a[ri])): 4 products.append(mat_a[ri][i] * mat_b[i][ci]) 5 6 return sum(products)

(5) リスト内包表記を使用して、さらにdot_product_elementを詰めます。

上記(4) のコードを、リスト内包表記を使用して詰めると以下になります。

python

1def dot_product_element(mat_a, mat_b, ri, ci): 2 return sum([mat_a[ri][i] * mat_b[i][ci] for i in range(len(mat_a[ri]))])

(6) 関数 dot_product_elementで行っていることを、res_matを得る式に直接書いて、dot_product_elementを削除します。

python

1res_mat = [[sum([mat_1[row_index][i] * mat_2[i][col_index] for i in range(len(mat_1[row_index]))]) for col_index in range(l)] for row_index in range(n)]

(7) 式および変数名を修正します。

len(mat_1[row_index])mat_1 の列数なので、m で置き換えることができます。

さらにいくつかの変数名を置き換えます。

-"i" to "j"
-"row_index" to "i"
-"col_index" to "k"

上記によって、ご質問にあるのと同じ

python

1res_mat = [[sum([mat_1[i][j] * mat_2[j][k] for j in range(m)]) for k in range(l)] for i in range(n)]

が得られました。

最後に得られた main.py を実行しても(3)の段階で得られた結果と同じ

[[50, 80, 110, 140], [100, 160, 220, 280], [150, 240, 330, 420]]

が得られますが、これがそもそも正解なのか?というのも確かめたいので、念のためnumpy の dot で検算します。

$ python Python 3.7.4 (default, Aug 13 2019, 15:17:50) [Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import numpy as np >>> mat_1 = [ ... [1, 2], ... [2, 4], ... [3, 6] ... ] >>> >>> mat_2 = [ ... [10, 20, 30, 40], ... [20, 30, 40, 50] ... ] >>> np.dot(mat_1, mat_2) array([[ 50, 80, 110, 140], [100, 160, 220, 280], [150, 240, 330, 420]]) >>>

合ってました。

eskliaさんがリスト内包表記に慣れていないとすると、上記のステップのうち(4)から(5)にコードを詰めるところで、「どうしてこうなるのか?」という疑問が湧くかもしれませんが、for文でリストを作るのを、内包表記で書きかえるのは慣れの問題かなと思います。初めは for で書いて内包表記に書き直すということ意識的に心がけるとよいかもしれません。

以上、参考になれば幸いです。

投稿2020/04/05 09:24

jun68ykt

総合スコア9058

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

esklia

2020/04/07 10:34

ご回答くださりありがとうございます。まずは簡単なforループで内積を作成してみて、のちにそれを内包表記として実装するというのは原始的ですが今の私にはかなり有効な方法かもしれません。おっしゃる通り最初は簡単なforループから実装することを心がけてみようと思います。説明のために時間を割いてくださり感謝申し上げます。
jun68ykt

2020/04/08 08:59

どういたしまして。このTeratail でも、Pythonの質問に対する熟練の回答者様からの回答の中には、複雑なロジックを巧みに内包表記に入れ込んでいるものをしばしば見かけます。そういったコードから私も勉強させて頂いておりますが、そのような回答を見かけたら、それを 内包表記は使わずに、for 文にしたり、あるいはlambda が使われていたら個別の関数にするなどして、「分解」してみて、分解しきった状態から再度、ご自身で内包表記で書いていき、元のコードに戻すという手作業を何回かやったりすると、カン所がつかめると思います。
guest

0

[[sum([mat_1[i][j]*mat_2[j][k] for j in range(m)]) for k in range(l)] for i in range(n)]

sum([mat_1[i][j]*mat_2[j][k] for j in range(m)])は内積を計算
for k in range(l)はmat_2の列方向の処理
for i in range(n)はmat_1の行方向の処理
だと思います。

sum([mat_1[i][j]*mat_2[j][k] for j in range(m)])については質問の追記、「[(a00b00) + (a01b10)]」になるので「sum[(a00b00),(a01b10)]」で良いかと思います。

ネストした内包表記は難しいです。
変数の順番を入れ替えてみたり、[]の位置を変更してみたり色々してみると理解が深まるかもしれません。

投稿2020/04/04 15:56

meg_

総合スコア10744

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

esklia

2020/04/05 06:03 編集

ご回答くださりありがとうございます。実装例も載せて下さり大変勉強になります。 仰る通りあくまで内積を求めているので、コードをずっと見ていたらなんとなくコードの意味も分かるようになってきました。 二重三重のループは書いていてしんどいので内包表記に挑戦したのもありますが、こちらも慣れが必要ですね…ちなみに補足なのですが、内包表記の`[]`の役割は「ループの区切りを示すことと処理内容をリストにすること(なので辞書内包表記なら{}でくくりますよね)」という解釈でおおむねあっていますか?
meg_

2020/04/05 06:46

内包表記は簡潔に書けるのと、実行時間が短いことが多いので使われるのだと思います。 リスト内包表記を耳にすることが多いですが、辞書内包表記とかセット内包表記とかもありますね。https://tiginkgo.hateblo.jp/entry/20180911/1536660772
guest

0

理解の助けになるかもしれないことを列挙しておきます。

  • 普通のforループで書き直しましょう。内包表記は目が慣れないときついです。

なお、基本的には以下のような方針で機械的に書き直せます。

python

1# 内包表記バージョン 2result = [x for x in range(10)] 3 4# 上をforループで書き直したもの 5result = [] 6for x in range(10): 7 result.append(x) 8 9# 多重の場合 10result = [[x * y for y in range(5)] for x in range(10)] 11 12# 上をforループで書き直したもの 13result = [] 14for x in range(10): 15 tmp = [] 16 for y in range(10): 17 tmp.append(x * y) 18 result.append(tmp)
  • 内積は難しすぎるので、もう少し簡単な例をいろいろ試してみましょう。当分は内包表記ではなくループバージョンで。

の間違いではないかと思っていたのですが、普通に動作するのでなぜこれで合っているのでしょうか?

なぜ間違いではないかと思ったのでしょうか?

現状だと「はい」というだけの回答になります。より実り豊かなコミュニケーションを求めるのであれば、間違いではないかと思った理由を言語化して説明する責務が質問者さんにはあります。

投稿2020/04/04 14:27

編集2020/04/04 14:27
hayataka2049

総合スコア30935

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

esklia

2020/04/05 05:57 編集

ご回答くださりありがとうございます。実装例も載せて下さり大変勉強になります。 自ら実装するのは難しいですが、コードの意味はみてだいたい分かるようになりました。 コードを短く書ける内包表記が自在に書けることが一つの指標であるとの意見を聞いたので内包表記に挑戦したのですが、おっしゃる通りまだ早かったのかもしれません。 個人的な事情で恐縮ですが、for i in range(hoge):のrange(hoge)で行列を分解していくときにrange(hoge)というリストが実際にはただのリストであるにもかかわらず意味を持ったものだと解釈してしまったのでforループの意味が分からなくなってしまったのだと思います(for i in range(hoge)のrange(hoge)は行列の行や列を参照しているが、あくまで数字であるのでそれ自体意味は持たないということです、分かりにくくてすみません)。 sum()については言語化するのが難しく、というか私の方で諦めてしまい回答者様に負担をかけてしまいました。今後は可能な限り具体的に質問をするよう心掛けます。申し訳ございません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問