顯示具有 Colab 標籤的文章。 顯示所有文章
顯示具有 Colab 標籤的文章。 顯示所有文章

2025年8月17日 星期日

Python 學習筆記 : K 線圖模組 kbar.py 改版 (全平台)

早上完成 kbar.py 程式改版, 經測試確認可在 Windows 與 Colab 平台上順利繪製有中文的 K 線圖後才匆匆出門去市集買菜. 中午飯後繼續趕工, 看看能否在其他 Linux 主機 (我手上只有 Mapleboard 的 Ubuntu Mate 與樹莓派 Rasbian) 上繪製有中文的 K 線圖. 雖然 Colab 的虛擬機也是 Linux, 但它與一般 Linux 在中文字型安裝上不同, 必須另外處理. 

我繼續與 Claude 協作, 但這次它有點秀逗, 先後測試了 Claude 因應 Linux 環境而改寫的兩款程式碼都失敗, 連在 Colab 上跑都倒退無法顯示中文, 由於程式碼比先前 Windows + Colab 可運作版還複雜, 實在不想一行一行檢查哪裡出錯, 乾脆回 ChatGPT, 把前一篇的 Windows + Colab 可運作版程式碼丟給它, 請它在此基礎上添加可在一般 Linux 平台順利執行的 kbar.py 程式碼 : 

# kbar.py
import mplfinance as mpf
import matplotlib as mpl
from matplotlib import font_manager
import os
import sys
import subprocess

def install_noto_cjk_linux():  # 在 Linux (Ubuntu/樹莓派) 安裝 Noto CJK 字體
    try:
        print('偵測到 Linux 環境,嘗試安裝 Noto CJK 字體...')
        result=subprocess.run(
            ['sudo', 'apt-get', 'install', '-y', 'fonts-noto-cjk'],
            capture_output=True,
            text=True
            )
        if result.returncode != 0:
            print(f'字體安裝失敗: {result.stderr}')
            return None
        # 更新 font cache
        subprocess.run(['fc-cache', '-fv'], capture_output=True)
        # 強制刷新 matplotlib 字體快取
        font_manager._load_fontmanager(try_read_cache=False)
        print('Noto CJK 字體安裝完成')
        return True
    except Exception as e:
        print(f'安裝過程出錯: {e}')
        return None

def download_noto_cjk_colab():  # 下載中文字體到 Colab 環境
    font_path='/content/NotoSansCJK-Regular.ttc'
    if not os.path.exists(font_path):
        print("正在下載中文字體...")
        url='https://github.com/googlefonts/noto-cjk/raw/main/' +\
            'Sans/OTC/NotoSansCJK-Regular.ttc'
        try:            
            result=subprocess.run(  # 使用 wget 下載字體
                ['wget', '-O', font_path, url],
                capture_output=True,
                text=True
                )            
            if result.returncode != 0:
                print(f'下載失敗: {result.stderr}')
                return None            
            print('中文字體下載完成')
            return font_path
        except Exception as e:
            print(f'字體下載失敗: {e}')
            return None
    else:
        print('字體檔案已存在')
        return font_path

def register_font_colab(font_path):  # 註冊字體並回傳可用的字體名稱
    if not font_path or not os.path.exists(font_path):
        return None    
    try:  # 註冊字體       
        font_manager.fontManager.addfont(font_path)        
        # 重建字體管理器快取
        font_manager._load_fontmanager(try_read_cache=False)        
        # 檢查註冊後可用的字體名稱
        fonts={f.name for f in font_manager.fontManager.ttflist}        
        # Noto Sans CJK 可能的名稱變化
        possible_names=[
            'Noto Sans CJK TC',
            'Noto Sans CJK JP', 
            'Noto Sans CJK SC',
            'Noto Sans CJK KR',
            'Noto Sans CJK'
            ]
        for name in possible_names:
            if name in fonts:
                print(f'成功註冊字體: {name}')
                return name
        print(f"字體註冊後可用名稱: {[f for f in fonts if 'Noto' in f or 'CJK' in f]}")
        return None
    except Exception as e:
        print(f"字體註冊失敗: {e}")
        return None

def detect_font():  # 偵測系統中是否有常見的中文字型 
    fonts={f.name for f in font_manager.fontManager.ttflist}
    # 檢查常見中文字體
    linux_chinese_fonts=['Noto Sans CJK TC','Noto Sans CJK JP','Noto Sans CJK SC']
    if 'Microsoft JhengHei' in fonts:
        return 'Microsoft JhengHei'
    elif 'PingFang TC' in fonts:
        return 'PingFang TC'
    elif any(name in fonts for name in linux_chinese_fonts):
        return next(name for name in linux_chinese_fonts if name in fonts)
    else:  # 檢測環境        
        in_colab='google.colab' in sys.modules
        in_linux=sys.platform.startswith('linux')
        if in_colab:
            print('Colab 環境偵測到,正在下載並設定中文字體...')
            font_path=download_noto_cjk_colab()
            if font_path:
                return register_font_colab(font_path)
        elif in_linux:
            print('Linux 環境偵測到,嘗試安裝 Noto CJK 字體...')
            if install_noto_cjk_linux():
                # 再檢查一次
                fonts={f.name for f in font_manager.fontManager.ttflist}
                for name in linux_chinese_fonts:
                    if name in fonts:
                        return name
        return None

def check_font(font):  # 檢查指定字型是否存在並回傳候選清單
    fonts={f.name for f in font_manager.fontManager.ttflist}
    candidates=[]    
    if font and font in fonts:
        candidates.append(font)
        print(f'使用指定字體: {font}')
    elif font:
        print(f"[警告] 找不到字型 '{font}',將使用 fallback 字型")
    # 通用 fallback 清單
    fallbacks=[  
        'Microsoft JhengHei',
        'PingFang TC',
        'Noto Sans CJK TC',
        'Noto Sans CJK JP',
        'Noto Sans CJK SC',
        'DejaVu Sans',
        'Liberation Sans',
        'Arial',
        'sans-serif'
        ]    
    for fallback in fallbacks:
        if fallback in fonts and fallback not in candidates:
            candidates.append(fallback)
    if candidates:
        print(f'字體候選清單: {candidates[:3]}')  # 只顯示前3個
        return candidates
    else:
        print('警告: 沒有找到合適的字體')
        return ['DejaVu Sans']

