星期四, 2月 09, 2023

 用 AI 修英文可不可行?

近期,ChatGPT 受到了非常多的關註,各種應用被開發出來,甚至很多人使用 ChatGPT 寫論文,引發爭議導致許多期刊禁止使用。但是真的完全不能用嗎?個人認為未必。ICML 的規定值得參考。ICML 是禁止使用 ChatGPT 的頂級會議,有詳細說明不可以使用的原因:


連結

   The Large Language Model (LLM) policy for ICML 2023 prohibits text produced entirely by LLMs (i.e., “generated”).  This does not prohibit authors from using LLMs for editing or polishing author-written text.
   The LLM policy is largely predicated on the principle of being conservative with respect to guarding against potential issues of using LLMs, including plagiarism.
   
我認為用 ChatGPT 進行輔助性作業,例如:換句話說,化繁為簡,改善用字....還是可以的。前提是原創文字來自人類,ChatGPT 只是輔助。


禁止使用 ChatGPT 的主要原因是原創性和正確性問題。首先,論文作者必須能對研究質量進行最終把關並負責。ChatGPT 無法負責其正確性的把關,因此不能擔任作者,也因此只有人類才能成為作者。

其次,如果研究人員使用 ChatGPT,生成具有原創性的文字。由於 ChatGPT 不能擔任作者,理論上作者應該要列入發明ChatGPT 的人或提供訓練數據的全體人類。但由於明顯的原因不可能做到。這導致實際貢獻者沒有出現在作者名單,引發抄襲問題。  

把握住根本原則後,再來看 LLM 的使用。在 AI 只是幫忙「打磨」文字的場合,文字本質是由人類作者原創,AI 幫忙編修並不減損人類的原創性及貢獻,並且最終品質仍有人類負責把關,所以無上述原創性、正確性問題。

現有很多服務、包含 DeepL、Grammarly 等也提供自動修改人類文本的功能,他們在現在和未來也可能使用 deeplearning 或 LLM,但我想不會有人認為有問題。因此,如果是
把 ChatGPT 當成高級翻譯或自動編修軟體來用,倫理上應該是沒什麼爭議的。最多被問的時候再指出有拿 AI 做 text polishing 或 paraphrasing。

最後不得不說,DL/LLM 真好用。我現在寫論文 DeepL、DeepL Write、ChatGPT、
Grammarly 四大守護神獸交替使用。中文底稿弄好,先丟 DeepL 機翻,人工小改,請
ChatGPT 潤筆重排、再丟回 DeepL Write 清潔,最終結果 Grammarly 大英修。文章的生產速度和質量都比預期好很多。

P.S:本文有用 ChatGPT 小潤筆 XD

全文連結

星期六, 1月 28, 2023

商周好文:待得久不是忠誠,是你「跑不掉」!為什麼想升主管就別太認真工作?

原文 

商周好文,很清楚點出高階主管和員工看事情的不同。其中很多觀念乍看確實不好接受,但其實有道理。

淘寶網馬雲經常語出驚人,比如他說特討厭太認真工作的人,鑽進去就出不來,帶給公司的創新少,看不出顯著貢獻。而且他也批評,重視細節的人就講不了格局,把公司的發展框限在小領域裡,一個大浪來就滅頂。
一旦讓公司覺得你『跑不了』,就會輕忽你的重要性與貢獻度。
只鎖住一個專業,給人的印象就像「很宅的工程師」,就是一個「窄」字,和「廣」正好對立。
很多都打中我啊。我也是學生畢業後就在同一機構工作,又宅 QQ 全文連結

星期二, 1月 24, 2023

如何寫好一篇論文(03) - 研究選題的三種途徑

原始出處

 

