Pascal VOCのフォーマットに関する調査
anntのpythonライブラリにpascalVOCの読み込み&書き込み機能を実装を検討中。 実装にあたって具体的な仕様を調べてみたもののあまり厳密に決まってなさそう。 調べてなんとなくわかったこととかメモなどを記載していく。
調べるにあたって参考にした資料
フォーマットの例
Pascal VOC 2007から抜粋したxmlファイルの例を記載する。
<annotation> <folder>VOC2007</folder> <filename>voc2007.jpg</filename> <source> <database>The VOC2007 Database</database> <annotation>PASCAL VOC2007</annotation> <image>flickr</image> <flickrid>325991873</flickrid> </source> <owner> <flickrid>archintent louisville</flickrid> <name>?</name> </owner> <size> <width>500</width> <height>375</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>chair</name> <pose>Rear</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>263</xmin> <ymin>211</ymin> <xmax>324</xmax> <ymax>339</ymax> </bndbox> </object> <object> <name>chair</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>165</xmin> <ymin>264</ymin> <xmax>253</xmax> <ymax>372</ymax> </bndbox> </object> <object> <name>chair</name> <pose>Unspecified</pose> <truncated>1</truncated> <difficult>1</difficult> <bndbox> <xmin>5</xmin> <ymin>244</ymin> <xmax>67</xmax> <ymax>374</ymax> </bndbox> </object> <object> <name>chair</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>241</xmin> <ymin>194</ymin> <xmax>295</xmax> <ymax>299</ymax> </bndbox> </object> <object> <name>chair</name> <pose>Unspecified</pose> <truncated>1</truncated> <difficult>1</difficult> <bndbox> <xmin>277</xmin> <ymin>186</ymin> <xmax>312</xmax> <ymax>220</ymax> </bndbox> </object> </annotation>
フォーマットの説明
1つのファイルに対して1つのアノテーション情報を記載したxmlファイルが用意されている。 xmlファイルは'annotation'タグで始まっていて、直下にメタデータに関するタグや、具体的なアノテーション情報が記載されているobjectタグなどが配置されている。
タグ名 | 説明 |
---|---|
folder | 画像が格納されているフォルダ名 |
filename | アノテーションした対象の画像ファイル名 |
source | (不明)情報源に関する情報? |
owner | (不明)画像の所有者に関する情報? |
size | タグ名通り画像サイズに関する情報(幅、高さ、チャネル数) |
segmented | (不明)0のことが多い気がする。 |
object | 肝心のアノテーション情報。詳細は次の段落で説明 |
objectタグについて
objectタグには肝心な画像内部に写っている物体の情報が記載されています。 各タグの説明は上述のガイドラインに従って記載しています。
タグ名 | 説明 |
---|---|
name | 写っている物体のクラス名 |
pose | おそらくアクション分類タスク用の人物の行動を表すタグ |
truncated | 物体が15~20%以上画像からはみ出しているか |
difficult | 認識するのが難しいオブジェクト。e.g. 理解に文脈が必要な画像等 |
occluded | 2008以降追加 物体の5%以上が何かに隠れているか |
bndbox | バウンディングボックスの位置情報 |
part | (不明)ポーズに関する情報? |
actions | 写っている人物の行動に関する情報。行動推測タスク用 |
point | (不明) |
殆ど不明となってしまった。明確なフォーマットが見つからずライブラリ作るのが大変そう。正直使う上で必要なタグは限られているので不要な月は実装しなくて良い気がする。 適宜、修正・追記していく予定。
AtCoder色変記事(緑)。あるいは趣味としての競プロの勧め。
水パフォ叩き出して入緑しました!めっちゃ嬉しい
— Kei-Suke (@toritoritori29) 2020年11月8日
toritoritori29さんのAtCoder Beginner Contest 182での成績:1031位
パフォーマンス:1440相当
レーティング:760→859 (+99) :)
Highestを更新し、6 級になりました!#AtCoder #ABC182 https://t.co/bTHL1c1rZ0
何番煎じか分かりませんがこの度晴れてAtCoderで緑帯に突入することが出来ました。 もう巷には緑になるために必要な修行方法が溢れているので、「緑になるために何をしたか」ではなくて、「趣味としての競プロ楽しいよ!」という内容にフォーカスを当てて記事を書こうかなと思います。
簡単に自己紹介
院卒社会人2年目です。理系だったものの学科も勤め先もITやプログラミングとは程遠くプログラミングは完全趣味でやっています。大学時代はコツコツ独学でAIなどの勉強をしてました。その延長でkaggleなどにも参加していましたが、あまりまとまった時間が取れずメダルを取ること無く引退してしまいました。
競プロに出会ったきっかけは覚えていませんが、初めて参加したきっかけは腕試し目的でした。独学ですがそれなりにプログラムを数書いてきた自負があったので緑くらい数回参加すればなれるでしょ、と思っていましたが初参加(確か企業コン)で現実を思い知らされました。以降悔しさから積極的に参加するようになって今では虜です。
競プロって何?
自分が説明するよりWkipediaを見ていただいたほうが正確で早いです。
あえて自分の言葉で説明すると、「数問の問題が出されるので、コンテスト時間内に問題を解くプログラムを正確かつ素早く解く競技」になるんでしょうか。AtCoder社が開催するABCというコンテストを例にすると、100分という制限時間の6問(A~F)の問題が出題されるので、自らの知識・技術・経験を総動員してできる限り多くの問題を素早く解くことを競います。1問目(A問題)は非常に簡単ですが、後半になると深いアルゴリズムの知識と考察力が必要とされるので全問解けるのは多くの場合参加者のごく一部です。
コンテストサイトによって細部は異なるかもしれませんが、大手のコンテストサイトには実力を示すレーティングの仕組みがあります。レーティング帯に対応する色が定義されていて、この色によってざっくりとその人の実力が分かるようになっています。AtCoderだとレーティング0~400が灰、400~800が茶、800~1200が緑と400ごとに区切りが設定されていて、初心者は多くの場合灰〜茶帯を彷徨うことになります。
競プロの魅力
で、それって面白いの?という話ですが、めちゃくちゃ楽しいです。楽しすぎて最近はABCには殆ど毎回欠かさず参加しています。これまで何で競プロが楽しいと思うのか?について言語したことがなかったので何とか言葉にしてみました。
達成感
個人的にはこれが一番のモチベーションです。おそらくですが最初数回のコンテスト参加で適正なレーティングに配置されます。アルゴリズムを少し齧ったことがある、程度だと多くの場合良くて茶色になるのではないかと思います。一度自分の適正レーティングに到達したあとに次の色を上げるためにはかなりの努力が必要です。
コンテストに参加しても思うように解けずレートが下がって(よく冷えるとか言われてます)ぐぬぬ...となります。Twitterで他の人が色変してると嫉妬で血の涙が出そうになります。それでも負けじとコンテストに参加し続けて次の色に到達したときはめちゃくちゃ嬉しいです。アドレナリンだかエンドルフィンだか分からないですけど脳内で謎の汁がいっぱい出ます。緑になったときは無意味にシャドーボクシングしてました。
努力が身になっているのが実感できる
競プロはコンテスト自体は2時間程度です。それ以外の時間は過去問を説いて勉強したり、解けなかった問題を振り返ったりという修行の時間になります。コンテスト自体は頻繁に開催されているので、努力したら努力した分見える形で結果として帰ってきます。最初自分は数学系の問題だったり幅優先探索系が苦手過ぎてコンテストの度に絶望する日々を送っていたのですが、精選100問やったり類題解きまくったりしてると「はいはいこのパターンね」みたいな感じになって俺TUEEEEE(弱い)が出来ます。
短期間で努力の結果がフィードバックされるのはモチベーションの維持としては良い仕組みだなーと思って修行してます。kaggleの数ヶ月に渡るマラソン的なコンテストと違って競プロは短時間なので忙しくて修行できなくても、落ち着いた段階で気楽に再開できるのも良い点だなと思ってます(この辺は個々人の好みによるところが大きいかもしれないです)。
感情を揺さぶられる
上2つの項目で書いたことの言い換えになってしまっているところはありますが、コンテストに参加すると感情が揺さぶられます。今までD問題までしか解けなかったときにE問題まで解けたときは嬉しくて情緒不安定になりました。逆にC問題が解けずレートが下がったときに回答を見たらめちゃくちゃ簡単な問題だったときは辛くて情緒不安定になりました。こんな感じでコンテストに参加していると喜怒哀楽大体の感情が楽しめます。コンテストに参加することによって平坦な生活に刺激が発生します。
コンテスト終わったあとにTwitter眺めているのも楽しいです。コンテスト後は喜んでる人、落ちこんでる人、解説してくれる人、いろんな人が居てお祭りみたいな感じです。普段Twitter見ないだけかも知れませんが。
社会人にこそおすすめの趣味
なんか参加している人学生が多そうだし社会人になってから始めるのもな...と感じているかも知れません。というか自分がそうでした。大して時間も取れないし学生に遅れを取りそうと思っていました。でも実際に初めて見ると社会人の方もたくさんいらっしゃいますし、時間的な面もあまりハンデになっていないなと思っています。
AtCoderのコンテストであれば大体土日の夜開催
初心者向けのABCを始めとしてAtCoderはだいたい土日の夜に開催されます。飲み会とかをセットしない限り独身であれば高確率で参加できます。突如コンテストが生えることもありますが、基本的に数日前には開催がFIXしてるのでそれなりに予定も組みやすいです。
隙間時間でも修行できる
修行するしないは個人の自由なので、仕事終わりに一日一問みたいな感じに練習してみてもいいですし、土日にまとめて修行みたいな感じでも好きなようにできます。自分はやる気がある日にぐぉーって自分の適正より難しい問題を数問説く感じでやっています。
というような感じで割と柔軟にできます。そういう意味ではまとまった時間が取りにくい社会人にこそおすすめの趣味なのでは?と思っています。
実利的な話をするとAtCoder Jobsなどで転職の助けになったりすることもあるみたいです。あとアルゴリズムの知識が仕事の役に立つかもしれないです。競プロは仕事の役に立たない!という意見も見かけますが、職場に寄るところが大きい気がします。「役にたたない...」と思ってやるよりは「役に立つ!」と思ってやったほうが確実にモチベーションは繋がるので役に立つと思っておくのがいいかと思います。
始めてみたい!
始めましょう。まずは日本語で参加できるAtCoderのコンテストがおすすめです。初回参加して絶望したら先人の色変記事を読みましょう。やるべきことが見えてきます。おすすめの記事を貼っておきます。
ntk-ta01.hatenablog.com qiita.com qiita.com
終わりに
もう色変記事じゃない気がしてきました。それにAtCoderの回し者みたいになってしまいました。この記事を見てAtCoder見て始めてくれる人がいたら嬉しいです。こんな楽しみ方もあるよ!という意見があれば是非記事にしてください!
多角形の内外判定(javascriptでの実装を添えて)
アノテーションツールでセグメンテーション用に多角形での範囲選択を実装しようとしている。 四角形の場合には簡単に内外判定ができるが、多角形だとそう簡単にはいかない。 今回実装にあたって多角形の内外判定を勉強した。
TL;DR
- Winding Number Algorithmは割と楽に実装できる
- 実装するときは角度の符号の計算を忘れない
はじめに参考資料
理論だったり詳しい実装のポイントは以下の資料を見てください。 この資料を見て理解できればこの記事は読まなくて大丈夫です。
Winding Number Algorighmとは
多角形の角2つと対象となる点が成す角度を順番に計算していき、それらの和が360°以上であれば(一周以上するのであれば)点は多角形内部にある。 一方で和が0であれば、点は多角形外部にある。
最初角度の和が0になればってどういうこと?と悩んだが、角度を符号つきで計算すると外部にあるときは角度同士がキャンセルしあって和は0になるよ、と理解すれば良さそうだ。
「360°以上」というところは自己交差領域があると和が一周以上になる場合が出てくる(上記のNTTの資料参考)。あまり数学的なところは理解できないが他にありえるのだろうか。
実装する上での注意点
角度を符号付きで計算することを忘れなければ実装はかなり簡単。符号の計算を忘れるとうまく動かなくて沼にハマる。 角度の絶対値はベクトルの内積を使えば簡単に計算できる。符号は外積を計算して、その符号だけを取ればOK。
javascriptでの実装
コメントを多めに入れたので長くなったが、やっていることは非常に簡単。 バグがあれば教えてください。
/** * 指定した点が多角形の中に含まれるかどうかを判定する * * @param {Array} polygon 多角形を構成する点(X, Y)の配列. 多角形を一周するように並んでいる必要がある. * @param {Number} x 判定する点のX座標 * @param {Number} y 判定する点のY座標 * @returns {Boolean} 点が多角形に含まれていればtrue */ function hit(polygon, x, y) { let thetaSum = 0; const n = polygon.length; // i-1 => 指定された点 => i の成す角度の和をthetaSumに足し込んでいく for (let i = 1; i < n; i++) { if (polygon[i][0] === x && polygon[i][1] === y) { // 指定された点が多角形の角の場合うまく角度が計算できないので、判明した時点でtrueを返す return true; } const v1x = polygon[i - 1][0] - x; const v1y = polygon[i - 1][1] - y; const v2x = polygon[i][0] - x; const v2y = polygon[i][1] - y; thetaSum += computeDegree(v1x, v1y, v2x, v2y); } // 0とN番目の成す角度 if (polygon[0][0] === x && polygon[0][1] === y) { return true; } const v1x = polygon[n - 1][0] - x; const v1y = polygon[n - 1][1] - y; const v2x = polygon[0][0] - x; const v2y = polygon[0][1] - y; thetaSum += computeDegree(v1x, v1y, v2x, v2y); thetaSum = Math.abs(thetaSum); if (thetaSum >= 0.1) { return true; } return false; } /** * 2つのベクトルの成す角度を符号付きで計算する * 角度の絶対値の計算には内積を、符号の計算には外戚を利用する * * @param {Number} x1 * @param {Number} y1 * @param {Number} x2 * @param {Number} y2 */ function computeDegree(x1, y1, x2, y2) { const abs1 = Math.sqrt(x1*x1+y1*y1); const abs2 = Math.sqrt(x2*x2+y2*y2); let theta = Math.acos((x1*x2+y1*y2)/(abs1*abs2)); // 内積を使って角度を計算 let sign = Math.sign(x1*y2-y1*x2); // 外積を使って符号を計算 theta *= sign; return theta } function main() { const pol1 = [[0, 0], [100, 20], [100, 120], [0, 100]]; // 通常の判定 console.log(hit(pol1, 50, 50)); // true console.log(hit(pol1, 101, 50)); // false console.log(hit(pol1, 100, 121)); // false // コーナーケース(多角形の辺上の場合) console.log(hit(pol1, 50, 10)); // true console.log(hit(pol1, 100, 60)); // true // コーナーケース(多角形の角上の場合) console.log(hit(pol1, 0, 0)); // true console.log(hit(pol1, 100, 20)); // true } main();
anntを大きくリファクタした
前公開したanntをちまちま改修してたのでリリース
前:
後:
主な改修内容は
- Google Drive対応
- 内部のリファクタリング
- デザインの変更
の3点
Google Drive対応
Google DriveはDropboxと違って、階層構造のファイルシステムじゃないので実装が手探りになってしまった。 Google Driveは普段使い慣れているWindowsやMacみたいにパスでファイルやフォルダを指定できない。(フラットファイルシステムというらしい) 各ファイルが親フォルダの情報を持っているので、SQLのSELECT~WHERE文みたいなイメージで指定したフォルダを親とするファイルをリストアップする。
深いパスのファイルを取得しようとすると階層分だけAPIを呼び出さないといけないので、余計なオーバーヘッドが発生している気がする。 Dropboxに比べて遅いのと、若干バグが多いのでここは今後の課題。 あとプライバシーポリシー作ってないので、OAuthの認証画面でやばいですよアピールされるのも課題
内部的なリファクタリング
リファクタリングをやって大分今後の改修がしやすくなった。 今後多角形のアノテーションの実装を検討しているが、以前までの絶望感が無い。 この調子なら割とすぐに実装できそうなので、次の課題にしてみようと思う。
デザイン的な変更
デザインの変更は前からの課題だったので、割と大きく直したつもりでいる。 前回のデザインをださいださいと思ってて直したが、直しても相変わらずしょっぱい気がしている。 バウンディングボックスがすごい見ずらいのでこれもどうにかしたい。
小さな改善点としてはタブ操作だけでタグを選択できるようになった点。 Vue使えばかんたんなインクリメントサーチを本当に楽に実装できる。 公式チュートリアルに例があるので、興味ある型はそちらを参照。
今後もちまちま直していこうと思う。
twitterはじめました ⇒ @toritoritori29
Google Drive APIでファイルをアップロード - Multipart/Related
前回に引き続きGoogleDriveAPIのお話です。 今回はファイルの新規アップロードについてです。
createエンドポイント
ファイルを新規にアップロードするにはcreateエンドポイントを使用します。
https://developers.google.com/drive/api/v3/reference/files/create
このエンドポイントが曲者で、アップロード時に3つのアップロードタイプから一つを選んで明示的に指定する必要があります。
現実問題としてメタデータ無しでファイルをアップロードしたいという局面は殆ど無いと思われます。Google Driveではフォルダの階層構造をファイルのメタデータで管理しているため、メタデータ抜きでは親ディレクトリの指定すらできません。
ということで多くの場合においてアップロードタイプとしてmultipartかresumableを指定することになるでしょう。複数回リクエストを投げるよりは一回のリクエストで片付けてしまいたいので、自分の中でmultipartを使ってみようということになりました。ところがこのmultipart、RFC 2387というプロトコルに則ってリクエストを作成する必要があります。日本語の文献もほぼ無いということで素直にRFCを流し読みしました。
RFC 2387 The MIME Multipart/Related Content-type
複数の異なるMimeTypeのデータをまとめてアップロードするための「Multipart/Related」というMimeTypeについてまとめてたものです。あまりしっかりと読めているわけではないですが、要点は以下の通りとなっています。
- Content-TypeでMultipart/Relatedを指定し、同時にBoundary(境界文字列)も指定する。
- リクエストボディは"--"+Boundaryで区切って、異なるデータのリクエストボディを指定する。
- リクエストの終わりは"--"+Boundary+"--"で締める
これだけだと抽象的で少し分かりづらいですが、RFCの原本に載っている例がわかりやすいです。
Content-Type: Multipart/Related; boundary=example-1 start="950120.aaCC@XIson.com"; type="Application/X-FixedRecord" start-info="-o ps"
--example-1 Content-Type: Application/X-FixedRecord Content-ID: <950120.aaCC@XIson.com> 25 10 34 10 25 21 26 10 --example-1 Content-Type: Application/octet-stream Content-Description: The fixed length records Content-Transfer-Encoding: base64 Content-ID: <950120.aaCB@XIson.com> T2xkIE1hY0RvbmFsZCBoYWQgYSBmYXJtCkUgSS BFIEkgTwpBbmQgb24gaGlzIGZhcm0gaGUgaGFk IHNvbWUgZHVja3MKRSBJIEUgSSBPCldpdGggYS BxdWFjayBxdWFjayBoZXJlLAphIHF1YWNrIHF1 YWNrIHRoZXJlLApldmVyeSB3aGVyZSBhIHF1YW NrIHF1YWNrCkUgSSBFIEkgTwo= --example-1--
HTTP本体のリクエストヘッダにはMultipart/Relatedを指定しています。startなどはどのデータから読み込めばいいかを示す情報のようです(それっぽいことが書いてありましたが良く理解できませんでした)。リクエストボディの各セクションでは、それぞれのデータに対して改めてヘッダを定義しています。
javascriptでの実装
Multipart/Relatedのリクエストボディを生成する関数を作成した。この関数でエンコードした文字列をリクエストボディにして、リクエストヘッダにはMultipart/Relatedを指定すれば
/** * 引数で指定したオブジェクトをMultipart/Relatedのリクエストボディに変換 * 以下のような配列を第一引数として指定する必要がある * e.g) * let objects = [ * { header: ['Content-Type': 'text/plane'], body: '内容1' }, * { header: ['Content-Type': 'text/plane'], body: '内容2' }, * ] * * @param {Array} エンコードしたいオブジェクトのリスト * @param {String} 境界文字列 * @returns {String} エンコード済みの文字列 */ function encodeMutipart(objects, separator) { let encoded = ''; for (let o of objects) { encoded += `--${separator}`; encoded += '\n'; Object.keys(o.headers).forEach((key) => { encoded += key; encoded += ': '; encoded += o.headers[key]; encoded += '\n'; }); encoded += '\n'; if (typeof o.body === 'object') { encoded += JSON.stringify(o.body); } else if (typeof o.body === 'string') { encoded += o.body; } encoded += '\n'; } encoded += `--${separator}--`; return encoded; }
Google Drive APIでパスを指定してファイル一覧を取得する
Google Drive APIで心が折れた
指定したパスのファイルが存在するか知りたいだけだった。Google Drive Api V3のリファレンスを読んでもあまりファイルパスに関する記述が殆ど見当たらない。クラウドストレージサービスのリファレンスでそんなことあるかと思って調べた。
Try to stop thinking in terms of file paths. Google Drive is a flat filesystem, where "parent" is simply an attribute, a bit like a tag. A file can have many parents, and so could be on many paths.
Google Driveはフラットファイルシステムなので、ファイルパスで考えるのはやめましょう。"親フォルダ"は単なるタグのような属性です。ファイルは複数の親フォルダを持つ可能性がありますし、複数のパスを持つ可能性があります。
Flat Filesystemって何や...って調べてみると英語版ウィキペディアの項目に小さくFlat Filesystemという項目がある。単純に階層構造の無いファイルシステムのようだけど何かしらのメリットがあるのだろうか... Amazon S3も同様の仕組みって書いてあるので大規模クラウドストレージ特有の問題に対応しやすいということだろうか。
要するに指定したパスにファイルが存在するかどうか確認したい場合には再帰的にパスをたどっていくしか無さそう。
listエンドポイント
https://developers.google.com/drive/api/v3/reference/files/list
ファイルのリストを取得するためにはlistエンドポイントを仕様する。
パスで指定するのではなく、qとfiledsパラメータを駆使して全てのファイルの中から必要なデータをフィルターするイメージ
qパラメータ
取得したファイル一覧をフィルタリングするためのクエリです。サポートしている文法を確認するためには"Search for files"を確認してください。
fieldsパラメータ
レスポンスに含まれるパスとフィールドを指定します。指定されなければレスポンスはこのメソッドのデフォルトの集合を返します。開発の際にはすべてのフィールドを返す記号*を使えますが、必要なフィールドだけを指定した方がパフォーマンスが優れています。
qで取得するファイルをフィルターして、fieldsで取得する項目(ファイル名、更新日等々)の情報を絞り込む。
javascriptで指定したディレクトリのファイル一覧を取得
javascriptで指定したディレクトリのファイル一覧を取得する。予め何らかの方法でGoogleのOauth2トークンを取得しておく必要がある。 __listDirは補助関数で本体はlistDir関数となっている。
const axios = require('axios'); const API_KEY = '---- Your API Key -----'; const MIMETYPE_FOLDER = 'application/vnd.google-apps.folder' /** * GoogleDriveからファイルのリストを取得する(補助関数) * * @param {String} token * @param {String} parent - 上位フォルダのID * @returns {Object} Google Drive APIからのレスポンス */ async function __listFiles(token, parentId) { const url = 'https://www.googleapis.com/drive/v3/files'; const options = { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json', }, params: { key: API_KEY, fields: 'files(id, name, mimeType)', q: `"${parentId}" in parents`, }, }; let resp = null; try { resp = await axios.get(url, options); return resp.data.files; } catch (error) { throw error; }; } /** * GoogleDriveからファイルのリストを取得する * * @param {String} token - OAuth2トークン * @param {String} path - ディレクトリのパス * @returns {Object} 指定したディレクトリ内のファイル一覧 */ async function listFiles(token, path) { const sepPath = path.split('/'); let files = null; try { files = await __listFiles(token, 'root'); } catch(error) { throw error; } for (let i=0; i < sepPath.length; i++) { let nextId = null; for (let e of files) { if (sepPath[i] === e.name && e.mimeType === MIMETYPE_FOLDER) { nextId = e.id; break; } } if (nextId) { try { files = await await __listFiles(token, nextId); } catch (error) { throw error; } } } return files; }
一個のファイルの情報を確認するためだけに数回のリクエストが必要になる。可能な限り過去のリクエストをキャッシュして通信回数を減らすのがいいのか。
Google Technical Writing Part 1を読み始めた
英語で技術よりの文書が書けるようになりたくて、Google Technical WrtingのPart 1を読み始めた。
内容は英語で技術文書を書くためのエッセンスを詰め込んだものになっている。読んでいてあたりまえじゃん!って思ったが、実際に過去に自分が書いた文章を見ていると全然出来てなくて驚く。
今のところ「Technical Writing One」〜 「Short Sentence」まで読んだので各章のメモ。
Word(単語)
色々なトピックに関して触れていたので箇条書きで。
- 文章内で単語の一貫性を保つ
- 単語を明確に定義する
- よく知られるものであれば単語の定義のリンクを貼る
- そうでないなら明確に定義する。量が多いなら用語集を作る。
- 略称を使う場合には初回だけ正式名称(略称)という表現を使う
- そもそも略称を使うべきか判断する(長さと頻度を判断基準に)
- 代名詞は混乱を招くので使用を控える
- 代名詞を使うのであれば明確な場合にのみ使う
メモ
英語だけでなく日本語で書く場合にも使える普遍的な内容だと思う。一貫性、定義、略称は混乱のもとなので普段から気を付けたい。 代名詞は日本語だと英語に比べて使用頻度が低い気がするが気を付けるに越したことはないと思う。
Active Voice(能動態)
能動態と受動態どちらを使うべきかという話。可能な限り能動態を使うべきという結論だった。理由としては
- 読者は暗黙的に脳内で能動態に変換している。
- 能動態に比べて読みづらい。能動態のほうが直接的に表現している。
- 短い
- 主語が抜ける場合があって明瞭性に欠ける
メモ
なるほどという感じだった。英語力が低くて逃げの受動態を使っていたが、載ってる例などを見ていると能動態のほうが圧倒的にわかりやすい。
日本語の場合だとどうなんだろうと考えてみた。あまり普段から能動態と受動態を意識したことが無いが、やはり能動態で書いた方がわかりやすいことが多いのかなと思う。日本語は能動態、受動態に関わらず主語が抜けることがあるので、そこが注意するべき点ではないだろうか。
Clear Sentences(明快な文章)
あまり読み込めていないが主なトピックは以下の通り。
- 強い動詞を使わない
- Thereを使わない
- あいまいな形容詞・副詞を使わない
強い動詞というのはhappenやoccurなどの普遍的で使いやすい単語を使うのではなくて、triggerやgenerateなどの使われる場面が限られている=情報量が多い動詞を使うべきと理解した。
メモ
強い動詞を使うという主張は理解しているが、語彙力が無くて普遍的な単語しか使えないという悲哀。Thereは長くて退屈だから使うなという主張のようだ。There文は単純に後ろにある文章を前に持ってくるか、主語をYouに変えて文章書き直せば簡単に直せるよという有り難いアドバイスが書いてあるので活用しようと思う。
Short Sentences(短い文章)
短いコードが読みやすいようにドキュメントも短く簡潔にという内容。
- 1文には一つのアイディアに留める
- andで繋いで長くなった文章は、リストに切り出すことを考える。
- 無駄に長い言い回しを避ける。
- whichとthatの使い分け(関係代名詞)
メモ
言葉にすれば簡単だが実践するのは難易度が高い内容だと思う。気がつけば長文が出来上がっている。この章の内容も日本語でも積極的に使いたい内容だった。特に日本語は無駄に長い言い回しをするのが得意な言語だと思うので、そこは直していきたい。
whichとthatは完全に同じ意味だと思っていたがそうではないらしい。少なくともアメリカでは。whichは無くても文意が通る場合に使い、thatは無いと文意が通らない場合に使うとのこと。豆知識を得てしまった。
まとめ
まだ前半しか読んでないが有意義な内容だった。長くてやってらんねーよ!という方はトピックを訳している記事があるのでそちらだけでも。