class KBar():
    def __init__(self, df, font=None):
        self.df=df
        self.addplots=[]
        self.font=font or detect_font()
        # 設定 matplotlib 全域參數
        if self.font:
            # 清除舊的字體設定
            if 'font.sans-serif' in mpl.rcParams:
                current_fonts=mpl.rcParams['font.sans-serif'].copy()
                # 移除可能造成問題的字體
                current_fonts=[f for f in current_fonts if f not in ['SimHei']]
                mpl.rcParams['font.sans-serif']=[self.font] + current_fonts
            else:
                mpl.rcParams['font.sans-serif']=[self.font, 'DejaVu Sans']
            mpl.rcParams['axes.unicode_minus']=False
            print(f'設定字體為: {self.font}')
        else:
            print('警告: 未找到中文字體,中文可能無法正常顯示')
    
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)
        self.addplots.append(plot)
    
    def plot(self, embedding=False, **kwargs):
        color=mpf.make_marketcolors(
            up='red',
            down='green',
            inherit=True
            )   
        # 取得字體候選清單
        font_candidates=check_font(self.font)
        style=mpf.make_mpf_style(
            base_mpf_style='default',
            marketcolors=color,
            rc={
                'font.family': font_candidates,
                'font.sans-serif': font_candidates,
                'axes.unicode_minus': False
                }
            )  
        kwargs['type']='candle'
        kwargs['style']=style
        kwargs['addplot']=self.addplots
        result=mpf.plot(self.df, **kwargs)
        if kwargs.get('returnfig', False):
            return result

可見它並沒有大幅改寫程式架構, 僅新增了一個 Linux 平台下載與安裝中文字型的函式 install_noto_cjk_linux(), 以及修改了一部分的 detect_font() 函式內容以納入 Linux 平台的偵測程式碼而已. 為了較好地區別函式功能, 我把前一版本 (Windows + Colab) 中的 register_font() 改名為 register_font_colab(); 把 download_chinese_font() 函式改名為 download_noto_cjk_colab(). 其他函式都沒變. 

先在 Windows 與 Colab 平台上測試此全平台版的 kbar.py, 結果與前一版本相同, 都能正常在 K 線圖上顯示中文, 在此略過. 接下來便是重頭戲, 要到 Linux 主機上測試, 我的 Mapleboard 系統為 Ubuntu Mate, 之前尚未安裝過 yfinance 與 mplfinance, 所以先安裝此二套件 :

安裝 yfinance : 

tony1966@LX2438:~/python$ pip3 install yfinance    
Defaulting to user installation because normal site-packages is not writeable
Collecting yfinance
  Downloading yfinance-0.2.65-py2.py3-none-any.whl (119 kB)
... (略) ...
Successfully built multitasking peewee
Installing collected packages: pytz, peewee, multitasking, pycparser, frozendict, beautifulsoup4, cffi, curl_cffi, yfinance
Successfully installed beautifulsoup4-4.13.4 cffi-1.17.1 curl_cffi-0.13.0 frozendict-2.4.6 multitasking-0.0.12 peewee-3.18.2 pycparser-2.22 pytz-2025.2 yfinance-0.2.65

安裝 mplfinance : 

tony1966@LX2438:~/python$ pip3 install mplfinance   
Defaulting to user installation because normal site-packages is not writeable
Collecting mplfinance
  Downloading mplfinance-0.12.10b0-py3-none-any.whl (75 kB)
... (略) ...
Installing collected packages: kiwisolver, fonttools, cycler, contourpy, matplotlib, mplfinance
Successfully installed contourpy-1.3.2 cycler-0.12.1 fonttools-4.59.1 kiwisolver-1.4.9 matplotlib-3.10.5 mplfinance-0.12.10b0

開啟 Thonny 於交談視窗輸入下列程式碼繪製 K 線圖 : 

from kbar import KBar   
import yfinance as yf   
df=yf.download('0050.TW', start='2024-07-01', end='2024-08-21', auto_adjust=False)   
df.columns=df.columns.map(lambda x: x[0])    # 改成舊版單層索引
kb=KBar(df)    # 未傳 font 參數使用預設字型
kb.plot(title='台灣五十(0050.TW)', volume=True)

>>> from kbar import KBar    
Matplotlib is building the font cache; this may take a moment.
>>> import yfinance as yf   
>>> df=yf.download('0050.TW', start='2024-07-01', end='2024-08-21', auto_adjust=False)    
[*********************100%***********************]  1 of 1 completed
>>> df.columns=df.columns.map(lambda x: x[0])     
>>> kb=KBar(df)    
設定字體為: Noto Sans CJK JP
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)    
使用指定字體: Noto Sans CJK JP
字體候選清單: ['Noto Sans CJK JP', 'DejaVu Sans', 'Liberation Sans']

可見 Ubuntu Mate 下載安裝了 Noto Sans CJK 字型來顯示中文, 結果如下 : 




OK, kbar.py 模組的改版工作至此就完工啦! 可以進行套件打包發佈了. 

Python 學習筆記 : K 線圖模組 kbar.py 改版 (Windows & Colab)

去年此時寫了一個方便繪製 K 線圖的模組 kbar.py 後一直想把它打包後發佈到 PyPI 網站分享, 但總是抽不出時間. 最近忙完 Mapleboard 的函式執行平台開發後, 終於騰出時間來研究打包與發佈套件的方法. 不過在測試時卻發現 kbar.py 模組存在一個臭蟲, 在 Colab 上使用它來畫 K 線圖時出現找不到微軟正黑體的警告訊息 :

WARNING:matplotlib.font_manager:findfont: Font family 'Microsoft JhengHei' not found.