- 回答正確答案需要「點」的知識,問出正確問題需要「面」的知識
- 問出正確的問題,科研就成功了一半。
- 確立新研究課題有三種途徑,可單獨或混合使用
  * 填補研究空白
    # 針對已被廣泛認可的老問題
    # 通常具備技術挑戰性
    # 重要的是「創新」,如新的解決方案、其他領域借鑒想法
    # 需了解前人做過什麼,我們有何不同,我們貢獻和他人怎麼配合
  * 延伸現有研究
    # 追蹤研究歷史,一項接一項的成就
    # 牽涉新問題和新領域、更多的新機會
    # 新研究者容易迷失
    # 須牢記長期目標,時時自問研究有何意義,對受眾有何貢獻
  * 把成功研究在不同情形進行複製
    # 成功研究,換不同變量、不同樣本群...
    # 這是相當常見、普遍且相對安全的方法
    # 需避免複製過程中可能發生的錯誤
    
- 好研究課題的要件
  * 具體、可行
    # 所有術語、變量都有明確定義
    # 整個課題有明確的邊界
    # 不存在先入為主的假設
    # 適合現有的資源條件
  * 開放、可爭議
    # 不可證偽的課題不科學
  * 能夠激發創新

- 最牛的兩種課題
  * 研究者填補了當前所有的漏洞,此課題無可再做
  * 突破了目前的知識框架,為更多機會打開新的大門

全文連結

星期日, 1月 01, 2023

 如何寫好一篇論文(02) - 引言的邏輯解析


原始出處

- 引言可拆成兩段:第一段研究機會和第二段解決方案
- 第一段
    * 從普遍的大問題(Problem)開始,到一個研究空白或問題(Question)結束
    * 說服讀者,為了解決某個大問題,我們必須解決某個具體的研究問題
- 第二段
    * 透過建立清晰的研究目標,去填補第一段建立的研究空白或問題,佔領研究機會
    * 目標不只本論文的研究目標,也包括作者的長期目標
    * 長期目標(Goal)呼應第一段的大問題(Problem)
    * 當前目標(Answer)呼應第一段的研究空白或問題(Question)
    * 為了達成長期目標,我們必須實現某個特定目標    
- 引言如搭橋
    * 讓讀者走進我們的研究世界
    * 橋的一端是讀者的已知
        # 和讀者的知識框架建立聯繫
        # 提出讀者關心的問題
        # 從讀者已經接受的觀念入手
    * 橋的中間逐漸加入讀者未知的文獻資料
        # 說服讀者某個問題值得研究,我們和讀者應該知道他們的答案

範例:問題 X 的重要性已經被學術界廣泛認可,研究者 A 發現 a1 研究者 B 發現 b1,基於上述理由回答問題 c1 對解決問題 X 至關重要

全文連結

 如何寫好一篇論文(01) - 論文的三段式結構


最近趕 Paper 弄到一個段落。試著把之前聽過的好課做成筆記,方便自己複習。劉教授原始課程質量非常高,講的其實已經非常精簡,完全沒有任何廢話,他也在 B 站免費公開,強烈推薦去看。筆記的目的是幫我自己複習。

- 論文其實可以拆成三段:引言段、中间段、结论段
- 引言段和結論段用議論文方式寫作,講究邏輯性
- 中間段則是敘事文,平鋪直述把工作描述出來
- 引言段:
     * 說明研究目標合理
     * 整篇論文存在價值的根基
     * 告訴讀者這篇文章(讀者未知)在整個領域(已知)處於什麼位置。
     * 使用文獻作為證據
     * 從普遍(大家普遍接受的問題或觀念)到特定(我們的研究目標)
     * 也能指導結論段
- 結論段:
     * 說明研究結果價值
     * 告訴讀者這個研究結果是有價值的
     * 使用研究結果文獻作為證據
     * 從特定(特定的研究結果)到普遍(普遍的意義)
- 中間段:
     * 即使沒有引言和結論,也能當成獨立的研究報告
     * 加上引言和結論後,形成完整論文
- 如果中間段是一個「洞」,引言段讓讀者願意走到洞裡面看,結論段則是引導讀者從洞走出去
全文連結

星期一, 12月 19, 2022

星期日, 12月 11, 2022

健身房攻防紀實

原本去的小健身房租約到期倒了,不得不換健身房。工作場所附近只有一家健身房器材符合需求,其他間不符需求。但這家健身房有業務、糾紛多、價格有陷阱,因此去之前做了功課,問了有簽的朋友,成功講到比較合理的價格。這次過程大概是這樣的:

我:您好,我想詢問一年、兩年方案分別怎麼算
業:我們入會費處理費 6888,兩年月費 1988。但我可以給您作主入會 3888,月費 1788(微笑)

我:你們這分店開幾年了?
業:四年
我:可是我朋友之前來,月費只簽 988 耶~~~(爆擊)
業:(眩暈)那個是開幕期間,設備不完善或施工中,所以才有這個價格 blahblahblah
我:1788 太貴了,我希望簽 1288!(1288 為攻略上 2022 年的成交價)
業:我去問一下長官看可不可以(遁走去小房間,結束這回合)

業:(回來)我可以給您用 XXXX 專案,這方案 0 入會費,只綁一年而且月費就是 1288,請問您今天可以簽約嗎?
我:我希望將合約帶回去審閱,我簽約習慣先看。我今天運動就單次付費 OOO 元
業:不用擔心太多,我們有七天鑑賞期,不滿意可退費。這樣也不用多付一次 OOO 元 blahblahblah
我:可是你們七天鑑賞期不是規定不能使用器材嗎?(甜笑)
業:(眩暈)...合約上是不行,但我可以作主 blahblahblah
我:(內心 OS:印象中合約還有條,簽約後雙方合意以合約紙本為主,口頭約定不算數吧?)
    我希望合約可以帶 回 去 審 閱,我今天就單次付費,當然簽約我還是樂意找您 blahblah
業:(打斷我)我懂您意思,但我希望各退一步,您約帶回去,今天付費 OOO 就用信用卡可以嗎?

最後我信用卡只刷單次付費的錢,約帶回來檢視完畢後,就進行簽約了。簽約首刷金額有把我的單次體驗費用扣除,等於讓我免費體驗一次感覺還不錯。很成功的沒踩到陷阱,當然我知道健身房絕對不虧。平局結束第一回合。

我懷疑這間健身房的話術是中央制定,全台統一。導致業務出的招 100% 在攻略上都見過(攤手)。之後應該就是應對教練課推銷吧。

全文連結

星期日, 11月 06, 2022

Jupyter notebook module 找不到除錯

參考文章連結 

今天玩 python NLP 遇到鬼打牆現象:明明已經 !pip install spacy 但 jupyter 執行 import spacy 一直說 module not found。 排查後才發現是 jupyter kernel 的問題。我 docker container 原本就自帶 python 3.7,apt-get install 又自己裝了一套 python 3.9 並只認自帶的版本,兩者各自獨立,前者裝的 package 後者找不到。 

後來 Dockerfile 做了些設定

apt-get install jupyter-client
pip install ipykernel
python -m ipykernel install --user

其中裝 jupyter-client 是為了能執行 jupyter kernelspec list。後兩者則是讓 jupyter 認我裝的 python 而非 jupyter 自帶的。Dockerfile 改好後 rebuild 問題解決。

全文連結

Docker network interface 好文

出處

太久沒玩 docker 又犯了小白錯誤。

在 container 裡面裝了 jupyter-notebook,結果在 container 裡面 wget 連的到。但切回 host 和外部環境就連不上。docker file EXPOSE 有開 port,docker run port 有 forward 但就是莫名 connection refused。

後來搜尋後才發現這是典型錯誤。jupyter-notebook bind 的 127.0.0.1 屬於自己 network namespace,我的 127.0.0.1 不是你的 127.0.0.1。後來加個 -ip="*" 廣開 binding 解決。

全文連結

星期五, 10月 28, 2022

星期三, 10月 26, 2022

VirtualBox Ubuntu VM 更改檔案空間大小

圖轉載自這裡 

之前曾多次遇到 VirtualBox VM 空間不夠問題,建立 VM 時給的硬碟空間太小,docker 弄一弄 VM 硬碟空間就不夠了。但按照 Stack Overflow 上的解法怎麼改,分割大小都是舊的,最後往往重裝 VM 解決。但每次都一堆 docker image 重抓真的非常麻煩。後來讀文章才整理出解法。 


 

