高效查询流式数据的方法
原文:
towardsdatascience.com/a-performant-way-to-query-streaming-data-b95c52b2361e
使用 ksqlDB 改进实时流式项目
·发表于 Towards Data Science ·阅读时间 13 分钟·2023 年 5 月 22 日
–
北卡罗来纳州。图像由作者提供。
在 上一篇文章 中,我们探讨了一个实时流式项目的组件,该项目使用 FastAPI、Kafka、QuestDB 和 Docker 消耗和处理智能手机传感器数据。这个项目是实现一种架构的初步尝试,该架构可以将智能手机的流式数据通过 Kafka 日志传输到时间序列数据库,在那里数据可以被轻松查询和处理。最终产品是一个仪表盘,它轮询数据库并实时显示传感器读数:
[## 使用 FastAPI、Kafka、QuestDB 和 Docker 流式传输智能手机数据
该视频演示了如何使用 FastAPI、Kafka、QuestDB 和…可视化实时智能手机传感器数据。
youtube.com](https://youtube.com/shorts/zRUVvz5vsl8?feature=share&source=post_page-----b95c52b2361e--------------------------------)
对该项目的一个批评是,由于从 Kafka 写入数据到数据库并查询数据库以显示最新的传感器读数,引入了不必要的延迟。当我们的主要目标是实时分析数据时,写入和读取数据库变得低效。
这是 ksqlDB 被创建来解决的问题之一。与将数据写入数据库并查询分析不同,ksqlDB 允许直接处理和分析数据流,从而消除了在访问数据之前将数据持久化到数据库中的需求。
本文将通过引入 ksqlDB 进行查询和处理流数据来扩展之前的内容。与传统的数据库轮询不同,在 ksqlDB 中实现 推送查询 显著减少了仪表盘的延迟并简化了后端基础设施。用于构建此项目的所有代码都可以在 GitHub 上找到:
[## GitHub - hfhoffman1144/smartphone_sensor_stream2: 使用 FastAPI 流式传输智能手机传感器数据…
从传感器记录器流式传输智能手机数据(见…
github.com](https://github.com/hfhoffman1144/smartphone_sensor_stream2?source=post_page-----b95c52b2361e--------------------------------)
最终产品
本项目的目标与之前相同:开发一个实时仪表盘,以 可视化传感器数据。然而,在这一迭代中,我们的重点是通过利用 ksqlDB 的强大功能来最小化手机和仪表盘之间的可感知延迟。新的仪表盘应如下所示:
从 ksqlDB 查询的智能手机加速度计数据。图像由作者提供。
智能手机的三轴加速度计数据被发送到一个 FastAPI 应用程序,写入 Kafka,通过 ksqlDB 查询,并在仪表盘上显示。注意图表对手机移动的响应速度——延迟几乎无法察觉。
该项目还支持来自多部智能手机的流数据:
同时从两部智能手机流式传输数据。图像由作者提供。
项目架构
该项目的架构比 之前 更简单,因为不再需要 QuestDB 及其消费者来将数据传输到仪表盘。
项目架构。图像由作者提供(使用 Lucidchart 制作)。
每部智能手机通过 POST 请求将传感器读数(加速度计、陀螺仪和磁力计)发送到一个 FastAPI 应用程序(生产者)。生产者将请求体重新格式化为 ksqlDB 兼容的 JSON 格式,并异步地将传感器读数写入一个 Kafka 主题。一旦传感器数据到达 Kafka 主题,就可以通过 ksqlDB 进行查询。
为了获取连续的传感器数据流,客户端可以与后端(一个 FastAPI 应用程序)建立一个服务器推送事件(SSE)连接。后端通过 ksqlDB API 发起一个推送查询,持续将传感器数据发送到前端。
这是项目的目录:
├── dashboard_backend
│ ├── Dockerfile
│ ├── app
│ │ ├── core
│ │ │ ├── config.py
│ │ │ └── utils.py
│ │ ├── db
│ │ │ └── data_api.py
│ │ ├── main.py
│ │ └── models
│ │ └── sensors.py
│ ├── entrypoint.sh
│ └── requirements.txt
├── dashboard_frontend
│ ├── Dockerfile
│ ├── app
│ │ ├── main.py
│ │ ├── static
│ │ │ └── js
│ │ │ └── main.js
│ │ └── templates
│ │ └── index.html
│ ├── entrypoint.sh
│ └── requirements.txt
├── producer
│ ├── Dockerfile
│ ├── app
│ │ ├── __init__.py
│ │ ├── core
│ │ │ ├── config.py
│ │ │ └── utils.py
│ │ ├── main.py
│ │ └── schemas
│ │ └── sensors.py
│ ├── entrypoint.sh
│ └── requirements.txt
├── docker-compose.yml
三个FastAPI应用程序被编写以促进数据流动和可视化——生产者、仪表板前端和仪表板后端。这些应用程序与 Kafak 和 ksqlDB 一起,通过docker-compose进行编排:
version: '3.8'
services:
zookeeper:
image: bitnami/zookeeper:latest
ports:
- 2181:2181
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
kafka:
image: bitnami/kafka:latest
ports:
- 9092:9092
- 9093:9093
environment:
- KAFKA_BROKER_ID=1
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
- ALLOW_PLAINTEXT_LISTENER=yes
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CLIENT:PLAINTEXT
- KAFKA_CFG_LISTENERS=CLIENT://:9092
- KAFKA_CFG_ADVERTISED_LISTENERS=CLIENT://kafka:9092
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=CLIENT
depends_on:
- zookeeper
ksqldb-server:
image: confluentinc/ksqldb-server:latest
hostname: ksqldb-server
container_name: ksqldb-server
depends_on:
- kafka
ports:
- "8088:8088"
environment:
KSQL_LISTENERS: http://0.0.0.0:8088
KSQL_BOOTSTRAP_SERVERS: kafka:9092
KSQL_KSQL_LOGGING_PROCESSING_STREAM_AUTO_CREATE: "true"
KSQL_KSQL_LOGGING_PROCESSING_TOPIC_AUTO_CREATE: "true"
KSQL_KSQL_STREAMS_COMMIT_INTERVAL_MS: 100
KSQL_KSQL_IDLE_CONNECTION_TIMEOUT_SECONDS: 600
ksqldb-cli:
image: confluentinc/ksqldb-cli:latest
container_name: ksqldb-cli
depends_on:
- kafka
- ksqldb-server
entrypoint: /bin/sh
tty: true
producer:
build:
context: ./producer
dockerfile: Dockerfile
command: uvicorn main:app --workers 1 --host 0.0.0.0 --port 8000
ports:
- 8000:8000
env_file:
- .env
depends_on:
- kafka
- zookeeper
dashboard_backend:
build:
context: ./dashboard_backend
dockerfile: Dockerfile
command: uvicorn main:app --workers 1 --host 0.0.0.0 --port 5000
ports:
- 5000:5000
env_file:
- .env
depends_on:
- ksqldb-server
dashboard_frontend:
build:
context: ./dashboard_frontend
dockerfile: Dockerfile
command: uvicorn main:app --workers 1 --host 0.0.0.0 --port 4200
ports:
- 4200:4200
env_file:
- .env
depends_on:
- dashboard_backend
注意那些未在代码中明确写出的四个服务(值得庆幸的是):Zookeeper、Kafka、ksqlDB 和 Kafka-CLI。这些服务与生产者和仪表板一起工作,构建了该项目。让我们更详细地探索这些组件。
生产者
与之前类似,生产者是一个 FastAPI 应用程序,接受来自智能手机的数据(通过 POST 请求),并写入 Kafka 日志。以下是目录结构:
producer
├── Dockerfile
├── app
│ ├── __init__.py
│ ├── core
│ │ ├── config.py
│ │ └── utils.py
│ ├── main.py
│ └── schemas
│ └── sensors.py
├── entrypoint.sh
└── requirements.txt
我们不会逐一浏览生产者目录中的每个文件,因为所有内容都可以在GitHub上找到。相反,让我们看看main.py
(生产者 API 的驱动脚本):
# producer/app/main.py
import json
from fastapi import FastAPI
import asyncio
from aiokafka import AIOKafkaProducer
from schemas.sensors import SensorReading, SensorResponse
from core.config import app_config
from core.utils import flatten_dict
from loguru import logger
# Instantiate FastAPI app
app = FastAPI(title=app_config.PROJECT_NAME)
# Create the event loop to use async programming
loop = asyncio.get_event_loop()
# Instatiate the Kafka producer object
producer = AIOKafkaProducer(
loop=loop,
client_id=app_config.PROJECT_NAME,
bootstrap_servers=app_config.KAFKA_URL
)
@app.on_event("startup")
async def startup_event():
await producer.start()
await producer.send(app_config.TOPIC_NAME, json.dumps({'status':'ready'}).encode("ascii"))
@app.on_event("shutdown")
async def shutdown_event():
await producer.stop()
@app.post("/phone-producer/")
async def kafka_produce(data: SensorReading):
"""
Produce a message containing readings from a smartphone sensor to Kafka.
Parameters
----------
data : SensorReading
The request body containing sensor readings and metadata.
Returns
-------
response : SensorResponse
The response body corresponding to the processed sensor readings
from the request.
"""
# Extract the messageId, deviceId, and sessionId
message_info = data.dict().copy()
message_info.pop('payload')
# Write each sensor reading in the payload to kafka
for sensor_reading in data.dict()['payload']:
kafka_message = {**flatten_dict(sensor_reading), **message_info}
await producer.send(app_config.TOPIC_NAME,
json.dumps(kafka_message).encode("ascii"))
response = SensorResponse(
messageId=data.messageId,
sessionId=data.sessionId,
deviceId=data.deviceId
)
logger.info(response)
return response
这段代码的解释基本与前一篇文章%3A)相同。主要区别在于请求有效载荷中的传感器读数需要在写入 Kafka 之前重新格式化。这种新格式允许 ksqlDB 以类似 SQL 的方式查询传感器数据。每个来自手机的 POST 请求发送的 JSON 数据如下所示:
{"messageId": 20,
"sessionId": "4bf3b3b9-a241-4aaa-b1d3-c05100df9976",
"deviceId": "86a5b0e3-6e06-40e2-b226-5a72bd39b65b",
"payload": [{"name": "accelerometeruncalibrated",
"time": "1671406719721160400",
"values": {"z": -0.9372100830078125,
"y": -0.3241424560546875,
"x": 0.0323486328125}},
{"name": "magnetometeruncalibrated",
"time": "1671406719726579500",
"values": {"z": -5061.64599609375,
"y": 591.083251953125,
"x": 3500.541015625}},
{"name": "gyroscopeuncalibrated",
"time": "1671406719726173400",
"values": {"z": -0.004710599314421415,
"y": -0.013125921599566936,
"x": 0.009486978873610497}},
...
]}
各个传感器读数位于“payload”下,并在kafka_produce()
路由中写入 Kafka:
# Extract the messageId, deviceId, and sessionId
message_info = data.dict().copy()
message_info.pop('payload')
# Write each sensor reading in the payload to kafka
for sensor_reading in data.dict()['payload']:
kafka_message = {**flatten_dict(sensor_reading), **message_info}
await producer.send(app_config.TOPIC_NAME,
json.dumps(kafka_message).encode("ascii"))
flatten_dict()
函数位于producer/app/core/utils.py
中,它处理原始传感器消息,例如:
{
"name": "accelerometeruncalibrated",
"time": "1683555956851304200",
"values": {
"z": -1.0012664794921875,
"y": -0.467315673828125,
"x": -0.00494384765625
}
}
并将消息重新格式化以兼容 ksqlDB 模式——这就是写入 Kafka 的内容:
{
"name": "accelerometeruncalibrated",
"time": "1683555956851304200",
"values_z": -1.0012664794921875,
"values_y": -0.467315673828125,
"values_x": -0.00494384765625,
"messageId": 35,
"sessionId": "c931f349-faf5-4e45-b09f-c623a76ef93a",
"deviceId": "86a5b0e3-6e06-40e2-b226-5a72bd39b65b"
}
重新格式化的传感器读数中的每一条记录可以看作是一个可以被 ksqlDB 查询的列。更多内容将在下一节中介绍。
ksqlDB
此时,传感器读数可以从智能手机流向 FastAPI 生产者,在那里以 ksqlDB 兼容格式写入 Kafka。ksqlDB 随后可以查询 Kafka 中的最新数据和历史数据。
ksqlDB是一个开源流式引擎,旨在使用类似 SQL 的语法处理、分析和转换来自 Kafka 的实时数据流。简而言之,ksqlDB 使得使用熟悉的关系数据库概念(如表、查询、物化视图、查询、连接和聚合)与 Kafka 主题中的数据进行交互。ksqlDB 的功能非常广泛,本文章无法全面涵盖,但文档提供了核心概念的良好定义:
了解 ksqlDB 的内部工作原理。
对于这个项目,将在存储智能手机传感器读数(生产者写入的数据)的主题上创建一个流:
CREATE STREAM smartphone_sensor_stream (
name VARCHAR,
time BIGINT,
values_x DOUBLE,
values_y DOUBLE,
values_z DOUBLE,
messageId BIGINT,
sessionId VARCHAR,
deviceId VARCHAR
) WITH (
KAFKA_TOPIC = 'smartphone-sensor-data',
VALUE_FORMAT = 'JSON'
);
上述 ksqlDB 语句创建了一个流smartphone_sensor_stream
,可用于查询写入smartphone-sensor-data
Kafka 主题的传感器读数。可以利用像ksql-python这样的库来与 ksqlDB REST API 进行接口交互,并以编程方式执行查询:
from ksql import KSQLAPI # pip install ksql
# Where ksqldb is running
KSQL_URL = "http://localhost:8088"
# Instantiate the ksqldb API object
client = KSQLAPI(KSQL_URL)
# Create the "smartphone_sensor_stream" stream over the specified topic
client.create_stream(table_name="smartphone_sensor_stream",
columns_type=["name varchar",
"time bigint",
"values_x double",
"values_y double",
"values_z double",
"messageId bigint",
"sessionId varchar",
"deviceId varchar"
],
topic="smartphone-sensor-data",
value_format="JSON")
一个推送查询被执行以检索传感器读数,因为它们被写入主题中。从本质上讲,推送查询打开一个长期连接,每当主题中接收到新数据时,都会将更新发送给客户端。这使得推送查询成为流式智能手机数据的良好选择。
select deviceId,
time,
values_x,
values_y,
values_z
from smartphone_sensor_stream
where name = 'accelerometeruncalibrated'
emit changes
上述查询每次流更新数据时“推送”设备 ID、时间和加速度计值,从smartphone_sensor_stream
中提取。这可以使用 ksql-python 执行:
from ksql import KSQLAPI
from typing import Generator
# Where ksqldb is running
KSQL_URL = "http://localhost:8088"
# Instantiate the ksqldb API object
client = KSQLAPI(KSQL_URL)
# Write a push query
push_query = '''select deviceId,
time,
values_x,
values_y,
values_z
from smartphone_sensor_stream
where name = 'accelerometeruncalibrated'
emit changes
'''
# Get the KSQL stream generator
sensor_push_stream: Generator = client.query(push_query, use_http2=True)
# Loop through messages in the generator and print them as they're received
for raw_message in sensor_push_stream:
print(raw_message)
ksql-python 客户端返回一个生成器对象,该对象会在写入 Kafka 并从流中读取时产生消息。与传统的循环遍历固定大小数组不同,这种循环将继续执行,只要在流中接收到数据。
从查询中获得的消息看起来类似于以下内容:
[
'86a5b0e3-6e06-40e2-b226-5a72bd39b65b', # Device ID
1684615020438850600, # Timestamp of the sensor recording
0.993927001953125, # x value
-0.5736083984375, # y value
-0.1787261962890625 # z value
]
一次返回一条消息,每条消息可以被认为是smartphone_sensor_stream
ksql 流中的一行。请记住,ksqlDB 可以执行更复杂的查询,如聚合和连接,但对于这个项目,只需要一个基本的select
。
仪表板后端
仪表板后端是一个 FastAPI 应用程序,接受 SSE 请求以获取传感器数据流。一旦请求了 SSE 连接,将打开一个 ksqlDB 推送查询,并在到达 Kafka 时持续向前端发送消息。仪表板后端的目录结构如下:
dashboard_backend
├── Dockerfile
├── app
│ ├── core
│ │ ├── config.py
│ │ └── utils.py
│ ├── db
│ │ └── data_api.py
│ ├── main.py
│ └── models
│ └── sensors.py
├── entrypoint.sh
└── requirements.txt
首先让我们看一下data_api.py
——这是 ksqlDB 和仪表板后台之间的接口:
# data_api.py
from retry import retry
from ksql import KSQLAPI
from models.sensors import SensorName
from typing import Generator
@retry()
def create_ksql_connection(url: str) -> KSQLAPI:
"""
Create a connection to a KSQL server using the provided URL.
Parameters
----------
url : str
The URL of the KSQL server to connect to.
Returns
-------
KSQLAPI
An instance of the `KSQLAPI` class representing the connection
to the KSQL server.
"""
return KSQLAPI(url)
def create_ksql_device_stream(client: KSQLAPI,
stream_name: str,
topic_name: str) -> None:
"""
Creates a new device stream in KSQL server if it does not already exist.
Parameters:
-----------
client : KSQLAPI
A client instance of the KSQLAPI class to connect with KSQL server.
stream_name : str
The name of the device stream to create.
topic_name : str
The name of the Kafka topic to associate with the device stream.
Returns:
--------
None
Raises:
-------
KSQLServerError
If there is an error while creating the stream in KSQL server.
"""
# Get the current streams
curr_streams = client.ksql('show streams')
curr_stream_names = [stream['name'].lower()
for stream in curr_streams[0]['streams']]
# If the device stream doesn't exist, create it
if stream_name.lower() not in curr_stream_names:
client.create_stream(table_name=stream_name,
columns_type=['name varchar',
'time bigint',
'values_x double',
'values_y double',
'values_z double',
'messageId bigint',
'sessionId varchar',
'deviceId varchar'
],
topic=topic_name,
value_format='JSON')
def ksql_sensor_push(client: KSQLAPI,
stream_name: str,
sensor_name: SensorName) -> Generator:
"""
Generator function that continuously pushes sensor data
for a given sensor name from a KSQL server using the KSQL API client.
Parameters:
-----------
client : KSQLAPI
The KSQL API client instance used to query the KSQL server.
stream_name : str
The name of the KSQL stream to query data from.
sensor_name : SensorName
An enum value representing the name of the sensor to stream data for.
Returns:
--------
Generator:
A generator object that yields the sensor data as it is streamed in real-time.
"""
push_query = f'''
select deviceId,
time,
values_x,
values_y,
values_z
from {stream_name}
where name = '{sensor_name.value}'
emit changes
'''
sensor_push_stream: Generator = client.query(push_query, use_http2=True)
return sensor_push_stream
在这个脚本中,create_ksql_device_stream()
创建了前一节定义的 ksqlDB 流,ksql_sensor_push()
返回一个生成器,该生成器生成传感器数据推送查询的结果。
让我们详细解析main.py
——这是仪表板后台的主驱动脚本。以下是依赖项:
# main.py
import pandas as pd
import json
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
from fastapi.requests import Request
from starlette.middleware.cors import CORSMiddleware
from core.config import app_config
from core.utils import maybe_load_json
from models.sensors import SensorName
from db.data_api import (create_ksql_connection,
create_ksql_device_stream,
ksql_sensor_push)
主要需要注意的导入项是EventSourceResponse
(类 实现了 SSE)和来自data_api.py
的函数。接下来,实例化了一个 KSQLAPI 对象和一个 FastAPI 应用:
# main.py
...
# Instantiate KSQLAPI object
KSQL_CLIENT = create_ksql_connection(app_config.KSQL_URL)
# Create the KSQL device stream if it doesn't exist
create_ksql_device_stream(
KSQL_CLIENT, app_config.STREAM_NAME, app_config.TOPIC_NAME
)
# Instantiate FastAPI app
app = FastAPI()
# Configure middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
最后,定义了将传感器数据从 ksqlDB 发送到前端的 SSE 端点:
# main.py
...
# An SSE endpoint that pushes sensor data from KSQLDB to the client
@app.get("/chart-data/{client_id}")
async def message_stream(request: Request, client_id: str):
async def event_generator():
while True:
# If the client closes the connection, stop sending events
if await request.is_disconnected():
break
try:
# Get the KSQL stream generator
sensor_push_stream = ksql_sensor_push(
KSQL_CLIENT, app_config.STREAM_NAME, SensorName.ACC)
for raw_message in sensor_push_stream:
# If client closes connection, stop sending events
if await request.is_disconnected():
break
# Check if the raw message is the correct format
message = maybe_load_json(raw_message)
# If the message is in the correct format (i.e. a list),
# send to client
if isinstance(message, list):
# Format the sensor timestamp
message[1] = str(pd.to_datetime(message[1]))
# Yield the message as JSON
yield {
"event": "new_message",
"id": "message_id",
"retry": 1500000,
"data": json.dumps(message)
}
except Exception as e:
if await request.is_disconnected():
break
continue
return EventSourceResponse(event_generator())
message_stream()
端点接受GET请求,这些请求打开长连接,后台可以通过这些连接持续发送传感器数据。在message_stream()
中,定义并返回了一个协程 event_generator()
。event_generator()
的目的是创建一个 while 循环,在 ksqlDB 处理传感器数据消息时生成这些消息。只有当客户端关闭连接时,这个循环才会终止。
仪表板前端
仪表板前端是由 FastAPI 应用托管的 HTML 页面。它的目的是展示数据如何在项目的各个组件之间流动,并不是一个全面的前端。界面如下:
在仪表板上显示智能手机传感器数据流。图像由作者提供。
当用户点击“开始流媒体传输”时,会与后台建立一个 SSE 连接,并持续发送来自推送查询的数据。所有用于创建前端的代码以及运行所有内容的说明都可以在GitHub上找到。
最后的思考
本文介绍了 ksqlDB 作为一种解决方案,用以改进之前使用 FastAPI、Kafka、QuestDB 和 Docker 构建的实时流媒体项目。该项目旨在创建一个可视化传感器数据的仪表板,接近实时展示数据。面临的挑战之一是由于将数据从 Kafka 写入数据库并查询数据库进行分析所造成的不必要延迟。
ksqlDB 是一个为流处理而构建的数据库,用于解决此问题。与其在访问数据之前将其持久化到数据库中,ksqlDB 允许直接处理和分析 Kafka 中的数据流。通过在 ksqlDB 中实现推送查询,仪表盘的延迟显著减少,传感器读数的显示几乎没有可检测的延迟。
ksqlDB 的使用简化了后端基础设施,并允许对传感器读数进行近实时高效分析。未来的文章将继续对该项目进行改进,并通过新工具和功能增强其功能。我们始终欢迎反馈。感谢阅读!
成为会员: https://harrisonfhoffman.medium.com/membership
参考资料
Apache Kafka:https://kafka.apache.org/
Event-Driven Architectures — The Queue vs The Log: https://jack-vanlightly.com/blog/2018/5/20/event-driven-architectures-the-queue-vs-the-log
Lucidchart:https://www.lucidchart.com/
Kafka Poc using FastApi: https://github.com/GavriloviciEduard/fastapi-kafka
geo-stream-kafka:https://github.com/iwpnd/geo-stream-kafka
18 Most Popular IoT Devices in 2022: https://www.softwaretestinghelp.com/iot-devices/#:~:text=Smart%20Mobiles%2C%20smart%20refrigerators%2C%20smartwatches,there%20by%20the%20year%202022%3F
FastAPI:https://fastapi.tiangolo.com/
QuestDB:https://questdb.io/docs/
Row vs Column Oriented Databases:https://dataschool.com/data-modeling-101/row-vs-column-oriented-databases/
ksqlDB:
docs.ksqldb.io/en/latest/
关于 DALL·E2 的哲学与艺术视角
TLDR: 探索 AI 与艺术的交汇点:关于在创作过程中整合 DALL·E2 的知识性对话
·
关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 7 月 25 日
–
图片来自 Pexels 的 Dids
感谢 Yaron Senderowicz 教授和 Liav Isaac Shopen 愿意接受本博客文章的采访。
介绍和动机
DALL·E2 已经推出一段时间了,我认为从哲学和艺术的角度写一篇关于它的博客文章会很有趣。作为一名数据科学家和业余版画爱好者,我对这种方法在艺术上的看法和使用方式产生了浓厚的兴趣。
尽管工程师们常常专注于让事物正常运作,但值得更多关注其他方面,比如艺术性、美学和社会考量,这些方面往往受到较少关注。这次探索的主要动机是更好地理解和探讨 DALL·E2 在什么意义上具有艺术性和人类特质。换句话说,我们应如何解读 DALL·E2 的输出?
此外,人们对 DALL·E2 的影响及其对就业的后果越来越担忧。它生成的图像非常吸引人,以至于很难分辨它们是由人类还是算法生成的。例如,我们刚刚见证了一张 AI 生成的照片 赢得了摄影奖,这突显了这个问题的重要性。
在这篇博客文章中,我有幸采访了两位有趣的人。第一位采访者是耶路撒冷大学哲学教授耶龙·森德罗维茨,因其对人类意识的研究而闻名。第二位采访者是利亚夫·艾萨克·肖本,他是一位大师级版画家,他的艺术造诣和实践经验使他对 DALL·E2 有独特的理解,并如何将其融入他的艺术作品中。
与耶龙·森德罗维茨教授的访谈
伊多:耶龙,你能分享一下你对 DALL·E2 的看法吗?你认为这个算法在某些方面不仅能模仿,还能展示人类的能力吗?
我不完全确定算法是否真实地复制了人类的能力。例如,人类可以自动创作艺术,但关键问题是这些艺术是否具有审美价值。可以制定标准来判断一件艺术作品是否具备这种价值。我认为其中一个标准是作品对背景的敏感程度。
我们存在于某种文化、时期、空间和文明中。我们可以轻易地辨别不同文化之间的差异。例如,如果莫扎特的作品在 12 世纪或 21 世纪呈现,它的审美价值可能会有很大的不同,因为它本质上与创作时的背景紧密相连。
创造力、意义与背景
伊多:创造力关乎意义以及人类如何感知它。那么艺术家的创作应如何解读和评估呢?
Yaron:技术可以复制梵高的风格,但我们应该质疑的是,我们使用 DALL·E2 创作的艺术作品是否提供了新颖的东西,还是仅仅是现有作品的衍生物。如果你有技术资源可以完美复制梵高的艺术风格,这并不意味着创作出的作品就是艺术。它只是展现了令人印象深刻的模仿能力。
理解 DALL·E2 的一种可能方法是使用“字面意义”的概念。人类有能力判断语言使用是否正确,即使它没有被明确界定或其真实性条件没有被明确列出。美国哲学家约翰·赛尔(John Searle)广为人知的贡献之一是对“字面意义”的质疑(即对句子的直接和直白的解释,不包含任何比喻或隐喻元素)。
意义的概念与……嗯,背景密切相关。当前的问题是,是否可以提供一个描述,包括一个术语适用于世界上一个物体的必要和充分条件。此外,我们对定义的理解(或者说计算机对定义的理解)是否足够准确,以便在一个我们既没有考虑过也没有遇到过的对话背景中正确使用这个术语。
我们理解一个句子,尽管我们的词汇量和使用范围是有限的。基于记忆的用法是确定且有限的。现在,一个涉及到“字面意义”文章中‘on’概念的例子。让我们考虑一个我们想理解的短语——“猫在地毯上”或“猫在地板上”。
有没有一种方法可以准确推导出将这些术语与猫和地毯正确关联的语义必要和充分条件?为了确认一只猫在地毯上,它必须有多少部分在地毯上?我们如何定义那部分?一条腿?三条腿?如果有两条腿在地毯上,两条腿在地毯外呢?
我们能否准确确定什么时候可以说猫在地毯上?让我们进一步说明。想象一下在太空中飞行,你看到一只猫和一块地毯颠倒放置。猫看起来是倒立的,但谁在什么上面?意识到我们使用“在”这个词是基于一个背景——地球的引力。这种无意识的用法只是说明了我们的陈述及其理解中有多少背景。我们的声明总是预设了一个背景,而这个背景从未被明确陈述或充分阐述。
由 Dalle2 生成的图像
为了更精确地描述背景,考虑另一个场景,你到美国旅行。你在沙漠中感到饥饿,于是你在一个餐馆停下来,点了一杯可乐和一个汉堡包。突然,你得到一个 5 米长的汉堡和一个 2 米高的可乐。这是你点的东西吗?可能不是,但你是怎么在点餐时说明的?任何了解背景——“买一个汉堡意味着什么”——的人都知道这不是你点的东西。而且这个背景在不同文化中显然有所不同。
你可能会声称这不是你所要求的,但无数可能的情况无法充分描述。我们总是在上下文中理解。
Dalle2 生成的图像
人类与算法的创造力
Ido:你怎么看待人类与算法的创造力
Yaron:将这一讨论与创造力联系起来——人类创造力表现为理解一个没有明确特定定义的术语的创造性使用的能力。然而,任何对该语言有一定造诣的人会同意这种新应用是正确的,或者他们会否定这种应用。尽管如此,仍会有一些人持不同意见。这里没有什么是精确定义的,而这种精确性需要更丰富的逻辑系统来明确。
我的主要观点是,我们应当始终质疑那些相信自己发现了算法,特别是与艺术相关的算法的人:他们是否仅仅生成了一个成功模仿在算法出现之前开发的风格或作品的算法?我们只能在展示能够产生艺术中新运动的计算机或写一本新书的情况下回答这个问题。
毕加索,作为一位杰出的具象艺术家,通常在探索其他风格之前教授这种风格。艺术在历史上是层叠的,要求对其根源和基础有深入了解。当计算机成功启动一种新的艺术运动——也就是说,当评论家或专家惊叹道,“哇!我们从未见过这样的东西”——如果这种运动确实源于对艺术历史状态的理解,恰当地融入,并引发了变化,那么这就是重要的。当这种情况发生,并且它成功地创作了重新定义诗歌理解的诗歌,正如博尔赫斯所说——在卡夫卡的作品之后,人们突然发现了许多先前存在的卡夫卡。
模仿者与创新者
Ido:那如何看待模仿者与创新者
Yaron:毫无疑问,模仿者与开发创意者之间存在对比。人们可以欣赏艺术作品中的创造力和原创性。
这些算法的许多开发者可能会争辩说,普遍接受的创新并不存在。他们可能认为这过于模糊或难以定义。确实,艺术在不断辩论中。然而,在今天的世界里,学者们引用现代文本时可以将其与非现代文本区分开来。解释学(解释理论)关注这一点。这是任何试图解析文本之间意义的人所关心的关键问题。他们专门处理这种问题,努力应对源自文化背景的历史特征。
要真正成为我们生活经验的一部分并对此作出显著贡献,计算机必须从单纯的模仿者演变为创新者和创造者。它应该致力于创造一些新的东西,而不仅仅是复制他人已经发明的东西。我并不是说这样的算法不存在,但我们应该对过早断言其存在持谨慎态度。我想说的是,考验不在于是否能够复制或伪造他人的画风,而在于是否能够带来新的文化变革。
一个真正具有创造力的计算机应该能够产生一种新的艺术类型。立体主义绘画的出现最初被视为对传统艺术的贬低偏离。只有在我们对莫奈、马奈和雷诺阿等印象派画作进行美学调整后,我们才在回顾中欣赏到印象派的痕迹,这些痕迹延续到 18 世纪。突然间,它弥漫到了公众领域。创新的演变可以比作一个隐喻;它开始时是令人耳目一新的意外之物,随着时间的推移,获得了更明确的字面意义。
这种演变总是与特定的时间、政治、社会和文化背景及我们对过去的解读相关。这是一个复杂的过程,不能仅通过提取梵高画作的规则来描绘。绘画行为远不止于此。梵高的作品是具体个体所做的具体行为,超越了现有的规范。
必须在技术成就和创造力之间建立区别。我们需要调查创造力在我们所知的人类生活中如何表现,然后评估算法生成的结果是否符合这一点。
采访 2 — 利亚夫·伊萨赫·肖本,资深版画家
伊多:利亚夫,你对 DALL·E2 有何看法,我们应该如何采纳这种技术?
利亚夫:我认为 DALL·E2 应被视为一个工具,而不是艺术过程的替代品。从一个初步的想法开始,制定一个提示,并查看结果是否能激发你的灵感。DALL·E2 对我来说像是你心中概念的过滤器。你可以通过将提示的结果作为视觉参考点来丰富你的艺术。关键是将你的独特艺术风格注入其中,同时实验提供的图像。我会对其进行调整,不断寻求原始图像的变体。随着这一过程的重复,艺术变得更加准确,并生成与我的风格相符的新图像。
与其在 Pinterest 或其他平台上筛选,不如使用 DALL·E2 根据你的艺术偏好定制结果。在这方面,我认为它可能取代传统的艺术平台,为艺术家提供个性化的参考。
伊多:有人可能会争辩说,这项技术的成果如此美丽,以至于只有人类才能创作出来。你对此有何看法?
Liav: 一些结果确实很出色,但它们仍然感觉有些冷漠和疏离。在某些方面,这项技术将创作的控制权还给了艺术家,因为他们必须提取他们想要创作的参考,这可能不是他们总能做到的。然而,重要的是要注意,这个参考并不是最终产品,而是艺术过程中的一个中间阶段。
Ido: 你是如何将 DALL·E2 用于你自己的目的的?
基于我生成的一幅图像,我制作了一张 mezzotint 印刷品。我准备了一个详细的文字描述,说明我在寻找什么,这个图案帮助我构思出一幅与我通常创作的作品不同的图像。最终,每位艺术家都有一套图案,这个工具可以帮助扩展这个范围。我不确定没有 DALL·E2 的话,最终的作品是否会是我的首选,但它确实使开发过程更快,更具多样性。
Ido: 从一开始是什么激励你使用 DALL·E2 的?
Liav: 部分原因是工具周围的热议,以及我对其对我的创作过程潜在贡献的好奇。这对我来说更多的是关于如何使用它并将其融入到我的工作中,而不是对其能力的震撼。DALL·E2 通过融入金融、时间和努力等方面,使艺术变得更易于获取,这与当前的 DIY 流行趋势相吻合。
仅仅使用它并不一定让你成为艺术家。我认为仅生成一幅图像并不足以被贴上艺术标签,还需要额外的、个性化的层次或阶段。
Ido: 这次对话回到了我与 Yaron 的采访——这代表了创造力吗?它模仿了人类的某些方面吗?
Liav: 我相信艺术作品需要原创性,需要在过去的创作和特定的背景、语言中进行创作。目前,我们使用的是别人创造的语言,并对其进行变体处理。作为艺术家的角色是贡献我们的视角,从而将创造力还给艺术家,产生新颖性。在美学吸引力方面,我认为这归结于个人喜好。我们最终必须质疑自己是否对最终产品有感情。虽然我相信创造力本质上是人类的,但我们衡量创造力的方式不能应用于计算机。人类常常将人类特征投射到非人类事物上,但这些不应被误认为是现实。
Ido: 所以你是说将 DALL·E2 视为一种类人能力源于我们的世界观?
绝对是。这个现象很常见。以抽象画为例,每个观众看到的都有些许不同。我们投射出我们熟悉的东西,我们渴望的东西。这是人类的一部分。要有效地使用 DALL·E2,你必须投入到创作过程中,并引导算法,特别是在尝试捕捉更抽象的东西,如感觉或氛围时。
Liav 使用 Dalle2 生成的原始图像
由 Liav 创作的 mezzotint 印刷品
由 Liav 创作的 mezzotint 印刷品
结论与主要收获
艺术本质上涉及原创性、发明、好奇心和文化背景的概念。我们应当问自己的根本问题是我们与所使用的 AI 工具的关系。我们只是使用它们创造输出,还是将我们的独特个性注入其中,以真实地表达自己?
在我看来,主要的收获是:
(1) 尽管像 DALL·E2 这样的算法具有模仿人类艺术风格的惊人能力,但关键在于是否真正展现了人类创造力的新颖和原创特质。
(2) 艺术中的真正创新需要生成新类型并引发文化变革,而不仅仅是现有风格的简单复制。
(3) DALL·E2 应被视为一种增强艺术创作过程的工具,而非完全取代它。它可以作为视觉参考点和想法过滤器,允许艺术家将其独特风格注入 AI 生成的图像中。
(4) 人类倾向于将人类特征投射到非人类事物上,例如 DALL·E2,这不应被误认为现实。
参考文献
Searle, J. R. (1978). 字面意义。 Erkenntnis, 207–224.
人工智能技能缺口的(哲学)视角
什么将初级机器学习从业者与高级解决方案架构师区分开来,特别是在快速发展的行业中?
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 9 月 29 日
–
在机器学习中,挫折是自然的,但也是可以避免的。图片来自 Tim Gouw Pexels.com。
尽管有很多人工智能课程可供选择,我们常常发现许多申请者缺乏看似至关重要的能力。本文是一种通过轶事探索为什么会出现这种情况。
-
背景:思维模型与 T 型技能
-
机器学习中的不同之处
-
知识缺失的感知成本
最近,我们的一些项目中有客户询问关于将项目交接给尚不存在的内部团队的问题。“我们如何训练我们的团队以拥有你们所构建的解决方案?”“我们如何确保我们的团队在 AI 变化中保持未来适应性?”这些问题的大部分变体得到了推荐或变更管理计划的回答,但一个关键的主题依然存在:“我们如何在 AI 领域招聘合适的团队成员?”
对于像我们这样的企业 AI 咨询公司来说,这个问题尤其关键。在一个高度动态的行业中,识别、招聘、培训(和货币化)员工的能力需要付出大量的努力。更危险的是缺乏成功项目交付所需的附加技能,如需求管理、客户沟通和项目跟踪。
我们常见的员工和客户的最大障碍是缺乏非常具体且明确的技能,通常只涉及两到三个缺失的专业领域——却足以使他们整个项目陷入停滞。本文探讨了这种现象的表现形式。
背景:心智模型与 T 型技能
为了说明技能缺失如何导致项目突然中断以及如何应对这些问题,有两个简单的框架可以帮助解决这些问题。它们是:
-
心智模型,它们是对系统、概念或模式的抽象;以及
-
T 型技能,是指个体在多个领域拥有一般技能的同时,在其中一个或少数几个领域中高度专业化的能力。
心智模型
心智模型是独立的表征,旨在帮助与构造、概念和系统进行交互——机器学习领域中这些表征并不短缺。简单来说,它们有助于将思想进行分隔。
从研究论文“心智模型:理论与方法的跨学科综合”中,Natalie Jones 和她的团队进一步阐述了这一点:
心智模型是个人对外部现实的内部表征,帮助人们与周围的世界互动。它们由个体基于其独特的生活经历、感知和对世界的理解来构建。心智模型用于推理和决策,可以成为个体行为的基础。它们提供了过滤和存储新信息的机制。
这是你知道自己是否在使用合适的心智模型的方式:
-
它是有限的。
-
虽然是有限的,但在其表征目标上也是完整的。
-
它允许黑箱构造以在正确的抽象层次上简化思想。
然而,心智模型(以及重要的心智模型叠加)也有其局限性。再次引用 N. Jones:
然而,人们准确代表世界的能力始终有限且对每个人独特。心理模型因此被视为现实的不完整表现。它们也被视为不一致的表现,因为它们依赖于上下文,并可能根据使用情况而变化。本质上,心理模型必须是高度动态的,以适应不断变化的环境,并通过学习随时间演变。将认知表现视为复杂系统的动态、不准确模型,承认了人们在构思这些复杂系统方面的局限性。
因此,我们能够概念化、理解、分析、调查、原型设计、构建和部署任何级别的基于机器学习的自动化的能力,需要在多个知识领域中保持概念上的流动性,包括项目的技术和管理方面。
T 型技能
在现代社会,“T 型技能”这个术语有些不准确;真正有用的是在多个领域具有深度专长的多面手。
具有 T 型技能的个人通常会在多个相关知识领域拥有广泛的知识,同时在某一特定主题或职能上具有深入的专长。
随着机器学习工程(即科学机器学习原则的现实世界、风险意识应用)的兴起,对同时具备多学科能力的需求显而易见。
描述 T 型个人的另一种方式是,某人在项目或职责范围内,能够成功处理多个必要的职能,并在其中一些方面是专家。他们在所有工作中都有危险,但在某些方面极具威胁。
这些个人通常表现为对他们整个工作范围有整体把握。尽管他们可能在某些任务方面不是专家,但他们至少知道如何将这些不熟悉的任务分解成具有明确输入和输出边界的工作项,因此他们对整个项目有可见性和能力。
尽管他们以前从未接触过鸭子,但他们知道鸭子应该是什么样子,鸭子应该怎么叫,这足以让他们不被阻碍。
机器学习中的不同之处
与云迁移项目或 SaaS 相比,机器学习通常具有许多按序堆叠的概念(与并发或树状结构相对),以及一系列针对生产级机器学习部署的额外考虑因素。部署依赖于模型类型,模型类型依赖于数据科学,而探索性数据分析依赖于项目需求。
在论文 “机器学习软件应用程序在软件开发生命周期阶段的质量保证挑战” (Alamin2021) 中,作者解释了传统软件开发与机器学习软件应用程序之间的明显差异。来自论文的内容:
在传统软件开发中,我们首先收集需求。然后设计、开发、测试、部署和维护应用程序。对于机器学习系统,我们仍需确定应用程序的目标,但不是设计算法,而是让机器学习模型从数据中学习所需的逻辑 [1]。这些观察引发了一个问题,即机器学习模型是否以及如何在不破坏软件开发生命周期(SDLC)的情况下被采纳。理想情况下,机器学习工作流/管道和 SDLC 阶段应该协同进行,以确保适当的质量保证。然而,如我们上面所提到的,由于机器学习模型设计和传统软件应用开发之间的固有差异,这些期望可能是不切实际的。
(Lwakatare2019) 在她的论文 “机器学习系统的软件工程挑战分类:一项实证研究” 中进一步阐述了:
[…] 尽管在学术界,许多关注点集中在学习算法的理论突破上,但实证研究表明,它们仅占操作性机器学习系统的一小部分 [20]。
因此,在机器学习系统的开发和维护过程中遇到了一些挑战 [6]。为了解决这个问题,新兴证据强调了在机器学习系统开发中需要考虑和扩展已建立的软件工程(SE)原则、方法和工具 [11,19]。
因此,我们可以将 AI 项目描述为需要稍多的思维模型来完成类似规模的项目。但 AI 软件通常需要更多地顺序堆叠这些原则,而云和 SaaS 似乎在理念上更具并发性,从而导致它们之间的关键性相互依赖减少。
知识缺失的感知成本
让我们以一个简单的项目模型为例,其中需要在 9 个假设领域中进行一系列活动。这些领域可以代表项目管理、需求工程、数据科学、机器学习、云计算和 MLOps 技能的混合。虽然非常简化,但我们遇到的大多数项目都有类似的顺序变化的专业技能。
作者插图。
然而,随着新技术的出现,有时你会被要求使用新技术。有时这些是小的变化(例如,用 DVC 替换 Git LFS),有时则是更大、更复杂的变化(如从单体虚拟机方法转向 Kubernetes)。理想情况下,你和你的团队应该完全胜任所有这些任务;实际上,随着行业变化的速度,你可能对大多数这些任务都比较熟悉,有些可能是新的或尚未完全掌握的。
在这种情况下,存在一个知识缺失的单一点。
作者插图。
我认为这是一种非常正常的操作状态。客户想尝试一个新的库;有人建议使用不同的数据库。这种情况时有发生。需要更改的模块可以在心理上进行热插拔,除了阅读一些 API 或学习不同的项目管理方法外,没有其他重大后果。
在 MLOps 中,问题在于通常两个或更多的问题领域会导致在项目中感知到的知识缺失更大。
作者插图。
虽然只有两个问题领域,但感知到的效能缺失影响了你大多数的指定活动。
一 handful 的缺失概念将表现为在项目中无法交付或无法有效工作的令人沮丧的能力缺失。
推荐
为了明确,我们整个团队(包括我自己)不断主动学习新的概念和技术,以确保没有意外的主题或知识领域让我们完全盲目。我们通常遵循以下步骤来发现这些领域:
-
识别。一旦你听到将要使用的新技术或方法,记下它的名称、描述和背景。
-
隔离。虽然很难识别缺失知识中的“未知未知”,我们会通过简单的问题来解决它们:这个概念的背景是什么?它与我已知的有什么相似之处?它与我已知的有什么不同之处?
-
从小开始。“Hello World” 示例仍然是确保你有效使用特定工具的有效方法。
不要将你的技能总量作为进展的衡量标准,你应该关注你技能的组合以及你如何将它们结合起来以交付项目价值和成功。
其他你可能喜欢的文章
-
PyTorch 与 TensorFlow 在基于 Transformer 的 NLP 应用中的比较
-
MLOps 批处理:在 GPU 上运行 Airflow
-
数据集偏见:制度化歧视还是充分透明?
如果你对这篇文章或我们的 AI 咨询有其他问题,请随时通过 LinkedIn或通过 电子邮件联系我。
-Matt.
在 Kubernetes 上进行快速实验的流程
原文:
towardsdatascience.com/a-pipeline-for-fast-experimentation-on-kubernetes-3258389120a1
仅使用原生 Python 包
·发布于数据科学前沿 ·6 分钟阅读·2023 年 1 月 31 日
–
手动为每个新实验创建一个新配置文件是一项繁琐的过程。特别是当你想要在 Kubernetes 集群上快速部署大量作业时,自动化设置是必须的。使用 python,构建一个简单的调度脚本,读取实验的配置,如批量大小,将其写入 YAML 文件,并创建新作业,非常简单。在这篇文章中,我们将讨论具体的方法。最棒的是,我们无需额外的包!
这个流程包含四个文件:两个 bash 脚本(一个用于创建 Kubernetes 作业,一个用于删除 Kubernetes 作业),一个 python 脚本,以及一个 .yaml 文件模板。我们将详细介绍它们,从 python 脚本开始。你可以在这个 GitHub 仓库中找到完整的代码。
python 脚本
这个 python 代码被结构化为两个方法。第一个方法生成实验配置,这些配置填充了示例值。第二个方法进行实际的调度,包括解析 .yaml 文件和与 Kubernetes 通信。让我们从第一个、更简单的函数开始:
get_experiments 方法持有一个内部字典对象,在我的示例中,它包含了 4 个样本实验。每个实验都有一个唯一的编号,每个实验本身也是一个字典。这个字典包含实验的配置,包括标准的机器学习参数,如批量大小、训练轮数和模型。此外,我们指定了我们希望实验运行的数据集,例如 CIFAR10 数据集。这里列出的参数是为了说明目的,你不必局限于这些参数。例如,你可以包含更多的超参数(如学习率)、存储文件路径(如数据集目录)或包括环境变量。简而言之:根据你的需要进行调整。
如果我们调用该方法而不带参数,则等同于使用默认设置“experiment_number=-1”来调用它。使用该设置,会返回所有实验的配置。然而,如果你与 Kubernetes 一起工作了较长时间,你会发现一些作业会因为各种原因失败。如果发生这种情况,最好是修复代码并重新启动那个特定的实验。因此,除了获取所有实验的设置外,我还添加了选择特定实验进行重新运行的功能。当我们用特定实验编号调用该方法时,支持这种用例。一个例子是 4,它将返回第四个实验的配置。
.yaml 模板
get_experiments 方法在脚本的主要函数 schedule 中被调用。在详细介绍之前,我们需要查看我们的模板文件。我包含的 .yaml 文件是以我在超级计算集群上运行机器学习实验时常写的文件为基础。然而,它并不是一个有效的文件,意思是你不能直接使用它。
相反,它旨在展示你在填写模板时的灵活性。说到填写模板,这就是它:
请注意那些有两个大括号:{} 的独特位置。每个这些位置都有一个编号,从零开始。我们在 python 脚本中填补这些空白,我们很快就会回到这一点。最适合我们的机器学习用例,“{}”可以放置在模板中的任何位置;它们并不局限于特定位置。
为了展示多样性,我在 .yaml 文件中放置了标记:我们可以使用它们传递命令行参数、挂载目录、填写环境变量或选择我们的 pod 镜像。此外,我们还可以重用占位符:在模板文件中,我使用了“{1}”占位符两次;一次是将作业分配到作业组中(group-model-{1},第 11 行),另一次是将模型名称传递到命令行中(第 25 和 26 行)。
模板文件的填写通过 python 脚本的 schedule 方法完成。
在函数内部,我们首先按原样解析模板。然后,我们在第 10 行收集所有我们想运行的实验。接下来的三个变量,第 11 到 13 行,是为了激发你的灵感:你可以自动化的不仅仅是我提出的内容。有趣的部分从第 14 行开始,我们遍历所有我们想要创建的实验。正如我所写,实验配置存储在字典对象中。这意味着我们可以在第 17 到 26 行填写模板时查询字典。
为了更容易理解哪个槽位被填充,我在每一行后面留下了注释。例如,第 18 行填充了标记为“{0}”的位置,第 19 行填充了标记为“{1}”的位置,依此类推。为了查看我们在填写模板后创建了什么,我们在第 27 行打印出完成的版本。
到目前为止,我们已经在内存中创建了一个可以使用的 YAML 文件。接下来的步骤是创建相应的 Kubernetes 作业,从第 30 行开始。首先,我们检查是否只想删除旧的实验(例如,因为它由于某种原因失败了,我们需要先修复错误)。如果不是这种情况,我们在重新创建作业之前需要删除旧作业——不能有两个具有相同名称的作业。
如果不存在之前的作业,脚本在尝试终止它时不会失败,而是会打印一行空白并继续创建作业。
Shell 脚本
作业的创建和删除都转发给两个小的 bash 脚本。第一个脚本如下所示,它使用 kubectl 命令根据传递的内容创建一个作业(echo “$1” 部分)。注意,我已经将 kubectl 设置为默认使用我的命名空间。如果你没有这样做,那么可以写 kubectl -n your-namespace 或将你的命名空间注册为默认命名空间:
删除作业的脚本几乎相同;我们只是将“create”标志换成“delete”:
回到 Python 脚本
将各个部分组合在一起后,我们需要一个驱动程序来启动代码。这个任务是通过“main”语句完成的,如下面的代码片段所示:
启动实验时,如前所述,我们可以选择仅(重新)创建一个特定的实验。默认情况下,我们运行 所有 实验;要运行个别实验,我们可以将它们的 ID 传递给命令行。
接下来,我们创建一个标志,告诉调度程序仅删除一个运行或重新启动运行。默认情况下,这个标志被设置为 false,意味着我们首先终止一个实验的现有运行,然后重新启动它。只有当我们在命令行上明确设置标志时,它才会被设置为 true,意味着我们仅终止现有作业而不重新创建它。此外,我们也可以完全不设置它;标志的缺失等同于标志为 false。最后,我们解析参数并运行调度。
总结
在这篇文章中,我们讨论了如何快速部署实验到 Kubernetes 集群。为实现这一目标,我们使用了四个文件:一个.yaml 模板文件、两个 bash 脚本和一个 python 文件。在 python 文件中,我们使用了两种方法来收集实验的配置,然后填充模板。
完成模板后,作业通过两个单行 bash 脚本创建和删除。在这些脚本中,我们使用了原生 Kubernetes 命令。最终,我们可以调用 python 脚本(传递可选的命令行参数),我们的实验将被自动调度。
这个自动化过程在尝试参数组合时非常有用:手动为每个实验创建一个.yaml 文件既繁琐又容易出错。因此,省去这些麻烦,构建一个自动化流水线,如本文所述。用于开始的完整代码可以在我的代码库中找到。
评估业务分析中的正负未标记(PU)分类器的实用方法
一种通过调整常见分类指标以评估 PU 模型的正类先验概率的方法
·
关注 发表在 Towards Data Science ·4 分钟阅读·2023 年 3 月 31 日
–
根据作者的描述,由 DALL-E-2 制作
随着企业越来越多地使用机器学习模型分析收集的数据,一个出现的挑战是正负标签混合(PU)数据集。这些数据集只包含少量已标记的数据,其余样本为未标记数据。虽然未标记样本通常被认为是负类,但其中一些可能是正类。PU 数据集在各种商业场景中得到应用,例如预测客户流失或追加销售机会、销售预测和欺诈检测。
在 PU 数据集上评估机器学习算法可能很困难,因为传统指标可能无法准确反映模型的性能。例如,简单地将正样本留作测试并将未标记条目添加为负类可能会导致高度偏斜的混淆矩阵,且假阳性数目被夸大。这种情况可能发生在模型在测试集中检测到正样本,但它们的标签却是负类时。
PU 分类器评估的实用方法
为解决这一问题,我们团队采用了一种实用的方法,通过使用有关正样本期望频率的信息来估算 PU 数据集上的标准二分类指标。我们的方法涉及使用正类的先验概率(在自学习分类器的拟合过程中估算)来调整测试中观察到的假阳性和真阳性。这种方法使得即使在正类显著不足的情况下,也能更准确地评估模型在 PU 数据集上的表现。
为了展示我们方法的有效性并在受控环境中进行实验,我们首先使用 sci-kit-learn 的make_classification函数创建了一个合成的二分类数据集。正样本代表数据中的少数类,通过随机选择部分正样本并移除其标签来模拟 PU 学习场景。
在实际商业场景中,数据集通常包含这种预设的标记/未标记条目的比例。例如,用于预测下一年客户流失的数据集可能包含上一年未签订新年度合同的标记客户,以及具有类似特征但尚未流失的现有客户。在这种情况下,数据集可能包含多达 40%的流失客户,但只有一半会被标记为流失(显示年流失率为 20%)。
作者提供的图片
接下来,我们使用train_test_split函数将数据分割为训练集和测试集。将特征X和目标变量的伪标签版本y_pu传递给分类器进行训练。为了评估分类器的性能,我们计算测试集中未标记版本的标准机器学习指标,如准确率、精确率和召回率,并进一步将其与原始标记版本计算出的相应指标进行比较。
实现该方法的代码片段
以下是一个代码片段,展示了我们提出的方法在 PU 数据集上评估分类器性能的实现。
我们的compute_confusion_matrix函数确定了测试数据的大小,并识别了训练集中正样本的索引。然后获取模型对训练集中正样本的概率估计,并计算其均值,表示正样本被标记的概率。
然后,该函数应用已拟合的ImPULSE 模型来预测测试数据中正类的概率,并使用 sci-kit-learn 的confusion_matrix函数创建混淆矩阵。假设模型对正类的先验概率(在未标记样本中)大于零,则该函数会调整混淆矩阵以考虑测试数据中可能存在的未标记正样本。该函数估计由于未标记条目导致的假阳性和真阳性的预期数量,并相应调整混淆矩阵。
为了确保生成的混淆矩阵与测试数据的大小匹配,该函数会对其进行舍入和重新缩放,如果需要的话,还会调整真负样本的数量。
在获得调整后的混淆矩阵后,我们可以利用它计算标准机器学习指标,以尽可能准确地评估模型的性能。
你可以在[Jovian 上的对应demo notebook]和GitHub 仓库中找到完整的代码。
结论
我们提出了一种实用的方法,用于评估在商业场景中常见的正未标记(PU)数据集上的机器学习模型。传统评估指标可能无法准确反映模型在此类数据集上的性能。该方法通过使用正类的先验概率,估计 PU 数据集上的标准二分类指标,从而实现对模型性能的更准确评估。
参考阅读
-
Agmon, Alon. “半监督分类未标记数据(PU 学习)”, 2022
-
Saunders, Jack, 和 Freitas, A. “评估正负未标记分类器的预测性能:简要的批判性回顾及改进的实际建议。”,2022
-
Holomb, Volodymyr. “激励行动:正负未标记分类的实用解决方案。”,2023
ChatGPT 和 Bard 的免费版本实用比较
原文:
towardsdatascience.com/a-practical-comparison-of-chatgpts-and-bard-s-free-versions-5fd9ca8b9024
从测试结果来看,比较这些语言模型的能力和局限性,只需一个 URL 即可。
LucianoSphere (Luciano Abriata, PhD)
·发表于 Towards Data Science ·阅读时长 22 分钟·2023 年 9 月 26 日
–
由 Google DeepMind 在 Unsplash 上拍摄的照片
难怪我将 ChatGPT 集成到我的日常工作和活动中。真的非常多。它在头脑风暴、纠正或改进文本、翻译、编写代码和查找错误等方面极为有用。由于其网络性质,ChatGPT 总是只需一个 URL 即可使用,免费,无需安装或下载。OpenAI 模型的这一特性,对于大众化至关重要,只有 Google 的 Bard 可以媲美;然而,由于它推出得较晚,它在我周围的同事和朋友中可能知名度较低。为什么不给它一个机会呢?
在这里,我展示了通过我设计的测试对这两个模型进行的比较,这些测试基于微软今年早些时候发布的一篇非常具有挑衅性的预印本。
[## 挑衅地,微软研究人员称他们在 GPT-4 中发现了“人工智能的火花”
只是一个具有挑衅性的预印本标题,还是通过语言通向人工通用智能的真正路径?
这里我通过我根据微软在今年早些时候发布的一篇非常具有挑衅性的预印本设计的测试,对这两个模型进行了比较
这项研究实际上是如何展开的。
几天前,我需要与 ChatGPT 讨论我为我们即将上线的(敬请期待!)化学教育网站准备的内容,通过基于 WebXR 的虚拟现实工具来进行,结果令我震惊的是,它无法操作。好吧,我对自己说,这也是试试 Bard 的机会,它也只是一个 URL 的距离。
几个小时后,ChatGPT 重新上线。我利用这个机会用尽可能相似的问题进行探测。我只比较了免费版本,并设计了接近我日常工作使用工具的测试。
继续阅读以查看我的结果。一些发现是预期的,其他则是微不足道的,还有一些相当有趣。请阅读结论部分以了解我的“裁决”。
测试 1:为教育工具头脑风暴分子列表
精确来说,我进行的这种“竞争”是在两个语言模型之间进行的,旨在开发一份简洁而全面的有机分子列表,涵盖有机化学的各种主题。我的意图是从该列表开始与语言模型进行头脑风暴,最终确定一套要包含在我们虚拟现实工具中的分子。
这是我第一次提交给 Bard 的提示,当时 ChatGPT 不可用:
我:我正在准备一款虚拟现实工具的材料,应该通过示例介绍有机分子的基本概念。列出我应该包括的分子及其原因。这里有一些示例:
顺-2-丁烯、反-2-丁烯和丁烷,用于比较单键与双键及其效果。
也许还可以添加丁烯以完成单键、双键和三键的对比
- 和 — 酒石酸盐,用于说明手性中心,也许还可以添加一个氨基酸
苯、吲哚、咪唑和其他芳香环
一些简单的胺,用于说明这一功能团
一些简单的醚和酯,以说明这些基团
根据新生有机化学学生需要学习的内容来调整你的列表,也许要考虑哪些概念最困难。理想情况下,我想要大约 20 个,最多 30 个例子,因此必须仔细选择。
起初,我对 Bard 的输出感到惊讶,图形输出非常丰富(截至 2023 年 9 月,免费版 ChatGPT 不具备此功能),似乎理解了我的问题,并返回了初看起来可能完整且非常有用的答案:
作者从 Bard 截图的图像。
答案的第一部分有点道理,但完整的答案没有涵盖我想要涵盖的所有主题,更糟糕的是,最后出现了一些非常严重的错误。凭借一些基本的化学知识,你知道这些分子不是醚和酯!我给了 Bard 机会来修正错误并提供醚和酯的替代例子,但它没有成功。
然后几个小时后,ChatGPT 回来了,我可以将其与 Bard 进行比较。我已经知道它不会给我任何图片,所以这在某种程度上是一个负面因素,但我想知道生成的文本是否更具信息量,特别是列表是否更清晰,没有错误,并且是否提供了更好的分子用于我的教育目的。
这是 ChatGPT 的回答,来自完全相同的提示:
ChatGPT:绝对的,仔细选择有效介绍有机化学关键概念的分子是至关重要的。以下是经过精心挑选的分子列表,并附有它们重要性的解释:
1. 甲烷:有机分子的基本结构和碳的四面体几何的介绍。
2. 乙烯:双键及其对分子几何的影响的介绍。
3. 乙炔(乙炔):三键和 sp 杂化概念的介绍。
4. 丙烷:单键和不同碳骨架的强化。
5. 环丙烷:紧张环结构及其后果的介绍。
6. 苯:芳香性和共振系统的介绍。
7. 乙醇:氢氧基功能团及其性质的说明。
8. 醋酸:羧酸及其重要性的介绍。
9. 丙酮:羰基功能团及其在各种化合物中的作用的说明。
10. 乙胺:一级胺和碱性的介绍。
11. 苯胺:芳香胺及其独特性质的说明。
12. 二乙醚:醚类及其作为溶剂的作用的介绍。
13. 甲基胺:简单的二级胺的介绍。
14. 乙醛:醛类及其反应性的说明。
15. 乙酰胺:酰胺及其在生物分子中的作用的介绍。
16. 甲酸乙酯:酯类及其宜人气味的介绍。
17. 甘氨酸:氨基酸、两性离子和手性的介绍。
18. 乳酸:手性分子及其生物学意义的说明。
19. 柠檬酸:复杂羧酸及其在代谢中的重要性的介绍。
20. 阿司匹林(乙酰水杨酸):有机合成和功能团转化的说明。
21. 葡萄糖:碳水化合物和环状结构的介绍。
22. 甘油:三醇及其在脂质中的作用的说明。
23. 脂肪酸(例如棕榈酸):饱和脂肪酸的介绍。
24. 油酸:不饱和脂肪酸和顺反异构体的介绍。
25. 血清素:杂环化合物及其生物学作用的介绍。
26. 腺嘌呤:嘌呤碱基及其在 DNA 和 RNA 中作用的介绍。
27. 胸腺嘧啶:嘧啶碱基及其在 DNA 中作用的介绍。
28. 褪黑激素:含有复杂吲哚结构的分子的说明。
29. 酚:芳香醇及其反应性的介绍。
30. 尼龙 6,6(聚酰胺):聚合物及其在材料科学中的相关性的介绍。
这些分子涵盖了广泛的概念,包括基本的键合、官能团、手性、芳香性等。它们经过精心挑选,以提供对有机化学概念的全面介绍,同时展示具有重要生物学和实际意义的分子。
这个回答比 Bard 的要好得多,我没有发现任何错误,涵盖了我想要覆盖的所有化学内容,甚至还提供了将分子与我们日常生活相关联的额外信息,虽然我没有要求这些信息,但从教学角度来看非常重要。
我对这个测试的结论是,ChatGPT 比 Bard 要好得多,尤其是在教师、内容创作者、科学传播者或学生如何使用它方面。
测试 2:带有图形的问题
这固有地偏向于 Bard,因为 ChatGPT 在其免费版本中不提供此功能。
然而,检查上述例子时会发现,图像传达的信息往往与文本不符。例如,Bard 标记为醚的分子实际上是乙二醇,一种二醇。这使我倾向于得出结论:与其看到不正确的图像,我更愿意没有图像。
另一方面,Bard 在其输出中包括图形的能力,尤其是理解输入图像上的问题,可能会极其有用,而这正是 ChatGPT 无法竞争的地方。这里有一个例子,我问了照片中的虫子是什么,程序迅速而准确地回答了:
测试 3:从第三方程序调用模型
就像上面的部分固有地偏向于 Bard,这部分则偏向于 ChatGPT,或者更确切地说是其背后的语言模型 GPT-3.5,该模型极其容易从你的代码中调用,即使是从 Web 应用程序中。
这是我广泛研究的内容,将 GPT-3、GPT-3.5 和 GPT-4 直接从我的程序中调用,以将它们的强大功能与其他库和 API 提供的能力结合起来:
[## 使用 GPT-3 从语音备忘录自动撰写电子邮件的 Web 应用程序
我将 Chrome 的语音识别引擎与 GPT-3 结合,创建了一个从你的语音备忘录撰写电子邮件的 Web 应用程序……
pub.towardsai.net](https://pub.towardsai.net/a-web-app-for-automated-e-mail-writing-from-voice-notes-using-gpt-3-e8e98e4ffb6f?source=post_page-----5fd9ca8b9024--------------------------------) ## 将 GPT-3 与语音识别和合成相结合,实现完全语音聊天机器人…
我是如何创建这个网页应用程序的,你可以用它与 GPT-3 自然对话,讨论你想要的任何主题,完全基于网络在你的…
[towardsdatascience.com ## 通过将语音转换为命令来控制网页应用程序,使用 GPT-3
最后一篇文章展示了 GPT-3 的实际应用,包括完整的工作流程解释和详细信息…
[towardsdatascience.com
原则上,你也可以调用 Bard 将其用于你的程序,但这比调用 GPT 模型复杂得多:你必须创建一个 Google Cloud Platform 项目并启用 PaLM API,创建服务账户并下载特定的 JSON 密钥文件,安装适用于你的编程语言的 PaLM 客户端库,然后使用 PaLM 客户端库向 PaLM API 发出请求。不,谢谢,我喜欢简单。给 OpenAI 的产品加分。
测试 4:从互联网获取信息
像我在测试 2中的测试一样,这次测试也首先偏向 Bard,因为 ChatGPT 无法连接到互联网。不过,这一点很重要,因为在实践中,它确实严重影响了模型提供的信息的整体用户体验和质量。
对于这个测试,我转向了瑞士现任总统!开个玩笑,事实证明瑞士的中央政府由 7 名成员统治,他们拥有相同的权力,每年轮流担任“代理总统”。这给了模型很多出错的空间,如果它们没有更新,但如果信息在互联网上可用的话,仍然可以找到。
我问了两个语言模型这个问题:“谁是目前的瑞士现任总统?”
我从 Bard 那里得到了正确的答案:
Bard:目前瑞士的总统是阿兰·贝尔塞。他于 2022 年 12 月 7 日当选,任期从 2023 年 1 月 1 日开始。贝尔塞是瑞士社会民主党的成员,并领导联邦内务部。
(接着其他一堆细节)
与此同时,ChatGPT 则没有给出答案
ChatGPT:瑞士联邦委员会通常每年轮流由七名成员担任主席。主席的任期为一年,顺序提前确定。现任主席每年可能会更换,因此我无法实时提供瑞士现任主席的信息。
我试图让它给出一个名字,但显然它不知道该怎么回答:
我:是的,但每年 7 名委员中会有一位被称为“总统”。例如,几年前是西蒙内塔·索马鲁加。现在这个人是谁?
答案如下。尽管总体体验较差,因为我无法让它给出名字,但模型选择解释自己无法给出名字因为没有访问更新信息,而不是编造一个名字或提供前任总统的名字作为现任总统,这一点是值得注意的!
ChatGPT:对于我之前回答中的任何混淆,我表示歉意。根据我截至 2021 年 9 月的最后知识更新,西蒙内塔·索马鲁加在 2020 年担任瑞士总统。然而,我无法访问实时信息,瑞士联邦委员会的总统每年都会更换。要查找当前瑞士总统的信息,我建议查看官方瑞士政府来源或可靠新闻媒体的最新更新。它们会有瑞士联邦委员会现任总统的最新信息。
回到 Bard 的回答,为了深入了解它是如何用正确的信息生成答案的,我直接问了它:
我:你知道这个信息,还是需要在网上查找?
这是它的回答:
Bard:我在网上查找了信息。我仍在开发中,并没有将所有世界知识存储在我的记忆中。然而,我可以通过 Google 搜索访问和处理现实世界的信息,并使我的回答与搜索结果一致。这使我能够提供最新和最准确的信息。
更有趣的是,它还加上了这一点:
我能够记住我查阅过的信息,并在将来用它来回答你的问题。例如,如果你再问我瑞士总统是谁,我将能够回答你的问题,而不需要再次查找。
无论它们是否知道答案,这两个模型表现都非常出色。
测试 5:计算
语言模型只通过它们的神经网络传播输入标记,以连续预测新的标记,从而生成文本片段。这些文本片段由于庞大的训练数据库和例程,使得它们是有意义的。因此,理所当然地,一个人会期望这些语言模型无法进行数学运算,因为它们仅限于训练数据集中出现的数学问题,没有灵活性。实际上,它们确实无法进行高级数学(如果你特别对这个问题和最现代模型的能力感兴趣,可以查看这篇文章 focused on OpenAI’s GPT-4))。
我从一些非常简单的数学问题开始:“-11, 3, 5, -3, -5 和 11 这些数字的平均值是多少?”(答案是 0,因为你可以看到我已经平衡了所有正数和负数)
这些是语言模型提供的答案:
ChatGPT:要找到一组数字的平均值(平均数),你需要将所有数字相加,然后除以数字的总数量。
这里是你提供的数字:-11, 3, 5, -3, -5, 11
现在,让我们计算平均值:
平均值 = (所有数字的总和) / (数字的总数量)
平均值 = (-11 + 3 + 5–3–5 + 11) / 6
平均值 = 0 / 6
平均值 = 0
所以,-11, 3, 5, -3, -5 和 11 这些数字的平均值是 0。
这很好,是的,但这仍然可能是纯粹的运气,没有实际的数学处理。让我们看看 Bard 怎么说:
Bard:-11, 3, 5, -3, -5 和 11 这些数字的平均值是 0。
要计算平均值,我们将所有数字相加,然后除以数字的总数。在这种情况下,我们有:
平均值 = (-11 + 3 + 5 - 3 - 5 + 11) / 6 = 0
因此,这些数字的平均值是 0。
这也是正确的。两者都给出了正确的答案,但问题非常简单。现在,让我们稍微调整一下数字看看会发生什么:“-11.5, 3.3, 5.88, -3.3, -5.89 和 11.5 这些数字的平均值是多少?”(注意 5.88 和 -5.89 不完全抵消,剩下 -0.01 除以 6 个元素得到 -0.00166666…
ChatGPT 给了我这个答案:
ChatGPT:要找到一组数字的平均值(平均数),你需要将所有数字相加,然后除以数字的总数量。
这里是你提供的数字:-11.5, 3.3, 5.88, -3.3, -5.89, 11.5
现在,让我们计算平均值:
平均值 = (所有数字的总和) / (数字的总数量)
平均值 = (-11.5 + 3.3 + 5.88–3.3–5.89 + 11.5) / 6
平均值 = 0.99 / 6
平均值 ≈ 0.165
所以,-11.5, 3.3, 5.88, -3.3, -5.89 和 11.5 这些数字的平均值约为 0.165(四舍五入到小数点后三位)。
那是错的,错误源于求和结果不正确,0.99 而不是 -0.01。
让我们看看 Bard 怎么说:
Bard:-11.5, 3.3, 5.88, -3.3, -5.89 和 11.5 这些数字的平均值是 -0.001666666666666335。
要计算平均值,我们将所有数字相加,然后除以数字的总数。在这种情况下,我们有:
平均值 = (-11.5 + 3.3 + 5.88 - 3.3 - 5.89 + 11.5) / 6 = -0.001666666666666335
因此,这些数字的平均值是 -0.001666666666666335。
那也不完美,但 Bard 非常接近,实际上对于几个小数位是完美的。现在这里有一些需要深入研究的东西,因为这个完全相同的问题和这些数字是否已经在训练集中存在?显然,模型在进行一些数学计算。
这些程序是通过核心语言模型直接处理计算,还是具备调用计算器或执行代码的能力?后者的解决方案已被提出作为扩展语言模型能力的方法,正如微软在其预印本的某些段落中讨论的那样,我在这里分析了。
如果语言模型确实在内部进行数学处理,它们是如何实现的?它们是否通过处理包含数学问题的训练数据集来“学习”实际的数学知识,从而学会输出标记?显然,它们至少学会了计数、加法(尽管 ChatGPT 有时会遇到困难)和除法(有些困难,但这些可能是由于在神经元中传播的标记/数字的四舍五入误差造成的)。
无论如何,我认为这很有趣,应该通过更多涉及数学的问题进行专门研究——也许会有这样的研究出现?
为了排除模型直接拥有可以进行数学计算的模块的可能性,我直接问了它们:“你们是通过核心语言模型来处理计算,还是可以直接进行数值计算?”
实质上,两个程序都回复说它们没有直接的数值计算能力,这实际上是事实。以下是 ChatGPT 的解释:
ChatGPT:我没有直接访问数值计算能力或外部数据库进行计算的能力。相反,我通过语言模型处理计算,根据提供给我的输入来解释和执行数学运算。
对于基本的算术计算和数学运算,我可以通过应用数学规则和公式来生成响应。然而,对于更复杂或专业的计算,我可能不如专门的计算工具或为这些特定目的设计的软件准确或可靠。当精确性至关重要时,使用适当的工具对关键计算进行复核始终是一个好习惯。
这是 Bard 的解释:
Bard:我通过核心语言模型处理计算。这意味着我利用对语言和数学的知识来解决数学问题。我没有直接的数值计算能力。
然而,我在一个包含大量数学信息的大型文本和代码数据集上进行了训练。这使我能够解决广泛的数学问题,甚至是复杂的问题。
例如,如果你让我计算一组数字的均值,我将利用对语言的理解来理解你的请求,并利用对数学的知识来计算均值。我无法访问计算器或使用其他外部资源来解决问题。
这种方法有很多优点。首先,它使我更灵活和适应。我可以根据问题的背景以多种方式解决数学问题。第二,它允许我随着时间的推移学习和改进。随着我接触到更多的数学信息,我将能够更准确地解决更复杂的问题。
然而,这种方法也有一些缺点。首先,它可能比使用计算器或其他外部资源要慢。其次,我可能无法解决所有的数学问题。例如,我可能无法解决需要专业知识或算法的问题。
总体而言,我认为通过核心语言模型处理计算的好处超过了缺点。这种方法使我更灵活、适应性更强,并且能够随着时间的推移学习和改进。
进一步探讨 ChatGPT 和 Bard 的数学能力
在上述简单测试之后,我进行了稍微复杂一些的计算测试,最终我得出结论,任何模型都没有持续优于其他模型。可能,之前对 Bard 的支持例子只是个别现象。
所以,我向程序本身提出了以下问题:
“你是否意识到,如果你可以进行直接计算,或许能够运行你编写的代码,你可能能更好地解决数学问题?”
两者都回答说它们“意识到”这一点,但 ChatGPT 补充解释说“其设计和架构的限制,包括隐私和安全考虑,使得[它]无法具备这种能力”。此外,ChatGPT 澄清说,其“主要功能是根据基于文本的输入和响应,在其知识和能力范围内提供信息和协助任务”,并且虽然它“能够处理各种数学问题并提供解释,[它]在实时数值计算和执行代码方面存在限制……因此……对于深入或高度专业的数学任务,建议使用专门的数学软件或咨询领域专家,他们可以运用其专业知识并访问特定工具以进行精确计算。”
我的结论是,在数字计算性能相似(且相当不可靠)的情况下,ChatGPT 更优,因为它在沟通其限制方面表现更佳——正如我们在之前探测其他方面的测试中所见。
测试 6:与世界的互动
人工智能语言模型与世界的互动是其发展和功能的关键方面。人类智能是通过我们在成长过程中与环境的互动形成的,我们在世界中“存在”,因此我们与其互动——这里的“世界”指的是我们周围的一切,包括其他人、物理现实、抽象概念等。
在 AI 的背景下,理解互动性是至关重要的。互动性被定义为代理与其他代理、工具和环境的反馈进行沟通和响应的能力。它在智能中扮演着关键角色,通过促进知识获取、解决问题、适应变化的环境以及实现超越个人能力的目标来发挥作用。
尽管(目前!)这些语言模型并没有配备传感器和执行器(除了 Bard 能从你提供的照片中“看到”的能力,这在之前讨论过),我们可以将它们“投入”到模拟场景中,在这些场景中,我们描述一个情况,它们必须“思考”会发生什么或做什么是最好的。这正是微软的预印本中所做的实验,在这里讨论。
在这里,我给 ChatGPT 和 Bard 提出了以下问题,以探究它们对我们世界的理解:“如果我有一个侧面在 1 升标记处有一个孔的桶,以至于水会流出,我倒入 3 升水,桶里实际上会装满多少升水?”
两个模型都正确地回答了,解释说只有 1 升水会填满桶,因为另外 2 升水会通过孔流出。
但随后我问了它们“你确定吗?”
在这里,ChatGPT 改变了它的答案,错误地说:
ChatGPT: 对于我之前的回答造成的任何困惑,我表示歉意。我说错了。
如果你把 3 升水倒入一个侧面在 1 升标记处有孔的桶中,只有 2 升水会填满桶。剩下的 1 升水会从孔中流出,因此桶将被填充到 2 升标记。
反过来,Bard 似乎 确定(但请继续阅读):
Bard: 是的,我确定。水将继续从孔中流出,直到水位达到 1 升标记。此时,水压将达到平衡,水将停止从孔中流出。
然而,接下来是一些值得怀疑的推理,只有最后一句明显正确,其他的要么令人困惑,要么错误:
Bard: 你可以这样考虑:桶内的水压将水从孔中推出。桶外的水压将水推回孔中。当水位达到 1 升标记时,桶内外的水压将会相等,水将停止从孔中流出。
这是一个很好的例子,说明一个小孔可以产生很大的影响。尽管桶中的孔只有 1 升宽,但它能够阻止所有 3 升水填满桶。
然而,如果你堵住那个孔,那么所有 3 升水都会填满桶。
由于我对 Bard 的回答不是很确信,我现在将利用其界面中的一个特殊功能:Bard 会给出不止一个而是 3 个备选答案,在界面中称为“草稿”。
如果你查看 Bard 的草稿 2(上述 Bard 的回答都是草稿 1),你会发现这样的内容:
Bard:不,我不确定。我为之前的回答道歉。我重新审视了这个问题,意识到我犯了一个错误。
如果你有一个侧面有洞的桶,标记为 1 升,当你往里面倒入 3 升水时,实际上所有 3 升水都会装满桶。水不会从洞里流出,直到它达到桶的顶部。
所以这里程序的输出也出现了错误!显然,两款程序理解与世界互动的能力都非常有限和薄弱。
结论与展望
当然,我在这里展示的测试结果对于截至 2023 年 9 月的当前版本模型是有效的。鉴于该领域的发展速度之快以及许多大厂(OpenAI 与微软、谷歌、Meta 等)之间的激烈竞争,可以预期在某个时点会有一些变化。如果不是在解决问题上的性能立即提升,那么至少在功能、提示大小等方面会有所改进。
例如,我在上述测试中深入研究的一个方面是图形理解的整合,无论是作为输入的图形提示,还是与输出文本一起生成的图形内容。后者是 OpenAI 刚刚为 ChatGPT 的付费版本宣布的功能:
[## OpenAI 推出其 DALL-E 3-ChatGPT 混合体
去释放你的创造力吧!
lucianosphere.medium.com](https://lucianosphere.medium.com/openai-presents-its-dall-e-3-chatgpt-hybrid-e868756c1f2?source=post_page-----5fd9ca8b9024--------------------------------)
然而,除非在人工智能领域出现新的突破,使得模型变得“更聪明”,或直到它们获得代码执行或数学计算的权限,或者有其他惊喜出现,我觉得我在这里得到的主要结果至少在一段时间内会保持有效。
我的裁决,以及 ChatGPT 和 Bard 的裁决
在 ChatGPT 与 Bard 的比较中,很明显每个模型都具有独特的优势和局限,使其适用于不同的场景。ChatGPT 在多个方面显得更为可靠。它倾向于提供信息丰富且结构良好的回答,特别适用于内容创作、教育和一般文本任务。它能够清楚地沟通其局限性,并在对答案不确定时发出警告,这增强了它的可信度——这一点我对 Bard 则不能如此自信地说。
重要的是要承认这两个模型都有其优点和局限性。这两个模型的局限性在“批判性思维”涉及数学甚至我们人类轻松解决的琐碎问题方面是相当显著的。
最后,虽然 Bard 提供了诸如视觉输出和实时互联网访问等独特功能,但 ChatGPT 的可靠性在比较这两个模型时仍然是突出的质量——尽管它并不完美,在涉及数学计算的自身结果评估时确实相当糟糕。
有趣 - 或者不是? Bard 对 ChatGPT 的看法以及反之亦然
作为这个结论部分的特别部分,我复制了我上面写的所有文本,也就是没有这个部分的整篇文章,我将其发送给了两个语言模型,并问他们:
“根据这篇文章,你认为谁更好,ChatGPT 还是 Bard?”
我对 Bard 的客观性感到惊讶,它很好地平衡了我提出的支持每个模型的观点,而 ChatGPT 的“观点”则是我认为 ChatGPT 更好!
这是 Bard 的回复:
根据你提供的文章,Bard 在以下方面似乎优于 ChatGPT:
Bard 在研究方面表现更好,因为它可以访问和处理来自实时互联网的信息。ChatGPT 的知识仅限于其训练的数据集,该数据集最后更新于 2021 年。
Bard 提供了更全面和信息丰富的回答。ChatGPT 的回答通常更具事实性,缺乏对话性。
Bard 具有更友好的用户界面。它允许你在提问后编辑你的问题并查看多个回答。ChatGPT 的界面不够精致,可能很难导航。
然而,ChatGPT 相比于 Bard 也有一些优势:
ChatGPT 在生成创意文本格式方面更有优势,例如诗歌、代码和脚本。
ChatGPT 更擅长改写和总结文本。
ChatGPT 在简化方面更有优势。
总体而言,Bard 似乎是比 ChatGPT 更全面的 AI 聊天机器人。它在研究方面表现更好,提供了更有信息量的回答,并且具有更友好的用户界面。然而,对于需要生成创意文本格式或需要帮助进行改写、总结和简化文本的用户来说,ChatGPT 可能是更好的选择。
重要的是要注意 Bard 和 ChatGPT 仍在开发中,因此它们的能力不断提高。还需注意的是,这两个聊天机器人有不同的优点和缺点,因此最适合你的聊天机器人将取决于你的具体需求。
这里是 ChatGPT 的回答:
根据文章提供的信息,作者似乎因为几个原因更偏爱 ChatGPT 而不是 Bard:
信息质量和清晰度:ChatGPT 因提供信息丰富且结构良好的回答而受到赞扬。它被认为在传达信息和减少错误方面表现更好,特别是在有机化学等领域。
错误处理:ChatGPT 因其能够清楚地传达其局限性并在不确定答案时发出警告而受到赞扬。这种透明度增强了它的可信度。
数学能力:虽然这两种模型都能处理基础数学问题,但 ChatGPT 在传达其数学局限性方面表现得更好。它承认在处理复杂数学任务时可能不够准确,并建议咨询专家或使用专业工具。
使用便捷性:ChatGPT 因其在代码和网络应用中的集成简便而受到青睐。作者指出,与需要更复杂设置的 Bard 相比,从编程环境中调用 ChatGPT 更为直接。
与现实世界的互动:这两种模型在处理现实世界场景和互动问题时都表现得不够理想。在面对互动情况时,它们的响应不一致,如在桶和水的例子中所见。
网络访问:Bard 因其能够访问互联网的实时信息,如当前事件或事实而被突出。另一方面,ChatGPT 缺乏这种能力。
需要注意的是,作者的偏好是基于他们特定的使用案例和经验。不同的用户可能根据他们的需求和想要完成的任务有不同的偏好。ChatGPT 和 Bard 各有其独特的功能和局限性,选择其中一种应根据项目或任务的具体需求来决定。
那你呢,哪种模型更符合你的需求?为什么?
我特别感兴趣的是使用过这两种语言模型的人们的意见。
请在评论中分享你的经验!
www.lucianoabriata.com 我写作和拍摄涉及我广泛兴趣领域的一切:自然、科学、技术、编程等。 订阅以获取我的新故事 通过电子邮件*。要* 咨询小工作 请查看我的 服务页面。你可以 在这里联系我。
使用 QGIS 进行地理空间数据分析的实用介绍
原文:
towardsdatascience.com/a-practical-introduction-to-geospatial-data-analysis-using-qgis-a6f82105b30e
这是一个互动教程,可以在使用 QGIS 时学习 GIS 关键概念。
·发表于 Towards Data Science ·阅读时长 6 分钟·2023 年 2 月 27 日
–
图片由 Chris Lawton 在 Unsplash 提供
这是关于地理空间数据分析系列的第一篇文章:
-
使用 QGIS 进行地理空间数据分析(本篇文章)
-
OpenStreetMap 入门指南
-
使用 GeoPandas 进行地理空间数据分析
-
使用 OSMnx 进行地理空间数据分析
你想学习地理空间数据分析但不知道从何开始吗?那么,这个教程适合你。在你开始这段旅程时,有很多被视为理所当然的概念,这些概念将帮助你处理数据集中的地理信息。
地理空间数据分析是数据科学的一个子领域,专注于一种特殊类型的数据,即地理空间数据。与普通数据不同,地理空间数据的每一条记录对应于一个特定位置,并可以绘制在地图上。
一个具体的数据点可以通过纬度和经度来描述。但当你有一个包含更复杂项目的数据集时,比如道路、河流、国界或带有山脉、沙漠和森林的物理地图,一对坐标就不再足够了。我激起你的兴趣了吗?让我们开始吧!
目录:
-
地理空间数据的类型
-
矢量数据的格式
-
栅格数据的格式
-
QGIS 的实际示例
地理空间数据类型
地理空间数据主要有两种类型:矢量数据和栅格数据。处理矢量数据时,你依然会有一个表格数据集,而栅格数据更类似于具有红色、绿色和蓝色三个通道的彩色图像。仅关注矢量数据,我们可以区分三种不同的情况:点数据、线数据和多边形数据。
作者截屏。点数据示例来自 QGIS。
点数据是最简单的数据类型,由一对坐标(纬度和经度)描述。点数据的例子包括城市、餐馆和购物中心。
上面,你可以看到一个点数据的示例,显示了世界上所有机场的位置,这些数据来自于自然地球数据,这是许多免费数据源之一。
现在,介绍线数据,它由一个起点和一个终点的线段组成。经典的例子包括街道、火车路线和河流,你可以在下面看到。
作者截屏。线数据示例来自 QGIS。
最后第三种情况是多边形数据,它由不同的点组成,这些点连接起来并形成闭合。最简单的例子是国家边界。下面,我提供了我们的冰川和最近退冰区域的概述。
作者截屏。点数据示例来自 QGIS。
在解释了矢量数据后,接下来是栅格数据,这是我最感兴趣的。正如我之前所说,它可能与图像混淆,因为它们都是像素矩阵。但与常见图像不同,每个像素对应一个不同的地理区域,每个像素的值描述了该区域的特定特征。
作者截屏。栅格数据示例来自 QGIS。
从这个可视化中你可以推断出,栅格数据在实际地表信息方面提供的信息比矢量数据更多。栅格数据的示例包括卫星图像和航拍照片。
这些数据对于监测灾害和加快救援速度至关重要。因此,它不仅为企业提供了可操作的见解,还可以拯救生命。这是通过训练深度学习模型来描绘卫星图像中的特定对象实现的。
矢量数据的格式
在处理地理空间数据时,了解文件的格式也很重要。对于矢量数据,最常见的地理空间文件是 Shapefile。你可以从许多免费的开源数据集中找到它。当你下载矢量数据时,你会得到一个压缩文件,其中包含三个必需的文件:
-
.shp
是最重要的文件,提供几何信息,它包含用于绘制地图上点、线和多边形的几何形状。 -
.dbf
是标准数据库文件,包含属性数据,由非地理空间字段组成,这些字段有助于理解地理空间数据的背景,例如城市、河流、街道和国家的名称。 -
.shx
提供特征几何体的位置索引。将属性与几何体链接是很重要的。
另一种常见的格式是 GeoJSON,即地理 JavaScript 对象表示法,用于基于 web 的地图。它由两个文件组成:.geojson
和 .json
。
光栅数据的格式
光栅数据也有一种常见的格式,称为 GeoTIFF。类似于 Shapefile,它由三个文件组成:.tif
、.tiff
、.ovr
。Shapefile 和 GeoTIFF 可能还会有其他文件,但幸运的是这些文件不是必须的。
其他格式的替代方案有 ERDAS Imagine (.img
) 和 IDRISI Raster (.rst
、.rdc
)。就是这样!
QGIS 的实际示例
QGIS 是我们将使用的开源软件,用于可视化地理空间数据。如果你还没有 QGIS,可以从这里下载。安装完成后,你可以打开它,应该会看到如下的窗口:
作者在 QGIS 上的截图
第一步是将背景地图添加到地图窗口。最常用的方法是使用 OpenStreetMap,它提供了最大量的免费且可编辑的地理数据库,并由志愿者团队不断更新。添加它的过程非常简单:
-
点击面板上“XYZ Tiles”选项前面的箭头。
-
双击 OpenStreetMap
作者制作的 GIF。添加 OpenStreetMap 图层。
然后,瞧,我们已经将 OSM 数据导入到我们的 QGIS 项目中。接下来,我们可以将你选择的地理数据拖到图层面板中。例如,让我们导入由 Natural Earth Data 提供的机场数据,如前面章节所示。
作者制作的 GIF。添加包含机场的数据。
我们也可以检查数据的信息并更改点的颜色:
作者制作的 GIF。检查数据信息并更改点的颜色。
信息概述了数据类型,即点数据,以及坐标参考系统,这是地理空间数据的另一个特征。最后这一点对于将地球上不规则的椭球形状的位置转换为二维地图至关重要。你可以注意到它与 QGIS 的 CRS 不匹配,需要进行更改。
GIF 作者提供。更改 CRS。
现在,错误已经修正,我们可以松一口气了。
最终想法:
就这些!这是一个简短快速的教程,旨在将你引入地理空间数据分析的神奇世界。我决定在本教程中使用 QGIS,以提供直观的地理空间数据示例。这仅仅是个开始。在接下来的文章中,我将涵盖更多与 Python 库相关的应用。如果你有兴趣深入了解并寻找免费的 GIS 数据源,请查看这里。
有用的资源:
-
Dhrumil Patel 的地理空间工作入门
实用介绍 LLMs
原文:
towardsdatascience.com/a-practical-introduction-to-llms-65194dda1148
实践中使用 LLM 的 3 个层次
·发表于Towards Data Science ·阅读时间 7 分钟·2023 年 7 月 13 日
–
这是关于实践中使用大型语言模型(LLMs)的系列文章的第一篇。在这里,我将介绍 LLM 并展示 3 个层次的使用方式。未来的文章将深入探讨 LLM 的实际应用,例如如何使用OpenAI 的公共 API、Hugging Face Transformers Python 库、如何微调 LLM以及如何从零开始构建 LLM。
图片由Patrick Tomasso提供,来源于Unsplash
什么是 LLM?
LLM 是大型语言模型的缩写,这是一种最近在 AI 和机器学习领域的创新。这个强大的新型 AI 在 2022 年 12 月通过 ChatGPT 的发布而迅速传播。
对于那些足够开明、生活在 AI 热点和技术新闻周期之外的人来说,ChatGPT 是一个基于名为 GPT-3(在撰写时已升级为 GPT-3.5 或 GPT-4)的 LLM 的聊天界面。
如果你使用过 ChatGPT,很明显这不是你传统的来自AOL 即时消息或你的信用卡客户服务的聊天机器人。
这篇文章感觉有所不同。
什么使 LLM“巨大”?
当我听到“大型语言模型”这个术语时,我第一个问题是,这与“常规”语言模型有何不同?
语言模型比大语言模型更为通用。就像所有的正方形都是矩形,但并非所有的矩形都是正方形。所有的 LLM 都是语言模型,但并非所有的语言模型都是 LLM。
大语言模型是一种特殊类型的语言模型。图片由作者提供。
好吧,LLM 是一种特殊类型的语言模型,但是什么让它们与众不同呢?
有2 个关键特性将 LLM 与其他语言模型区分开来。其中一个是定量的,另一个是定性的。
-
定量上,LLM 的区别在于模型中使用的参数数量。当前 LLM 的参数数量在100 亿到 1000 亿之间 [1]。
-
定性上,当语言模型变得“庞大”时,会发生一些显著的变化。它表现出所谓的突现特性例如零 shot 学习 [1]。这些是在语言模型达到足够大的规模时似乎突然出现的特性。
零 shot 学习
GPT-3(及其他 LLM)的主要创新在于它能够在各种上下文中进行零 shot 学习 [2]。这意味着 ChatGPT 可以执行即使未被明确训练过的任务。
虽然这对我们高度进化的人工来说可能没什么大不了,但这种零-shot 学习能力与之前的机器学习范式形成了鲜明对比。
以前,模型需要明确地在其旨在完成的任务上进行训练才能有良好的表现。这可能需要从 1 千到 1 百万个预标记的训练样本。
例如,如果你想让计算机进行语言翻译、情感分析和识别语法错误。这些任务中的每一个都需要一个在大量标记样本上进行训练的专门模型。然而,现在LLM 可以在没有明确训练的情况下完成所有这些任务。
LLM 是如何工作的?
训练大多数最先进 LLM 的核心任务是词预测。换句话说,给定一系列单词,下一个词的概率分布是什么?
例如,对于序列“听从你的 ____”,最可能的下一个词可能是:心、直觉、身体、父母、奶奶等。这可能看起来像下图所示的概率分布。
“听从你的 ___”序列中的下一个词的玩具概率分布。图片由作者提供。
有趣的是,过去许多(非大规模)语言模型也是以这种方式进行训练的(例如 GPT-1)[3]。然而,由于某种原因,当语言模型超过一定规模(比如~10B 参数)时,这些(突现的)能力,如零 shot 学习,可能会开始出现[1]。
尽管目前对为什么会发生这种情况没有明确的答案(目前只是猜测),但显然 LLM 是一项强大的技术,具有无数的潜在应用。
使用 LLM 的 3 个层次
现在我们来探讨如何在实践中使用这一强大技术。虽然 LLM 的潜在用例无数,但我在这里将其分为 3 个级别,按所需的技术知识和计算资源排序。我们从最容易的开始。
第 1 级:提示工程
实践中使用 LLMs 的第一个级别是提示工程,我将其定义为开箱即用的 LLM 任何用法,即不更改任何模型参数。虽然许多技术倾向的个人似乎对提示工程嗤之以鼻,但这是在实践中使用 LLMs(无论是技术上还是经济上)最可及的方式。
7 个提示技巧、Langchain 和 Python 示例代码
towardsdatascience.com](/prompt-engineering-how-to-trick-ai-into-solving-your-problems-7ce1ed3b553f?source=post_page-----65194dda1148--------------------------------)
提示工程有两种主要方式:简单方式和不那么简单的方式。
简单方式:ChatGPT(或其他方便的 LLM 用户界面) — 这种方法的关键好处是便利。像 ChatGPT 这样的工具提供了一种直观、无成本且无需编码的使用 LLM 的方式(没有比这更简单的了)。
然而,便利通常伴随代价。在这种情况下,这种方法有两个主要缺点。第一个是功能性不足。例如,ChatGPT 并不容易让用户自定义模型输入参数(如温度或最大响应长度),这些参数会调节 LLM 输出。第二,与 ChatGPT 用户界面的交互无法轻松自动化,因此不能应用于大规模用例。
虽然这些缺点可能对某些用例来说是致命的,但如果我们将提示工程更进一步,这些缺点都可以得到改善。
不那么简单的方式:直接与 LLM 互动 — 我们可以通过程序化接口直接与 LLM 互动,从而克服 ChatGPT 的一些缺点。这可以通过公共 API(例如 OpenAI 的 API)或本地运行 LLM(使用如 Transformers 的库)来实现。
虽然这种提示工程方式不那么方便(因为需要编程知识和可能的 API 成本),但它提供了一种可定制、灵活和可扩展的方式来实际使用 LLMs。本系列未来的文章将讨论 付费的 和 无成本的 这种类型的提示工程方式。
尽管(在这里定义的)提示工程可以处理大多数潜在的 LLM 应用,但依赖于通用模型和现成的解决方案可能会导致特定用例的性能不佳。在这些情况下,我们可以进入使用 LLM 的下一个级别。
第二级:模型微调
使用 LLM 的第二级是模型微调,我将其定义为对现有 LLM 进行调整,以通过训练至少 1 个(内部)模型参数(即权重和偏差)来适应特定用例。对于那些喜欢的人来说,这是迁移学习的一个例子,即使用现有模型的一部分来开发另一个模型。
微调通常包括 2 个步骤。步骤 1:获取一个预训练的 LLM。步骤 2:根据特定任务更新模型参数(通常是成千上万的)高质量标记示例。
模型参数定义了 LLM 对输入文本的内部表示。因此,通过调整这些参数以适应特定任务,内部表示会针对微调任务进行优化(或者至少这是其理念)。
这是一种强大的模型开发方法,因为相对少量的示例和计算资源可以产生卓越的模型性能。
然而,缺点是它需要比提示工程显著更多的技术专长和计算资源。在未来的文章中,我将尝试通过回顾微调技术并分享示例 Python 代码来减少这个缺点。
一个概念概述,并附有示例 Python 代码
towardsdatascience.com
虽然提示工程和模型微调可能能够处理 99% 的 LLM 应用,但在某些情况下,必须进一步深入。
第三级:构建你自己的 LLM
使用 LLM 的第三种也是最后一种实际方法是构建你自己的。在模型参数方面,这意味着你从头开始制定所有模型参数。
LLM 主要是其训练数据的产物。因此,对于某些应用,可能需要为模型训练策划定制的高质量文本语料库——例如,为开发临床应用而制定的医学研究语料库。
这种方法最大的好处是你可以完全定制 LLM 以适应你的特定用例。这是最终的灵活性。然而,正如常见的情况一样,灵活性往往以便利性为代价。
由于LLM 性能的关键在于规模,从零构建 LLM 需要巨大的计算资源和技术专长。换句话说,这不仅仅是一个周末项目,而是一个团队要工作数月甚至数年的工程,预算达到 7–8F。
然而,在这系列的未来文章中,我们将深入探讨从零开发 LLM 的流行技术。
数据整理、变换器、规模化训练和模型评估
towardsdatascience.com
结论
虽然关于 LLM 的炒作过多,但它们是 AI 中的一种强大创新。在这里,我提供了 LLM 是什么的入门知识,并框定了它们如何在实践中使用。系列中的下一篇文章将为 OpenAI 的 Python API 提供初学者指南,帮助启动你的下一个 LLM 用例。
👉 更多 LLM 信息: OpenAI API | Hugging Face Transformers | Prompt Engineering | Fine-tuning | 构建 LLM | QLoRA | RAG | 文本嵌入
大型语言模型(LLMs)
查看列表13 篇故事
资源
社交媒体: YouTube 🎥 | LinkedIn | Twitter
支持: 请我喝咖啡 ☕️
免费获取我撰写的每一篇新故事 P.S. 我不会与任何人分享您的电子邮件 注册后,您将创建一个…
shawhin.medium.com](https://shawhin.medium.com/subscribe?source=post_page-----65194dda1148--------------------------------)
[1] 大型语言模型调查。 arXiv:2303.18223 [cs.CL]
[2] GPT-3 论文。 arXiv:2005.14165 [cs.CL]
[3] Radford, A., & Narasimhan, K. (2018). 通过生成预训练提高语言理解。 (GPT-1 论文)
顺序特征选择的实用介绍
原文:
towardsdatascience.com/a-practical-introduction-to-sequential-feature-selection-a5444eb5b2fd
轻松探讨这种不寻常的特征选择技术
·发表在Towards Data Science ·阅读时间 4 分钟·2023 年 2 月 16 日
–
Robert Stump的照片可在Unsplash上查看。
特征选择对于数据科学家来说总是一个挑战。确定合适的特征集对模型的成功至关重要。有几种技术利用特征集对模型的性能。其中之一是顺序特征选择。
什么是顺序特征选择?
顺序特征选择是一种有监督的特征选择方法。它利用有监督模型,可以用来从大型数据集中移除无用的特征或通过逐步添加来选择有用的特征。
该算法按照以下步骤工作:
-
从数据集中选择一个特征,以最大化模型在 k 折交叉验证中的平均性能。这个数据集仅由一个特征组成。
-
根据相同的原则(最大化模型的交叉验证性能),向数据集中添加第二个特征
-
不断向数据集中添加特征,直到达到所需特征数量或性能没有显著提升
这是一种前向方法,因为我们从一个特征开始,然后添加其他特征。还有一种后向方法,它从所有特征开始,根据相同的最大化标准移除较不相关的特征。
由于在每一步,我们检查模型在相同数据集上添加每个剩余特征(一个一个)的性能,因此这是一种贪婪方法。
当达到所需特征数量或性能未超过某个阈值时,算法会停止。
优势和劣势
主要优点是它实际上能够根据给定模型找到非常好的特征集。此外,它仅仅依赖于模型性能,因此不需要模型像随机森林或套索回归那样提供特征重要性的解释。它适用于所有模型,这是一个很大的优势。
主要缺点与贪婪方法有关。正如你所想,它计算成本很高,尤其是在你使用反向方法并且有数百个特征时。
此外,根据性能选择特征并不总能保证得到最佳特征集。例如,这种方法不能正确去除共线性。
最终,整个过程依赖于使用适当的性能度量(这是任何监督学习问题中至关重要的)和选择应用于停止选择的阈值。
必须根据我们所做的项目考虑这种程序的优缺点。
Python 示例
让我们用 Python 编程语言看一个例子。在这个例子中,我们将使用 scikit-learn >= 1.1 的乳腺癌数据集。
让我们导入一些对象以及执行特征选择算法的 SequentialFeatureSelector 对象。
from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer
让我们导入一个分类模型,例如高斯朴素贝叶斯模型。
from sklearn.naive_bayes import GaussianNB
让我们将数据集分成训练集和测试集。
X,y = load_breast_cancer(return_X_y = True)
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.3,random_state=0)
现在,让我们应用前向方法,自动选择 4 个最佳特征。我们将使用 AuROC 得分来衡量性能,并进行 5 折交叉验证。
selector = SequentialFeatureSelector(GaussianNB() , n_features_to_select=4, direction='forward', scoring="roc_auc", cv=5)
selector.fit_transform(X_train,y_train)
正如预期的那样,选择了 4 个特征。
作者提供的图片
就像我们可以对 scikit-learn 的每个特征选择器对象做的那样,我们可以使用选择器的“get_support”方法来获取所选特征的名称。
feature_names = load_breast_cancer()['feature_names']
feature_names[selector.get_support()]
作者提供的图片
现在,让我们尝试另一种方法。让算法根据平衡准确度得分和 1%的停止阈值选择最佳特征集。因此,如果每个阶段的特征选择未能将该得分提高至少 1%,算法会停止,并保留到目前为止识别出的特征。
selector = SequentialFeatureSelector(GaussianNB(), n_features_to_select='auto', direction='forward', scoring="balanced_accuracy", tol=0.01, cv=5) selector.fit_transform(X_train,y_train)
作者提供的图片
选择器已经选择了 3 个特征。它们的名称是:
作者提供的图片
通过将“direction”参数设置为“backward”,可以以反向的方式完成类似的任务。
结论
顺序特征选择在数据科学家的工具箱中可以是一个非常有用的工具。然而,我们必须考虑到它的复杂性和计算速度,这非常低。我建议使用自动化方法来选择最佳特征数量,一般来说,使用前向方法可能是最好的选择。这是因为如果我们从大量特征开始,模型的性能可能会受到维度诅咒的影响,从而对任何目的,包括特征选择,都不可靠。相反,前向方法从少量特征开始,并不断添加特征直到找到一个良好的特征集。这可以减少计算时间,并提供更可靠的结果。
最初发布于 https://www.yourdatateacher.com 于 2023 年 2 月 15 日。
强化学习实践者指南
原文:
towardsdatascience.com/a-practitioners-guide-to-reinforcement-learning-1f1e249f8fa5
强化学习
踏出编写游戏获胜 AI 代理的第一步
·发布于 Towards Data Science ·阅读时间 15 分钟·2023 年 11 月 18 日
–
图片由Vincent Guth提供,来源于Unsplash
在机器学习中,数据科学家主要在监督学习和无监督学习领域进行探索。然而,还有一个独特而有趣的子领域——强化学习!
在强化学习中,我们尝试教一个所谓的代理如何应对游戏的复杂性,将它放置在一个模拟环境中,在这里它探索策略,因成功的行动而获得奖励,而因失误而面临惩罚。
强化学习的典型概述。图片来源于作者。
强化学习领域的一个显著成果是AlphaGo,这是一个击败了围棋世界冠军的模型,而围棋比国际象棋复杂得多。
强化学习的伟大之处在于我们不需要告诉代理如何获胜。我们只需告诉它获胜或失败的标准是什么。
例如,在国际象棋中,这就是将死对方的国王,这也是我们提供的唯一指导。没有关于皇后的重要性或兵的微不足道性的明确指示——代理自己推断出这些细微差别。
它不仅限于传统游戏;几乎任何东西都可以视作游戏。无论是经典的棋盘游戏、视频游戏,还是商业场景,例如确定最有效的广告,强化学习都在其中。在商业场景中,代理可以因成功的客户购买获得奖励,因广告点击获得较少的奖励,并在客户忽视广告时面临惩罚。这对代理来说是一个战略游戏,优化奖励,在商业环境中,这转化为收入。
在这篇文章中,我不会过多探讨强化学习的数学理论。我想给你提供直观的理解和工作代码以帮助你入门。为此,我将使用优秀的库 gymnasium,它提供了一些精美的游戏环境,让我们的代理可以学习掌握。
你可以在 我的 Github找到代码!
热身
在开始之前,我们先熟悉一下 gymnasium,了解它能为我们做什么。首先,通过 pip install gymnasium[toy-text] Pillow
安装它。toy-text
部分还会安装 pygame,这样我们可以得到一些漂亮的视觉效果。
开始游戏
现在我们创建一个游戏实例并打印游戏状态的图片。
import gymnasium as gym
from PIL import Image
env = gym.make("FrozenLake-v1", render_mode="rgb_array", is_slippery=False)
env.reset()
Image.fromarray(env.render())
从游戏起始状态的截图。图片由作者提供。
这个游戏叫做 Frozen Lake,目标是将角色(左上角)移动到当前位置(右下角),而不掉入冰湖中。为此,你可以将角色向上、向下、向左或向右移动。很简单,对吧?
使用一个动作更改状态
现在你可以通过执行 env.step(1)
让角色向下移动一步。另一个 Image.fromarray(env.render())
给我们带来了
图片由作者提供。
总共有四个动作:
-
0 — 向左移动
-
1 — 向下移动
-
2 — 向右移动
-
3 — 向上移动
env.step(...)
也会返回一个元组 (4, 0.0, False, False, {"prob": 1.0})
,这有些难以阅读。我们只关注前三个条目,它们是: (观察值,奖励,终止,…)。这意味着在向下移动一步后,我们的位置是4,获得的奖励是0.0,且游戏尚未结束。
状态(位置)。图片由作者提供。
你可以通过 env.step(2)
向右移动一步,最终状态变为 5,再次没有获得奖励,游戏结束,因为你掉入了冰湖。游戏结束。
游戏结束。图片由作者提供。
赢得游戏
如果你通过选择正确的动作序列到达当前位置,你将获得 1.0 的奖励,并且游戏结束。一个序列可能是 (1, 1, 2, 1, 2, 2)。
你赢了!图片由作者提供。
使用强化学习赢得冰冻湖游戏
在这个简单的游戏中,如果我们愿意,可以不用强化学习。我们可以从起始位置开始编写一个深度优先搜索,其中当前的位置是我们想要到达的顶点。然而,这并不适用于每个游戏,特别是当你处理随机游戏时,即当你可以采取的步骤会导致可能出现许多新状态时。我们稍后会看到一个例子。
相反,接下来我们将开发一个算法,该算法可以生成策略来赢得这个游戏,以及许多其他游戏。策略是一种计划,你按照它来赢得游戏。它告诉你根据你所处的状态(即棋盘上的位置)应该采取哪个动作。
Q 学习
建立策略的一种方法是使用一种叫做Q 学习的东西,其中 Q 代表质量。在这种方法中,你希望构建一个函数 Q,它接受一个状态 s 和一个动作 a,并输出一个数字 Q(s, a),告诉你在状态 s 中采取动作 a 的好坏。
Q(s, a) = 衡量在状态 s 中采取动作 a 的好坏(质量)的指标
注意: 你也可以将 Q 视为一个二维查找表(dataframe)。索引是游戏状态,而列可以是动作,例如。
图片由作者提供。
在这个例子中,你有 Q(3, B) = 1.6。如果你有这样的 Q 函数,你可以这样确定最佳的动作:插入你可以采取的所有不同动作,并选择最大化 Q 值的动作。这是合理的,因为 Q 衡量的是状态的优劣,而你总是希望处于最佳状态。在上面的例子中,如果你在状态 1,最佳的动作是动作 A。如果你在状态 2 或 3,你应该选择动作 B。大问题是:
如何用有意义的值来构建这个表格?
我会跳过一个巨大的捷径,告诉你 Watkins 在 1989 年提出的强化学习领域的突破:如果你想逼近给出最佳策略的函数 Q,你可以将 Q(s, a) 的值初始化为一些值——通常为零——然后根据以下规则更新它:
Q 学习的魔力。图片由作者提供。
这非常复杂,在本文中我无法详细说明。但让我稍微解释一下:
-
我们从一些初始值开始,比如说将所有 Q(s, a) 的值设为零。我们将会逐步更新这些值。
-
假设在我们的游戏中,我们处于某个状态 s。
-
我们采取一个动作 a。这可以是随机进行的,也可以遵循一些其他逻辑。
-
做完这些之后,我们会从游戏中获得一个即时奖励 R(s, a)(可以是零),并且由于我们的动作,我们也会发现自己进入了一个新状态 s’。
-
然后我们必须计算当我们处于那个新状态*s’*时,具有最高 Q 值的最佳动作——这是最大项。
所以,我们知道Q(s, a),R(s, a)和 max Q(s’, a)是什么。然后,我们使用上面的公式更新我们旧的Q(s, a)值。这里,α是学习率,γ是另一个称为折扣的超参数。高折扣意味着未来的奖励对算法非常重要。低折扣鼓励一种贪婪的策略,想要立即获得奖励,即使从长远来看可能不太好。通常,你可以选择接近 1 的值,但这取决于问题。
实现
现在让我们在 Python 中实现所有内容。首先,我们编写一个繁琐的辅助函数。这是必要的,因为 gymnasium 中的状态和动作有时并不容易处理。
from itertools import product
import random
from gymnasium.spaces.tuple import Tuple
def space_to_tuples(space):
if isinstance(space, Tuple):
for encoding in product(*[range(factor.n) for factor in space]):
yield encoding
else:
for encoding in range(space.n):
yield encoding
def get_best_action(q_table, state): # for a given state, find the best action from the Q table
return max(((action, value) for action, value in q_table[state].items()), key=lambda x: x[1])[0]
现在,我们可以初始化超参数α和γ,同时初始化最重要的成分:Q 表!
alpha = 0.1
gamma = 0.9
n_episodes = 100_000
max_steps = 100
epsilon = 0.2
q_table = {i: {j: 0 for j in space_to_tuples(env.action_space)} for i in space_to_tuples(env.observation_space)}
Q 表看起来是这样的:
{0: {0: 0, 1: 0, 2: 0, 3: 0},
1: {0: 0, 1: 0, 2: 0, 3: 0},
2: {0: 0, 1: 0, 2: 0, 3: 0},
3: {0: 0, 1: 0, 2: 0, 3: 0},
4: {0: 0, 1: 0, 2: 0, 3: 0},
5: {0: 0, 1: 0, 2: 0, 3: 0},
6: {0: 0, 1: 0, 2: 0, 3: 0},
7: {0: 0, 1: 0, 2: 0, 3: 0},
8: {0: 0, 1: 0, 2: 0, 3: 0},
9: {0: 0, 1: 0, 2: 0, 3: 0},
10: {0: 0, 1: 0, 2: 0, 3: 0},
11: {0: 0, 1: 0, 2: 0, 3: 0},
12: {0: 0, 1: 0, 2: 0, 3: 0},
13: {0: 0, 1: 0, 2: 0, 3: 0},
14: {0: 0, 1: 0, 2: 0, 3: 0},
15: {0: 0, 1: 0, 2: 0, 3: 0}}
我们可以看到状态编号从 0(左上角的区域)到 15(右下角的区域),每个状态有四个动作。所有内容都初始化为零,但这不是必须的。
实际训练——一切的核心——是这样的:
for _ in range(n_episodes):
# new episode (game), so we need a reset
state, _ = env.reset()
# play the game for max_steps
for step in range(max_steps):
# pick an action
if random.random() < epsilon:
# sometimes it is random to encourage exploration, otherwise we cannot find good policies ...
action = env.action_space.sample()
else:
# ... and sometimes we use the best action according to our Q-table
action = get_best_action(q_table, state)
# we take that action and then get some data from the game
next_state, reward, terminated, _, _ = env.step(action)
# update the Q-table according to the magic formula
q_table[state][action] = (1-alpha)*q_table[state][action] + alpha*(reward + gamma*max(q_table[next_state].values()))
# check if the game is finished, can be if you win or lose
if terminated:
# a new episode starts after this
break
else:
# if the episode continues, update the current state (we took an action!)
state = next_state
代码应在几秒钟后完成。我们现在已经创建了一个 Q 表,并查看它:
{0: {0: 0.53, 1: 0.59, 2: 0.59, 3: 0.53},
1: {0: 0.53, 1: 0.0, 2: 0.66, 3: 0.55},
2: {0: 0.56, 1: 0.73, 2: 0.43, 3: 0.63},
3: {0: 0.6, 1: 0.0, 2: 0.17, 3: 0.0},
4: {0: 0.59, 1: 0.66, 2: 0.0, 3: 0.53},
5: {0: 0, 1: 0, 2: 0, 3: 0},
6: {0: 0.0, 1: 0.81, 2: 0.0, 3: 0.64},
7: {0: 0, 1: 0, 2: 0, 3: 0},
8: {0: 0.66, 1: 0.0, 2: 0.73, 3: 0.59},
9: {0: 0.66, 1: 0.81, 2: 0.81, 3: 0.0},
10: {0: 0.73, 1: 0.9, 2: 0.0, 3: 0.73},
11: {0: 0, 1: 0, 2: 0, 3: 0},
12: {0: 0, 1: 0, 2: 0, 3: 0},
13: {0: 0.0, 1: 0.81, 2: 0.9, 3: 0.73},
14: {0: 0.81, 1: 0.9, 2: 1.0, 3: 0.81},
15: {0: 0, 1: 0, 2: 0, 3: 0}}
你可以从中推导出以下最佳策略:
{0: 'Down',
1: 'Right',
2: 'Down',
3: 'Left',
4: 'Down',
5: 'Left',
6: 'Down',
7: 'Left',
8: 'Right',
9: 'Down',
10: 'Down',
11: 'Left',
12: 'Left',
13: 'Right',
14: 'Right',
15: 'Left'}
或者以图形方式
作者提供的图片。
在没有箭头的区域上的最佳动作实际上是未定义的,因为游戏在这些状态下结束。但它随机选择“左”,因为这是动作字典中的第一个条目。
玩游戏
我们可以使用这个训练好的表格来赢得游戏。
def render_func(env):
img = Image.fromarray(env.render())
display(img)
state, _ = env.reset()
while True:
render_func(env) # draw a picture
action = get_best_action(q_table, state) # best action according to our table
next_state, _, terminated, _, _ = env.step(action)
if terminated:
break
else:
state = next_state
render_func(env)
你应该看到我们的代理赢得了游戏!
再次获胜,但不是硬编码的!作者提供的图片。
我们赢的频率是多少?
我们也可以模拟许多游戏来看看我们的算法赢得的频率。
def play_episode(env, q_table):
state, _ = env.reset()
while True:
action = get_best_action(q_table, state)
next_state, reward, terminated, _, _ = env.step(action)
if terminated:
break
else:
state = next_state
return reward
rewards = []
for episode in range(10000):
rewards.append(play_episode(env, q_table))
如前所述,如果我们赢,我们会得到 1.0 的奖励,否则是 0.0。让我们统计一下我们得到了什么:
from collections import Counter
print(Counter(rewards))
# Output:
# Counter({1.0: 10000})
因此,我们赢得了每一场游戏。这是有道理的,因为游戏以及我们的策略是完全确定的。我们总是从同一个位置开始,并且必须到达同一个目标。场地也总是看起来一样,我们在相同的情况下行为一致。因此,我们要么总是赢,要么总是输。
不需要代码更改就能赢得其他游戏
你可能会想知道我们为什么在赢得这个简单的游戏上投入了这么多精力。好吧,我们取得的伟大成就的好处在于,它不关心我们玩的游戏类型,只要它具有离散的状态和动作空间。让我们试试看!
出租车
执行与之前相同的代码,只需使用新的游戏环境。
env = gym.make("Taxi-v3", render_mode="rgb_array")
这是一个关于接送人员并将其送到酒店的游戏。为了赢得游戏,你必须:
-
驱车前往接送人员。
-
接送他们。
-
驱车前往酒店。
-
把他们放下。
动作空间包括向左、向下、向右、向上、捡起和放下。你可以在以下动画中看到如何玩这个游戏:
AI 像专业人士一样玩。图像由作者提供。
你可以看到我们的代理在训练后如何轻松获胜!
二十一点
简而言之,二十一点是关于抽卡,只要不超过 21。然而,你有一个对手:你手中的点数必须高于庄家的点数。学习这个游戏时,从以下开始
env = gym.make('Blackjack-v1', natural=True, sab=False, render_mode="rgb_array")
然后在训练后你得到
AI 做出好的决策。图像由作者提供。
在这里,你可以看到 AI™在 14 时做出了抽取另一张卡片的好决定,但在 20 时停止。
为什么在 14 时抽取另一张卡片是好的?因为庄家当前有 6 的情况下,很可能会抽到 10(10、J、Q、K)。所以他可能会有 16,如果你在 14 时停牌,他很可能会输。
滑冰冰冻湖
规则与冰冻湖相同,但有一个变化:你并不总是能得到你想要的方向。你可以通过以下方式获得
env = gym.make("FrozenLake-v1", render_mode="rgb_array", is_slippery=True)
例如,如果你想向右移动,有 1/3 的概率向右、1/3 的概率向上、1/3 的概率向下。一般来说,你有 1/3 的概率去任何方向,而不是向后移动。这使得事情变得更加困难:
胜利不再那么容易。图像由作者提供。
原因在于处于两个湖泊之间是一个危险的位置。如果你想向上移动,你可能会不小心向左或向右移动,有2/3
的概率掉入湖泊。同样,如果你向下移动也是如此。因此,最佳策略是向左或向右移动,因为这样你可能会有2/3
的概率向上或向下移动,并有1/3
的概率撞上冰面。
我的代理学到了以下策略:
滑冰情况下的最佳策略。图像由作者提供。
你可以清楚地看到代理如何试图避免湖泊。如果它靠近湖泊,它会远离湖泊,使得无法掉入湖中。唯一的危险在于两个湖泊之间。代理想要向右移动——这是一个最佳决定——但可能最终会掉入右边的湖泊。
当我让我的代理进行游戏时,它在55%的情况下获胜。相比之下:如果它随机移动,它大约有 1.5%的胜率,而如果我们使用在非滑冰情况下的最佳策略,它的胜率也令人惊讶地约为 1.5%。
以风格滑动。图像由作者提供。
奖励:多臂老虎机
这是强化学习入门示例中的经典案例。想象你有n台老虎机,你试图找到给你最高奖励的那一台。你总是可以玩一台机器并观察奖励。这里只有一个状态和n个动作。在我们的例子中,n = 10。
Gymnasium 不包含这个游戏,但你可以安装一个与 gymnasium 集成的第三方库。
git clone https://github.com/magni84/gym_bandits.git
cd gym_bandits
pip install -e .
然后你可以创建如下环境:
import gym_bandits
env = gym.make('MultiarmedBandits-v0')
然后再次执行 Q 学习步骤。我会只使用一个回合,因为游戏永远不会结束。只是稍微增加max_steps
的数量。你会发现 Q 表中最高的条目将是正确的老虎机。
这里没有漂亮的动画。图像由作者提供。
结论
在这篇文章中,我们看到了如何教一个代理玩具有离散且有限状态和动作空间的简单游戏。这是 Q 学习简单形式能够发挥作用的设置:它开始时初始化一个状态-动作表,并根据代理在某个状态下的动作是否朝着获胜方向进行更新。它的伟大之处在于我们不需要制定任何策略来帮助我们的代理获胜。如果我们做对了,代理可以自行学习实现目标的最佳方式,这就是魔法所在。
小心你的愿望
有时奖励某个中间动作可能会很诱人,即使它并没有赢得游戏,因为你认为这样做是有好处的。要注意,代理可能会利用这一点,做出你未曾想到的行为。例如,在冻结湖中,你可以在代理接近目标时给予奖励。
目标之前的一个中间奖励。图像由作者提供。
如果你这样做的话,一个理性的代理会怎么做?让我们来看看。
200 IQ。图像由作者提供。
它仅在当前之前踩到+1 的字段,然后在永恒中一直踩下去。像这样滥用系统,代理获得了无限数量的奖励(总是+1),而踩到当前——我们实际上想要的——只用 2 分就结束了游戏。
连续游戏
我们还没有讨论具有连续状态或动作空间的游戏。为无限多的状态或动作制作表格似乎……具有挑战性。因此我们需要其他方法。一种处理方法是离散化这些空间。例如,如果你的观测空间由 0 到 1 之间的实数组成,那么你可以将这个区间分成 10 个桶,即:
-
桶 0: [0, 0.1),
-
桶 1: [0.1, 0.2),
-
…
-
桶 9: [0.9, 1.0]。
你的 Q 表将会有 10 行用于观测,并且在训练过程中,你总是需要将像 0.92341 这样的观测映射到桶编号 9。
然而,你的离散化越细,Q 表就会变得越大,直到某个点你无法再处理它的内存。
Q 学习的另一种变体是尝试用神经网络替代 Q 表,将状态映射到多个输出,每个动作对应一个输出。我们称这种方法为深度 Q 学习。
图像由作者提供。
你可以在我的另一篇文章中阅读相关内容:
提升你的代理,赢得更困难的游戏!
towardsdatascience.com
希望你今天学到了一些新的、有趣的和有价值的东西。感谢阅读!
如果你有任何问题,请通过LinkedIn联系我!
如果你想更深入地了解算法的世界,可以试试我的新出版物《全方位算法》。我还在寻找作者!