詢問 ChatGPT 才知道, 原來 Colab 虛擬機根本就沒有中文字型可用, 遑論微軟正黑體. 之前寫好的 kbar.py 當時是在 Windows 下測試開發的, 所謂的臭蟲是指我在程式碼中把字型寫死為微軟正黑體, 這在 Windows 上跑沒問題 (正黑體是系統內建的) :

font={'font.family': 'Microsoft JhengHei'}

把 kbar.py 用下列程式碼上傳到 Colab : 

from google.colab import files
uploaded=files.upload()




先用下列指令安裝要用到的 yfinance 與 mplfinance 套件 (Colab 沒有預載) : 

!pip install mplfinance yfinance  

這樣就可以用下列程式碼來畫 K 線圖了 : 

import yfinance as yf 
from kbar import KBar
df=yf.download('0050.tw', start='2024-07-01', end='2024-08-21', auto_adjust=False) 
df.columns=df.columns.map(lambda x: x[0])
kb=KBar(df) 
kb.plot(volume=True)

K 線圖是畫出來了, 但是中文部分變成怪碼 :




而且會出現找不到正黑體字型 'Microsoft JhengHei' 的警告 : 




抄錄這個 Windows 專用的 kbar.py 程式碼如下 : 

# kbar.py (only for Windows)
import mplfinance as mpf

class KBar():
    def __init__(self, df):
        self.df=df
        self.addplots=[]
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)
        self.addplots.append(plot)
    def plot(self, embedding=False, **kwargs):
        color=mpf.make_marketcolors(up='red', down='green', inherit=True)   
        font={'font.family': 'Microsoft JhengHei'}   
        style=mpf.make_mpf_style(base_mpf_style='default',
                                 marketcolors=color,
                                 rc=font)
        kwargs['type']='candle'
        kwargs['style']=style
        kwargs['addplot']=self.addplots
        if 'returnfig' in kwargs and kwargs['returnfig'] is True:
            fig, axes=mpf.plot(self.df, **kwargs)
            return fig, axes
        else:
            mpf.plot(self.df, **kwargs)

此警告出現的原因是因為在 plot() 中硬性指定使用微軟正黑體字型, 這在 Windows 機器上因為是內建字型完全沒有問題, 但在 Colab 虛擬機器上就會因為找不到正黑體字型而出現警告 (但 K 線圖還是正常繪製, 只是中文標題與軸標籤會變成怪碼). 

以下分兩步驟進行改版, 首先是移除寫死的正黑體, 改成在建立 KBar 物件時可傳入 font 參數指定要使用的字型, 當找不到指定字型 (例如名稱拼錯) 時就使用退回清單中的字型 : 


一. kbar.py 改版 A (可指定字型) : 

修正後的程式碼如下 :

# kbar.py (new version 1)
import mplfinance as mpf
import matplotlib as mpl
from matplotlib import font_manager

def detect_font():  # 偵測系統中是否有常見的中文字型    
    fonts={f.name for f in font_manager.fontManager.ttflist}  
    if 'Microsoft JhengHei' in fonts:  # Windows 內建中文字型 (正黑)
        return 'Microsoft JhengHei'
    elif 'PingFang TC' in fonts:  # macOS 內建中文字型
        return 'PingFang TC'
    elif 'Noto Sans CJK TC' in fonts:  # Linux/Colab 常用中文字型
        return 'Noto Sans CJK TC'
    else:  # 沒有找到常見中文字型傳回 None           
        return None  # 交給 fallback 處理

def check_font(font):  # 檢查指定字型是否存在
    fonts={f.name for f in font_manager.fontManager.ttflist}
    candidates=[]  # 儲存可用字型清單
    if font and font in fonts:  # 指定的字型存在
        candidates.append(font)
    elif font:  # 指定字型不存在
        print(f"[警告] 找不到字型 '{font}',將使用 fallback 字型")
    # 通用 fallback 清單 (先放中文字型再放英文字型)
    fallbacks=[  
        'Microsoft JhengHei',  # Windows 中文字型
        'PingFang TC',         # macOS 中文字型
        'Noto Sans CJK TC',    # Linux 中文字型
        'Arial',               # 英文字型
        'Liberation Sans',     # 英文字型
        'DejaVu Sans',         # 英文字型
        'sans-serif'           # 英文字型
        ]
    for fallback in fallbacks:
        if fallback in fonts and fallback not in candidates:
            candidates.append(fallback)
    return candidates or None

class KBar():
    def __init__(self, df, font=None):
        self.df=df  # 儲存 OHLCV DataFrame (開高低收成交量)
        self.addplots=[]  # 儲存額外副圖的串列
        self.font=font or detect_font()  # 若未指定字型則自動偵測
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)  # 建立副圖物件
        self.addplots.append(plot)  # 添加副圖
    def plot(self, embedding=False, **kwargs):
        color=mpf.make_marketcolors(  # 設定紅漲綠跌的配色
            up='red',
            down='green',
            inherit=True
            )   
        style=mpf.make_mpf_style(  # 自訂 K 線圖風格
            base_mpf_style='default',  # 指定預設風格
            marketcolors=color,  # 指定配色
            rc={'font.family': check_font(self.font)}  
            )  
        kwargs['type']='candle'  # 預設使用蠟燭圖
        kwargs['style']=style   # 套用自訂風格
        kwargs['addplot']=self.addplots  # 加入副圖設定
        result=mpf.plot(self.df, **kwargs)  # 繪製 K 線圖
        if kwargs.get('returnfig', False):  # 是否回傳 Figure 與 Axes
            return result  # 回傳繪圖物件 fig, axes