問題根本可以用這張圖說明,現在的 Linux (我用 Ubuntu 20.04 LTS)在檔案系統上疊太多層抽象化,要正確擴充可用空間大小必須每一層都改到。在 VirtualBox 的步驟大概是 

1. 修改「實體硬碟」的大小

VirtualBox 需要把所有 Snapshot 取消。有 Snapshot 那 VM 寫入的「硬碟」實際上有可能分散到 snapshot 裡。然後 GUI 的虛擬媒體管理員直接調大小。 

2. 修改 Partition 大小 

先用 lsblk 看要擴容的檔案系統到底實際是掛在哪顆硬碟上。我是 /dev/sda3,這時

sudo growpart /dev/sda 3

下達後用 lsblk 檢查,/dev/sda3 應該已經擴大,但下方掛的 Logical volume 大小還是舊的,sudo pvdisplay 大小也是舊的 

 3. 擴大 Physical volume 大小

pvresize /dev/sda3

再來用 sudo pvdisplay 看大小對了,但 lsblk 看到的 ─ubuntu--vg-ubuntu--lv (logical volume)和 df -h 看的大小還是舊的

4. 調整 Logical volume 大小

sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
應該要可以看到 Size of logical volume ... changed from...,再來
sudo vgdisplay

應該就要看到 VG Size 為新的大小,此時 df -h 看到的 Filesystem 大小還是舊的,此時應接最後一道工法

5. 調整 file system 大小

sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
最後 df -h 看到分割區大小變大,打完收工!現在 Linux 的檔案系統真的抽象化堆的太誇張了....
全文連結

星期一, 10月 24, 2022

Record of Lodoss War-Deedlit in Wonder Labyrinth-

Steam 頁面

 

全破了一輪。遊戲是橫向捲軸、類惡魔城遊戲。遊戲長度只有幾小時就能全破一輪。目前好像沒多周目要素。作為動作遊戲簡單爽快,裡面有些小機制,例如風火抗性要即時、輪流切換才能順利破關和打王有趣。

不過遊戲最香的,還是長腿精靈大姊姊蒂德莉特,哪怕是像素版一樣香。遊戲聽說還是羅德斯島戰記小說原作者的監督下完成的。

唯一小遺憾的是結局,雖然從小說續作已經知道了,但看到和帕恩的相遇只是夢一場,恢復記憶醒來後蒂德莉特去墳前告別,仍然是有點心酸。

全文連結

星期五, 10月 14, 2022

AI 研究小感

 壓力好像真的是寫程式的動力。

之前答應合作夥伴,解決土炮搜尋引擎中的查詢精確度問題(例如搜尋 dissection 跑出一堆 No CT evidence of dissection),打算用 deep learning NLP 處理並寫了研究計畫。
 
通過後花了很多時間自己標了 5000 份報告。樣本標好了,一直沒力做實驗。結果被 angio 的消息搞到嚴重焦慮,實驗就一口氣做完了。實驗過程中,我的 AI 能力也從調包俠順利轉職 Lv1 調參俠,還學會了通靈。
 
例如:class imbalance 太嚴重,模型 3 epoch 後全押 trivial answer 怎麼辦?
 
焚香入定向虛空無光之海祈禱,慷慨的虛空回應了真理:「Ez, Shuul'wah! Sk'woth'gl yu'gaz yog'ghyl ilfah!」歐我懂了 learning rate 要砍一半是吧?oh yeah 模型真的動了!!
 
從做產品的角度來說問題應該可說是解了,看到精美的 F1 score 和 accuracy 都 >95% 好到擔心是不是 overfitting。自己試了些 free typing 的自創 input 結果感覺還行,一部分的問題也有繞過或進一步 finetune 的思路。只要 model 扔進 pipeline 就行。
 
不過「研究」計畫產出的水論文階段真的就有點難倒我。上篇也是唯一一篇 published paper 還是快十年前的 case report 還是大佬手把手帶出來的,碩論雖然是資工相關但那是沒發表的文章。自己找了下 google scholar 看別人做的東西感覺賣點好像有限,方法也不 novel,得花腦筋想出夠好的賣點還有想辦法找高人了 XDDD

