Các mô hình máy biến áp là nền tảng của hệ thống AI. Có vô số sơ đồ về cấu trúc cốt lõi của “cách thức hoạt động của Transformers”.
Nhưng những sơ đồ này không cung cấp bất kỳ sự biểu diễn trực quan nào về khuôn khổ tính toán mô hình này. Khi các nhà nghiên cứu quan tâm đến cách thức hoạt động của Máy biến áp, việc nắm bắt được cơ chế hoạt động của nó một cách trực quan sẽ trở nên rất hữu ích.
Suy nghĩ như máy biến áp Bài viết này đề xuất một khung tính toán cấp máy biến áp, trực tiếp tính toán và mô phỏng các phép tính của Máy biến áp. Sử dụng ngôn ngữ lập trình RASP, mỗi chương trình được biên dịch thành một Transformer đặc biệt.
Trong blog này, tôi tái tạo một biến thể của RASP (RASPy) bằng Python. Ngôn ngữ gần giống với bản gốc, nhưng có thêm một vài thay đổi mà tôi nghĩ là thú vị. Thông qua công việc của những ngôn ngữ này, tác giả Gail Weiss đưa ra một cách đầy thử thách và thú vị để hiểu cách chúng hoạt động.
!pip cài đặt git+https://github.com/srush/RASPy
Trước khi nói về ngôn ngữ, chúng ta hãy xem một ví dụ về mã hóa với Transformers trông như thế nào. Đây là một số mã tính toán việc lật, tức là đảo ngược chuỗi đầu vào. Bản thân mã sử dụng hai lớp Transformer để áp dụng sự chú ý và tính toán toán học để đạt được kết quả này.
def flip(): length = (key(1) == query(1)).value(1) flip = (key(length - indexes - 1) == query(indices)).value(tokens) return flip flip()
Thư mục bài viết
- Phần thứ nhất: Transformers as Code
- Phần 2: Viết chương trình với Transformers
Mục tiêu của chúng tôi là xác định một dạng tính toán giúp giảm thiểu biểu thức của Transformers. Chúng tôi sẽ mô tả từng cấu trúc ngôn ngữ và đối tác của nó trong Transformers bằng cách tương tự. (Để biết thông số kỹ thuật ngôn ngữ chính thức, vui lòng xem liên kết tới toàn văn bài viết ở cuối bài viết này).
Đơn vị cốt lõi của ngôn ngữ này là thao tác trình tự biến đổi một chuỗi thành một chuỗi khác có cùng độ dài. Tôi sẽ gọi chúng là biến đổi sau.
đi vào
Trong Máy biến áp, lớp cơ sở là đầu vào chuyển tiếp cho mô hình. Đầu vào này thường chứa mã thông báo gốc và thông tin vị trí.
Trong mã, tính năng mã thông báo thể hiện phép biến đổi đơn giản nhất, trả về các mã thông báo được truyền qua mô hình.
các mã thông báo
Nếu muốn thay đổi đầu vào trong biến đổi, chúng ta sử dụng phương thức nhập để truyền giá trị.
token.input([5, 2, 4, 5, 2, 2])
Với tư cách là Transformers, chúng tôi không thể trực tiếp chấp nhận vị trí của các chuỗi này. Nhưng để mô phỏng việc nhúng vị trí, chúng ta có thể lấy chỉ mục của vị trí
chỉ số
sop = chỉ số sop.input("tạm biệt")
mạng chuyển tiếp
Sau khi đi qua lớp đầu vào, chúng ta đến lớp mạng tiếp liệu. Trong Transformer, bước này áp dụng các phép toán độc lập cho từng phần tử của chuỗi.
Trong mã, chúng tôi thể hiện bước này bằng cách tính toán các phép biến đổi. Một phép toán độc lập được thực hiện trên mỗi phần tử của chuỗi.
token == "l"
Kết quả là một biến đổi mới được tính là được xây dựng lại sau khi đầu vào mới được xây dựng lại
mô hình = mã thông báo * 2 - 1 mô hình.đầu vào([1, 2, 3, 5, 2])
Thao tác này có thể kết hợp nhiều Biến đổi. Ví dụ: lấy các mã thông báo và chỉ số ở trên làm ví dụ, danh mục Biến đổi có thể theo dõi nhiều thông tin phân đoạn.
mô hình = mã thông báo - 5 + chỉ số mô hình.input([1, 2, 3, 5, 2])
(mã thông báo == "l") | (chỉ số == 1)
Chúng tôi cung cấp một số hàm trợ giúp để giúp việc chuyển đổi văn bản trở nên dễ dàng hơn. Ví dụ: Where cung cấp cấu trúc tương tự như chức năng if.
trong đó((mã thông báo == "h") | (mã thông báo == "l"), mã thông báo, "q")
map cho phép chúng ta xác định các hoạt động của riêng mình, chẳng hạn như chuyển đổi một chuỗi thành int. (Người dùng nên thận trọng với các thao tác có thể được tính toán bằng mạng thần kinh đơn giản).
atoi = token.map(lambda x: ord(x) - ord('0')) atoi.input("31234")
Các hàm có thể dễ dàng mô tả các tầng của các phép biến đổi này. Ví dụ: đây là thao tác sử dụng Where và atoi và cộng 2.
def atoi(seq=tokens): return seq.map(lambda x: ord(x) - ord('0')) op = (atoi(trong đó(tokens == "-", "0", token)) + 2) op.input("02-13")
bộ lọc chú ý
Mọi thứ bắt đầu trở nên thú vị khi bạn bắt đầu áp dụng cơ chế chú ý. Điều này sẽ cho phép trao đổi thông tin giữa các phần tử khác nhau của chuỗi.
Chúng tôi bắt đầu bằng cách xác định các khái niệm về khóa và truy vấn có thể được tạo trực tiếp từ các phép biến đổi ở trên. Ví dụ: nếu chúng ta muốn xác định một khóa, chúng ta gọi nó là key .
chìa khóa (mã thông báo)
Điều tương tự cũng xảy ra với truy vấn.
truy vấn(mã thông báo)
Vô hướng có thể được sử dụng làm khóa hoặc truy vấn và chúng được phát theo độ dài của chuỗi cơ bản.
truy vấn(1)
Chúng tôi đã tạo các bộ lọc để áp dụng các thao tác giữa khóa và truy vấn. Điều này tương ứng với một ma trận nhị phân cho biết khóa nào cần tập trung vào cho mỗi truy vấn. Không giống như Transformers, ma trận chú ý này không thêm trọng số.
eq = (key(tokens) == query(tokens)) eq
Một số ví dụ:
- Vị trí khớp của bộ chọn được bù bằng 1:
offset = (key(chỉ số) == query(chỉ số - 1)) offset
- Bộ chọn có khóa sớm hơn truy vấn:
trước = key(chỉ số) < query(chỉ số) trước
- Bộ chọn có khóa muộn hơn truy vấn:
sau = key(chỉ số) > query(chỉ số) sau
Bộ chọn có thể được kết hợp thông qua các phép toán Boolean. Ví dụ: bộ chọn này kết hợp before và eq và chúng tôi hiển thị điều này bằng cách đưa cặp khóa và giá trị vào ma trận.
trước & eq
Sử dụng cơ chế chú ý
Với bộ chọn chú ý, chúng tôi có thể cung cấp giá trị chuỗi cho các hoạt động tổng hợp. Chúng tôi thực hiện tổng hợp bằng cách tích lũy các giá trị thực được bộ chọn chọn.
(Lưu ý: Trong bài viết gốc, họ sử dụng phép toán tổng hợp trung bình và hiển thị một cấu trúc thông minh trong đó tổng hợp trung bình có thể biểu thị phép tính tổng. RASPy sử dụng tích lũy theo mặc định để làm cho nó đơn giản và tránh phân mảnh. Trên thực tế, Điều này có nghĩa là thô lỗ có thể đánh giá thấp số lượng lớp cần thiết (một mô hình dựa trên giá trị trung bình có thể yêu cầu gấp đôi số lượng này).
Lưu ý rằng các hoạt động tổng hợp cho phép chúng ta tính toán các đặc điểm như biểu đồ.
(key(tokens) == query(tokens)).giá trị(1)
Về mặt trực quan, chúng tôi tuân theo cấu trúc sơ đồ với Truy vấn ở bên trái, Khóa ở trên cùng, Giá trị ở dưới cùng và đầu ra ở bên phải.
Một số hoạt động của cơ chế chú ý thậm chí không yêu cầu mã thông báo đầu vào. Ví dụ: để tính độ dài chuỗi, chúng tôi tạo bộ lọc chú ý " select all " và gán cho nó một giá trị.
chiều dài = (key(1) == query(1)).giá trị(1) chiều dài = chiều dài.tên("chiều dài") chiều dài
Có nhiều ví dụ phức tạp hơn ở đây, được hiển thị từng bước bên dưới. (Nó giống như thực hiện một cuộc phỏng vấn).
Chúng ta muốn tính tổng các giá trị liền kề của một dãy thì đầu tiên chúng ta cắt ngắn về phía trước
WINDOW=3 s1 = (key(chỉ số) >= query(chỉ số - WINDOW + 1)) s1
Sau đó chúng tôi cắt ngắn về phía sau
s2 = (key(chỉ số) <= query(chỉ số)) s2
Hai cái giao nhau
sel = s1 và s2 sel
tập hợp cuối cùng
sum2 = sel.value(mã thông báo) sum2.input([1,3,2,2,2])
Đây là một ví dụ có thể tính tổng tích lũy. Chúng tôi giới thiệu khả năng đặt tên cho biến đổi để giúp bạn gỡ lỗi.
def cumsum(seq=tokens): x = (trước | (key(indices) == query(indices))).value(seq) trả về x.name("cumsum") cumsum().input([3, 1, -2, 3, 1])
lớp
Ngôn ngữ hỗ trợ biên dịch các phép biến đổi phức tạp hơn. Ông cũng vận hành lớp điện toán bằng cách theo dõi từng hoạt động.
Dưới đây là ví dụ về phép biến đổi 2 lớp, lớp đầu tiên tương ứng với độ dài được tính toán và lớp thứ hai tương ứng với tổng tích lũy.
x = cumsum(chiều dài - chỉ số) x.input([3, 2, 3, 5])
Sử dụng thư viện này, chúng ta có thể viết một tác vụ phức tạp. Gail Weiss đã đưa ra cho tôi một câu hỏi cực kỳ khó khăn để phá vỡ bước này: Chúng ta có thể tải một Transformer có cộng các số có độ dài tùy ý không?
Ví dụ: Cho một chuỗi "19492+23919", chúng ta có thể tải kết quả đầu ra chính xác không?
Nếu bạn muốn tự mình thử, chúng tôi đã cung cấp một phiên bản mà bạn có thể tự mình thử.
Thử thách 1: Chọn một chỉ mục nhất định
Tải một chuỗi trong đó tất cả các phần tử ở chỉ mục i đều có giá trị.
def index(i, seq=tokens): x = (key(indices) == query(i)).value(seq) return x.name("index") index(1)
Thử thách 2: Chuyển đổi
Di chuyển tất cả các thẻ sang bên phải thông qua vị trí i.
def shift(i=1, default="_", seq=tokens): x = (key(indices) == query(indices-i)).value(seq, default) return x.name("shift") shift(2)
Thử thách 3: Giảm thiểu
Tính giá trị nhỏ nhất của dãy (Bước này trở nên khó khăn, phiên bản của chúng tôi sử dụng cơ chế chú ý 2 lớp).
def minimum(seq=tokens): sel1 = trước & (key(seq) == query(seq)) sel2 = key(seq) < query(seq) ít hơn = (sel1 | sel2).value(1) x = (key(less) == query(0)).value(seq) trả về x.name("min") minimum()([5,3,2,5,2])
Thử thách thứ tư: Chỉ số đầu tiên
Tính chỉ số đầu tiên (cấp 2) với token q.
def first(q, seq=tokens): trả về giá trị tối thiểu(trong đó(seq == q, chỉ số, 99)) first("l")
Thử thách 5: Căn phải
Căn phải một chuỗi đệm. Ví dụ: " ralign().inputs('xyz___') ='—xyz' " (Cấp 2).
def ralign(mặc định="-", sop=mã thông báo): c = (khóa(sop) == truy vấn("_")).giá trị(1) x = (khóa(chỉ số + c) == truy vấn(chỉ số)).giá trị(sop, mặc định) trả về x.tên("ralign") ralign()("xyz__")
Thử thách 6: Tách biệt
Chia chuỗi thành hai phần ở mã thông báo "v" và căn phải (2 lớp)
def split(v, i, sop=tokens): mid = (key(sop) == query(v)).value(indices) nếu i == 0: x = ralign("0", trong đó(indices < mid, sop, "_")) trả về x nếu không: x = trong đó(indices > mid, sop, "0") trả về x split("+", 1)("xyz+zyr")
chia("+", 0)("xyz+zyr")
Thử thách 7: Trượt
Thay thế mã thông báo đặc biệt "<" bằng giá trị "<" gần nhất (Cấp 2)
def slide(match, seq=tokens): x = cumsum(match) y = ((key(x) == query(x + 1)) & (key(match) == query(True))).value(seq) seq = where(match, seq, y) return seq.name("slide") slide(tokens != "<").input("xxxh<<
Thử thách 8: Tăng
Bạn muốn thực hiện phép cộng hai số. Dưới đây là các bước.
thêm().đầu vào("683+345")
- Chia thành hai phần. Chuyển sang nhựa. tham gia vào
“683+345” => [0, 0, 0, 9, 12, 8] 。
- Tính toán các điều khoản về tính di động. Ba khả năng: 1 được mang, 0 không được mang, < có thể được mang.
[0, 0, 0, 9, 12, 8] => “00<100” 。
- hệ số mang trượt
“00<100” => 001100" 。
- Bổ sung đầy đủ
Đây đều là 1 dòng mã. Hệ thống hoàn chỉnh là 6 cơ chế chú ý. (Mặc dù Gail nói rằng việc này có thể hoàn thành trong 5 phút nếu bạn đủ cẩn thận!).
def add(sop=tokens): # 0) Phân tích cú pháp và thêm x = atoi(split("+", 0, sop)) + atoi(split("+", 1, sop)) # 1) Kiểm tra các số nhớ carry = shift(-1, "0", where(x > 9, "1", where(x == 9, "<", "0"))) # 2) Song song, các số trượt mang đến cột của chúng delivers = atoi(slide(carry != "<", carry)) # 3) Thêm vào các số nhớ. return (x + delivers) % 10 add()("683+345")
683 + 345
1028
Hoàn thành một cách hoàn hảo! .
Tài liệu tham khảo & liên kết trong văn bản:
- Nếu bạn quan tâm đến chủ đề này và muốn tìm hiểu thêm, hãy xem bài viết: Suy nghĩ như Transformers
- và tìm hiểu thêm về ngôn ngữ RASP
- Nếu bạn quan tâm đến "Ngôn ngữ chính thức và Mạng lưới thần kinh" (FLaNN) hoặc biết ai đó quan tâm, vui lòng mời họ tham gia cộng đồng trực tuyến của chúng tôi!
- Bài đăng blog này chứa nội dung từ thư viện, sổ ghi chép và bài đăng blog.
- Bài đăng trên blog này được đồng tác giả bởi Sasha Rush và Gail Weiss
Văn bản gốc tiếng Anh: Suy nghĩ như Transformers.
Người dịch: đổi mới64 (Lý Dương).
Cuối cùng, bài viết về việc hiểu cách "suy nghĩ" của Transformers kết thúc tại đây. Nếu bạn muốn biết thêm về cách hiểu về cách "suy nghĩ" của Transformers, vui lòng tìm kiếm các bài viết của CFSDN hoặc tiếp tục duyệt các bài viết liên quan. .
Tôi là một lập trình viên xuất sắc, rất giỏi!