主要的更改之處摘要說明如下 :
  • KBar 類別建構式增加了一個 font 參數用來指定字型, 在呼叫 KBar() 建立 KBar 物件時可以傳入 font 指定字型, 所以在初始化函式 __init__() 裡添加了 font 參數來接收建構式傳來的指定字型, 若未指定則呼叫 detect_font() 函式取得常用中文字型, 結果儲存在 KBar 物件的 font 屬性裡. 
  • 新增 detect_font() 函式用來偵測作業系統中是否有常見的中文字型, 傳回各系統常見的內建中文字型, 如果都沒有就傳回 None 進入退回模式. 
  • 新增 check_font() 函式, 在呼叫 mplfinance 的 make_mpf_style() 設定繪圖風格, 用 rc 參數設定字型時會呼叫此函式並傳入 KBar 物件的 font 屬性, 它儲存了物件初始化時偵測出來的中文字型或呼叫者指定的字型名稱, 如果初始化時已偵測出中文字型, 那它會被放在可用字型清單的第一個元素, 後面可能跟著測試出來的系統常見之退回字型 (英文), rc 參數的 font-family 鍵接受一個字型清單, matplotlib 會自動依序調用裡面下一個的字型. 
  • 修改呼叫 mpf.make_mpf_style() 傳回之自訂 K 線圖風格變數 style 的 rc 參數, 不再硬性指定正黑體 (這正是 warning 的原因), 而是由 KBar 物件的 font 屬性取得. 
先在 Windows 上測試 : 

>>> from kbar import KBar   
>>> import yfinance as yf   
>>> df=yf.download('0050.TW', start='2024-07-01', end='2024-08-21', auto_adjust=False)   
[*********************100%***********************]  1 of 1 completed
>>> df.columns=df.columns.map(lambda x: x[0])    # 改成舊版單層索引
>>> kb=KBar(df)    # 未傳 font 參數預設使用正黑體
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)   

結果會使用內建正黑體顯示標題與軸標籤等文字 :




然後刪除 KBar 物件, 重新呼叫 KBar() 並傳入新細明體字型名稱 MingLiU :

>>> del kb   
>>> kb=KBar(df, font='MingLiU')     
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)    

結果文字果然都變成了新細明體 : 




接下來指定一個不存在的字型 "PMingLiU", 這樣會因為找不到此字型而進入 fallback (退回) 模式, 在呼叫 check_font() 時從一組各系統常見中英文字型組成的退回清單中找尋系統有支援的字型組成串列來退回, 在 Windows 上會選擇第一個元素正黑體字型 :

>>> kb=KBar(df, font='PMingLiU')   
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)   
[警告] 找不到字型 'PMingLiU',將使用 fallback 字型   

結果如下 : 




可見傳入不存在的字型會以退回字型顯示, 在 Windows 會退回改用正黑體. 

但是這次的 kbar.py 改版只是把字型從寫死的正黑體中解放出來而已, 並沒有解決在 Colab 上無法顯示中文的問題. 

我向 ChatGPT 尋求 Colab 上的解決方案, 但花了半天做了許多嘗試仍然失敗, 於是轉而向 Claude 求解, 它給出的改版作法是偵測是否在 Colab 執行環境, 是的話就下載中文字型來跑, Claude 果然是 coding 之王者, 解決了 Colab 上的中文顯示問題. 


二. kbar.py 改版 B (Colab 上可顯示中文) : 

經 Cloude 優化改版後的程式碼如下 :

# kbar.py
import mplfinance as mpf
import matplotlib as mpl
from matplotlib import font_manager
import os
import sys
import subprocess

def download_chinese_font():  # 下載中文字體到 Colab 環境
    font_path='/content/NotoSansCJK-Regular.ttc'
    if not os.path.exists(font_path):
        print("正在下載中文字體...")
        url='https://github.com/googlefonts/noto-cjk/raw/main/' +\
            'Sans/OTC/NotoSansCJK-Regular.ttc'
        try:            
            result=subprocess.run(  # 使用 wget 下載字體
                ['wget', '-O', font_path, url],
                capture_output=True,
                text=True
                )            
            if result.returncode != 0:
                print(f'下載失敗: {result.stderr}')
                return None            
            print('中文字體下載完成')
            return font_path
        except Exception as e:
            print(f'字體下載失敗: {e}')
            return None
    else:
        print('字體檔案已存在')
        return font_path

def register_font(font_path):  # 註冊字體並回傳可用的字體名稱
    if not font_path or not os.path.exists(font_path):
        return None    
    try:  # 註冊字體       
        font_manager.fontManager.addfont(font_path)        
        # 重建字體管理器快取
        font_manager._load_fontmanager(try_read_cache=False)        
        # 檢查註冊後可用的字體名稱
        fonts={f.name for f in font_manager.fontManager.ttflist}        
        # Noto Sans CJK 可能的名稱變化
        possible_names=[
            'Noto Sans CJK TC',
            'Noto Sans CJK JP', 
            'Noto Sans CJK SC',
            'Noto Sans CJK KR',
            'Noto Sans CJK'
            ]
        for name in possible_names:
            if name in fonts:
                print(f'成功註冊字體: {name}')
                return name
        print(f"字體註冊後可用名稱: {[f for f in fonts if 'Noto' in f or 'CJK' in f]}")
        return None
    except Exception as e:
        print(f"字體註冊失敗: {e}")
        return None

def detect_font():  # 偵測系統中是否有常見的中文字型 
    fonts={f.name for f in font_manager.fontManager.ttflist}     
    # 檢查常見的中文字體
    if 'Microsoft JhengHei' in fonts:
        return 'Microsoft JhengHei'
    elif 'PingFang TC' in fonts:
        return 'PingFang TC'
    elif 'Noto Sans CJK TC' in fonts:
        return 'Noto Sans CJK TC'
    elif 'Noto Sans CJK JP' in fonts:
        return 'Noto Sans CJK JP'
    elif 'Noto Sans CJK SC' in fonts:
        return 'Noto Sans CJK SC'
    else:  # 檢測是否在 Colab 環境
        in_colab='google.colab' in sys.modules        
        if in_colab:
            print('Colab 環境偵測到,正在設定中文字體...')
            font_path=download_chinese_font()
            if font_path:
                return register_font(font_path)        
        return None