順便再次讚嘆 vast.ai,比特幣崩盤後租 GPU 的價格真的香,RTX 3090 24GB VRAM $0.3 USD/hr,model finetune 一次 17 分鐘解決,基本可以邊打報告邊 training ...
全文連結

星期三, 10月 12, 2022

大富翁三

Steam 頁面

大富翁系列是小時候很喜歡的遊戲之一,特別是二代、三代、四代(五代以後玩不習慣,都是玩了秒刪)。Steam 出了 DOSBox 版就入手了,二三代是重溫,四代是補票 XD

這幾天花了八小時把三代破過一輪,實現了孫小美的勝利 XD 以現在眼光來看,遊戲不困難,AI 太笨,特別是不會濫用道具和炒股存股。三代的道具店遙控骰子和路障還可以無限制買,搭配黑市存夠道具和卡片後基本就鎖定勝局,連 S/L都不大需要。

這塊地很值錢呢,他是我的了(使用購地卡)~
踩到噴錢的地點了,喔嫁禍卡和免費卡的複合式反應裝甲還有四層那沒事了~
AI 真是好心人有高價卡片都捨不得用拿來送我呢,看我用搶奪卡搶奪你所有的搶奪卡再搶走你其他高價卡~

不過 AI 雖笨,但回憶無價,當年玩二代三代的時候還是國小屁孩。現在重溫兼補票,很遺憾 Steam 版四代目前沒有超時空之旅,看未來官方會不會補了。

全文連結

星期二, 9月 27, 2022

老同學 Godie 見面

這次北台灣旅遊沒掀到人,反而回老家的時候掀到老同學。

Godie 是當年小學同學,後來又剛好同高中並且一起加入資訊社 TFCIS,上次見面應該是快 20 年前應該是 302 一起打 CS。命運的安排讓老同學上週五來奇美演講,就趁機掀吃飯了。

Godie 演講完後弄了自己的行程,順便回老家探望雙親順便換掉西裝,時間到在醫院見面,然後由我帶去吃附近唯一一家有冷氣且相對高檔的義大利餐廳 XDDD

隔了快 20 年,老同學們邊吃飯邊講古,我國小的事情只記得零碎片段,只零星記得幾個人名和一些瑣事,例如小六時幫 Godie 競選某個職位 XD

倒是 Godie 和一些同學有緣讀同所國中,一直保持聯絡最後也加了臉書。看到當年洽北北女同學變成照片中洋溢著幸福的二寶媽、另一個男同學變大人變帥還剛好在奇美(電子)工作,真的有時光飛逝滄海桑田的感覺。

吃飯中也聊了生活其他面向、工作、情感、居住無所不聊。連創業都聊,例如未來如果一起弄套交友軟體,一定要有特殊功能可以把優質對象直接下架並內線介紹給公司高層 etc XDDD。

這場快樂的吃飯以我地頭蛇請客結束。有老朋友來看我太難得了,也算是對當年大專乙組一題奪冠遲來的補請客了 :D

全文連結

星期四, 9月 22, 2022

Docker 取回硬碟空間

sudo sh -c "truncate -s 0 /var/lib/docker/containers/*/*-json.log"
Docker container 長時間執行後常遇到大量佔據硬碟空間,docker system prune 沒用,一個可能兇手就是大量 container log。用指令把冗餘 log file 清掉就能取回空間。新版 docker 好像有 log rotation 機制,有機會再研究 全文連結

星期一, 9月 12, 2022

台北旅行遊記

工作累積太多疲勞,剛好遇到中秋連假,就額外多請幾天,湊了六天連假來自助小旅遊。

本來考慮去九族文化村和日月潭,但想到本肥年近 40、體重近 90,續航力不佳,戶外景點很怕玩三小時人就沒力,然後就得回飯店待機。所以後來決定去台北。台北戶外玩累還可以玩室內行程,最不濟還有書局。這次行程玩了許多過去沒玩過,或很久沒來過的點。

