2024/06/17

gradio

gradio 是一組 python library,可快速建立網頁介面的 API,目的是提供給 Machine Learning Model demo 的使用者操作介面。大部分的 AI model 是以 python 開發,且使用程式或文字介面操作,gradio 可快速將使用介面轉換為網頁應用程式,不需要撰寫網頁 css, javascript 等程式碼。

Quickstart 是 gradio 的快速入門說明

安裝 gradio

pip3 install gradio

在執行時,遇到這樣的錯誤訊息

ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips  26 Jan 2017'. See: https://github.com/urllib3/urllib3/issues/2168

根據 python - ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with LibreSSL 2.8.3 - Stack Overflow 的說明,要 downgrade urllib3

pip3 uninstall urllib3
pip3 install 'urllib3<2.0'

test1.py,launch 時加上 server_name="0.0.0.0" 參數,可讓機器所有 network interfaces 在 TCP Port 7860 提供網頁服務

import gradio as gr

def greet(name, intensity):
    return "Hello " * intensity + name + "!"

demo = gr.Interface(
    fn=greet,
    inputs=["text", "slider"],
    outputs=["text"],
)

demo.launch(server_name="0.0.0.0")

在瀏覽器連接 http://localhost:7860

如果在 launch 加上參數 share=True

啟動時,會出現這樣的訊息

$ python3 test1.py
Running on local URL:  http://127.0.0.1:7860
Running on public URL: https://424222ce32aa52ca72.gradio.live

This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to Spaces (https://huggingface.co/spaces)

這表示可透過一個 public redirect 的網址,連接到這個 service

References

gradio

Creating A Chatbot Fast

2024/06/03

log4j2

java 開發環境,JDK 預設有提供 logging 機制,但 logging 機制的發展,也是一段精彩的歷史。就以往使用的經驗,從一開始的 log4j,後來換成了 logback + slf4j,到現在這個階段,似乎又要換成 log4j2,因為 log4j2 的效能評估,結果已經是又超過了 logback。

logging framwork 的發展可以參考這一篇文章: Java Logging 的歷史與戰爭

但這種情況也說明了,開放且多元的 java 開發環境,一個必要且熱門的 framework,會吸引大家注意並不斷提出更好的框架,但使用者就得跟著時代不斷的進步。

使用 log4j 用 maven 引用 library,在 pom.xml 的 dependencies 裡面加上 log4j2 及 slf4j。

slf4j 是 logging 的 wrapper,可用一致 API 介面進行程式開發,在不修改程式的狀況下,可替換內部的實作為 logback 或 log4j 或 jdk logging。

    <dependencies>
        <!-- log4j2 + slf4j -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.22.1</version>
        </dependency>
    </dependencies>

在 classpath 裡面加上設定檔 log4j2.xml

以下是調整後的設定

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
        </Console>

        <!--
        <File name="FILE" fileName="testweb.log">
            <PatternLayout>
                <Pattern>%d [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
        </File>
        -->
        <RollingFile name="RollingFile" fileName="logs/testweb.log" filePattern="logs/testweb-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout>
                <Pattern>%d [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy />
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${baseDir}" maxDepth="2">
                    <IfFileName glob="*/test-*.log.gz" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!-- hibernate -->
        <logger name="org.hibernate" level="INFO" />
        <Logger name="org.hibernate.SQL" level="DEBUG" />
        <logger name="org.hibernate.cfg" level="ERROR" />

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

RollingFile 的部分可以參考官方說明Log4j 2 Appenders

Policies 的部分,決定什麼時候要 trigger rolling 的機制。OnStartupTriggeringPolicy 代表程式啟動要做一次。TimeBasedTriggeringPolicy 則是跟著 Pattern 裡面日期的最小精度的部分 trigger,目前日期是設定為日,所以這個範例,每天會處理一次。SizeBasedTriggeringPolicy 則是會判斷 log file 的檔案大小 trigger,pattern 部分會因為 %i 這個設定,每次會將檔案改為 -1, -2 這樣的檔名。

DefaultRolloverStrategy 可將超過 30 天,符合glob 設定的 pattern 的檔案刪除。

使用 logging 是透過 sl4j 介面

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    Logger log = LoggerFactory.getLogger(this.getClass().getName());
    public void sayHello(){
        log.debug("This will be printed on debug");
        log.info("This will be printed on info");
        log.warn("This will be printed on warn");
        log.error("This will be printed on error");

        log.info("Appending string: {}.", "Hello, World");
    }
}

log4j2 對效能最大的改進點在非同步的 file appender Log4j 2 Appenders

調整 log4j2.xml 設定,基於上面的 RollingFile 增加 Appenders。

bufferSize 預設為 1024,代表可暫存 1024 則 log,必須要設定為 2 的指數。

        <Async name="AsyncRollingFile" bufferSize="262144">
            <AppenderRef ref="RollingFile"/>
        </Async>

然後修改下面的 AppenderRef,就可以換成 AsyncRollingFile

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncRollingFile"/>
        </Root>

References

Log4j2中RollingFile的文件滚动更新机制 - Ye_yang - 博客园

Java Logging Frameworks: log4j vs logback vs log4j2

Log4j; Performance

2024/05/27

Guava Reflection

TypeToken

因為 java 有 type erasure 特性,無法在 runtime 得知 generic Class 物件

TypeToken 使用了 workaround 方法,可處理 generict types,就是能夠在 runtime 檢測 Generic Type

    @Test
    public void typeToken() {
        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));
        // returns true, even though ArrayList<String> is not assignable from ArrayList<Integer>

        TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
        TypeToken<List<Integer>> integerListToken = new TypeToken<List<Integer>>() {};
        TypeToken<List<? extends Number>> numberTypeToken = new TypeToken<List<? extends Number>>() {};
        assertFalse(stringListToken.isSubtypeOf(integerListToken));
        assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
        assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
    }