def check_font(font):  # 檢查指定字型是否存在並回傳候選清單
    fonts={f.name for f in font_manager.fontManager.ttflist}
    candidates=[]    
    if font and font in fonts:
        candidates.append(font)
        print(f'使用指定字體: {font}')
    elif font:
        print(f"[警告] 找不到字型 '{font}',將使用 fallback 字型")
    # 通用 fallback 清單
    fallbacks=[  
        'Microsoft JhengHei',
        'PingFang TC',
        'Noto Sans CJK TC',
        'Noto Sans CJK JP',
        'Noto Sans CJK SC',
        'DejaVu Sans',
        'Liberation Sans',
        'Arial',
        'sans-serif'
        ]    
    for fallback in fallbacks:
        if fallback in fonts and fallback not in candidates:
            candidates.append(fallback)
    if candidates:
        print(f'字體候選清單: {candidates[:3]}')  # 只顯示前3個
        return candidates
    else:
        print('警告: 沒有找到合適的字體')
        return ['DejaVu Sans']

class KBar():
    def __init__(self, df, font=None):
        self.df=df
        self.addplots=[]
        self.font=font or detect_font()
        # 設定 matplotlib 全域參數
        if self.font:
            # 清除舊的字體設定
            if 'font.sans-serif' in mpl.rcParams:
                current_fonts=mpl.rcParams['font.sans-serif'].copy()
                # 移除可能造成問題的字體
                current_fonts=[f for f in current_fonts if f not in ['SimHei']]
                mpl.rcParams['font.sans-serif']=[self.font] + current_fonts
            else:
                mpl.rcParams['font.sans-serif']=[self.font, 'DejaVu Sans']
            mpl.rcParams['axes.unicode_minus']=False
            print(f'設定字體為: {self.font}')
        else:
            print('警告: 未找到中文字體,中文可能無法正常顯示')
    
    def addplot(self, data, **kwargs):
        plot=mpf.make_addplot(data, **kwargs)
        self.addplots.append(plot)
    
    def plot(self, embedding=False, **kwargs):
        color=mpf.make_marketcolors(
            up='red',
            down='green',
            inherit=True
            )   
        # 取得字體候選清單
        font_candidates=check_font(self.font)
        style=mpf.make_mpf_style(
            base_mpf_style='default',
            marketcolors=color,
            rc={
                'font.family': font_candidates,
                'font.sans-serif': font_candidates,
                'axes.unicode_minus': False
                }
            )  
        kwargs['type']='candle'
        kwargs['style']=style
        kwargs['addplot']=self.addplots
        result=mpf.plot(self.df, **kwargs)
        if kwargs.get('returnfig', False):
            return result

摘要說明如下 :
  • 新增了專為 Colab 而設的 download_chinese_font() 與 register_font() 函式, 並且修改 detect_font() 函式, 增加偵測執行環境是否為 Colab 的程式碼, KBar 物件初始化時會呼叫 detect_font(), 若偵測到 Colab 環境就呼叫 download_chinese_font() 與 register_font() 函式去下載與註冊 NotoSansCJK 中文字型.
  • check_font() 中的退回字型清單中添加了幾個 NotoSansCJK 相關字型, 當指定的字型不存在時 Matplotlib 就會在退回字型清單中找尋可用之中文字型. 
此新版 kbar 在 Windows 下測試均與上面改版 1 結果相同不另貼圖, 從下列執行過程可知程式會顯示設定之字型與退回時所使用之字型 : 

>>> from kbar import KBar   
>>> import yfinance as yf   
>>> df=yf.download('0050.TW', start='2024-07-01', end='2024-08-21', auto_adjust=False)  
[*********************100%***********************]  1 of 1 completed
>>> df.columns=df.columns.map(lambda x: x[0])    # 改成舊版單層索引   
>>> kb=KBar(df)    # 未傳 font 參數預設使用正黑體
設定字體為: Microsoft JhengHei
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)  
使用指定字體: Microsoft JhengHei
字體候選清單: ['Microsoft JhengHei', 'DejaVu Sans', 'Arial'] 

刪除 KBar 物件, 重建物件時傳入新細明體 font='MingLiU' 測試結果為顯示新細明體 : 

>>> del kb  
>>> kb=KBar(df, font='MingLiU')   
設定字體為: MingLiU
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)   
使用指定字體: MingLiU
字體候選清單: ['MingLiU', 'Microsoft JhengHei', 'DejaVu Sans']

接下來指定使用不存在的字型 'PMingLiU', 會退回使用候選清單中的正黑體 :

>>> del kb   
>>> kb=KBar(df, font='PMingLiU')   
設定字體為: PMingLiU
>>> kb.plot(title='台灣五十(0050.TW)', volume=True)   
[警告] 找不到字型 'PMingLiU',將使用 fallback 字型
字體候選清單: ['Microsoft JhengHei', 'DejaVu Sans', 'Arial']

可見在 Windows 上執行結果與上面改版 1 相同. 

重頭戲是在 Colab, 將此新版 kbar.py 上傳到 Colab, 重新啟動工作階段 : 




再次繪製 K 線圖成功顯示中文字型 :




Claude 再次幫了我大忙節省了不少時間, 讚! 

2025年2月18日 星期二

如何在 Colab 平台設定時區

Colab 是一個非常方便的 Python 執行平台, 除了內建的標準函式庫外, 也已經預先安裝了許多常用的第三方套件 (主要是資料科學與機器學習套件), 幾乎毋須配置環境即可馬上執行 Python 應用程式. 如果套件或模組無法直接 import 進來, 表示 Colab 沒有預先安裝, 可用 !pip install 指令自行安裝. 

本系列之前的文章參考 :

如何在 Colab 中隱藏與取用密鑰

本篇要來測試如何將 Colab 上預設的 UTC 日期時間改為本地時間.


1. 利用 os 與 time 模組設定時區 : 

Colab 的虛擬機器時區預設是 UTC 時區, 例如下列指令執行後印出的是 UTC 時間 :

from datetime import datetime   
print(datetime.now())   