這次去了陽明山。但這次沒做太多功課,純粹隨緣,結果有點失敗。大部分時間花在走路和搭公車移動上,吸了很多芬多精但沒看到太多景點有點可惜。 

陽明山上散步吸芬多精,來的時間不對,動線沒規劃完整,沒看很多景點,但至少吸了芬多精 XD


路上遇到野怪 -- 貓貓。貓貓看來並不怕人。至少肯擺 pose 給拍照


數位放大後的貓貓,不親人但也不怕人





回來前一天去了貓空。貓空是從來沒玩過的景點。貓空景點相對集中,下捷運動物園站後搭纜車到貓空站,然後徒步移動走玩右線和一部分左線。貓空真的有點日本小鎮的感覺。

貓空天恩宮,路過拍照,沒進去看


在貓空纜車上照到的街景

貓空夕陽照,剛好抓到太陽下山的 timing 


Y 區地下街照片,Y區地下街這次沒玩到太多,室內行程都去天瓏書局看書了



西門町夜景,新換的 S20FE 夜拍確實比前一支 LG V20 給力。

虎之穴店門口,還沒進店門在樓梯應該是可以拍照的 XD


天瓏書局,台南北門路的宏華收了以後,就沒有充滿電腦書的書店了。誠品電腦書不少,北門路也有天瓏台南據點。但純就數量而言台南沒一個比的上台北天瓏的量。

台北宅行程必逛安利美特,就算什麼都沒買來吸吸宅氣也好。永恆不變的入口照 XD

永遠的經典新世紀福音戰士。動畫擺了 N 年但永遠沒看完的一天 XD

 


新流行的作品,但個人對腐是完全不能接受的 XD

這次旅行唯一小遺憾就是沒掀到 Twsam,他說可愛小寶寶剛回家應該無法分身!看來未來幾年掀到的機率也非常渺茫了 QQ。當年 TFCIS iii 程設組五人,kochi 已知結婚生子,DNA 結婚更早,現在 Twsam 的狀態也從已知單身忽然跳到有小孩(看過低調的沒看過這麼低調的 XDDDD),DreamLinuxer 在美國,所以進度停滯不前的單身漢就剩下我了嗎?



全文連結

星期日, 8月 28, 2022

Bioshock remastered

好久沒廢寢忘食打 FPS 。這款躺在 Steam 吃灰很長一段時間,忘記為什麼忽然想拿出來打,但來了興趣就一口氣破完。

遊戲充滿了各種對立、矛盾元素。遊戲發生在海底遠離主世界塵囂的的烏托邦 -- 銷魂城(Rapture city)。裡面建築風格明明是幾十年前的美國,卻有遠超地表的超級黑科技,例如能讓人飛簷走壁、發射火球、閃電、甚至粒子化瞬間移動的基改技術。

但美侖美奐的城市卻因濫用基改物質(ADAM)和內鬥等因素,最後變成反烏托邦。美麗如渡假飯店的城市,內部最常見的住民卻是被 ADAM 侵蝕神智、精神失常的瘋子,或是被肉體心靈雙重改造蒐集 ADAM 的 Little girl 和其保護者 Big daddy。

遊戲的主線故事也牽涉到善惡兩極反轉,遊戲以主角墜機剛好來到銷魂城開始,在戰友透過收音機指導棋的幫助下,一路披荊斬棘幫自己逃生,並尋找殺死戰友家人、控制城市的惡魔。最後真相揭曉,惡魔也許不是好人,但戰友卻不是戰友。最後主角成功解除自身被埋下的心靈暗示,踏過屍山血海打死最終 BOSS,迎來結局。

遊戲的戰鬥系統有其特色,在後續系列作也持續使用。主角有 FPS 標配的槍械類武器的同時,也能透基改 plasmid 掌控各種能力,例如放電、引火、召喚蟲群、催眠他人等類法術能力、或提昇駭客技術、提高抗性等自我改造。主角也有駭客技術,遇到機械類敵人也能選擇解拼圖、改寫敵人邏輯取得控制權。遊戲連貨幣都有兩種,能買槍械子彈恢復藥劑的金錢,還有可以用來買 plasmid 或提自己能力的 ADAM。