利用 TypeToken 在 runtime 取得 complex type 資訊

    abstract class ParametrizedClass<T> {
        TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
    @Test public void typeToken2() throws NoSuchMethodException {
        //// 產生一個 class 裡面儲存了 TypeToken 欄位
        ParametrizedClass<String> parametrizedClass = new ParametrizedClass<String>() {};
        assertEquals(parametrizedClass.type, TypeToken.of(String.class));

        //// 也能產生 TypeToken of a complex type,裡面有多個 generic type
        // runtime 還是能取得 type 資訊
        TypeToken<Function<Integer, String>> funToken
                = new TypeToken<Function<Integer, String>>() {};
        TypeToken<?> funResultToken = funToken
                .resolveType(Function.class.getTypeParameters()[1]);
        assertEquals(funResultToken, TypeToken.of(String.class));

        /// 也能在 Map 裡面得知 entry 的 data type
        TypeToken<Map<String, Integer>> mapToken
                = new TypeToken<Map<String, Integer>>() {};
        TypeToken<?> entrySetToken = mapToken
                .resolveType(Map.class.getMethod("entrySet")
                        .getGenericReturnType());
        assertEquals(
                entrySetToken,
                new TypeToken<Set<Map.Entry<String, Integer>>>() {});
    }

Invokable

Invokable 是 java.lang.reflec.Method 及 java.lang.reflect.Constructor 的 fluent wrapper,提供簡化後的 API

    @Test
    public void invokable() throws NoSuchMethodException {
        Method method = CustomClass.class.getMethod("somePublicMethod");
        Invokable<CustomClass, ?> invokable
                = new TypeToken<CustomClass>() {}
                .method(method);
        // 分別透過 Guava 及 標準的 JAVA reflection API 檢測是否有 somePublicMethod 這個 method
        boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
        boolean isPublicGuava = invokable.isPublic();

        assertTrue(isPublicStandradJava);
        assertTrue(isPublicGuava);

        ////////
        // checking if a method is overridable
        Method method2 = CustomClass.class.getMethod("notOverridablePublicMethod");
        // 用 Guava TypeToken 比較簡單
        Invokable<CustomClass, ?> invokable2
                = new TypeToken<CustomClass>() {}.method(method2);

        // 用標準 java 的方法
        boolean isOverridableStandardJava = (!(Modifier.isFinal(method2.getModifiers())
                || Modifier.isPrivate(method2.getModifiers())
                || Modifier.isStatic(method2.getModifiers())
                || Modifier.isFinal(method2.getDeclaringClass().getModifiers())));
        boolean isOverridableFinalGauava = invokable2.isOverridable();

        assertFalse(isOverridableStandardJava);
        assertFalse(isOverridableFinalGauava);
    }

References

Guide to Guava's Reflection Utilities | Baeldung

ReflectionExplained · google/guava Wiki · GitHub