現在台灣時間為下午近五點, 但顯示的卻是早上近九點, 差了 8 小時 (台灣是 UTC+8). 

可以匯入 os 與 time 模組後利用 os.environ['TZ'] 設定時區為 'ASIA/Taipei', 然後呼叫 time.tzset() 函式設定時區, 這樣 datetime.now() 就會印出台灣時間了 : 

import os   
import time   

os.environ['TZ']='Asia/Taipei'  
time.tzset()   





2. 利用第三方套件 pytz 設定時區 :   

先匯入 pytz 模組 (目前 Colab 已經預先安裝了) :

import pytz   

然後呼叫其 timezone() 函式並傳入 'ASIA/Taipei' 將時區設定為台灣時區後, 呼叫 datetime 物件的 astimezone() 方法並傳入 TimeZone 物件即可 : 

tz=pytz.timezone('Asia/Taipei')  
utc_now=datetime.now(pytz.utc)  
print(utc_now)    
print(utc_now.astimezone(tz))    



2024年10月27日 星期日

如何在 Colab 中隱藏與取用密鑰

在測試 OpenAI 或 Line Notify 等 API 功能時都需要註冊申請金鑰 (API key) 或權杖 (token), 通常在測試時為了方便會直接賦值給一個字串變數例如 OPENAI_API_KEY 或 LINE_NITIFY_TOKEN 來使用, 但這種做法常常在將程式分享到 GitHub 或社群時一時忘記刪除真實金鑰或權杖而洩漏, 尤其是 GitHub 有版本控制功能, 即使刪除或更新仍會保留舊資料. 

解決辦法是使用第三方模組例如 dotenv 將金鑰或權杖儲存在系統變數裡, 利用 os 模組的 getenv() 來取得 API Key 或 token (在 Colab 中統稱為密鑰 Secret). 在 Colab 中除了這個方法外還提供了 Secrets 頁面來儲存金鑰或權杖, 在程式中則可利用 google.colab 的 userdata 模組來取得這些機敏資料. 

本系列相關文章參考 :



1. 設定密鑰 (Secret) :   

下面以設定 OpenAI API key 為例說明如何在 Colab 上設定密鑰 :

首先在 Colab 新增一個 ipynb 筆記本後按左方工具列上的鑰匙圖示開啟密鑰設定頁面 : 




按 "+ 新增密鑰" 鈕 : 




依序勾選開啟筆記本存取權, 輸入密鑰名稱 (此處為 OPENAI_API_KEY), 貼上 OpenAI API key, 然後按右上角的 X 關閉設定頁面 (隨時可按鑰匙圖示進來按眼睛圖示右邊的複製鈕將密鑰複製起來) : 




2. 取用密鑰 (Secret) :   

在上面設定密鑰頁面底下就有取用密鑰的語法, 只要呼叫 google.colab.userdata 模組的 get() 函式並傳入密鑰名稱就會傳回密鑰內容 : 

from google.colab import userdata   

由於上面密鑰設定中設定密鑰名稱為 OPENAI_API_KEY, 因此要傳入此字串 : 

openai_api_key=userdata.get("OPENAI_API_KEY")

呼叫 OpenAI API 之前要先安裝 openai 套件 : 

!pip install openai 

參考 :





接著用 userdata.get() 讀取密鑰 :

OPENAI_API_KEY=userdata.get('OPENAI_API_KEY')   

雖然很犯規 (用 Secret 儲存就是要掩藏密鑰啊) 但還是印出來確認看看 :

print(OPENAI_API_KEY)      




密鑰內容正確, 呼叫 OpenAI 類別的建構子 OpenAI() 並傳入密鑰以建立 OpenAI 物件 :

client=OpenAI(api_key=OPENAI_API_KEY)    # 建立 OpenAI 物件

呼叫 OpenAI API (chat.completion.create) : 

chat_completion=client.chat.completions.create(
    messages=[
        {"role": "user",
         "content": "請簡單說明何謂量子糾纏?",
        }],
    model="gpt-4o-mini",
    )     

取得回應內容 : 

print(chat_completion.choices[0].message.content)    

結果如下 : 

量子糾纏是一種量子力學現象,當兩個或多個粒子在某種方式上互相聯繫時,這些粒子的量子狀態將不再獨立,而是形成一種整體的狀態。即使這些粒子相隔很遠,對其中一個粒子的測量會立即影響到另一個粒子的狀態。這意味著它們之間存在著某種超越經典物理的「瞬時」聯繫。

量子糾纏的特性挑戰了我們對物質和信息傳遞的傳統理解,並且在量子通信和量子計算等領域具有重要的應用潛力。




我們可以將呼叫 OpenAI API 的程序寫成一個函式 ask_gpt() :

def ask_gpt(prompt, api_key, model='gpt-4o-mini'):
    client=OpenAI(api_key=api_key)
    chat_completion=client.chat.completions.create(
        messages=[
            {"role": "user",
             "content": prompt,
            }],
        model=model,
        )
    return chat_completion.choices[0].message.content

結果如下 : 

量子糾纏是一種量子力學現象,當兩個或多個量子系統(例如,粒子)相互作用並形成一個整體時,它們的狀態會變得互相關聯,即使這些粒子相隔很遠。這意味著對一個粒子的測量會立即影響到另一個粒子的狀態,不管它們之間的距離有多遠。

舉例來說,如果兩個糾纏的粒子被創造出來,並且其中一個被測量為某種特定的狀態,那麼另一個粒子的狀態也會瞬間變成與之相對應的狀態。這種現象挑戰了我們對距離和獨立性的直觀理解,並引發了許多關於量子力學和宇宙基本性質的討論與研究。量子糾纏在量子計算、量子通訊等技術中具有重要的應用潛力。




除了 OpenAI API key 外, 任何其他不想因為不小心洩漏的密鑰都可以放在 Secret 中, 例如要在 Colab 筆記本上發出 LINE Notify 推播訊息, 可以將其權杖 (token) 存入 Secret 中, 然後透過 userdata.get() 取用. 

2024年9月14日 星期六