遊戲過關還是要用點心思,特別是跟 Big Daddy 的戰鬥,對手攻高血厚硬拼往往拼不過,特別是什麼都缺的前期。要打贏必須用策略,例如激怒 Big Daddy 後把他引到被我方控制的火箭炮台。透過能造成麻痺的電擊 plasmid 不停強控、直到他被火箭炮擊殺。或是用催眠 plasmid 控制另外一個 Big Daddy 製造內鬥再減尾刀等。地圖本身也有少量解謎元素,例如被冰封的門要用點火能力打開、放在拿不到地方的鑰匙要用格空取物能力取得。

遊戲武器、Plasmid 都有升級系統。武器可以透過特定地點升級,解鎖更強的威力、豁免自己武器傷害等升級。Plasmid 可以在遊戲過程中買、合成更強的版本,付出 ADAM 解鎖更多安裝位置等。

遊戲有多重結局。結局好壞取決於怎麼處理 Little Sister。打倒 Big Daddy 後選擇殺掉 Little Sister 可以獲得更多 ADAM 卻會導向壞結局。選擇救贖 Little Sister 解除讓其變回常人,得到的 ADAM 會少一些,但從頭到尾都選救贖,遊戲才能有好解局。但救贖之路科學家其實會送禮表達感謝,算上禮物價值後其實 ADAM 沒少拿多少,我選擇救贖、最後看到好結局。

破關後再選 new game 發現有二周目 new game plus,但暫時沒力氣玩了。


全文連結

星期四, 8月 11, 2022

Autohotkey 抓多螢幕解析度方法

在公司打報告,希望用 OCR 判讀螢幕數字並自動抄錄以減少作業時間。這功能需要每個螢幕的長寬大小(pixel)才能讓程式知道 OCR 該看螢幕截圖的哪個位置。

原本是用 Autohotkey 的 SysGet Monitor 解決。但 SysGet 傳回的資料是錯的,也沒體力除錯了。為了省工不想寫額外程式,希望能用簡單的腳本解決。後來發現 powershell 可以做這件事情 

Get-WmiObject -Class Win32_VideoController | Out-File screen.out

輸出結果導向到檔案儲存,然後回 Autohotkey 從檔案中用 regex 從 VideoModeDescription 讀出解析度數值,然後做些加減乘除就有各螢幕大小和座標了。 值得一提的是其他的失敗方案:

Add-Type -AssemblyName System.Windows.Forms
System.Windows.Forms.Screen]::AllScreens

 這個 powershell 方法能得到全螢幕解析度,但會受到 scaling factor 干擾,得到的 width、height 必須乘以該螢幕的對應 scaling factor 才能還原為真實的螢幕解析度,但找了一陣子沒發現快速得到 scaling factor 的方法,因此就放棄此法。

[System.Windows.Forms.SystemInformation]::PrimaryMonitorSize 

這個方法則是只能得到主螢幕解析度,看不到第二第三顆的。

 年紀真的大了,花了一個半小時研究城市又開始眩暈 QQ

--------------------------------------------

$pinvokeCode = @" 
using System; 
using System.Runtime.InteropServices; 
using System.Collections.Generic;
namespace Resolution 
{ 
    [StructLayout(LayoutKind.Sequential)] 
    public struct DEVMODE1 
    { 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
        public string dmDeviceName; 
        public short dmSpecVersion; 
        public short dmDriverVersion; 
        public short dmSize; 
        public short dmDriverExtra; 
        public int dmFields; 
        public short dmOrientation; 
        public short dmPaperSize; 
        public short dmPaperLength; 
        public short dmPaperWidth; 
        public short dmScale; 
        public short dmCopies; 
        public short dmDefaultSource; 
        public short dmPrintQuality; 
        public short dmColor; 
        public short dmDuplex; 
        public short dmYResolution; 
        public short dmTTOption; 
        public short dmCollate; 
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 
        public string dmFormName; 
        public short dmLogPixels; 
        public short dmBitsPerPel; 
        public int dmPelsWidth; 
        public int dmPelsHeight; 
        public int dmDisplayFlags; 
        public int dmDisplayFrequency; 
        public int dmICMMethod; 
        public int dmICMIntent; 
        public int dmMediaType; 
        public int dmDitherType; 
        public int dmReserved1; 
        public int dmReserved2; 
        public int dmPanningWidth; 
        public int dmPanningHeight; 
    }; 
	