如何在 Colab 解壓縮 zip 檔

當要上傳很多檔案到 Colab 時, 一個一個上傳會很花時間, 最好是將這些檔案在本機壓縮成 zip 檔上傳 Colab 後再解壓縮. 

本系列之前的文章參考 :


首先在本機中將要上傳的檔案壓縮成 zip 檔, 例如要辨識的車牌圖檔 : 




按 Colab 檔案管理框上面的上傳按鈕, 點選要上傳的 zip 檔 :





可見 plates.zip 已上傳至 Colab 的預設資料夾 /content 下了. 

在 Colab 中解壓縮 zip 檔有兩個做法 : 


1. 使用 !unzip 指令 :

語法如下 :

!unzip 壓縮檔路徑檔名 -d 解壓目的地資料夾路徑

如果沒有指定資料夾路徑的話, 預設就是根目錄下的 /content 資料夾, 此處我想將解壓縮後的檔案放在 /content/plates 下, 所以先在檔案管理框按滑鼠右鍵點選 "新增資料夾", 然後輸入 plates :





然後在儲存格輸入下列指令 :

!unzip plates.zip -d plates    

也可以明確加上路徑 :

!unzip /content/plates.zip -d /content/plates    




這樣便完成解壓縮了. 點擊 ABC5678.jpg 會在最右邊顯示此車牌圖檔 :




2. 使用 zipfile 模組解壓縮 : 

zipfile 是 Python 的內建模組, 裡面有一個 ZipFile 類別可用來解壓縮 zip 檔, 呼叫其建構式 ZipFile() 並傳入 zip 檔之路徑檔名以 'r' 模式開啟 zip 檔後, 呼叫檔案參考物件之 extractall() 方法即可, 語法如下 :

with ZipFile('zip 檔之路徑檔名', 'r') as z : 
    z.extractall('目的地資料夾路徑') 

先在 Colab 檔案管理框預設資料夾 /content 下新增一個 plates2 資料夾來存放解壓後之圖檔 :




然後在程式碼儲存格輸入下列指令 :

from zipfile import ZipFile
with ZipFile('plates.zip', 'r') as z:
    z.extractall('plates2')

執行後檢查 plates2 資料夾果然圖檔都已解壓在此 :




如前所述, 當 Colab 連線終止時這些上傳到 Linux 虛擬機器上的資料都會被清掉, 所以也不需要特地去刪除. 但如果有節省儲存空間考量需動態刪除不用的資料夾, 可以用 shutil 模組的 rmtree() 函式來刪除 :

import shutil  
shutil.rmtree('plates2')  




可見 plates2 資料夾已被刪除. 注意, 如果是手動操作, Colab 的檔案管理框無法直接刪除一個裡面還有檔案的資料夾, 必須將裡面的檔案一一刪除成為空資料夾後才能刪除它. 

參考 :


2024年9月13日 星期五

如何管理 Colab 上的檔案 (上傳/下載/更名/刪除)

谷歌 Colab 平台是非常方便好用的 Python 應用執行環境, 只要連線其 Linux 虛擬機器, 即可用與本機 Jupyter Notebook 一樣的操作方式執行 Python 程式碼. 如果執行程式時需要用到外部檔案, 則必須上傳到 Colab 虛擬機器的檔案系統下才行.

本系列之前的文章參考 :

如何在 Colab 開啟終端機 (xterm)

首先開啟 Colab 的 ipynb 筆記本檔案, 如果尚未連線則要按右上角的連線按鈕 : 





連線完成會顯示 "已連線" 或 RAM/磁碟使用情形 : 




然後按左邊導覽列上的檔案系統按鈕 (已連線狀態下按此按鈕才會出現檔案管理框) : 




這時就會出現一個檔案管理框, 內建已經有一個 sample_data 資料夾, 點一下開啟它會看到裡面預設已經放了好幾個資料科學常用的 .csv 與 .json 資料集 :




點擊這些檔案會在頁面最右端顯示檔案內容, txt 或 json 會以純文字編輯器開啟, 可以在上面編輯內容 (具備自動存檔功能) :




如果是 csv 檔則以網頁表格顯示, 無法線上編輯 :




如果是圖檔也是會在網頁中顯示此圖片. 

按一下檔案管理框最上面的上一層 .. 鈕會往上跳一層到系統根目錄, 其下的 /content 就是 Colab 的預設資料夾, 如果沒有特別切會, 上傳的檔案會放在 /content 下. 

但如果是在根目錄下上傳, 檔案會被放在 /var 資料夾下, 例如我在根目錄下上傳一張車牌圖檔 333-AD.jpg, 會先出現一個提醒視窗, 表示上傳到 Colab 的任何檔案都會在連線結束時連同虛擬機器一起被清掉 (連線時間最長是 12 小時) :




上傳到根目錄的檔案會被放在 /var 資料夾下 :




如果是在預設資料夾下上傳, 則檔案會放在 /content 下 : 




點選檔案右邊的三個點按鈕可以對該檔案進行下載/刪除/改名等操作 :





注意, Colab 虛擬機器檔案系統中的檔案都會在連線終止時消失, 因此若使用 Colab 跑 Python 程式, 需注意連線逾時問題即時下載或備份至雲端硬碟, 避免產生的資料因為連線終止而消失. 

而 ipynb 筆記本檔案則是存放於雲端硬碟的 Colab Notebooks 資料夾下面, 不會隨虛擬機器連線終止而消失 : 




2022年11月10日 星期四

如何在 Colab 開啟終端機 (xterm)

今天繼續自然語言處理內訓課程 Day 2, 在課堂上學到如何在 Colab 開啟終端機介面, 只要在程式碼儲存格中輸入如下指令安裝 colab-xterm 後載入執行 xterm 即可 :

!pip install colab-xterm
%load_ext colabxterm
%xterm

注意 load_ext 指令是載入 colabxterm 而非 colab-xterm, 結果如下 : 




習慣用終端機命令列的人應該會很喜歡這個功能. 

參考 : 