	[Flags()]
	public enum DisplayDeviceStateFlags : int
	{
		/// <summary>The device is part of the desktop.</summary>
		AttachedToDesktop = 0x1,
		MultiDriver = 0x2,
		/// <summary>The device is part of the desktop.</summary>
		PrimaryDevice = 0x4,
		/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
		MirroringDriver = 0x8,
		/// <summary>The device is VGA compatible.</summary>
		VGACompatible = 0x10,
		/// <summary>The device is removable; it cannot be the primary display.</summary>
		Removable = 0x20,
		/// <summary>The device has more display modes than its output devices support.</summary>
		ModesPruned = 0x8000000,
		Remote = 0x4000000,
		Disconnect = 0x2000000
	}
	[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
	public struct DISPLAY_DEVICE 
	{
		  [MarshalAs(UnmanagedType.U4)]
		  public int cb;
		  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
		  public string DeviceName;
		  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
		  public string DeviceString;
		  [MarshalAs(UnmanagedType.U4)]
		  public DisplayDeviceStateFlags StateFlags;
		  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
		  public string DeviceID;
		[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
		  public string DeviceKey;
	}
    class User_32 
    { 
        [DllImport("user32.dll")] 
        public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode); 
        [DllImport("user32.dll")] 
        public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags); 
		[DllImport("user32.dll")]
		public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
        public const int ENUM_CURRENT_SETTINGS = -1; 
        public const int CDS_UPDATEREGISTRY = 0x01; 
        public const int CDS_TEST = 0x02; 
        public const int DISP_CHANGE_SUCCESSFUL = 0; 
        public const int DISP_CHANGE_RESTART = 1; 
        public const int DISP_CHANGE_FAILED = -1; 
    } 
    public class Displays
    {
		public static IList<string> GetDisplayNames()
		{
			var returnVals = new List<string>();
			for(var x=0U; x<1024; ++x)
			{
				DISPLAY_DEVICE outVar = new DISPLAY_DEVICE();
				outVar.cb = (short)Marshal.SizeOf(outVar);
				if(User_32.EnumDisplayDevices(null, x, ref outVar, 1U))
				{
					returnVals.Add(outVar.DeviceName);
				}
			}
			return returnVals;
		}
		
		public static string GetCurrentResolution(string deviceName)
        {
            string returnValue = null;
            DEVMODE1 dm = GetDevMode1();
            if (0 != User_32.EnumDisplaySettings(deviceName, User_32.ENUM_CURRENT_SETTINGS, ref dm))
            {
                returnValue = dm.dmPelsWidth + "," + dm.dmPelsHeight;
            }
            return returnValue;
        }
		
		public static IList<string> GetResolutions()
		{
			var displays = GetDisplayNames();
			var returnValue = new List<string>();
			foreach(var display in displays)
			{
				returnValue.Add(GetCurrentResolution(display));
			}
			return returnValue;
		}
		
        private static DEVMODE1 GetDevMode1() 
        { 
            DEVMODE1 dm = new DEVMODE1(); 
            dm.dmDeviceName = new String(new char[32]); 
            dm.dmFormName = new String(new char[32]); 
            dm.dmSize = (short)Marshal.SizeOf(dm); 
            return dm; 
        } 
    }
} 
"@
Add-Type $pinvokeCode
[Resolution.Displays]::GetResolutions();

方法出處 

結果本以為能用的方法失敗,最後換了這個方法,在 Autohotkey 中呼叫 powershell.exe 執行這段腳本,用 Out-File 導出成文字檔用 regex parse。 話說 powershell 可以直接執行 C# 代碼耶,作為「腳本語言」這會不會太強了點?XDDDDD

全文連結