2022年3月22日 星期二

SpaCy 學習筆記 (三) : 在 Colab 上安裝執行 SpaCy

前兩篇的 SpaCy 測試都是在本機或其官網上執行, 本篇則是要在谷歌 Colab 平台上安裝 SpaCy 與語言統計模型後利用 Google 雲端計算資源來執行自然語言處理管線, 好處是完全不需要擔心本機安裝時可能會遇到的問題, 且可將執行 NLP 任務的過程紀錄下來或分享到 GitHub. 

本系列之前的文章參考 :


Colab 是整合於谷歌雲端硬碟上的免費線上運算平台, 用法可參考這篇 : 



一. 在雲端硬碟建立資料夾與 Colab : 

在谷歌雲端硬碟上安裝 SpaCy 套件與模型之前, 基於資料整理歸檔考量, 最好先建立一個資料夾來收納 ipynb 等相關檔案, 首先按雲端硬碟左上角的 " + 新增" 鈕, 點選 "資料夾" :




在彈出視窗中輸入資料夾名稱, 例如 "SpaCy NLP", 按 "建立" 鈕 :




如此便在雲端硬碟下建立了 "SpaCy NLP" 這個資料夾, 點選進入此資料夾後, 再次按左上角的 "+新增" 鈕, 點選選單中最底下的 "更多", 於次選單中點選 "Google colaboratory" : 




 這樣會新增並開啟一個 Colab 筆記本, 也就是一個 Jupyter Notebook 頁面 (.ipynb 檔) :




接著就可以在此 .ipynb 檔案上安裝 SpaCy 套件與其語言模型了. 


二. 安裝或升版 SpaCy 套件 : 

在 Colab 筆記本的程式碼欄位內輸入下列指令安裝 SpaCy :

!pip install spacy    

按欄位前面的 Play 鍵執行安裝, 結果顯示之前已安裝過 2.2.4 版 :




若要升級到最新版, 按左上角 "+程式碼" 鈕新增一個儲存格, 輸入升版指令 : 

!pip install -U spacy 




可見下載安裝了最新的 SpaCy 3.2.3 版. 

可再新增儲存格輸入如下指令檢查 SpaCy 版本 : 

import spacy  
spacy.__version__    

結果如下 : 




可見確實是 SpaCy 3.2.3 版.


三. 安裝語言的統計模型 : 

在 Colab 安裝 SpaCy 的語言模型必須使用 pip 指令線上安裝, 每個語言模型的下載網址可在 SpaCy 官網查詢 :


例如英文與中文的模型網址如下 : 


以體積最小的英文模型 en_core_web_sm 為例, 其模型之 whl 檔或 gz 檔查詢方式如下 :






其模型網址為 :

https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.2.0/en_core_web_sm-3.2.0-py3-none-any.whl   (13.3 MB)

於 Colab 筆記本新增一儲存格, 輸入如下指令安裝此英文模型 : 

!pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.2.0/en_core_web_sm-3.2.0-py3-none-any.whl     

結果如下 : 





中文模型 zh_core_web_sm 的 whl 檔網址為 :

https://github.com/explosion/spacy-models/releases/download/zh_core_web_sm-3.2.0/zh_core_web_sm-3.2.0-py3-none-any.whl    (47.2 MB)

於 Colab 筆記本新增一儲存格, 輸入如下指令安裝此英文模型 : 

!pip install https://github.com/explosion/spacy-models/releases/download/zh_core_web_sm-3.2.0/zh_core_web_sm-3.2.0-py3-none-any.whl    

結果如下 : 




這樣就可以開始在 Colab 上執行 NLP 管線任務了, 依序新增儲存格輸入如下指令 : 

import spacy
nlp=spacy.load('en_core_web_sm')
doc=nlp('I am going to visit the White House.')
print(f'text\tlemma_\ttag_\tpos_\tdep_\thead')  
for token in doc:
    fstr=(f'{token.text}\t{token.lemma_}\t{token.tag_}\t{token.pos_}\t'
          f'{token.dep_}\t{token.head}')
    print(fstr)

結果如下 : 




注意, 此例中因為 f 字串太長了, 所以將其分兩列放在小括號 () 中. 


四. 語法相依關係與命名實體視覺化 :

在 Colab 執行視覺化與在本機使用 Jupyter Notebook 一樣必須呼叫 doc.render() 而不是 doc.serve(), 而且必須傳入參數 jupyter=True 才能在網頁中繪製圖形. 為了簡單起見, 下面改為分析 'I live in Taiwan' 這個句子來分析, 新增儲存格輸入如下指令 :

doc=nlp('I live in Taiwan.')    
spacy.displacy.render(doc, jupyter=True)       

參考 : 


結果如下 : 




可見 displaycy.render() 不傳入 style 參數時預設是 'dep', 即繪製語法相依圖. 若傳入 style='ent' 則顯示命名實體標記圖, 例如 : 

spacy.displacy.render(doc, style='ent', jupyter=True)

結果如下 : 




上面也安裝了中文模型 zh_core_web_sm, 所以最後來試試中文的視覺化, 輸入如下指令 :

nlp=spacy.load('en_core_web_sm')    
text='''戴笠, 浙江省江山縣人, 中華民國陸軍中將, 生於清光緒 23 年,
國民政府軍統局少將副局長, 蔣介石愛將, 民國 35 年 3 月 17 日於南京上空飛機失事身亡.'''   
doc=nlp(text)   
spacy.displacy.render(doc, style='ent', jupyter=True)    

結果如下 : 




與前一篇測試使用 SpaCy 線上視覺化的輸出有些不一樣, 例如 '蔣介石愛將' 被標記為 ORG (很奇怪), 以及 '南京' 未被辨識出來等, 其餘相同. 

在 Colab 上執行 SpaCy 最方便的地方就是會自動記錄執行過程為 ipynb 檔, 以上測試過程我分享於 GitHub, 開啟後可點選 "檔案/在雲端硬碟另存複本" 複製到個人的雲端硬碟上 :